Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

adding initial release

  • Loading branch information...
commit 5de4a56ebfaf10cc5892edc1d03085b81682dc9e 1 parent 6bc748e
@addyosmani authored
View
155 application.css
@@ -0,0 +1,155 @@
+html, body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: "Helvetica Neue", helvetica, arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.4em;
+ background: #eeeeee;
+ color: #333333;
+}
+
+#views {
+ width: 520px;
+ margin: 0 auto 40px auto;
+ background: white;
+
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+
+ -moz-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ -webkit-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#tasks {
+ padding: 20px;
+}
+
+#tasks h1 {
+ font-size: 36px;
+ font-weight: bold;
+ text-align: center;
+ padding: 0 0 10px 0;
+}
+
+#tasks input[type="text"] {
+ width: 466px;
+ font-size: 24px;
+ font-family: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ padding: 6px;
+ border: 1px solid #999999;
+
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+}
+
+#tasks input::-webkit-input-placeholder {
+ font-style: italic;
+}
+
+#tasks .items {
+ margin: 10px 0;
+ list-style: none;
+}
+
+#tasks .item {
+ padding: 15px 20px 15px 0;
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px solid #cccccc;
+}
+
+#tasks .item.done span {
+ color: #777777;
+ text-decoration: line-through;
+}
+
+#tasks .item .destroy {
+ position: absolute;
+ right: 10px;
+ top: 16px;
+ display: none;
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+ background: url(destroy.png) no-repeat center center;
+}
+
+#tasks .item:hover .destroy {
+ display: block;
+}
+
+#tasks .item .edit { display: none; }
+#tasks .item.editing .edit { display: block; }
+#tasks .item.editing .view { display: none; }
+#tasks .item .original { clear:both; font-size:11px;}
+
+#tasks footer {
+ display: block;
+ margin: 20px -20px -20px -20px;
+ overflow: hidden;
+
+ color: #555555;
+ background: #f4fce8;
+ border-top: 1px solid #ededed;
+ padding: 0 20px;
+ line-height: 36px;
+
+ -moz-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ -webkit-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#tasks .clear {
+ display: block;
+ float: right;
+ line-height: 20px;
+ text-decoration: none;
+
+ background: rgba(0, 0, 0, 0.1);
+ color: #555555;
+ font-size: 11px;
+ margin-top: 8px;
+ padding: 0 10px 1px;
+
+ -moz-border-radius: 12px;
+ -webkit-border-radius: 12px;
+ -o-border-radius: 12px;
+ border-radius: 12px;
+
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+
+ cursor: pointer;
+}
+
+#tasks .clear:hover {
+ background: rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+}
+
+#tasks .clear:active {
+ position: relative;
+ top: 1px;
+}
+
+#tasks .count span {
+ font-weight: bold;
+}
View
BIN  destroy.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
248 index.html
@@ -0,0 +1,248 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+
+ <script src="jquery.js" type="text/javascript" charset="utf-8"></script>
+ <script src="jquery.tmpl.js" type="text/javascript" charset="utf-8"></script>
+ <script src="spine.tmpl.js" type="text/javascript" charset="utf-8"></script>
+ <script src="spine.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="spine.route.js" type="text/javascript" charset="utf-8"></script>
+ <script src="jquery.bitlydfd.js" type="text/javascript"></script>
+<script src="spine.model.local.js" type="text/javascript" charset="utf-8"></script>
+
+
+<link rel="stylesheet" type="text/css" href="application.css"/>
+
+
+ <script type="text/javascript" charset="utf-8">
+ var Users = Spine.Controller.create({
+ init: function(){
+ this.navigate("/step", '1');
+ },
+
+ active: function(number){
+ console.log("active", number)
+ }
+ });
+
+ // Fire event on load
+ Spine.Route.setup();
+
+ // Add routes in App controller
+ var App = Spine.Controller.create({
+ init: function(){
+ this.users = Users.init();
+ this.routes({
+ "/step/:number": function(num){
+ this.users.active(num);
+ },
+ "/step/*glob": function(any){
+ console.log('any', any);
+ }
+ });
+ }
+ }).init();
+
+ // Add routes manually
+
+ // Add a group of routes
+ Spine.Route.add({
+ "/groups/:id": function(id){
+ console.log('groups id', id)
+ }
+ });
+
+ // More generic routes added last
+
+ // Use your own regex
+ Spine.Route.add(/\/groups(\/)?/, function(){
+ console.log('groups')
+ });
+ </script>
+
+<script type="text/javascript">
+// Create the Task model.
+var Task = Spine.Model.setup("Task", ["name", "done", "original"]);
+
+// Persist model between page reloads.
+Task.extend(Spine.Model.Local);
+
+Task.extend({
+ // Return all active tasks.
+ active: function(){
+ return(this.select(function(item){ return !item.done; }));
+ },
+
+ // Return all done tasks.
+ done: function(){
+ return(this.select(function(item){ return !!item.done; }));
+ },
+
+ // Clear all done tasks.
+ destroyDone: function(){
+ this.done().forEach(function(rec){ rec.destroy() });
+ }
+});
+
+jQuery(function($){
+
+
+ window.Tasks = Spine.Controller.create({
+ tag: "li",
+
+ proxied: ["render", "remove"],
+
+ events: {
+ "change input[type=checkbox]": "toggle",
+ "click .destroy": "destroy",
+ "dblclick .view": "edit",
+ "keypress input[type=text]": "blurOnEnter",
+ "blur input[type=text]": "close"
+ },
+
+ elements: {
+ "input[type=text]": "input",
+ ".item": "wrapper"
+ },
+
+ init: function(){
+ this.item.bind("update", this.render);
+ this.item.bind("destroy", this.remove);
+ },
+
+ render: function(){
+ var elements = $("#taskTemplate").tmpl(this.item);
+ this.el.html(elements);
+ this.refreshElements();
+ return this;
+ },
+
+ toggle: function(){
+ this.item.done = !this.item.done;
+ this.item.save();
+ },
+
+ destroy: function(){
+ this.item.destroy();
+ },
+
+ edit: function(){
+ this.wrapper.addClass("editing");
+ this.input.focus();
+ },
+
+ blurOnEnter: function(e) {
+ if (e.keyCode == 13) e.target.blur();
+ },
+
+ close: function(){
+ this.wrapper.removeClass("editing");
+ this.item.updateAttributes({name: this.input.val()});
+ },
+
+ remove: function(){
+ this.el.remove();
+ }
+ });
+
+ window.TaskApp = Spine.Controller.create({
+ el: $("#tasks"),
+
+ proxied: ["addOne", "addAll", "renderCount"],
+
+ events: {
+ "submit form": "create",
+ "click .clear": "clear"
+ },
+
+ elements: {
+ ".items": "items",
+ ".countVal": "count",
+ ".clear": "clear",
+ "form input": "input"
+ },
+
+ init: function(){
+ Task.bind("create", this.addOne);
+ Task.bind("refresh", this.addAll);
+ Task.bind("refresh change", this.renderCount);
+ Task.fetch();
+ },
+
+ addOne: function(task) {
+ var view = Tasks.init({item: task});
+ this.items.append(view.render().el);
+
+ },
+
+ addAll: function() {
+ Task.each(this.addOne);
+ },
+
+ create: function(){
+ var q = this.input;
+ q.bitlyShorten({longUrl:this.input.val()});
+ this.input.val("");
+ return false;
+ },
+
+ clear: function(){
+ Task.destroyDone();
+ },
+
+ renderCount: function(){
+ var active = Task.active().length;
+ this.count.text(active);
+
+ var inactive = Task.done().length;
+ this.clear[inactive ? "show" : "hide"]();
+ }
+ });
+
+ window.App = TaskApp.init();
+});
+
+
+</script>
+</head>
+<body>
+
+
+ <script type="text/x-jquery-tmpl" id="taskTemplate">
+ <div class="item {{if done}}done{{/if}}">
+ <div class="view" title="Double click to edit...">
+ <input type="checkbox" {{if done}}checked="checked"{{/if}}>
+
+ <span>${name}</span>
+ <span class="original">${original}</span>
+ <a class="destroy"></a>
+ </div>
+
+ <div class="edit">
+ <input type="text" value="${name}">
+ </div>
+ </div>
+ </script>
+
+
+
+
+ <div id="views">
+ <div id="tasks">
+ <h1>ShorterUrl</h1>
+
+ <form>
+ <input class="inputUrl" type="text" placeholder="Enter the URL you would like shortened">
+ </form>
+
+ <div class="items"></div>
+ <footer>
+ <a class="clear">Clear completed</a>
+ </footer>
+ </div>
+ </div>
+
+
+</body>
+</html>
View
45 jquery.bitlydfd.js
@@ -0,0 +1,45 @@
+$.fn.bitlyShorten = function(options) {
+
+ var defaults = {
+ version: '2.0.1',
+ login: 'legacye',
+ apiKey: 'R_32f60d09cccde1f266bcba8c242bfb5a',
+ history: '0',
+ longUrl: '',
+ };
+
+ var args = arguments,
+ opts = $.extend(defaults,options),
+ collection = this,
+ result = "",
+ queryUrl = "http://api.bit.ly/shorten?"
+ +"version="+opts.version
+ +"&longUrl="+opts.longUrl
+ +"&login="+opts.login
+ +"&apiKey="+opts.apiKey
+ +"&history="+opts.history
+ +"&format=json&callback=?";
+
+ $.getJSON(queryUrl, function(data){
+ result = (data.results[defaults.longUrl].shortUrl);
+ })
+ .then(
+ function(){
+ if(result){
+ return collection.each(function() {
+ //$(this).val(result);
+ Task.create({name: result, original:opts.longUrl});
+ });
+ }
+ },
+ function(){
+ return collection.each(function() {
+ //$(this).val('Error: an issue was experienced when connecting to the bit.ly API');
+ alert('There was an error connecting to the Bit.ly API')
+ });
+ }
+);
+
+};
+
+
View
8,374 jquery.js
8,374 additions, 0 deletions not shown
View
503 jquery.tmpl.js
@@ -0,0 +1,503 @@
+/*!
+ * jQuery Templates Plugin
+ * http://github.com/jquery/jquery-tmpl
+ *
+ * Copyright Software Freedom Conservancy, Inc.
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+(function( jQuery, undefined ){
+ var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
+ newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
+
+ function newTmplItem( options, parentItem, fn, data ) {
+ // Returns a template item data structure for a new rendered instance of a template (a 'template item').
+ // The content field is a hierarchical array of strings and nested items (to be
+ // removed and replaced by nodes field of dom elements, once inserted in DOM).
+ var newItem = {
+ data: data || (parentItem ? parentItem.data : {}),
+ _wrap: parentItem ? parentItem._wrap : null,
+ tmpl: null,
+ parent: parentItem || null,
+ nodes: [],
+ calls: tiCalls,
+ nest: tiNest,
+ wrap: tiWrap,
+ html: tiHtml,
+ update: tiUpdate
+ };
+ if ( options ) {
+ jQuery.extend( newItem, options, { nodes: [], parent: parentItem } );
+ }
+ if ( fn ) {
+ // Build the hierarchical content to be used during insertion into DOM
+ newItem.tmpl = fn;
+ newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem );
+ newItem.key = ++itemKey;
+ // Keep track of new template item, until it is stored as jQuery Data on DOM element
+ (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
+ }
+ return newItem;
+ }
+
+ // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
+ jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+ }, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems,
+ parent = this.length === 1 && this[0].parentNode;
+
+ appendToTmplItems = newTmplItems || {};
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ ret = this;
+ } else {
+ for ( i = 0, l = insert.length; i < l; i++ ) {
+ cloneIndex = i;
+ elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+ ret = ret.concat( elems );
+ }
+ cloneIndex = 0;
+ ret = this.pushStack( ret, name, insert.selector );
+ }
+ tmplItems = appendToTmplItems;
+ appendToTmplItems = null;
+ jQuery.tmpl.complete( tmplItems );
+ return ret;
+ };
+ });
+
+ jQuery.fn.extend({
+ // Use first wrapped element as template markup.
+ // Return wrapped set of template items, obtained by rendering template against data.
+ tmpl: function( data, options, parentItem ) {
+ return jQuery.tmpl( this[0], data, options, parentItem );
+ },
+
+ // Find which rendered template item the first wrapped DOM element belongs to
+ tmplItem: function() {
+ return jQuery.tmplItem( this[0] );
+ },
+
+ tmplElement: function() {
+ return jQuery.tmplElement( this[0] );
+ },
+
+ // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
+ template: function( name ) {
+ return jQuery.template( name, this[0] );
+ },
+
+ domManip: function( args, table, callback, options ) {
+ // This appears to be a bug in the appendTo, etc. implementation
+ // it should be doing .call() instead of .apply(). See #6227
+ if ( args[0] && args[0].nodeType ) {
+ var dmArgs = jQuery.makeArray( arguments ), argsLength = args.length, i = 0, tmplItem;
+ while ( i < argsLength && !(tmplItem = jQuery.data( args[i++], "tmplItem" ))) {}
+ if ( argsLength > 1 ) {
+ dmArgs[0] = [jQuery.makeArray( args )];
+ }
+ if ( tmplItem && cloneIndex ) {
+ dmArgs[2] = function( fragClone ) {
+ // Handler called by oldManip when rendered template has been inserted into DOM.
+ jQuery.tmpl.afterManip( this, fragClone, callback );
+ };
+ }
+ oldManip.apply( this, dmArgs );
+ } else {
+ oldManip.apply( this, arguments );
+ }
+ cloneIndex = 0;
+ if ( !appendToTmplItems ) {
+ jQuery.tmpl.complete( newTmplItems );
+ }
+ return this;
+ }
+ });
+
+ jQuery.extend({
+ // Return wrapped set of template items, obtained by rendering template against data.
+ tmpl: function( tmpl, data, options, parentItem ) {
+ var ret, topLevel = !parentItem;
+ if ( topLevel ) {
+ // This is a top-level tmpl call (not from a nested template using {{tmpl}})
+ parentItem = topTmplItem;
+ if ( typeof tmpl != "function" )
+ tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl );
+ wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
+ } else if ( !tmpl ) {
+ // The template item is already associated with DOM - this is a refresh.
+ // Re-evaluate rendered template for the parentItem
+ tmpl = parentItem.tmpl;
+ newTmplItems[parentItem.key] = parentItem;
+ parentItem.nodes = [];
+ if ( parentItem.wrapped ) {
+ updateWrapped( parentItem, parentItem.wrapped );
+ }
+ // Rebuild, without creating a new template item
+ return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) ));
+ }
+ if ( !tmpl ) {
+ return []; // Could throw...
+ }
+ if ( typeof data === "function" ) {
+ data = data.call( parentItem || {} );
+ }
+ if ( options && options.wrapped ) {
+ updateWrapped( options, options.wrapped );
+ }
+ ret = jQuery.isArray( data ) ?
+ jQuery.map( data, function( dataItem ) {
+ return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;
+ }) :
+ [ newTmplItem( options, parentItem, tmpl, data ) ];
+ return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret;
+ },
+
+ // Return rendered template item for an element.
+ tmplItem: function( elem ) {
+ var tmplItem;
+ if ( elem instanceof jQuery ) {
+ elem = elem[0];
+ }
+ while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {}
+ return tmplItem || topTmplItem;
+ },
+
+ tmplElement: function( elem ) {
+ var tmplItem;
+ if ( elem instanceof jQuery ) {
+ elem = elem[0];
+ }
+ while ( elem && elem.nodeType === 1 && !jQuery.data( elem, "tmplItem" ) && (elem = elem.parentNode) ) {}
+ return elem;
+ },
+
+ // 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 includes at least one HTML tag, $.template( templateString ) is equivalent
+ // to $.template( null, templateString )
+ template: function( name, tmpl ) {
+ if (tmpl) {
+ // Compile template and associate with name
+ if ( typeof tmpl === "string" ) {
+ // This is an HTML string being passed directly in.
+ tmpl = buildTmplFn( tmpl )
+ } else if ( tmpl instanceof jQuery ) {
+ tmpl = tmpl[0] || {};
+ }
+ if ( tmpl.nodeType ) {
+ // If this is a template block, use cached copy, or generate tmpl function and cache.
+ tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML ));
+ }
+ return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;
+ }
+ // Return named compiled template
+ return name ? (typeof name !== "string" ? jQuery.template( null, name ):
+ (jQuery.template[name] ||
+ // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
+ jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null;
+ },
+
+ encode: function( text ) {
+ // Do HTML encoding replacing < > & and ' and " by corresponding entities.
+ return ("" + text).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
+ }
+ });
+
+ jQuery.extend( jQuery.tmpl, {
+ tag: {
+ "tmpl": {
+ _default: { $2: "null" },
+ open: "if($notnull_1){_=_.concat($item.nest($1,$2));}"
+ // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
+ // This means that {{tmpl foo}} treats foo as a template (which IS a function).
+ // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
+ },
+ "wrap": {
+ _default: { $2: "null" },
+ open: "$item.calls(_,$1,$2);_=[];",
+ close: "call=$item.calls();_=call._.concat($item.wrap(call,_));"
+ },
+ "each": {
+ _default: { $2: "$index, $value" },
+ open: "if($notnull_1){$.each($1a,function($2){with(this){",
+ close: "}});}"
+ },
+ "if": {
+ open: "if(($notnull_1) && $1a){",
+ close: "}"
+ },
+ "else": {
+ _default: { $1: "true" },
+ open: "}else if(($notnull_1) && $1a){"
+ },
+ "html": {
+ // Unecoded expression evaluation.
+ open: "if($notnull_1){_.push($1a);}"
+ },
+ "=": {
+ // Encoded expression evaluation. Abbreviated form is ${}.
+ _default: { $1: "$data" },
+ open: "if($notnull_1){_.push($.encode($1a));}"
+ },
+ "!": {
+ // Comment tag. Skipped by parser
+ open: ""
+ }
+ },
+
+ // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
+ complete: function( items ) {
+ newTmplItems = {};
+ },
+
+ // Call this from code which overrides domManip, or equivalent
+ // Manage cloning/storing template items etc.
+ afterManip: function afterManip( elem, fragClone, callback ) {
+ // Provides cloned fragment ready for fixup prior to and after insertion into DOM
+ var content = fragClone.nodeType === 11 ?
+ jQuery.makeArray(fragClone.childNodes) :
+ fragClone.nodeType === 1 ? [fragClone] : [];
+
+ // Return fragment to original caller (e.g. append) for DOM insertion
+ callback.call( elem, fragClone );
+
+ // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data.
+ storeTmplItems( content );
+ cloneIndex++;
+ }
+ });
+
+ //========================== Private helper functions, used by code above ==========================
+
+ function build( tmplItem, nested, content ) {
+ // Convert hierarchical content into flat string array
+ // and finally return array of fragments ready for DOM insertion
+ var frag, ret = content ? jQuery.map( content, function( item ) {
+ return (typeof item === "string") ?
+ // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
+ (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) :
+ // This is a child template item. Build nested template.
+ build( item, tmplItem, item._ctnt );
+ }) :
+ // 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()}}.
+ tmplItem;
+ if ( nested ) {
+ return ret;
+ }
+
+ // top-level template
+ ret = ret.join("");
+
+ // Support templates which have initial or final text nodes, or consist only of text
+ // Also support HTML entities within the HTML markup.
+ ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) {
+ frag = jQuery( middle ).get();
+
+ storeTmplItems( frag );
+ if ( before ) {
+ frag = unencode( before ).concat(frag);
+ }
+ if ( after ) {
+ frag = frag.concat(unencode( after ));
+ }
+ });
+ return frag ? frag : unencode( ret );
+ }
+
+ function unencode( text ) {
+ // Use createElement, since createTextNode will not render HTML entities correctly
+ var el = document.createElement( "div" );
+ el.innerHTML = text;
+ return jQuery.makeArray(el.childNodes);
+ }
+
+ // Generate a reusable function that will serve to render a template against data
+ function buildTmplFn( markup ) {
+ return new Function("jQuery","$item",
+ "var $=jQuery,call,_=[],$data=$item.data;" +
+
+ // Introduce the data as local variables using with(){}
+ "with($data){_.push('" +
+
+ // Convert the template into pure JavaScript
+ jQuery.trim(markup)
+ .replace( /([\\'])/g, "\\$1" )
+ .replace( /[\r\t\n]/g, " " )
+ .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" )
+ .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
+ function( all, slash, type, fnargs, target, parens, args ) {
+ var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect;
+ if ( !tag ) {
+ throw "Template command not found: " + type;
+ }
+ def = tag._default || [];
+ if ( parens && !/\w$/.test(target)) {
+ target += parens;
+ parens = "";
+ }
+ if ( target ) {
+ target = unescape( target );
+ args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : "");
+ // Support for target being things like a.toLowerCase();
+ // In that case don't call with template item as 'this' pointer. Just evaluate...
+ expr = parens ? (target.indexOf(".") > -1 ? target + parens : ("(" + target + ").call($data" + args)) : target;
+ exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
+ } else {
+ exprAutoFnDetect = expr = def.$1 || "null";
+ }
+ fnargs = unescape( fnargs );
+ return "');" +
+ tag[ slash ? "close" : "open" ]
+ .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" )
+ .split( "$1a" ).join( exprAutoFnDetect )
+ .split( "$1" ).join( expr )
+ .split( "$2" ).join( fnargs ?
+ fnargs.replace( /\s*([^\(]+)\s*(\((.*?)\))?/g, function( all, name, parens, params ) {
+ params = params ? ("," + params + ")") : (parens ? ")" : "");
+ return params ? ("(" + name + ").call($item" + params) : all;
+ })
+ : (def.$2||"")
+ ) +
+ "_.push('";
+ }) +
+ "');}return _;"
+ );
+ }
+ function updateWrapped( options, wrapped ) {
+ // Build the wrapped content.
+ options._wrap = build( options, true,
+ // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
+ jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()]
+ ).join("");
+ }
+
+ function unescape( args ) {
+ return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null;
+ }
+ function outerHtml( elem ) {
+ var div = document.createElement("div");
+ div.appendChild( elem.cloneNode(true) );
+ return div.innerHTML;
+ }
+
+ // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
+ function storeTmplItems( content ) {
+ var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
+ for ( i = 0, l = content.length; i < l; i++ ) {
+ if ( (elem = content[i]).nodeType !== 1 ) {
+ continue;
+ }
+ elems = elem.getElementsByTagName("*");
+ for ( m = elems.length - 1; m >= 0; m-- ) {
+ processItemKey( elems[m] );
+ }
+ processItemKey( elem );
+ }
+ function processItemKey( el ) {
+ var pntKey, pntNode = el, pntItem, tmplItem, key;
+ // Ensure that each rendered template inserted into the DOM has its own template item,
+ if ( (key = el.getAttribute( tmplItmAtt ))) {
+ while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { }
+ if ( pntKey !== key ) {
+ // The next ancestor with a _tmplitem expando is on a different key than this one.
+ // So this is a top-level element within this template item
+ // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
+ pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0;
+ if ( !(tmplItem = newTmplItems[key]) ) {
+ // The item is for wrapped content, and was copied from the temporary parent wrappedItem.
+ tmplItem = wrappedItems[key];
+ tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode], null, true );
+ tmplItem.key = ++itemKey;
+ newTmplItems[itemKey] = tmplItem;
+ }
+ if ( cloneIndex ) {
+ cloneTmplItem( key );
+ }
+ }
+ el.removeAttribute( tmplItmAtt );
+ } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) {
+ // This was a rendered element, cloned during append or appendTo etc.
+ // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
+ cloneTmplItem( tmplItem.key );
+ newTmplItems[tmplItem.key] = tmplItem;
+ pntNode = jQuery.data( el.parentNode, "tmplItem" );
+ pntNode = pntNode ? pntNode.key : 0;
+ }
+ if ( tmplItem ) {
+ pntItem = tmplItem;
+ // Find the template item of the parent element.
+ // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
+ while ( pntItem && pntItem.key != pntNode ) {
+ // Add this element as a top-level node for this rendered template item, as well as for any
+ // ancestor items between this item and the item of its parent element
+ pntItem.nodes.push( el );
+ pntItem = pntItem.parent;
+ }
+ // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
+ delete tmplItem._ctnt;
+ delete tmplItem._wrap;
+ // Store template item as jQuery data on the element
+ jQuery.data( el, "tmplItem", tmplItem );
+ }
+ function cloneTmplItem( key ) {
+ key = key + keySuffix;
+ tmplItem = newClonedItems[key] =
+ (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent, null, true ));
+ }
+ }
+ }
+
+ //---- Helper functions for template item ----
+
+ function tiCalls( content, tmpl, data, options ) {
+ if ( !content ) {
+ return stack.pop();
+ }
+ stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options });
+ }
+
+ function tiNest( tmpl, data, options ) {
+ // nested template, using {{tmpl}} tag
+ return jQuery.tmpl( jQuery.template( tmpl ), data, options, this );
+ }
+
+ function tiWrap( call, wrapped ) {
+ // nested template, using {{wrap}} tag
+ var options = call.options || {};
+ options.wrapped = wrapped;
+ // Apply the template, which may incorporate wrapped content,
+ return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item );
+ }
+
+ function tiHtml( filter, textOnly ) {
+ var wrapped = this._wrap;
+ return jQuery.map(
+ jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ),
+ function(e) {
+ return textOnly ?
+ e.innerText || e.textContent :
+ e.outerHTML || outerHtml(e);
+ });
+ }
+
+ function tiUpdate() {
+ var coll = this.nodes;
+ jQuery.tmpl( null, null, null, this).insertBefore( coll[0] );
+ jQuery( coll ).remove();
+ }
+})( jQuery );
View
49 spine.controller.manager.js
@@ -0,0 +1,49 @@
+(function(Spine, $){
+
+var Manager = Spine.Controller.Manager = Spine.Class.create();
+Manager.include(Spine.Events);
+
+Manager.include({
+ addAll: function(){
+ var args = Spine.makeArray(arguments);
+ for (var i=0; i < args.length; i++) this.add(args[i]);
+ },
+
+ add: function(controller){
+ if ( !controller ) throw("Controller required");
+
+ this.bind("change", function(current){
+ if (controller == current)
+ controller.activate();
+ else
+ controller.deactivate();
+ });
+
+ controller.active(this.proxy(function(){
+ this.trigger("change", controller);
+ }));
+ }
+});
+
+Spine.Controller.include({
+ active: function(callback){
+ (typeof callback == "function") ? this.bind("active", callback) : this.trigger("active");
+ return this;
+ },
+
+ isActive: function(){
+ return this.el.hasClass("active");
+ },
+
+ activate: function(){
+ this.el.addClass("active");
+ return this;
+ },
+
+ deactivate: function(){
+ this.el.removeClass("active");
+ return this;
+ }
+});
+
+})(Spine, Spine.$);
View
42 spine.list.js
@@ -0,0 +1,42 @@
+(function(Spine, $){
+
+ Spine.List = Spine.Controller.create({
+ events: {
+ "click .item": "click"
+ },
+
+ proxied: ["change"],
+
+ init: function(){
+ this.bind("change", this.change);
+ },
+
+ template: function(){ return arguments[0] },
+
+ change: function(item){
+ if ( !item ) return;
+ this.current = item;
+
+ this.children().removeClass("current");
+ this.children().forItem(this.current).addClass("current");
+ },
+
+ render: function(items){
+ if (items) this.items = items;
+ this.el.html(this.template(this.items));
+ this.change(this.current);
+ if ( !this.children(".current").length || !this.current )
+ this.children(":first").click();
+ },
+
+ children: function(sel){
+ return this.el.children(sel);
+ },
+
+ click: function(e){
+ var item = $(e.target).item();
+ this.trigger("change", item);
+ }
+ });
+
+})(Spine, Spine.$);
View
1  spine.min.js
@@ -0,0 +1 @@
+(function(){var g;if(typeof exports!=="undefined"){g=exports}else{g=this.Spine={}}g.version="0.0.3";var e=g.$=this.jQuery||this.Zepto;var b=g.makeArray=function(k){return Array.prototype.slice.call(k,0)};var j=g.Events={bind:function(n,o){var k=n.split(" ");var m=this._callbacks||(this._callbacks={});for(var l=0;l<k.length;l++){(this._callbacks[k[l]]||(this._callbacks[k[l]]=[])).push(o)}return this},trigger:function(){var m=b(arguments);var p=m.shift();var q,o,n,k;if(!(o=this._callbacks)){return false}if(!(q=this._callbacks[p])){return false}for(n=0,k=q.length;n<k;n++){if(q[n].apply(this,m)===false){break}}return true},unbind:function(o,q){if(!o){this._callbacks={};return this}var p,n,m,k;if(!(n=this._callbacks)){return this}if(!(p=this._callbacks[o])){return this}if(!q){delete this._callbacks[o];return this}for(m=0,k=p.length;m<k;m++){if(q===p[m]){p.splice(m,1);break}}return this}};var f=g.Log={trace:true,logPrefix:"(App)",log:function(){if(!this.trace){return}if(typeof console=="undefined"){return}var k=b(arguments);if(this.logPrefix){k.unshift(this.logPrefix)}console.log.apply(console,k);return this}};if(typeof Object.create!=="function"){Object.create=function(l){function k(){}k.prototype=l;return new k()}}var h=["included","extended"];var a=g.Class={inherited:function(){},created:function(){},prototype:{initializer:function(){},init:function(){}},create:function(k,m){var l=Object.create(this);l.parent=this;l.prototype=l.fn=Object.create(this.prototype);if(k){l.include(k)}if(m){l.extend(m)}l.created();this.inherited(l);return l},init:function(){var k=Object.create(this.prototype);k.parent=this;k.initializer.apply(k,arguments);k.init.apply(k,arguments);return k},proxy:function(l){var k=this;return(function(){return l.apply(k,arguments)})},proxyAll:function(){var l=b(arguments);for(var k=0;k<l.length;k++){this[l[k]]=this.proxy(this[l[k]])}},include:function(m){for(var k in m){if(h.indexOf(k)==-1){this.fn[k]=m[k]}}var l=m.included;if(l){l.apply(this)}return this},extend:function(m){for(var l in m){if(h.indexOf(l)==-1){this[l]=m[l]}}var k=m.extended;if(k){k.apply(this)}return this}};a.prototype.proxy=a.proxy;a.prototype.proxyAll=a.proxyAll;a.inst=a.init;a.sub=a.create;g.guid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(m){var l=Math.random()*16|0,k=m=="x"?l:(l&3|8);return k.toString(16)}).toUpperCase()};var c=g.Model=a.create();c.extend(j);c.extend({setup:function(l,m){var k=c.sub();if(l){k.name=l}if(m){k.attributes=m}return k},created:function(k){this.records={};this.attributes=[];this.bind("create",this.proxy(function(l){this.trigger("change","create",l)}));this.bind("update",this.proxy(function(l){this.trigger("change","update",l)}));this.bind("destroy",this.proxy(function(l){this.trigger("change","destroy",l)}))},find:function(l){var k=this.records[l];if(!k){throw ("Unknown record")}return k.dup()},exists:function(l){try{return this.find(l)}catch(k){return false}},refresh:function(m){this.records={};for(var n=0,l=m.length;n<l;n++){var k=this.init(m[n]);k.newRecord=false;this.records[k.id]=k}this.trigger("refresh")},select:function(m){var k=[];for(var l in this.records){if(m(this.records[l])){k.push(this.records[l])}}return this.dupArray(k)},findByAttribute:function(k,m){for(var l in this.records){if(this.records[l][k]==m){return this.records[l].dup()}}},findAllByAttribute:function(k,l){return(this.select(function(m){return(m[k]==l)}))},each:function(l){for(var k in this.records){l(this.records[k])}},all:function(){return this.dupArray(this.recordsValues())},first:function(){var k=this.recordsValues()[0];return(k&&k.dup())},last:function(){var l=this.recordsValues();var k=l[l.length-1];return(k&&k.dup())},count:function(){return this.recordsValues().length},deleteAll:function(){for(var k in this.records){delete this.records[k]}},destroyAll:function(){for(var k in this.records){this.records[k].destroy()}},update:function(l,k){this.find(l).updateAttributes(k)},create:function(l){var k=this.init(l);k.save();return k},destroy:function(k){this.find(k).destroy()},sync:function(k){this.bind("change",k)},fetch:function(k){k?this.bind("fetch",k):this.trigger("fetch")},toJSON:function(){return this.recordsValues()},recordsValues:function(){var k=[];for(var l in this.records){k.push(this.records[l])}return k},dupArray:function(m){var k=[];for(var l=0;l<m.length;l++){k.push(m[l].dup())}return k}});c.include({model:true,newRecord:true,init:function(k){if(k){this.load(k)}},isNew:function(){return this.newRecord},validate:function(){},load:function(l){for(var k in l){this[k]=l[k]}},attributes:function(){var l={};for(var m=0;m<this.parent.attributes.length;m++){var k=this.parent.attributes[m];l[k]=this[k]}l.id=this.id;return l},eql:function(k){return(k&&k.id===this.id&&k.parent===this.parent)},save:function(){var k=this.validate();if(k){if(!this.trigger("error",k)){throw ("Validation failed: "+k)}}this.trigger("beforeSave");this.newRecord?this.create():this.update();this.trigger("save")},updateAttribute:function(k,l){this[k]=l;return this.save()},updateAttributes:function(k){this.load(k);return this.save()},destroy:function(){this.trigger("beforeDestroy");delete this.parent.records[this.id];this.trigger("destroy")},dup:function(){var k=this.parent.init(this.attributes());k.newRecord=this.newRecord;return k},reload:function(){return(this.parent.find(this.id))},toJSON:function(){return(this.attributes())},exists:function(){return(this.id&&this.id in this.parent.records)},update:function(){this.trigger("beforeUpdate");this.parent.records[this.id]=this.dup();this.trigger("update")},create:function(){this.trigger("beforeCreate");if(!this.id){this.id=g.guid()}this.newRecord=false;this.parent.records[this.id]=this.dup();this.trigger("create")},bind:function(k,l){return this.parent.bind(k,this.proxy(function(m){if(m&&this.eql(m)){l.apply(this,arguments)}}))},trigger:function(l){var k=b(arguments);k.splice(1,0,this);return this.parent.trigger.apply(this.parent,k)}});var i=/^(\w+)\s*(.*)$/;var d=g.Controller=a.create({tag:"div",initializer:function(k){this.options=k;for(var l in this.options){this[l]=this.options[l]}if(!this.el){this.el=document.createElement(this.tag)}this.el=e(this.el);if(!this.events){this.events=this.parent.events}if(!this.elements){this.elements=this.parent.elements}if(this.events){this.delegateEvents()}if(this.elements){this.refreshElements()}if(this.proxied){this.proxyAll.apply(this,this.proxied)}},$:function(k){return e(k,this.el)},delegateEvents:function(){for(var o in this.events){var m=this.events[o];var p=this.proxy(this[m]);var n=o.match(i);var l=n[1],k=n[2];if(k===""){this.el.bind(l,p)}else{this.el.delegate(k,l,p)}}},refreshElements:function(){for(var k in this.elements){this[this.elements[k]]=this.$(k)}},delay:function(k,l){setTimeout(this.proxy(k),l||0)}});d.include(j);d.include(f);g.App=d.create({create:function(k){this.parent.include(k);return this}}).init();d.fn.App=g.App})();
View
95 spine.model.ajax.js
@@ -0,0 +1,95 @@
+(function(Spine, $){
+
+var Model = Spine.Model;
+
+var getUrl = function(object){
+ if (!(object && object.url)) return null;
+ return((typeof object.url == "function") ? object.url() : object.url);
+};
+
+var methodMap = {
+ "create": "POST",
+ "update": "PUT",
+ "destroy": "DELETE",
+ "read": "GET"
+};
+
+var urlError = function() {
+ throw new Error("A 'url' property or function must be specified");
+};
+
+var ajaxSync = function(method, record){
+ if (Model._noSync) return;
+
+ var params = {
+ type: methodMap[method],
+ contentType: "application/json",
+ dataType: "json",
+ processData: false
+ };
+
+ if (method == "create" && record.model)
+ params.url = getUrl(record.parent);
+ else
+ params.url = getUrl(record);
+
+ if (!params.url) throw("Invalid URL");
+
+ if (method == "create" || method == "update") {
+ var data = {};
+
+ if (Model.ajaxPrefix) {
+ var prefix = record.parent.name.toLowerCase();
+ data[prefix] = record;
+ } else {
+ data = record;
+ }
+
+ params.data = JSON.stringify(data);
+ }
+
+ if (method == "read")
+ params.success = function(data){
+ (record.refresh || record.load).call(record, data);
+ };
+
+ params.error = function(xhr, s, e){
+ record.trigger("ajaxError", xhr, s, e);
+ };
+
+ $.ajax(params);
+};
+
+Model.Ajax = {
+ extended: function(){
+ this.sync(ajaxSync);
+ this.fetch(this.proxy(function(){
+ ajaxSync("read", this);
+ }));
+ }
+};
+
+Model.extend({
+ ajaxPrefix: false,
+
+ url: function() {
+ return "/" + this.name.toLowerCase() + "s"
+ },
+
+ noSync: function(callback){
+ Model._noSync = true;
+ callback.apply(callback, arguments);
+ Model._noSync = false;
+ }
+});
+
+Model.include({
+ url: function(){
+ var base = getUrl(this.parent);
+ base += (base.charAt(base.length - 1) == "/" ? "" : "/");
+ base += encodeURIComponent(this.id);
+ return base;
+ }
+});
+
+})(Spine, Spine.$);
View
18 spine.model.local.js
@@ -0,0 +1,18 @@
+Spine.Model.Local = {
+ extended: function(){
+ this.sync(this.proxy(this.saveLocal));
+ this.fetch(this.proxy(this.loadLocal));
+ },
+
+ saveLocal: function(){
+ var result = JSON.stringify(this);
+ localStorage[this.name] = result;
+ },
+
+ loadLocal: function(){
+ var result = localStorage[this.name];
+ if ( !result ) return;
+ var result = JSON.parse(result);
+ this.refresh(result);
+ }
+};
View
130 spine.route.js
@@ -0,0 +1,130 @@
+// Spine routing, based on Backbone's implementation.
+// Backbone.js 0.3.3
+// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://documentcloud.github.com/backbone
+//
+// For usage, see examples/route.html
+
+(function(Spine, $){
+ var Route = Spine.Route = Spine.Class.create();
+
+ var hashStrip = /^#*/;
+
+ Route.extend({
+ routes: [],
+
+ historySupport: ("history" in window),
+ history: false,
+
+ add: function(path, callback){
+ if (typeof path == "object")
+ for(var p in path) this.add(p, path[p]);
+ else
+ this.routes.push(this.init(path, callback));
+ },
+
+ setup: function(options){
+ if (options && options.history)
+ this.history = this.historySupport && options.history;
+
+ if ( this.history )
+ $(window).bind("popstate", this.change);
+ else
+ $(window).bind("hashchange", this.change);
+ this.change();
+ },
+
+ unbind: function(){
+ if (this.history)
+ $(window).unbind("popstate", this.change);
+ else
+ $(window).unbind("hashchange", this.change);
+ },
+
+ navigate: function(){
+ var path = Spine.makeArray(arguments).join("/");
+ if (this.path == path) return;
+ this.path = path;
+
+ if (this.history)
+ history.pushState({},
+ document.title,
+ this.getHost() + this.path
+ );
+ else
+ window.location.hash = this.path;
+ },
+
+ // Private
+
+ getPath: function(){
+ return window.location.pathname;
+ },
+
+ getHash: function(){
+ return window.location.hash;
+ },
+
+ getHost: function(){
+ return((document.location + "").replace(
+ this.getPath() + this.getHash(), ""
+ ));
+ },
+
+ getFragment: function(){
+ return this.getHash().replace(hashStrip, "");
+ },
+
+ change: function(e){
+ var path = (this.history ? this.getPath() : this.getFragment());
+ if (path == this.path) return;
+ this.path = path;
+ for (var i=0; i < this.routes.length; i++)
+ if (this.routes[i].match(path)) return;
+ }
+ });
+
+ Route.proxyAll("change");
+
+ var namedParam = /:([\w\d]+)/g;
+ var splatParam = /\*([\w\d]+)/g;
+ var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+ Route.include({
+ init: function(path, callback){
+ this.callback = callback;
+ if (typeof path == "string") {
+ path = path.replace(escapeRegExp, "\\$&")
+ .replace(namedParam, "([^\/]*)")
+ .replace(splatParam, "(.*?)");
+
+ this.route = new RegExp('^' + path + '$');
+ } else {
+ this.route = path;
+ }
+ },
+
+ match: function(path){
+ var match = this.route.exec(path)
+ if ( !match ) return false;
+ var params = match.slice(1);
+ this.callback.apply(this.callback, params);
+ return true;
+ }
+ });
+
+ Spine.Controller.fn.route = function(path, callback){
+ Spine.Route.add(path, this.proxy(callback));
+ };
+
+ Spine.Controller.fn.routes = function(routes){
+ for(var path in routes)
+ this.route(path, routes[path]);
+ };
+
+ Spine.Controller.fn.navigate = function(){
+ Spine.Route.navigate.apply(Spine.Route, arguments);
+ };
+})(Spine, Spine.$);
View
18 spine.tmpl.js
@@ -0,0 +1,18 @@
+// jQuery.tmpl.js utilities
+
+(function($){
+
+$.fn.item = function(){
+ var item = $(this).tmplItem().data;
+ return($.isFunction(item.reload) ? item.reload() : null);
+};
+
+$.fn.forItem = function(item){
+ return this.filter(function(){
+ var compare = $(this).tmplItem().data;
+ if (item.eql && item.eql(compare) || item === compare)
+ return true;
+ });
+};
+
+})(jQuery);
Please sign in to comment.
Something went wrong with that request. Please try again.