wip javascript
This commit is contained in:
@@ -0,0 +1,573 @@
|
||||
/*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */
|
||||
/*
|
||||
* 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, extend,
|
||||
FALSE = false, TRUE = true,
|
||||
jQuery = window.jQuery, document = window.document,
|
||||
htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
|
||||
rPath = /^(true|false|null|[\d\.]+)|(\w+|\$(view|data|ctx|(\w+)))([\w\.]*)|((['"])(?:\\\1|.)*\7)$/g,
|
||||
rParams = /(\$?[\w\.\[\]]+)(?:(\()|\s*(===|!==|==|!=|<|>|<=|>=)\s*|\s*(\=)\s*)?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g,
|
||||
rNewLine = /\r?\n/g,
|
||||
rUnescapeQuotes = /\\(['"])/g,
|
||||
rEscapeQuotes = /\\?(['"])/g,
|
||||
rBuildHash = /\x08([^\x08]+)\x08/g,
|
||||
autoName = 0,
|
||||
escapeMapForHtml = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">"
|
||||
},
|
||||
htmlSpecialChar = /[\x00"&'<>]/g,
|
||||
slice = Array.prototype.slice;
|
||||
|
||||
if ( jQuery ) {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// jQuery is loaded, so make $ the jQuery object
|
||||
$ = jQuery;
|
||||
|
||||
$.fn.extend({
|
||||
// Use first wrapped element as template markup.
|
||||
// Return string obtained by rendering the template against data.
|
||||
render: function( data, context, parentView, path ) {
|
||||
return render( data, this[0], context, parentView, path );
|
||||
},
|
||||
|
||||
// Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
|
||||
template: function( name, context ) {
|
||||
return $.template( name, this[0], context );
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// jQuery is not loaded. Make $ the JsViews object
|
||||
|
||||
// Map over the $ in case of overwrite
|
||||
_$ = window.$;
|
||||
|
||||
window.JsViews = JsViews = window.$ = $ = {
|
||||
extend: function( target, source ) {
|
||||
var name;
|
||||
for ( name in source ) {
|
||||
target[ name ] = source[ name ];
|
||||
}
|
||||
return target;
|
||||
},
|
||||
isArray: Array.isArray || function( obj ) {
|
||||
return Object.prototype.toString.call( obj ) === "[object Array]";
|
||||
},
|
||||
noConflict: function() {
|
||||
if ( window.$ === JsViews ) {
|
||||
window.$ = _$;
|
||||
}
|
||||
return JsViews;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extend = $.extend;
|
||||
|
||||
//=================
|
||||
// View constructor
|
||||
//=================
|
||||
|
||||
function View( context, path, parentView, data, template ) {
|
||||
// Returns a view data structure for a new rendered instance of a template.
|
||||
// The content field is a hierarchical array of strings and nested views.
|
||||
|
||||
parentView = parentView || { viewsCount:0, ctx: viewsNs.helpers };
|
||||
|
||||
var parentContext = parentView && parentView.ctx;
|
||||
|
||||
return {
|
||||
jsViews: "v1.0pre",
|
||||
path: path || "",
|
||||
// inherit context from parentView, merged with new context.
|
||||
itemNumber: ++parentView.viewsCount || 1,
|
||||
viewsCount: 0,
|
||||
tmpl: template,
|
||||
data: data || parentView.data || {},
|
||||
// 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( extend( {}, parentContext ), context ) : context||{}),
|
||||
// If no jQuery, extend does not support chained copies - so limit to two parameters
|
||||
parent: parentView
|
||||
};
|
||||
}
|
||||
extend( $, {
|
||||
views: viewsNs = {
|
||||
templates: {},
|
||||
tags: {
|
||||
"if": function() {
|
||||
var ifTag = this,
|
||||
view = ifTag._view;
|
||||
view.onElse = function( presenter, args ) {
|
||||
var i = 0,
|
||||
l = args.length;
|
||||
while ( l && !args[ i++ ]) {
|
||||
// Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
|
||||
if ( i === l ) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
view.onElse = undefined; // If condition satisfied, so won't run 'else'.
|
||||
return render( view.data, presenter.tmpl, view.ctx, view);
|
||||
};
|
||||
return view.onElse( this, arguments );
|
||||
},
|
||||
"else": function() {
|
||||
var view = this._view;
|
||||
return view.onElse ? view.onElse( this, arguments ) : "";
|
||||
},
|
||||
each: function() {
|
||||
var i,
|
||||
self = this,
|
||||
result = "",
|
||||
args = arguments,
|
||||
l = args.length,
|
||||
content = self.tmpl,
|
||||
view = self._view;
|
||||
for ( i = 0; i < l; i++ ) {
|
||||
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, self._path, self.tag );
|
||||
},
|
||||
"=": function( value ) {
|
||||
return value;
|
||||
},
|
||||
"*": function( value ) {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
helpers: {
|
||||
not: function( value ) {
|
||||
return !value;
|
||||
}
|
||||
},
|
||||
allowCode: FALSE,
|
||||
debugMode: TRUE,
|
||||
err: function( e ) {
|
||||
return viewsNs.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>"): '""';
|
||||
},
|
||||
|
||||
//===============
|
||||
// setDelimiters
|
||||
//===============
|
||||
|
||||
setDelimiters: function( openTag, closeTag ) {
|
||||
// Set or modify the delimiter characters for tags: "{{" and "}}"
|
||||
var firstCloseChar = closeTag.charAt( 0 ),
|
||||
secondCloseChar = closeTag.charAt( 1 );
|
||||
openTag = "\\" + openTag.charAt( 0 ) + "\\" + openTag.charAt( 1 );
|
||||
closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar;
|
||||
|
||||
// Build regex with new delimiters
|
||||
// {{
|
||||
rTag = openTag
|
||||
// # tag (followed by space,! or }) or equals or code
|
||||
+ "(?:(?:(\\#)?(\\w+(?=[!\\s\\" + firstCloseChar + "]))" + "|(?:(\\=)|(\\*)))"
|
||||
// params
|
||||
+ "\\s*((?:[^\\" + firstCloseChar + "]|\\" + firstCloseChar + "(?!\\" + secondCloseChar + "))*?)"
|
||||
// encoding
|
||||
+ "(!(\\w*))?"
|
||||
// closeBlock
|
||||
+ "|(?:\\/([\\w\\$\\.\\[\\]]+)))"
|
||||
// }}
|
||||
+ closeTag;
|
||||
|
||||
// Default rTag: # tag equals code params encoding closeBlock
|
||||
// /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
|
||||
|
||||
rTag = new RegExp( rTag, "g" );
|
||||
},
|
||||
|
||||
|
||||
//===============
|
||||
// registerTags
|
||||
//===============
|
||||
|
||||
// Register declarative tag.
|
||||
registerTags: registerTags = function( name, tagFn ) {
|
||||
var key;
|
||||
if ( typeof name === "object" ) {
|
||||
for ( key in name ) {
|
||||
registerTags( key, name[ key ]);
|
||||
}
|
||||
} else {
|
||||
// Simple single property case.
|
||||
viewsNs.tags[ name ] = tagFn;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
//===============
|
||||
// registerHelpers
|
||||
//===============
|
||||
|
||||
// Register helper function for use in markup.
|
||||
registerHelpers: registerHelpers = function( name, helper ) {
|
||||
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.
|
||||
var key;
|
||||
for ( key in name ) {
|
||||
registerHelpers( key, name[ key ]);
|
||||
}
|
||||
} else {
|
||||
// Simple single property case.
|
||||
viewsNs.helpers[ name ] = helper;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
//===============
|
||||
// tmpl.encode
|
||||
//===============
|
||||
|
||||
encode: function( encoding, text ) {
|
||||
return text
|
||||
? ( tmplEncode[ encoding || "html" ] || tmplEncode.html)( text ) // HTML encoding is the default
|
||||
: "";
|
||||
},
|
||||
|
||||
encoders: tmplEncode = {
|
||||
"none": function( text ) {
|
||||
return text;
|
||||
},
|
||||
"html": function( text ) {
|
||||
// HTML encoding helper: Replace < > & and ' and " by corresponding entities.
|
||||
// Implementation, from Mike Samuel <msamuel@google.com>
|
||||
return String( text ).replace( htmlSpecialChar, replacerForHtml );
|
||||
}
|
||||
//TODO add URL encoding, and perhaps other encoding helpers...
|
||||
},
|
||||
|
||||
//===============
|
||||
// renderTag
|
||||
//===============
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
content = content && view.tmpl.nested[ content - 1 ];
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
//===============
|
||||
// render
|
||||
//===============
|
||||
|
||||
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 presenter objects)
|
||||
var i, l, dataItem, arrayView, content, result = "";
|
||||
|
||||
if ( arguments.length === 2 && data.jsViews ) {
|
||||
parentView = data;
|
||||
context = parentView.ctx;
|
||||
data = parentView.data;
|
||||
}
|
||||
tmpl = $.template( tmpl );
|
||||
if ( !tmpl ) {
|
||||
return ""; // Could throw...
|
||||
}
|
||||
|
||||
if ( $.isArray( data )) {
|
||||
// Create a view item for the array, whose child views correspond to each data item.
|
||||
arrayView = new View( context, path, parentView, data);
|
||||
l = data.length;
|
||||
for ( i = 0, l = data.length; i < l; i++ ) {
|
||||
dataItem = data[ i ];
|
||||
content = dataItem ? tmpl( dataItem, new View( context, path, arrayView, dataItem, tmpl, this )) : "";
|
||||
result += viewsNs.activeViews ? "<!--item-->" + content + "<!--/item-->" : content;
|
||||
}
|
||||
} else {
|
||||
result += tmpl( data, new View( context, path, parentView, data, tmpl ));
|
||||
}
|
||||
|
||||
return viewsNs.activeViews
|
||||
// If in activeView mode, include annotations
|
||||
? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
|
||||
// else return just the string result
|
||||
: result;
|
||||
},
|
||||
|
||||
//===============
|
||||
// template
|
||||
//===============
|
||||
|
||||
template: function( name, tmpl ) {
|
||||
// Set:
|
||||
// Use $.template( name, tmpl ) to cache a named template,
|
||||
// where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
|
||||
// Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
|
||||
|
||||
// Get:
|
||||
// Use $.template( name ) to access a cached template.
|
||||
// Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
|
||||
// will return the compiled template, without adding a name reference.
|
||||
// If templateString is not a selector, $.template( templateString ) is equivalent
|
||||
// to $.template( null, templateString ). To ensure a string is treated as a template,
|
||||
// include an HTML element, an HTML comment, or a template comment tag.
|
||||
|
||||
if (tmpl) {
|
||||
// Compile template and associate with name
|
||||
if ( "" + tmpl === tmpl ) { // type string
|
||||
// This is an HTML string being passed directly in.
|
||||
tmpl = compile( tmpl );
|
||||
} else if ( jQuery && tmpl instanceof $ ) {
|
||||
tmpl = tmpl[0];
|
||||
}
|
||||
if ( tmpl ) {
|
||||
if ( jQuery && tmpl.nodeType ) {
|
||||
// If this is a template block, use cached copy, or generate tmpl function and cache.
|
||||
tmpl = $.data( tmpl, "tmpl" ) || $.data( tmpl, "tmpl", compile( tmpl.innerHTML ));
|
||||
}
|
||||
viewsNs.templates[ tmpl._name = tmpl._name || name || "_" + autoName++ ] = tmpl;
|
||||
}
|
||||
return tmpl;
|
||||
}
|
||||
// Return named compiled template
|
||||
return name
|
||||
? "" + name !== name // not type string
|
||||
? (name._name
|
||||
? name // already compiled
|
||||
: $.template( null, name ))
|
||||
: viewsNs.templates[ name ] ||
|
||||
// If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
|
||||
$.template( null, htmlExpr.test( name ) ? name : try$( name ))
|
||||
: null;
|
||||
}
|
||||
});
|
||||
|
||||
viewsNs.setDelimiters( "{{", "}}" );
|
||||
|
||||
//=================
|
||||
// compile template
|
||||
//=================
|
||||
|
||||
// Generate a reusable function that will serve to render a template against data
|
||||
// (Compile AST then build template function)
|
||||
|
||||
function parsePath( all, comp, object, viewDataCtx, viewProperty, path, string, quot ) {
|
||||
return object
|
||||
? ((viewDataCtx
|
||||
? viewProperty
|
||||
? ("$view." + viewProperty)
|
||||
: object
|
||||
:("$data." + object)
|
||||
) + ( path || "" ))
|
||||
: string || (comp || "");
|
||||
}
|
||||
|
||||
function compile( markup ) {
|
||||
var newNode,
|
||||
loc = 0,
|
||||
stack = [],
|
||||
topNode = [],
|
||||
content = topNode,
|
||||
current = [,,topNode];
|
||||
|
||||
function pushPreceedingContent( shift ) {
|
||||
shift -= loc;
|
||||
if ( shift ) {
|
||||
content.push( markup.substr( loc, shift ).replace( rNewLine,"\\n"));
|
||||
}
|
||||
}
|
||||
|
||||
function parseTag( all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index ) {
|
||||
// rTag : # tagName equals code params encode closeBlock
|
||||
// /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;
|
||||
|
||||
// Build abstract syntax tree: [ tagName, params, content, encode ]
|
||||
var named,
|
||||
hash = "",
|
||||
parenDepth = 0,
|
||||
quoted = FALSE, // boolean for string content in double qoutes
|
||||
aposed = FALSE; // or in single qoutes
|
||||
|
||||
function parseParams( all, path, paren, comp, eq, comma, apos, quot, rightParen, space, index ) {
|
||||
// path paren eq comma apos quot rtPrn space
|
||||
// /(\$?[\w\.\[\]]+)(?:(\()|(===)|(\=))?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g
|
||||
|
||||
return aposed
|
||||
// within single-quoted string
|
||||
? ( aposed = !apos, (aposed ? all : '"'))
|
||||
: quoted
|
||||
// within double-quoted string
|
||||
? ( quoted = !quot, (quoted ? all : '"'))
|
||||
: comp
|
||||
// comparison
|
||||
? ( path.replace( rPath, parsePath ) + comp)
|
||||
: eq
|
||||
// named param
|
||||
? parenDepth ? "" :( named = TRUE, '\b' + path + ':')
|
||||
: paren
|
||||
// function
|
||||
? (parenDepth++, path.replace( rPath, parsePath ) + '(')
|
||||
: rightParen
|
||||
// function
|
||||
? (parenDepth--, ")")
|
||||
: path
|
||||
// path
|
||||
? path.replace( rPath, parsePath )
|
||||
: comma
|
||||
? ","
|
||||
: space
|
||||
? (parenDepth
|
||||
? ""
|
||||
: named
|
||||
? ( named = FALSE, "\b")
|
||||
: ","
|
||||
)
|
||||
: (aposed = apos, quoted = quot, '"');
|
||||
}
|
||||
|
||||
tagName = tagName || equals;
|
||||
pushPreceedingContent( index );
|
||||
if ( code ) {
|
||||
if ( viewsNs.allowCode ) {
|
||||
content.push([ "*", params.replace( rUnescapeQuotes, "$1" )]);
|
||||
}
|
||||
} else if ( tagName ) {
|
||||
if ( tagName === "else" ) {
|
||||
current = stack.pop();
|
||||
content = current[ 2 ];
|
||||
isBlock = TRUE;
|
||||
}
|
||||
params = (params
|
||||
? (params + " ")
|
||||
.replace( rParams, parseParams )
|
||||
.replace( rBuildHash, function( all, keyValue, index ) {
|
||||
hash += keyValue + ",";
|
||||
return "";
|
||||
})
|
||||
: "");
|
||||
params = params.slice( 0, -1 );
|
||||
newNode = [
|
||||
tagName,
|
||||
useEncode ? encode || "none" : "",
|
||||
isBlock && [],
|
||||
"{" + hash + "_hash:'" + hash + "',_path:'" + params + "'}",
|
||||
params
|
||||
];
|
||||
|
||||
if ( isBlock ) {
|
||||
stack.push( current );
|
||||
current = newNode;
|
||||
}
|
||||
content.push( newNode );
|
||||
} else if ( closeBlock ) {
|
||||
current = stack.pop();
|
||||
}
|
||||
loc = index + all.length; // location marker - parsed up to here
|
||||
if ( !current ) {
|
||||
throw "Expected block tag";
|
||||
}
|
||||
content = current[ 2 ];
|
||||
}
|
||||
markup = markup.replace( rEscapeQuotes, "\\$1" );
|
||||
markup.replace( rTag, parseTag );
|
||||
pushPreceedingContent( markup.length );
|
||||
return buildTmplFunction( topNode );
|
||||
}
|
||||
|
||||
// Build javascript compiled template function, from AST
|
||||
function buildTmplFunction( nodes ) {
|
||||
var ret, node, i,
|
||||
nested = [],
|
||||
l = nodes.length,
|
||||
code = "try{var views="
|
||||
+ (jQuery ? "jQuery" : "JsViews")
|
||||
+ '.views,tag=views.renderTag,enc=views.encode,html=views.encoders.html,$ctx=$view && $view.ctx,result=""+\n\n';
|
||||
|
||||
for ( i = 0; i < l; i++ ) {
|
||||
node = nodes[ i ];
|
||||
if ( node[ 0 ] === "*" ) {
|
||||
code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + ( i + 1 < l ? "result+=" : "" );
|
||||
} else if ( "" + node === node ) { // type string
|
||||
code += '"' + node + '"+';
|
||||
} else {
|
||||
var tag = node[ 0 ],
|
||||
encode = node[ 1 ],
|
||||
content = node[ 2 ],
|
||||
obj = node[ 3 ],
|
||||
params = node[ 4 ],
|
||||
paramsOrEmptyString = params + '||"")+';
|
||||
|
||||
if( content ) {
|
||||
nested.push( buildTmplFunction( content ));
|
||||
}
|
||||
code += tag === "="
|
||||
? (!encode || encode === "html"
|
||||
? "html(" + paramsOrEmptyString
|
||||
: encode === "none"
|
||||
? ("(" + 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 + ")+";
|
||||
}
|
||||
}
|
||||
ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" );
|
||||
ret.nested = nested;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//========================== Private helper functions, used by code above ==========================
|
||||
|
||||
function replacerForHtml( ch ) {
|
||||
// Original code from Mike Samuel <msamuel@google.com>
|
||||
return escapeMapForHtml[ ch ]
|
||||
// Intentional assignment that caches the result of encoding ch.
|
||||
|| ( escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";" );
|
||||
}
|
||||
|
||||
function try$( selector ) {
|
||||
// If selector is valid, return jQuery object, otherwise return (invalid) selector string
|
||||
try {
|
||||
return $( selector );
|
||||
} catch( e) {}
|
||||
return selector;
|
||||
}
|
||||
})( window );
|
||||
Reference in New Issue
Block a user