Skip to content

Commit

Permalink
Removed the layout template feature, replacing it by an alternative
Browse files Browse the repository at this point in the history
approach using wrapped arrays. (See new sample 05_arrays-plus-headers-and-footers.html).
Modified the behavior when you render a template with no data, so that now
it will render once. You can call {{for tmpl='myTmpl'/}} or $.render.myTmpl(),
and the template will be rendered once, with current data item 'undefined'.
See many new unit tests which indicate the details of this behavior.
See also discussion in issues 57 and 118.
  • Loading branch information
BorisMoore committed May 13, 2012
1 parent 83a7d9a commit fc7a28b
Show file tree
Hide file tree
Showing 14 changed files with 123 additions and 129 deletions.
2 changes: 1 addition & 1 deletion demos/demos.html
Expand Up @@ -23,7 +23,6 @@ <h3>JsRender - step-by-step samples</h3>
<li><a href="step-by-step/08_custom-tags.html">Custom tags</a></li> <li><a href="step-by-step/08_custom-tags.html">Custom tags</a></li>
<li><a href="step-by-step/09_helper-functions.html">'Helper' functions</a></li> <li><a href="step-by-step/09_helper-functions.html">'Helper' functions</a></li>
<li><a href="step-by-step/10_comparison-tests.html">Comparison tests</a></li> <li><a href="step-by-step/10_comparison-tests.html">Comparison tests</a></li>
<li><a href="step-by-step/11_layout-templates.html">Layout templates</a></li>
<li><a href="step-by-step/20_without-jquery.html">JsRender without jQuery</a></li> <li><a href="step-by-step/20_without-jquery.html">JsRender without jQuery</a></li>
</ul> </ul>


Expand All @@ -40,6 +39,7 @@ <h3>JsRender - scenario examples</h3>
<li><a href="scenarios/02_separators-scenario.html">'Separators' scenario</a></li> <li><a href="scenarios/02_separators-scenario.html">'Separators' scenario</a></li>
<li><a href="scenarios/03_iterating-through-fields-scenario.html">'Iterating through fields' scenario</a></li> <li><a href="scenarios/03_iterating-through-fields-scenario.html">'Iterating through fields' scenario</a></li>
<li><a href="scenarios/04_assigning-variables-scenario.html">'Assigning variables' scenario</a></li> <li><a href="scenarios/04_assigning-variables-scenario.html">'Assigning variables' scenario</a></li>
<li><a href="scenarios/05_arrays-plus-headers-and-footers.html">Layout templates: headers, footers and rendered items</a></li>
</ul> </ul>
</body> </body>
</html> </html>
8 changes: 4 additions & 4 deletions demos/scenarios/01_default-values-scenario.html
Expand Up @@ -36,7 +36,7 @@ <h3>Example Scenario: providing default values for data.</h3>
{{get languages defaultValue="No languages!"/}} {{get languages defaultValue="No languages!"/}}


$.views.tags({ $.views.tags({
get: function( value ){ get: function( value ) {
return value || this.props.defaultValue; return value || this.props.defaultValue;
} }
}); });
Expand All @@ -55,7 +55,7 @@ <h3>Example Scenario: providing default values for data.</h3>
{{yesNo languages yes="Alternate languages available:" no="No alternate languages"/}} {{yesNo languages yes="Alternate languages available:" no="No alternate languages"/}}


$.views.tags({ $.views.tags({
yesNo: function( value ){ yesNo: function( value ) {
return value ? this.props.yes : this.props.no; return value ? this.props.yes : this.props.no;
} }
}); });
Expand Down Expand Up @@ -103,11 +103,11 @@ <h3>Example Scenario: providing default values for data.</h3>
<script type="text/javascript"> <script type="text/javascript">


$.views.tags({ $.views.tags({
get: function( value ){ get: function( value ) {
return value || this.props.defaultValue; return value || this.props.defaultValue;
}, },


yesNo: function( value ){ yesNo: function( value ) {
return value ? this.props.yes : this.props.no; return value ? this.props.yes : this.props.no;
} }
}); });
Expand Down
Expand Up @@ -10,34 +10,33 @@
<body> <body>
<a href="../demos.html">JsRender Demos</a><br /> <a href="../demos.html">JsRender Demos</a><br />


<h3>Using layout templates to render arrays along with headers or footers.</h3> <h3>To pass an array to a "layout template" - which includes headers or footers,<br />
as well as the rendered items - wrap the array in an array...</h3>


<div class="subhead">Top-level layout:</div> <div class="subhead">Top-level layout:</div>
<pre> <pre>
$.templates( "moviesLayout", {
markup: "#movieTemplate",
layout: true
});

$( "#movieList" ).html( $( "#movieList" ).html(
$.render.moviesLayout( movies ) // Wrap movies array in an array
$("#movieTemplate").render( [movies] )
); );


header Template:
{{for #data}}
item header
{{/for}} {{for #data}}
footer item
{{/for}}
footer
</pre> </pre>


<div class="subhead">Nested layout:</div> <div class="subhead">Nested layout:</div>
<pre> <pre>
{{for languages layout=true}} {{for [languages]}}
header header
{{for #data}} {{for #data}}
item item
{{/for}} {{/for}}
footer footer
{{/for}} {{/for}}
</pre> </pre>


Expand All @@ -53,17 +52,16 @@ <h3>Using layout templates to render arrays along with headers or footers.</h3>
<tr> <tr>
<td>{{:title}}</td> <td>{{:title}}</td>
<td> <td>
{{for languages layout=true}} {{for [languages]}}

This comment has been minimized.

Copy link
@Thinkscape

Thinkscape May 14, 2012

Why is it [languages] as opposed to just languages ? Are those brackets required or optional? If required, it's unnecessary syntax detail to remember each time one uses for. I supposed it's there to prevent from traversing objects (non-arrays).

My suggestion for that is to simplify it even more by introducing more explicit include tag.

  1. Leave {{for}} a dedicated tag for iterating over arrays/collections. This means that primary usage scenario is always {{for someArray}} something-to-iterate-through {{/for}.
  2. Second scenario for {{for}} is iterating with a named template {{for someArray tmpl=templateName}}.
  3. Add the simplest templating tag on the planet: {{include templateName/}}. It does not iterate but simply includes a single instance of other template (either named or # dom element based).
  4. ....
  5. profit! - Confusion gone ;))

This comment has been minimized.

Copy link
@BorisMoore

BorisMoore May 15, 2012

Author Owner

With that design, what would you use in the above sample? What we are doing is moving the current data context to the languages array, but without iterating. It's a similar problem to jQuery if you want to add jQuery data to an array. You have to write $([myArray]).data(name, value), because jQuery is smart about arrays and will iterate by default. So $(myArray).data(name, value) will set the value on each item of the array, not on the array itself.

But {{include}} won't help us here.

In your suggestion for {{include template/}} the data context of the included template would be the same as the calling context (just like currently is the case with {{if}} and {{else}} tags. With {{for}} you are moving to a new context, {{for someField}} will move to the field as context. So the equivalent of your {{include template/}} with current code would be {{for #data tmpl=template/}} - which deliberately stays on the current context.

We could simplify the above scenario with a new tag by saying that {{for someField}} is only for iteration (and moves the context to each item), so that if someField is not an array, {{for someField}} will output nothing - i.e. the empty string. Then we could add a {{with }} tag, where {{with someField}} will render just once and move the context to someField.

With that design, we could use {{with languages}}, in the above sample, and we could also do the equivalent of your {{include}} by writing {{with #data tmpl=template/}}.

However, one reason I didn't go for the idea of having two tags, {[for}} and {{with}} which render content against data, but which specifically work with arrays and simple objects respectively is that by having just one tag, it becomes equivalent to the render method. Call myTemplate.render(someData) to render a template against any kind of data. It will return the rendered template, but in the case that the data is an array it will render against each item and concatenate the result. Now if you want to do exactly the same, but nested within the output of another template, you call {{for someData tmpl=myTemplate}}. So now if we had two tags, {{for myArray tmpl=myTemplate/}} and {{with myObject tmpl=myTemplate/}} we would lose that natural correspondance with the render method. (Unless, that is, we had two render methods, also: template.renderArray(myArray) and template.renderObject(myObject)`. But in that case you would need to say that if you pass an object to renderArray it simply returns the empty string, and if you pass an array to renderObject it also returns an empty string, so things get a bit complex).

I prefer just one render method and just on {{for}} tag.

This comment has been minimized.

Copy link
@Thinkscape

Thinkscape May 22, 2012

Sorry for delay.

You've divided the subject into two aspects: iteration and context.

To reiterate:

  1. {{for dataSet tmpl=template}}
    • Iterates over dataSet, a set of values (i.e. #data or an array inside data)
    • Changes context to current item in the iteration loop.
    • Each item is processed with template
  2. {{include tmpl=template}}
    • Does not iterate.
    • Does not change context.
    • template is parsed once and appended.
  3. {{include object tmpl=template}}
    • Does not iterate.
    • Changes context to object
    • template is parsed once and appended.
  4. {{with}} === {{include}}

Main arguments: - context is important, but standards are important too. - `{{include}}` is implemented in each and every templating language out there - include-with-context-change is implemented in some templating engines - `with` smells like VisualBasic :-) `include` is the most popular tag name across templating languages.

This comment has been minimized.

Copy link
@BorisMoore

BorisMoore Sep 5, 2014

Author Owner

@Thinkscape
Sorry, I forgot to get back to this thread.
The {{include}} tag which you had strongly advocated is now in JsRender and the behavior is just as you describe above. In addition, if the current context is an array, then {{for}}...{{/for}} or {{for tmpl=template/}} will iterate against that array - semantically equivalent to {{for #data... where #data is an array.
See also #98 (comment)

<div> <div>
{{if #data}} {{if #data}}
<strong>{{:length}}</strong> languages available: <strong>{{:length}}</strong> languages available:
{{for #data}}
<em>{{:name}}</em>
{{/for}}
{{else}} {{else}}
Original version only Original version only
{{/if}} {{/if}}

{{for #data}}
<em>{{:name}}</em>
{{/for}}
</div> </div>
<div> <div>
{{if #data}} {{if #data}}
Expand Down Expand Up @@ -101,14 +99,9 @@ <h3>Using layout templates to render arrays along with headers or footers.</h3>
] ]
} }
]; ];

$.templates( "moviesLayout", {
markup: "#movieTemplate",
layout: true
});

$( "#movieList" ).html( $( "#movieList" ).html(
$.render.moviesLayout( movies ) // Wrap movies array in an array
$( "#movieTemplate" ).render( [movies] )
); );
</script> </script>


Expand Down
4 changes: 2 additions & 2 deletions demos/step-by-step/08_custom-tags.html
Expand Up @@ -19,7 +19,7 @@ <h3>Custom tags</h3>


// Tag to reverse-sort an array // Tag to reverse-sort an array


sort: function( array ){ sort: function( array ) {
var ret = ""; var ret = "";
if ( this.props.reverse ) { if ( this.props.reverse ) {
// Render in reverse order // Render in reverse order
Expand Down Expand Up @@ -67,7 +67,7 @@ <h3>Custom tags</h3>
$.views.tags({ $.views.tags({


// Tag to reverse-sort an array // Tag to reverse-sort an array
sort: function( array ){ sort: function( array ) {
var ret = ""; var ret = "";
if ( this.props.reverse ) { if ( this.props.reverse ) {
// Render in reverse order // Render in reverse order
Expand Down
2 changes: 1 addition & 1 deletion demos/step-by-step/09_helper-functions.html
Expand Up @@ -17,7 +17,7 @@ <h3>Helper functions</h3>


$.views.helpers({ $.views.helpers({


format: function( val, format ){ format: function( val, format ) {
... ...
return val.toUpperCase(); return val.toUpperCase();
... ...
Expand Down
2 changes: 1 addition & 1 deletion demos/step-by-step/20_without-jquery.html
Expand Up @@ -43,7 +43,7 @@ <h3>JsRender without jQuery</h3>


jsviews.tags({ jsviews.tags({


sort: function( array ){ sort: function( array ) {
var ret = ""; var ret = "";
if ( this.props.reverse ) { if ( this.props.reverse ) {
// Render in reverse order // Render in reverse order
Expand Down
Expand Up @@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" /> <link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<< <a href="../variants.html">JsRender Demos: Variants</a><br /> <div class="subhead"><< <a href="../variants.html">JsRender Demos: Variants</a></div>


<h3>Compiling template objects from strings</h3> <h3>Compiling template objects from strings</h3>


Expand Down
Expand Up @@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" /> <link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<< <a href="../variants.html">JsRender Demos: Variants</a><br /> <div class="subhead"><< <a href="../variants.html">JsRender Demos: Variants</a></div>


<h3>Register script block template declarations, as named templates</h3> <h3>Register script block template declarations, as named templates</h3>


Expand Down
Expand Up @@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" /> <link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<< <a href="../variants.html">JsRender Demos: Variants</a><br /> <div class="subhead"><< <a href="../variants.html">JsRender Demos: Variants</a></div>


<h3>Getting template objects from script block template declarations</h3> <h3>Getting template objects from script block template declarations</h3>


Expand Down
Expand Up @@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" /> <link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<< <a href="../variants.html">JsRender Demos: Variants</a><br /> <div class="subhead"><< <a href="../variants.html">JsRender Demos: Variants</a></div>


<h3>Template composition. Registering and accessing subtemplates.</h3> <h3>Template composition. Registering and accessing subtemplates.</h3>


Expand Down
Expand Up @@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" /> <link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<< <a href="../variants.html">JsRender Demos: Variants</a><br /> <div class="subhead"><< <a href="../variants.html">JsRender Demos: Variants</a></div>


<h3>Template composition. Passing in template objects as the contextual template parameters.</h3> <h3>Template composition. Passing in template objects as the contextual template parameters.</h3>


Expand Down
2 changes: 1 addition & 1 deletion demos/variants/variants.html
Expand Up @@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>JsRender: Demos</title> <title>JsRender: Demos</title>
<link href="resources/demos.css" rel="stylesheet" type="text/css" /> <link href="../resources/demos.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>


Expand Down
17 changes: 9 additions & 8 deletions jsrender.js
Expand Up @@ -6,7 +6,7 @@
* Copyright 2012, Boris Moore * Copyright 2012, Boris Moore
* Released under the MIT License. * Released under the MIT License.
*/ */
// informal pre beta commit counter: 9 // informal pre beta commit counter: 10


this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) { this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {


Expand Down Expand Up @@ -276,7 +276,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
function renderContent(data, context, path, key, parentView, onRender) { function renderContent(data, context, path, key, parentView, onRender) {
// Render template against data as a tree of subviews (nested template), or as a string (top-level template). // Render template against data as a tree of subviews (nested template), or as a string (top-level template).
// tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects) // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
var i, l, dataItem, newView, itemWrap, itemsWrap, itemResult, parentContext, tmpl, layout, onRender, props, swapContent, var i, l, dataItem, newView, itemWrap, itemsWrap, itemResult, parentContext, tmpl, onRender, props, swapContent, isLayout,
self = this, self = this,
result = ""; result = "";
if (key === TRUE) { if (key === TRUE) {
Expand Down Expand Up @@ -304,12 +304,12 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
parentView = parentView || jsv.topView; parentView = parentView || jsv.topView;
parentContext = parentView.ctx; parentContext = parentView.ctx;
if (tmpl) { if (tmpl) {
layout = tmpl.layout;
if (data === parentView) { if (data === parentView) {
// Inherit the data from the parent view. // Inherit the data from the parent view.
// This may be the contents of an {{if}} block // This may be the contents of an {{if}} block
// Set isLayout = true so we don't iterated the if block if the data is an array.
data = parentView.data; data = parentView.data;
layout = TRUE; isLayout = TRUE;
} }


// Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views) // Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views)
Expand All @@ -329,7 +329,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
onRender = onRender || parentView._onRender; onRender = onRender || parentView._onRender;


if (tmpl) { if (tmpl) {
if ($.isArray(data) && !layout) { if ($.isArray(data) && !isLayout) {
// Create a view for the array, whose child views correspond to each data item. // Create a view for the array, whose child views correspond to each data item.
// (Note: if key and parentView are passed in along with parent view, treat as // (Note: if key and parentView are passed in along with parent view, treat as
// insert -e.g. from view.addViews - so parentView is already the view item for array) // insert -e.g. from view.addViews - so parentView is already the view item for array)
Expand All @@ -344,7 +344,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
// Create a view for singleton data object. // Create a view for singleton data object.
newView = swapContent ? parentView : View(context, path, parentView, data, tmpl, key, onRender); newView = swapContent ? parentView : View(context, path, parentView, data, tmpl, key, onRender);
newView._onRender = onRender; newView._onRender = onRender;
result += (data || layout) ? tmpl.fn(data, newView, jsv) : ""; result += tmpl.fn(data, newView, jsv);
} }
parentView.topKey = newView.key; parentView.topKey = newView.key;
return onRender ? onRender(result, tmpl, props, newView.key, path) : result; return onRender ? onRender(result, tmpl, props, newView.key, path) : result;
Expand Down Expand Up @@ -837,8 +837,9 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
result = "", result = "",
args = arguments, args = arguments,
l = args.length; l = args.length;
if (self.props && self.props.layout) { if (l === 0) {
self.tmpl.layout = TRUE; // If no parameters, render once, with #data undefined
l = 1;
} }
for (i = 0; i < l; i++) { for (i = 0; i < l; i++) {
result += self.renderContent(args[i]); result += self.renderContent(args[i]);
Expand Down

0 comments on commit fc7a28b

Please sign in to comment.