/*!
* CanJS - 1.1.5 (2013-03-27)
* http://canjs.us/
* Copyright (c) 2013 Bitovi
* Licensed MIT
*/
(function (window, $, undefined) {
// ## can/util/can.js
var can = window.can || {};
if (typeof GLOBALCAN === 'undefined' || GLOBALCAN !== false) {
window.can = can;
}
can.isDeferred = function (obj) {
var isFunction = this.isFunction;
// Returns `true` if something looks like a deferred.
return obj && isFunction(obj.then) && isFunction(obj.pipe);
};
var cid = 0;
can.cid = function (object, name) {
if (object._cid) {
return object._cid
} else {
return object._cid = (name || "") + (++cid)
}
}
// ## can/util/array/each.js
can.each = function (elements, callback, context) {
var i = 0,
key;
if (elements) {
if (typeof elements.length === 'number' && elements.pop) {
if (elements.attr) {
elements.attr('length');
}
for (key = elements.length; i < key; i++) {
if (callback.call(context || elements[i], elements[i], i, elements) === false) {
break;
}
}
} else if (elements.hasOwnProperty) {
for (key in elements) {
if (elements.hasOwnProperty(key)) {
if (callback.call(context || elements[key], elements[key], key, elements) === false) {
break;
}
}
}
}
}
return elements;
};
// ## can/util/jquery/jquery.js
// _jQuery node list._
$.extend(can, $, {
trigger: function (obj, event, args) {
if (obj.trigger) {
obj.trigger(event, args);
} else {
$.event.trigger(event, args, obj, true);
}
},
addEvent: function (ev, cb) {
$([this]).bind(ev, cb);
return this;
},
removeEvent: function (ev, cb) {
$([this]).unbind(ev, cb);
return this;
},
// jquery caches fragments, we always needs a new one
buildFragment: function (elems, context) {
var oldFragment = $.buildFragment,
ret;
elems = [elems];
// Set context per 1.8 logic
context = context || document;
context = !context.nodeType && context[0] || context;
context = context.ownerDocument || context;
ret = oldFragment.call(jQuery, elems, context);
return ret.cacheable ? $.clone(ret.fragment) : ret.fragment || ret;
},
$: $,
each: can.each
});
// Wrap binding functions.
$.each(['bind', 'unbind', 'undelegate', 'delegate'], function (i, func) {
can[func] = function () {
var t = this[func] ? this : $([this]);
t[func].apply(t, arguments);
return this;
};
});
// Wrap modifier functions.
$.each(["append", "filter", "addClass", "remove", "data", "get"], function (i, name) {
can[name] = function (wrapped) {
return wrapped[name].apply(wrapped, can.makeArray(arguments).slice(1));
};
});
// Memory safe destruction.
var oldClean = $.cleanData;
$.cleanData = function (elems) {
$.each(elems, function (i, elem) {
if (elem) {
can.trigger(elem, "destroyed", [], false);
}
});
oldClean(elems);
};
// ## can/util/string/string.js
// ##string.js
// _Miscellaneous string utility functions._
// Several of the methods in this plugin use code adapated from Prototype
// Prototype JavaScript framework, version 1.6.0.1.
// © 2005-2007 Sam Stephenson
var strUndHash = /_|-/,
strColons = /\=\=/,
strWords = /([A-Z]+)([A-Z][a-z])/g,
strLowUp = /([a-z\d])([A-Z])/g,
strDash = /([a-z\d])([A-Z])/g,
strReplacer = /\{([^\}]+)\}/g,
strQuote = /"/g,
strSingleQuote = /'/g,
// Returns the `prop` property from `obj`.
// If `add` is true and `prop` doesn't exist in `obj`, create it as an
// empty object.
getNext = function (obj, prop, add) {
return prop in obj ? obj[prop] : (add && (obj[prop] = {}));
},
// Returns `true` if the object can have properties (no `null`s).
isContainer = function (current) {
return (/^f|^o/).test(typeof current);
};
can.extend(can, {
// Escapes strings for HTML.
esc: function (content) {
// Convert bad values into empty strings
var isInvalid = content === null || content === undefined || (isNaN(content) && ("" + content === 'NaN'));
return ("" + (isInvalid ? '' : content)).replace(/&/g, '&').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,
ret, i;
// Make sure roots is an `array`.
roots = can.isArray(roots) ? roots : [roots || window];
if (!length) {
return roots[0];
}
// For each root, mark it as current.
while (roots[r]) {
current = roots[r];
// Walk current to the 2nd to last object or until there
// is not a container.
for (i = 0; i < length - 1 && isContainer(current); i++) {
current = getNext(current, parts[i], add);
}
// If we can get a property from the 2nd to last object...
if (isContainer(current)) {
// Get (and possibly set) the property.
ret = getNext(current, parts[i], add);
// If there is a value, we exit.
if (ret !== undefined) {
// If `add` is `false`, delete the property
if (add === false) {
delete current[parts[i]];
}
return ret;
}
}
r++;
}
},
// Capitalizes a string.
capitalize: function (s, cache) {
// Used to make newId.
return s.charAt(0).toUpperCase() + s.slice(1);
},
// Underscores a string.
underscore: function (s) {
return s.replace(strColons, '/').replace(strWords, '$1_$2').replace(strLowUp, '$1_$2').replace(strDash, '_').toLowerCase();
},
// Micro-templating.
sub: function (str, data, remove) {
var obs = [];
obs.push(str.replace(strReplacer, function (whole, inside) {
// Convert inside to type.
var ob = can.getObject(inside, data, remove === undefined ? remove : !remove);
if (ob === undefined) {
obs = null;
return "";
}
// If a container, push into objs (which will return objects found).
if (isContainer(ob) && obs) {
obs.push(ob);
return "";
}
return "" + ob;
}));
return obs === null ? obs : (obs.length <= 1 ? obs[0] : obs);
},
// These regex's are used throughout the rest of can, so let's make
// them available.
replacer: strReplacer,
undHash: strUndHash
});
// ## can/construct/construct.js
// ## construct.js
// `can.Construct`
// _This is a modified version of
// [John Resig's class](http://ejohn.org/blog/simple-javascript-inheritance/).
// It provides class level inheritance and callbacks._
// A private flag used to initialize a new class instance without
// initializing it's bindings.
var initializing = 0;
can.Construct = function () {
if (arguments.length) {
return can.Construct.extend.apply(can.Construct, arguments);
}
};
can.extend(can.Construct, {
newInstance: function () {
// Get a raw instance object (`init` is not called).
var inst = this.instance(),
arg = arguments,
args;
// Call `setup` if there is a `setup`
if (inst.setup) {
args = inst.setup.apply(inst, arguments);
}
// Call `init` if there is an `init`
// If `setup` returned `args`, use those as the arguments
if (inst.init) {
inst.init.apply(inst, args || arguments);
}
return inst;
},
// Overwrites an object with methods. Used in the `super` plugin.
// `newProps` - New properties to add.
// `oldProps` - Where the old properties might be (used with `super`).
// `addTo` - What we are adding to.
_inherit: function (newProps, oldProps, addTo) {
can.extend(addTo || newProps, newProps || {})
},
// used for overwriting a single property.
// this should be used for patching other objects
// the super plugin overwrites this
_overwrite: function (what, oldProps, propName, val) {
what[propName] = val;
},
// Set `defaults` as the merger of the parent `defaults` and this
// object's `defaults`. If you overwrite this method, make sure to
// include option merging logic.
setup: function (base, fullName) {
this.defaults = can.extend(true, {}, base.defaults, this.defaults);
},
// Create's a new `class` instance without initializing by setting the
// `initializing` flag.
instance: function () {
// Prevents running `init`.
initializing = 1;
var inst = new this();
// Allow running `init`.
initializing = 0;
return inst;
},
// Extends classes.
extend: function (fullName, klass, proto) {
// Figure out what was passed and normalize it.
if (typeof fullName != 'string') {
proto = klass;
klass = fullName;
fullName = null;
}
if (!proto) {
proto = klass;
klass = null;
}
proto = proto || {};
var _super_class = this,
_super = this.prototype,
name, shortName, namespace, prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor).
prototype = this.instance();
// Copy the properties over onto the new prototype.
can.Construct._inherit(proto, _super, prototype);
// The dummy class constructor.
function Constructor() {
// All construction is actually done in the init method.
if (!initializing) {
return this.constructor !== Constructor && arguments.length ?
// We are being called without `new` or we are extending.
arguments.callee.extend.apply(arguments.callee, arguments) :
// We are being called with `new`.
this.constructor.newInstance.apply(this.constructor, arguments);
}
}
// Copy old stuff onto class (can probably be merged w/ inherit)
for (name in _super_class) {
if (_super_class.hasOwnProperty(name)) {
Constructor[name] = _super_class[name];
}
}
// Copy new static properties on class.
can.Construct._inherit(klass, _super_class, Constructor);
// Setup namespaces.
if (fullName) {
var parts = fullName.split('.'),
shortName = parts.pop(),
current = can.getObject(parts.join('.'), window, true),
namespace = current,
_fullName = can.underscore(fullName.replace(/\./g, "_")),
_shortName = can.underscore(shortName);
current[shortName] = Constructor;
}
// Set things that shouldn't be overwritten.
can.extend(Constructor, {
constructor: Constructor,
prototype: prototype,
namespace: namespace,
shortName: shortName,
_shortName: _shortName,
fullName: fullName,
_fullName: _fullName
});
// Make sure our prototype looks nice.
Constructor.prototype.constructor = Constructor;
// Call the class `setup` and `init`
var t = [_super_class].concat(can.makeArray(arguments)),
args = Constructor.setup.apply(Constructor, t);
if (Constructor.init) {
Constructor.init.apply(Constructor, args || t);
}
return Constructor;
}
});
// ## can/observe/observe.js
// ## observe.js
// `can.Observe`
// _Provides the observable pattern for JavaScript Objects._
// Returns `true` if something is an object with properties of its own.
var canMakeObserve = function (obj) {
return obj && (can.isArray(obj) || can.isPlainObject(obj) || (obj instanceof can.Observe));
},
// Removes all listeners.
unhookup = function (items, namespace) {
return can.each(items, function (item) {
if (item && item.unbind) {
item.unbind("change" + namespace);
}
});
},
// Listens to changes on `val` and "bubbles" the event up.
// `val` - The object to listen for changes on.
// `prop` - The property name is at on.
// `parent` - The parent object of prop.
// `ob` - (optional) The Observe object constructor
// `list` - (optional) The observable list constructor
hookupBubble = function (val, prop, parent, Ob, List) {
Ob = Ob || Observe;
List = List || Observe.List;
// If it's an `array` make a list, otherwise a val.
if (val instanceof Observe) {
// We have an `observe` already...
// Make sure it is not listening to this already
unhookup([val], parent._cid);
} else if (can.isArray(val)) {
val = new List(val);
} else {
val = new Ob(val);
}
// Listen to all changes and `batchTrigger` upwards.
val.bind("change" + parent._cid, function () {
// `batchTrigger` the type on this...
var args = can.makeArray(arguments),
ev = args.shift();
args[0] = (prop === "*" ? [parent.indexOf(val), args[0]] : [prop, args[0]]).join(".");
// track objects dispatched on this observe
ev.triggeredNS = ev.triggeredNS || {};
// if it has already been dispatched exit
if (ev.triggeredNS[parent._cid]) {
return;
}
ev.triggeredNS[parent._cid] = true;
// send change event with modified attr to parent
can.trigger(parent, ev, args);
// send modified attr event to parent
//can.trigger(parent, args[0], args);
});
return val;
},
// An `id` to track events for a given observe.
observeId = 0,
// A helper used to serialize an `Observe` or `Observe.List`.
// `observe` - The observable.
// `how` - To serialize with `attr` or `serialize`.
// `where` - To put properties, in an `{}` or `[]`.
serialize = function (observe, how, where) {
// Go through each property.
observe.each(function (val, name) {
// If the value is an `object`, and has an `attrs` or `serialize` function.
where[name] = canMakeObserve(val) && can.isFunction(val[how]) ?
// Call `attrs` or `serialize` to get the original data back.
val[how]() :
// Otherwise return the value.
val;
});
return where;
},
$method = function (name) {
return function () {
return can[name].apply(this, arguments);
};
},
bind = $method('addEvent'),
unbind = $method('removeEvent'),
attrParts = function (attr, keepKey) {
if (keepKey) {
return [attr];
}
return can.isArray(attr) ? attr : ("" + attr).split(".");
},
// Which batch of events this is for -- might not want to send multiple
// messages on the same batch. This is mostly for event delegation.
batchNum = 1,
// how many times has start been called without a stop
transactions = 0,
// an array of events within a transaction
batchEvents = [],
stopCallbacks = [];
var Observe = can.Observe = can.Construct({
// keep so it can be overwritten
bind: bind,
unbind: unbind,
id: "id",
canMakeObserve: canMakeObserve,
// starts collecting events
// takes a callback for after they are updated
// how could you hook into after ejs
startBatch: function (batchStopHandler) {
transactions++;
batchStopHandler && stopCallbacks.push(batchStopHandler);
},
stopBatch: function (force, callStart) {
if (force) {
transactions = 0;
} else {
transactions--;
}
if (transactions == 0) {
var items = batchEvents.slice(0),
callbacks = stopCallbacks.slice(0);
batchEvents = [];
stopCallbacks = [];
batchNum++;
callStart && this.startBatch();
can.each(items, function (args) {
can.trigger.apply(can, args);
});
can.each(callbacks, function (cb) {
cb();
});
}
},
triggerBatch: function (item, event, args) {
// Don't send events if initalizing.
if (!item._init) {
if (transactions == 0) {
return can.trigger(item, event, args);
} else {
event = typeof event === "string" ? {
type: event
} : event;
event.batchNum = batchNum;
batchEvents.push([
item, event, args]);
}
}
},
keys: function (observe) {
var keys = [];
Observe.__reading && Observe.__reading(observe, '__keys');
for (var keyName in observe._data) {
keys.push(keyName);
}
return keys;
}
},
{
setup: function (obj) {
// `_data` is where we keep the properties.
this._data = {};
// The namespace this `object` uses to listen to events.
can.cid(this, ".observe");
// Sets all `attrs`.
this._init = 1;
this.attr(obj);
this.bind('change' + this._cid, can.proxy(this._changes, this));
delete this._init;
},
_changes: function (ev, attr, how, newVal, oldVal) {
Observe.triggerBatch(this, {
type: attr,
batchNum: ev.batchNum
}, [newVal, oldVal]);
},
_triggerChange: function (attr, how, newVal, oldVal) {
Observe.triggerBatch(this, "change", can.makeArray(arguments))
},
attr: function (attr, val) {
// This is super obfuscated for space -- basically, we're checking
// if the type of the attribute is not a `number` or a `string`.
var type = typeof attr;
if (type !== "string" && type !== "number") {
return this._attrs(attr, val)
} else if (val === undefined) { // If we are getting a value.
// Let people know we are reading.
Observe.__reading && Observe.__reading(this, attr)
return this._get(attr)
} else {
// Otherwise we are setting.
this._set(attr, val);
return this;
}
},
each: function () {
Observe.__reading && Observe.__reading(this, '__keys');
return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments)))
},
removeAttr: function (attr) {
// Info if this is List or not
var isList = this instanceof can.Observe.List,
// Convert the `attr` into parts (if nested).
parts = attrParts(attr),
// The actual property to remove.
prop = parts.shift(),
// The current value.
current = isList ? this[prop] : this._data[prop];
// If we have more parts, call `removeAttr` on that part.
if (parts.length) {
return current.removeAttr(parts)
} else {
if (isList) {
this.splice(prop, 1)
} else if (prop in this._data) {
// Otherwise, `delete`.
delete this._data[prop];
// Create the event.
if (!(prop in this.constructor.prototype)) {
delete this[prop]
}
// Let others know the number of keys have changed
Observe.triggerBatch(this, "__keys");
this._triggerChange(prop, "remove", undefined, current);
}
return current;
}
},
// Reads a property from the `object`.
_get: function (attr) {
var value = typeof attr === 'string' && !! ~attr.indexOf('.') && this.__get(attr);
if (value) {
return value;
}
// break up the attr (`"foo.bar"`) into `["foo","bar"]`
var parts = attrParts(attr),
// get the value of the first attr name (`"foo"`)
current = this.__get(parts.shift());
// if there are other attributes to read
return parts.length ?
// and current has a value
current ?
// lookup the remaining attrs on current
current._get(parts) :
// or if there's no current, return undefined
undefined :
// if there are no more parts, return current
current;
},
// Reads a property directly if an `attr` is provided, otherwise
// returns the "real" data object itself.
__get: function (attr) {
return attr ? this._data[attr] : this._data;
},
// Sets `attr` prop as value on this object where.
// `attr` - Is a string of properties or an array of property values.
// `value` - The raw value to set.
_set: function (attr, value, keepKey) {
// Convert `attr` to attr parts (if it isn't already).
var parts = attrParts(attr, keepKey),
// The immediate prop we are setting.
prop = parts.shift(),
// The current value.
current = this.__get(prop);
// If we have an `object` and remaining parts.
if (canMakeObserve(current) && parts.length) {
// That `object` should set it (this might need to call attr).
current._set(parts, value)
} else if (!parts.length) {
// We're in "real" set territory.
if (this.__convert) {
value = this.__convert(prop, value)
}
this.__set(prop, value, current)
} else {
throw "can.Observe: Object does not exist"
}
},
__set: function (prop, value, current) {
// Otherwise, we are setting it on this `object`.
// TODO: Check if value is object and transform
// are we changing the value.
if (value !== current) {
// Check if we are adding this for the first time --
// if we are, we need to create an `add` event.
var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add";
// Set the value on data.
this.___set(prop,
// If we are getting an object.
canMakeObserve(value) ?
// Hook it up to send event.
hookupBubble(value, prop, this) :
// Value is normal.
value);
if (changeType == "add") {
// If there is no current value, let others know that
// the the number of keys have changed
Observe.triggerBatch(this, "__keys", undefined);
}
// `batchTrigger` the change event.
this._triggerChange(prop, changeType, value, current);
//Observe.triggerBatch(this, prop, [value, current]);
// If we can stop listening to our old value, do it.
current && unhookup([current], this._cid);
}
},
// Directly sets a property on this `object`.
___set: function (prop, val) {
this._data[prop] = val;
// Add property directly for easy writing.
// Check if its on the `prototype` so we don't overwrite methods like `attrs`.
if (!(prop in this.constructor.prototype)) {
this[prop] = val
}
},
bind: bind,
unbind: unbind,
serialize: function () {
return serialize(this, 'serialize', {});
},
_attrs: function (props, remove) {
if (props === undefined) {
return serialize(this, 'attr', {})
}
props = can.extend({}, props);
var prop, self = this,
newVal;
Observe.startBatch();
this.each(function (curVal, prop) {
newVal = props[prop];
// If we are merging...
if (newVal === undefined) {
remove && self.removeAttr(prop);
return;
}
if (self.__convert) {
newVal = self.__convert(prop, newVal)
}
// if we're dealing with models, want to call _set to let converter run
if (newVal instanceof can.Observe) {
self.__set(prop, newVal, curVal)
// if its an object, let attr merge
} else if (canMakeObserve(curVal) && canMakeObserve(newVal) && curVal.attr) {
curVal.attr(newVal, remove)
// otherwise just set
} else if (curVal != newVal) {
self.__set(prop, newVal, curVal)
}
delete props[prop];
})
// Add remaining props.
for (var prop in props) {
newVal = props[prop];
this._set(prop, newVal, true)
}
Observe.stopBatch()
return this;
},
compute: function (prop) {
var self = this,
computer = function (val) {
return self.attr(prop, val);
};
return can.compute ? can.compute(computer) : computer;
}
});
// Helpers for `observable` lists.
var splice = [].splice,
list = Observe(
{
setup: function (instances, options) {
this.length = 0;
can.cid(this, ".observe")
this._init = 1;
if (can.isDeferred(instances)) {
this.replace(instances)
} else {
this.push.apply(this, can.makeArray(instances || []));
}
this.bind('change' + this._cid, can.proxy(this._changes, this));
can.extend(this, options);
delete this._init;
},
_triggerChange: function (attr, how, newVal, oldVal) {
Observe.prototype._triggerChange.apply(this, arguments)
// `batchTrigger` direct add and remove events...
if (!~attr.indexOf('.')) {
if (how === 'add') {
Observe.triggerBatch(this, how, [newVal, +attr]);
Observe.triggerBatch(this, 'length', [this.length]);
} else if (how === 'remove') {
Observe.triggerBatch(this, how, [oldVal, +attr]);
Observe.triggerBatch(this, 'length', [this.length]);
} else {
Observe.triggerBatch(this, how, [newVal, +attr])
}
}
},
__get: function (attr) {
return attr ? this[attr] : this;
},
___set: function (attr, val) {
this[attr] = val;
if (+attr >= this.length) {
this.length = (+attr + 1)
}
},
// Returns the serialized form of this list.
serialize: function () {
return serialize(this, 'serialize', []);
},
splice: function (index, howMany) {
var args = can.makeArray(arguments),
i;
for (i = 2; i < args.length; i++) {
var val = args[i];
if (canMakeObserve(val)) {
args[i] = hookupBubble(val, "*", this, this.constructor.Observe, this.constructor)
}
}
if (howMany === undefined) {
howMany = args[1] = this.length - index;
}
var removed = splice.apply(this, args);
can.Observe.startBatch();
if (howMany > 0) {
this._triggerChange("" + index, "remove", undefined, removed);
unhookup(removed, this._cid);
}
if (args.length > 2) {
this._triggerChange("" + index, "add", args.slice(2), removed);
}
can.Observe.stopBatch();
return removed;
},
_attrs: function (items, remove) {
if (items === undefined) {
return serialize(this, 'attr', []);
}
// Create a copy.
items = can.makeArray(items);
Observe.startBatch();
this._updateAttrs(items, remove);
Observe.stopBatch()
},
_updateAttrs: function (items, remove) {
var len = Math.min(items.length, this.length);
for (var prop = 0; prop < len; prop++) {
var curVal = this[prop],
newVal = items[prop];
if (canMakeObserve(curVal) && canMakeObserve(newVal)) {
curVal.attr(newVal, remove)
} else if (curVal != newVal) {
this._set(prop, newVal)
} else {
}
}
if (items.length > this.length) {
// Add in the remaining props.
this.push.apply(this, items.slice(this.length));
} else if (items.length < this.length && remove) {
this.splice(items.length)
}
}
}),
// Converts to an `array` of arguments.
getArgs = function (args) {
return args[0] && can.isArray(args[0]) ? args[0] : can.makeArray(args);
};
// Create `push`, `pop`, `shift`, and `unshift`
can.each({
push: "length",
unshift: 0
},
// Adds a method
// `name` - The method name.
// `where` - Where items in the `array` should be added.
function (where, name) {
var orig = [][name]
list.prototype[name] = function () {
// Get the items being added.
var args = [],
// Where we are going to add items.
len = where ? this.length : 0,
i = arguments.length,
res, val, constructor = this.constructor;
// Go through and convert anything to an `observe` that needs to be converted.
while (i--) {
val = arguments[i];
args[i] = canMakeObserve(val) ? hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) : val;
}
// Call the original method.
res = orig.apply(this, args);
if (!this.comparator || args.length) {
this._triggerChange("" + len, "add", args, undefined);
}
return res;
}
});
can.each({
pop: "length",
shift: 0
},
// Creates a `remove` type method
function (where, name) {
list.prototype[name] = function () {
var args = getArgs(arguments),
len = where && this.length ? this.length - 1 : 0;
var res = [][name].apply(this, args)
// Create a change where the args are
// `*` - Change on potentially multiple properties.
// `remove` - Items removed.
// `undefined` - The new values (there are none).
// `res` - The old, removed values (should these be unbound).
// `len` - Where these items were removed.
this._triggerChange("" + len, "remove", undefined, [res])
if (res && res.unbind) {
res.unbind("change" + this._cid)
}
return res;
}
});
can.extend(list.prototype, {
indexOf: function (item) {
this.attr('length')
return can.inArray(item, this)
},
join: [].join,
reverse: [].reverse,
slice: function () {
var temp = Array.prototype.slice.apply(this, arguments);
return new this.constructor(temp);
},
concat: function () {
var args = [];
can.each(can.makeArray(arguments), function (arg, i) {
args[i] = arg instanceof can.Observe.List ? arg.serialize() : arg;
});
return new this.constructor(Array.prototype.concat.apply(this.serialize(), args));
},
forEach: function (cb, thisarg) {
can.each(this, cb, thisarg || this);
},
replace: function (newList) {
if (can.isDeferred(newList)) {
newList.then(can.proxy(this.replace, this));
} else {
this.splice.apply(this, [0, this.length].concat(can.makeArray(newList || [])));
}
return this;
}
});
Observe.List = list;
Observe.setup = function () {
can.Construct.setup.apply(this, arguments);
// I would prefer not to do it this way. It should
// be using the attributes plugin to do this type of conversion.
this.List = Observe.List({
Observe: this
}, {});
}
// ## can/model/model.js
// ## model.js
// `can.Model`
// _A `can.Observe` that connects to a RESTful interface._
// Generic deferred piping function
var pipe = function (def, model, func) {
var d = new can.Deferred();
def.then(function () {
var args = can.makeArray(arguments);
args[0] = model[func](args[0]);
d.resolveWith(d, args);
}, function () {
d.rejectWith(this, arguments);
});
if (typeof def.abort === 'function') {
d.abort = function () {
return def.abort();
}
}
return d;
},
modelNum = 0,
ignoreHookup = /change.observe\d+/,
getId = function (inst) {
// Instead of using attr, use __get for performance.
// Need to set reading
can.Observe.__reading && can.Observe.__reading(inst, inst.constructor.id)
return inst.__get(inst.constructor.id);
},
// Ajax `options` generator function
ajax = function (ajaxOb, data, type, dataType, success, error) {
var params = {};
// If we get a string, handle it.
if (typeof ajaxOb == "string") {
// If there's a space, it's probably the type.
var parts = ajaxOb.split(/\s+/);
params.url = parts.pop();
if (parts.length) {
params.type = parts.pop();
}
} else {
can.extend(params, ajaxOb);
}
// If we are a non-array object, copy to a new attrs.
params.data = typeof data == "object" && !can.isArray(data) ? can.extend(params.data || {}, data) : data;
// Get the url with any templated values filled out.
params.url = can.sub(params.url, params.data, true);
return can.ajax(can.extend({
type: type || "post",
dataType: dataType || "json",
success: success,
error: error
}, params));
},
makeRequest = function (self, type, success, error, method) {
var args;
// if we pass an array as `self` it it means we are coming from
// the queued request, and we're passing already serialized data
// self's signature will be: [self, serializedData]
if (can.isArray(self)) {
args = self[1];
self = self[0];
} else {
args = self.serialize();
}
args = [args];
var deferred,
// The model.
model = self.constructor,
jqXHR;
// `destroy` does not need data.
if (type == 'destroy') {
args.shift();
}
// `update` and `destroy` need the `id`.
if (type !== 'create') {
args.unshift(getId(self));
}
jqXHR = model[type].apply(model, args);
deferred = jqXHR.pipe(function (data) {
self[method || type + "d"](data, jqXHR);
return self;
});
// Hook up `abort`
if (jqXHR.abort) {
deferred.abort = function () {
jqXHR.abort();
};
}
deferred.then(success, error);
return deferred;
},
// This object describes how to make an ajax request for each ajax method.
// The available properties are:
// `url` - The default url to use as indicated as a property on the model.
// `type` - The default http request type
// `data` - A method that takes the `arguments` and returns `data` used for ajax.
ajaxMethods = {
create: {
url: "_shortName",
type: "post"
},
update: {
data: function (id, attrs) {
attrs = attrs || {};
var identity = this.id;
if (attrs[identity] && attrs[identity] !== id) {
attrs["new" + can.capitalize(id)] = attrs[identity];
delete attrs[identity];
}
attrs[identity] = id;
return attrs;
},
type: "put"
},
destroy: {
type: "delete",
data: function (id) {
var args = {};
args.id = args[this.id] = id;
return args;
}
},
findAll: {
url: "_shortName"
},
findOne: {}
},
// Makes an ajax request `function` from a string.
// `ajaxMethod` - The `ajaxMethod` object defined above.
// `str` - The string the user provided. Ex: `findAll: "/recipes.json"`.
ajaxMaker = function (ajaxMethod, str) {
// Return a `function` that serves as the ajax method.
return function (data) {
// If the ajax method has it's own way of getting `data`, use that.
data = ajaxMethod.data ? ajaxMethod.data.apply(this, arguments) :
// Otherwise use the data passed in.
data;
// Return the ajax method with `data` and the `type` provided.
return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get")
}
}
can.Model = can.Observe({
fullName: "can.Model",
setup: function (base) {
// create store here if someone wants to use model without inheriting from it
this.store = {};
can.Observe.setup.apply(this, arguments);
// Set default list as model list
if (!can.Model) {
return;
}
this.List = ML({
Observe: this
}, {});
var self = this,
clean = can.proxy(this._clean, self);
// go through ajax methods and set them up
can.each(ajaxMethods, function (method, name) {
// if an ajax method is not a function, it's either
// a string url like findAll: "/recipes" or an
// ajax options object like {url: "/recipes"}
if (!can.isFunction(self[name])) {
// use ajaxMaker to convert that into a function
// that returns a deferred with the data
self[name] = ajaxMaker(method, self[name]);
}
// check if there's a make function like makeFindAll
// these take deferred function and can do special
// behavior with it (like look up data in a store)
if (self["make" + can.capitalize(name)]) {
// pass the deferred method to the make method to get back
// the "findAll" method.
var newMethod = self["make" + can.capitalize(name)](self[name]);
can.Construct._overwrite(self, base, name, function () {
// increment the numer of requests
this._reqs++;
var def = newMethod.apply(this, arguments);
var then = def.then(clean, clean);
then.abort = def.abort;
// attach abort to our then and return it
return then;
})
}
});
if (self.fullName == "can.Model" || !self.fullName) {
self.fullName = "Model" + (++modelNum);
}
// Add ajax converters.
this._reqs = 0;
this._url = this._shortName + "/{" + this.id + "}"
},
_ajax: ajaxMaker,
_makeRequest: makeRequest,
_clean: function () {
this._reqs--;
if (!this._reqs) {
for (var id in this.store) {
if (!this.store[id]._bindings) {
delete this.store[id];
}
}
}
return arguments[0];
},
models: function (instancesRawData, oldList) {
if (!instancesRawData) {
return;
}
if (instancesRawData instanceof this.List) {
return instancesRawData;
}
// Get the list type.
var self = this,
tmp = [],
res = oldList instanceof can.Observe.List ? oldList : new(self.List || ML),
// Did we get an `array`?
arr = can.isArray(instancesRawData),
// Did we get a model list?
ml = (instancesRawData instanceof ML),
// Get the raw `array` of objects.
raw = arr ?
// If an `array`, return the `array`.
instancesRawData :
// Otherwise if a model list.
(ml ?
// Get the raw objects from the list.
instancesRawData.serialize() :
// Get the object's data.
instancesRawData.data),
i = 0;
if (res.length) {
res.splice(0);
}
can.each(raw, function (rawPart) {
tmp.push(self.model(rawPart));
});
// We only want one change event so push everything at once
res.push.apply(res, tmp);
if (!arr) { // Push other stuff onto `array`.
can.each(instancesRawData, function (val, prop) {
if (prop !== 'data') {
res.attr(prop, val);
}
})
}
return res;
},
model: function (attributes) {
if (!attributes) {
return;
}
if (attributes instanceof this) {
attributes = attributes.serialize();
}
var id = attributes[this.id],
model = (id || id === 0) && this.store[id] ? this.store[id].attr(attributes, this.removeAttr || false) : new this(attributes);
if (this._reqs) {
this.store[attributes[this.id]] = model;
}
return model;
}
},
{
isNew: function () {
var id = getId(this);
return !(id || id === 0); // If `null` or `undefined`
},
save: function (success, error) {
return makeRequest(this, this.isNew() ? 'create' : 'update', success, error);
},
destroy: function (success, error) {
if (this.isNew()) {
var self = this;
return can.Deferred().done(function (data) {
self.destroyed(data)
}).resolve(self);
}
return makeRequest(this, 'destroy', success, error, 'destroyed');
},
bind: function (eventName) {
if (!ignoreHookup.test(eventName)) {
if (!this._bindings) {
this.constructor.store[this.__get(this.constructor.id)] = this;
this._bindings = 0;
}
this._bindings++;
}
return can.Observe.prototype.bind.apply(this, arguments);
},
unbind: function (eventName) {
if (!ignoreHookup.test(eventName)) {
this._bindings--;
if (!this._bindings) {
delete this.constructor.store[getId(this)];
}
}
return can.Observe.prototype.unbind.apply(this, arguments);
},
// Change `id`.
___set: function (prop, val) {
can.Observe.prototype.___set.call(this, prop, val)
// If we add an `id`, move it to the store.
if (prop === this.constructor.id && this._bindings) {
this.constructor.store[getId(this)] = this;
}
}
});
can.each({
makeFindAll: "models",
makeFindOne: "model"
}, function (method, name) {
can.Model[name] = function (oldFind) {
return function (params, success, error) {
var def = pipe(oldFind.call(this, params), this, method);
def.then(success, error);
// 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);
// Call event on the instance
can.trigger(this, funcName);
// 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 () {
can.Observe.List.prototype.setup.apply(this, arguments);
// Send destroy events.
var self = this;
this.bind('change', function (ev, how) {
if (/\w+\.destroyed/.test(how)) {
var index = self.indexOf(ev.target);
if (index != -1) {
self.splice(index, 1);
}
}
})
}
})
// ## can/util/string/deparam/deparam.js
// ## deparam.js
// `can.deparam`
// _Takes a string of name value pairs and returns a Object literal that represents those params._
var digitTest = /^\d+$/,
keyBreaker = /([^\[\]]+)|(\[\])/g,
paramTest = /([^?#]*)(#.*)?$/,
prep = function (str) {
return decodeURIComponent(str.replace(/\+/g, " "));
};
can.extend(can, {
deparam: function (params) {
var data = {},
pairs, lastPart;
if (params && paramTest.test(params)) {
pairs = params.split('&'),
can.each(pairs, function (pair) {
var parts = pair.split('='),
key = prep(parts.shift()),
value = prep(parts.join("=")),
current = data;
if (key) {
parts = key.match(keyBreaker);
for (var j = 0, l = parts.length - 1; j < l; j++) {
if (!current[parts[j]]) {
// If what we are pointing to looks like an `array`
current[parts[j]] = digitTest.test(parts[j + 1]) || parts[j + 1] == "[]" ? [] : {};
}
current = current[parts[j]];
}
lastPart = parts.pop();
if (lastPart == "[]") {
current.push(value);
} else {
current[lastPart] = value;
}
}
});
}
return data;
}
});
// ## can/route/route.js
// ## route.js
// `can.route`
// _Helps manage browser history (and client state) by synchronizing the
// `window.location.hash` with a `can.Observe`._
// Helper methods used for matching routes.
var
// `RegExp` used to match route variables of the type ':name'.
// Any word character or a period is matched.
matcher = /\:([\w\.]+)/g,
// Regular expression for identifying &key=value lists.
paramsMatcher = /^(?:&[^=]+=[^&]*)+/,
// Converts a JS Object into a list of parameters that can be
// inserted into an html element tag.
makeProps = function (props) {
var tags = [];
can.each(props, function (val, name) {
tags.push((name === 'className' ? 'class' : name) + '="' + (name === "href" ? val : can.esc(val)) + '"');
});
return tags.join(" ");
},
// Checks if a route matches the data provided. If any route variable
// is not present in the data, the route does not match. If all route
// variables are present in the data, the number of matches is returned
// to allow discerning between general and more specific routes.
matchesData = function (route, data) {
var count = 0,
i = 0,
defaults = {};
// look at default values, if they match ...
for (var name in route.defaults) {
if (route.defaults[name] === data[name]) {
// mark as matched
defaults[name] = 1;
count++;
}
}
for (; i < route.names.length; i++) {
if (!data.hasOwnProperty(route.names[i])) {
return -1;
}
if (!defaults[route.names[i]]) {
count++;
}
}
return count;
},
onready = !0,
location = window.location,
wrapQuote = function (str) {
return (str + '').replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1");
},
each = can.each,
extend = can.extend;
can.route = function (url, defaults) {
defaults = defaults || {};
// Extract the variable names and replace with `RegExp` that will match
// an atual URL with values.
var names = [],
test = url.replace(matcher, function (whole, name, i) {
names.push(name);
var next = "\\" + (url.substr(i + whole.length, 1) || can.route._querySeparator);
// a name without a default value HAS to have a value
// a name that has a default value can be empty
// The `\\` is for string-escaping giving single `\` for `RegExp` escaping.
return "([^" + next + "]" + (defaults[name] ? "*" : "+") + ")";
});
// Add route in a form that can be easily figured out.
can.route.routes[url] = {
// A regular expression that will match the route when variable values
// are present; i.e. for `:page/:type` the `RegExp` is `/([\w\.]*)/([\w\.]*)/` which
// will match for any value of `:page` and `:type` (word chars or period).
test: new RegExp("^" + test + "($|" + wrapQuote(can.route._querySeparator) + ")"),
// The original URL, same as the index for this entry in routes.
route: url,
// An `array` of all the variable names in this route.
names: names,
// Default values provided for the variables.
defaults: defaults,
// The number of parts in the URL separated by `/`.
length: url.split('/').length
};
return can.route;
};
extend(can.route, {
_querySeparator: '&',
_paramsMatcher: paramsMatcher,
param: function (data, _setRoute) {
// Check if the provided data keys match the names in any routes;
// Get the one with the most matches.
var route,
// Need to have at least 1 match.
matches = 0,
matchCount, routeName = data.route,
propCount = 0;
delete data.route;
each(data, function () {
propCount++;
});
// Otherwise find route.
each(can.route.routes, function (temp, name) {
// best route is the first with all defaults matching
matchCount = matchesData(temp, data);
if (matchCount > matches) {
route = temp;
matches = matchCount;
}
if (matchCount >= propCount) {
return false;
}
});
// If we have a route name in our `can.route` data, and it's
// just as good as what currently matches, use that
if (can.route.routes[routeName] && matchesData(can.route.routes[routeName], data) === matches) {
route = can.route.routes[routeName];
}
// If this is match...
if (route) {
var cpy = extend({}, data),
// Create the url by replacing the var names with the provided data.
// If the default value is found an empty string is inserted.
res = route.route.replace(matcher, function (whole, name) {
delete cpy[name];
return data[name] === route.defaults[name] ? "" : encodeURIComponent(data[name]);
}),
after;
// Remove matching default values
each(route.defaults, function (val, name) {
if (cpy[name] === val) {
delete cpy[name];
}
});
// The remaining elements of data are added as
// `&` separated parameters to the url.
after = can.param(cpy);
// if we are paraming for setting the hash
// we also want to make sure the route value is updated
if (_setRoute) {
can.route.attr('route', route.route);
}
return res + (after ? can.route._querySeparator + after : "");
}
// If no route was found, there is no hash URL, only paramters.
return can.isEmptyObject(data) ? "" : can.route._querySeparator + can.param(data);
},
deparam: function (url) {
// See if the url matches any routes by testing it against the `route.test` `RegExp`.
// By comparing the URL length the most specialized route that matches is used.
var route = {
length: -1
};
each(can.route.routes, function (temp, name) {
if (temp.test.test(url) && temp.length > route.length) {
route = temp;
}
});
// If a route was matched.
if (route.length > -1) {
var // Since `RegExp` backreferences are used in `route.test` (parens)
// the parts will contain the full matched string and each variable (back-referenced) value.
parts = url.match(route.test),
// Start will contain the full matched string; parts contain the variable values.
start = parts.shift(),
// The remainder will be the `&key=value` list at the end of the URL.
remainder = url.substr(start.length - (parts[parts.length - 1] === can.route._querySeparator ? 1 : 0)),
// If there is a remainder and it contains a `&key=value` list deparam it.
obj = (remainder && can.route._paramsMatcher.test(remainder)) ? can.deparam(remainder.slice(1)) : {};
// Add the default values for this route.
obj = extend(true, {}, route.defaults, obj);
// Overwrite each of the default values in `obj` with those in
// parts if that part is not empty.
each(parts, function (part, i) {
if (part && part !== can.route._querySeparator) {
obj[route.names[i]] = decodeURIComponent(part);
}
});
obj.route = route.route;
return obj;
}
// If no route was matched, it is parsed as a `&key=value` list.
if (url.charAt(0) !== can.route._querySeparator) {
url = can.route._querySeparator + url;
}
return can.route._paramsMatcher.test(url) ? can.deparam(url.slice(1)) : {};
},
data: new can.Observe({}),
routes: {},
ready: function (val) {
if (val === false) {
onready = val;
}
if (val === true || onready === true) {
can.route._setup();
setState();
}
return can.route;
},
url: function (options, merge) {
if (merge) {
options = extend({}, curParams, options)
}
return "#!" + can.route.param(options);
},
link: function (name, options, props, merge) {
return "" + name + "";
},
current: function (options) {
return location.hash == "#!" + can.route.param(options)
},
_setup: function () {
// If the hash changes, update the `can.route.data`.
can.bind.call(window, 'hashchange', setState);
},
_getHash: function () {
return location.href.split(/#!?/)[1] || "";
},
_setHash: function (serialized) {
var path = (can.route.param(serialized, true));
location.hash = "#!" + path;
return path;
}
});
// The functions in the following list applied to `can.route` (e.g. `can.route.attr('...')`) will
// instead act on the `can.route.data` observe.
each(['bind', 'unbind', 'delegate', 'undelegate', 'attr', 'removeAttr'], function (name) {
can.route[name] = function () {
return can.route.data[name].apply(can.route.data, arguments)
}
})
var // A ~~throttled~~ debounced function called multiple times will only fire once the
// timer runs down. Each call resets the timer.
timer,
// Intermediate storage for `can.route.data`.
curParams,
// Deparameterizes the portion of the hash of interest and assign the
// values to the `can.route.data` removing existing values no longer in the hash.
// setState is called typically by hashchange which fires asynchronously
// So it's possible that someone started changing the data before the
// hashchange event fired. For this reason, it will not set the route data
// if the data is changing or the hash already matches the hash that was set.
setState = can.route.setState = function () {
var hash = can.route._getHash();
curParams = can.route.deparam(hash);
// if the hash data is currently changing, or
// the hash is what we set it to anyway, do NOT change the hash
if (!changingData || hash !== lastHash) {
can.route.attr(curParams, true);
}
},
// The last hash caused by a data change
lastHash,
// Are data changes pending that haven't yet updated the hash
changingData;
// If the `can.route.data` changes, update the hash.
// Using `.serialize()` retrieves the raw data contained in the `observable`.
// This function is ~~throttled~~ debounced so it only updates once even if multiple values changed.
// This might be able to use batchNum and avoid this.
can.route.bind("change", function (ev, attr) {
// indicate that data is changing
changingData = 1;
clearTimeout(timer);
timer = setTimeout(function () {
// indicate that the hash is set to look like the data
changingData = 0;
var serialized = can.route.data.serialize();
lastHash = can.route._setHash(serialized);
}, 1);
});
// `onready` event...
can.bind.call(document, "ready", can.route.ready);
// Libraries other than jQuery don't execute the document `ready` listener
// if we are already DOM ready
if ((document.readyState === 'complete' || document.readyState === "interactive") && onready) {
can.route.ready();
}
// extend route to have a similar property
// that is often checked in mustache to determine
// an object's observability
can.route.constructor.canMakeObserve = can.Observe.canMakeObserve;
// ## can/control/control.js
// ## control.js
// `can.Control`
// _Controller_
// Binds an element, returns a function that unbinds.
var bind = function (el, ev, callback) {
can.bind.call(el, ev, callback);
return function () {
can.unbind.call(el, ev, callback);
};
},
isFunction = can.isFunction,
extend = can.extend,
each = can.each,
slice = [].slice,
paramReplacer = /\{([^\}]+)\}/g,
special = can.getObject("$.event.special", [can]) || {},
// Binds an element, returns a function that unbinds.
delegate = function (el, selector, ev, callback) {
can.delegate.call(el, selector, ev, callback);
return function () {
can.undelegate.call(el, selector, ev, callback);
};
},
// Calls bind or unbind depending if there is a selector.
binder = function (el, ev, callback, selector) {
return selector ? delegate(el, can.trim(selector), ev, callback) : bind(el, ev, callback);
},
basicProcessor;
var Control = can.Control = can.Construct(
{
// Setup pre-processes which methods are event listeners.
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)) {
control.actions[funcName] = control._action(funcName);
}
}
}
},
// 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;
if (!isFunction(method)) {
method = context[method];
}
return function () {
context.called = name;
return method.apply(context, [this.nodeName ? can.$(this) : this].concat(slice.call(arguments, 0)));
};
},
// Return `true` if is an action.
_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).
_action: function (methodName, options) {
// If we don't have options (a `control` instance), we'll run this
// later.
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, [options, window]) : methodName;
if (!convertedName) {
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();
return {
processor: processors[event] || basicProcessor,
parts: [name, parts.join(" "), event],
delegate: arr ? convertedName[0] : undefined
};
}
},
// An object of `{eventName : function}` pairs that Control uses to
// hook up events auto-magically.
processors: {},
// A object of name-value pairs that act as default values for a
// control instance
defaults: {}
},
{
// Sets `this.element`, saves the control in `data, binds event
// handlers.
setup: function (element, options) {
var cls = this.constructor,
pluginname = cls.pluginName || cls._fullName,
arr;
// Want the raw element here.
this.element = can.$(element)
if (pluginname && pluginname !== 'can_control') {
// Set element and `className` on element.
this.element.addClass(pluginname);
}
(arr = can.data(this.element, "controls")) || can.data(this.element, "controls", arr = []);
arr.push(this);
// Option merging.
this.options = extend({}, cls.defaults, options);
// Bind all event handlers.
this.on();
// Get's passed into `init`.
return [this.element, this.options];
},
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,
element = this.element,
destroyCB = can.Control._shifter(this, "destroy"),
funcName, ready;
for (funcName in actions) {
// Only push if we have the action and no option is `undefined`
if (actions.hasOwnProperty(funcName) && (ready = actions[funcName] || cls._action(funcName, this.options))) {
bindings.push(ready.processor(ready.delegate || element, ready.parts[2], ready.parts[1], funcName, this));
}
}
// Setup to be destroyed...
// don't bind because we don't want to remove it.
can.bind.call(element, "destroyed", destroyCB);
bindings.push(function (el) {
can.unbind.call(el, "destroyed", destroyCB);
});
return bindings.length;
}
if (typeof el == 'string') {
func = eventName;
eventName = selector;
selector = el;
el = this.element;
}
if (func === undefined) {
func = eventName;
eventName = selector;
selector = null;
}
if (typeof func == 'string') {
func = can.Control._shifter(this, func);
}
this._bindings.push(binder(el, eventName, func, selector));
return this._bindings.length;
},
// Unbinds all event handlers on the controller.
off: function () {
var el = this.element[0]
each(this._bindings || [], function (value) {
value(el);
});
// Adds bindings.
this._bindings = [];
},
// Prepares a `control` for garbage collection
destroy: function () {
var Class = this.constructor,
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.
this.element = null;
}
});
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);
};
// Set common events to be processed as a `basicProcessor`
each(["change", "click", "contextmenu", "dblclick", "keydown", "keyup", "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;
});
// ## can/control/route/route.js
// ## control/route.js
// _Controller route integration._
can.Control.processors.route = function (el, event, selector, funcName, controller) {
selector = selector || "";
can.route(selector);
var batchNum, check = function (ev, attr, how) {
if (can.route.attr('route') === (selector) && (ev.batchNum === undefined || ev.batchNum !== batchNum)) {
batchNum = ev.batchNum;
var d = can.route.attr();
delete d.route;
if (can.isFunction(controller[funcName])) {
controller[funcName](d);
} else {
controller[controller[funcName]](d);
}
}
};
can.route.bind('change', check);
return function () {
can.route.unbind('change', check);
};
};
// ## can/view/view.js
// ## view.js
// `can.view`
// _Templating abstraction._
var isFunction = can.isFunction,
makeArray = can.makeArray,
// Used for hookup `id`s.
hookupId = 1,
$view = can.view = 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 `