Skip to content

Commit

Permalink
Added support for sorting, filtering, selective updating, selective
Browse files Browse the repository at this point in the history
removing, of items/rows within a set of rendered templates.
This is done using a static 'tmplCmd' plugin.
Removed previous 'update' and 'remove' methods on template context, in favor
of this approach using static methods, which allows passing one or more
data items or template contexts, for removing/replacing/updating...

Movie demo now includes sorting.
  • Loading branch information
BorisMoore committed Jun 16, 2010
1 parent a118f34 commit cd27dfc
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 153 deletions.
11 changes: 8 additions & 3 deletions demos/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,20 @@

jQuery(function(){
var tmpl = '<li><a href="${url}">${getName()}</a> {{if $ctx.showCities}}(${cityJoin()}){{/if}}</li>';
$.templates.myTmpl = $.tmpl('<li>My template: <a href="${url}">${getName()}</a> {{if $ctx.showCities}}(${cityJoin()}){{/if}}</li>');

// Appends one LI, filled with data, into the UL
$("ul").append( tmpl, dataObject );

var cityCtxs;
// Appends multiple LIs for each item. Use options referenced in tmpl string
$("ul").append( "#sometmpl", arrayOfDataObjects, { array: arrayOfDataObjects, showCities: true } );
$("ul").prepend( "#sometmpl", arrayOfDataObjects, { array: arrayOfDataObjects, showCities: true, newCtxs: cityCtxs } );

// Example of template that has leading or trailing text
$("div").append( "#leadingOrTrailingText", arrayOfDataObjects );
$("div").before( "#leadingOrTrailingText", arrayOfDataObjects );

// Use template from $.templates. Target wrapped set has more than one element, so rendered template cloned into to places in DOM
$(".multiple").after( "myTmpl", dataObject );
});
</script>

Expand All @@ -76,6 +81,6 @@
${firstName} <strong>${lastName}</strong> <br/>
</script>

<ul></ul>
<ul><li class="multiple">first</li><li class="multiple">last</li></ul>

<div></div>
111 changes: 52 additions & 59 deletions jquery.tmpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,22 @@
(function(jQuery){
// Override the DOM manipulation function
var oldManip = jQuery.fn.domManip, tCtxAtt = "_tmplctx", filterAll = "[" + tCtxAtt + "]", itm, ob,
htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$/, newCtxs = {}, topCtx = newCtx({ key: 0 }), ctxKey = 0;
htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$/, newCtxs, topCtx = { key: 0 }, ctxKey = 0;

function newCtx( options, parentCtx, fn, data ) {
// Returns a template context for a new instance of a template.
// The content field is a hierarchical array of strings and nested contexts (to be
// removed and replaced by nodes field of dom elements, once inserted in DOM).
var newCtx = {
index: -1,
data: data || null,
tmpl: null,
parent: parentCtx || null,
nodes: [],
update: update,
remove: remove
nodes: []
// When we have wrapper/container templates - with a concept of where their 'child content' is, we can also support append and prepend
};
if ( options) {
jQuery.extend( newCtx, options, { nodes: [], parent: parentCtx } );
fn = fn || (typeof options.tmpl === "function" ? options.tmpl : null);
}
if ( fn ) {
// Build the hierarchical content to be used during insertion into DOM
Expand All @@ -35,18 +34,6 @@
newCtxs[ctxKey] = newCtx;
}
return newCtx;

function update( context ) {
context = context ? jQuery.extend( this, context ) : this;
var nodes = context.nodes;
jQuery( nodes[0] ).before( context );
jQuery( nodes ).remove();
return this;
}
function remove() {
jQuery( this.nodes ).remove();
return this;
}
}

jQuery.fn.extend({
Expand All @@ -66,13 +53,15 @@
else if ( args.length >= 2 && typeof args[1] === "object" && !args[1].nodeType ) {
// args[1] is data, for a template. Eval template to obtain fragment to clone and insert
parentCtx = args[3] || topCtx;
newCtxs = {};
dmArgs[0] = [ jQuery.tmpl( args[0], parentCtx, args[1], args[2], true ) ];
}
else if ( args.length === 1 && typeof args[0] === "object" && !args[0].nodeType && !(args[0] instanceof jQuery) ) {
// args[0] is template context (already inserted in DOM) to be refreshed
newCtxs = {};
parentCtx = args[0];
newCtxs[parentCtx.key] = parentCtx;
dmArgs[0] = [ jQuery.tmpl( parentCtx ) ];
dmArgs[0] = [ jQuery.tmpl( null, parentCtx ) ];
dmArgs[1] = parentCtx.data;
}
if ( parentCtx ) {
Expand All @@ -84,12 +73,17 @@
cloneIndex = -1;

// Call onRendered for each inserted template instance.
ctxs = newCtxs;
newCtxs = {};
for ( itm in ctxs ) {
ob = ctxs[itm]; // Could test for hasOwnProperty...
if ( ob.rendered ) {
ob.rendered( ob );
if ( newCtxs ) {
ctxs = newCtxs;
newCtxs = null;
for ( itm in ctxs ) {
ob = ctxs[itm]; // Could test for hasOwnProperty...
if ( ob.newCtxs && jQuery.inArray( ob, ob.newCtxs ) === -1 ) {
ob.newCtxs.push( ob );
}
if ( ob.rendered ) {
ob.rendered( ob );
}
}
}
return this;
Expand All @@ -113,13 +107,14 @@
parent = ctx = newCtxs[key];
if ( cloneIndex ) {
key = key + keySuffix;
newCtxs[key] = newCtxs[key] || newCtx( ctx, newCtxs[ctx.parent.key + keySuffix] || ctx.parent, true );
newCtxs[key] = newCtxs[key] || newCtx( ctx, newCtxs[ctx.parent.key + keySuffix] || ctx.parent, null, true );
}
parentNodeCtx = jQuery.attr(this.parentNode, tCtxAtt) || 0;
while ( parent && parent.key != parentNodeCtx ) {
parent.nodes.push( this );
parent = parent.parent;
}
delete ctx.content; // Could keep this available. Currently deleting to reduce API surface area, and memory use...
jQuery.data( this, "tmplCtx", ctx );
}
}).removeAttr( tCtxAtt );
Expand All @@ -131,43 +126,38 @@

jQuery.extend({
tmpl: function( tmpl, parentCtx, data, options, domFrag ) {
var fn, targetCtx;
if ( arguments.length === 1 ) {
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
if ( (typeof tmpl === "string") && !htmlExpr.test( tmpl ) ) {
// it is a selector
tmpl = jQuery( tmpl )[0];
}
else if ( tmpl instanceof jQuery ) {
var fn, targetCtx, coll;
if ( !tmpl && arguments.length === 2) {
// Re-evaluate rendered template for the parentCtx
targetCtx = parentCtx;
tmpl = parentCtx.tmpl;
}
if ( typeof tmpl === "string" ) {
if ( htmlExpr.test( tmpl) ) {
// This is an HTML string being passed directly in.
// Assume the user doesn't want it cached.
// They can stick it in jQuery.templates to cache it.
tmpl = tmpl.get(0);
}
if ( tmpl.nodeType ) {
// Return template context for an element, unless element is a script block template declaration.
if (jQuery.attr( tmpl, "type") !== "text/html" ) {
while ( tmpl && !(tmplCtx = jQuery.data( tmpl, "tmplCtx" )) && (tmpl = tmpl.parentNode) ) {}
return tmplCtx || topCtx;
}
tmpl = tmplFn( tmpl )
} else if ( fn = jQuery.templates[ tmpl ] ) {
// Use a pre-defined template, if available
tmpl = fn;
} else {
// Render an updated template context, already associated with DOM.
targetCtx = tmpl;
tmpl = tmpl.tmpl;
}
}
if ( !tmpl ) {
return topCtx;
}
// If arguments.length > 1 render template against data, and return fragments ready for DOM insertion.
if ( typeof tmpl === "string" ) {
// Use a pre-defined template, if available
fn = jQuery.templates[ tmpl ];
if ( !fn ) {
fn = tmplFn( jQuery( tmpl )[0].innerHTML );
// It's a selector
tmpl = jQuery( tmpl )[0];
}
} else if ( typeof tmpl === "function" ) {
}
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
if ( tmpl instanceof jQuery ) {
tmpl = tmpl.get(0);
}
if ( tmpl.nodeType && arguments.length === 1 && jQuery.attr( tmpl, "type") !== "text/html" ) {
// Return template context for an element, unless element is a script block template declaration.
while ( tmpl && !(tmplCtx = jQuery.data( tmpl, "tmplCtx" )) && (tmpl = tmpl.parentNode) ) {}
return tmplCtx || topCtx;
}
// If arguments.length > 2, render template against data, and return fragments ready for DOM insertion.
if ( typeof tmpl === "function" ) {
fn = tmpl;
} else if ( tmpl.nodeType ) {
// If this is a template block, cache
Expand All @@ -188,6 +178,9 @@
if ( !data ) {
return fn;
}
if ( !parentCtx ) {
return []; //Could throw...
}
if ( typeof data === "function" ) {
data = data.call( parentCtx.data || {}, parentCtx );
}
Expand All @@ -198,7 +191,7 @@
[ newCtx( options, parentCtx, fn, data ) ];

return domFrag ? build( parentCtx ) : parentCtx.content;

function build( ctx, parent ) {
// Convert hierarchical content into flat string array
// and finally return array of fragments ready for DOM insertion
Expand Down Expand Up @@ -267,7 +260,7 @@
: (def["$2"]||"")
) +
"_.push('";
}) +
}) +
"');}return _;"
);
}
Expand Down
51 changes: 51 additions & 0 deletions jquery.tmplcmd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* jQuery Templating Plugin Commands
*/
(function(jQuery){
jQuery.extend({
tmplCmd: function( command, data, contexts ) {
var retCtxs = [], before;
data = jQuery.isArray( data ) ? data : [ data ];
switch ( command ) {
case "find":
return find( data, contexts );
case "replace":
data.reverse();
}
jQuery.each( contexts ? find( data, contexts ) : data, function( i, ctx ) {
coll = ctx.nodes;
switch ( command ) {
case "update":
jQuery( coll[0] ).before( ctx );
jQuery( coll ).remove();
break;
case "remove":
jQuery( coll ).remove();
if ( contexts ) {
contexts.splice( jQuery.inArray( ctx, contexts ), 1 );
}
break;
case "replace":
before = before ?
jQuery( coll ).insertBefore( before )[0] :
jQuery( coll ).appendTo( coll[0].parentNode )[0];
retCtxs.unshift( ctx );
}
});
return retCtxs;
function find( data, ctxs ) {
var found = [], ctx, ci, cl = ctxs.length, dataItem, di = 0, dl = data.length;
for ( ; di < dl; ) {
dataItem = data[di++];
for ( ci = 0; ci < cl; ) {
ctx = ctxs[ci++];
if ( ctx.data === dataItem ) {
found.push( ctx );
}
}
}
return found;
}
}
});
})(jQuery);
44 changes: 36 additions & 8 deletions movies/movies.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
font-size: 12px;
}

#about
{
text-align:center;
margin: auto;
margin-bottom: 5px;
background-color: #F9F5FA;
padding: 3px;
width: 50%;
}

#pageBody
{
display: block;
Expand Down Expand Up @@ -154,27 +164,45 @@
.cart td
{
height: 30px;
text-align:center;
font-size: 14px;
font-style: italic;
line-height:30px;
border: 3px double #00509f;
background-color: #F9F5FA;
text-align:center;
}

.cart .cart-true
{
background-color: #E8DAEB;
}

.cart input
.cart span.text
{
color: #2F5071;
float:none;
height: 25px;
line-height: 25px;
font-size: 14px;
font-style: italic;
border: none;
}

#submit, #cancel, #sort
{
color: #2F5071;
font-weight: 700;
float: right;
height:26px;
margin: 2px 15px 2px;
height: 25px;
line-height: 25px;
font-style: italic;
margin: 0 15px;
}

#submit, #cancel, #sortBtn
{
text-decoration: underline;
cursor: pointer;
}

#cancel
#submit
{
float: left;
}
Expand Down
Loading

0 comments on commit cd27dfc

Please sign in to comment.