(function(can, window, undefined){ // # CanJS v1.0.7 // (c) 2012 Bitovi // MIT license // [http://canjs.us/](http://canjs.us/) // jquery.js // --------- // _jQuery node list._ $.extend( can, jQuery, { trigger: function( obj, event, args ) { obj.trigger ? obj.trigger( event, args ) : $.event.trigger( event, args, obj, true ); }, addEvent: function(ev, cb){ $([this]).bind(ev, cb) return this; }, removeEvent: function(ev, cb){ $([this]).unbind(ev, cb) return this; }, // jquery caches fragments, we always needs a new one buildFragment : function(result, element){ var ret = $.buildFragment([result],[element]); return ret.cacheable ? $.clone(ret.fragment) : ret.fragment }, $: jQuery }); // Wrap binding functions. $.each(['bind','unbind','undelegate','delegate'],function(i,func){ can[func] = function(){ var t = this[func] ? this : $([this]) t[func].apply(t, arguments) return this; } }) // Wrap modifier functions. $.each(["append","filter","addClass","remove","data","get"], function(i,name){ can[name] = function(wrapped){ return wrapped[name].apply(wrapped, can.makeArray(arguments).slice(1)) } }) // Memory safe destruction. var oldClean = $.cleanData; $.cleanData = function( elems ) { $.each( elems, function( i, elem ) { can.trigger(elem,"destroyed",[],false) }); oldClean(elems); }; can.each = function (elements, callback, context) { var i = 0, key; if (elements) { if (typeof elements.length == 'number' && elements.pop) { elements.attr && elements.attr('length'); for (var len = elements.length; i < len; i++) { if (callback.call(context || elements[i], elements[i], i, elements) === false) { break; } } } else { for (key in elements) { if (callback.call(context || elements[i], elements[key], key, elements) === false) { break; } } } } return elements; } ; // ##string.js // _Miscellaneous string utility functions._ // Several of the methods in this plugin use code adapated from Prototype // Prototype JavaScript framework, version 1.6.0.1. // © 2005-2007 Sam Stephenson var undHash = /_|-/, colons = /==/, words = /([A-Z]+)([A-Z][a-z])/g, lowUp = /([a-z\d])([A-Z])/g, dash = /([a-z\d])([A-Z])/g, replacer = /\{([^\}]+)\}/g, quote = /"/g, singleQuote = /'/g, // Returns the `prop` property from `obj`. // If `add` is true and `prop` doesn't exist in `obj`, create it as an // empty object. getNext = function( obj, prop, add ) { return prop in obj ? obj[ prop ] : ( add && ( obj[ prop ] = {} )); }, // Returns `true` if the object can have properties (no `null`s). isContainer = function( current ) { return /^f|^o/.test( typeof current ); }; can.extend(can, { // Escapes strings for HTML. esc : function( content ) { return ( "" + content ) .replace(/&/g, '&') .replace(//g, '>') .replace(quote, '"') .replace(singleQuote, "'"); }, getObject : function( name, roots, add ) { // The parts of the name we are looking up // `['App','Models','Recipe']` var parts = name ? name.split('.') : [], length = parts.length, current, r = 0, ret, i; // Make sure roots is an `array`. roots = can.isArray(roots) ? roots : [roots || window]; if ( ! length ) { return roots[0]; } // For each root, mark it as current. while( current = roots[r++] ) { // Walk current to the 2nd to last object or until there // is not a container. for (i =0; i < length - 1 && isContainer( current ); i++ ) { current = getNext( current, parts[i], add ); } // If we can get a property from the 2nd to last object... if( isContainer(current) ) { // Get (and possibly set) the property. ret = getNext(current, parts[i], add); // If there is a value, we exit. if ( ret !== undefined ) { // If `add` is `false`, delete the property if ( add === false ) { delete current[parts[i]]; } return ret; } } } }, // Capitalizes a string. capitalize: function( s, cache ) { // Used to make newId. return s.charAt(0).toUpperCase() + s.slice(1); }, // Underscores a string. underscore: function( s ) { return s .replace(colons, '/') .replace(words, '$1_$2') .replace(lowUp, '$1_$2') .replace(dash, '_') .toLowerCase(); }, // Micro-templating. sub: function( str, data, remove ) { var obs = []; obs.push( str.replace( replacer, function( whole, inside ) { // Convert inside to type. var ob = can.getObject( inside, data, remove === undefined? remove : !remove ); // If a container, push into objs (which will return objects found). if ( isContainer( ob ) ) { obs.push( ob ); return ""; } else { return "" + ob; } })); return obs.length <= 1 ? obs[0] : obs; }, // These regex's are used throughout the rest of can, so let's make // them available. replacer : replacer, undHash : undHash }); // ## construct.js // `can.Construct` // _This is a modified version of // [John Resig's class](http://ejohn.org/blog/simple-javascript-inheritance/). // It provides class level inheritance and callbacks._ // A private flag used to initialize a new class instance without // initializing it's bindings. var initializing = 0; can.Construct = function() { if (arguments.length) { return can.Construct.extend.apply(can.Construct, arguments); } }; can.extend(can.Construct, { newInstance: function() { // Get a raw instance object (`init` is not called). var inst = this.instance(), arg = arguments, args; // Call `setup` if there is a `setup` if ( inst.setup ) { args = inst.setup.apply(inst, arguments); } // Call `init` if there is an `init` // If `setup` returned `args`, use those as the arguments if ( inst.init ) { inst.init.apply(inst, args || arguments); } return inst; }, // Overwrites an object with methods. Used in the `super` plugin. // `newProps` - New properties to add. // `oldProps` - Where the old properties might be (used with `super`). // `addTo` - What we are adding to. _inherit: function( newProps, oldProps, addTo ) { can.extend(addTo || newProps, newProps || {}) }, // used for overwriting a single property. // this should be used for patching other objects // the super plugin overwrites this _overwrite : function(what, oldProps, propName, val){ what[propName] = val; }, // Set `defaults` as the merger of the parent `defaults` and this // object's `defaults`. If you overwrite this method, make sure to // include option merging logic. setup: function( base, fullName ) { this.defaults = can.extend(true,{}, base.defaults, this.defaults); }, // Create's a new `class` instance without initializing by setting the // `initializing` flag. instance: function() { // Prevents running `init`. initializing = 1; var inst = new this(); // Allow running `init`. initializing = 0; return inst; }, // Extends classes. extend: function( fullName, klass, proto ) { // Figure out what was passed and normalize it. if ( typeof fullName != 'string' ) { proto = klass; klass = fullName; fullName = null; } if ( ! proto ) { proto = klass; klass = null; } proto = proto || {}; var _super_class = this, _super = this.prototype, name, shortName, namespace, prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor). prototype = this.instance(); // Copy the properties over onto the new prototype. can.Construct._inherit(proto, _super, prototype); // The dummy class constructor. function Constructor() { // All construction is actually done in the init method. if ( ! initializing ) { return this.constructor !== Constructor && arguments.length ? // We are being called without `new` or we are extending. arguments.callee.extend.apply(arguments.callee, arguments) : // We are being called with `new`. this.constructor.newInstance.apply(this.constructor, arguments); } } // Copy old stuff onto class (can probably be merged w/ inherit) for ( name in _super_class ) { if ( _super_class.hasOwnProperty(name) ) { Constructor[name] = _super_class[name]; } } // Copy new static properties on class. can.Construct._inherit(klass, _super_class, Constructor); // Setup namespaces. if ( fullName ) { var parts = fullName.split('.'), shortName = parts.pop(), current = can.getObject(parts.join('.'), window, true), namespace = current, _fullName = can.underscore(fullName.replace(/\./g, "_")), _shortName = can.underscore(shortName); //@steal-remove-start if(current[shortName]){ } //@steal-remove-end current[shortName] = Constructor; } // Set things that shouldn't be overwritten. can.extend(Constructor, { constructor: Constructor, prototype: prototype, namespace: namespace, shortName: shortName, _shortName : _shortName, fullName: fullName, _fullName: _fullName }); // Make sure our prototype looks nice. Constructor.prototype.constructor = Constructor; // Call the class `setup` and `init` var t = [_super_class].concat(can.makeArray(arguments)), args = Constructor.setup.apply(Constructor, t ); if ( Constructor.init ) { Constructor.init.apply(Constructor, args || t ); } return Constructor; // // } }); // ## observe.js // `can.Observe` // _Provides the observable pattern for JavaScript Objects._ // // Returns `true` if something is an object with properties of its own. var canMakeObserve = function( obj ) { return obj && typeof obj === 'object' && !(obj instanceof Date); }, // Removes all listeners. unhookup = function(items, namespace){ return can.each(items, function(item){ if(item && item.unbind){ item.unbind("change" + namespace); } }); }, // Listens to changes on `val` and "bubbles" the event up. // `val` - The object to listen for changes on. // `prop` - The property name is at on. // `parent` - The parent object of prop. hookupBubble = function( val, prop, parent ) { // If it's an `array` make a list, otherwise a val. if (val instanceof Observe){ // We have an `observe` already... // Make sure it is not listening to this already unhookup([val], parent._namespace); } else if ( can.isArray(val) ) { val = new Observe.List(val); } else { val = new Observe(val); } // Listen to all changes and `batchTrigger` upwards. val.bind("change" + parent._namespace, function( ev, attr ) { // `batchTrigger` the type on this... var args = can.makeArray(arguments), ev = args.shift(); args[0] = prop === "*" ? parent.indexOf(val)+"." + args[0] : prop + "." + args[0]; // track objects dispatched on this observe ev.triggeredNS = ev.triggeredNS || {}; // if it has already been dispatched exit if (ev.triggeredNS[parent._namespace]) { return; } ev.triggeredNS[parent._namespace] = true; can.trigger(parent, ev, args); can.trigger(parent,args[0],args); }); return val; }, // An `id` to track events for a given observe. observeId = 0, // A reference to an `array` of events that will be dispatched. collecting = undefined, // Call to start collecting events (`Observe` sends all events at // once). collect = function() { if (!collecting ) { collecting = []; return true; } }, // Creates an event on item, but will not send immediately // if collecting events. // `item` - The item the event should happen on. // `event` - The event name, ex: `change`. // `args` - Tn array of arguments. batchTrigger = function( item, event, args ) { // Don't send events if initalizing. if ( ! item._init) { if (!collecting ) { return can.trigger(item, event, args); } else { collecting.push([ item, { type: event, batchNum : batchNum }, args ] ); } } }, // Which batch of events this is for -- might not want to send multiple // messages on the same batch. This is mostly for event delegation. batchNum = 1, // Sends all pending events. sendCollection = function() { var items = collecting.slice(0); collecting = undefined; batchNum++; can.each(items, function( item ) { can.trigger.apply(can, item) }) }, // A helper used to serialize an `Observe` or `Observe.List`. // `observe` - The observable. // `how` - To serialize with `attr` or `serialize`. // `where` - To put properties, in an `{}` or `[]`. serialize = function( observe, how, where ) { // Go through each property. observe.each(function( val, name ) { // If the value is an `object`, and has an `attrs` or `serialize` function. where[name] = canMakeObserve(val) && can.isFunction( val[how] ) ? // Call `attrs` or `serialize` to get the original data back. val[how]() : // Otherwise return the value. val }) return where; }, $method = function( name ) { return function() { return can[name].apply(this, arguments ); } }, bind = $method('addEvent'), unbind = $method('removeEvent'), attrParts = function(attr){ return can.isArray(attr) ? attr : (""+attr).split(".") }; var Observe = can.Construct('can.Observe', { // keep so it can be overwritten setup : function(){ can.Construct.setup.apply(this, arguments) }, bind : bind, unbind: unbind, id: "id" }, { setup: function( obj ) { // `_data` is where we keep the properties. this._data = {}; // The namespace this `object` uses to listen to events. this._namespace = ".observe" + (++observeId); // Sets all `attrs`. this._init = 1; this.attr(obj); delete this._init; }, attr: function( attr, val ) { // This is super obfuscated for space -- basically, we're checking // if the type of the attribute is not a `number` or a `string`. if ( !~ "ns".indexOf((typeof attr).charAt(0))) { return this._attrs(attr, val) } else if ( val === undefined ) {// If we are getting a value. // Let people know we are reading. Observe.__reading && Observe.__reading(this, attr) return this._get(attr) } else { // Otherwise we are setting. this._set(attr, val); return this; } }, each: function() { return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments))) }, removeAttr: function( attr ) { // Convert the `attr` into parts (if nested). var parts = attrParts(attr), // The actual property to remove. prop = parts.shift(), // The current value. current = this._data[prop]; // If we have more parts, call `removeAttr` on that part. if ( parts.length ) { return current.removeAttr(parts) } else { // Otherwise, `delete`. delete this._data[prop]; // Create the event. if (!(prop in this.constructor.prototype)) { delete this[prop] } batchTrigger(this, "change", [prop, "remove", undefined, current]); batchTrigger(this, prop, [undefined, current]); return current; } }, // Reads a property from the `object`. _get: function( attr ) { var parts = attrParts(attr), current = this.__get(parts.shift()); return parts.length ? current ? current._get(parts) : undefined : current; }, // Reads a property directly if an `attr` is provided, otherwise // returns the "real" data object itself. __get: function( attr ) { return attr ? this._data[attr] : this._data; }, // Sets `attr` prop as value on this object where. // `attr` - Is a string of properties or an array of property values. // `value` - The raw value to set. _set: function( attr, value ) { // Convert `attr` to attr parts (if it isn't already). var parts = attrParts(attr), // The immediate prop we are setting. prop = parts.shift(), // The current value. current = this.__get(prop); // If we have an `object` and remaining parts. if ( canMakeObserve(current) && parts.length ) { // That `object` should set it (this might need to call attr). current._set(parts, value) } else if (!parts.length ) { // We're in "real" set territory. if(this.__convert){ value = this.__convert(prop, value) } this.__set(prop, value, current) } else { throw "can.Observe: Object does not exist" } }, __set : function(prop, value, current){ // Otherwise, we are setting it on this `object`. // TODO: Check if value is object and transform // are we changing the value. if ( value !== current ) { // Check if we are adding this for the first time -- // if we are, we need to create an `add` event. var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add"; // Set the value on data. this.___set(prop, // If we are getting an object. canMakeObserve(value) ? // Hook it up to send event. hookupBubble(value, prop, this) : // Value is normal. value); // `batchTrigger` the change event. batchTrigger(this, "change", [prop, changeType, value, current]); batchTrigger(this, prop, [value, current]); // If we can stop listening to our old value, do it. current && unhookup([current], this._namespace); } }, // Directly sets a property on this `object`. ___set: function( prop, val ) { this._data[prop] = val; // Add property directly for easy writing. // Check if its on the `prototype` so we don't overwrite methods like `attrs`. if (!(prop in this.constructor.prototype)) { this[prop] = val } }, bind: bind, unbind: unbind, serialize: function() { return serialize(this, 'serialize', {}); }, _attrs: function( props, remove ) { if ( props === undefined ) { return serialize(this, 'attr', {}) } props = can.extend(true, {}, props); var prop, collectingStarted = collect(), self = this, newVal; this.each(function(curVal, prop){ newVal = props[prop]; // If we are merging... if ( newVal === undefined ) { remove && self.removeAttr(prop); return; } if ( canMakeObserve(curVal) && canMakeObserve(newVal) ) { curVal.attr(newVal, remove) } else if ( curVal != newVal ) { self._set(prop, newVal) } else { } delete props[prop]; }) // Add remaining props. for ( var prop in props ) { newVal = props[prop]; this._set(prop, newVal) } if ( collectingStarted ) { sendCollection(); } return this; } }); // Helpers for `observable` lists. var splice = [].splice, list = Observe('can.Observe.List', { setup: function( instances, options ) { this.length = 0; this._namespace = ".observe" + (++observeId); this._init = 1; this.bind('change',can.proxy(this._changes,this)); this.push.apply(this, can.makeArray(instances || [])); can.extend(this, options); delete this._init; }, _changes : function(ev, attr, how, newVal, oldVal){ // `batchTrigger` direct add and remove events... if ( !~ attr.indexOf('.')){ if( how === 'add' ) { batchTrigger(this, how, [newVal,+attr]); batchTrigger(this,'length',[this.length]); } else if( how === 'remove' ) { batchTrigger(this, how, [oldVal, +attr]); batchTrigger(this,'length',[this.length]); } else { batchTrigger(this,how,[newVal, +attr]) } } }, __get : function(attr){ return attr ? this[attr] : this; }, ___set : function(attr, val){ this[attr] = val; if(+attr >= this.length){ this.length = (+attr+1) } }, // Returns the serialized form of this list. serialize: function() { return serialize(this, 'serialize', []); }, // splice: function( index, howMany ) { var args = can.makeArray(arguments), i; for ( i = 2; i < args.length; i++ ) { var val = args[i]; if ( canMakeObserve(val) ) { args[i] = hookupBubble(val, "*", this) } } if ( howMany === undefined ) { howMany = args[1] = this.length - index; } var removed = splice.apply(this, args); if ( howMany > 0 ) { batchTrigger(this, "change", [""+index, "remove", undefined, removed]); unhookup(removed, this._namespace); } if ( args.length > 2 ) { batchTrigger(this, "change", [""+index, "add", args.slice(2), removed]); } return removed; }, _attrs: function( props, remove ) { if ( props === undefined ) { return serialize(this, 'attr', []); } // Create a copy. props = props.slice(0); var len = Math.min(props.length, this.length), collectingStarted = collect(), prop; for ( var prop = 0; prop < len; prop++ ) { var curVal = this[prop], newVal = props[prop]; if ( canMakeObserve(curVal) && canMakeObserve(newVal) ) { curVal.attr(newVal, remove) } else if ( curVal != newVal ) { this._set(prop, newVal) } else { } } if ( props.length > this.length ) { // Add in the remaining props. this.push(props.slice(this.length)) } else if ( props.length < this.length && remove ) { this.splice(props.length) } if ( collectingStarted ) { sendCollection() } } }), // Converts to an `array` of arguments. getArgs = function( args ) { return args[0] && can.isArray(args[0]) ? args[0] : can.makeArray(args); }; // Create `push`, `pop`, `shift`, and `unshift` can.each({ push: "length", unshift: 0 }, // Adds a method // `name` - The method name. // `where` - Where items in the `array` should be added. function( where, name ) { list.prototype[name] = function() { // Get the items being added. var args = getArgs(arguments), // Where we are going to add items. len = where ? this.length : 0; // Go through and convert anything to an `observe` that needs to be converted. for ( var i = 0; i < args.length; i++ ) { var val = args[i]; if ( canMakeObserve(val) ) { args[i] = hookupBubble(val, "*", this) } } // Call the original method. var res = [][name].apply(this, args); if ( !this.comparator || !args.length ) { batchTrigger(this, "change", [""+len, "add", args, undefined]) } return res; } }); can.each({ pop: "length", shift: 0 }, // Creates a `remove` type method function( where, name ) { list.prototype[name] = function() { var args = getArgs(arguments), len = where && this.length ? this.length - 1 : 0; var res = [][name].apply(this, args) // Create a change where the args are // `*` - Change on potentially multiple properties. // `remove` - Items removed. // `undefined` - The new values (there are none). // `res` - The old, removed values (should these be unbound). // `len` - Where these items were removed. batchTrigger(this, "change", [""+len, "remove", undefined, [res]]) if ( res && res.unbind ) { res.unbind("change" + this._namespace) } return res; } }); list.prototype. indexOf = [].indexOf || function(item){ return can.inArray(item, this) }; // ## model.js // `can.Model` // _A `can.Observe` that connects to a RESTful interface._ // // Generic deferred piping function var pipe = function( def, model, func ) { var d = new can.Deferred(); def.then(function(){ arguments[0] = model[func](arguments[0]) d.resolve.apply(d, arguments) },function(){ d.rejectWith.apply(this,arguments) }) return d; }, modelNum = 0, ignoreHookup = /change.observe\d+/, getId = function( inst ) { return inst[inst.constructor.id] }, // Ajax `options` generator function ajax = function( ajaxOb, data, type, dataType, success, error ) { // If we get a string, handle it. if ( typeof ajaxOb == "string" ) { // If there's a space, it's probably the type. var parts = ajaxOb.split(" ") ajaxOb = { url : parts.pop() }; if(parts.length){ ajaxOb.type = parts.pop(); } } // If we are a non-array object, copy to a new attrs. ajaxOb.data = typeof data == "object" && !can.isArray(data) ? can.extend(ajaxOb.data || {}, data) : data; // Get the url with any templated values filled out. ajaxOb.url = can.sub(ajaxOb.url, ajaxOb.data, true); return can.ajax(can.extend({ type: type || "post", dataType: dataType ||"json", success : success, error: error }, ajaxOb )); }, makeRequest = function( self, type, success, error, method ) { var deferred , args = [self.serialize()], // The model. model = self.constructor, jqXHR; // `destroy` does not need data. if ( type == 'destroy' ) { args.shift(); } // `update` and `destroy` need the `id`. if ( type !== 'create' ) { args.unshift(getId(self)) } jqXHR = model[type].apply(model, args); deferred = jqXHR.pipe(function(data){ self[method || type + "d"](data, jqXHR); return self }) // Hook up `abort` if(jqXHR.abort){ deferred.abort = function(){ jqXHR.abort(); } } return deferred.then(success,error); }, // This object describes how to make an ajax request for each ajax method. // The available properties are: // `url` - The default url to use as indicated as a property on the model. // `type` - The default http request type // `data` - A method that takes the `arguments` and returns `data` used for ajax. // // // ajaxMethods = { create : { url : "_shortName", type :"post" }, update : { data : function(id, attrs){ attrs = attrs || {}; var identity = this.id; if ( attrs[identity] && attrs[identity] !== id ) { attrs["new" + can.capitalize(id)] = attrs[identity]; delete attrs[identity]; } attrs[identity] = id; return attrs; }, type : "put" }, destroy : { type : "delete", data : function(id){ var args = {}; args[this.id] = id; return args; } }, findAll : { url : "_shortName" }, findOne: {} }, // Makes an ajax request `function` from a string. // `ajaxMethod` - The `ajaxMethod` object defined above. // `str` - The string the user provided. Ex: `findAll: "/recipes.json"`. ajaxMaker = function(ajaxMethod, str){ // Return a `function` that serves as the ajax method. return function(data){ // If the ajax method has it's own way of getting `data`, use that. data = ajaxMethod.data ? ajaxMethod.data.apply(this, arguments) : // Otherwise use the data passed in. data; // Return the ajax method with `data` and the `type` provided. return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get") } } can.Observe("can.Model",{ setup : function(base){ can.Observe.apply(this, arguments); if(this === can.Model){ return; } var self = this, clean = can.proxy(this._clean, self); can.each(ajaxMethods, function(method, name){ if ( ! can.isFunction( self[name] )) { self[name] = ajaxMaker(method, self[name]); } if (self["make"+can.capitalize(name)]){ var newMethod = self["make"+can.capitalize(name)](self[name]); can.Construct._overwrite(self, base, name,function(){ this._super; this._reqs++; return newMethod.apply(this, arguments).then(clean, clean); }) } }); if(!self.fullName || self.fullName == base.fullName){ self.fullName = self._shortName = "Model"+(++modelNum); } // Ddd ajax converters. this.store = {}; this._reqs = 0; this._url = this._shortName+"/{"+this.id+"}" }, _ajax : ajaxMaker, _clean : function(){ this._reqs--; if(!this._reqs){ for(var id in this.store) { if(!this.store[id]._bindings){ delete this.store[id]; } } } }, models: function( instancesRawData ) { if ( ! instancesRawData ) { return; } if ( instancesRawData instanceof this.List ) { return instancesRawData; } // Get the list type. var self = this, res = new( self.List || ML), // Did we get an `array`? arr = can.isArray(instancesRawData), // Did we get a model list? ml = (instancesRawData instanceof ML), // Get the raw `array` of objects. raw = arr ? // If an `array`, return the `array`. instancesRawData : // Otherwise if a model list. (ml ? // Get the raw objects from the list. instancesRawData.serialize() : // Get the object's data. instancesRawData.data), i = 0; can.each(raw, function( rawPart ) { res.push( self.model( rawPart )); }); if ( ! arr ) { // Push other stuff onto `array`. can.each(instancesRawData, function(val, prop){ if ( prop !== 'data' ) { res[prop] = val; } }) } return res; }, model: function( attributes ) { if (!attributes ) { return; } if ( attributes instanceof this ) { attributes = attributes.serialize(); } var model = this.store[attributes[this.id]] ? this.store[attributes[this.id]].attr(attributes) : new this( attributes ); if(this._reqs){ this.store[attributes[this.id]] = model; } return model; } }, { isNew: function() { var id = getId(this); return ! ( id || id === 0 ); // If `null` or `undefined` }, save: function( success, error ) { return makeRequest(this, this.isNew() ? 'create' : 'update', success, error); }, destroy: function( success, error ) { return makeRequest(this, 'destroy', success, error, 'destroyed'); }, bind : function(eventName){ if ( ! ignoreHookup.test( eventName )) { if ( ! this._bindings ) { this.constructor.store[getId(this)] = this; this._bindings = 0; } this._bindings++; } return can.Observe.prototype.bind.apply( this, arguments ); }, unbind : function(eventName){ if(!ignoreHookup.test(eventName)) { this._bindings--; if(!this._bindings){ delete this.constructor.store[getId(this)]; } } return can.Observe.prototype.unbind.apply(this, arguments); }, // Change `id`. ___set: function( prop, val ) { can.Observe.prototype.___set.call(this,prop, val) // If we add an `id`, move it to the store. if(prop === this.constructor.id && this._bindings){ this.constructor.store[getId(this)] = this; } } }); can.each({makeFindAll : "models", makeFindOne: "model"}, function(method, name){ can.Model[name] = function(oldFind){ return function(params, success, error){ return pipe( oldFind.call( this, params ), this, method ).then(success,error) } }; }); can.each([ "created", "updated", "destroyed"], function( funcName ) { can.Model.prototype[funcName] = function( attrs ) { var stub, constructor = this.constructor; // Update attributes if attributes have been passed stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs); // Call event on the instance can.trigger(this,funcName); can.trigger(this,"change",funcName) // Call event on the instance's Class can.trigger(constructor,funcName, this); }; }); // Model lists are just like `Observe.List` except that when their items are // destroyed, it automatically gets removed from the list. var ML = can.Observe.List('can.Model.List',{ setup : function(){ can.Observe.List.prototype.setup.apply(this, arguments ); // Send destroy events. var self = this; this.bind('change', function(ev, how){ if(/\w+\.destroyed/.test(how)){ self.splice(self.indexOf(ev.target),1); } }) } }) ; // ## deparam.js // `can.deparam` // _Takes a string of name value pairs and returns a Object literal that represents those params._ var digitTest = /^\d+$/, keyBreaker = /([^\[\]]+)|(\[\])/g, paramTest = /([^?#]*)(#.*)?$/, prep = function( str ) { return decodeURIComponent( str.replace(/\+/g, " ") ); } can.extend(can, { deparam: function(params){ var data = {}, pairs, lastPart; if ( params && paramTest.test( params )) { pairs = params.split('&'), can.each( pairs, function( pair ) { var parts = pair.split('='), key = prep( parts.shift() ), value = prep( parts.join("=") ); current = data; parts = key.match(keyBreaker); for ( var j = 0, l = parts.length - 1; j < l; j++ ) { if (!current[parts[j]] ) { // If what we are pointing to looks like an `array` current[parts[j]] = digitTest.test(parts[j+1]) || parts[j+1] == "[]" ? [] : {} } current = current[parts[j]]; } lastPart = parts.pop() if ( lastPart == "[]" ) { current.push(value) } else { current[lastPart] = value; } }); } return data; } }); // ## route.js // `can.route` // _Helps manage browser history (and client state) by synchronizing the // `window.location.hash` with a `can.Observe`._ // // Helper methods used for matching routes. var // `RegExp` used to match route variables of the type ':name'. // Any word character or a period is matched. matcher = /\:([\w\.]+)/g, // Regular expression for identifying &key=value lists. paramsMatcher = /^(?:&[^=]+=[^&]*)+/, // Converts a JS Object into a list of parameters that can be // inserted into an html element tag. makeProps = function( props ) { var tags = []; can.each(props, function(val, name){ tags.push( ( name === 'className' ? 'class' : name )+ '="' + (name === "href" ? val : can.esc(val) ) + '"'); }); return tags.join(" "); }, // Checks if a route matches the data provided. If any route variable // is not present in the data, the route does not match. If all route // variables are present in the data, the number of matches is returned // to allow discerning between general and more specific routes. matchesData = function(route, data) { var count = 0, i = 0, defaults = {}; // look at default values, if they match ... for( var name in route.defaults ) { if(route.defaults[name] === data[name]){ // mark as matched defaults[name] = 1; count++; } } for (; i < route.names.length; i++ ) { if (!data.hasOwnProperty(route.names[i]) ) { return -1; } if(!defaults[route.names[i]]){ count++; } } return count; }, onready = !0, location = window.location, each = can.each, extend = can.extend; can.route = function( url, defaults ) { defaults = defaults || {} // Extract the variable names and replace with `RegExp` that will match // an atual URL with values. var names = [], test = url.replace(matcher, function( whole, name, i ) { names.push(name); var next = "\\"+( url.substr(i+whole.length,1) || "&" ) // a name without a default value HAS to have a value // a name that has a default value can be empty // The `\\` is for string-escaping giving single `\` for `RegExp` escaping. return "([^" +next+"]"+(defaults[name] ? "*" : "+")+")" }); // Add route in a form that can be easily figured out. can.route.routes[url] = { // A regular expression that will match the route when variable values // are present; i.e. for `:page/:type` the `RegExp` is `/([\w\.]*)/([\w\.]*)/` which // will match for any value of `:page` and `:type` (word chars or period). test: new RegExp("^" + test+"($|&)"), // The original URL, same as the index for this entry in routes. route: url, // An `array` of all the variable names in this route. names: names, // Default values provided for the variables. defaults: defaults, // The number of parts in the URL separated by `/`. length: url.split('/').length } return can.route; }; extend(can.route, { param: function( data , _setRoute ) { // Check if the provided data keys match the names in any routes; // Get the one with the most matches. var route, // Need to have at least 1 match. matches = 0, matchCount, routeName = data.route, propCount = 0; delete data.route; each(data, function(){propCount++}); // Otherwise find route. each(can.route.routes, function(temp, name){ // best route is the first with all defaults matching matchCount = matchesData(temp, data); if ( matchCount > matches ) { route = temp; matches = matchCount } if(matchCount >= propCount){ return false; } }); // If we have a route name in our `can.route` data, and it's // just as good as what currently matches, use that if (can.route.routes[routeName] && matchesData(can.route.routes[routeName], data ) === matches) { route = can.route.routes[routeName]; } // If this is match... if ( route ) { var cpy = extend({}, data), // Create the url by replacing the var names with the provided data. // If the default value is found an empty string is inserted. res = route.route.replace(matcher, function( whole, name ) { delete cpy[name]; return data[name] === route.defaults[name] ? "" : encodeURIComponent( data[name] ); }), after; // Remove matching default values each(route.defaults, function(val,name){ if(cpy[name] === val) { delete cpy[name] } }) // The remaining elements of data are added as // `&` separated parameters to the url. after = can.param(cpy); // if we are paraming for setting the hash // we also want to make sure the route value is updated if(_setRoute){ can.route.attr('route',route.route); } return res + (after ? "&" + after : "") } // If no route was found, there is no hash URL, only paramters. return can.isEmptyObject(data) ? "" : "&" + can.param(data); }, deparam: function( url ) { // See if the url matches any routes by testing it against the `route.test` `RegExp`. // By comparing the URL length the most specialized route that matches is used. var route = { length: -1 }; each(can.route.routes, function(temp, name){ if ( temp.test.test(url) && temp.length > route.length ) { route = temp; } }); // If a route was matched. if ( route.length > -1 ) { var // Since `RegExp` backreferences are used in `route.test` (parens) // the parts will contain the full matched string and each variable (back-referenced) value. parts = url.match(route.test), // Start will contain the full matched string; parts contain the variable values. start = parts.shift(), // The remainder will be the `&key=value` list at the end of the URL. remainder = url.substr(start.length - (parts[parts.length-1] === "&" ? 1 : 0) ), // If there is a remainder and it contains a `&key=value` list deparam it. obj = (remainder && paramsMatcher.test(remainder)) ? can.deparam( remainder.slice(1) ) : {}; // Add the default values for this route. obj = extend(true, {}, route.defaults, obj); // Overwrite each of the default values in `obj` with those in // parts if that part is not empty. each(parts,function(part, i){ if ( part && part !== '&') { obj[route.names[i]] = decodeURIComponent( part ); } }); obj.route = route.route; return obj; } // If no route was matched, it is parsed as a `&key=value` list. if ( url.charAt(0) !== '&' ) { url = '&' + url; } return paramsMatcher.test(url) ? can.deparam( url.slice(1) ) : {}; }, data: new can.Observe({}), routes: {}, ready: function(val) { if( val === false ) { onready = val; } if( val === true || onready === true ) { setState(); } return can.route; }, url: function( options, merge ) { if (merge) { options = extend({}, curParams, options) } return "#!" + can.route.param(options) }, link: function( name, options, props, merge ) { return "" + name + ""; }, current: function( options ) { return location.hash == "#!" + can.route.param(options) } }); // The functions in the following list applied to `can.route` (e.g. `can.route.attr('...')`) will // instead act on the `can.route.data` observe. each(['bind','unbind','delegate','undelegate','attr','removeAttr'], function(name){ can.route[name] = function(){ return can.route.data[name].apply(can.route.data, arguments) } }) var // A ~~throttled~~ debounced function called multiple times will only fire once the // timer runs down. Each call resets the timer. timer, // Intermediate storage for `can.route.data`. curParams, // Deparameterizes the portion of the hash of interest and assign the // values to the `can.route.data` removing existing values no longer in the hash. // setState is called typically by hashchange which fires asynchronously // So it's possible that someone started changing the data before the // hashchange event fired. For this reason, it will not set the route data // if the data is changing and the hash already matches the hash that was set. setState = function() { var hash = location.href.split(/#!?/)[1] || "" curParams = can.route.deparam( hash ); // if the hash data is currently changing, and // the hash is what we set it to anyway, do NOT change the hash if(!changingData || hash !== lastHash){ can.route.attr(curParams, true); } }, // The last hash caused by a data change lastHash, // Are data changes pending that haven't yet updated the hash changingData; // If the hash changes, update the `can.route.data`. can.bind.call(window,'hashchange', setState); // If the `can.route.data` changes, update the hash. // Using `.serialize()` retrieves the raw data contained in the `observable`. // This function is ~~throttled~~ debounced so it only updates once even if multiple values changed. // This might be able to use batchNum and avoid this. can.route.bind("change", function(ev, attr) { // indicate that data is changing changingData = 1; clearTimeout( timer ); timer = setTimeout(function() { // indicate that the hash is set to look like the data changingData = 0; var serialized = can.route.data.serialize(); location.hash = "#!" + (lastHash = can.route.param(serialized, true)) }, 1); }); // `onready` event... can.bind.call(document,"ready",can.route.ready); (function() { // ## control.js // `can.Control` // _Controller_ // Binds an element, returns a function that unbinds. var bind = function( el, ev, callback ) { can.bind.call( el, ev, callback ) return function() { can.unbind.call(el, ev, callback); }; }, isFunction = can.isFunction, extend = can.extend, each = can.each, slice = [].slice, paramReplacer = /\{([^\}]+)\}/g, special = can.getObject("$.event.special") || {}, // Binds an element, returns a function that unbinds. delegate = function( el, selector, ev, callback ) { can.delegate.call(el, selector, ev, callback) return function() { can.undelegate.call(el, selector, ev, callback); }; }, // Calls bind or unbind depending if there is a selector. binder = function( el, ev, callback, selector ) { return selector ? delegate( el, can.trim( selector ), ev, callback ) : bind( el, ev, callback ); }, // Moves `this` to the first argument, wraps it with `jQuery` if it's an element shifter = function shifter(context, name) { var method = typeof name == "string" ? context[name] : name; if(!isFunction(method)){ method = context[method]; } return function() { context.called = name; return method.apply(context, [this.nodeName ? can.$(this) : this].concat( slice.call(arguments, 0))); }; }, basicProcessor; can.Construct("can.Control", { // Setup pre-processes which methods are event listeners. setup: function() { // Allow contollers to inherit "defaults" from super-classes as it // done in `can.Construct` can.Construct.setup.apply( this, arguments ); // If you didn't provide a name, or are `control`, don't do anything. if ( this !== can.Control ) { // Cache the underscored names. var control = this, funcName; // Calculate and cache actions. control.actions = {}; for ( funcName in control.prototype ) { if ( control._isAction(funcName) ) { control.actions[funcName] = control._action(funcName); } } } }, // Return `true` if is an action. _isAction: function( methodName ) { var val = this.prototype[methodName], type = typeof val; // if not the constructor return (methodName !== 'constructor') && // and is a function or links to a function ( type == "function" || (type == "string" && isFunction(this.prototype[val] ) ) ) && // and is in special, a processor, or has a funny character !! ( special[methodName] || processors[methodName] || /[^\w]/.test(methodName) ); }, // Takes a method name and the options passed to a control // and tries to return the data necessary to pass to a processor // (something that binds things). _action: function( methodName, options ) { // If we don't have options (a `control` instance), we'll run this // later. paramReplacer.lastIndex = 0; if ( options || ! paramReplacer.test( methodName )) { // If we have options, run sub to replace templates `{}` with a // value from the options or the window var convertedName = options ? can.sub(methodName, [options, window]) : methodName, // If a `{}` resolves to an object, `convertedName` will be // an array arr = can.isArray(convertedName), // Get the parts of the function // `[convertedName, delegatePart, eventPart]` // `/^(?:(.*?)\s)?([\w\.\:>]+)$/` - Breaker `RegExp`. parts = (arr ? convertedName[1] : convertedName).match(/^(?:(.*?)\s)?([\w\.\:>]+)$/); var event = parts[2], processor = processors[event] || basicProcessor; return { processor: processor, parts: parts, delegate : arr ? convertedName[0] : undefined }; } }, // An object of `{eventName : function}` pairs that Control uses to // hook up events auto-magically. processors: {}, // A object of name-value pairs that act as default values for a // control instance defaults: {} }, { // Sets `this.element`, saves the control in `data, binds event // handlers. setup: function( element, options ) { var cls = this.constructor, pluginname = cls.pluginName || cls._fullName, arr; // Want the raw element here. this.element = can.$(element) if ( pluginname && pluginname !== 'can_control') { // Set element and `className` on element. this.element.addClass(pluginname); } (arr = can.data(this.element,"controls")) || can.data(this.element,"controls",arr = []); arr.push(this); // Option merging. this.options = extend({}, cls.defaults, options); // Bind all event handlers. this.on(); // Get's passed into `init`. return [this.element, this.options]; }, on: function( el, selector, eventName, func ) { if ( ! el ) { // Adds bindings. this.off(); // Go through the cached list of actions and use the processor // to bind var cls = this.constructor, bindings = this._bindings, actions = cls.actions, element = this.element, destroyCB = shifter(this,"destroy"), funcName, ready; for ( funcName in actions ) { if ( actions.hasOwnProperty( funcName )) { ready = actions[funcName] || cls._action(funcName, this.options); bindings.push( ready.processor(ready.delegate || element, ready.parts[2], ready.parts[1], funcName, this)); } } // Setup to be destroyed... // don't bind because we don't want to remove it. can.bind.call(element,"destroyed", destroyCB); bindings.push(function( el ) { can.unbind.call(el,"destroyed", destroyCB); }); return bindings.length; } if ( typeof el == 'string' ) { func = eventName; eventName = selector; selector = el; el = this.element; } if ( typeof func == 'string' ) { func = shifter(this,func); } this._bindings.push( binder( el, eventName, func, selector )); return this._bindings.length; }, // Unbinds all event handlers on the controller. off : function(){ var el = this.element[0] each(this._bindings || [], function( value ) { value(el); }); // Adds bindings. this._bindings = []; }, // Prepares a `control` for garbage collection destroy: function() { var Class = this.constructor, pluginName = Class.pluginName || Class._fullName, controls; // Unbind bindings. this.off(); if(pluginName && pluginName !== 'can_control'){ // Remove the `className`. this.element.removeClass(pluginName); } // Remove from `data`. controls = can.data(this.element,"controls"); controls.splice(can.inArray(this, controls),1); can.trigger( this, "destroyed"); // In case we want to know if the `control` is removed. this.element = null; } }); var processors = can.Control.processors, // Processors do the binding. // They return a function that unbinds when called. // // The basic processor that binds events. basicProcessor = function( el, event, selector, methodName, control ) { return binder( el, event, shifter(control, methodName), selector); }; // Set common events to be processed as a `basicProcessor` each(["change", "click", "contextmenu", "dblclick", "keydown", "keyup", "keypress", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "reset", "resize", "scroll", "select", "submit", "focusin", "focusout", "mouseenter", "mouseleave"], function( v ) { processors[v] = basicProcessor; }); }()); // ## control/route.js // _Controller route integration._ can.Control.processors.route = function( el, event, selector, funcName, controller ) { can.route( selector || "" ) var batchNum, check = function( ev, attr, how ) { if ( can.route.attr('route') === ( selector || "" ) && ( ev.batchNum === undefined || ev.batchNum !== batchNum ) ) { batchNum = ev.batchNum; var d = can.route.attr(); delete d.route; if(can.isFunction(controller[funcName])){ controller[funcName]( d ) }else { controller[controller[funcName]](d) } } } can.route.bind( 'change', check ); return function() { can.route.unbind( 'change', check ) } } ; // ## view.js // `can.view` // _Templating abstraction._ var isFunction = can.isFunction, makeArray = can.makeArray, // Used for hookup `id`s. hookupId = 1, $view = can.view = function(view, data, helpers, callback){ // Get the result. var result = $view.render(view, data, helpers, callback); if(can.isDeferred(result)){ return result.pipe(function(result){ return $view.frag(result); }) } // Convert it into a dom frag. return $view.frag(result); }; can.extend( $view, { // creates a frag and hooks it up all at once frag: function(result, parentNode ){ return $view.hookup( $view.fragment(result), parentNode ); }, // simply creates a frag // this is used internally to create a frag // insert it // then hook it up fragment: function(result){ var frag = can.buildFragment(result,document.body); // If we have an empty frag... if(!frag.childNodes.length) { frag.appendChild(document.createTextNode('')) } return frag; }, // Convert a path like string into something that's ok for an `element` ID. toId : function( src ) { return can.map(src.toString().split(/\/|\./g), function( part ) { // Dont include empty strings in toId functions if ( part ) { return part; } }).join("_"); }, hookup: function(fragment, parentNode ){ var hookupEls = [], id, func, el, i=0; // Get all `childNodes`. can.each(fragment.childNodes ? can.makeArray(fragment.childNodes) : fragment, function(node){ if(node.nodeType === 1){ hookupEls.push(node) hookupEls.push.apply(hookupEls, can.makeArray( node.getElementsByTagName('*'))) } }); // Filter by `data-view-id` attribute. for (; el = hookupEls[i++]; ) { if ( el.getAttribute && (id = el.getAttribute('data-view-id')) && (func = $view.hookups[id]) ) { func(el, parentNode, id); delete $view.hookups[id]; el.removeAttribute('data-view-id'); } } return fragment; }, hookups: {}, hook: function( cb ) { $view.hookups[++hookupId] = cb; return " data-view-id='"+hookupId+"'"; }, cached: {}, cache: true, register: function( info ) { this.types["." + info.suffix] = info; }, types: {}, ext: ".ejs", registerScript: function() {}, preload: function( ) {}, render: function( view, data, helpers, callback ) { // If helpers is a `function`, it is actually a callback. if ( isFunction( helpers )) { callback = helpers; helpers = undefined; } // See if we got passed any deferreds. var deferreds = getDeferreds(data); if ( deferreds.length ) { // Does data contain any deferreds? // The deferred that resolves into the rendered content... var deferred = new can.Deferred(); // Add the view request to the list of deferreds. deferreds.push(get(view, true)) // Wait for the view and all deferreds to finish... can.when.apply(can, deferreds).then(function( resolved ) { // Get all the resolved deferreds. var objs = makeArray(arguments), // Renderer is the last index of the data. renderer = objs.pop(), // The result of the template rendering with data. result; // Make data look like the resolved deferreds. if ( can.isDeferred(data) ) { data = usefulPart(resolved); } else { // Go through each prop in data again and // replace the defferreds with what they resolved to. for ( var prop in data ) { if ( can.isDeferred(data[prop]) ) { data[prop] = usefulPart(objs.shift()); } } } // Get the rendered result. result = renderer(data, helpers); // Resolve with the rendered view. deferred.resolve(result); // If there's a `callback`, call it back with the result. callback && callback(result); }); // Return the deferred... return deferred; } else { // No deferreds! Render this bad boy. var response, // If there's a `callback` function async = isFunction( callback ), // Get the `view` type deferred = get(view, async); // If we are `async`... if ( async ) { // Return the deferred response = deferred; // And fire callback with the rendered result. deferred.then(function( renderer ) { callback(renderer(data, helpers)) }) } else { // Otherwise, the deferred is complete, so // set response to the result of the rendering. deferred.then(function( renderer ) { response = renderer(data, helpers); }); } return response; } } }); // Returns `true` if something looks like a deferred. can.isDeferred = function( obj ) { return obj && isFunction(obj.then) && isFunction(obj.pipe) // Check if `obj` is a `can.Deferred`. } // Makes sure there's a template, if not, have `steal` provide a warning. var checkText = function( text, url ) { if ( ! text.length ) { //@steal-remove-start //@steal-remove-end throw "can.view: No template or empty template:" + url; } }, // `Returns a `view` renderer deferred. // `url` - The url to the template. // `async` - If the ajax request should be asynchronous. // Returns a deferred. get = function( url, async ) { var suffix = url.match(/\.[\w\d]+$/), type, // If we are reading a script element for the content of the template, // `el` will be set to that script element. el, // A unique identifier for the view (used for caching). // This is typically derived from the element id or // the url for the template. id, // The ajax request used to retrieve the template content. jqXHR, // Used to generate the response. response = function( text ) { // Get the renderer function. var func = type.renderer(id, text), d = new can.Deferred(); d.resolve(func) // Cache if we are caching. if ( $view.cache ) { $view.cached[id] = d; } // Return the objects for the response's `dataTypes` // (in this case view). return d; }; //If the url has a #, we assume we want to use an inline template //from a script element and not current page's HTML if( url.match(/^#/) ) { url = url.substr(1); } // If we have an inline template, derive the suffix from the `text/???` part. // This only supports `