diff -r 000000000000 -r 80c32ef237c6 Resources/js/can.jquery-1.1.6.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/js/can.jquery-1.1.6.js Mon Sep 02 02:22:21 2013 -0700 @@ -0,0 +1,4538 @@ +/*! + * CanJS - 1.1.6 + * http://canjs.us/ + * Copyright (c) 2013 Bitovi + * Mon, 15 Jul 2013 04:14:43 GMT + * Licensed MIT + * Includes: can/construct/construct.js,can/observe/observe.js,can/observe/compute/compute.js,can/model/model.js,can/view/view.js,can/view/ejs/ejs.js,can/control/control.js,can/route/route.js,can/control/route/route.js,can/observe/backup/backup.js,can/util/object/object.js,can/util/string/string.js + * Download from: http://bitbuilder.herokuapp.com/can.custom.js?configuration=jquery&plugins=can%2Fconstruct%2Fconstruct.js&plugins=can%2Fobserve%2Fobserve.js&plugins=can%2Fobserve%2Fcompute%2Fcompute.js&plugins=can%2Fmodel%2Fmodel.js&plugins=can%2Fview%2Fview.js&plugins=can%2Fview%2Fejs%2Fejs.js&plugins=can%2Fcontrol%2Fcontrol.js&plugins=can%2Froute%2Froute.js&plugins=can%2Fcontrol%2Froute%2Froute.js&plugins=can%2Fobserve%2Fbackup%2Fbackup.js&plugins=can%2Futil%2Fobject%2Fobject.js&plugins=can%2Futil%2Fstring%2Fstring.js + */ +(function(undefined) { + + // ## can/util/can.js + var __m5 = (function() { + var can = window.can || {}; + if (typeof GLOBALCAN === 'undefined' || GLOBALCAN !== false) { + window.can = can; + } + + can.isDeferred = function(obj) { + var isFunction = this.isFunction; + // Returns `true` if something looks like a deferred. + return obj && isFunction(obj.then) && isFunction(obj.pipe); + }; + + var cid = 0; + can.cid = function(object, name) { + if (object._cid) { + return object._cid + } else { + return object._cid = (name || "") + (++cid) + } + } + can.VERSION = '@EDGE'; + return can; + })(); + + // ## can/util/array/each.js + var __m6 = (function(can) { + can.each = function(elements, callback, context) { + var i = 0, + key; + if (elements) { + if (typeof elements.length === 'number' && elements.pop) { + if (elements.attr) { + elements.attr('length'); + } + for (key = elements.length; i < key; i++) { + if (callback.call(context || elements[i], elements[i], i, elements) === false) { + break; + } + } + } else if (elements.hasOwnProperty) { + for (key in elements) { + if (elements.hasOwnProperty(key)) { + if (callback.call(context || elements[key], elements[key], key, elements) === false) { + break; + } + } + } + } + } + return elements; + }; + + return can; + })(__m5); + + // ## can/util/jquery/jquery.js + var __m3 = (function($, can) { + // _jQuery node list._ + $.extend(can, $, { + trigger: function(obj, event, args) { + if (obj.trigger) { + obj.trigger(event, args); + } else { + $.event.trigger(event, args, obj, true); + } + }, + addEvent: function(ev, cb) { + $([this]).bind(ev, cb); + return this; + }, + removeEvent: function(ev, cb) { + $([this]).unbind(ev, cb); + return this; + }, + // jquery caches fragments, we always needs a new one + buildFragment: function(elems, context) { + var oldFragment = $.buildFragment, + ret; + + elems = [elems]; + // Set context per 1.8 logic + context = context || document; + context = !context.nodeType && context[0] || context; + context = context.ownerDocument || context; + + ret = oldFragment.call(jQuery, elems, context); + + return ret.cacheable ? $.clone(ret.fragment) : ret.fragment || ret; + }, + $: $, + each: can.each + }); + + // Wrap binding functions. + $.each(['bind', 'unbind', 'undelegate', 'delegate'], function(i, func) { + can[func] = function() { + var t = this[func] ? this : $([this]); + t[func].apply(t, arguments); + return this; + }; + }); + + // Wrap modifier functions. + $.each(["append", "filter", "addClass", "remove", "data", "get"], function(i, name) { + can[name] = function(wrapped) { + return wrapped[name].apply(wrapped, can.makeArray(arguments).slice(1)); + }; + }); + + // Memory safe destruction. + var oldClean = $.cleanData; + + $.cleanData = function(elems) { + $.each(elems, function(i, elem) { + if (elem) { + can.trigger(elem, "destroyed", [], false); + } + }); + oldClean(elems); + }; + + return can; + })(jQuery, __m5, __m6); + + // ## can/util/string/string.js + var __m2 = (function(can) { + // ##string.js + // _Miscellaneous string utility functions._ + + // Several of the methods in this plugin use code adapated from Prototype + // Prototype JavaScript framework, version 1.6.0.1. + // © 2005-2007 Sam Stephenson + var strUndHash = /_|-/, + strColons = /\=\=/, + strWords = /([A-Z]+)([A-Z][a-z])/g, + strLowUp = /([a-z\d])([A-Z])/g, + strDash = /([a-z\d])([A-Z])/g, + strReplacer = /\{([^\}]+)\}/g, + strQuote = /"/g, + strSingleQuote = /'/g, + + // Returns the `prop` property from `obj`. + // If `add` is true and `prop` doesn't exist in `obj`, create it as an + // empty object. + getNext = function(obj, prop, add) { + var result = obj[prop]; + + if (result === undefined && add === true) { + result = obj[prop] = {} + } + return result + }, + + // Returns `true` if the object can have properties (no `null`s). + isContainer = function(current) { + return (/^f|^o/).test(typeof current); + }; + + can.extend(can, { + // Escapes strings for HTML. + + esc: function(content) { + // Convert bad values into empty strings + var isInvalid = content === null || content === undefined || (isNaN(content) && ("" + content === 'NaN')); + return ("" + (isInvalid ? '' : content)) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(strQuote, '"') + .replace(strSingleQuote, "'"); + }, + + + getObject: function(name, roots, add) { + + // The parts of the name we are looking up + // `['App','Models','Recipe']` + var parts = name ? name.split('.') : [], + length = parts.length, + current, + r = 0, + i, container, rootsLength; + + // Make sure roots is an `array`. + roots = can.isArray(roots) ? roots : [roots || window]; + + rootsLength = roots.length + + if (!length) { + return roots[0]; + } + + // For each root, mark it as current. + for (r; r < rootsLength; r++) { + current = roots[r]; + container = undefined; + + // Walk current to the 2nd to last object or until there + // is not a container. + for (i = 0; i < length && isContainer(current); i++) { + container = current; + current = getNext(container, parts[i]); + } + + // If we found property break cycle + if (container !== undefined && current !== undefined) { + break + } + } + + // Remove property from found container + if (add === false && current !== undefined) { + delete container[parts[i - 1]] + } + + // When adding property add it to the first root + if (add === true && current === undefined) { + current = roots[0] + + for (i = 0; i < length && isContainer(current); i++) { + current = getNext(current, parts[i], true); + } + } + + return current; + }, + // Capitalizes a string. + + capitalize: function(s, cache) { + // Used to make newId. + return s.charAt(0).toUpperCase() + s.slice(1); + }, + + // Underscores a string. + + underscore: function(s) { + return s + .replace(strColons, '/') + .replace(strWords, '$1_$2') + .replace(strLowUp, '$1_$2') + .replace(strDash, '_') + .toLowerCase(); + }, + // Micro-templating. + + sub: function(str, data, remove) { + var obs = []; + + str = str || ''; + + obs.push(str.replace(strReplacer, function(whole, inside) { + + // Convert inside to type. + var ob = can.getObject(inside, data, remove === true ? false : undefined); + + if (ob === undefined) { + obs = null; + return ""; + } + + // If a container, push into objs (which will return objects found). + if (isContainer(ob) && obs) { + obs.push(ob); + return ""; + } + + return "" + ob; + })); + + return obs === null ? obs : (obs.length <= 1 ? obs[0] : obs); + }, + + // These regex's are used throughout the rest of can, so let's make + // them available. + replacer: strReplacer, + undHash: strUndHash + }); + return can; + })(__m3); + + // ## can/construct/construct.js + var __m1 = (function(can) { + + // ## construct.js + // `can.Construct` + // _This is a modified version of + // [John Resig's class](http://ejohn.org/blog/simple-javascript-inheritance/). + // It provides class level inheritance and callbacks._ + + // A private flag used to initialize a new class instance without + // initializing it's bindings. + var initializing = 0; + + + can.Construct = function() { + if (arguments.length) { + return can.Construct.extend.apply(can.Construct, arguments); + } + }; + + + can.extend(can.Construct, { + + newInstance: function() { + // Get a raw instance object (`init` is not called). + var inst = this.instance(), + arg = arguments, + args; + + // Call `setup` if there is a `setup` + if (inst.setup) { + args = inst.setup.apply(inst, arguments); + } + + // Call `init` if there is an `init` + // If `setup` returned `args`, use those as the arguments + if (inst.init) { + inst.init.apply(inst, args || arguments); + } + + return inst; + }, + // Overwrites an object with methods. Used in the `super` plugin. + // `newProps` - New properties to add. + // `oldProps` - Where the old properties might be (used with `super`). + // `addTo` - What we are adding to. + _inherit: function(newProps, oldProps, addTo) { + can.extend(addTo || newProps, newProps || {}) + }, + // used for overwriting a single property. + // this should be used for patching other objects + // the super plugin overwrites this + _overwrite: function(what, oldProps, propName, val) { + what[propName] = val; + }, + // Set `defaults` as the merger of the parent `defaults` and this + // object's `defaults`. If you overwrite this method, make sure to + // include option merging logic. + + setup: function(base, fullName) { + this.defaults = can.extend(true, {}, base.defaults, this.defaults); + }, + // Create's a new `class` instance without initializing by setting the + // `initializing` flag. + instance: function() { + + // Prevents running `init`. + initializing = 1; + + var inst = new this(); + + // Allow running `init`. + initializing = 0; + + return inst; + }, + // Extends classes. + + extend: function(fullName, klass, proto) { + // Figure out what was passed and normalize it. + if (typeof fullName != 'string') { + proto = klass; + klass = fullName; + fullName = null; + } + + if (!proto) { + proto = klass; + klass = null; + } + proto = proto || {}; + + var _super_class = this, + _super = this.prototype, + name, shortName, namespace, prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor). + prototype = this.instance(); + + // Copy the properties over onto the new prototype. + can.Construct._inherit(proto, _super, prototype); + + // The dummy class constructor. + + function Constructor() { + // All construction is actually done in the init method. + if (!initializing) { + return this.constructor !== Constructor && arguments.length ? + // We are being called without `new` or we are extending. + arguments.callee.extend.apply(arguments.callee, arguments) : + // We are being called with `new`. + this.constructor.newInstance.apply(this.constructor, arguments); + } + } + + // Copy old stuff onto class (can probably be merged w/ inherit) + for (name in _super_class) { + if (_super_class.hasOwnProperty(name)) { + Constructor[name] = _super_class[name]; + } + } + + // Copy new static properties on class. + can.Construct._inherit(klass, _super_class, Constructor); + + // Setup namespaces. + if (fullName) { + + var parts = fullName.split('.'), + shortName = parts.pop(), + current = can.getObject(parts.join('.'), window, true), + namespace = current, + _fullName = can.underscore(fullName.replace(/\./g, "_")), + _shortName = can.underscore(shortName); + + + + current[shortName] = Constructor; + } + + // Set things that shouldn't be overwritten. + can.extend(Constructor, { + constructor: Constructor, + prototype: prototype, + + namespace: namespace, + + _shortName: _shortName, + + fullName: fullName, + _fullName: _fullName + }); + + // Dojo and YUI extend undefined + if (shortName !== undefined) { + Constructor.shortName = shortName; + } + + // Make sure our prototype looks nice. + Constructor.prototype.constructor = Constructor; + + + // Call the class `setup` and `init` + var t = [_super_class].concat(can.makeArray(arguments)), + args = Constructor.setup.apply(Constructor, t); + + if (Constructor.init) { + Constructor.init.apply(Constructor, args || t); + } + + + return Constructor; + + } + + }); + return can.Construct; + })(__m2); + + // ## can/util/bind/bind.js + var __m8 = (function(can) { + + + // ## Bind helpers + can.bindAndSetup = function() { + // Add the event to this object + can.addEvent.apply(this, arguments); + // If not initializing, and the first binding + // call bindsetup if the function exists. + if (!this._init) { + if (!this._bindings) { + this._bindings = 1; + // setup live-binding + this._bindsetup && this._bindsetup(); + + } else { + this._bindings++; + } + + } + + return this; + }; + + can.unbindAndTeardown = function(ev, handler) { + // Remove the event handler + can.removeEvent.apply(this, arguments); + + this._bindings--; + // If there are no longer any bindings and + // there is a bindteardown method, call it. + if (!this._bindings) { + this._bindteardown && this._bindteardown(); + } + return this; + } + + return can; + + })(__m3); + + // ## can/observe/observe.js + var __m7 = (function(can, bind) { + // ## observe.js + // `can.Observe` + // _Provides the observable pattern for JavaScript Objects._ + // Returns `true` if something is an object with properties of its own. + var canMakeObserve = function(obj) { + return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject(obj) || (obj instanceof can.Observe)); + }, + + // Removes all listeners. + unhookup = function(items, namespace) { + return can.each(items, function(item) { + if (item && item.unbind) { + item.unbind("change" + namespace); + } + }); + }, + // Listens to changes on `child` and "bubbles" the event up. + // `child` - The object to listen for changes on. + // `prop` - The property name is at on. + // `parent` - The parent object of prop. + // `ob` - (optional) The Observe object constructor + // `list` - (optional) The observable list constructor + hookupBubble = function(child, prop, parent, Ob, List) { + Ob = Ob || Observe; + List = List || Observe.List; + + // If it's an `array` make a list, otherwise a child. + if (child instanceof Observe) { + // We have an `observe` already... + // Make sure it is not listening to this already + // It's only listening if it has bindings already. + parent._bindings && unhookup([child], parent._cid); + } else if (can.isArray(child)) { + child = new List(child); + } else { + child = new Ob(child); + } + // only listen if something is listening to you + if (parent._bindings) { + // Listen to all changes and `batchTrigger` upwards. + bindToChildAndBubbleToParent(child, prop, parent) + } + + + return child; + }, + bindToChildAndBubbleToParent = function(child, prop, parent) { + child.bind("change" + parent._cid, function() { + // `batchTrigger` the type on this... + var args = can.makeArray(arguments), + ev = args.shift(); + args[0] = (prop === "*" ? [parent.indexOf(child), args[0]] : [prop, args[0]]).join("."); + + // track objects dispatched on this observe + ev.triggeredNS = ev.triggeredNS || {}; + + // if it has already been dispatched exit + if (ev.triggeredNS[parent._cid]) { + return; + } + + ev.triggeredNS[parent._cid] = true; + // send change event with modified attr to parent + can.trigger(parent, ev, args); + // send modified attr event to parent + //can.trigger(parent, args[0], args); + }); + } + // An `id` to track events for a given observe. + observeId = 0, + // A helper used to serialize an `Observe` or `Observe.List`. + // `observe` - The observable. + // `how` - To serialize with `attr` or `serialize`. + // `where` - To put properties, in an `{}` or `[]`. + serialize = function(observe, how, where) { + // Go through each property. + observe.each(function(val, name) { + // If the value is an `object`, and has an `attrs` or `serialize` function. + where[name] = canMakeObserve(val) && can.isFunction(val[how]) ? + // Call `attrs` or `serialize` to get the original data back. + val[how]() : + // Otherwise return the value. + val; + }); + return where; + }, + attrParts = function(attr, keepKey) { + if (keepKey) { + return [attr]; + } + return can.isArray(attr) ? attr : ("" + attr).split("."); + }, + // Which batch of events this is for -- might not want to send multiple + // messages on the same batch. This is mostly for event delegation. + batchNum = 1, + // how many times has start been called without a stop + transactions = 0, + // an array of events within a transaction + batchEvents = [], + stopCallbacks = [], + makeBindSetup = function(wildcard) { + return function() { + var parent = this; + this._each(function(child, prop) { + if (child && child.bind) { + bindToChildAndBubbleToParent(child, wildcard || prop, parent) + } + }) + }; + }; + + + var Observe = can.Map = can.Observe = can.Construct({ + + // keep so it can be overwritten + bind: can.bindAndSetup, + unbind: can.unbindAndTeardown, + id: "id", + canMakeObserve: canMakeObserve, + // starts collecting events + // takes a callback for after they are updated + // how could you hook into after ejs + + startBatch: function(batchStopHandler) { + transactions++; + batchStopHandler && stopCallbacks.push(batchStopHandler); + }, + + stopBatch: function(force, callStart) { + if (force) { + transactions = 0; + } else { + transactions--; + } + + if (transactions == 0) { + var items = batchEvents.slice(0), + callbacks = stopCallbacks.slice(0); + batchEvents = []; + stopCallbacks = []; + batchNum++; + callStart && this.startBatch(); + can.each(items, function(args) { + can.trigger.apply(can, args); + }); + can.each(callbacks, function(cb) { + cb(); + }); + } + }, + + triggerBatch: function(item, event, args) { + // Don't send events if initalizing. + if (!item._init) { + if (transactions == 0) { + return can.trigger(item, event, args); + } else { + event = typeof event === "string" ? { + type: event + } : + event; + event.batchNum = batchNum; + batchEvents.push([ + item, + event, + args + ]); + } + } + }, + + keys: function(observe) { + var keys = []; + Observe.__reading && Observe.__reading(observe, '__keys'); + for (var keyName in observe._data) { + keys.push(keyName); + } + return keys; + } + }, + + { + setup: function(obj) { + // `_data` is where we keep the properties. + this._data = {}; + + // The namespace this `object` uses to listen to events. + can.cid(this, ".observe"); + // Sets all `attrs`. + this._init = 1; + this.attr(obj); + this.bind('change' + this._cid, can.proxy(this._changes, this)); + delete this._init; + }, + _bindsetup: makeBindSetup(), + _bindteardown: function() { + var cid = this._cid; + this._each(function(child) { + unhookup([child], cid) + }) + }, + _changes: function(ev, attr, how, newVal, oldVal) { + Observe.triggerBatch(this, { + type: attr, + batchNum: ev.batchNum + }, [newVal, oldVal]); + }, + _triggerChange: function(attr, how, newVal, oldVal) { + Observe.triggerBatch(this, "change", can.makeArray(arguments)) + }, + // no live binding iterator + _each: function(callback) { + var data = this.__get(); + for (var prop in data) { + if (data.hasOwnProperty(prop)) { + callback(data[prop], prop) + } + } + }, + + attr: function(attr, val) { + // This is super obfuscated for space -- basically, we're checking + // if the type of the attribute is not a `number` or a `string`. + var type = typeof attr; + if (type !== "string" && type !== "number") { + return this._attrs(attr, val) + } else if (arguments.length === 1) { // If we are getting a value. + // Let people know we are reading. + Observe.__reading && Observe.__reading(this, attr) + return this._get(attr) + } else { + // Otherwise we are setting. + this._set(attr, val); + return this; + } + }, + + each: function() { + Observe.__reading && Observe.__reading(this, '__keys'); + return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments))) + }, + + removeAttr: function(attr) { + // Info if this is List or not + var isList = this instanceof can.Observe.List, + // Convert the `attr` into parts (if nested). + parts = attrParts(attr), + // The actual property to remove. + prop = parts.shift(), + // The current value. + current = isList ? this[prop] : this._data[prop]; + + // If we have more parts, call `removeAttr` on that part. + if (parts.length) { + return current.removeAttr(parts) + } else { + if (isList) { + this.splice(prop, 1) + } else if (prop in this._data) { + // Otherwise, `delete`. + delete this._data[prop]; + // Create the event. + if (!(prop in this.constructor.prototype)) { + delete this[prop] + } + // Let others know the number of keys have changed + Observe.triggerBatch(this, "__keys"); + this._triggerChange(prop, "remove", undefined, current); + + } + return current; + } + }, + // Reads a property from the `object`. + _get: function(attr) { + var value = typeof attr === 'string' && !! ~attr.indexOf('.') && this.__get(attr); + if (value) { + return value; + } + + // break up the attr (`"foo.bar"`) into `["foo","bar"]` + var parts = attrParts(attr), + // get the value of the first attr name (`"foo"`) + current = this.__get(parts.shift()); + // if there are other attributes to read + return parts.length ? + // and current has a value + current ? + // lookup the remaining attrs on current + current._get(parts) : + // or if there's no current, return undefined + undefined : + // if there are no more parts, return current + current; + }, + // Reads a property directly if an `attr` is provided, otherwise + // returns the "real" data object itself. + __get: function(attr) { + return attr ? this._data[attr] : this._data; + }, + // Sets `attr` prop as value on this object where. + // `attr` - Is a string of properties or an array of property values. + // `value` - The raw value to set. + _set: function(attr, value, keepKey) { + // Convert `attr` to attr parts (if it isn't already). + var parts = attrParts(attr, keepKey), + // The immediate prop we are setting. + prop = parts.shift(), + // The current value. + current = this.__get(prop); + + // If we have an `object` and remaining parts. + if (canMakeObserve(current) && parts.length) { + // That `object` should set it (this might need to call attr). + current._set(parts, value) + } else if (!parts.length) { + // We're in "real" set territory. + if (this.__convert) { + value = this.__convert(prop, value) + } + this.__set(prop, value, current) + } else { + throw "can.Observe: Object does not exist" + } + }, + __set: function(prop, value, current) { + + // Otherwise, we are setting it on this `object`. + // TODO: Check if value is object and transform + // are we changing the value. + if (value !== current) { + // Check if we are adding this for the first time -- + // if we are, we need to create an `add` event. + var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add"; + + // Set the value on data. + this.___set(prop, + + // If we are getting an object. + canMakeObserve(value) ? + + // Hook it up to send event. + hookupBubble(value, prop, this) : + // Value is normal. + value); + + if (changeType == "add") { + // If there is no current value, let others know that + // the the number of keys have changed + + Observe.triggerBatch(this, "__keys", undefined); + + } + // `batchTrigger` the change event. + this._triggerChange(prop, changeType, value, current); + + //Observe.triggerBatch(this, prop, [value, current]); + // If we can stop listening to our old value, do it. + current && unhookup([current], this._cid); + } + + }, + // Directly sets a property on this `object`. + ___set: function(prop, val) { + this._data[prop] = val; + // Add property directly for easy writing. + // Check if its on the `prototype` so we don't overwrite methods like `attrs`. + if (!(prop in this.constructor.prototype)) { + this[prop] = val + } + }, + + + bind: can.bindAndSetup, + + unbind: can.unbindAndTeardown, + + serialize: function() { + return serialize(this, 'serialize', {}); + }, + + _attrs: function(props, remove) { + + if (props === undefined) { + return serialize(this, 'attr', {}) + } + + props = can.extend({}, props); + var prop, + self = this, + newVal; + Observe.startBatch(); + this.each(function(curVal, prop) { + newVal = props[prop]; + + // If we are merging... + if (newVal === undefined) { + remove && self.removeAttr(prop); + return; + } + + if (self.__convert) { + newVal = self.__convert(prop, newVal) + } + + // if we're dealing with models, want to call _set to let converter run + if (newVal instanceof can.Observe) { + self.__set(prop, newVal, curVal) + // if its an object, let attr merge + } else if (canMakeObserve(curVal) && canMakeObserve(newVal) && curVal.attr) { + curVal.attr(newVal, remove) + // otherwise just set + } else if (curVal != newVal) { + self.__set(prop, newVal, curVal) + } + + delete props[prop]; + }) + // Add remaining props. + for (var prop in props) { + newVal = props[prop]; + this._set(prop, newVal, true) + } + Observe.stopBatch() + return this; + }, + + + compute: function(prop) { + return can.compute(this, prop); + } + }); + // Helpers for `observable` lists. + var splice = [].splice, + + list = Observe( + + { + setup: function(instances, options) { + this.length = 0; + can.cid(this, ".observe") + this._init = 1; + if (can.isDeferred(instances)) { + this.replace(instances) + } else { + this.push.apply(this, can.makeArray(instances || [])); + } + // this change needs to be ignored + this.bind('change' + this._cid, can.proxy(this._changes, this)); + can.extend(this, options); + delete this._init; + }, + _triggerChange: function(attr, how, newVal, oldVal) { + + Observe.prototype._triggerChange.apply(this, arguments) + // `batchTrigger` direct add and remove events... + if (!~attr.indexOf('.')) { + + if (how === 'add') { + Observe.triggerBatch(this, how, [newVal, +attr]); + Observe.triggerBatch(this, 'length', [this.length]); + } else if (how === 'remove') { + Observe.triggerBatch(this, how, [oldVal, +attr]); + Observe.triggerBatch(this, 'length', [this.length]); + } else { + Observe.triggerBatch(this, how, [newVal, +attr]) + } + + } + + }, + __get: function(attr) { + return attr ? this[attr] : this; + }, + ___set: function(attr, val) { + this[attr] = val; + if (+attr >= this.length) { + this.length = (+attr + 1) + } + }, + _each: function(callback) { + var data = this.__get(); + for (var i = 0; i < data.length; i++) { + callback(data[i], i) + } + }, + _bindsetup: makeBindSetup("*"), + // Returns the serialized form of this list. + + serialize: function() { + return serialize(this, 'serialize', []); + }, + + splice: function(index, howMany) { + var args = can.makeArray(arguments), + i; + + for (i = 2; i < args.length; i++) { + var val = args[i]; + if (canMakeObserve(val)) { + args[i] = hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) + } + } + if (howMany === undefined) { + howMany = args[1] = this.length - index; + } + var removed = splice.apply(this, args); + can.Observe.startBatch(); + if (howMany > 0) { + this._triggerChange("" + index, "remove", undefined, removed); + unhookup(removed, this._cid); + } + if (args.length > 2) { + this._triggerChange("" + index, "add", args.slice(2), removed); + } + can.Observe.stopBatch(); + return removed; + }, + + _attrs: function(items, remove) { + if (items === undefined) { + return serialize(this, 'attr', []); + } + + // Create a copy. + items = can.makeArray(items); + + Observe.startBatch(); + this._updateAttrs(items, remove); + Observe.stopBatch() + }, + + _updateAttrs: function(items, remove) { + var len = Math.min(items.length, this.length); + + for (var prop = 0; prop < len; prop++) { + var curVal = this[prop], + newVal = items[prop]; + + if (canMakeObserve(curVal) && canMakeObserve(newVal)) { + curVal.attr(newVal, remove) + } else if (curVal != newVal) { + this._set(prop, newVal) + } else { + + } + } + if (items.length > this.length) { + // Add in the remaining props. + this.push.apply(this, items.slice(this.length)); + } else if (items.length < this.length && remove) { + this.splice(items.length) + } + } + }), + + // Converts to an `array` of arguments. + getArgs = function(args) { + return args[0] && can.isArray(args[0]) ? + args[0] : + can.makeArray(args); + }; + // Create `push`, `pop`, `shift`, and `unshift` + can.each({ + + push: "length", + + unshift: 0 + }, + // Adds a method + // `name` - The method name. + // `where` - Where items in the `array` should be added. + + function(where, name) { + var orig = [][name] + list.prototype[name] = function() { + // Get the items being added. + var args = [], + // Where we are going to add items. + len = where ? this.length : 0, + i = arguments.length, + res, + val, + constructor = this.constructor; + + // Go through and convert anything to an `observe` that needs to be converted. + while (i--) { + val = arguments[i]; + args[i] = canMakeObserve(val) ? + hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) : + val; + } + + // Call the original method. + res = orig.apply(this, args); + + if (!this.comparator || args.length) { + + this._triggerChange("" + len, "add", args, undefined); + } + + return res; + } + }); + + can.each({ + + pop: "length", + + shift: 0 + }, + // Creates a `remove` type method + + function(where, name) { + list.prototype[name] = function() { + + var args = getArgs(arguments), + len = where && this.length ? this.length - 1 : 0; + + var res = [][name].apply(this, args) + + // Create a change where the args are + // `len` - Where these items were removed. + // `remove` - Items removed. + // `undefined` - The new values (there are none). + // `res` - The old, removed values (should these be unbound). + this._triggerChange("" + len, "remove", undefined, [res]) + + if (res && res.unbind) { + res.unbind("change" + this._cid) + } + return res; + } + }); + + can.extend(list.prototype, { + + indexOf: function(item) { + this.attr('length') + return can.inArray(item, this) + }, + + + join: [].join, + + + reverse: [].reverse, + + + slice: function() { + var temp = Array.prototype.slice.apply(this, arguments); + return new this.constructor(temp); + }, + + + concat: function() { + var args = []; + can.each(can.makeArray(arguments), function(arg, i) { + args[i] = arg instanceof can.Observe.List ? arg.serialize() : arg; + }); + return new this.constructor(Array.prototype.concat.apply(this.serialize(), args)); + }, + + + forEach: function(cb, thisarg) { + can.each(this, cb, thisarg || this); + }, + + + replace: function(newList) { + if (can.isDeferred(newList)) { + newList.then(can.proxy(this.replace, this)); + } else { + this.splice.apply(this, [0, this.length].concat(can.makeArray(newList || []))); + } + + return this; + } + }); + + can.List = Observe.List = list; + Observe.setup = function() { + can.Construct.setup.apply(this, arguments); + // I would prefer not to do it this way. It should + // be using the attributes plugin to do this type of conversion. + this.List = Observe.List({ + Observe: this + }, {}); + } + return Observe; + })(__m3, __m8, __m1); + + // ## can/observe/compute/compute.js + var __m9 = (function(can, bind) { + + // returns the + // - observes and attr methods are called by func + // - the value returned by func + // ex: `{value: 100, observed: [{obs: o, attr: "completed"}]}` + var getValueAndObserved = function(func, self) { + + var oldReading; + if (can.Observe) { + // Set a callback on can.Observe to know + // when an attr is read. + // Keep a reference to the old reader + // if there is one. This is used + // for nested live binding. + oldReading = can.Observe.__reading; + can.Observe.__reading = function(obj, attr) { + // Add the observe and attr that was read + // to `observed` + observed.push({ + obj: obj, + attr: attr + "" + }); + }; + } + + var observed = [], + // Call the "wrapping" function to get the value. `observed` + // will have the observe/attribute pairs that were read. + value = func.call(self); + + // Set back so we are no longer reading. + if (can.Observe) { + can.Observe.__reading = oldReading; + } + return { + value: value, + observed: observed + }; + }, + // Calls `callback(newVal, oldVal)` everytime an observed property + // called within `getterSetter` is changed and creates a new result of `getterSetter`. + // Also returns an object that can teardown all event handlers. + computeBinder = function(getterSetter, context, callback, computeState) { + // track what we are observing + var observing = {}, + // a flag indicating if this observe/attr pair is already bound + matched = true, + // the data to return + data = { + // we will maintain the value while live-binding is taking place + value: undefined, + // a teardown method that stops listening + teardown: function() { + for (var name in observing) { + var ob = observing[name]; + ob.observe.obj.unbind(ob.observe.attr, onchanged); + delete observing[name]; + } + } + }, + batchNum; + + // when a property value is changed + var onchanged = function(ev) { + // If the compute is no longer bound (because the same change event led to an unbind) + // then do not call getValueAndBind, or we will leak bindings. + if (computeState && !computeState.bound) { + return; + } + if (ev.batchNum === undefined || ev.batchNum !== batchNum) { + // store the old value + var oldValue = data.value, + // get the new value + newvalue = getValueAndBind(); + + // update the value reference (in case someone reads) + data.value = newvalue; + // if a change happened + if (newvalue !== oldValue) { + callback(newvalue, oldValue); + } + batchNum = batchNum = ev.batchNum; + } + + + }; + + // gets the value returned by `getterSetter` and also binds to any attributes + // read by the call + var getValueAndBind = function() { + var info = getValueAndObserved(getterSetter, context), + newObserveSet = info.observed; + + var value = info.value; + matched = !matched; + + // go through every attribute read by this observe + can.each(newObserveSet, function(ob) { + // if the observe/attribute pair is being observed + if (observing[ob.obj._cid + "|" + ob.attr]) { + // mark at as observed + observing[ob.obj._cid + "|" + ob.attr].matched = matched; + } else { + // otherwise, set the observe/attribute on oldObserved, marking it as being observed + observing[ob.obj._cid + "|" + ob.attr] = { + matched: matched, + observe: ob + }; + ob.obj.bind(ob.attr, onchanged); + } + }); + + // Iterate through oldObserved, looking for observe/attributes + // that are no longer being bound and unbind them + for (var name in observing) { + var ob = observing[name]; + if (ob.matched !== matched) { + ob.observe.obj.unbind(ob.observe.attr, onchanged); + delete observing[name]; + } + } + return value; + }; + // set the initial value + data.value = getValueAndBind(); + + data.isListening = !can.isEmptyObject(observing); + return data; + } + + // if no one is listening ... we can not calculate every time + + can.compute = function(getterSetter, context, eventName) { + if (getterSetter && getterSetter.isComputed) { + return getterSetter; + } + // stores the result of computeBinder + var computedData, + // how many listeners to this this compute + bindings = 0, + // the computed object + computed, + // an object that keeps track if the computed is bound + // onchanged needs to know this. It's possible a change happens and results in + // something that unbinds the compute, it needs to not to try to recalculate who it + // is listening to + computeState = { + bound: false, + // true if this compute is calculated from other computes and observes + hasDependencies: false + }, + // The following functions are overwritten depending on how compute() is called + // a method to setup listening + on = function() {}, + // a method to teardown listening + off = function() {}, + // the current cached value (only valid if bound = true) + value, + // how to read the value + get = function() { + return value + }, + // sets the value + set = function(newVal) { + value = newVal; + }, + // this compute can be a dependency of other computes + canReadForChangeEvent = true; + + computed = function(newVal) { + // setting ... + if (arguments.length) { + // 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 + var setVal = set.call(context, newVal, old); + + // if this has dependencies return the current value + if (computed.hasDependencies) { + return get.call(context); + } + + 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 + if (old !== value) { + can.Observe.triggerBatch(computed, "change", [value, old]); + } + return value; + } else { + // Let others know to listen to changes in this compute + if (can.Observe.__reading && canReadForChangeEvent) { + can.Observe.__reading(computed, 'change'); + } + // if we are bound, use the cached value + if (computeState.bound) { + return value; + } else { + return get.call(context); + } + } + } + if (typeof getterSetter === "function") { + set = getterSetter; + get = getterSetter; + canReadForChangeEvent = eventName === false ? false : true; + computed.hasDependencies = false; + on = function(update) { + computedData = computeBinder(getterSetter, context || this, update, computeState); + computed.hasDependencies = computedData.isListening + value = computedData.value; + } + off = function() { + computedData && computedData.teardown(); + } + } else if (context) { + + if (typeof context == "string") { + // `can.compute(obj, "propertyName", [eventName])` + + var propertyName = context, + isObserve = getterSetter instanceof can.Observe; + if (isObserve) { + computed.hasDependencies = true; + } + get = function() { + if (isObserve) { + return getterSetter.attr(propertyName); + } else { + return getterSetter[propertyName]; + } + } + set = function(newValue) { + if (isObserve) { + getterSetter.attr(propertyName, newValue) + } else { + getterSetter[propertyName] = newValue; + } + } + var handler; + on = function(update) { + handler = function() { + update(get(), value) + }; + can.bind.call(getterSetter, eventName || propertyName, handler) + + // use getValueAndObserved because + // we should not be indicating that some parent + // reads this property if it happens to be binding on it + value = getValueAndObserved(get).value + } + off = function() { + can.unbind.call(getterSetter, eventName || propertyName, handler) + } + + } else { + // `can.compute(initialValue, setter)` + if (typeof context === "function") { + value = getterSetter; + set = context; + } else { + // `can.compute(initialValue,{get:, set:, on:, off:})` + value = getterSetter; + var options = context; + get = options.get || get; + set = options.set || set; + on = options.on || on; + off = options.off || off; + } + + } + + + + } else { + // `can.compute(5)` + value = getterSetter; + } + + computed.isComputed = true; + + can.cid(computed, "compute") + + var updater = function(newValue, oldValue) { + value = newValue; + // might need a way to look up new and oldVal + can.Observe.triggerBatch(computed, "change", [newValue, oldValue]) + } + + return can.extend(computed, { + _bindsetup: function() { + computeState.bound = true; + // setup live-binding + on.call(this, updater) + }, + _bindteardown: function() { + off.call(this, updater) + computeState.bound = false; + }, + + bind: can.bindAndSetup, + + unbind: can.unbindAndTeardown + }); + }; + can.compute.binder = computeBinder; + return can.compute; + })(__m3, __m8); + + // ## can/model/model.js + var __m10 = (function(can) { + + // ## model.js + // `can.Model` + // _A `can.Observe` that connects to a RESTful interface._ + // Generic deferred piping function + + var pipe = function(def, model, func) { + var d = new can.Deferred(); + def.then(function() { + var args = can.makeArray(arguments); + args[0] = model[func](args[0]); + d.resolveWith(d, args); + }, function() { + d.rejectWith(this, arguments); + }); + + if (typeof def.abort === 'function') { + d.abort = function() { + return def.abort(); + } + } + + return d; + }, + modelNum = 0, + ignoreHookup = /change.observe\d+/, + getId = function(inst) { + // Instead of using attr, use __get for performance. + // Need to set reading + can.Observe.__reading && can.Observe.__reading(inst, inst.constructor.id) + return inst.__get(inst.constructor.id); + }, + // Ajax `options` generator function + ajax = function(ajaxOb, data, type, dataType, success, error) { + + var params = {}; + + // If we get a string, handle it. + if (typeof ajaxOb == "string") { + // If there's a space, it's probably the type. + var parts = ajaxOb.split(/\s+/); + params.url = parts.pop(); + if (parts.length) { + params.type = parts.pop(); + } + } else { + can.extend(params, ajaxOb); + } + + // If we are a non-array object, copy to a new attrs. + params.data = typeof data == "object" && !can.isArray(data) ? + can.extend(params.data || {}, data) : data; + + // Get the url with any templated values filled out. + params.url = can.sub(params.url, params.data, true); + + return can.ajax(can.extend({ + type: type || "post", + dataType: dataType || "json", + success: success, + error: error + }, params)); + }, + makeRequest = function(self, type, success, error, method) { + var args; + // if we pass an array as `self` it it means we are coming from + // the queued request, and we're passing already serialized data + // self's signature will be: [self, serializedData] + if (can.isArray(self)) { + args = self[1]; + self = self[0]; + } else { + args = self.serialize(); + } + args = [args]; + var deferred, + // The model. + model = self.constructor, + jqXHR; + + // `destroy` does not need data. + if (type == 'destroy') { + args.shift(); + } + // `update` and `destroy` need the `id`. + if (type !== 'create') { + args.unshift(getId(self)); + } + + + jqXHR = model[type].apply(model, args); + + deferred = jqXHR.pipe(function(data) { + self[method || type + "d"](data, jqXHR); + return self; + }); + + // Hook up `abort` + if (jqXHR.abort) { + deferred.abort = function() { + jqXHR.abort(); + }; + } + + deferred.then(success, error); + return deferred; + }, + + // This object describes how to make an ajax request for each ajax method. + // The available properties are: + // `url` - The default url to use as indicated as a property on the model. + // `type` - The default http request type + // `data` - A method that takes the `arguments` and returns `data` used for ajax. + + ajaxMethods = { + + create: { + url: "_shortName", + type: "post" + }, + + update: { + data: function(id, attrs) { + attrs = attrs || {}; + var identity = this.id; + if (attrs[identity] && attrs[identity] !== id) { + attrs["new" + can.capitalize(id)] = attrs[identity]; + delete attrs[identity]; + } + attrs[identity] = id; + return attrs; + }, + type: "put" + }, + + destroy: { + type: "delete", + data: function(id) { + var args = {}; + args.id = args[this.id] = id; + return args; + } + }, + + findAll: { + url: "_shortName" + }, + + findOne: {} + }, + // Makes an ajax request `function` from a string. + // `ajaxMethod` - The `ajaxMethod` object defined above. + // `str` - The string the user provided. Ex: `findAll: "/recipes.json"`. + ajaxMaker = function(ajaxMethod, str) { + // Return a `function` that serves as the ajax method. + return function(data) { + // If the ajax method has it's own way of getting `data`, use that. + data = ajaxMethod.data ? + ajaxMethod.data.apply(this, arguments) : + // Otherwise use the data passed in. + data; + // Return the ajax method with `data` and the `type` provided. + return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get") + } + } + + + + can.Model = can.Observe({ + fullName: "can.Model", + _reqs: 0, + setup: function(base) { + // create store here if someone wants to use model without inheriting from it + this.store = {}; + can.Observe.setup.apply(this, arguments); + // Set default list as model list + if (!can.Model) { + return; + } + this.List = ML({ + Observe: this + }, {}); + var self = this, + clean = can.proxy(this._clean, self); + + + // go through ajax methods and set them up + can.each(ajaxMethods, function(method, name) { + // if an ajax method is not a function, it's either + // a string url like findAll: "/recipes" or an + // ajax options object like {url: "/recipes"} + if (!can.isFunction(self[name])) { + // use ajaxMaker to convert that into a function + // that returns a deferred with the data + self[name] = ajaxMaker(method, self[name]); + } + // check if there's a make function like makeFindAll + // these take deferred function and can do special + // behavior with it (like look up data in a store) + if (self["make" + can.capitalize(name)]) { + // pass the deferred method to the make method to get back + // the "findAll" method. + var newMethod = self["make" + can.capitalize(name)](self[name]); + can.Construct._overwrite(self, base, name, function() { + // increment the numer of requests + can.Model._reqs++; + var def = newMethod.apply(this, arguments); + var then = def.then(clean, clean); + then.abort = def.abort; + + // attach abort to our then and return it + return then; + }) + } + }); + + if (self.fullName == "can.Model" || !self.fullName) { + self.fullName = "Model" + (++modelNum); + } + // Add ajax converters. + can.Model._reqs = 0; + this._url = this._shortName + "/{" + this.id + "}" + }, + _ajax: ajaxMaker, + _makeRequest: makeRequest, + _clean: function() { + can.Model._reqs--; + if (!can.Model._reqs) { + for (var id in this.store) { + if (!this.store[id]._bindings) { + delete this.store[id]; + } + } + } + return arguments[0]; + }, + + models: function(instancesRawData, oldList) { + // until "end of turn", increment reqs counter so instances will be added to the store + can.Model._reqs++; + if (!instancesRawData) { + return; + } + + if (instancesRawData instanceof this.List) { + return instancesRawData; + } + + // Get the list type. + var self = this, + tmp = [], + res = oldList instanceof can.Observe.List ? oldList : new(self.List || ML), + // Did we get an `array`? + arr = can.isArray(instancesRawData), + + // Did we get a model list? + ml = (instancesRawData instanceof ML), + + // Get the raw `array` of objects. + raw = arr ? + + // If an `array`, return the `array`. + instancesRawData : + + // Otherwise if a model list. + (ml ? + + // Get the raw objects from the list. + instancesRawData.serialize() : + + // Get the object's data. + instancesRawData.data), + i = 0; + + + + if (res.length) { + res.splice(0); + } + + can.each(raw, function(rawPart) { + tmp.push(self.model(rawPart)); + }); + + // We only want one change event so push everything at once + res.push.apply(res, tmp); + + if (!arr) { // Push other stuff onto `array`. + can.each(instancesRawData, function(val, prop) { + if (prop !== 'data') { + res.attr(prop, val); + } + }) + } + // at "end of turn", clean up the store + setTimeout(can.proxy(this._clean, this), 1); + return res; + }, + + model: function(attributes) { + if (!attributes) { + return; + } + if (attributes instanceof this) { + attributes = attributes.serialize(); + } + var id = attributes[this.id], + model = (id || id === 0) && this.store[id] ? + this.store[id].attr(attributes, this.removeAttr || false) : new this(attributes); + if (can.Model._reqs) { + this.store[attributes[this.id]] = model; + } + return model; + } + }, + + + { + + isNew: function() { + var id = getId(this); + return !(id || id === 0); // If `null` or `undefined` + }, + + save: function(success, error) { + return makeRequest(this, this.isNew() ? 'create' : 'update', success, error); + }, + + destroy: function(success, error) { + if (this.isNew()) { + var self = this; + var def = can.Deferred(); + def.then(success, error); + return def.done(function(data) { + self.destroyed(data) + }).resolve(self); + } + return makeRequest(this, 'destroy', success, error, 'destroyed'); + }, + + _bindsetup: function() { + this.constructor.store[this.__get(this.constructor.id)] = this; + return can.Observe.prototype._bindsetup.apply(this, arguments); + }, + + _bindteardown: function() { + delete this.constructor.store[getId(this)]; + return can.Observe.prototype._bindteardown.apply(this, arguments) + }, + // Change `id`. + ___set: function(prop, val) { + can.Observe.prototype.___set.call(this, prop, val) + // If we add an `id`, move it to the store. + if (prop === this.constructor.id && this._bindings) { + this.constructor.store[getId(this)] = this; + } + } + }); + + can.each({ + makeFindAll: "models", + makeFindOne: "model", + makeCreate: "model", + makeUpdate: "model" + }, function(method, name) { + can.Model[name] = function(oldMethod) { + return function() { + var args = can.makeArray(arguments), + oldArgs = can.isFunction(args[1]) ? args.splice(0, 1) : args.splice(0, 2), + def = pipe(oldMethod.apply(this, oldArgs), this, method); + def.then(args[0], args[1]); + // return the original promise + return def; + }; + }; + }); + + can.each([ + + "created", + + "updated", + + "destroyed" + ], function(funcName) { + can.Model.prototype[funcName] = function(attrs) { + var stub, + constructor = this.constructor; + + // Update attributes if attributes have been passed + stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs); + + // triggers change event that bubble's like + // handler( 'change','1.destroyed' ). This is used + // to remove items on destroyed from Model Lists. + // but there should be a better way. + can.trigger(this, "change", funcName) + + + // Call event on the instance's Class + can.trigger(constructor, funcName, this); + }; + }); + + // Model lists are just like `Observe.List` except that when their items are + // destroyed, it automatically gets removed from the list. + + var ML = can.Model.List = can.Observe.List({ + setup: function(params) { + if (can.isPlainObject(params) && !can.isArray(params)) { + can.Observe.List.prototype.setup.apply(this); + this.replace(this.constructor.Observe.findAll(params)) + } else { + can.Observe.List.prototype.setup.apply(this, arguments); + } + }, + _changes: function(ev, attr) { + can.Observe.List.prototype._changes.apply(this, arguments); + if (/\w+\.destroyed/.test(attr)) { + var index = this.indexOf(ev.target); + if (index != -1) { + this.splice(index, 1); + } + } + } + }) + + return can.Model; + })(__m3, __m7); + + // ## can/view/view.js + var __m11 = (function(can) { + // ## view.js + // `can.view` + // _Templating abstraction._ + + var isFunction = can.isFunction, + makeArray = can.makeArray, + // Used for hookup `id`s. + hookupId = 1, + + $view = can.view = can.template = function(view, data, helpers, callback) { + // If helpers is a `function`, it is actually a callback. + if (isFunction(helpers)) { + callback = helpers; + helpers = undefined; + } + + var pipe = function(result) { + return $view.frag(result); + }, + // In case we got a callback, we need to convert the can.view.render + // result to a document fragment + wrapCallback = isFunction(callback) ? function(frag) { + callback(pipe(frag)); + } : null, + // Get the result. + result = $view.render(view, data, helpers, wrapCallback), + deferred = can.Deferred(); + + if (isFunction(result)) { + return result; + } + + if (can.isDeferred(result)) { + result.then(function(result, data) { + deferred.resolve.call(deferred, pipe(result), data); + }, function() { + deferred.fail.apply(deferred, arguments); + }); + return deferred; + } + + // Convert it into a dom frag. + return pipe(result); + }; + + can.extend($view, { + // creates a frag and hooks it up all at once + frag: function(result, parentNode) { + return $view.hookup($view.fragment(result), parentNode); + }, + + // simply creates a frag + // this is used internally to create a frag + // insert it + // then hook it up + fragment: function(result) { + var frag = can.buildFragment(result, document.body); + // If we have an empty frag... + if (!frag.childNodes.length) { + frag.appendChild(document.createTextNode('')); + } + return frag; + }, + + // Convert a path like string into something that's ok for an `element` ID. + toId: function(src) { + return can.map(src.toString().split(/\/|\./g), function(part) { + // Dont include empty strings in toId functions + if (part) { + return part; + } + }).join("_"); + }, + + hookup: function(fragment, parentNode) { + var hookupEls = [], + id, + func; + + // Get all `childNodes`. + can.each(fragment.childNodes ? can.makeArray(fragment.childNodes) : fragment, function(node) { + if (node.nodeType === 1) { + hookupEls.push(node); + hookupEls.push.apply(hookupEls, can.makeArray(node.getElementsByTagName('*'))); + } + }); + + // Filter by `data-view-id` attribute. + can.each(hookupEls, function(el) { + if (el.getAttribute && (id = el.getAttribute('data-view-id')) && (func = $view.hookups[id])) { + func(el, parentNode, id); + delete $view.hookups[id]; + el.removeAttribute('data-view-id'); + } + }); + + return fragment; + }, + + + hookups: {}, + + + hook: function(cb) { + $view.hookups[++hookupId] = cb; + return " data-view-id='" + hookupId + "'"; + }, + + + cached: {}, + + cachedRenderers: {}, + + + cache: true, + + + register: function(info) { + this.types["." + info.suffix] = info; + }, + + types: {}, + + + ext: ".ejs", + + + registerScript: function() {}, + + + preload: function() {}, + + + render: function(view, data, helpers, callback) { + // If helpers is a `function`, it is actually a callback. + if (isFunction(helpers)) { + callback = helpers; + helpers = undefined; + } + + // See if we got passed any deferreds. + var deferreds = getDeferreds(data); + + if (deferreds.length) { // Does data contain any deferreds? + // The deferred that resolves into the rendered content... + var deferred = new can.Deferred(), + dataCopy = can.extend({}, data); + + // Add the view request to the list of deferreds. + deferreds.push(get(view, true)) + + // Wait for the view and all deferreds to finish... + can.when.apply(can, deferreds).then(function(resolved) { + // Get all the resolved deferreds. + var objs = makeArray(arguments), + // Renderer is the last index of the data. + renderer = objs.pop(), + // The result of the template rendering with data. + result; + + // Make data look like the resolved deferreds. + if (can.isDeferred(data)) { + dataCopy = usefulPart(resolved); + } else { + // Go through each prop in data again and + // replace the defferreds with what they resolved to. + for (var prop in data) { + if (can.isDeferred(data[prop])) { + dataCopy[prop] = usefulPart(objs.shift()); + } + } + } + + // Get the rendered result. + result = renderer(dataCopy, helpers); + + // Resolve with the rendered view. + deferred.resolve(result, dataCopy); + + // If there's a `callback`, call it back with the result. + callback && callback(result, dataCopy); + }, function() { + deferred.reject.apply(deferred, arguments) + }); + // Return the deferred... + return deferred; + } else { + // No deferreds! Render this bad boy. + var response, + // If there's a `callback` function + async = isFunction(callback), + // Get the `view` type + deferred = get(view, async); + + // If we are `async`... + if (async) { + // Return the deferred + response = deferred; + // And fire callback with the rendered result. + deferred.then(function(renderer) { + callback(data ? renderer(data, helpers) : renderer); + }) + } else { + // if the deferred is resolved, call the cached renderer instead + // this is because it's possible, with recursive deferreds to + // need to render a view while its deferred is _resolving_. A _resolving_ deferred + // is a deferred that was just resolved and is calling back it's success callbacks. + // If a new success handler is called while resoliving, it does not get fired by + // jQuery's deferred system. So instead of adding a new callback + // we use the cached renderer. + // We also add __view_id on the deferred so we can look up it's cached renderer. + // In the future, we might simply store either a deferred or the cached result. + if (deferred.state() === "resolved" && deferred.__view_id) { + var currentRenderer = $view.cachedRenderers[deferred.__view_id]; + return data ? currentRenderer(data, helpers) : currentRenderer; + } else { + // Otherwise, the deferred is complete, so + // set response to the result of the rendering. + deferred.then(function(renderer) { + response = data ? renderer(data, helpers) : renderer; + }); + } + } + + return response; + } + }, + + + registerView: function(id, text, type, def) { + // Get the renderer function. + var func = (type || $view.types[$view.ext]).renderer(id, text); + def = def || new can.Deferred(); + + // Cache if we are caching. + if ($view.cache) { + $view.cached[id] = def; + def.__view_id = id; + $view.cachedRenderers[id] = func; + } + + // Return the objects for the response's `dataTypes` + // (in this case view). + return def.resolve(func); + } + }); + + // Makes sure there's a template, if not, have `steal` provide a warning. + var checkText = function(text, url) { + if (!text.length) { + + throw "can.view: No template or empty template:" + url; + } + }, + // `Returns a `view` renderer deferred. + // `url` - The url to the template. + // `async` - If the ajax request should be asynchronous. + // Returns a deferred. + get = function(url, async) { + var suffix = url.match(/\.[\w\d]+$/), + type, + // If we are reading a script element for the content of the template, + // `el` will be set to that script element. + el, + // A unique identifier for the view (used for caching). + // This is typically derived from the element id or + // the url for the template. + id, + // The ajax request used to retrieve the template content. + jqXHR; + + //If the url has a #, we assume we want to use an inline template + //from a script element and not current page's HTML + if (url.match(/^#/)) { + url = url.substr(1); + } + // If we have an inline template, derive the suffix from the `text/???` part. + // This only supports `