moel@348: /*! moel@348: * jQuery Templates Plugin 1.0.0pre moel@348: * http://github.com/jquery/jquery-tmpl moel@348: * Requires jQuery 1.4.2 moel@348: * moel@348: * Copyright 2011, Software Freedom Conservancy, Inc. moel@348: * Dual licensed under the MIT or GPL Version 2 licenses. moel@348: * http://jquery.org/license moel@348: */ moel@348: (function( jQuery, undefined ){ moel@348: var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /, moel@348: newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; moel@348: moel@348: function newTmplItem( options, parentItem, fn, data ) { moel@348: // Returns a template item data structure for a new rendered instance of a template (a 'template item'). moel@348: // The content field is a hierarchical array of strings and nested items (to be moel@348: // removed and replaced by nodes field of dom elements, once inserted in DOM). moel@348: var newItem = { moel@348: data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}), moel@348: _wrap: parentItem ? parentItem._wrap : null, moel@348: tmpl: null, moel@348: parent: parentItem || null, moel@348: nodes: [], moel@348: calls: tiCalls, moel@348: nest: tiNest, moel@348: wrap: tiWrap, moel@348: html: tiHtml, moel@348: update: tiUpdate moel@348: }; moel@348: if ( options ) { moel@348: jQuery.extend( newItem, options, { nodes: [], parent: parentItem }); moel@348: } moel@348: if ( fn ) { moel@348: // Build the hierarchical content to be used during insertion into DOM moel@348: newItem.tmpl = fn; moel@348: newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); moel@348: newItem.key = ++itemKey; moel@348: // Keep track of new template item, until it is stored as jQuery Data on DOM element moel@348: (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; moel@348: } moel@348: return newItem; moel@348: } moel@348: moel@348: // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). moel@348: jQuery.each({ moel@348: appendTo: "append", moel@348: prependTo: "prepend", moel@348: insertBefore: "before", moel@348: insertAfter: "after", moel@348: replaceAll: "replaceWith" moel@348: }, function( name, original ) { moel@348: jQuery.fn[ name ] = function( selector ) { moel@348: var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, moel@348: parent = this.length === 1 && this[0].parentNode; moel@348: moel@348: appendToTmplItems = newTmplItems || {}; moel@348: if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { moel@348: insert[ original ]( this[0] ); moel@348: ret = this; moel@348: } else { moel@348: for ( i = 0, l = insert.length; i < l; i++ ) { moel@348: cloneIndex = i; moel@348: elems = (i > 0 ? this.clone(true) : this).get(); moel@348: jQuery( insert[i] )[ original ]( elems ); moel@348: ret = ret.concat( elems ); moel@348: } moel@348: cloneIndex = 0; moel@348: ret = this.pushStack( ret, name, insert.selector ); moel@348: } moel@348: tmplItems = appendToTmplItems; moel@348: appendToTmplItems = null; moel@348: jQuery.tmpl.complete( tmplItems ); moel@348: return ret; moel@348: }; moel@348: }); moel@348: moel@348: jQuery.fn.extend({ moel@348: // Use first wrapped element as template markup. moel@348: // Return wrapped set of template items, obtained by rendering template against data. moel@348: tmpl: function( data, options, parentItem ) { moel@348: return jQuery.tmpl( this[0], data, options, parentItem ); moel@348: }, moel@348: moel@348: // Find which rendered template item the first wrapped DOM element belongs to moel@348: tmplItem: function() { moel@348: return jQuery.tmplItem( this[0] ); moel@348: }, moel@348: moel@348: // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. moel@348: template: function( name ) { moel@348: return jQuery.template( name, this[0] ); moel@348: }, moel@348: moel@348: domManip: function( args, table, callback, options ) { moel@348: if ( args[0] && jQuery.isArray( args[0] )) { moel@348: var dmArgs = jQuery.makeArray( arguments ), elems = args[0], elemsLength = elems.length, i = 0, tmplItem; moel@348: while ( i < elemsLength && !(tmplItem = jQuery.data( elems[i++], "tmplItem" ))) {} moel@348: if ( tmplItem && cloneIndex ) { moel@348: dmArgs[2] = function( fragClone ) { moel@348: // Handler called by oldManip when rendered template has been inserted into DOM. moel@348: jQuery.tmpl.afterManip( this, fragClone, callback ); moel@348: }; moel@348: } moel@348: oldManip.apply( this, dmArgs ); moel@348: } else { moel@348: oldManip.apply( this, arguments ); moel@348: } moel@348: cloneIndex = 0; moel@348: if ( !appendToTmplItems ) { moel@348: jQuery.tmpl.complete( newTmplItems ); moel@348: } moel@348: return this; moel@348: } moel@348: }); moel@348: moel@348: jQuery.extend({ moel@348: // Return wrapped set of template items, obtained by rendering template against data. moel@348: tmpl: function( tmpl, data, options, parentItem ) { moel@348: var ret, topLevel = !parentItem; moel@348: if ( topLevel ) { moel@348: // This is a top-level tmpl call (not from a nested template using {{tmpl}}) moel@348: parentItem = topTmplItem; moel@348: tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); moel@348: wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level moel@348: } else if ( !tmpl ) { moel@348: // The template item is already associated with DOM - this is a refresh. moel@348: // Re-evaluate rendered template for the parentItem moel@348: tmpl = parentItem.tmpl; moel@348: newTmplItems[parentItem.key] = parentItem; moel@348: parentItem.nodes = []; moel@348: if ( parentItem.wrapped ) { moel@348: updateWrapped( parentItem, parentItem.wrapped ); moel@348: } moel@348: // Rebuild, without creating a new template item moel@348: return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); moel@348: } moel@348: if ( !tmpl ) { moel@348: return []; // Could throw... moel@348: } moel@348: if ( typeof data === "function" ) { moel@348: data = data.call( parentItem || {} ); moel@348: } moel@348: if ( options && options.wrapped ) { moel@348: updateWrapped( options, options.wrapped ); moel@348: } moel@348: ret = jQuery.isArray( data ) ? moel@348: jQuery.map( data, function( dataItem ) { moel@348: return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; moel@348: }) : moel@348: [ newTmplItem( options, parentItem, tmpl, data ) ]; moel@348: return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; moel@348: }, moel@348: moel@348: // Return rendered template item for an element. moel@348: tmplItem: function( elem ) { moel@348: var tmplItem; moel@348: if ( elem instanceof jQuery ) { moel@348: elem = elem[0]; moel@348: } moel@348: while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} moel@348: return tmplItem || topTmplItem; moel@348: }, moel@348: moel@348: // Set: moel@348: // Use $.template( name, tmpl ) to cache a named template, moel@348: // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. moel@348: // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. moel@348: moel@348: // Get: moel@348: // Use $.template( name ) to access a cached template. moel@348: // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) moel@348: // will return the compiled template, without adding a name reference. moel@348: // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent moel@348: // to $.template( null, templateString ) moel@348: template: function( name, tmpl ) { moel@348: if (tmpl) { moel@348: // Compile template and associate with name moel@348: if ( typeof tmpl === "string" ) { moel@348: // This is an HTML string being passed directly in. moel@348: tmpl = buildTmplFn( tmpl ); moel@348: } else if ( tmpl instanceof jQuery ) { moel@348: tmpl = tmpl[0] || {}; moel@348: } moel@348: if ( tmpl.nodeType ) { moel@348: // If this is a template block, use cached copy, or generate tmpl function and cache. moel@348: tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); moel@348: // Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space. moel@348: // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x. moel@348: // To correct this, include space in tag: foo="${ x }" -> foo="value of x" moel@348: } moel@348: return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; moel@348: } moel@348: // Return named compiled template moel@348: return name ? (typeof name !== "string" ? jQuery.template( null, name ): moel@348: (jQuery.template[name] || moel@348: // If not in map, and not containing at least on HTML tag, treat as a selector. moel@348: // (If integrated with core, use quickExpr.exec) moel@348: jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; moel@348: }, moel@348: moel@348: encode: function( text ) { moel@348: // Do HTML encoding replacing < > & and ' and " by corresponding entities. moel@348: return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); moel@348: } moel@348: }); moel@348: moel@348: jQuery.extend( jQuery.tmpl, { moel@348: tag: { moel@348: "tmpl": { moel@348: _default: { $2: "null" }, moel@348: open: "if($notnull_1){__=__.concat($item.nest($1,$2));}" moel@348: // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) moel@348: // This means that {{tmpl foo}} treats foo as a template (which IS a function). moel@348: // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. moel@348: }, moel@348: "wrap": { moel@348: _default: { $2: "null" }, moel@348: open: "$item.calls(__,$1,$2);__=[];", moel@348: close: "call=$item.calls();__=call._.concat($item.wrap(call,__));" moel@348: }, moel@348: "each": { moel@348: _default: { $2: "$index, $value" }, moel@348: open: "if($notnull_1){$.each($1a,function($2){with(this){", moel@348: close: "}});}" moel@348: }, moel@348: "if": { moel@348: open: "if(($notnull_1) && $1a){", moel@348: close: "}" moel@348: }, moel@348: "else": { moel@348: _default: { $1: "true" }, moel@348: open: "}else if(($notnull_1) && $1a){" moel@348: }, moel@348: "html": { moel@348: // Unecoded expression evaluation. moel@348: open: "if($notnull_1){__.push($1a);}" moel@348: }, moel@348: "=": { moel@348: // Encoded expression evaluation. Abbreviated form is ${}. moel@348: _default: { $1: "$data" }, moel@348: open: "if($notnull_1){__.push($.encode($1a));}" moel@348: }, moel@348: "!": { moel@348: // Comment tag. Skipped by parser moel@348: open: "" moel@348: } moel@348: }, moel@348: moel@348: // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events moel@348: complete: function( items ) { moel@348: newTmplItems = {}; moel@348: }, moel@348: moel@348: // Call this from code which overrides domManip, or equivalent moel@348: // Manage cloning/storing template items etc. moel@348: afterManip: function afterManip( elem, fragClone, callback ) { moel@348: // Provides cloned fragment ready for fixup prior to and after insertion into DOM moel@348: var content = fragClone.nodeType === 11 ? moel@348: jQuery.makeArray(fragClone.childNodes) : moel@348: fragClone.nodeType === 1 ? [fragClone] : []; moel@348: moel@348: // Return fragment to original caller (e.g. append) for DOM insertion moel@348: callback.call( elem, fragClone ); moel@348: moel@348: // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. moel@348: storeTmplItems( content ); moel@348: cloneIndex++; moel@348: } moel@348: }); moel@348: moel@348: //========================== Private helper functions, used by code above ========================== moel@348: moel@348: function build( tmplItem, nested, content ) { moel@348: // Convert hierarchical content into flat string array moel@348: // and finally return array of fragments ready for DOM insertion moel@348: var frag, ret = content ? jQuery.map( content, function( item ) { moel@348: return (typeof item === "string") ? moel@348: // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. moel@348: (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : moel@348: // This is a child template item. Build nested template. moel@348: build( item, tmplItem, item._ctnt ); moel@348: }) : moel@348: // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}. moel@348: tmplItem; moel@348: if ( nested ) { moel@348: return ret; moel@348: } moel@348: moel@348: // top-level template moel@348: ret = ret.join(""); moel@348: moel@348: // Support templates which have initial or final text nodes, or consist only of text moel@348: // Also support HTML entities within the HTML markup. moel@348: ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { moel@348: frag = jQuery( middle ).get(); moel@348: moel@348: storeTmplItems( frag ); moel@348: if ( before ) { moel@348: frag = unencode( before ).concat(frag); moel@348: } moel@348: if ( after ) { moel@348: frag = frag.concat(unencode( after )); moel@348: } moel@348: }); moel@348: return frag ? frag : unencode( ret ); moel@348: } moel@348: moel@348: function unencode( text ) { moel@348: // Use createElement, since createTextNode will not render HTML entities correctly moel@348: var el = document.createElement( "div" ); moel@348: el.innerHTML = text; moel@348: return jQuery.makeArray(el.childNodes); moel@348: } moel@348: moel@348: // Generate a reusable function that will serve to render a template against data moel@348: function buildTmplFn( markup ) { moel@348: return new Function("jQuery","$item", moel@348: // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10). moel@348: "var $=jQuery,call,__=[],$data=$item.data;" + moel@348: moel@348: // Introduce the data as local variables using with(){} moel@348: "with($data){__.push('" + moel@348: moel@348: // Convert the template into pure JavaScript moel@348: jQuery.trim(markup) moel@348: .replace( /([\\'])/g, "\\$1" ) moel@348: .replace( /[\r\t\n]/g, " " ) moel@348: .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) moel@348: .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, moel@348: function( all, slash, type, fnargs, target, parens, args ) { moel@348: var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; moel@348: if ( !tag ) { moel@348: throw "Unknown template tag: " + type; moel@348: } moel@348: def = tag._default || []; moel@348: if ( parens && !/\w$/.test(target)) { moel@348: target += parens; moel@348: parens = ""; moel@348: } moel@348: if ( target ) { moel@348: target = unescape( target ); moel@348: args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); moel@348: // Support for target being things like a.toLowerCase(); moel@348: // In that case don't call with template item as 'this' pointer. Just evaluate... moel@348: expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target; moel@348: exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; moel@348: } else { moel@348: exprAutoFnDetect = expr = def.$1 || "null"; moel@348: } moel@348: fnargs = unescape( fnargs ); moel@348: return "');" + moel@348: tag[ slash ? "close" : "open" ] moel@348: .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) moel@348: .split( "$1a" ).join( exprAutoFnDetect ) moel@348: .split( "$1" ).join( expr ) moel@348: .split( "$2" ).join( fnargs || def.$2 || "" ) + moel@348: "__.push('"; moel@348: }) + moel@348: "');}return __;" moel@348: ); moel@348: } moel@348: function updateWrapped( options, wrapped ) { moel@348: // Build the wrapped content. moel@348: options._wrap = build( options, true, moel@348: // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. moel@348: jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] moel@348: ).join(""); moel@348: } moel@348: moel@348: function unescape( args ) { moel@348: return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; moel@348: } moel@348: function outerHtml( elem ) { moel@348: var div = document.createElement("div"); moel@348: div.appendChild( elem.cloneNode(true) ); moel@348: return div.innerHTML; moel@348: } moel@348: moel@348: // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. moel@348: function storeTmplItems( content ) { moel@348: var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; moel@348: for ( i = 0, l = content.length; i < l; i++ ) { moel@348: if ( (elem = content[i]).nodeType !== 1 ) { moel@348: continue; moel@348: } moel@348: elems = elem.getElementsByTagName("*"); moel@348: for ( m = elems.length - 1; m >= 0; m-- ) { moel@348: processItemKey( elems[m] ); moel@348: } moel@348: processItemKey( elem ); moel@348: } moel@348: function processItemKey( el ) { moel@348: var pntKey, pntNode = el, pntItem, tmplItem, key; moel@348: // Ensure that each rendered template inserted into the DOM has its own template item, moel@348: if ( (key = el.getAttribute( tmplItmAtt ))) { moel@348: while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } moel@348: if ( pntKey !== key ) { moel@348: // The next ancestor with a _tmplitem expando is on a different key than this one. moel@348: // So this is a top-level element within this template item moel@348: // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. moel@348: pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; moel@348: if ( !(tmplItem = newTmplItems[key]) ) { moel@348: // The item is for wrapped content, and was copied from the temporary parent wrappedItem. moel@348: tmplItem = wrappedItems[key]; moel@348: tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] ); moel@348: tmplItem.key = ++itemKey; moel@348: newTmplItems[itemKey] = tmplItem; moel@348: } moel@348: if ( cloneIndex ) { moel@348: cloneTmplItem( key ); moel@348: } moel@348: } moel@348: el.removeAttribute( tmplItmAtt ); moel@348: } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { moel@348: // This was a rendered element, cloned during append or appendTo etc. moel@348: // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. moel@348: cloneTmplItem( tmplItem.key ); moel@348: newTmplItems[tmplItem.key] = tmplItem; moel@348: pntNode = jQuery.data( el.parentNode, "tmplItem" ); moel@348: pntNode = pntNode ? pntNode.key : 0; moel@348: } moel@348: if ( tmplItem ) { moel@348: pntItem = tmplItem; moel@348: // Find the template item of the parent element. moel@348: // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) moel@348: while ( pntItem && pntItem.key != pntNode ) { moel@348: // Add this element as a top-level node for this rendered template item, as well as for any moel@348: // ancestor items between this item and the item of its parent element moel@348: pntItem.nodes.push( el ); moel@348: pntItem = pntItem.parent; moel@348: } moel@348: // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... moel@348: delete tmplItem._ctnt; moel@348: delete tmplItem._wrap; moel@348: // Store template item as jQuery data on the element moel@348: jQuery.data( el, "tmplItem", tmplItem ); moel@348: } moel@348: function cloneTmplItem( key ) { moel@348: key = key + keySuffix; moel@348: tmplItem = newClonedItems[key] = moel@348: (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent )); moel@348: } moel@348: } moel@348: } moel@348: moel@348: //---- Helper functions for template item ---- moel@348: moel@348: function tiCalls( content, tmpl, data, options ) { moel@348: if ( !content ) { moel@348: return stack.pop(); moel@348: } moel@348: stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); moel@348: } moel@348: moel@348: function tiNest( tmpl, data, options ) { moel@348: // nested template, using {{tmpl}} tag moel@348: return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); moel@348: } moel@348: moel@348: function tiWrap( call, wrapped ) { moel@348: // nested template, using {{wrap}} tag moel@348: var options = call.options || {}; moel@348: options.wrapped = wrapped; moel@348: // Apply the template, which may incorporate wrapped content, moel@348: return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); moel@348: } moel@348: moel@348: function tiHtml( filter, textOnly ) { moel@348: var wrapped = this._wrap; moel@348: return jQuery.map( moel@348: jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), moel@348: function(e) { moel@348: return textOnly ? moel@348: e.innerText || e.textContent : moel@348: e.outerHTML || outerHtml(e); moel@348: }); moel@348: } moel@348: moel@348: function tiUpdate() { moel@348: var coll = this.nodes; moel@348: jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); moel@348: jQuery( coll ).remove(); moel@348: } moel@348: })( jQuery );