UNPKG

todomvc

Version:

> Helping you select an MV\* framework

1,030 lines (994 loc) 32.9 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/map"], function(can, Map){ // Helpers for `observable` lists. var splice = [].splice, /** * @add can.List */ list = Map( /** * @static */ { /** * @property {can.Map} can.List.Map * * @description Specify the Map type used to make objects added to this list observable. * * @option {can.Map} When objects are added to a can.List, those objects are * converted into can.Map instances. For example: * * var list = new can.List(); * list.push({name: "Justin"}); * * var map = list.attr(0); * map.attr("name") //-> "Justin" * * By changing [can.List.Map], you can specify a different type of Map instance to * create. For example: * * var User = can.Map.extend({ * fullName: function(){ * return this.attr("first")+" "+this.attr("last") * } * }); * * User.List = can.List.extend({ * Map: User * }); * * var list = new User.List(); * list.push({first: "Justin", last: "Meyer"}); * * var user = list.attr(0); * user.fullName() //-> "Justin Meyer" * * * */ Map: Map /** * @function can.Map.extend * * @signature `can.List.extend([name,] [staticProperties,] instanceProperties)` * * Creates a new extended constructor function. Learn more at [can.Construct.extend]. * * @param {String} [name] If provided, adds the extened List constructor function * to the window at the given name. * * @param {Object} [staticProperties] Properties and methods * directly on the constructor function. The most common property to set is [can.List.Map]. * * @param {Object} [instanceProperties] Properties and methods on instances of this list type. * * @body * * ## Use * * */ }, /** * @prototype */ { setup: function( instances, options ) { this.length = 0; can.cid(this, ".map") this._init = 1; if( can.isDeferred(instances) ) { this.replace(instances) } else { this.push.apply(this, can.makeArray(instances || [])); } // this change needs to be ignored this.bind('change',can.proxy(this._changes,this)); can.simpleExtend(this, options); delete this._init; }, _triggerChange: function(attr, how, newVal, oldVal){ Map.prototype._triggerChange.apply(this,arguments) // `batchTrigger` direct add and remove events... if ( !~ attr.indexOf('.')){ if( how === 'add' ) { can.batch.trigger(this, how, [newVal,+attr]); can.batch.trigger(this,'length',[this.length]); } else if( how === 'remove' ) { can.batch.trigger(this, how, [oldVal, +attr]); can.batch.trigger(this,'length',[this.length]); } else { can.batch.trigger(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) } }, _each: function(callback){ var data = this.__get(); for(var i =0; i < data.length; i++){ callback(data[i],i) } }, _bindsetup: Map.helpers.makeBindSetup("*"), // Returns the serialized form of this list. /** * @hide * Returns the serialized form of this list. */ serialize: function() { return Map.helpers.serialize(this, 'serialize', []); }, /** * @function can.List.prototype.each each * @description Call a function on each element of a List. * @signature `list.each( callback(item, index) )` * * `each` iterates through the Map, calling a function * for each element. * * @param {function(*, Number)} callback the function to call for each element * The value and index of each element will be passed as the first and second * arguments, respectively, to the callback. If the callback returns false, * the loop will stop. * * @return {can.List} this List, for chaining * * @body * @codestart * var i = 0; * new can.Map([1, 10, 100]).each(function(element, index) { * i += element; * }); * * i; // 111 * * i = 0; * new can.Map([1, 10, 100]).each(function(element, index) { * i += element; * if(index >= 1) { * return false; * } * }); * * i; // 11 * @codeend */ // /** * @function can.List.prototype.splice splice * @description Insert and remove elements from a List. * @signature `list.splice(index[, howMany[, ...newElements]])` * @param {Number} index where to start removing or inserting elements * * @param {Number} [howMany] the number of elements to remove * If _howMany_ is not provided, `splice` will all elements from `index` to the end of the List. * * @param {*} newElements elements to insert into the List * * @return {Array} the elements removed by `splice` * * @body * `splice` lets you remove elements from and insert elements into a List. * * This example demonstrates how to do surgery on a list of numbers: * * @codestart * var list = new can.List([0, 1, 2, 3]); * * // starting at index 2, remove one element and insert 'Alice' and 'Bob': * list.splice(2, 1, 'Alice', 'Bob'); * list.attr(); // [0, 1, 'Alice', 'Bob', 3] * @codeend * * ## Events * * `splice` causes the List it's called on to emit _change_ events, * _add_ events, _remove_ events, and _length_ events. If there are * any elements to remove, a _change_ event, a _remove_ event, and a * _length_ event will be fired. If there are any elements to insert, a * separate _change_ event, an _add_ event, and a separate _length_ event * will be fired. * * This slightly-modified version of the above example should help * make it clear how `splice` causes events to be emitted: * * @codestart * var list = new can.List(['a', 'b', 'c', 'd']); * list.bind('change', function(ev, attr, how, newVals, oldVals) { * console.log('change: ' + attr + ', ' + how + ', ' + newVals + ', ' + oldVals); * }); * list.bind('add', function(ev, newVals, where) { * console.log('add: ' + newVals + ', ' + where); * }); * list.bind('remove', function(ev, oldVals, where) { * console.log('remove: ' + oldVals + ', ' + where); * }); * list.bind('length', function(ev, length) { * console.log('length: ' + length + ', ' + this.attr()); * }); * * // starting at index 2, remove one element and insert 'Alice' and 'Bob': * list.splice(2, 1, 'Alice', 'Bob'); // change: 2, 'remove', undefined, ['c'] * // remove: ['c'], 2 * // length: 5, ['a', 'b', 'Alice', 'Bob', 'd'] * // change: 2, 'add', ['Alice', 'Bob'], ['c'] * // add: ['Alice', 'Bob'], 2 * // length: 5, ['a', 'b', 'Alice', 'Bob', 'd'] * @codeend * * More information about binding to these events can be found under [can.List.attr attr]. */ splice: function( index, howMany ) { var args = can.makeArray(arguments), i; for ( i = 2; i < args.length; i++ ) { var val = args[i]; if ( Map.helpers.canMakeObserve(val) ) { args[i] = Map.helpers.hookupBubble(val, "*", this, this.constructor.Map, this.constructor) } } if ( howMany === undefined ) { howMany = args[1] = this.length - index; } var removed = splice.apply(this, args); can.batch.start(); if ( howMany > 0 ) { this._triggerChange(""+index, "remove", undefined, removed); Map.helpers.unhookup(removed, this); } if ( args.length > 2 ) { this._triggerChange(""+index, "add", args.slice(2), removed); } can.batch.stop(); return removed; }, /** * @description Get or set elements in a List. * @function can.List.prototype.attr attr * * @signature `list.attr()` * * Gets an array of all the elements in this `can.List`. * * @return {Array} An array with all the elements in this List. * * @signature `list.attr(index)` * * Reads an element from this `can.List`. * * @param {Number} index The element to read. * @return {*} The value at _index_. * * @signature `list.attr(index, value)` * * Assigns _value_ to the index _index_ on this `can.List`, expanding the list if necessary. * * @param {Number} index The element to set. * @param {*} value The value to assign at _index_. * @return {can.List} This list, for chaining. * * @signature `list.attr(elements[, replaceCompletely])` * * Merges the members of _elements_ into this List, replacing each from the beginning in order. If * _elements_ is longer than the current List, the current List will be expanded. If _elements_ * is shorter than the current List, the extra existing members are not affected (unless * _replaceCompletely_ is `true`). To remove elements without replacing them, use `[can.Map::removeAttr removeAttr]`. * * @param {Array} elements An array of elements to merge in. * * @param {bool} [replaceCompletely=false] whether to completely replace the elements of List * If _replaceCompletely_ is `true` and _elements_ is shorter than the List, the existing * extra members of the List will be removed. * * @return {can.List} This list, for chaining. * * @body * * * ## Use * * `attr` gets or sets elements on the `can.List` it's called on. Here's a tour through * how all of its forms work: * * var people = new can.List(['Alex', 'Bill']); * * // set an element: * people.attr(0, 'Adam'); * * // get an element: * people.attr(0); // 'Adam' * people[0]; // 'Adam' * * // get all elements: * people.attr(); // ['Adam', 'Bill'] * * // extend the array: * people.attr(4, 'Charlie'); * people.attr(); // ['Adam', 'Bill', undefined, undefined, 'Charlie'] * * // merge the elements: * people.attr(['Alice', 'Bob', 'Eve']); * people.attr(); // ['Alice', 'Bob', 'Eve', undefined, 'Charlie'] * * ## 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.List([{name: 'Alex'}, {name: 'Bob'}]); * * // set a property: * people.attr('0.name', 'Alice'); * * // get a property: * people.attr('0.name'); // 'Alice' * people[0].attr('name'); // 'Alice' * * // get all properties: * people.attr(); // [{name: 'Alice'}, {name: 'Bob'}] * @codeend * * The discussion of deep properties under `[can.Map.prototype.attr]` may also * be enlightening. * * ## Events * * `can.List`s emit five types of events in response to changes. They are: * * - the _change_ event fires on every change to a List. * - the _set_ event is fired when an element is set. * - the _add_ event is fired when an element is added to the List. * - the _remove_ event is fired when an element is removed from the List. * - the _length_ event is fired when the length of the List changes. * * ### 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 List. * * @codestart * var list = new can.List([]); * list.bind('change', function(ev, index, how, newVal, oldVal) { * console.log('Something changed.'); * }); * @codeend * * The parameters of the event handler for the _change_ event are: * * - _ev_ The event object. * - _index_ Where the change took place. * - _how_ Whether elements were added, removed, or set. * Possible values are `'add'`, `'remove'`, or `'set'`. * - _newVal_ The elements affected after the change * _newVal_ will be a single value when an index is set, an Array when elements * were added, and `undefined` if elements were removed. * - _oldVal_ The elements affected before the change. * _newVal_ will be a single value when an index is set, an Array when elements * were removed, and `undefined` if elements were added. * * Here is a concrete tour through the _change_ event handler's arguments: * * @codestart * var list = new can.List(); * list.bind('change', function(ev, index, how, newVal, oldVal) { * console.log(ev + ', ' + index + ', ' + how + ', ' + newVal + ', ' + oldVal); * }); * * list.attr(['Alexis', 'Bill']); // [object Object], 0, add, ['Alexis', 'Bill'], undefined * list.attr(2, 'Eve'); // [object Object], 2, add, Eve, undefined * list.attr(0, 'Adam'); // [object Object], 0, set, Adam, Alexis * list.attr(['Alice', 'Bob']); // [object Object], 0, set, Alice, Adam * // [object Object], 1, set, Bob, Bill * list.removeAttr(1); // [object Object], 1, remove, undefined, Bob * @codeend * * ### The _set_ event * * _set_ events are fired when an element at an index that already exists in the List is * modified. Actions can cause _set_ events to fire never also cause _length_ events * to fire (although some functions, such as `[can.List.prototype.splice splice]` * may cause unrelated sets of events to fire after being batched). * * The parameters of the event handler for the _set_ event are: * * - _ev_ The event object. * - _newVal_ The new value of the element. * - _index_ where the set took place. * * Here is a concrete tour through the _set_ event handler's arguments: * * @codestart * var list = new can.List(); * list.bind('set', function(ev, newVal, index) { * console.log(newVal + ', ' + index); * }); * * list.attr(['Alexis', 'Bill']); * list.attr(2, 'Eve'); * list.attr(0, 'Adam'); // Adam, 0 * list.attr(['Alice', 'Bob']); // Alice, 0 * // Bob, 1 * list.removeAttr(1); * @codeend * * ### The _add_ event * * _add_ events are fired when elements are added or inserted * into the List. * * The parameters of the event handler for the _add_ event are: * * - _ev_ The event object. * - _newElements_ The new elements. * If more than one element is added, _newElements_ will be an array. * Otherwise, it is simply the new element itself. * - _index_ Where the add or insert took place. * * Here is a concrete tour through the _add_ event handler's arguments: * * @codestart * var list = new can.List(); * list.bind('add', function(ev, newElements, index) { * console.log(newElements + ', ' + index); * }); * * list.attr(['Alexis', 'Bill']); // ['Alexis', 'Bill'], 0 * list.attr(2, 'Eve'); // Eve, 2 * list.attr(0, 'Adam'); * list.attr(['Alice', 'Bob']); * * list.removeAttr(1); * @codeend * * ### The _remove_ event * * _remove_ events are fired when elements are removed from the list. * * The parameters of the event handler for the _remove_ event are: * * - _ev_ The event object. * - _removedElements_ The removed elements. * If more than one element was removed, _removedElements_ will be an array. * Otherwise, it is simply the element itself. * - _index_ Where the removal took place. * * Here is a concrete tour through the _remove_ event handler's arguments: * * @codestart * var list = new can.List(); * list.bind('remove', function(ev, removedElements, index) { * console.log(removedElements + ', ' + index); * }); * * list.attr(['Alexis', 'Bill']); * list.attr(2, 'Eve'); * list.attr(0, 'Adam'); * list.attr(['Alice', 'Bob']); * * list.removeAttr(1); // Bob, 1 * @codeend * * ### The _length_ event * * _length_ events are fired whenever the list changes. * * The parameters of the event handler for the _length_ event are: * * - _ev_ The event object. * - _length_ The current length of the list. * If events were batched when the _length_ event was triggered, _length_ * will have the length of the list when `stopBatch` was called. Because * of this, you may recieve multiple _length_ events with the same * _length_ parameter. * * Here is a concrete tour through the _length_ event handler's arguments: * * @codestart * var list = new can.List(); * list.bind('length', function(ev, length) { * console.log(length); * }); * * list.attr(['Alexis', 'Bill']); // 2 * list.attr(2, 'Eve'); // 3 * list.attr(0, 'Adam'); * list.attr(['Alice', 'Bob']); * * list.removeAttr(1); // 2 * @codeend */ _attrs: function( items, remove ) { if ( items === undefined ) { return Map.helpers.serialize(this, 'attr', []); } // Create a copy. items = can.makeArray( items ); can.batch.start(); this._updateAttrs(items, remove); can.batch.stop() }, _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 ( Map.helpers.canMakeObserve(curVal) && Map.helpers.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( { /** * @function can.List.prototype.push push * @description Add elements to the end of a list. * @signature `list.push(...elements)` * * `push` adds elements onto the end of a List.] * * @param {*} elements the elements to add to the List * * @return {Number} the new length of the List * * @body * `push` is fairly straightforward: * * @codestart * var list = new can.List(['Alice']); * * list.push('Bob', 'Eve'); * list.attr(); // ['Alice', 'Bob', 'Eve'] * @codeend * * If you have an array you want to concatenate to the end * of the List, you can use `apply`: * * @codestart * var names = ['Bob', 'Eve'], * list = new can.List(['Alice']); * * list.push.apply(list, names); * list.attr(); // ['Alice', 'Bob', 'Eve'] * @codeend * * ## Events * * `push` causes _change_, _add_, and _length_ events to be fired. * * ## See also * * `push` has a counterpart in [can.List.pop pop], or you may be * looking for [can.List.unshift unshift] and its counterpart [can.List.shift shift]. */ push: "length", /** * @function can.List.prototype.unshift unshift * @description Add elements to the beginning of a List. * @signature `list.unshift(...elements)` * * `unshift` adds elements onto the beginning of a List. * * @param {*} elements the elements to add to the List * * @return {Number} the new length of the List * * @body * `unshift` adds elements to the front of the list in bulk in the order specified: * * @codestart * var list = new can.List(['Alice']); * * list.unshift('Bob', 'Eve'); * list.attr(); // ['Bob', 'Eve', 'Alice'] * @codeend * * If you have an array you want to concatenate to the beginning * of the List, you can use `apply`: * * @codestart * var names = ['Bob', 'Eve'], * list = new can.List(['Alice']); * * list.push.apply(list, names); * list.attr(); // ['Bob', 'Eve', 'Alice'] * @codeend * * ## Events * * `unshift` causes _change_, _add_, and _length_ events to be fired. * * ## See also * * `unshift` has a counterpart in [can.List.shift shift], or you may be * looking for [can.List.push push] and its counterpart [can.List.pop pop]. */ 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 `map` that needs to be converted. while(i--){ val = arguments[i]; args[i] = Map.helpers.canMakeObserve(val) ? Map.helpers.hookupBubble(val, "*", this, this.constructor.Map, 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({ /** * @function can.List.prototype.pop pop * @description Remove an element from the end of a List. * @signature `list.pop()` * * `push` removes an element from the end of a List. * * @return {*} the element just popped off the List, or `undefined` if the List was empty * * @body * `pop` is the opposite action from `[can.List.push push]`: * * @codestart * var list = new can.List(['Alice']); * * list.push('Bob', 'Eve'); * list.attr(); // ['Alice', 'Bob', 'Eve'] * * list.pop(); // 'Eve' * list.pop(); // 'Bob' * list.pop(); // 'Alice' * list.pop(); // undefined * @codeend * * ## Events * * `pop` causes _change_, _remove_, and _length_ events to be fired if the List is not empty * when it is called. * * ## See also * * `pop` has its counterpart in [can.List.push push], or you may be * looking for [can.List.unshift unshift] and its counterpart [can.List.shift shift]. */ pop: "length", /** * @function can.List.prototype.shift shift * @description Remove en element from the front of a list. * @signature `list.shift()` * * `shift` removes an element from the beginning of a List. * * @return {*} the element just shifted off the List, or `undefined` if the List is empty * * @body * `shift` is the opposite action from `[can.List.unshift unshift]`: * * @codestart * var list = new can.List(['Alice']); * * list.unshift('Bob', 'Eve'); * list.attr(); // ['Bob', 'Eve', 'Alice'] * * list.shift(); // 'Bob' * list.shift(); // 'Eve' * list.shift(); // 'Alice' * list.shift(); // undefined * @codeend * * ## Events * * `pop` causes _change_, _remove_, and _length_ events to be fired if the List is not empty * when it is called. * * ## See also * * `shift` has a counterpart in [can.List.unshift unshift], or you may be * looking for [can.List.push push] and its counterpart [can.List.pop pop]. */ 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 // `len` - Where these items were removed. // `remove` - Items removed. // `undefined` - The new values (there are none). // `res` - The old, removed values (should these be unbound). this._triggerChange(""+len, "remove", undefined, [res]) if ( res && res.unbind ) { can.stopListening.call(this, res, "change"); } return res; } }); can.extend(list.prototype, { /** * @function can.List.prototype.indexOf indexOf * @description Look for an item in a List. * @signature `list.indexOf(item)` * * `indexOf` finds the position of a given item in the List. * * @param {*} item the item to find * * @return {Number} the position of the item in the List, or -1 if the item is not found. * * @body * @codestart * var list = new can.List(['Alice', 'Bob', 'Eve']); * list.indexOf('Alice'); // 0 * list.indexOf('Charlie'); // -1 * @codeend * * It is trivial to make a `contains`-type function using `indexOf`: * * @codestart * function(list, item) { * return list.indexOf(item) >= 0; * } * @codeend */ indexOf: function(item, fromIndex) { this.attr('length') return can.inArray(item, this, fromIndex) }, /** * @function can.List.prototype.join join * @description Join a List's elements into a string. * @signature `list.join(separator)` * * `join` turns a List into a string by inserting _separator_ between the string representations * of all the elements of the List. * * @param {String} separator the string to seperate elements with * * @return {String} the joined string * * @body * @codestart * var list = new can.List(['Alice', 'Bob', 'Eve']); * list.join(', '); // 'Alice, Bob, Eve' * * var beatles = new can.List(['John', 'Paul', 'Ringo', 'George']); * beatles.join('&'); // 'John&Paul&Ringo&George' * @codeend */ join: function(){ return [].join.apply(this.attr(), arguments) }, /** * @function can.List.prototype.reverse reverse * @description Reverse the order of a List. * @signature `list.reverse()` * * `reverse` reverses the elements of the List in place. * * @return {can.List} the List, for chaining * * @body * @codestart * var list = new can.List(['Alice', 'Bob', 'Eve']); * var reversedList = list.reverse(); * * reversedList.attr(); // ['Eve', 'Bob', 'Alice']; * list === reversedList; // true * @codeend */ reverse: [].reverse, /** * @function can.List.prototype.slice slice * @description Make a copy of a part of a List. * @signature `list.slice([start[, end]])` * * `slice` creates a copy of a portion of the List. * * @param {Number} [start=0] the index to start copying from * * @param {Number} [end] the first index not to include in the copy * If _end_ is not supplied, `slice` will copy until the end of the list. * * @return {can.List} a new `can.List` with the extracted elements * * @body * @codestart * var list = new can.List(['Alice', 'Bob', 'Charlie', 'Daniel', 'Eve']); * var newList = list.slice(1, 4); * newList.attr(); // ['Bob', 'Charlie', 'Daniel'] * @codeend * * `slice` is the simplest way to copy a List: * * @codestart * var list = new can.List(['Alice', 'Bob', 'Eve']); * var copy = list.slice(); * * copy.attr(); // ['Alice', 'Bob', 'Eve'] * list === copy; // false * @codeend */ slice : function() { var temp = Array.prototype.slice.apply(this, arguments); return new this.constructor( temp ); }, /** * @function can.List.prototype.concat concat * @description Merge many collections together into a List. * @signature `list.concat(...args)` * @param {Array|can.List|*} args Any number of arrays, Lists, or values to add in * For each parameter given, if it is an Array or a List, each of its elements will be added to * the end of the concatenated List. Otherwise, the parameter itself will be added. * * @body * `concat` makes a new List with the elements of the List followed by the elements of the parameters. * * @codestart * var list = new can.List(); * var newList = list.concat( * 'Alice', * ['Bob', 'Charlie']), * new can.List(['Daniel', 'Eve']), * {f: 'Francis'} * ); * newList.attr(); // ['Alice', 'Bob', 'Charlie', 'Daniel', 'Eve', {f: 'Francis'}] * @codeend */ concat : function() { var args = []; can.each( can.makeArray( arguments ), function( arg, i ) { args[i] = arg instanceof can.List ? arg.serialize() : arg ; }); return new this.constructor(Array.prototype.concat.apply(this.serialize(), args)); }, /** * @function can.List.prototype.forEach forEach * @description Call a function for each element of a List. * @signature `list.forEach(callback[, thisArg])` * @param {function(element, index, list)} callback a function to call with each element of the List * The three parameters that _callback_ gets passed are _element_, the element at _index_, _index_ the * current element of the list, and _list_ the List the elements are coming from. * @param {Object} [thisArg] the object to use as `this` inside the callback * * @body * `forEach` calls a callback for each element in the List. * * @codestart * var list = new can.List([1, 2, 3]); * list.forEach(function(element, index, list) { * list.attr(index, element * element); * }); * list.attr(); // [1, 4, 9] * @codeend */ forEach : function( cb, thisarg ) { return can.each(this, cb, thisarg || this ); }, /** * @function can.List.prototype.replace replace * @description Replace all the elements of a List. * @signature `list.replace(collection)` * @param {Array|can.List|can.Deferred} collection the collection of new elements to use * If a [can.Deferred] is passed, it must resolve to an `Array` or `can.List`. * The elements of the list are not actually removed until the Deferred resolves. * * @body * `replace` replaces all the elements of this List with new ones. * * `replace` is especially useful when `can.List`s are live-bound into `[can.Control]`s, * and you intend to populate them with the results of a `[can.Model]` call: * * @codestart * can.Control({ * init: function() { * this.list = new Todo.List(); * // live-bind the list into the DOM * this.element.html(can.view('list.mustache', this.list)); * // when this AJAX call returns, the live-bound DOM will be updated * this.list.replace(Todo.findAll()); * } * }); * @codeend * * Learn more about [can.Model.List making Lists of models]. * * ## Events * * A major difference between `replace` and `attr(newElements, true)` is that `replace` always emits * an_add_ event and a _remove_ event, whereas `attr` will cause _set_ events along an _add_ or _remove_ * event if needed. Corresponding _change_ and _length_ events will be fired as well. * * The differences in the events fired by `attr` and `replace` are demonstrated concretely by this example: * @codestart * var attrList = new can.List(['Alexis', 'Bill']); * attrList.bind('change', function(ev, index, how, newVals, oldVals) { * console.log(index + ', ' + how + ', ' + newVals + ', ' + oldVals); * }); * * var replaceList = new can.List(['Alexis', 'Bill']); * replaceList.bind('change', function(ev, index, how, newVals, oldVals) { * console.log(index + ', ' + how + ', ' + newVals + ', ' + oldVals); * }); * * attrList.attr(['Adam', 'Ben'], true); // 0, set, Adam, Alexis * // 1, set, Ben, Bill * replaceList.replace(['Adam', 'Ben']); // 0, remove, undefined, ['Alexis', 'Bill'] * // 0, add, undefined, ['Adam', 'Ben'] * * attrList.attr(['Amber'], true); // 0, set, Amber, Adam * // 1, remove, undefined, Ben * replaceList.replace(['Amber']); // 0, remove, undefined, ['Adam', 'Ben'] * // 0, add, Amber, ['Adam', 'Ben'] * * attrList.attr(['Alice', 'Bob', 'Eve'], true); // 0, set, Alice, Amber * // 1, add, ['Bob', 'Eve'], undefined * replaceList.replace(['Alice', 'Bob', 'Eve']); // 0, remove, undefined, Amber * // 0, add, ['Alice', 'Bob', 'Eve'], Amber * @codeend */ 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; } }); can.List = Map.List = list; return can.List; });