-
Notifications
You must be signed in to change notification settings - Fork 479
Reusable UI Dust modules
Most often we have markup that needs to be re-used across different pages and use cases. Dust already provides constructs to write partials and inherit and extend templates. But wrting reusable, local or globally scoped partials/components, with support for branching logic, default/ fallback values, inline documentation for the expected params is not always easy. We have been exploring a few options on how to use one ore more of these inbuilt constructs effectively to support the common use cases that are shared across different pages/apps at LinkedIn.
Some solutions we came up with,
-
Partials with {>partial/} syntax for shared markup and default/fallback values in the markup, for param declaration
-
Inline params for aliasing values and creating default values
-
Blocks and Inline partials with {+/} and {</} syntax for placeholder or global values
-
Dynamic partials for selecting one of the many flavors of reusable partial at run-time
-
Combination of > partial and @partial, @param , Inline partials/blocks (+ and <) for placeholders, defaults and global values
-
Using the default Dust override context {>partial:context/} syntax with a partial for accepting any json object
-
Extending the > partial syntax to take params as part of the {>partial a=b/} and @param helpers for defining global and default/fallback values
Helpers with @ syntax
- client rendered, inline to the template, can be rendered on server with JS engine. Since we expect to server-side render the same template, we strictly enforce no DOM manipulation in the helpers
- anything possible in Javascript can be done in a helper tag
- they are not limited to being lambda's in JSON, hence cacheable on CDN as any other Javascript
- context modification on the fly to append more info to the given JSON
Partials with {>partial/} syntax
- best bet for shared/ common snippets of markup, but writing re-usable partials is not easy since the including template needs to adhere to the exact JSON contract in the partial, a clean way to pass params, default values from including template, or defining params and fallback values in partials needs some thinking.
- the markup is inline and thus supports readability, rather than Javascript code generating markup in a @helper
-
- all the benefits of helper, since every partial is pre-compiled to Javascript and should be on par with a helper in term of performance
- partials can themselves use @ helpers inline!! For performance reasons, we also strictly enforce no inline script blocks in the partial.
A simple use case is displaying a degree icon and a placeholder text on the LinkedIn profile based on the connection strength (a.k.a distance, that can have 0,1,2,100 values based on a 1st, 2nd and 3rd degree or not be connected at all.
Solution 1 : Create a partial named "degree_icon" and then invoke this partial from the parent template within a block, say a list of people
{#people distance=distance name="Don Draper"}
{>"shared/degree_icon"/}
{/people}
- default Dust constructs are used, but not generic, it is not always possible to have a block like #people. What if I need to render the degree icon by itself? How do I pass the params to the partial?
Solution 2 : Use-case specific helpers, for instance, @degree_icon for our example
dust.helpers.degree_icon = function(chunk, context, bodies, params){
var distance = params.distance || 0:
switch( parseInt(distance) ){
// do something
default:
}
}
- Works, it can define the defaults, do some branching logic if required, create the markup on the fly ( kind of ugly ). What if we need an @i18n helper to resolve strings while displaying the degree icon text? One way is to wrap one helper within the other. Another way is to inject i18n strings into the context helper before rendering the template. It is still not elegant.
See below ...
{#people}
{@i18n key="label" text="Degree"}
{@i18n key="distanceStr" text="you have {:num} connections with {name}"}
{@degree_icon distance=profile.distance}
// access to keys label and distanceStr
{/degree_icon}
{/i18n}
{/people}
Solution 3: Generic @partial helper that can render any partial such as the degree_icon partial and take expected and default values, similar to Solution 1, but does not rely on a custom # in the JSON.
{#people}
{@partial distance=people.distance name="Don Draper"}
{>"shared/degree_icon"/}
{/partial}
{#people}
-
One more aspect we have not touched upon, do we want a local scope in the @partial block or allow the degreee_icon to access any global scope. With @partial it is easy to use a scope="local" param and then use that info in the @partial helper to either use the existing context while rendering the body chunk or create a new context using dust.makebase. Default values can be defined in the partial, but to be generic enough we need some convention. One way is for every param we need to pass in a default value, like the below
{#people} {@partial distance=people.distance def_distance="2" name="Don Draper" def_name="None"} {>"shared/degree_icon"/} {/partial} {#people}
Solution 4 : Use the @partial generic helper, but the onus of defining the default params and expected params is on the partial itself and not on the including template. @param helper and use it inside the partials, so that the invoking template is no longer concerned about the expected params and its default values
// degree_icon partial can define one or more of these for all the expected params.
// It can create fallback values is need be
{@param name="distance" default="1"/} // inserts into the current context
{@i18n key="label" text="Degree"/} // insert it into the current context
{@param name="distanceStr" default="distance"/}
Solution 5 : Extend the Dust grammar to support params in the default partial invocation syntax instead of using the @partial helper to pass args/params. This is probably the most clean one. Still use the @param helper to define the defaults and expected values inside the partial template. Since every partial invoked with the > uses the global context, if at all we need only the local context, use >> (Proposed enhancement) to distinguish between local scoped partial and global scoped partial.
// uses global context
{>"shared/degree_icon" distance=people.distance name="Don Draper"/}
// create a local context with the inline params
{>>"shared/degree_icon" distance=people.distance name="Don Draper"/}
rragan: Defaulting should belong to the partial/reuseable component -- not to the including template. We have tried a simple helper that works more or less adequately for modest-sized partials
{@setDefault showHeader="true" showSecondaryNav="false" showFooter="true"} body of partial code {/setDefault}
By pushing the extra params on the context stack they provide values for missing params and leave supplied values alone. This is all I want from a default mechanism. I'd rather avoid the wrapping required vs. just setting a value that is transparently set and used. Can you actually merge values into the current context? If you can, I'm worried about side effects this might cause as the partial changes things that others may also be consuming.
I favor extending the compiler syntax to allow params. This would make partials consistent with other parts of the language.