UNPKG

todomvc

Version:

> Helping you select an MV\* framework

1,045 lines (999 loc) 32.6 kB
/*! * CanJS - 2.0.3 * http://canjs.us/ * Copyright (c) 2013 Bitovi * Tue, 26 Nov 2013 18:21:22 GMT * Licensed MIT * Includes: CanJS default build * Download from: http://canjs.us/ */ define(["can/util/library", "can/util/bind", "can/construct", "can/util/batch"], function(can, bind) { // ## map.js // `can.Map` // _Provides the observable pattern for JavaScript Objects._ // // Removes all listeners. var bindToChildAndBubbleToParent = function(child, prop, parent){ can.listenTo.call(parent,child,"change", function( /* ev, attr */ ) { // `batchTrigger` the type on this... var args = can.makeArray(arguments), ev = args.shift(); args[0] = (prop === "*" ? [ parent.indexOf( child ), args[0]] : [ prop, args[0]] ).join("."); // track objects dispatched on this map 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); }); }, // An `id` to track events for a given map. observeId = 0, attrParts = function(attr, keepKey) { if(keepKey) { return [attr]; } return can.isArray(attr) ? attr : (""+attr).split("."); }, makeBindSetup = function(wildcard){ return function(){ var parent = this; this._each(function(child, prop){ if(child && child.bind){ bindToChildAndBubbleToParent(child, wildcard || prop, parent) } }) }; }, // A map that temporarily houses a reference // to maps that have already been made for a plain ole JS object madeMap = null, addToMap = function(obj, instance){ var teardown = false; if(!madeMap){ teardown = true; madeMap = {} } // record if it has a Cid before we add one var hasCid = obj._cid; var cid = can.cid(obj); // only update if there already isn't one if( !madeMap[cid] ){ madeMap[cid] = { obj: obj, instance: instance, added: !hasCid } } return teardown; }; teardownMap = function(){ for(var cid in madeMap){ if(madeMap[cid].added) { delete madeMap[cid].obj._cid; } } madeMap = null; }, getMapFromObject = function(obj){ return madeMap && madeMap[obj._cid] && madeMap[obj._cid].instance } /** * @add can.Map */ // var Map = can.Map = can.Construct.extend( { /** * @static */ setup: function(){ can.Construct.setup.apply( this, arguments ); if(can.Map){ if(!this.defaults){ this.defaults = {}; } for(var prop in this.prototype){ if(typeof this.prototype[prop] !== "function"){ this.defaults[prop] = this.prototype[prop]; } } } // if we inerit from can.Map, but not can.List if(can.List && !(this.prototype instanceof can.List) ){ this.List = Map.List({ Map : this }, {}); } }, // keep so it can be overwritten bind: can.bindAndSetup, on: can.bindAndSetup, unbind: can.unbindAndTeardown, off: can.unbindAndTeardown, id: "id", helpers: { canMakeObserve : function( obj ) { return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject( obj ) || ( obj instanceof can.Map )); }, unhookup: function(items, parent){ return can.each(items, function(item){ if(item && item.unbind){ can.stopListening.call(parent, item,"change"); } }); }, // Listens to changes on `child` and "bubbles" the event up. // `child` - The object to listen for changes on. // `prop` - The property name is at on. // `parent` - The parent object of prop. // `ob` - (optional) The Map object constructor // `list` - (optional) The observable list constructor hookupBubble: function( child, prop, parent, Ob, List ) { Ob = Ob || Map; List = List || Map.List; // If it's an `array` make a list, otherwise a child. if (child instanceof Map){ // We have an `map` already... // Make sure it is not listening to this already // It's only listening if it has bindings already. parent._bindings && Map.helpers.unhookup([child], parent); } else if ( can.isArray(child) ) { child = getMapFromObject(child) || new List(child); } else { child = getMapFromObject(child) || new Ob(child); } // only listen if something is listening to you if(parent._bindings){ // Listen to all changes and `batchTrigger` upwards. bindToChildAndBubbleToParent(child, prop, parent) } return child; }, // A helper used to serialize an `Map` or `Map.List`. // `map` - The observable. // `how` - To serialize with `attr` or `serialize`. // `where` - To put properties, in an `{}` or `[]`. serialize: function( map, how, where ) { // Go through each property. map.each(function( val, name ) { // If the value is an `object`, and has an `attrs` or `serialize` function. where[name] = Map.helpers.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; }, makeBindSetup: makeBindSetup }, // starts collecting events // takes a callback for after they are updated // how could you hook into after ejs /** * @function can.Map.keys keys * @parent can.Map.static * @description Iterate over the keys of an Map. * @signature `can.Map.keys(map)` * @param {can.Map} map the `can.Map` to get the keys from * @return {Array} array An array containing the keys from _map_. * * @body * `keys` iterates over an map to get an array of its keys. * * @codestart * var people = new can.Map({ * a: 'Alice', * b: 'Bob', * e: 'Eve' * }); * * can.Map.keys(people); // ['a', 'b', 'e'] * @codeend */ keys: function(map) { var keys = []; can.__reading && can.__reading(map, '__keys'); for(var keyName in map._data) { keys.push(keyName); } return keys; } }, /** * @prototype */ { setup: function( obj ) { // `_data` is where we keep the properties. this._data = {} /** * @property {String} can.Map.prototype._cid * @hide * * A globally unique ID for this `can.Map` instance. */ // The namespace this `object` uses to listen to events. can.cid(this, ".map"); // Sets all `attrs`. this._init = 1; this._setupComputes(); var teardownMapping = obj && addToMap(obj, this); /** * @property {*} can.Map.prototype.DEFAULT-ATTR * * @description Specify a default property and value. * * @option {*} A value of any type other than a function that will * be set as the `DEFAULT-ATTR` attribute's value. * * @body * * ## Use * * When extending [can.Map], if a prototype property is not a function, * it is used as a default value on instances of the extended Map. For example: * * var Paginate = can.Map.extend({ * limit: 20, * offset: 0, * next: function(){ * this.attr("offset", this.attr("offset")+this.attr("limit")) * } * }); * * var paginate = new Paginate({limit: 30}); * * paginate.attr("offset") //-> 0 * paginate.attr("limit") //-> 30 * * paginate.next(); * * paginate.attr("offset") //-> 30 */ var data = can.extend( can.extend(true,{},this.constructor.defaults || {}), obj ) this.attr(data); if(teardownMapping){ teardownMap() } this.bind('change',can.proxy(this._changes,this)); delete this._init; }, /** * @property {can.compute} can.Map.prototype.COMPUTE-ATTR * * @description Specify an attribute that is computed from other attributes. * * @option {can.compute} A compute that reads values on instances of the * map and returns a derived value. The compute may also be a getter-setter * compute and able to be passed a value. * * @body * * ## Use * * When extending [can.Map], if a prototype property is a [can.compute] * it will setup that compute to behave like a normal attribute. This means * that it can be read and written to with [can.Map::attr attr] and bound to * with [can.Map::bind bind]. * * The following example makes a `fullName` attribute on `Person` maps: * * var Person = can.Map.extend({ * fullName: can.compute(function(){ * return this.attr("first")+" "+this.attr("last") * }) * }) * * var me = new Person({first: "Justin", last: "Meyer"}) * * me.attr("fullName") //-> "Justin Meyer" * * me.bind("fullName", function(ev, newValue, oldValue){ * newValue //-> Brian Moschel * oldValue //-> Justin Meyer * }) * * me.attr({first: "Brian", last: "Moschel"}) * * ## Getter / Setter computes * * A compute's setter will be called if [can.Map::attr attr] is * used to set the compute-property's value. * * The following makes `fullName` able to set `first` and `last`: * * var Person = can.Map.extend({ * fullName: can.compute(function(newValue){ * if( arguments.length ) { * var parts = newValue.split(" "); * this.attr({ * first: parts[0], * last: parts[1] * }); * * } else { * return this.attr("first")+" "+this.attr("last"); * } * }) * }) * * var me = new Person({first: "Justin", last: "Meyer"}) * * me.attr("fullName", "Brian Moschel") * me.attr("first") //-> "Brian" * me.attr("last") //-> "Moschel" * * * ## Alternatives * * [can.Mustache] and [can.EJS] will automatically convert any function * read in the template to a can.compute. So, simply having a fullName * function like: * * var Person = can.Map.extend({ * fullName: function(){ * return this.attr("first")+" "+this.attr("last") * } * }) * var me = new Person({first: "Justin", last: "Meyer"}) * * Will already be live-bound if read in a template like: * * {{me.fullName}} * <%= me.attr("fullName") %> * * The [can.Map.setter setter] plugin can also provide similar functionality as * Getter/Setter computes. */ _setupComputes: function(){ var prototype = this.constructor.prototype; this._computedBindings = {} for(var prop in prototype){ if(prototype[prop] && prototype[prop].isComputed){ this[prop] = prototype[prop].clone(this); this._computedBindings[prop] = { count: 0 } } } }, _bindsetup: makeBindSetup(), _bindteardown: function(){ var self = this; this._each(function(child){ Map.helpers.unhookup([child], self) }) }, _changes: function(ev, attr, how,newVal, oldVal){ can.batch.trigger(this, {type:attr, batchNum: ev.batchNum}, [newVal,oldVal]); }, _triggerChange: function(attr, how,newVal, oldVal){ can.batch.trigger(this,"change",can.makeArray(arguments)) }, // no live binding iterator _each: function(callback){ var data = this.__get(); for(var prop in data){ if(data.hasOwnProperty(prop)){ callback(data[prop],prop) } } }, /** * @function can.Map.prototype.attr attr * @description Get or set properties on an Map. * @signature `map.attr()` * * Gets a collection of all the properties in this `can.Map`. * * @return {Object<String, *>} an object with all the properties in this `can.Map`. * * @signature `map.attr(key)` * * Reads a property from this `can.Map`. * * @param {String} key the property to read * @return {*} the value assigned to _key_. * * @signature `map.attr(key, value)` * * Assigns _value_ to a property on this `can.Map` called _key_. * * @param {String} key the property to set * @param {*} the value to assign to _key_. * @return {can.Map} this Map, for chaining * * @signature `map.attr(obj[, removeOthers])` * * Assigns each value in _obj_ to a property on this `can.Map` named after the * corresponding key in _obj_, effectively merging _obj_ into the Map. * * @param {Object<String, *>} obj a collection of key-value pairs to set. * If any properties already exist on the `can.Map`, they will be overwritten. * * @param {bool} [removeOthers=false] whether to remove keys not present in _obj_. * To remove keys without setting other keys, use `[can.Map::removeAttr removeAttr]`. * * @return {can.Map} this Map, for chaining * * @body * `attr` gets or sets properties on the `can.Map` it's called on. Here's a tour through * how all of its forms work: * * @codestart * var people = new can.Map({}); * * // set a property: * people.attr('a', 'Alex'); * * // get a property: * people.attr('a'); // 'Alex' * * // set and merge multiple properties: * people.attr({ * a: 'Alice', * b: 'Bob' * }); * * // get all properties: * people.attr(); // {a: 'Alice', b: 'Bob'} * * // set properties while removing others: * people.attr({ * b: 'Bill', * e: 'Eve' * }, true); * * people.attr(); // {b: 'Bill', e: 'Eve'} * @codeend * * ## Deep properties * * `attr` can also set and read deep properties. All you have to do is specify * the property name as you normally would if you weren't using `attr`. * * @codestart * var people = new can.Map({names: {}}); * * // set a property: * people.attr('names.a', 'Alice'); * * // get a property: * people.attr('names.a'); // 'Alice' * people.names.attr('a'); // 'Alice' * * // get all properties: * people.attr(); // {names: {a: 'Alice'}} * @codeend * * Objects that are added to Observes become Observes themselves behind the scenes, * so changes to deep properties fire events at each level, and you can bind at any * level. As this example shows, all the same events are fired no matter what level * you call `attr` at: * * @codestart * var people = new can.Map({names: {}}); * * people.bind('change', function(ev, attr, how, newVal, oldVal) { * console.log('people change: ' + attr + ', ' + how + ', ' + newVal + ', ' + oldVal); * }); * * people.names.bind('change', function(ev, attr, how, newVal, oldVal) { * console.log('people.names change' + attr + ', ' + how + ', ' + newVal + ', ' + oldVal); * }); * * people.bind('names', function(ev, newVal, oldVal) { * console.log('people names: ' + newVal + ', ' + oldVal); * }); * * people.names.bind('a', function(ev, newVal, oldVal) { * console.log('people.names a: ' + newVal + ', ' + oldVal); * }); * * people.bind('names.a', function(ev, newVal, oldVal) { * console.log('people names.a: ' + newVal + ', ' + oldVal); * }); * * people.attr('names.a', 'Alice'); // people change: names.a, add, Alice, undefined * // people.names change: a, add, Alice, undefined * // people.names a: Alice, undefined * // people names.a: Alice, undefined * * people.names.attr('b', 'Bob'); // people change: names.b, add, Bob, undefined * // people.names change: b, add, Bob, undefined * // people.names b: Bob, undefined * // people names.b: Bob, undefined * @codeend * * ## See also * * For information on the events that are fired on property changes and how * to listen for those events, see [can.Map.prototype.bind bind]. */ 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 ( arguments.length === 1 ) {// If we are getting a value. // Let people know we are reading. can.__reading && can.__reading(this, attr) return this._get(attr) } else { // Otherwise we are setting. this._set(attr, val); return this; } }, /** * @function can.Map.prototype.each each * @description Call a function on each property of an Map. * @signature `map.each( callback(item, propName ) )` * * `each` iterates through the Map, calling a function * for each property value and key. * * @param {function(*,String)} callback(item,propName) the function to call for each property * The value and key of each property will be passed as the first and second * arguments, respectively, to the callback. If the callback returns false, * the loop will stop. * * @return {can.Map} this Map, for chaining * * @body * @codestart * var names = []; * new can.Map({a: 'Alice', b: 'Bob', e: 'Eve'}).each(function(value, key) { * names.push(value); * }); * * names; // ['Alice', 'Bob', 'Eve'] * * names = []; * new can.Map({a: 'Alice', b: 'Bob', e: 'Eve'}).each(function(value, key) { * names.push(value); * if(key === 'b') { * return false; * } * }); * * names; // ['Alice', 'Bob'] * * @codeend */ each: function() { can.__reading && can.__reading(this, '__keys'); return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments))) }, /** * @function can.Map.prototype.removeAttr removeAttr * @description Remove a property from an Map. * @signature `map.removeAttr(attrName)` * @param {String} attrName the name of the property to remove * @return {*} the value of the property that was removed * * @body * `removeAttr` removes a property by name from an Map. * * @codestart * var people = new can.Map({a: 'Alice', b: 'Bob', e: 'Eve'}); * * people.removeAttr('b'); // 'Bob' * people.attr(); // {a: 'Alice', e: 'Eve'} * @codeend * * Removing an attribute will cause a _change_ event to fire with `'remove'` * passed as the _how_ parameter and `undefined` passed as the _newVal_ to * handlers. It will also cause a _property name_ event to fire with `undefined` * passed as _newVal_. An in-depth description at these events can be found * under `[can.Map.prototype.attr attr]`. */ removeAttr: function( attr ) { // Info if this is List or not var isList = can.List && this instanceof can.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 can.batch.trigger(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 ) { if(attr){ if(this[attr] && this[attr].isComputed && can.isFunction(this.constructor.prototype[attr])){ return this[attr]() } else { return this._data[attr] } } else { return 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 ( Map.helpers.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.Map: 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. Map.helpers.canMakeObserve(value) ? // Hook it up to send event. Map.helpers.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 can.batch.trigger(this, "__keys", undefined); } // `batchTrigger` the change event. this._triggerChange(prop, changeType, value, current); //can.batch.trigger(this, prop, [value, current]); // If we can stop listening to our old value, do it. current && Map.helpers.unhookup([current], this); } }, // Directly sets a property on this `object`. ___set: function( prop, val ) { if(this[prop] && this[prop].isComputed && can.isFunction(this.constructor.prototype[prop])){ this[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 (!(can.isFunction(this.constructor.prototype[prop]))) { this[prop] = val } }, /** * @function can.Map.prototype.bind bind * @description Bind event handlers to an Map. * * @signature `map.bind(eventType, handler)` * * @param {String} eventType the type of event to bind this handler to * @param {Function} handler the handler to be called when this type of event fires * The signature of the handler depends on the type of event being bound. See below * for details. * @return {can.Map} this Map, for chaining * * @body * `bind` binds event handlers to property changes on `can.Map`s. When you change * a property using `attr`, two events are fired on the Map, allowing other parts * of your application to map the changes to the object. * * ## The _change_ event * * The first event that is fired is the _change_ event. The _change_ event is useful * if you want to react to all changes on an Map. * * @codestart * var o = new can.Map({}); * o.bind('change', function(ev, attr, how, newVal, oldVal) { * console.log('Something changed.'); * }); * @codeend * * The parameters of the event handler for the _change_ event are: * * - _ev_ The event object. * - _attr_ Which property changed. * - _how_ Whether the property was added, removed, or set. Possible values are `'add'`, `'remove'`, or `'set'`. * - _newVal_ The value of the property after the change. `newVal` will be `undefined` if the property was removed. * - _oldVal_ Thishe value of the property before the change. `oldVal` will be `undefined` if the property was added. * * Here is a concrete tour through the _change_ event handler's arguments: * * @codestart * var o = new can.Map({}); * o.bind('change', function(ev, attr, how, newVal, oldVal) { * console.log(ev + ', ' + attr + ', ' + how + ', ' + newVal + ', ' + oldVal); * }); * * o.attr('a', 'Alexis'); // [object Object], a, add, Alexis, undefined * o.attr('a', 'Adam'); // [object Object], a, set, Adam, Alexis * o.attr({ * 'a': 'Alice', // [object Object], a, set, Alice, Adam * 'b': 'Bob' // [object Object], b, add, Bob, undefined * }); * o.removeAttr('a'); // [object Object], a, remove, undefined, Alice * @codeend * * (See also `[can.Map::removeAttr removeAttr]`, which removes properties). * * ## The _property name_ event * * The second event that is fired is an event whose type is the same as the changed * property's name. This event is useful for noticing changes to a specific property. * * @codestart * var o = new can.Map({}); * o.bind('a', function(ev, newVal, oldVal) { * console.log('The value of a changed.'); * }); * @codeend * * The parameters of the event handler for the _property name_ event are: * * - _ev_ The event object. * - _newVal_ The value of the property after the change. `newVal` will be `undefined` if the property was removed. * - _oldVal_ The value of the property before the change. `oldVal` will be `undefined` if the property was added. * * Here is a concrete tour through the _property name_ event handler's arguments: * * @codestart * var o = new can.Map({}); * o.bind('a', function(ev, newVal, oldVal) { * console.log(ev + ', ' + newVal + ', ' + oldVal); * }); * * o.attr('a', 'Alexis'); // [object Object], Alexis, undefined * o.attr('a', 'Adam'); // [object Object], Adam, Alexis * o.attr({ * 'a': 'Alice', // [object Object], Alice, Adam * 'b': 'Bob' * }); * o.removeAttr('a'); // [object Object], undefined, Alice * @codeend * * ## See also * * More information about changing properties on Observes can be found under * [can.Map.prototype.attr attr]. * * For a more specific way to changes on Observes, see the [can.Map.delegate] plugin. */ bind: function(eventName, handler){ var computedBinding = this._computedBindings && this._computedBindings[eventName] if( computedBinding ) { if( !computedBinding.count ) { computedBinding.count = 1; var self = this; computedBinding.handler = function(ev, newVal, oldVal){ can.batch.trigger(self, {type: eventName, batchNum: ev.batchNum}, [newVal, oldVal] ) } this[eventName].bind("change", computedBinding.handler) } else { computedBinding.count++ } } return can.bindAndSetup.apply(this, arguments); }, /** * @function can.Map.prototype.unbind unbind * @description Unbind event handlers from an Map. * @signature `map.unbind(eventType[, handler])` * @param {String} eventType the type of event to unbind, exactly as passed to `bind` * @param {Function} [handler] the handler to unbind * * @body * `unbind` unbinds event handlers previously bound with [can.Map.prototype.bind|`bind`]. * If no _handler_ is passed, all handlers for the given event type will be unbound. * * @codestart * var i = 0, * increaseBy2 = function() { i += 2; }, * increaseBy3 = function() { i += 3; }, * o = new can.Map(); * * o.bind('change', increaseBy2); * o.bind('change', increaseBy3); * o.attr('a', 'Alice'); * i; // 5 * * o.unbind('change', increaseBy2); * o.attr('b', 'Bob'); * i; // 8 * * o.unbind('change'); * o.attr('e', 'Eve'); * i; // 8 * @codeend */ unbind: function(eventName, handler){ var computedBinding = this._computedBindings && this._computedBindings[eventName] if( computedBinding ) { if( computedBinding.count == 1 ) { computedBinding.count = 0; this[eventName].unbind("change", computedBinding.handler); delete computedBinding.handler; } else { computedBinding.count++ } } return can.unbindAndTeardown.apply(this, arguments); }, /** * @function can.Map.prototype.serialize serialize * @description Serialize this object to something that * can be passed to `JSON.stringify`. * @signature `map.serialize()` * * * Get the serialized Object form of the map. Serialized * data is typically used to send back to a server. * * o.serialize() //-> { name: 'Justin' } * * Serialize currently returns the same data * as [can.Map.prototype.attrs]. However, in future * versions, serialize will be able to return serialized * data similar to [can.Model]. The following will work: * * new Map({time: new Date()}) * .serialize() //-> { time: 1319666613663 } * * @return {Object} a JavaScript Object that can be * serialized with `JSON.stringify` or other methods. * */ serialize: function() { return can.Map.helpers.serialize(this, 'serialize', {}); }, /** * @hide * Set multiple properties on the observable * @param {Object} props * @param {Boolean} remove true if you should remove properties that are not in props */ _attrs: function( props, remove ) { if ( props === undefined ) { return Map.helpers.serialize(this, 'attr', {}) } props = can.simpleExtend({}, props); var prop, self = this, newVal; can.batch.start(); this.each(function(curVal, prop){ // you can not have a _cid property! if(prop === "_cid"){ return; } 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.Map ) { self.__set(prop, newVal, curVal) // if its an object, let attr merge } else if ( Map.helpers.canMakeObserve(curVal) && Map.helpers.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 ) { if(prop !== "_cid"){ newVal = props[prop]; this._set(prop, newVal, true) } } can.batch.stop() return this; }, /** * @function can.Map.prototype.compute compute * @description Make a can.compute from an observable property. * @signature `map.compute(attrName)` * @param {String} attrName the property to bind to * @return {can.compute} a [can.compute] bound to _attrName_ * * @body * `compute` is a convenience method for making computes from properties * of Observes. More information about computes can be found under [can.compute]. * * @codestart * var map = new can.Map({a: 'Alexis'}); * var name = map.compute('a'); * name.bind('change', function(ev, nevVal, oldVal) { * console.log('a changed from ' + oldVal + 'to' + newName + '.'); * }); * * name(); // 'Alexis' * * map.attr('a', 'Adam'); // 'a changed from Alexis to Adam.' * name(); // 'Adam' * * name('Alice'); // 'a changed from Adam to Alice.' * name(); // 'Alice' */ compute: function(prop) { if(can.isFunction( this.constructor.prototype[prop] )){ return can.compute(this[prop], this); } else { return can.compute(this,prop); } } }); Map.prototype.on = Map.prototype.bind; Map.prototype.off = Map.prototype.unbind; return Map; });