/*! * CanJS - 1.1.5 (2013-03-27) * http://canjs.us/ * Copyright (c) 2013 Bitovi * Licensed MIT */ (function (window, $, undefined) { // ## can/util/can.js var can = window.can || {}; if (typeof GLOBALCAN === 'undefined' || GLOBALCAN !== false) { window.can = can; } can.isDeferred = function (obj) { var isFunction = this.isFunction; // Returns `true` if something looks like a deferred. return obj && isFunction(obj.then) && isFunction(obj.pipe); }; var cid = 0; can.cid = function (object, name) { if (object._cid) { return object._cid } else { return object._cid = (name || "") + (++cid) } } // ## can/util/array/each.js can.each = function (elements, callback, context) { var i = 0, key; if (elements) { if (typeof elements.length === 'number' && elements.pop) { if (elements.attr) { elements.attr('length'); } for (key = elements.length; i < key; i++) { if (callback.call(context || elements[i], elements[i], i, elements) === false) { break; } } } else if (elements.hasOwnProperty) { for (key in elements) { if (elements.hasOwnProperty(key)) { if (callback.call(context || elements[key], elements[key], key, elements) === false) { break; } } } } } return elements; }; // ## can/util/jquery/jquery.js // _jQuery node list._ $.extend(can, $, { trigger: function (obj, event, args) { if (obj.trigger) { obj.trigger(event, args); } else { $.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 (elems, context) { var oldFragment = $.buildFragment, ret; elems = [elems]; // Set context per 1.8 logic context = context || document; context = !context.nodeType && context[0] || context; context = context.ownerDocument || context; ret = oldFragment.call(jQuery, elems, context); return ret.cacheable ? $.clone(ret.fragment) : ret.fragment || ret; }, $: $, each: can.each }); // 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) { if (elem) { can.trigger(elem, "destroyed", [], false); } }); oldClean(elems); }; // ## can/util/string/string.js // ##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 strUndHash = /_|-/, strColons = /\=\=/, strWords = /([A-Z]+)([A-Z][a-z])/g, strLowUp = /([a-z\d])([A-Z])/g, strDash = /([a-z\d])([A-Z])/g, strReplacer = /\{([^\}]+)\}/g, strQuote = /"/g, strSingleQuote = /'/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) { // Convert bad values into empty strings var isInvalid = content === null || content === undefined || (isNaN(content) && ("" + content === 'NaN')); return ("" + (isInvalid ? '' : content)).replace(/&/g, '&').replace(//g, '>').replace(strQuote, '"').replace(strSingleQuote, "'"); }, 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 (roots[r]) { 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; } } r++; } }, // 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(strColons, '/').replace(strWords, '$1_$2').replace(strLowUp, '$1_$2').replace(strDash, '_').toLowerCase(); }, // Micro-templating. sub: function (str, data, remove) { var obs = []; obs.push(str.replace(strReplacer, function (whole, inside) { // Convert inside to type. var ob = can.getObject(inside, data, remove === undefined ? remove : !remove); if (ob === undefined) { obs = null; return ""; } // If a container, push into objs (which will return objects found). if (isContainer(ob) && obs) { obs.push(ob); return ""; } return "" + ob; })); return obs === null ? obs : (obs.length <= 1 ? obs[0] : obs); }, // These regex's are used throughout the rest of can, so let's make // them available. replacer: strReplacer, undHash: strUndHash }); // ## can/construct/construct.js // ## 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); 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; } }); // ## can/observe/observe.js // ## 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 && (can.isArray(obj) || can.isPlainObject(obj) || (obj instanceof can.Observe)); }, // 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. // `ob` - (optional) The Observe object constructor // `list` - (optional) The observable list constructor hookupBubble = function (val, prop, parent, Ob, List) { Ob = Ob || Observe; List = List || Observe.List; // 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._cid); } else if (can.isArray(val)) { val = new List(val); } else { val = new Ob(val); } // Listen to all changes and `batchTrigger` upwards. val.bind("change" + parent._cid, function () { // `batchTrigger` the type on this... var args = can.makeArray(arguments), ev = args.shift(); args[0] = (prop === "*" ? [parent.indexOf(val), args[0]] : [prop, args[0]]).join("."); // track objects dispatched on this observe ev.triggeredNS = ev.triggeredNS || {}; // if it has already been dispatched exit if (ev.triggeredNS[parent._cid]) { return; } ev.triggeredNS[parent._cid] = true; // send change event with modified attr to parent can.trigger(parent, ev, args); // send modified attr event to parent //can.trigger(parent, args[0], args); }); return val; }, // An `id` to track events for a given observe. observeId = 0, // 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, keepKey) { if (keepKey) { return [attr]; } return can.isArray(attr) ? attr : ("" + attr).split("."); }, // 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, // how many times has start been called without a stop transactions = 0, // an array of events within a transaction batchEvents = [], stopCallbacks = []; var Observe = can.Observe = can.Construct({ // keep so it can be overwritten bind: bind, unbind: unbind, id: "id", canMakeObserve: canMakeObserve, // starts collecting events // takes a callback for after they are updated // how could you hook into after ejs startBatch: function (batchStopHandler) { transactions++; batchStopHandler && stopCallbacks.push(batchStopHandler); }, stopBatch: function (force, callStart) { if (force) { transactions = 0; } else { transactions--; } if (transactions == 0) { var items = batchEvents.slice(0), callbacks = stopCallbacks.slice(0); batchEvents = []; stopCallbacks = []; batchNum++; callStart && this.startBatch(); can.each(items, function (args) { can.trigger.apply(can, args); }); can.each(callbacks, function (cb) { cb(); }); } }, triggerBatch: function (item, event, args) { // Don't send events if initalizing. if (!item._init) { if (transactions == 0) { return can.trigger(item, event, args); } else { event = typeof event === "string" ? { type: event } : event; event.batchNum = batchNum; batchEvents.push([ item, event, args]); } } }, keys: function (observe) { var keys = []; Observe.__reading && Observe.__reading(observe, '__keys'); for (var keyName in observe._data) { keys.push(keyName); } return keys; } }, { setup: function (obj) { // `_data` is where we keep the properties. this._data = {}; // The namespace this `object` uses to listen to events. can.cid(this, ".observe"); // Sets all `attrs`. this._init = 1; this.attr(obj); this.bind('change' + this._cid, can.proxy(this._changes, this)); delete this._init; }, _changes: function (ev, attr, how, newVal, oldVal) { Observe.triggerBatch(this, { type: attr, batchNum: ev.batchNum }, [newVal, oldVal]); }, _triggerChange: function (attr, how, newVal, oldVal) { Observe.triggerBatch(this, "change", can.makeArray(arguments)) }, 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`. var type = typeof attr; if (type !== "string" && type !== "number") { 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 () { Observe.__reading && Observe.__reading(this, '__keys'); return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments))) }, removeAttr: function (attr) { // Info if this is List or not var isList = this instanceof can.Observe.List, // Convert the `attr` into parts (if nested). parts = attrParts(attr), // The actual property to remove. prop = parts.shift(), // The current value. current = isList ? this[prop] : this._data[prop]; // If we have more parts, call `removeAttr` on that part. if (parts.length) { return current.removeAttr(parts) } else { if (isList) { this.splice(prop, 1) } else if (prop in this._data) { // Otherwise, `delete`. delete this._data[prop]; // Create the event. if (!(prop in this.constructor.prototype)) { delete this[prop] } // Let others know the number of keys have changed Observe.triggerBatch(this, "__keys"); this._triggerChange(prop, "remove", undefined, current); } return current; } }, // Reads a property from the `object`. _get: function (attr) { var value = typeof attr === 'string' && !! ~attr.indexOf('.') && this.__get(attr); if (value) { return value; } // break up the attr (`"foo.bar"`) into `["foo","bar"]` var parts = attrParts(attr), // get the value of the first attr name (`"foo"`) current = this.__get(parts.shift()); // if there are other attributes to read return parts.length ? // and current has a value current ? // lookup the remaining attrs on current current._get(parts) : // or if there's no current, return undefined undefined : // if there are no more parts, return current 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, keepKey) { // Convert `attr` to attr parts (if it isn't already). var parts = attrParts(attr, keepKey), // 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); if (changeType == "add") { // If there is no current value, let others know that // the the number of keys have changed Observe.triggerBatch(this, "__keys", undefined); } // `batchTrigger` the change event. this._triggerChange(prop, changeType, value, current); //Observe.triggerBatch(this, prop, [value, current]); // If we can stop listening to our old value, do it. current && unhookup([current], this._cid); } }, // 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({}, props); var prop, self = this, newVal; Observe.startBatch(); this.each(function (curVal, prop) { newVal = props[prop]; // If we are merging... if (newVal === undefined) { remove && self.removeAttr(prop); return; } if (self.__convert) { newVal = self.__convert(prop, newVal) } // if we're dealing with models, want to call _set to let converter run if (newVal instanceof can.Observe) { self.__set(prop, newVal, curVal) // if its an object, let attr merge } else if (canMakeObserve(curVal) && canMakeObserve(newVal) && curVal.attr) { curVal.attr(newVal, remove) // otherwise just set } else if (curVal != newVal) { self.__set(prop, newVal, curVal) } delete props[prop]; }) // Add remaining props. for (var prop in props) { newVal = props[prop]; this._set(prop, newVal, true) } Observe.stopBatch() return this; }, compute: function (prop) { var self = this, computer = function (val) { return self.attr(prop, val); }; return can.compute ? can.compute(computer) : computer; } }); // Helpers for `observable` lists. var splice = [].splice, list = Observe( { setup: function (instances, options) { this.length = 0; can.cid(this, ".observe") this._init = 1; if (can.isDeferred(instances)) { this.replace(instances) } else { this.push.apply(this, can.makeArray(instances || [])); } this.bind('change' + this._cid, can.proxy(this._changes, this)); can.extend(this, options); delete this._init; }, _triggerChange: function (attr, how, newVal, oldVal) { Observe.prototype._triggerChange.apply(this, arguments) // `batchTrigger` direct add and remove events... if (!~attr.indexOf('.')) { if (how === 'add') { Observe.triggerBatch(this, how, [newVal, +attr]); Observe.triggerBatch(this, 'length', [this.length]); } else if (how === 'remove') { Observe.triggerBatch(this, how, [oldVal, +attr]); Observe.triggerBatch(this, 'length', [this.length]); } else { Observe.triggerBatch(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, this.constructor.Observe, this.constructor) } } if (howMany === undefined) { howMany = args[1] = this.length - index; } var removed = splice.apply(this, args); can.Observe.startBatch(); if (howMany > 0) { this._triggerChange("" + index, "remove", undefined, removed); unhookup(removed, this._cid); } if (args.length > 2) { this._triggerChange("" + index, "add", args.slice(2), removed); } can.Observe.stopBatch(); return removed; }, _attrs: function (items, remove) { if (items === undefined) { return serialize(this, 'attr', []); } // Create a copy. items = can.makeArray(items); Observe.startBatch(); this._updateAttrs(items, remove); Observe.stopBatch() }, _updateAttrs: function (items, remove) { var len = Math.min(items.length, this.length); for (var prop = 0; prop < len; prop++) { var curVal = this[prop], newVal = items[prop]; if (canMakeObserve(curVal) && canMakeObserve(newVal)) { curVal.attr(newVal, remove) } else if (curVal != newVal) { this._set(prop, newVal) } else { } } if (items.length > this.length) { // Add in the remaining props. this.push.apply(this, items.slice(this.length)); } else if (items.length < this.length && remove) { this.splice(items.length) } } }), // 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) { var orig = [][name] list.prototype[name] = function () { // Get the items being added. var args = [], // Where we are going to add items. len = where ? this.length : 0, i = arguments.length, res, val, constructor = this.constructor; // Go through and convert anything to an `observe` that needs to be converted. while (i--) { val = arguments[i]; args[i] = canMakeObserve(val) ? hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) : val; } // Call the original method. res = orig.apply(this, args); if (!this.comparator || args.length) { this._triggerChange("" + 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. this._triggerChange("" + len, "remove", undefined, [res]) if (res && res.unbind) { res.unbind("change" + this._cid) } return res; } }); can.extend(list.prototype, { indexOf: function (item) { this.attr('length') return can.inArray(item, this) }, join: [].join, reverse: [].reverse, slice: function () { var temp = Array.prototype.slice.apply(this, arguments); return new this.constructor(temp); }, concat: function () { var args = []; can.each(can.makeArray(arguments), function (arg, i) { args[i] = arg instanceof can.Observe.List ? arg.serialize() : arg; }); return new this.constructor(Array.prototype.concat.apply(this.serialize(), args)); }, forEach: function (cb, thisarg) { can.each(this, cb, thisarg || this); }, replace: function (newList) { if (can.isDeferred(newList)) { newList.then(can.proxy(this.replace, this)); } else { this.splice.apply(this, [0, this.length].concat(can.makeArray(newList || []))); } return this; } }); Observe.List = list; Observe.setup = function () { can.Construct.setup.apply(this, arguments); // I would prefer not to do it this way. It should // be using the attributes plugin to do this type of conversion. this.List = Observe.List({ Observe: this }, {}); } // ## can/model/model.js // ## 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 () { var args = can.makeArray(arguments); args[0] = model[func](args[0]); d.resolveWith(d, args); }, function () { d.rejectWith(this, arguments); }); if (typeof def.abort === 'function') { d.abort = function () { return def.abort(); } } return d; }, modelNum = 0, ignoreHookup = /change.observe\d+/, getId = function (inst) { // Instead of using attr, use __get for performance. // Need to set reading can.Observe.__reading && can.Observe.__reading(inst, inst.constructor.id) return inst.__get(inst.constructor.id); }, // Ajax `options` generator function ajax = function (ajaxOb, data, type, dataType, success, error) { var params = {}; // 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(/\s+/); params.url = parts.pop(); if (parts.length) { params.type = parts.pop(); } } else { can.extend(params, ajaxOb); } // If we are a non-array object, copy to a new attrs. params.data = typeof data == "object" && !can.isArray(data) ? can.extend(params.data || {}, data) : data; // Get the url with any templated values filled out. params.url = can.sub(params.url, params.data, true); return can.ajax(can.extend({ type: type || "post", dataType: dataType || "json", success: success, error: error }, params)); }, makeRequest = function (self, type, success, error, method) { var args; // if we pass an array as `self` it it means we are coming from // the queued request, and we're passing already serialized data // self's signature will be: [self, serializedData] if (can.isArray(self)) { args = self[1]; self = self[0]; } else { args = self.serialize(); } args = [args]; var deferred, // 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(); }; } deferred.then(success, error); return deferred; }, // 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.id = 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.Model = can.Observe({ fullName: "can.Model", setup: function (base) { // create store here if someone wants to use model without inheriting from it this.store = {}; can.Observe.setup.apply(this, arguments); // Set default list as model list if (!can.Model) { return; } this.List = ML({ Observe: this }, {}); var self = this, clean = can.proxy(this._clean, self); // go through ajax methods and set them up can.each(ajaxMethods, function (method, name) { // if an ajax method is not a function, it's either // a string url like findAll: "/recipes" or an // ajax options object like {url: "/recipes"} if (!can.isFunction(self[name])) { // use ajaxMaker to convert that into a function // that returns a deferred with the data self[name] = ajaxMaker(method, self[name]); } // check if there's a make function like makeFindAll // these take deferred function and can do special // behavior with it (like look up data in a store) if (self["make" + can.capitalize(name)]) { // pass the deferred method to the make method to get back // the "findAll" method. var newMethod = self["make" + can.capitalize(name)](self[name]); can.Construct._overwrite(self, base, name, function () { // increment the numer of requests this._reqs++; var def = newMethod.apply(this, arguments); var then = def.then(clean, clean); then.abort = def.abort; // attach abort to our then and return it return then; }) } }); if (self.fullName == "can.Model" || !self.fullName) { self.fullName = "Model" + (++modelNum); } // Add ajax converters. this._reqs = 0; this._url = this._shortName + "/{" + this.id + "}" }, _ajax: ajaxMaker, _makeRequest: makeRequest, _clean: function () { this._reqs--; if (!this._reqs) { for (var id in this.store) { if (!this.store[id]._bindings) { delete this.store[id]; } } } return arguments[0]; }, models: function (instancesRawData, oldList) { if (!instancesRawData) { return; } if (instancesRawData instanceof this.List) { return instancesRawData; } // Get the list type. var self = this, tmp = [], res = oldList instanceof can.Observe.List ? oldList : 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; if (res.length) { res.splice(0); } can.each(raw, function (rawPart) { tmp.push(self.model(rawPart)); }); // We only want one change event so push everything at once res.push.apply(res, tmp); if (!arr) { // Push other stuff onto `array`. can.each(instancesRawData, function (val, prop) { if (prop !== 'data') { res.attr(prop, val); } }) } return res; }, model: function (attributes) { if (!attributes) { return; } if (attributes instanceof this) { attributes = attributes.serialize(); } var id = attributes[this.id], model = (id || id === 0) && this.store[id] ? this.store[id].attr(attributes, this.removeAttr || false) : 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) { if (this.isNew()) { var self = this; return can.Deferred().done(function (data) { self.destroyed(data) }).resolve(self); } return makeRequest(this, 'destroy', success, error, 'destroyed'); }, bind: function (eventName) { if (!ignoreHookup.test(eventName)) { if (!this._bindings) { this.constructor.store[this.__get(this.constructor.id)] = 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) { var def = pipe(oldFind.call(this, params), this, method); def.then(success, error); // return the original promise return def; }; }; }); 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); // triggers change event that bubble's like // handler( 'change','1.destroyed' ). This is used // to remove items on destroyed from Model Lists. // but there should be a better way. 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.Model.List = can.Observe.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)) { var index = self.indexOf(ev.target); if (index != -1) { self.splice(index, 1); } } }) } }) // ## can/util/string/deparam/deparam.js // ## 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; if (key) { 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; } }); // ## can/route/route.js // ## 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, wrapQuote = function (str) { return (str + '').replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1"); }, 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) || can.route._querySeparator); // 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 + "($|" + wrapQuote(can.route._querySeparator) + ")"), // 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, { _querySeparator: '&', _paramsMatcher: paramsMatcher, 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 ? can.route._querySeparator + after : ""); } // If no route was found, there is no hash URL, only paramters. return can.isEmptyObject(data) ? "" : can.route._querySeparator + 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] === can.route._querySeparator ? 1 : 0)), // If there is a remainder and it contains a `&key=value` list deparam it. obj = (remainder && can.route._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 !== can.route._querySeparator) { 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) !== can.route._querySeparator) { url = can.route._querySeparator + url; } return can.route._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) { can.route._setup(); 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) }, _setup: function () { // If the hash changes, update the `can.route.data`. can.bind.call(window, 'hashchange', setState); }, _getHash: function () { return location.href.split(/#!?/)[1] || ""; }, _setHash: function (serialized) { var path = (can.route.param(serialized, true)); location.hash = "#!" + path; return path; } }); // 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 or the hash already matches the hash that was set. setState = can.route.setState = function () { var hash = can.route._getHash(); curParams = can.route.deparam(hash); // if the hash data is currently changing, or // 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 `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(); lastHash = can.route._setHash(serialized); }, 1); }); // `onready` event... can.bind.call(document, "ready", can.route.ready); // Libraries other than jQuery don't execute the document `ready` listener // if we are already DOM ready if ((document.readyState === 'complete' || document.readyState === "interactive") && onready) { can.route.ready(); } // extend route to have a similar property // that is often checked in mustache to determine // an object's observability can.route.constructor.canMakeObserve = can.Observe.canMakeObserve; // ## can/control/control.js // ## 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", [can]) || {}, // 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); }, basicProcessor; var Control = can.Control = can.Construct( { // 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 (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); } } } }, // Moves `this` to the first argument, wraps it with `jQuery` if it's an element _shifter: function (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))); }; }, // 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 (!convertedName) { return null; } // If a `{}` template resolves to an object, `convertedName` will be // an array var arr = can.isArray(convertedName), // Get the name name = arr ? convertedName[1] : convertedName, // Grab the event off the end parts = name.split(/\s+/g), event = parts.pop(); return { processor: processors[event] || basicProcessor, parts: [name, parts.join(" "), event], 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 = can.Control._shifter(this, "destroy"), funcName, ready; for (funcName in actions) { // Only push if we have the action and no option is `undefined` 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 (func === undefined) { func = eventName; eventName = selector; selector = null; } if (typeof func == 'string') { func = can.Control._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, can.Control._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", // #104 - Add touch events as default processors // TOOD feature detect? "touchstart", "touchmove", "touchcancel", "touchend", "touchleave"], function (v) { processors[v] = basicProcessor; }); // ## can/control/route/route.js // ## control/route.js // _Controller route integration._ can.Control.processors.route = function (el, event, selector, funcName, controller) { selector = selector || ""; 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); }; }; // ## can/view/view.js // ## 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) { // If helpers is a `function`, it is actually a callback. if (isFunction(helpers)) { callback = helpers; helpers = undefined; } var pipe = function (result) { return $view.frag(result); }, // In case we got a callback, we need to convert the can.view.render // result to a document fragment wrapCallback = isFunction(callback) ? function (frag) { callback(pipe(frag)); } : null, // Get the result. result = $view.render(view, data, helpers, wrapCallback), deferred = can.Deferred(); if (isFunction(result)) { return result; } if (can.isDeferred(result)) { result.then(function (result, data) { deferred.resolve.call(deferred, pipe(result), data); }, function () { deferred.fail.apply(deferred, arguments); }); return deferred; } // Convert it into a dom frag. return pipe(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; // 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. can.each(hookupEls, function (el) { 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: {}, cachedRenderers: {}, 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(), dataCopy = can.extend({}, data); // 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)) { dataCopy = 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])) { dataCopy[prop] = usefulPart(objs.shift()); } } } // Get the rendered result. result = renderer(dataCopy, helpers); // Resolve with the rendered view. deferred.resolve(result, dataCopy); // If there's a `callback`, call it back with the result. callback && callback(result, dataCopy); }, function () { deferred.reject.apply(deferred, arguments) }); // 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(data ? renderer(data, helpers) : renderer); }) } else { // if the deferred is resolved, call the cached renderer instead // this is because it's possible, with recursive deferreds to // need to render a view while its deferred is _resolving_. A _resolving_ deferred // is a deferred that was just resolved and is calling back it's success callbacks. // If a new success handler is called while resoliving, it does not get fired by // jQuery's deferred system. So instead of adding a new callback // we use the cached renderer. // We also add __view_id on the deferred so we can look up it's cached renderer. // In the future, we might simply store either a deferred or the cached result. if (deferred.state() === "resolved" && deferred.__view_id) { var currentRenderer = $view.cachedRenderers[deferred.__view_id]; return data ? currentRenderer(data, helpers) : currentRenderer; } else { // Otherwise, the deferred is complete, so // set response to the result of the rendering. deferred.then(function (renderer) { response = data ? renderer(data, helpers) : renderer; }); } } return response; } }, registerView: function (id, text, type, def) { // Get the renderer function. var func = (type || $view.types[$view.ext]).renderer(id, text); def = def || new can.Deferred(); // Cache if we are caching. if ($view.cache) { $view.cached[id] = def; def.__view_id = id; $view.cachedRenderers[id] = func; } // Return the objects for the response's `dataTypes` // (in this case view). return def.resolve(func); } }); // Makes sure there's a template, if not, have `steal` provide a warning. var checkText = function (text, url) { if (!text.length) { 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; //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 `