Permalink
Browse files

Removed the layout template feature, replacing it by an alternative

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...
1 parent 83a7d9a commit fc7a28b0b2c670c548f7966bb22bf14038aed4e0 @BorisMoore committed May 13, 2012
View
@@ -23,7 +23,6 @@
<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/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>
</ul>
@@ -40,6 +39,7 @@
<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/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>
</body>
</html>
@@ -36,7 +36,7 @@
{{get languages defaultValue="No languages!"/}}
$.views.tags({
- get: function( value ){
+ get: function( value ) {
return value || this.props.defaultValue;
}
});
@@ -55,7 +55,7 @@
{{yesNo languages yes="Alternate languages available:" no="No alternate languages"/}}
$.views.tags({
- yesNo: function( value ){
+ yesNo: function( value ) {
return value ? this.props.yes : this.props.no;
}
});
@@ -103,11 +103,11 @@
<script type="text/javascript">
$.views.tags({
- get: function( value ){
+ get: function( value ) {
return value || this.props.defaultValue;
},
- yesNo: function( value ){
+ yesNo: function( value ) {
return value ? this.props.yes : this.props.no;
}
});
@@ -10,34 +10,33 @@
<body>
<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>
<pre>
-$.templates( "moviesLayout", {
- markup: "#movieTemplate",
- layout: true
-});
-
$( "#movieList" ).html(
- $.render.moviesLayout( movies )
+ // Wrap movies array in an array
+ $("#movieTemplate").render( [movies] )
);
-header
- {{for #data}}
- item
- {{/for}}
-footer
+Template:
+
+ header
+ {{for #data}}
+ item
+ {{/for}}
+ footer
</pre>
<div class="subhead">Nested layout:</div>
<pre>
-{{for languages layout=true}}
- header
- {{for #data}}
- item
- {{/for}}
- footer
+{{for [languages]}}
+ header
+ {{for #data}}
+ item
+ {{/for}}
+ footer
{{/for}}
</pre>
@@ -53,17 +52,16 @@
<tr>
<td>{{:title}}</td>
<td>
- {{for languages layout=true}}
+ {{for [languages]}}
@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 ;))
@BorisMoore

BorisMoore May 15, 2012

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.

@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.
@BorisMoore

BorisMoore Sep 5, 2014

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>
{{if #data}}
<strong>{{:length}}</strong> languages available:
+ {{for #data}}
+ <em>{{:name}}</em>
+ {{/for}}
{{else}}
Original version only
{{/if}}
-
- {{for #data}}
- <em>{{:name}}</em>
- {{/for}}
</div>
<div>
{{if #data}}
@@ -101,14 +99,9 @@
]
}
];
-
- $.templates( "moviesLayout", {
- markup: "#movieTemplate",
- layout: true
- });
-
$( "#movieList" ).html(
- $.render.moviesLayout( movies )
+ // Wrap movies array in an array
+ $( "#movieTemplate" ).render( [movies] )
);
</script>
@@ -19,7 +19,7 @@
// Tag to reverse-sort an array
- sort: function( array ){
+ sort: function( array ) {
var ret = "";
if ( this.props.reverse ) {
// Render in reverse order
@@ -67,7 +67,7 @@
$.views.tags({
// Tag to reverse-sort an array
- sort: function( array ){
+ sort: function( array ) {
var ret = "";
if ( this.props.reverse ) {
// Render in reverse order
@@ -17,7 +17,7 @@
$.views.helpers({
- format: function( val, format ){
+ format: function( val, format ) {
...
return val.toUpperCase();
...
@@ -43,7 +43,7 @@
jsviews.tags({
- sort: function( array ){
+ sort: function( array ) {
var ret = "";
if ( this.props.reverse ) {
// Render in reverse order
@@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head>
<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>
@@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head>
<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>
@@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head>
<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>
@@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head>
<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>
@@ -8,7 +8,7 @@
<link href="../../resources/movielist.css" rel="stylesheet" type="text/css" />
</head>
<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>
@@ -2,7 +2,7 @@
<html>
<head>
<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>
<body>
View
@@ -6,7 +6,7 @@
* Copyright 2012, Boris Moore
* 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) {
@@ -276,7 +276,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
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).
// 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,
result = "";
if (key === TRUE) {
@@ -304,12 +304,12 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
parentView = parentView || jsv.topView;
parentContext = parentView.ctx;
if (tmpl) {
- layout = tmpl.layout;
if (data === parentView) {
// Inherit the data from the parent view.
// 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;
- 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)
@@ -329,7 +329,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
onRender = onRender || parentView._onRender;
if (tmpl) {
- if ($.isArray(data) && !layout) {
+ if ($.isArray(data) && !isLayout) {
// 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
// insert -e.g. from view.addViews - so parentView is already the view item for array)
@@ -344,7 +344,7 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
// Create a view for singleton data object.
newView = swapContent ? parentView : View(context, path, parentView, data, tmpl, key, onRender);
newView._onRender = onRender;
- result += (data || layout) ? tmpl.fn(data, newView, jsv) : "";
+ result += tmpl.fn(data, newView, jsv);
}
parentView.topKey = newView.key;
return onRender ? onRender(result, tmpl, props, newView.key, path) : result;
@@ -837,8 +837,9 @@ this.jsviews || this.jQuery && jQuery.views || (function(window, undefined) {
result = "",
args = arguments,
l = args.length;
- if (self.props && self.props.layout) {
- self.tmpl.layout = TRUE;
+ if (l === 0) {
+ // If no parameters, render once, with #data undefined
+ l = 1;
}
for (i = 0; i < l; i++) {
result += self.renderContent(args[i]);
Oops, something went wrong.

0 comments on commit fc7a28b

Please sign in to comment.