Optional Partials in DustJS - node.js

Is it possible to have optional partials in Dust? Let's say I define a layout like this:
<div>
{>"{module}"/>
</div>
I have been defining the module in Express's res.locals object. However, what if I forgot to define a module, or I actually want a default module for use when I do not define one? Worse, what if I did define one but it's the incorrect module, meaning there's no template file in the view folder with the name of that module? I don't want the user to see the ugly error message, which seems to be something like:
Error: ENOENT, open 'view_path\{module}.dust'
where {module} is the name of the module, or an empty string if I did not specify a res.locals.module. Should I resort to try-catch blocks (not even sure how to do them in dust), or is there a method for making templates optional, rather than required? NOTE: The template would be optional, but the module variable would (usually) still be a string. It seems that dust sections are optional, meaning if the exact key is not available, the section is simply not included. For example, say I have the context {friends: [{name: "Harry"}, {name: "Ron"}, {name: "Hermione"}]}. If I define the section:
{#friends}
{name} is {age} years old.
{/friends}
it will output
Harry is years old.
Ron is years old.
Hermione is years old.
Notice there are 2 spaces between is and years in each case, where the age would be if we defined any ages. If this functionality is included, how is it that neither the original creators of dust nor LinkedIn thought to not require partials? How do I specify optional partials in dust?

You could use a conditional like so,
<div>
{?module}
{>"{module}"/>
{:else}
{>"{YourDefaultPartial}"}
{/module}
</div>
This will work when {module} is either undefined or an empty string or a falsey value.
But, in case its a string pointing to a partial that does not exist, it'll throw an error. Hope this helps.

Related

Jest - how to test if a component does not exist?

How do I check if a component is not present, i.e. that a specific component has not been rendered?
.contains receives a React Node or array of Nodes as an argument. Instead, use .find:
expect(wrapper.find('selector').exists()).toBeTruthy()
You can use enzymes contains to check if the component was rendered:
expect(component.contains(<ComponentName />)).toBe(false)
If you're using react-testing-library (I know the OP wasn't but I found this question via web search) then this will work:
expect(component.queryByText("Text I care about")).not.toBeInTheDocument();
You can query by Text, Role, and several others. See docs for more info.
Note: queryBy* will return null if it is not found. If you use getBy* then it will error out for elements not found.
Providing a slightly updated answer based on the documentation for enzyme-matchers's toExist. This will require you to install the enzyme-matchers package.
function Fixture() {
return (
<div>
<span className="foo" />
<span className="bar baz" />
</div>
);
}
const wrapper = mount(<Fixture />); // mount/render/shallow when applicable
expect(wrapper.find('span')).toExist();
expect(wrapper.find('ul')).not.toExist();
.contains does not expect a selector, unlike find. You can look at the length attribute of the ShallowWrapper
expect(wrapper.find('...')).toHaveLength(0)
I found I needed to use this syntax with Enzyme and Jest to test if a Connected Component existed in the rendered output.
We use Jest and Enzyme, and I've found the only good test is to import the sub-component and test this way:
expect(component.find(SubComponent).length).toEqual(0); // or (1) for exists, obvs
I tried all the other answers and none worked reliably.
If you are using react-testing-library, then this also will work:
expect(component.queryByText("Text I care about").toBeNull());
expect(within(component).queryByText("Text I care about")).toBeNull();
Note: In my case, I needed to use queryBy* because it doesn´t error out when the text element (that contains the text: Text I care about) does not exist. Therefore, I could evaluate whether there is an existence of a text component or not.

Why is a global `name` variable declared in typescript and can I avoid using it?

A friend refactored some code and moved the definition of a variable called name from the function's top-level scope into a then's body. This variable was used in a subsequent then which caused a ReferenceError since name was not in scope.
We couldn't understand how the code passed compilation until we saw that typescript/lib.d.ts has the following deceleration:
declare const name: never;
Long story short, I have two questions.
Why is name (as well as length and many other globals) added by default to typescript?
From the surrounding code this seems meant for projects intended to run in a browser, we're a node.js project. Can we opt out from having these declarations added for us?
This seems to be a very old browser behaviour. Referring to the MDN both name and length are properties of the window object.
https://developer.mozilla.org/en-US/docs/Web/API/Window/name
https://developer.mozilla.org/en-US/docs/Web/API/Window/length
In order to get rid of all the DOM-specific declarations, you can set the lib property in your tsconfig accordingly. You cann see all options on this page. Take a look at the --lib flag.
An option to tell TypeScript your code runs on Node.JS would be nice. But it seems not yet implemented: https://github.com/Microsoft/TypeScript/issues/9466

Interpolating values in HTML attributes - Pug (Jade)

I am trying to construct an anchor tag with a dynamic href attribute in Jade.
I did go through the docs and some SO questions but they didn't help me. This is what I tried.
a(href= "http://www.imdb.com/title/#{movie.imdb_id}") Know more
But it renders
http://www.imdb.com/title/#{movie.imdb_id}
rather than
http://www.imdb.com/title/tt1234567
However this works
a(href= "http://www.imdb.com/title/" + movie.imdb_id) Know more
and this too.
- var url = "http://www.imdb.com/title/" + movie.imdb_id;
a(href= url) Know more
What's wrong with the first version?
Interpolation is only available in text.
You need to use JS string concatenation for attributes:
a(href="http://www.imdb.com/title/" + movie.imdb_id) Know more
If you JavaScript runtime supports ES2015 template string, you can also use them (notice the backticks):
a(href=`http://www.imdb.com/title/${movie.imdb_id}`) Know more
Reference
the pug variable declaration doesnt work in this case using #{...}
the right syntax goes this way,
a(attributes) Know more
a(href="http://www.imdb.com/title/"+ movie.imdb_id) Know more
the attributes is an expression so it renders correcly, or you could use ES5 template literals with back quotes to render the variable along side the text which becomes
a(href=`http://www.imdb.com/title/${movie.imdb_id}`) Know more
note that when using back quotes with template literals your variable expression are enclosed in parenthesis and a leading $ sign, that is ${..expression..}
When you quote it simply tells pug "this is a string". That's basic JS. Interpolation works with #{'#{interpolation}'} too! is an example which renders "Interpolation works with #{interpolation} too!"
I don't have any knowledge about pug(jade)
But my guess is "a(your code)" is already a signal to pug(jade) that it is in the controller's scope already.. and "{variable}" is also an indicator that you are accessing controller's scope. so
a(href= "http://www.imdb.com/title/#{movie.imdb_id}") Know more
for "{}" inside a() is no longer an indicator that your are trying to access controller's scope because you're already in the controller's scope.. so "{}" inside a() is just a string, {movie.imdb_id} is part of the link string.
So in order for the framework to identity that movie.imdb_id is a variable, you should separate it from the actual string.
NOTE: This is just a guess..I'm using angular

Why in Mojito, renaming controller.server.js to controller.server-foo.js will have no effect?

In Mojito on top of Node.js, I followed the example on http://developer.yahoo.com/cocktails/mojito/docs/quickstart/
What I did was renaming controller.server.js to controller.server-foo.js, and created a new file controller.server.js to show "Hello World".
But when mojito is started, the old file controller.server-foo.js is being used and so the "Hello World" is not printed. How come Mojito will use the old file?
(I also tried renaming controller.server-foo.js to foo-controller.server.js and now the "Hello World" is printed, but why is controller.server-foo.js used?)
I found out that historically, the "affinity" of the controller can be two parts. The first part is common, server, or client, and the second part is optional, and it might be tests, or other words, so use other names such as controller-not-used-server.js to disable it.
#Charles, there are 3 registration processes in mojito (yes, it is confusing at first):
Affinity (server, client or common).
YUI.add when creating yui modules (controllers, models, binders, etc)
and the less common which is the registration by name (which includes soemthing that we call selectors)
In your case, by having two controllers, one of them with a custom selector named "foo", you are effectible putting in use the 3 registration at once. Here is what happen internally:
A controller is always detonated as "controller" filename from the mojit folder, which is part of the registration by name, and since you have "foo" selector for one of the controller, your mojit will have to modes, "default" and "foo". Which one of them will be use? depends on the application.json, where you can have some conditions to set the value of "selector", which by default is empty. If you set the value of selector to "foo" when, for example, device is an iphone, then that controller will be used when that condition matches.
Then the YUI.add plays an important role, it is the way we can identify which controller should be used, and its only requirement is that NO OTHER MODULE in the app can have the same YUI Module name, which means that your controllers can't be named the same when registering them thru YUI.add. And I'm sure this is what is happening in your case. If they both have the same name under YUI.add() one will always override the other, and you should probably see that in the logs as a warning, if not, feel free to open an issue thru github.
To summarize:
The names used when registering YUI modules have to be unique, in your case, you can use: YUI.add('MyMojit', function(){}) and YUI.add('MyMojitFoo', function(){}), for each controller.
Use the selector (e.g.: controller.server-mobile.js) to select which YUI module should be used for a particular request by setting selector to the proper value in application.json.

global variables not parsing as parameters

I am having troubles with global variables not parsing when passed as parameters.
{exp:channel:entries
disable="categories|category_fields|member_data|pagination|trackbacks"
dynamic="no"
entry_id="{structure:child_ids_for:21}"
}
(0.012500 / 3.36MB) Tag: {exp:channel:entries disable="categories|category_fields|member_data|pagination|trackbacks" dynamic="no" entry_id="{structure:child_ids_for:21}" }
The same result is produced with and without parse="inward"
However this works fine and grabs the data I need
{exp:channel:entries
disable="categories|category_fields|member_data|pagination|trackbacks"
dynamic="no"
entry_id="{exp:query sql='SELECT exp_structure.entry_id,
exp_structure.parent_id,
exp_structure.lft
FROM exp_structure
WHERE parent_id = 21
ORDER BY exp_structure.lft ASC'}{entry_id}|{/exp:query}"
parse="inward"
}
But, then if I add in a global variable author_id="{logged_in_member_id}" it fails to work, if I hard code that value as 1 then it functions.
Any thoughts as to what could be happening here?
You can avoid the overhead of embeds by using Stash for this sort of thing. It has the insanely useful ability to let you explicitly state your preferred parse order for different chunks of code. In this case the first thing you'd do is store the two variables via {exp:stash:set}, then you can retrieve them in the second chunk of code via {exp:stash:get}. The magic bit is the parse priority tag; because the first item has a priority of 10 it will be executed first, which ensures the vars are available for use as channel entries parameters in the second {exp:stash:parse} tag.
{exp:stash:parse priority="10" process="end"}
{exp:stash:set}
{stash:structure_ids}{structure:sibling_ids}{/stash:structure_ids}
{stash:logged_in_member}{logged_in_member_id}{/stash:logged_in_member}
{/exp:stash:set}
{/exp:stash:parse}
{exp:stash:parse priority="20" process="end"}
{exp:channel:entries
disable="categories|category_fields|member_data|pagination|trackbacks"
dynamic="no"
entry_id="{exp:stash:get name='structure_ids'}"
author_id="{exp:stash:get name='logged_in_member'}"
parse="inward"
}
...
{/exp:channel:entries}
{/exp:stash:parse}
I can't speak for Structure's global variables, but {logged_in_member_id} is a late-parsed global variable, meaning you can't use it in a module tag parameter. I can only only assume that the same goes for the Structure variables.
You can use the CURRENT_USER constant in the author_id parameter though (docs).
Unfortunately, the solution for your {structure:child_ids_for:21} issue is to pass that as an embed variable, and put your Channel Entries loop in an embed. (I say unfortunately because embeds do incur some overhead.)
One note: parse="inward" has no effect on module tag pairs - they always parse inward. It only affects plugin tag pairs.

Resources