UNPKG

funcunit

Version:
1,429 lines (1,208 loc) 142 kB
/*! * 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(strQuote, '&#34;').replace(strSingleQuote, "&#39;"); }, 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) {