Permalink
Browse files

Ongoing work on supporting Presenter pattern.

  • Loading branch information...
1 parent 78b34cb commit 667b56ca5ea135de7be07f603dab86c317b0f162 @BorisMoore committed Nov 13, 2011
Showing with 132 additions and 98 deletions.
  1. +6 −5 README.md
  2. +0 −1 demos/step-by-step/04_editable-data.html
  3. +65 −32 jquery.views.js
  4. +61 −60 jsrender.js
View
11 README.md
@@ -1,6 +1,7 @@
-<h2>JsViews: Next-generation jQuery Templates</h2>
-<em>Interactive data-driven views, built on top of JsRender templates</em>
+## JsViews: Next-generation jQuery Templates
+_Interactive data-driven views, built on top of JsRender templates_<br/>
+To view demo pages (on gh-branch) navigate to [http://borismoore.github.com/jsviews/demos/index.html](http://borismoore.github.com/jsviews/demos/index.html "JsViews Samples").
+See also [JsRender step-by-step samples](http://borismoore.github.com/jsrender/demos/index.html)
-To view demo pages (on gh-branch) navigate to [http://borismoore.github.com/jsviews/demos/index.html](http://borismoore.github.com/jsviews/demos/index.html).
-
-See also [http://borismoore.github.com/jsrender/demos/index.html](JsRender step-by-step samples)
+**Warning:** JsViews is not yet Beta, and there may be frequent changes to APIs and features in the coming period.<br/>
+<font color="red">Recent breaking change:</font> JsRender: Parameter order change: `$.render( data, template )`
View
1 demos/step-by-step/04_editable-data.html
@@ -53,7 +53,6 @@
<div id="movieDetail"></div>
<script type="text/javascript">
-
var app = {
selectedItem: null
},
View
97 jquery.views.js
@@ -58,7 +58,6 @@ function elemChangeHandler( ev ) {
target = linkToInfo[ 0 ].slice( 0, -1 );
data = (view && view.data) || data;
// data is the current view data, or the top-level target of linkTo.
-//TODO make sure we are not missing intermediate levels.
// get the target object
target = getTargetObject( data, view, target );
cnvt = linkToInfo[ 3 ];
@@ -197,7 +196,7 @@ function setViewContext( view, context, merge ) {
}
function View( context, node, path, template, parentView, parentElViews, data ) {
- var views, index, viewCount, tagName, ctx,
+ var views, index, viewCount, tagInfo, ctx, presenter, options,
self = this;
$.extend( self, {
@@ -220,44 +219,56 @@ function View( context, node, path, template, parentView, parentElViews, data )
while ( index++ < viewCount-1 ) {
$.observable( views[ index ] ).setProperty( "index", index );
}
- if ( tagName = parentView.tag ) {
- self.tag = tagName;
- }
+ self.tag = parentView.tag;
} else {
- // TODO getDataAndContext and passing of context and data from tags needs work. Also, consider more 'codeless' approach, and more consistent syntax with codeless tag markup
if ( path ) {
+ // TODO getDataAndContext and passing of context and data from tags needs work.
+ // Also, consider more 'codeless' approach, and more consistent syntax with codeless tag markup
data = getDataAndContext( data, parentView, path );
context = context || data[ 1 ];
data = data[ 0 ];
}
self.index = views.length;
views.push( self );
- if ( template.slice( 0, 4 ) === "tag:" ) {
- tagName = self.tag = template.slice( 4 );
+ if ( context.tag ) {
+ self.tag = [context.tag];
+ } else {
+ template = template.split( "=" );
+ if ( template.shift() === "tag" ) {
+ self.tag = template;
+ }
}
}
}
self.data = data;
setViewContext( self, context );
setArrayChangeLink( self );
-
- if ( (tagName = self.tag) && !$.isArray( data )) {
+
+ if ( (tagInfo = self.tag) && !$.isArray( data )) {
// This view is from a registered presenter
- presenter = viewsNs.tags[ tagName ];
- if ( presenter && presenter.pstr ) {
- ctx = presenter.context || {};
- ctx[ tagName ] = new presenter.pstr( self );
+ presenter = viewsNs.tags[ tagInfo[ 0 ]];
+ if ( presenter && presenter.presenter ) {
+ ctx = presenter.ctx || {};
+ options = tagInfo[1]
+ ? getTargetObject( parentView.data, parentView, "{" + tagInfo[ 1 ] + "}" )
+ // Declarative presenter {{foo}}
+ : parentView.ctx;
+ // Imperative: link( data, foo )
+ $.extend( options, presenter.options || {} );
+ // attach plugin to content of view
+
+ ctx[ tagInfo[ 0 ]] = new presenter.presenter( options, self );
// Make presenter available off the ctx object
self.context( ctx );
}
}
}
function createNestedViews( node, parent, nextNode, depth, data, context, prevNode, index ) {
- var tokens, parentElViews, view, existing, parentNode, elem, elem2, params, presenter, tagName, ctx,
- currentView = parent,
+ var tokens, parentElViews, view, existing, parentNode, elem, elem2, params, presenter, tagInfo, ctx,
+ currentView = parent,
viewDepth = depth;
-
+ context = context || {};
index = index || 0;
node = prevNode || node;
@@ -287,7 +298,7 @@ function createNestedViews( node, parent, nextNode, depth, data, context, prevNo
if ( tokens[ 1 ]) {
// <!--/item--> or <!--/tmpl-->
currentView.nextNode = node;
- if (( tagName = currentView.tag ) && ( ctx = currentView.ctx[ tagName ]) && ctx.onAfterCreate ) {
+ if (( tagInfo = currentView.tag ) && ( ctx = currentView.ctx[ tagInfo[ 0 ]]) && ctx.onAfterCreate ) {
// This view is from a registered presenter which has registered an onAfterCreate callback
ctx.onAfterCreate();
}
@@ -784,7 +795,7 @@ $.extend({
viewsNs = $.views = $.views || {};
$.extend( viewsNs, {
- pstrs: {},
+ presenters: {},
activeViews: true,
getProperty: function( data, value ) {
// support for property getter on data
@@ -808,21 +819,36 @@ $.extend( viewsNs, {
// registerPresenters
//===============
- // Register declarative tag.
+ // Register a 'control' - which associates a Presenter object with a template
+ // Optionally associate with a tag, for declarative use. (Rendering of template and instantiation/attaching of presenter).
registerPresenters: registerPresenters = function( name, presenter ) {
- var key;
+ var key, tag;
+
if ( typeof name === "object" ) {
- // Object representation where property name is path and property value is value.
- // TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
for ( key in name ) {
registerPresenters( key, name[ key ]);
}
} else {
// Simple single property case.
- viewsNs.pstrs[ name ] = presenter;
- if ( presenter.tag ) {
- viewsNs.tags[ name ] = presenter;
+ presenter.ctx = presenter.ctx || {};
+
+ tag = (tag = presenter.tag) === undefined ? name : tag;
+ // If tag not set to null or empty string, then register a tag for this control
+ if ( tag ) {
+ presenter.tag = tag;
+ viewsNs.tags[ tag ] = presenter;
}
+
+ plugin = (plugin = presenter.plugin) === undefined ? name : plugin;
+ // If plugin not set to null or empty string, then register create a generated jQuery plugin for this control
+ if ( plugin ) {
+ //Generated jQuery plugin
+ $.fn[ plugin ] = $.fn[ plugin ] || function( data, options ) {
+ return this.link( data, presenter, options );
+ }
+ presenter.plugin = plugin;
+ }
+ viewsNs.presenters[ name ] = presenter;
}
return this;
},
@@ -993,15 +1019,22 @@ $.fn.extend({
link: function( data, tmpl, context ) {
// Declarative Linking
// If context is a function, cb - shorthand for { beforeChange: cb }
- // if tmpl not a map, corresponds to $("#container").html( $.render( data , tmpl)).link( data );
+ // if tmpl not a map, corresponds to $("#container").html( $.render( data, tmpl )).link( data );
if ( !this.length ) {
return this;
}
- if ( $.isPlainObject( tmpl )) {
- context = tmpl;
- } else {
- tmpl = $.template( tmpl );
- if ( tmpl ) {
+
+ if ( tmpl ) {
+ tmpl = tmpl.tag && tmpl === $.views.tags[ tmpl.tag ]
+ ? (context.tag = tmpl.tag, context.tmpl || tmpl.tmpl)
+ // Special case: tmpl is a presenter
+ : $.isPlainObject( tmpl )
+ ? (context = tmpl, FALSE)
+ // Linking only. (context was passed in as second parameter, no template parameter passed)
+ : tmpl;
+ // Linking and rendering (passing a template)
+
+ if ( tmpl && (tmpl = $.template( tmpl ))) {
removeLinksToData( this[0], declLinkTo );
this.empty();
if ( data ) {
View
121 jsrender.js
@@ -1,13 +1,13 @@
/*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */
/*
- * Optimized version of jQuery Templates, for rendering to string, using 'codeless' markup.
+ * Optimized version of jQuery Templates, fosr rendering to string, using 'codeless' markup.
*
* Copyright 2011, Boris Moore
* Released under the MIT License.
*/
window.JsViews || window.jQuery && jQuery.views || (function( window, undefined ) {
-var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers,
+var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers, extend,
FALSE = false, TRUE = true,
jQuery = window.jQuery, document = window.document,
htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
@@ -56,10 +56,6 @@ if ( jQuery ) {
window.JsViews = JsViews = window.$ = $ = {
extend: function( target, source ) {
var name;
- if ( source === undefined ) {
- source = target;
- target = $;
- }
for ( name in source ) {
target[ name ] = source[ name ];
}
@@ -77,6 +73,8 @@ if ( jQuery ) {
};
}
+extend = $.extend;
+
//=================
// View constructor
//=================
@@ -100,12 +98,12 @@ function View( context, path, parentView, data, template ) {
// Set additional context on this view (which will modify the context inherited from the parent, and be inherited by child views)
ctx : context && context === parentContext
? parentContext
- : (parentContext ? $.extend( {}, parentContext, context ) : context||{}),
+ : (parentContext ? extend( extend( {}, parentContext ), context ) : context||{}),
+ // If no jQuery, extend does not support chained copies - so limit to two parameters
parent: parentView
};
}
-
-$.extend({
+extend( $, {
views: viewsNs = {
templates: {},
tags: {
@@ -131,17 +129,19 @@ $.extend({
return view.onElse ? view.onElse( this, arguments ) : "";
},
each: function() {
- var i, result = "",
+ var i,
+ self = this,
+ result = "",
args = arguments,
l = args.length,
- content = this.tmpl,
- view = this._view;
+ content = self.tmpl,
+ view = self._view;
for ( i = 0; i < l; i++ ) {
- result += args[ i ] ? render( args[ i ], content, view.ctx, view, this._path, this._tag ) : "";
+ result += args[ i ] ? render( args[ i ], content, self.ctx || view.ctx, view, self._path, self._ctor ) : "";
}
return l ? result
// If no data parameter, use the current $data from view, and render once
- : result + render( view.data, content, view.ctx, view, this._path, this._tag );
+ : result + render( view.data, content, view.ctx, view, self._path, self.tag );
},
"=": function( value ) {
return value;
@@ -167,8 +167,8 @@ $.extend({
setDelimiters: function( openTag, closeTag ) {
// Set or modify the delimiter characters for tags: "{{" and "}}"
- var firstCloseChar = closeTag.charAt( 1 ),
- secondCloseChar = closeTag.charAt( 0 );
+ var firstCloseChar = closeTag.charAt( 0 ),
+ secondCloseChar = closeTag.charAt( 1 );
openTag = "\\" + openTag.charAt( 0 ) + "\\" + openTag.charAt( 1 );
closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar;
@@ -198,17 +198,15 @@ $.extend({
//===============
// Register declarative tag.
- registerTags: registerTags = function( name, tag ) {
+ registerTags: registerTags = function( name, tagFn ) {
var key;
if ( typeof name === "object" ) {
- // Object representation where property name is path and property value is value.
- // TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
for ( key in name ) {
registerTags( key, name[ key ]);
}
} else {
// Simple single property case.
- viewsNs.tags[ name ] = tag;
+ viewsNs.tags[ name ] = tagFn;
}
return this;
},
@@ -259,29 +257,38 @@ $.extend({
// renderTag
//===============
- renderTag: function( tagName, view, encode, content, presenter ) {
- // This is a tag call, with arguments: "tagName", view, encode, content[, params...], presenter
- var ret,
- tagFn = viewsNs.tags[ tagName ];
+ renderTag: function( tag, view, encode, content, tagProperties ) {
+ // This is a tag call, with arguments: "tag", view, encode, content, presenter [, params...]
+ var ret, ctx, name,
+ args = arguments,
+ presenters = viewsNs.presenters;
+ hash = tagProperties._hash,
+ tagFn = viewsNs.tags[ tag ];
if ( !tagFn ) {
return "";
}
- if ( viewsNs.pstrs && viewsNs.pstrs[ tagName ]) {
- // This is a presenter tag (from JsViews registerPresenter)
- presenter = $.extend( tagFn , presenter );
- presenter._tag = tagName;
- tagFn = FALSE;
- }
-
- presenter._encode = encode;
- presenter._view = view;
content = content && view.tmpl.nested[ content - 1 ];
- presenter.tmpl = presenter.tmpl || content;
- tagFn = tagFn || viewsNs.tags.each;
- ret = tagFn && (tagFn.apply( presenter, slice.call( arguments, 5 )));
- return ret || (ret === undefined ? "" : ret.toString());
+ tagProperties.tmpl = tagProperties.tmpl || content || undefined;
+ // Set the tmpl property to the content of the block tag, unless set as an override property on the tag
+
+ if ( presenters && presenters[ tag ]) {
+ ctx = extend( extend( {}, tagProperties.ctx ), tagProperties );
+ delete ctx.ctx;
+ delete ctx._path;
+ delete ctx.tmpl;
+ tagProperties.ctx = ctx;
+ tagProperties._ctor = tag + (hash ? "=" + hash.slice( 0, -1 ) : "");
+
+ tagProperties = extend( extend( {}, tagFn ), tagProperties );
+ tagFn = viewsNs.tags.each; // Use each to render the layout template against the data
+ }
+
+ tagProperties._encode = encode;
+ tagProperties._view = view;
+ ret = tagFn.apply( tagProperties, args.length > 5 ? slice.call( args, 5 ) : [view.data] );
+ return ret || (ret === undefined ? "" : ret.toString()); // (If ret is the value 0 or false or null, will render to string)
}
},
@@ -291,7 +298,7 @@ $.extend({
render: render = function( data, tmpl, context, parentView, path, tagName ) {
// 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 context objects)
+ // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
var i, l, dataItem, arrayView, content, result = "";
if ( arguments.length === 2 && data.jsViews ) {
@@ -319,7 +326,7 @@ $.extend({
return viewsNs.activeViews
// If in activeView mode, include annotations
- ? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag:" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
+ ? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
// else return just the string result
: result;
},
@@ -361,7 +368,7 @@ $.extend({
}
// Return named compiled template
return name
- ? "" + name !== name
+ ? "" + name !== name // not type string
? (name._name
? name // already compiled
: $.template( null, name ))
@@ -396,9 +403,9 @@ function compile( markup ) {
var newNode,
loc = 0,
stack = [],
- top = [],
- content = top,
- current = [,,top];
+ topNode = [],
+ content = topNode,
+ current = [,,topNode];
function pushPreceedingContent( shift ) {
shift -= loc;
@@ -408,10 +415,10 @@ function compile( markup ) {
}
function parseTag( all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index ) {
- // rTag : # tag equals code params encode closeBlock
+ // rTag : # tagName equals code params encode closeBlock
// /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
- // Build abstract syntax tree: [ tag, params, content, encode ]
+ // Build abstract syntax tree: [ tagName, params, content, encode ]
var named,
hash = "",
parenDepth = 0,
@@ -480,7 +487,7 @@ function compile( markup ) {
tagName,
useEncode ? encode || "none" : "",
isBlock && [],
- "{" + hash + "_path:'" + params + "'}",
+ "{" + hash + "_hash:'" + hash + "',_path:'" + params + "'}",
params
];
@@ -501,7 +508,7 @@ function compile( markup ) {
markup = markup.replace( rEscapeQuotes, "\\$1" );
markup.replace( rTag, parseTag );
pushPreceedingContent( markup.length );
- return buildTmplFunction( top );
+ return buildTmplFunction( topNode );
}
// Build javascript compiled template function, from AST
@@ -524,30 +531,25 @@ function buildTmplFunction( nodes ) {
encode = node[ 1 ],
content = node[ 2 ],
obj = node[ 3 ],
- params = node[ 4 ];
+ params = node[ 4 ],
+ paramsOrEmptyString = params + '||"")+';
if( content ) {
nested.push( buildTmplFunction( content ));
}
code += tag === "="
? (!encode || encode === "html"
- ? "html(" + params + ")+"
+ ? "html(" + paramsOrEmptyString
: encode === "none"
- ? (params + "+")
- : ('enc("' + encode + '",' + params + ")+")
+ ? ("(" + paramsOrEmptyString)
+ : ('enc("' + encode + '",' + paramsOrEmptyString)
)
: 'tag("' + tag + '",$view,"' + ( encode || "" ) + '",'
+ (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
- + "," + obj + (params ? "," : "") + params + ')+';
+ + "," + obj + (params ? "," : "") + params + ")+";
}
}
- try {
- ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" );
- } catch(e) {
- ret = function() {
- return viewsNs.err( e );
- };
- }
+ ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" );
ret.nested = nested;
return ret;
}
@@ -568,5 +570,4 @@ function try$( selector ) {
} catch( e) {}
return selector;
}
-
})( window );

0 comments on commit 667b56c

Please sign in to comment.