diff --git a/.gitignore b/.gitignore index 56d3518d640..7a5c1c09b67 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ docs* *.DS_Store .idea bower_components/ -test/pluginified/latest.js \ No newline at end of file +test/pluginified/latest.js +docco \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index d848652d594..3181e533ec2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -376,15 +376,22 @@ module.exports = function (grunt) { } }, docco: { - dev: { - src: [ - 'component/**/*.js', 'compute/**/*.js', 'construct/**/*.js', 'control/**/*.js', 'list/**/*.js', - 'map/**/*.js', 'model/**/*.js', 'observe/**/*.js','route/**/*.js', 'util/**/*.js','view/**/*.js', - '!util/dojo/dojo-1.8.1.js', '!util/dojo/nodelist-traverse.js','!**/*_test.js' + options: { + dst: 'docco/', + layout : 'parallel', + css : 'resources/docco.css' + }, + docs: { + files : [ + { + src : [ + 'component/**/*.js', 'compute/**/*.js', 'construct/**/*.js', 'control/**/*.js', 'list/**/*.js', + 'map/**/*.js', 'model/**/*.js', 'observe/**/*.js','route/**/*.js', 'util/**/*.js','view/**/*.js', + '!util/dojo/dojo-1.8.1.js', '!util/dojo/nodelist-traverse.js','!**/*_test.js' + ], + expand : true + } ], - options: { - output: 'docco/' - } } }, plato: { @@ -450,7 +457,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-shell'); grunt.loadNpmTasks('bitovi-tools'); grunt.loadNpmTasks('grunt-jsbeautifier'); - grunt.loadNpmTasks('grunt-docco'); + grunt.loadNpmTasks('grunt-docco2'); grunt.loadNpmTasks('grunt-plato'); grunt.registerTask('quality', [ 'jsbeautifier', 'jshint']); diff --git a/component/component.js b/component/component.js index 86599a225ec..79ecb076951 100644 --- a/component/component.js +++ b/component/component.js @@ -35,11 +35,16 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m // rebinds all templated event handlers. can.extend({ setup: function (el, options) { + // call `can.Control.prototype.setup` on the element var res = can.Control.prototype.setup.call(this, el, options); + // set the scope to the one passed from the `options` object this.scope = options.scope; var self = this; + // rebind events on the `scope` change this.on(this.scope, "change", function updateScope() { + // rebind events self.on(); + // manually rebind this function after this change self.on(self.scope, "change", updateScope); }); return res; @@ -73,10 +78,13 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m if (this.prototype.template) { if (typeof this.prototype.template === "function") { var temp = this.prototype.template; + // If `this.prototype.template` is a function create renderer from it by + // wrapping it with the anonymous function that will pass it the arguments this.renderer = function () { return can.view.frag(temp.apply(null, arguments)); }; } else { + // otherwise create the render from the string this.renderer = can.view.mustache(this.prototype.template); } } @@ -106,15 +114,14 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m componentScope, frag; - // scope prototype properties marked with an "@" are added here + // Add scope prototype properties marked with an "@" to the `initialScopeData` object can.each(this.constructor.attributeScopeMappings, function (val, prop) { initalScopeData[prop] = el.getAttribute(can.hyphenate(val)); }); - // get the value in the scope for each attribute + // Get the value in the scope for each attribute // the hookup should probably happen after? can.each(can.makeArray(el.attributes), function (node, index) { - var name = can.camelize(node.nodeName.toLowerCase()), value = node.value; // ignore attributes already in ScopeMappings @@ -135,48 +142,54 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m componentScope.attr(name, newVal); scopePropertyUpdating = null; }; - // compute only returned if bindable + // Compute only returned if bindable compute.bind("change", handler); - // set the value to be added to the scope + // Set the value to be added to the scope initalScopeData[name] = compute(); + // We don't need to listen to the compute `change` if it doesn't have any dependencies if (!compute.hasDependencies) { compute.unbind("change", handler); } else { - // make sure we unbind (there's faster ways of doing this) + // Make sure we unbind (there's faster ways of doing this) can.bind.call(el, "removed", function () { compute.unbind("change", handler); }); - // setup two-way binding + // Setup the two-way binding twoWayBindings[name] = computeData; } }); - if (this.constructor.Map) { + // If `Map` property is set on the constructor use it to wrap the `initialScopeData` componentScope = new this.constructor.Map(initalScopeData); } else if (this.scope instanceof can.Map) { + // If `this.scope` is instance of `can.Map` assign it to the `componentScope` componentScope = this.scope; } else if (can.isFunction(this.scope)) { - + // If `this.scope` is a function, call the function and var scopeResult = this.scope(initalScopeData, hookupOptions.scope, el); - // if the function returns a can.Map, use that as the scope + if (scopeResult instanceof can.Map) { + // If the function returns a can.Map, use that as the scope componentScope = scopeResult; } else if (scopeResult.prototype instanceof can.Map) { + // If `scopeResult` is of a `can.Map` type, use it to wrap the `initialScopeData` componentScope = new scopeResult(initalScopeData); } else { + // Otherwise extend `can.Map` with the `scopeResult` and initialize it with the `initialScopeData` componentScope = new(can.Map.extend(scopeResult))(initalScopeData); } } + // Object to hold the bind handlers so we can tear them down var handlers = {}; - // setup reverse bindings + // Setup reverse bindings can.each(twoWayBindings, function (computeData, prop) { handlers[prop] = function (ev, newVal) { - // check that this property is not being changed because + // Check that this property is not being changed because // it's source value just changed if (scopePropertyUpdating !== prop) { computeData.compute(newVal); @@ -184,35 +197,39 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m }; componentScope.bind(prop, handlers[prop]); }); - // teardown reverse bindings when element is removed + // Teardown reverse bindings when the element is removed can.bind.call(el, "removed", function () { can.each(handlers, function (handler, prop) { componentScope.unbind(prop, handlers[prop]); }); }); - // setup attributes bindings + // Setup the attributes bindings if (!can.isEmptyObject(this.constructor.attributeScopeMappings)) { - + // Bind on the `attributes` event and update the scope. can.bind.call(el, "attributes", function (ev) { + // Convert attribute name from `attribute-name` to the `attributeName` format. var camelized = can.camelize(ev.attributeName); if (component.constructor.attributeScopeMappings[camelized]) { + // If there is a mapping for this attribute, update the `componentScope` attribute componentScope.attr(camelized, el.getAttribute(ev.attributeName)); } }); } + // Set `componentScope` to `this.scope` and set it to the element's `data` object as a `scope` property this.scope = componentScope; can.data(can.$(el), "scope", this.scope); - // create a real Scope object out of the scope property + // Create a real Scope object out of the scope property var renderedScope = hookupOptions.scope.add(this.scope), - // setup helpers to callback with `this` as the component options = { helpers: {} }; + + // Setup helpers to callback with `this` as the component can.each(this.helpers || {}, function (val, prop) { if (can.isFunction(val)) { options.helpers[prop] = function () { @@ -221,29 +238,30 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m } }); - // create a control to listen to events + // Create a control to listen to events this._control = new this.constructor.Control(el, { + // {ass the scope to the control so we can listen to it's changes scope: this.scope }); - // if this component has a template (that we've already converted to a renderer) + // If this component has a template (that we've already converted to a renderer) if (this.constructor.renderer) { // add content to tags if (!options.tags) { options.tags = {}; } - // we need be alerted to when a element is rendered so we can put the original contents of the widget in its place + // We need be alerted to when a element is rendered so we can put the original contents of the widget in its place options.tags.content = function contentHookup(el, rendererOptions) { - // first check if there was content within the custom tag + // First check if there was content within the custom tag // otherwise, render what was within , the default code var subtemplate = hookupOptions.subtemplate || rendererOptions.subtemplate; if (subtemplate) { - // rendererOptions.options is a scope of helpers where `` was found, so + // `rendererOptions.options` is a scope of helpers where `` was found, so // the right helpers should already be available. - // However, _tags.content is going to point to this current content callback. We need to + // However, `_tags.content` is going to point to this current content callback. We need to // remove that so it will walk up the chain delete options.tags.content; @@ -255,37 +273,45 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m rendererOptions.options)); - // restore the content tag so it could potentially be used again (as in lists) + // Restore the content tag so it could potentially be used again (as in lists) options.tags.content = contentHookup; } }; - // render the component's template + // Render the component's template frag = this.constructor.renderer(renderedScope, hookupOptions.options.add(options)); } else { - // otherwise render the contents between the + // Otherwise render the contents between the frag = can.view.frag(hookupOptions.subtemplate ? hookupOptions.subtemplate(renderedScope, hookupOptions.options.add(options)) : ""); } + // Append the resulting document fragment to the element can.appendChild(el, frag); } }); + // If there is a `$` object and it has the `fn` object then create the `scope` plugin that returns + // the scope object if (window.$ && $.fn) { $.fn.scope = function (attr) { if (attr) { + // If `attr` is passed to the `scope` plugin return the value of that attribute on the `scope` object return this.data("scope") .attr(attr); } else { + // otherwise return the whole scope return this.data("scope"); } }; } + // Define the `can.scope` function that can be used to retrieve the `scope` from the element can.scope = function (el, attr) { el = can.$(el); if (attr) { + // If `attr` is passed to the `can.scope` function return the value of that attribute on the `scope` object return can.data(el, "scope") .attr(attr); } else { + // otherwise return the whole scope return can.data(el, "scope"); } }; diff --git a/component/component.md b/component/component.md index 3ffb1e06bbd..60a84375dff 100644 --- a/component/component.md +++ b/component/component.md @@ -3,7 +3,7 @@ @test can/component/test.html @parent canjs @release 2.0 -@link ../docco/component.html docco +@link ../docco/component/component.html docco @description Create widgets that use a template, a view-model diff --git a/compute/compute.js b/compute/compute.js index aa67c1acc7d..cf09b974328 100644 --- a/compute/compute.js +++ b/compute/compute.js @@ -1,38 +1,53 @@ + // # can.compute + // + // `can.compute` allows creation of observable values + // from the result of a funciton. Any time an observable + // value that the function depends on changes, the + // function automatically updates. This enables creating + // observable data that relies on other sources, potentially + // multiple different ones. For instance, a `can.compute` is + // able to: + // - Combine a first and last name into a full name and update when either changes + // - Calculate the absolute value of an observable number, updating any time the observable number does + // - Specify complicated behavior for getting and setting a value, as well as how to handle changes + steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { - // # can.compute - // ## Reading Helpers // - // The following methods are used to call a function and know which observable events - // to listen to for changes. This is done by having every observable - // method that reads a value "broadcast" the corresponding - // event by calling `can.__reading(obserable, event)`. + // The following methods are used to call a function that relies on + // observable data and to track the observable events which should + // be listened to when changes occur. + // To do this, [`can.__reading(observable, event)`](#can-__reading) is called to + // "broadcast" the corresponding event on each read. // // ### Observed // - // An "Observed" is an Object of observable objects and events that - // need to be listened to to know when to check a function for updates. - // It looks like + // An "Observed" is an object of observable objects and events that + // a function relies on. These objects and events must be listened to + // in order to determine when to check a function for updates. + // This looks like the following: // // { - // "map1|first": {obs: map, event: "first"}, - // "map1|last" : {obs: map, event: "last"} + // "map1|first": {obj: map, event: "first"}, + // "map1|last" : {obj: map, event: "last"} // } // - // Each pair is mapped so no duplicates will be listed. - // + // Each object-event pair is mapped so no duplicates will be listed. + // ### State // - // `can.__read` can call a function that ends up calling `can.__read` again. For example, - // a compute can read another compute. - // To make sure we know each compute's "Observed" values, maintain a stack of - // each `__read` call's Observed valeus. + // `can.__read` may call a function that calls `can.__read` again. For + // example, a compute can read another compute. To track each compute's + // `Observed` object (containing observable objects and events), we maintain + // a stack of Observed values for each call to `__read`. var stack = []; - // Calls a function given a context and returns - // the return value of the function and the observable properties and events - // that were read. Example: `{value: 100, observed: Observed}` + // ### can.__read + // + // With a given function and context, calls the function + // and returns the resulting value of the function as well + // as the observable properties and events that were read. can.__read = function (func, self) { // Add an object that `can.__read` will write to. @@ -40,17 +55,22 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { var value = func.call(self); + // Example return value: + // `{value: 100, observed: Observed}` return { value: value, observed: stack.pop() }; }; - // When an observable value is read, it should call `can.__reading` to - // indicate which object and event should be listened to. + // ### can.__reading + // + // When an observable value is read, it must call `can.__reading` to + // broadcast which object and event should be listened to. can.__reading = function (obj, event) { - // Add the observe and attr that was read - // to `observed` + // Add the observable object and the event + // that was read to the `Observed` object on + // the stack. if (stack.length) { stack[stack.length-1][obj._cid + '|' + event] = { obj: obj, @@ -59,7 +79,12 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { } }; + + // ### can.__clearReading + // // Clears and returns the current observables. + // This can be used to access a value without + // it being handled as a regular `read`. can.__clearReading = function () { if (stack.length) { var ret = stack[stack.length-1]; @@ -67,25 +92,30 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { return ret; } }; - // Specifies reading values. + // Specifies current observables. can.__setReading = function (o) { if (stack.length) { stack[stack.length-1] = o; } }; - // Calls a function, and using it's "Observed", sets up bindings to call - // `onchanged` when those events are triggered. + // ## Section Name + + // ### getValueAndBind + // + // Calls a function and sets up bindings to call `onchanged` + // when events from its "Observed" object are triggered. + // Removes bindings from `oldObserved` that are no longer needed. // - func - the function to call. // - context - the `this` of the function. - // - oldObserved - An object that contains what has been bound to - // - onchanged - what to call when any change has happened + // - oldObserved - an object that contains what has already been bound to + // - onchanged - the function to call when any change occurs var getValueAndBind = function (func, context, oldObserved, onchanged) { - // Call the function, get the value and the observeds. + // Call the function, get the value as well as the observed objects and events var info = can.__read(func, context), - // What needs to beound to. + // The objects-event pairs that must be bound to newObserveSet = info.observed, - // A flag that is used to figure out if we are already observing on an event. + // A flag that is used to determine if an event is already being observed. obEv, name; @@ -93,11 +123,13 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { for( name in newObserveSet ) { if( oldObserved[name] ) { - // If name has already been observed, remove from - // `oldObserved` to prevent event from being unbound later. + // After binding is set up, values + // in `oldObserved` will be unbound. So if a name + // has already be observed, remove from `oldObserved` + // to prevent this. delete oldObserved[name]; } else { - // If this has not been observed, listen to it. + // If current name has not been observed, listen to it. obEv = newObserveSet[name]; obEv.obj.bind(obEv.event, onchanged); } @@ -113,8 +145,11 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { return info; }; + // ### updateOnChange + // + // Fires a change event when a compute's value changes var updateOnChange = function(compute, newValue, oldValue, batchNum){ - //console.log("update",compute._cid, newValue, oldValue) + // Only trigger event when value has changed if (newValue !== oldValue) { can.batch.trigger(compute, batchNum ? {type: "change", batchNum: batchNum} : 'change', [ newValue, @@ -123,23 +158,33 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { } }; + // ###setupComputeHandlers + // + // Sets up handlers for a compute. + // - compute - the compute to set up handlers for + // - func - the getter/setter function for the compute + // - context - the `this` for the compute + // - setCachedValue - function for setting cached value + // + // Returns an object with `on` and `off` functions. var setupComputeHandlers = function(compute, func, context, setCachedValue) { - var readInfo, onchanged, batchNum; return { + // Set up handler for when the compute changes on: function(updater){ if(!onchanged) { onchanged = function(ev){ if (compute.bound && (ev.batchNum === undefined || ev.batchNum !== batchNum) ) { - // store the old value + // Keep the old value var oldValue = readInfo.value; - // get the new value + // Get the new value readInfo = getValueAndBind(func, context, readInfo.observed, onchanged); + // Call the updater with old and new values updater(readInfo.value, oldValue, ev.batchNum); batchNum = batchNum = ev.batchNum; @@ -153,6 +198,7 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { compute.hasDependencies = !can.isEmptyObject(readInfo.observed); }, + // Remove handler for the compute off: function(updater){ for (var name in readInfo.observed) { var ob = readInfo.observed[name]; @@ -162,36 +208,51 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { }; }; + // ###isObserve + // + // Checks if an object is observable var isObserve = function (obj) { return obj instanceof can.Map || obj && obj.__get; }, + // Instead of calculating whether anything is listening every time, + // use a function to do nothing (which may be overwritten) k = function () {}; - // if no one is listening ... we can not calculate every time + + // ## Creating a can.compute + // + // A `can.compute` can be created by + // - [Specifying the getterSeter function](#specifying-gettersetter-function) + // - [Observing a property of an object](#observing-a-property-of-an-object) + // - [Specifying an initial value and a setter function](#specifying-an-initial-value-and-a-setter) + // - [Specifying an initial value and how to read, update, and listen to changes](#specifying-an-initial-value-and-a-settings-object) + // - [Simply specifying an initial value](#specifying-only-a-value) can.compute = function (getterSetter, context, eventName) { - + // ### Setting up + // Do nothing if getterSetter is already a compute if (getterSetter && getterSetter.isComputed) { return getterSetter; } - // the computed object + // The computed object var computed, // The following functions are overwritten depending on how compute() is called - // a method to setup listening + // A method to set up listening on = k, - // a method to teardown listening + // A method to teardown listening off = k, - // the current cached value (only valid if bound = true) + // Current cached value (valid only when bound is true) value, - // how to read the value + // How the value is read by default get = function () { return value; }, - // sets the value + // How the value is set by default set = function (newVal) { value = newVal; }, setCached = set, - // save for clone + // Save arguments for cloning args = can.makeArray(arguments), + // updater for when value is changed updater = function (newValue, oldValue, batchNum) { setCached(newValue); updateOnChange(computed, newValue,oldValue, batchNum); @@ -199,32 +260,36 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { // the form of the arguments form; computed = function (newVal) { - // setting ... + // If the computed function is called with arguments, + // a value should be set if (arguments.length) { - // save a reference to the old value + // Save a reference to the old value var old = value; - // setter may return a value if - // setter is for a value maintained exclusively by this compute + // Setter may return the value if setter + // is for a value maintained exclusively by this compute. var setVal = set.call(context, newVal, old); - // if this has dependencies return the current value + // If the computed function has dependencies, + // return the current value if (computed.hasDependencies) { return get.call(context); } + // Setting may not fire a change event, in which case + // the value must be read if (setVal === undefined) { - // it's possible, like with the DOM, setting does not - // fire a change event, so we must read value = get.call(context); } else { value = setVal; } - // fire the change + // Fire the change updateOnChange(computed, value, old); return value; } else { - // Another compute wants to bind to this compute + // Another compute may bind to this `computed` if (stack.length && computed.canReadForChangeEvent !== false) { // Tell the compute to listen to change on this computed + // Use `can.__reading` to allow other compute to listen + // for a change on this `computed` can.__reading(computed, 'change'); // We are going to bind on this compute. // If we are not bound, we should bind so that @@ -233,7 +298,7 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { can.compute.temporarilyBind(computed); } } - // if we are bound, use the cached value + // If computed is bound, use the cached value if (computed.bound) { return value; } else { @@ -241,7 +306,12 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { } } }; + // ###Specifying getterSetter function + // + // If `can.compute` is [called with a getterSetter function](http://canjs.com/docs/can.compute.html#sig_can_compute_getterSetter__context__), + // override set and get if (typeof getterSetter === 'function') { + // `can.compute(getterSetter, [context])` set = getterSetter; get = getterSetter; computed.canReadForChangeEvent = eventName === false ? false : true; @@ -249,7 +319,13 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { var handlers = setupComputeHandlers(computed, getterSetter, context || this, setCached); on = handlers.on; off = handlers.off; - + + // ###Observing a property of an object + // + // If `can.compute` is called with an + // [object, property name, and optional event name](http://canjs.com/docs/can.compute.html#sig_can_compute_object_propertyName__eventName__), + // create a compute from a property of an object. This allows the + // creation of a compute on objects that can be listened to with [`can.bind`](http://canjs.com/docs/can.bind.html) } else if (context) { if (typeof context === 'string') { // `can.compute(obj, "propertyName", [eventName])` @@ -258,6 +334,8 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { if (isObserve) { computed.hasDependencies = true; } + // If object is observable, `attr` will be used + // for getting and setting. get = function () { if (isObserve) { return getterSetter.attr(propertyName); @@ -287,6 +365,10 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { off = function () { can.unbind.call(getterSetter, eventName || propertyName, handler); }; + // ###Specifying an initial value and a setter + // + // If `can.compute` is called with an [initial value and a setter function](http://canjs.com/docs/can.compute.html#sig_can_compute_initialValue_setter_newVal_oldVal__), + // a compute that can adjust incoming values is set up. } else { // `can.compute(initialValue, setter)` if (typeof context === 'function') { @@ -294,6 +376,13 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { set = context; context = eventName; form = 'setter'; + // ###Specifying an initial value and a settings object + // + // If `can.compute` is called with an [initial value and optionally a settings object](http://canjs.com/docs/can.compute.html#sig_can_compute_initialValue__settings__), + // a can.compute is created that can optionally specify how to read, + // update, and listen to changes in dependent values. This form of + // can.compute can be used to derive a compute that derives its + // value from any source } else { // `can.compute(initialValue,{get:, set:, on:, off:})` value = getterSetter; @@ -310,8 +399,12 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { off = options.off || off; } } + // ###Specifying only a value + // + // If can.compute is called with an initialValue only, + // reads to this value can be observed. } else { - // `can.compute(5)` + // `can.compute(initialValue)` value = getterSetter; } can.cid(computed, 'compute'); @@ -324,10 +417,11 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { isComputed: true, _bindsetup: function () { this.bound = true; - // setup live-binding - // while binding, this does not count as a read + // Set up live-binding + // While binding, this should not count as a read var oldReading = can.__clearReading(); on.call(this, updater); + // Restore "Observed" for reading can.__setReading(oldReading); }, _bindteardown: function () { @@ -385,7 +479,7 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { } }); }; - // a list of temporarily bound computes + // A list of temporarily bound computes var computes, unbindComputes = function () { for (var i = 0, len = computes.length; i < len; i++) { computes[i].unbind('change', k); @@ -402,6 +496,7 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) { computes.push(compute); }; + // Whether a compute is truthy can.compute.truthy = function (compute) { return can.compute(function () { var res = compute(); diff --git a/compute/compute.md b/compute/compute.md index 4962acf77d6..12f5cf54cf8 100644 --- a/compute/compute.md +++ b/compute/compute.md @@ -1,7 +1,7 @@ @function can.compute @parent canjs @release 1.1 -@link ../docco/compute.html docco +@link ../docco/compute/compute.html docco @description Create an observable value. diff --git a/construct/construct.md b/construct/construct.md index 108b8c7f464..9ba78f4c39f 100644 --- a/construct/construct.md +++ b/construct/construct.md @@ -3,7 +3,7 @@ @test can/construct/test.html @parent canjs @group can.Construct.plugins plugins -@link ../docco/construct.html docco +@link ../docco/construct/construct.html docco @description diff --git a/control/control.js b/control/control.js index 9792d2d576d..a71f738c3de 100644 --- a/control/control.js +++ b/control/control.js @@ -1,9 +1,14 @@ -steal('can/util', 'can/construct', function (can) { - // ## control.js - // `can.Control` - // _Controller_ +// # can/control/control.js +// +// Create organized, memory-leak free, rapidly performing, stateful +// controls with declarative eventing binding. Used when creating UI +// controls with behaviors, bound to elements on the page. +// ## helpers - // Binds an element, returns a function that unbinds. +steal('can/util', 'can/construct', function (can) { + // + // ### bind + // this helper binds to one element and returns a function that unbinds from that element. var bind = function (el, ev, callback) { can.bind.call(el, ev, callback); @@ -19,7 +24,10 @@ steal('can/util', 'can/construct', function (can) { paramReplacer = /\{([^\}]+)\}/g, special = can.getObject("$.event.special", [can]) || {}, - // Binds an element, returns a function that unbinds. + // ### delegate + // + // this helper binds to elements based on a selector and returns a + // function that unbinds. delegate = function (el, selector, ev, callback) { can.delegate.call(el, selector, ev, callback); return function () { @@ -27,6 +35,8 @@ steal('can/util', 'can/construct', function (can) { }; }, + // ### binder + // // Calls bind or unbind depending if there is a selector. binder = function (el, ev, callback, selector) { return selector ? @@ -40,32 +50,24 @@ steal('can/util', 'can/construct', function (can) { /** * @add can.Control */ - // + // ## *static functions* /** * @static */ { - // Setup pre-processes which methods are event listeners. - /** - * @hide - * - * Setup pre-process which methods are event listeners. - * - */ + // ## can.Control.setup + // + // This function pre-processes which methods are event listeners and which are methods of + // the control. It has a mechanism to allow controllers to inherit default values from super + // classes, like `can.Construct`, and will cache functions that are action functions (see `_isAction`) + // or functions with an underscored name. 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)) { @@ -74,7 +76,10 @@ steal('can/util', 'can/construct', function (can) { } } }, - // Moves `this` to the first argument, wraps it with `jQuery` if it's an element + // ## can.Control._shifter + // + // 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; @@ -89,55 +94,35 @@ steal('can/util', 'can/construct', function (can) { }; }, - // Return `true` if is an action. - /** - * @hide - * @param {String} methodName a prototype function - * @return {Boolean} truthy if an action or not - */ + // ## can.Control._isAction + // + // Return `true` if `methodName` refers to an action. An action is a `methodName` value that + // is not the constructor, and is either a function or string that refers to a function, or is + // defined in `special`, `processors`. Detects whether `methodName` is also a valid method name. _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). - /** - * @hide - * 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). - * - * For performance reasons, this called twice. First, it is called when - * the Control class is created. If the methodName is templated - * like: "{window} foo", it returns null. If it is not templated - * it returns event binding data. - * - * The resulting data is added to this.actions. - * - * When a control instance is created, _action is called again, but only - * on templated actions. - * - * @param {Object} methodName the method that will be bound - * @param {Object} [options] first param merged with class default options - * @return {Object} null or the processor and pre-split parts. - * The processor is what does the binding/subscribing. - */ + // ## can.Control._action + // + // 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). + // + // For performance reasons, `_action` is called twice: + // * It's called when the Control class is created. for templated method names (e.g., `{window} foo`), it returns null. For non-templated method names it returns the event binding data. That data is added to `this.actions`. + // * It is called wehn a control instance is created, but only for templated actions. _action: function (methodName, options) { - // If we don't have options (a `control` instance), we'll run this - // later. + // If we don't have options (a `control` instance), we'll run this later. If we have + // options, run `can.sub` to replace the action template `{}` with values from the `options` + // or `window`. If a `{}` template resolves to an object, `convertedName` will be an array. + // In that case, the event name we want will be the last item in that array. 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, this._lookup(options)) : methodName; if (!convertedName) { //!steal-remove-start @@ -145,14 +130,8 @@ steal('can/util', 'can/construct', function (can) { //!steal-remove-end 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(); @@ -166,505 +145,72 @@ steal('can/util', 'can/construct', function (can) { _lookup: function (options) { return [options, window]; }, + // ## can.Control.processors + // // An object of `{eventName : function}` pairs that Control uses to - // hook up events auto-magically. - /** - * @property {Object.} can.Control.processors processors - * @parent can.Control.static - * - * @description A collection of hookups for custom events on Controls. - * - * @body - * `processors` is an object that allows you to add new events to bind - * to on a control, or to change how existent events are bound. Each - * key-value pair of `processors` is a specification that pertains to - * an event where the key is the name of the event, and the value is - * a function that processes calls to bind to the event. - * - * The processor function takes five arguments: - * - * - _el_: The Control's element. - * - _event_: The event type. - * - _selector_: The selector preceding the event in the binding used on the Control. - * - _callback_: The callback function being bound. - * - _control_: The Control the event is bound on. - * - * Inside your processor function, you should bind _callback_ to the event, and - * return a function for can.Control to call when _callback_ needs to be unbound. - * (If _selector_ is defined, you will likely want to use some form of delegation - * to bind the event.) - * - * Here is a Control with a custom event processor set and two callbacks bound - * to that event: - * - * @codestart - * can.Control.processors.birthday = function(el, ev, selector, callback, control) { - * if(selector) { - * myFramework.delegate(ev, el, selector, callback); - * return function() { myFramework.undelegate(ev, el, selector, callback); }; - * } else { - * myFramework.bind(ev, el, callback); - * return function() { myFramework.unbind(ev, el, callback); }; - * } - * }; - * - * can.Control("EventTarget", { }, { - * 'birthday': function(el, ev) { - * // do something appropriate for the occasion - * }, - * '.grandchild birthday': function(el, ev) { - * // do something appropriate for the occasion - * } - * }); - * - * var target = new EventTarget('#person'); - * @codeend - * - * When `target` is initialized, can.Control will call `can.Control.processors.birthday` - * twice (because there are two event hookups for the _birthday_ event). The first - * time it's called, the arguments will be: - * - * - _el_: A NodeList that wraps the element with id 'person'. - * - _ev_: `'birthday'` - * - _selector_: `''` - * - _callback_: The function assigned to `' birthday'` in the prototype section of `EventTarget`'s - * definition. - * - _control_: `target` itself. - * - * The second time, the arguments are slightly different: - * - * - _el_: A NodeList that wraps the element with id 'person'. - * - _ev_: `'birthday'` - * - _selector_: `'.grandchild'` - * - _callback_: The function assigned to `'.grandchild birthday'` in the prototype section of `EventTarget`'s - * definition. - * - _control_: `target` itself. - * - * can.Control already has processors for these events: - * - * - change - * - click - * - contextmenu - * - dblclick - * - focusin - * - focusout - * - keydown - * - keyup - * - keypress - * - mousedown - * - mouseenter - * - mouseleave - * - mousemove - * - mouseout - * - mouseover - * - mouseup - * - reset - * - resize - * - scroll - * - select - * - submit - */ + // hook up events automatically. processors: {}, - // A object of name-value pairs that act as default values for a - // control instance + // ## can.Control.defaults + // A object of name-value pairs that act as default values for a control instance defaults: {} - /** - * @property {Object} can.Control.defaults defaults - * @parent can.Control.static - * @description Default values for the Control's options. - * - * @body - * `defaults` provides default values for a Control's options. - * Options passed into the constructor function will be shallowly merged - * into the values from defaults in [can.Control::setup], and - * the result will be stored in [can.Control::options this.options]. - * - * Message = can.Control.extend({ - * defaults: { - * message: "Hello World" - * } - * }, { - * init: function(){ - * this.element.text( this.options.message ); - * } - * }); - * - * new Message( "#el1" ); //writes "Hello World" - * new Message( "#el12", { message: "hi" } ); //writes hi - */ }, { + // ## *prototype functions* /** * @prototype */ - // - /** - * @functioncan.Control.prototype.init init - * @parent can.Control.prototype - * @description instance init method required for most applications of [can.Control] - * @signature `control.init(element,options)` - * @param element The wrapped element passed to the control. - * Control accepts a raw HTMLElement, a CSS selector, or a NodeList. - * This is set as `this.element` on the control instance. - * @param options The second argument passed to new Control, - * extended with the can.Control's static _defaults__. - * This is set as `this.options` on the control instance. - * Note that static is used formally to indicate that - * _default values are shared across control instances_. - * - * @body - * Any additional arguments provided to the constructor will be passed as normal. - */ - // Sets `this.element`, saves the control in `data, binds event - // handlers. - /** - * @property {NodeList} can.Control.prototype.element element - * @parent can.Control.prototype - * @description The element associated with this control. - * - * @body - * The library-wrapped element this control is associated with, - * as passed into the constructor. If you want to change the element - * that a Control will attach to, you should do it in [can.Control::setup setup]. - * If you change the element later, make sure to call [can.Control::on on] - * to rebind all the bindings. - * - * If `element` is removed from the DOM, [can.Control::destroy] will - * be called and the Control will be destroyed. - */ - // - /** - * @function can.Control.prototype.setup setup - * @parent can.Control.prototype - * @description Perform pre-initialization logic. - * @signature `control.setup(element, options)` - * @param {HTMLElement|NodeList|String} element The element as passed to the constructor. - * @param {Object} [options] option values for the control. These get added to - * this.options and merged with [can.Control.static.defaults defaults]. - * @return {undefined|Array} return an array if you want to change what init is called with. By - * default it is called with the element and options passed to the control. - * - * @body - * Setup is where most of control's magic happens. It does the following: - * - * ### Sets this.element - * - * The first parameter passed to new Control( el, options ) is expected to be - * an element. This gets converted to a Wrapped NodeList element and set as - * [can.Control.prototype.element this.element]. - * - * ### Adds the control's name to the element's className - * - * Control adds it's plugin name to the element's className for easier - * debugging. For example, if your Control is named "Foo.Bar", it adds - * "foo_bar" to the className. - * - * ### Saves the control in $.data - * - * A reference to the control instance is saved in $.data. You can find - * instances of "Foo.Bar" like: - * - * $( '#el' ).data( 'controls' )[ 'foo_bar' ] - * - * ### Merges Options - * Merges the default options with optional user-supplied ones. - * Additionally, default values are exposed in the static [can.Control.static.defaults defaults] - * so that users can change them. - * - * ### Binds event handlers - * - * Setup does the event binding described in [can.Control]. - */ + // ## setup + // + // Setup is where most of the Control's magic happens. It performs several pre-initialization steps: + // - Sets `this.element` + // - Adds the Control's name to the element's className + // - Saves the Control in `$.data` + // - Merges Options + // - Binds event handlers using `delegate` + // The final step is to return pass the element and prepareed options, to be used in `init`. setup: function (element, options) { var cls = this.constructor, pluginname = cls.pluginName || cls._fullName, arr; - // Want the raw element here. + // Retrieve the raw element, then set the plugin name as a class there. this.element = can.$(element); if (pluginname && pluginname !== 'can_control') { - // Set element and `className` on element. this.element.addClass(pluginname); } - // Set up the 'controls' data on the element + // Set up the 'controls' data on the element. If it does not exist, initialize + // it to an empty array. arr = can.data(this.element, 'controls'); if (!arr) { - // If it does not exist, initialize it to an empty array arr = []; can.data(this.element, 'controls', arr); } arr.push(this); - // Option merging. - /** - * @property {Object} can.Control.prototype.options options - * @parent can.Control.prototype - * - * @description - * - * Options used to configure a control. - * - * @body - * - * The `this.options` property is an Object that contains - * configuration data passed to a control when it is - * created (`new can.Control(element, options)`). - * - * In the following example, an options object with - * a message is passed to a `Greeting` control. The - * `Greeting` control changes the text of its [can.Control::element element] - * to the options' message value. - * - * var Greeting = can.Control.extend({ - * init: function(){ - * this.element.text( this.options.message ) - * } - * }) - * - * new Greeting("#greeting",{message: "I understand this.options"}) - * - * The options argument passed when creating the control - * is merged with [can.Control.defaults defaults] in - * [can.Control.prototype.setup setup]. - * - * In the following example, if no message property is provided, - * the defaults' message property is used. - * - * var Greeting = can.Control.extend({ - * defaults: { - * message: "Defaults merged into this.options" - * } - * },{ - * init: function(){ - * this.element.text( this.options.message ) - * } - * }) - * - * new Greeting("#greeting") - * - */ + // The `this.options` property is an Object that contains configuration data + // passed to a control when it is created (`new can.Control(element, options)`) + // + // The `options` argument passed when creating the control is merged with `can.Control.defaults` + // in [can.Control.prototype.setup setup]. + // + // If no `options` value is used during creation, the value in `defaults` is used instead this.options = extend({}, cls.defaults, options); - // Bind all event handlers. this.on(); - // Gets passed into `init`. - /** - * @property {can.NodeList} can.Control.prototype.element element - * - * @description The element the Control is associated with. - * - * @parent can.Control.prototype - * - * @body - * - * The control instance's HTMLElement (or window) wrapped by the - * util library for ease of use. It is set by the first - * parameter to `new can.Construct( element, options )` - * in [can.Control::setup]. By default, a control listens to events on `this.element`. - * - * ### Quick Example - * - * The following `HelloWorld` control sets the control`s text to "Hello World": - * - * HelloWorld = can.Control({ - * init: function(){ - * this.element.text( 'Hello World' ); - * } - * }); - * - * // create the controller on the element - * new HelloWorld( document.getElementById( '#helloworld' ) ); - * - * ## Wrapped NodeList - * - * `this.element` is a wrapped NodeList of one HTMLELement (or window). This - * is for convenience in libraries like jQuery where all methods operate only on a - * NodeList. To get the raw HTMLElement, write: - * - * this.element[0] //-> HTMLElement - * - * The following details the NodeList used by each library with - * an example of updating its text: - * - * __jQuery__ `jQuery( HTMLElement )` - * - * this.element.text("Hello World") - * - * __Zepto__ `Zepto( HTMLElement )` - * - * this.element.text("Hello World") - * - * __Dojo__ `new dojo.NodeList( HTMLElement )` - * - * this.element.text("Hello World") - * - * __Mootools__ `$$( HTMLElement )` - * - * this.element.empty().appendText("Hello World") - * - * __YUI__ - * - * this.element.set("text", "Hello World") - * - * - * ## Changing `this.element` - * - * Sometimes you don't want what's passed to `new can.Control` - * to be this.element. You can change this by overwriting - * setup or by unbinding, setting this.element, and rebinding. - * - * ### Overwriting Setup - * - * The following Combobox overwrites setup to wrap a - * select element with a div. That div is used - * as `this.element`. Notice how `destroy` sets back the - * original element. - * - * Combobox = can.Control({ - * setup: function( el, options ) { - * this.oldElement = $( el ); - * var newEl = $( '
' ); - * this.oldElement.wrap( newEl ); - * can.Control.prototype.setup.call( this, newEl, options ); - * }, - * init: function() { - * this.element //-> the div - * }, - * ".option click": function() { - * // event handler bound on the div - * }, - * destroy: function() { - * var div = this.element; //save reference - * can.Control.prototype.destroy.call( this ); - * div.replaceWith( this.oldElement ); - * } - * }); - * - * ### unbinding, setting, and rebinding. - * - * You could also change this.element by calling - * [can.Control::off], setting this.element, and - * then calling [can.Control::on] like: - * - * move: function( newElement ) { - * this.off(); - * this.element = $( newElement ); - * this.on(); - * } - */ return [this.element, this.options]; }, - /** - * @function can.Control.prototype.on on - * @parent can.Control.prototype - * - * @description Bind an event handler to a Control, or rebind all event handlers on a Control. - * - * @signature `control.on([el,] selector, eventName, func)` - * @param {HTMLElement|jQuery collection|Object} [el=this.element] - * The element to be bound. If no element is provided, the control's element is used instead. - * @param {CSSSelectorString} selector A CSS selector for event delegation. - * @param {String} eventName The name of the event to listen for. - * @param {Function|String} func A callback function or the String name of a control function. If a control - * function name is given, the control function is called back with the bound element and event as the first - * and second parameter. Otherwise the function is called back like a normal bind. - * @return {Number} The id of the binding in this._bindings. - * - * @body - * `on(el, selector, eventName, func)` binds an event handler for an event to a selector under the scope of the given element. - * - * @signature `control.on()` - * - * Rebind all of a control's event handlers. - * - * @return {Number} The number of handlers bound to this Control. - * - * @body - * `this.on()` is used to rebind - * all event handlers when [can.Control::options this.options] has changed. It - * can also be used to bind or delegate from other elements or objects. - * - * ## Rebinding - * - * By using templated event handlers, a control can listen to objects outside - * `this.element`. This is extremely common in MVC programming. For example, - * the following control might listen to a task model's `completed` property and - * toggle a strike className like: - * - * TaskStriker = can.Control({ - * "{task} completed": function(){ - * this.update(); - * }, - * update: function(){ - * if ( this.options.task.completed ) { - * this.element.addClass( 'strike' ); - * } else { - * this.element.removeClass( 'strike' ); - * } - * } - * }); - * - * var taskstriker = new TaskStriker({ - * task: new Task({ completed: 'true' }) - * }); - * - * To update the `taskstriker`'s task, add a task method that updates - * this.options and rebinds the event handlers for the new task like: - * - * TaskStriker = can.Control({ - * "{task} completed": function(){ - * this.update(); - * }, - * update: function() { - * if ( this.options.task.completed ) { - * this.element.addClass( 'strike' ); - * } else { - * this.element.removeClass( 'strike' ); - * } - * }, - * task: function( newTask ) { - * this.options.task = newTask; - * this.on(); - * this.update(); - * } - * }); - * - * var taskstriker = new TaskStriker({ - * task: new Task({ completed: true }) - * }); - * - * // Now, add a new task that is not yet completed - * taskstriker.task(new Task({ completed: false })); - * - * ## Adding new events - * - * If events need to be bound to outside of the control and templated event handlers - * are not sufficient, you can call this.on to bind or delegate programmatically: - * - * init: function() { - * // calls somethingClicked( el, ev ) - * this.on( 'click', 'somethingClicked' ); - * - * // calls function when the window is clicked - * this.on( window, 'click', function( ev ) { - * //do something - * }); - * }, - * somethingClicked: function( el, ev ) { - * // ... - * } - */ + // ## on + // + // This binds an event handler for an event to a selector under the scope of `this.element` + // If no options are specified, all events are rebound to their respective elements. The actions, + // which were cached in `setup`, are used and all elements are bound using `delegate` from `this.element`. 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, @@ -681,8 +227,7 @@ steal('can/util', 'can/construct', function (can) { } } - // Setup to be destroyed... - // don't bind because we don't want to remove it. + // Set up the ability to `destroy` the control later. can.bind.call(element, "removed", destroyCB); bindings.push(function (el) { can.unbind.call(el, "removed", destroyCB); @@ -710,127 +255,25 @@ steal('can/util', 'can/construct', function (can) { } this._bindings.push(binder(el, eventName, func, selector)); - return this._bindings.length; }, + // ## off + // // Unbinds all event handlers on the controller. - /** - * @hide - * Unbinds all event handlers on the controller. You should never - * be calling this unless in use with [can.Control::on]. - */ + // This should _only_ be called in combination with .on() off: function () { var el = this.element[0]; each(this._bindings || [], function (value) { value(el); }); - // Adds bindings. this._bindings = []; }, - // Prepares a `control` for garbage collection - /** - * @description Remove a Control from an element and clean up the Control. - * @signature `control.destroy()` - * - * Prepares a control for garbage collection and is a place to - * reset any changes the control has made. - * - * @function can.Control.prototype.destroy destroy - * @parent can.Control.prototype - * - * @body - * - * - * ## Allowing Garbage Collection - * - * Destroy is called whenever a control's element is removed from the page using - * the library's standard HTML modifier methods. This means that you - * don't have to call destroy yourself and it - * will be called automatically when appropriate. - * - * The following `Clicker` widget listens on the window for clicks and updates - * its element's innerHTML. If we remove the element, the window's event handler - * is removed auto-magically: - * - * - * Clickr = can.Control({ - * "{window} click": function() { - * this.element.html( this.count ? - * this.count++ : this.count = 0 ); - * } - * }); - * - * // create a clicker on an element - * new Clicker( "#clickme" ); - * - * // remove the element - * $( '#clickme' ).remove(); - * - * - * The methods you can use that will destroy controls automatically by library: - * - * __jQuery and Zepto__ - * - * - $.fn.remove - * - $.fn.html - * - $.fn.replaceWith - * - $.fn.empty - * - * __Dojo__ - * - * - dojo.destroy - * - dojo.empty - * - dojo.place (with the replace option) - * - * __Mootools__ - * - * - Element.prototype.destroy - * - * __YUI__ - * - * - Y.Node.prototype.remove - * - Y.Node.prototype.destroy - * - * - * ## Teardown in Destroy - * - * Sometimes, you want to reset a controlled element back to its - * original state when the control is destroyed. Overwriting destroy - * lets you write teardown code of this manner. __When overwriting - * destroy, make sure you call Control's base functionality__. - * - * The following example changes an element's text when the control is - * created and sets it back when the control is removed: - * - * Changer = can.Control.extend({ - * init: function() { - * this.oldText = this.element.text(); - * this.element.text( "Changed!!!" ); - * }, - * destroy: function() { - * this.element.text( this.oldText ); - * can.Control.prototype.destroy.call( this ); - * } - * }); - * - * // create a changer which changes #myel's text - * var changer = new Changer( '#myel' ); - * - * // destroy changer which will reset it - * changer.destroy(); - * - * ## Base Functionality - * - * Control prepares the control for garbage collection by: - * - * - unbinding all event handlers - * - clearing references to this.element and this.options - * - clearing the element's reference to the control - * - removing it's [can.Control.pluginName] from the element's className - * - */ + // ## destroy + // + // Prepares a `control` for garbage collection. + // First checks if it has already been removed. Then, removes all the bindings, data, and + // the element from the Control instance. destroy: function () { - //Control already destroyed if (this.element === null) { //!steal-remove-start can.dev.warn("can/control/control.js: Control already destroyed"); @@ -841,29 +284,26 @@ steal('can/util', 'can/construct', function (can) { 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. + can.trigger(this, "destroyed"); this.element = null; } }); + // ## Processors + // + // Processors do the binding. This basic processor binds events. Each returns a function that unbinds + // when called. 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); }; @@ -873,8 +313,6 @@ steal('can/util', 'can/construct', function (can) { "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; diff --git a/control/control.md b/control/control.md index 23db52ec341..ab61e892492 100644 --- a/control/control.md +++ b/control/control.md @@ -6,7 +6,7 @@ @inherits can.Construct @description widget factory with declarative event binding. @group can.Control.plugins plugins -@link ../docco/control.html docco +@link ../docco/control/control.html docco @description Create organized, memory-leak free, rapidly performing, stateful controls with declarative event binding. Use `can.Control` to create UI diff --git a/control/doc/defaults.md b/control/doc/defaults.md new file mode 100644 index 00000000000..78750b03006 --- /dev/null +++ b/control/doc/defaults.md @@ -0,0 +1,25 @@ +@property {Object} can.Control.defaults defaults +@parent can.Control.static +@description Default values for the Control's options. + +@body + +Default options provided for when a new control is created without values set in `options`. + +`defaults` provides default values for a Control's options. +Options passed into the constructor function will be shallowly merged +into the values from defaults in [can.Control::setup], and +the result will be stored in [can.Control::options this.options]. + + Message = can.Control.extend({ + defaults: { + message: "Hello World" + } + }, { + init: function(){ + this.element.text( this.options.message ); + } + }); + + new Message( "#el1" ); //writes "Hello World" + new Message( "#el12", { message: "hi" } ); //writes hi \ No newline at end of file diff --git a/control/doc/destroy.md b/control/doc/destroy.md new file mode 100644 index 00000000000..869cd822c3f --- /dev/null +++ b/control/doc/destroy.md @@ -0,0 +1,95 @@ +@function can.Control.prototype.destroy destroy +@parent can.Control.prototype +@description Remove a Control from an element and clean up the Control. +@signature `control.destroy()` + +Prepares a control for garbage collection and is a place to +reset any changes the control has made. + +@body +## Allowing Garbage Collection + +Destroy is called whenever a control's element is removed from the page using +the library's standard HTML modifier methods. This means that you +don't have to call destroy yourself and it +will be called automatically when appropriate. + +The following `Clicker` widget listens on the window for clicks and updates +its element's innerHTML. If we remove the element, the window's event handler +is removed auto-magically: + + + Clicker = can.Control({ + "{window} click": function() { + this.element.html( this.count ? + this.count++ : this.count = 0 ); + } + }); + + // create a clicker on an element + new Clicker( "#clickme" ); + + // remove the element + $( '#clickme' ).remove(); + +The methods you can use that will destroy controls automatically by library: + +__jQuery and Zepto__ + +- `$.fn.remove` +- `$.fn.html` +- `$.fn.replaceWith` +- `$.fn.empty` + +__Dojo__ + +- `dojo.destroy` +- `dojo.empty` +- `dojo.place (with the replace option)` + +__Mootools__ + +- `Element.prototype.destroy` + +__YUI__ + +- `Y.Node.prototype.remove` +- `Y.Node.prototype.destroy` + + +## Teardown in Destroy + +Sometimes, you want to reset a controlled element back to its +original state when the control is destroyed. Overwriting destroy +lets you write teardown code of this manner. + +__NOTE__: When overwriting destroy, make sure you call Control's base functionality. + +The following example changes an element's text when the control is +created and sets it back when the control is removed: + + Changer = can.Control.extend({ + init: function() { + this.oldText = this.element.text(); + this.element.text( "Changed!!!" ); + }, + destroy: function() { + this.element.text( this.oldText ); + can.Control.prototype.destroy.call( this ); + } + }); + + // create a changer which changes #myel's text + var changer = new Changer( '#myel' ); + + // destroy changer which will reset it + changer.destroy(); + +## Base Functionality + +Control prepares the control for garbage collection by: + +- unbinding all event handlers +- clearing references to this.element and this.options +- clearing the element's reference to the control +- removing it's `can.Control.pluginName` from the element's className diff --git a/control/doc/element.md b/control/doc/element.md new file mode 100644 index 00000000000..03658f5e904 --- /dev/null +++ b/control/doc/element.md @@ -0,0 +1,100 @@ +@property {can.NodeList} can.Control.prototype.element element +@parent can.Control.prototype +@description The element passed to the Control when creating a new instance. + +@body + +The control instance's HTMLElement (or window) wrapped by the +util library for ease of use. + +It is set by the first parameter to `new can.Construct( element, options )` +in [can.Control::setup]. By default, a control listens to events on `this.element`. + +### Example - NodeList + +The following `HelloWorld` control sets the control`s text to "Hello World": + + HelloWorld = can.Control({ + init: function(){ + this.element.text( 'Hello World' ); + } + }); + + // create the controller on the element + new HelloWorld( document.getElementById( '#helloworld' ) ); + +## Wrapped NodeList + +`this.element` is a wrapped NodeList of one HTMLELement (or window). This +is for convenience in libraries like jQuery where all methods operate only on a +NodeList. To get the raw HTMLElement, write: + + this.element[0] //-> HTMLElement + +The following details the NodeList used by each library with +an example of updating its text: + +__jQuery__ `jQuery( HTMLElement )` + + this.element.text("Hello World") + +__Zepto__ `Zepto( HTMLElement )` + + this.element.text("Hello World") + +__Dojo__ `new dojo.NodeList( HTMLElement )` + + this.element.text("Hello World") + +__Mootools__ `$$( HTMLElement )` + + this.element.empty().appendText("Hello World") + +__YUI__ + + this.element.set("text", "Hello World") + +## Changing `this.element` + +Sometimes you don't want what's passed to `new can.Control` +to be `this.element`. You can change this by overwriting +setup or by unbinding, setting this.element, and rebinding. + +### Overwriting Setup + +The following Combobox overwrites setup to wrap a +select element with a div. That div is used +as `this.element`. Notice how `destroy` sets back the +original element. + + Combobox = can.Control({ + setup: function( el, options ) { + this.oldElement = $( el ); + var newEl = $( '
' ); + this.oldElement.wrap( newEl ); + can.Control.prototype.setup.call( this, newEl, options ); + }, + init: function() { + this.element //-> the div + }, + ".option click": function() { + // event handler bound on the div + }, + destroy: function() { + var div = this.element; //save reference + can.Control.prototype.destroy.call( this ); + div.replaceWith( this.oldElement ); + } + }); + +### Unbinding, setting, and rebinding. + +You could also change this.element by calling +[can.Control::off], setting this.element, and +then calling [can.Control::on] like: + + move: function( newElement ) { + this.off(); + this.element = $( newElement ); + this.on(); + } \ No newline at end of file diff --git a/control/doc/on.md b/control/doc/on.md new file mode 100644 index 00000000000..5a837ac2725 --- /dev/null +++ b/control/doc/on.md @@ -0,0 +1,96 @@ +@function can.Control.prototype.on on +@parent can.Control.prototype +@description Bind an event handler to a Control, or rebind all event handlers on a Control. + +@signature `control.on([el,] selector, eventName, func)` +@param {HTMLElement|jQuery collection|Object} [el=this.element] +The element to be bound. If no element is provided, the control's element is used instead. +@param {CSSSelectorString} selector A CSS selector for event delegation. +@param {String} eventName The name of the event to listen for. +@param {Function|String} func A callback function or the String name of a control function. If a control +function name is given, the control function is called back with the bound element and event as the first +and second parameter. Otherwise the function is called back like a normal bind. +@return {Number} The id of the binding in this._bindings. + +`on(el, selector, eventName, func)` binds an event handler for an event to a selector under the scope of the given element. + +@signature `control.on()` + +Rebind all of a control's event handlers. + +@return {Number} The number of handlers bound to this Control. + +@body +`this.on()` is used to rebind +all event handlers when [can.Control::options this.options] has changed. It +can also be used to bind or delegate from other elements or objects. + +## Rebinding + +By using templated event handlers, a control can listen to objects outside +`this.element`. This is extremely common in MVC programming. For example, +the following control might listen to a task model's `completed` property and +toggle a strike className like: + + TaskStriker = can.Control({ + "{task} completed": function(){ + this.update(); + }, + update: function(){ + if ( this.options.task.completed ) { + this.element.addClass( 'strike' ); + } else { + this.element.removeClass( 'strike' ); + } + } + }); + + var taskstriker = new TaskStriker({ + task: new Task({ completed: 'true' }) + }); + +To update the `taskstriker`'s task, add a task method that updates +this.options and rebinds the event handlers for the new task like: + + TaskStriker = can.Control({ + "{task} completed": function(){ + this.update(); + }, + update: function() { + if ( this.options.task.completed ) { + this.element.addClass( 'strike' ); + } else { + this.element.removeClass( 'strike' ); + } + }, + task: function( newTask ) { + this.options.task = newTask; + this.on(); + this.update(); + } + }); + + var taskstriker = new TaskStriker({ + task: new Task({ completed: true }) + }); + + // Now, add a new task that is not yet completed + taskstriker.task(new Task({ completed: false })); + +## Adding new events + +If events need to be bound to outside of the control and templated event handlers +are not sufficient, you can call this.on to bind or delegate programmatically: + + init: function() { + // calls somethingClicked( el, ev ) + this.on( 'click', 'somethingClicked' ); + + // calls function when the window is clicked + this.on( window, 'click', function( ev ) { + // do something + }); + }, + somethingClicked: function( el, ev ) { + // ... + } \ No newline at end of file diff --git a/control/doc/options.md b/control/doc/options.md new file mode 100644 index 00000000000..794b6de5dea --- /dev/null +++ b/control/doc/options.md @@ -0,0 +1,41 @@ +@property {Object} can.Control.prototype.options options +@parent can.Control.prototype +@description Options used to configure a control. + +@body + +The `this.options` property is an Object that contains +configuration data passed to a control when it is +created (`new can.Control(element, options)`). + +In the following example, an options object with +a message is passed to a `Greeting` control. The +`Greeting` control changes the text of its [can.Control::element element] +to the options' message value. + + var Greeting = can.Control.extend({ + init: function(){ + this.element.text( this.options.message ) + } + }); + + new Greeting("#greeting",{message: "I understand this.options"}); + +The options argument passed when creating the control +is merged with [can.Control.defaults defaults] in +[can.Control.prototype.setup setup]. + +In the following example, if no message property is provided, +the defaults' message property is used. + + var Greeting = can.Control.extend({ + defaults: { + message: "Defaults merged into this.options" + } + },{ + init: function(){ + this.element.text( this.options.message ) + } + }); + + new Greeting("#greeting"); \ No newline at end of file diff --git a/control/doc/processors.md b/control/doc/processors.md new file mode 100644 index 00000000000..975c81fc4d0 --- /dev/null +++ b/control/doc/processors.md @@ -0,0 +1,91 @@ +@property {Object.} can.Control.processors processors +@parent can.Control.static +@description A collection of hookups for custom events on Controls. +@body + +`processors` is an object that allows you to add new events to bind +to on a control, or to change how existent events are bound. Each +key-value pair of `processors` is a specification that pertains to +an event where the key is the name of the event, and the value is +a function that processes calls to bind to the event. + +The processor function takes five arguments: + +- _el_: The Control's element. +- _event_: The event type. +- _selector_: The selector preceding the event in the binding used on the Control. +- _callback_: The callback function being bound. +- _control_: The Control the event is bound on. + +Inside your processor function, you should bind _callback_ to the event, and +return a function for can.Control to call when _callback_ needs to be unbound. +(If _selector_ is defined, you will likely want to use some form of delegation +to bind the event.) + +Here is a Control with a custom event processor set and two callbacks bound +to that event: + + can.Control.processors.birthday = function(el, ev, selector, callback, control) { + if(selector) { + myFramework.delegate(ev, el, selector, callback); + return function() { myFramework.undelegate(ev, el, selector, callback); }; + } else { + myFramework.bind(ev, el, callback); + return function() { myFramework.unbind(ev, el, callback); }; + } + }; + + can.Control("EventTarget", { }, { + 'birthday': function(el, ev) { + // do something appropriate for the occasion + }, + '.grandchild birthday': function(el, ev) { + // do something appropriate for the occasion + } + }); + + var target = new EventTarget('#person'); + +When `target` is initialized, can.Control will call `can.Control.processors.birthday` +twice (because there are two event hookups for the _birthday_ event). The first +time it's called, the arguments will be: + +- _el_: A NodeList that wraps the element with id 'person'. +- _ev_: `'birthday'` +- _selector_: `''` +- _callback_: The function assigned to `' birthday'` in the prototype section of `EventTarget`'s +definition. +- _control_: `target` itself. + +The second time, the arguments are slightly different: + +- _el_: A NodeList that wraps the element with id 'person'. +- _ev_: `'birthday'` +- _selector_: `'.grandchild'` +- _callback_: The function assigned to `'.grandchild birthday'` in the prototype section of `EventTarget`'s +definition. +- _control_: `target` itself. + +can.Control already has processors for these events: + +- change +- click +- contextmenu +- dblclick +- focusin +- focusout +- keydown +- keyup +- keypress +- mousedown +- mouseenter +- mouseleave +- mousemove +- mouseout +- mouseover +- mouseup +- reset +- resize +- scroll +- select +- submit diff --git a/control/doc/setup.md b/control/doc/setup.md new file mode 100644 index 00000000000..e413d6ce671 --- /dev/null +++ b/control/doc/setup.md @@ -0,0 +1,45 @@ +@function can.Control.prototype.setup setup +@parent can.Control.prototype +@description Perform pre-initialization logic for control instances and classes. + +@signature `control.setup(element, options)` +@param {HTMLElement|NodeList|String} element The element as passed to the constructor. +@param {Object} [options] option values for the control. These get added to +this.options and merged with [can.Control.static.defaults defaults]. +@return {undefined|Array} return an array if you want to change what init is called with. By +default it is called with the element and options passed to the control. + +@body + +## Lifecycle of `setup` + +Setup, when called, does the following: + +### Sets this.element + +The first parameter passed to new Control( el, options ) is expected to be +an element. This gets converted to a Wrapped NodeList element and set as +[can.Control.prototype.element this.element]. + +### Adds the control's name to the element's className + +Control adds it's plugin name to the element's className for easier +debugging. For example, if your Control is named "Foo.Bar", it adds +"foo_bar" to the className. + +### Saves the control in $.data + +A reference to the control instance is saved in $.data. You can find +instances of "Foo.Bar" like: + + $( '#el' ).data( 'controls' )[ 'foo_bar' ] + +### Merges Options + +Merges the default options with optional user-supplied ones. +Additionally, default values are exposed in the static [can.Control.static.defaults defaults] +so that users can change them. + +### Binds event handlers + +Setup does the event binding described in [can.Control]. \ No newline at end of file diff --git a/list/doc/Map.md b/list/doc/Map.md new file mode 100644 index 00000000000..173e9c9f5cc --- /dev/null +++ b/list/doc/Map.md @@ -0,0 +1,32 @@ +@page can.List.Map Map +@parent can.List.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" \ No newline at end of file diff --git a/list/doc/extend.md b/list/doc/extend.md new file mode 100644 index 00000000000..e19cce2e8f2 --- /dev/null +++ b/list/doc/extend.md @@ -0,0 +1,14 @@ +@page can.List.extend extend +@parent can.List.static + +@function 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. \ No newline at end of file diff --git a/list/list.md b/list/doc/list.md similarity index 96% rename from list/list.md rename to list/doc/list.md index 2a0cddf7c43..53161423ef9 100644 --- a/list/list.md +++ b/list/doc/list.md @@ -4,8 +4,12 @@ @test can/list/test.html @parent canjs @release 2.0 -@group can.List.plugins plugins -@link ../docco/list.html docco + +@group can.List.prototype 0 Prototype +@group can.List.static 1 Static +@group can.List.plugins 2 plugins + +@link ../docco/list/list.html docco Use for observable array-like objects. diff --git a/list/doc/prototype.attr.md b/list/doc/prototype.attr.md new file mode 100644 index 00000000000..cd348571eed --- /dev/null +++ b/list/doc/prototype.attr.md @@ -0,0 +1,243 @@ +@page can.List.prototype.attr attr +@parent can.List.prototype + +@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 \ No newline at end of file diff --git a/list/doc/prototype.each.md b/list/doc/prototype.each.md new file mode 100644 index 00000000000..b7b7d0d7c9e --- /dev/null +++ b/list/doc/prototype.each.md @@ -0,0 +1,36 @@ +@page can.List.prototype.each each +@parent can.List.prototype + +@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 \ No newline at end of file diff --git a/list/doc/prototype.reverse.md b/list/doc/prototype.reverse.md new file mode 100644 index 00000000000..80ab2c8074c --- /dev/null +++ b/list/doc/prototype.reverse.md @@ -0,0 +1,25 @@ +@page can.List.prototype.reverse reverse +@parent can.List.prototype + +@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` calls `replace` internally and triggers corresponding `add`, `remove`, `change` and `length` events respectively. + +## Demo + +@iframe can/list/doc/reverse.html 350 \ No newline at end of file diff --git a/list/doc/prototype.splice.md b/list/doc/prototype.splice.md new file mode 100644 index 00000000000..f9885d6d04b --- /dev/null +++ b/list/doc/prototype.splice.md @@ -0,0 +1,65 @@ +@page can.List.prototype.splice splice +@parent can.List.prototype + +@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]. \ No newline at end of file diff --git a/list/doc/reverse.html b/list/doc/reverse.html new file mode 100644 index 00000000000..2aba99f770f --- /dev/null +++ b/list/doc/reverse.html @@ -0,0 +1,55 @@ + + + + + + + + + \ No newline at end of file diff --git a/map/attributes/attributes.js b/map/attributes/attributes.js index 8a67deeb032..5409563893b 100644 --- a/map/attributes/attributes.js +++ b/map/attributes/attributes.js @@ -11,152 +11,9 @@ steal('can/util', 'can/map', 'can/list', function (can, Map) { return typeof obj === 'object' && obj !== null && obj; }; can.extend(clss, { - /** - * @property can.Map.attributes.static.attributes attributes - * @parent can.Map.attributes.static - * - * `can.Map.attributes` is a property that contains key/value pair(s) of an attribute's name and its - * respective type for using in [can.Map.attributes.static.convert convert] and [can.Map.prototype.serialize serialize]. - * - * var Contact = can.Map.extend({ - * attributes : { - * birthday : 'date', - * age: 'number', - * name: 'string' - * } - * }); - * - */ + attributes: {}, - /** - * @property can.Map.attributes.static.convert convert - * @parent can.Map.attributes.static - * - * You often want to convert from what the observe sends you to a form more useful to JavaScript. - * For example, contacts might be returned from the server with dates that look like: "1982-10-20". - * We can observe to convert it to something closer to `new Date(1982,10,20)`. - * - * Convert comes with the following types: - * - * - __date__ Converts to a JS date. Accepts integers or strings that work with Date.parse - * - __number__ An integer or number that can be passed to parseFloat - * - __boolean__ Converts "false" to false, and puts everything else through Boolean() - * - * The following sets the birthday attribute to "date" and provides a date conversion function: - * - * var Contact = can.Map.extend({ - * attributes : { - * birthday : 'date' - * }, - * convert : { - * date : function(raw){ - * if(typeof raw == 'string'){ - * //- Extracts dates formated 'YYYY-DD-MM' - * var matches = raw.match(/(\d+)-(\d+)-(\d+)/); - * - * //- Parses to date object and returns - * return new Date(matches[1], - * (+matches[2])-1, - * matches[3]); - * - * }else if(raw instanceof Date){ - * return raw; - * } - * } - * } - * },{}); - * - * var contact = new Contact(); - * - * //- calls convert on attribute set - * contact.attr('birthday', '4-26-2012') - * - * contact.attr('birthday'); //-> Date - * - * If a property is set with an object as a value, the corresponding converter is called with the unmerged data (the raw object) - * as the first argument, and the old value (a can.Map) as the second: - * - * var MyObserve = can.Map.extend({ - * attributes: { - * nested: "nested" - * }, - * convert: { - * nested: function(data, oldVal) { - * if(oldVal instanceof MyObserve) { - * return oldVal.attr(data); - * } - * return new MyObserve(data); - * } - * } - * },{}); - * - * ## Differences From `attr` - * - * The way that return values from convertors affect the value of an Observe's property is - * different from [can.Map::attr attr]'s normal behavior. Specifically, when the - * property's current value is an Observe or List, and an Observe or List is returned - * from a convertor, the effect will not be to merge the values into the current value as - * if the return value was fed straight into `attr`, but to replace the value with the - * new Observe or List completely. Because of this, any bindings you have on the previous - * observable object will break. - * - * If you would rather have the new Observe or List merged into the current value, call - * `attr` directly on the property instead of on the Observe: - * - * @codestart - * var Contact = can.Map.extend({ - * attributes: { - * info: 'info' - * }, - * convert: { - * 'info': function(data, oldVal) { - * return data; - * } - * } - * }, {}); - * - * var alice = new Contact({info: {name: 'Alice Liddell', email: 'alice@liddell.com'}}); - * alice.attr(); // {name: 'Alice Liddell', 'email': 'alice@liddell.com'} - * alice.info._cid; // '.observe1' - * - * alice.attr('info', {name: 'Allison Wonderland', phone: '888-888-8888'}); - * alice.attr(); // {name: 'Allison Wonderland', 'phone': '888-888-8888'} - * alice.info._cid; // '.observe2' - * - * alice.info.attr({email: 'alice@wonderland.com', phone: '000-000-0000'}); - * alice.attr(); // {name: 'Allison Wonderland', email: 'alice@wonderland.com', 'phone': '000-000-0000'} - * alice.info._cid; // '.observe2' - * @codeend - * - * ## Assocations and Convert - * - * If you have assocations defined within your model(s), you can use convert to automatically - * call serialize on those models. - * - * @codestart - * var Contact = can.Model.extend({ - * attributes : { - * tasks: Task - * } - * }, {}); - * - * var Task = can.Model.extend({ - * attributes : { - * due : 'date' - * } - * },{}); - * - * var contact = new Contact({ - * tasks: [ new Task({ - * due: new Date() - * }) ] - * }); - * - * contact.serialize(); - * //-> { tasks: [ { due: 1333219754627 } ] } - * @codeend - */ convert: { 'date': function (str) { var type = typeof str; @@ -205,41 +62,6 @@ steal('can/util', 'can/map', 'can/list', function (can, Map) { return typeof construct === 'function' ? construct.call(context, val, oldVal) : val; } }, - /** - * @property can.Map.attributes.static.serialize serialize - * @parent can.Map.attributes.static - * - * `can.Map.serialize` is an object of name-function pairs that are used to - * serialize attributes. - * - * Similar to [can.Map.attributes.static.convert can.Map.attributes.convert], in that the keys of this object correspond to - * the types specified in [can.Map.attributes]. - * - * By default every attribute will be passed through the 'default' serialization method - * that will return the value if the property holds a primitive value (string, number, ...), - * or it will call the "serialize" method if the property holds an object with the "serialize" method set. - * - * For example, to serialize all dates to ISO format: - * - * @codestart - * var Contact = can.Map.extend({ - * attributes : { - * birthday : 'date' - * }, - * serialize : { - * date : function(val, type){ - * return new Date(val).toISOString(); - * } - * } - * },{}); - * - * var contact = new Contact({ - * birthday: new Date("Oct 25, 1973") - * }).serialize(); - * //-> { "birthday" : "1973-10-25T05:00:00.000Z" } - * @codeend - * - */ serialize: { 'default': function (val, type) { return isObject(val) && val.serialize ? val.serialize() : val; @@ -295,44 +117,6 @@ steal('can/util', 'can/map', 'can/list', function (can, Map) { } return value === null || !type ? value : converter.call(Class, value, oldVal, function () {}, type); }; - /** - * @function can.Map.prototype.attributes.serialize serialize - * @parent can.Map.attributes.prototype - * - * @description Serializes the observe's properties using - * the [can.Map.attributes attribute plugin]. - * - * @signature `observe.serialize([attrName])` - * @param {String} [attrName] If passed, returns only a serialization of the named attribute. - * @return {String} A serialization of this Observe. - * - * @body - * You can set the serialization methods similar to the convert methods: - * - * var Contact = can.Map.extend({ - * attributes : { - * birthday : 'date' - * }, - * serialize : { - * date : function( val, type ){ - * return val.getYear() + - * "-" + (val.getMonth() + 1) + - * "-" + val.getDate(); - * } - * } - * },{}) - * - * var contact = new Contact(); - * contact.attr('birthday', new Date()); - * contact.serialize() - * //-> { birthday: 'YYYY-MM-DD' } - * - * You can also get and serialize an individual property by passing the attribute - * name to the `serialize` function. Building on the above demo, we can serialize - * the `birthday` attribute only. - * - * contact.serialize('birthday') //-> 'YYYY-MM-DD' - */ can.List.prototype.serialize = function (attrName, stack) { return can.makeArray(can.Map.prototype.serialize.apply(this, arguments)); }; diff --git a/map/attributes/attributes-assocations.html b/map/attributes/doc/attributes-assocations.html similarity index 96% rename from map/attributes/attributes-assocations.html rename to map/attributes/doc/attributes-assocations.html index 0c925b69137..b3d1323a90d 100644 --- a/map/attributes/attributes-assocations.html +++ b/map/attributes/doc/attributes-assocations.html @@ -14,11 +14,11 @@
- + + + + + + diff --git a/view/bindings/input-checkbox.html b/view/bindings/doc/input-checkbox.html similarity index 93% rename from view/bindings/input-checkbox.html rename to view/bindings/doc/input-checkbox.html index 40fb2e41e0e..ae19b2dd28d 100644 --- a/view/bindings/input-checkbox.html +++ b/view/bindings/doc/input-checkbox.html @@ -1,5 +1,5 @@
- + - + + - + - + - + - + + - +