Checkpoint.
* Got fonts working cross platform by only using SVG. This will change
with the new webkit engine when Tide 1.4 is released.
* Use the Tide API accessors for cross-window data.
* Make the ThingFish server object an observable, so models and their
URI bases can be changed easily.
* Add options for saving window state and position.
* Contextual menu updates/tests for later.
/*!
* 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(/>/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 `<script>` tags.
if (el = document.getElementById(url)) {
suffix = "." + el.type.match(/\/(x\-)?(.+)/)[2];
}
// If there is no suffix, add one.
if (!suffix && !$view.cached[url]) {
url += (suffix = $view.ext);
}
if (can.isArray(suffix)) {
suffix = suffix[0]
}
// Convert to a unique and valid id.
id = $view.toId(url);
// If an absolute path, use `steal` to get it.
// You should only be using `//` if you are using `steal`.
if (url.match(/^\/\//)) {
var sub = url.substr(2);
url = !window.steal ?
sub :
steal.config().root.mapJoin("" + steal.id(sub));
}
// Set the template engine type.
type = $view.types[suffix];
// If it is cached,
if ($view.cached[id]) {
// Return the cached deferred renderer.
return $view.cached[id];
// Otherwise if we are getting this from a `<script>` element.
} else if (el) {
// Resolve immediately with the element's `innerHTML`.
return $view.registerView(id, el.innerHTML, type);
} else {
// Make an ajax request for text.
var d = new can.Deferred();
can.ajax({
async: async,
url: url,
dataType: "text",
error: function(jqXHR) {
checkText("", url);
d.reject(jqXHR);
},
success: function(text) {
// Make sure we got some text back.
checkText(text, url);
$view.registerView(id, text, type, d)
}
});
return d;
}
},
// Gets an `array` of deferreds from an `object`.
// This only goes one level deep.
getDeferreds = function(data) {
var deferreds = [];
// pull out deferreds
if (can.isDeferred(data)) {
return [data]
} else {
for (var prop in data) {
if (can.isDeferred(data[prop])) {
deferreds.push(data[prop]);
}
}
}
return deferreds;
},
// Gets the useful part of a resolved deferred.
// This is for `model`s and `can.ajax` that resolve to an `array`.
usefulPart = function(resolved) {
return can.isArray(resolved) && resolved[1] === 'success' ? resolved[0] : resolved
};
//!steal-pluginify-remove-start
if (window.steal) {
steal.type("view js", function(options, success, error) {
var type = $view.types["." + options.type],
id = $view.toId(options.id);
options.text = "steal('" + (type.plugin || "can/view/" + options.type) + "',function(can){return " + "can.view.preload('" + id + "'," + options.text + ");\n})";
success();
})
}
//!steal-pluginify-remove-end
can.extend($view, {
register: function(info) {
this.types["." + info.suffix] = info;
//!steal-pluginify-remove-start
if (window.steal) {
steal.type(info.suffix + " view js", function(options, success, error) {
var type = $view.types["." + options.type],
id = $view.toId(options.id + '');
options.text = type.script(id, options.text)
success();
})
};
//!steal-pluginify-remove-end
$view[info.suffix] = function(id, text) {
if (!text) {
// Return a nameless renderer
var renderer = function() {
return $view.frag(renderer.render.apply(this, arguments));
}
renderer.render = function() {
var renderer = info.renderer(null, id);
return renderer.apply(renderer, arguments);
}
return renderer;
}
$view.preload(id, info.renderer(id, text));
return can.view(id);
}
},
registerScript: function(type, id, src) {
return "can.view.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");";
},
preload: function(id, renderer) {
$view.cached[id] = new can.Deferred().resolve(function(data, helpers) {
return renderer.call(data, data, helpers);
});
function frag() {
return $view.frag(renderer.apply(this, arguments));
}
// expose the renderer for mustache
frag.render = renderer;
return frag;
}
});
return can;
})(__m3);
// ## can/view/elements.js
var __m14 = (function() {
var elements = {
tagToContentPropMap: {
option: "textContent" in document.createElement("option") ? "textContent" : "innerText",
textarea: "value"
},
attrMap: {
"class": "className",
"value": "value",
"innerText": "innerText",
"textContent": "textContent",
"checked": true,
"disabled": true,
"readonly": true,
"required": true
},
// elements whos default value we should set
defaultValue: ["input", "textarea"],
// a map of parent element to child elements
tagMap: {
"": "span",
table: "tbody",
tr: "td",
ol: "li",
ul: "li",
tbody: "tr",
thead: "tr",
tfoot: "tr",
select: "option",
optgroup: "option"
},
// a tag's parent element
reverseTagMap: {
tr: "tbody",
option: "select",
td: "tr",
th: "tr",
li: "ul"
},
getParentNode: function(el, defaultParentNode) {
return defaultParentNode && el.parentNode.nodeType === 11 ? defaultParentNode : el.parentNode;
},
// set an attribute on an element
setAttr: function(el, attrName, val) {
var tagName = el.nodeName.toString().toLowerCase(),
prop = elements.attrMap[attrName];
// if this is a special property
if (prop === true) {
el[attrName] = true;
} else if (prop) {
// set the value as true / false
el[prop] = val;
if (prop === "value" && can.inArray(tagName, elements.defaultValue) >= 0) {
el.defaultValue = val;
}
} else {
el.setAttribute(attrName, val);
}
},
// gets the value of an attribute
getAttr: function(el, attrName) {
// Default to a blank string for IE7/8
return (elements.attrMap[attrName] && el[elements.attrMap[attrName]] ?
el[elements.attrMap[attrName]] :
el.getAttribute(attrName)) || '';
},
// removes the attribute
removeAttr: function(el, attrName) {
if (elements.attrMap[attrName] === true) {
el[attrName] = false;
} else {
el.removeAttribute(attrName);
}
},
contentText: function(text) {
if (typeof text == 'string') {
return text;
}
// If has no value, return an empty string.
if (!text && text !== 0) {
return '';
}
return "" + text;
}
};
return elements;
})();
// ## can/view/scanner.js
var __m13 = (function(can, elements) {
var newLine = /(\r|\n)+/g,
// Escapes characters starting with `\`.
clean = function(content) {
return content
.split('\\').join("\\\\")
.split("\n").join("\\n")
.split('"').join('\\"')
.split("\t").join("\\t");
},
// Returns a tagName to use as a temporary placeholder for live content
// looks forward ... could be slow, but we only do it when necessary
getTag = function(tagName, tokens, i) {
// if a tagName is provided, use that
if (tagName) {
return tagName;
} else {
// otherwise go searching for the next two tokens like "<",TAG
while (i < tokens.length) {
if (tokens[i] == "<" && elements.reverseTagMap[tokens[i + 1]]) {
return elements.reverseTagMap[tokens[i + 1]];
}
i++;
}
}
return '';
},
bracketNum = function(content) {
return (--content.split("{").length) - (--content.split("}").length);
},
myEval = function(script) {
eval(script);
},
attrReg = /([^\s]+)[\s]*=[\s]*$/,
// Commands for caching.
startTxt = 'var ___v1ew = [];',
finishTxt = "return ___v1ew.join('')",
put_cmd = "___v1ew.push(",
insert_cmd = put_cmd,
// Global controls (used by other functions to know where we are).
// Are we inside a tag?
htmlTag = null,
// Are we within a quote within a tag?
quote = null,
// What was the text before the current quote? (used to get the `attr` name)
beforeQuote = null,
// Whether a rescan is in progress
rescan = null,
// Used to mark where the element is.
status = function() {
// `t` - `1`.
// `h` - `0`.
// `q` - String `beforeQuote`.
return quote ? "'" + beforeQuote.match(attrReg)[1] + "'" : (htmlTag ? 1 : 0);
};
can.view.Scanner = Scanner = function(options) {
// Set options on self
can.extend(this, {
text: {},
tokens: []
}, options);
// Cache a token lookup
this.tokenReg = [];
this.tokenSimple = {
"<": "<",
">": ">",
'"': '"',
"'": "'"
};
this.tokenComplex = [];
this.tokenMap = {};
for (var i = 0, token; token = this.tokens[i]; i++) {
// Save complex mappings (custom regexp)
if (token[2]) {
this.tokenReg.push(token[2]);
this.tokenComplex.push({
abbr: token[1],
re: new RegExp(token[2]),
rescan: token[3]
});
}
// Save simple mappings (string only, no regexp)
else {
this.tokenReg.push(token[1]);
this.tokenSimple[token[1]] = token[0];
}
this.tokenMap[token[0]] = token[1];
}
// Cache the token registry.
this.tokenReg = new RegExp("(" + this.tokenReg.slice(0).concat(["<", ">", '"', "'"]).join("|") + ")", "g");
};
Scanner.prototype = {
helpers: [
{
name: /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
fn: function(content) {
var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
parts = content.match(quickFunc);
return "can.proxy(function(__){var " + parts[1] + "=can.$(__);" + parts[2] + "}, this);";
}
}
],
scan: function(source, name) {
var tokens = [],
last = 0,
simple = this.tokenSimple,
complex = this.tokenComplex;
source = source.replace(newLine, "\n");
if (this.transform) {
source = this.transform(source);
}
source.replace(this.tokenReg, function(whole, part) {
// offset is the second to last argument
var offset = arguments[arguments.length - 2];
// if the next token starts after the last token ends
// push what's in between
if (offset > last) {
tokens.push(source.substring(last, offset));
}
// push the simple token (if there is one)
if (simple[whole]) {
tokens.push(whole);
}
// otherwise lookup complex tokens
else {
for (var i = 0, token; token = complex[i]; i++) {
if (token.re.test(whole)) {
tokens.push(token.abbr);
// Push a rescan function if one exists
if (token.rescan) {
tokens.push(token.rescan(part));
}
break;
}
}
}
// update the position of the last part of the last token
last = offset + part.length;
});
// if there's something at the end, add it
if (last < source.length) {
tokens.push(source.substr(last));
}
var content = '',
buff = [startTxt + (this.text.start || '')],
// Helper `function` for putting stuff in the view concat.
put = function(content, bonus) {
buff.push(put_cmd, '"', clean(content), '"' + (bonus || '') + ');');
},
// A stack used to keep track of how we should end a bracket
// `}`.
// Once we have a `<%= %>` with a `leftBracket`,
// we store how the file should end here (either `))` or `;`).
endStack = [],
// The last token, used to remember which tag we are in.
lastToken,
// The corresponding magic tag.
startTag = null,
// Was there a magic tag inside an html tag?
magicInTag = false,
// The current tag name.
tagName = '',
// stack of tagNames
tagNames = [],
// Pop from tagNames?
popTagName = false,
// Declared here.
bracketCount,
i = 0,
token,
tmap = this.tokenMap;
// Reinitialize the tag state goodness.
htmlTag = quote = beforeQuote = null;
for (;
(token = tokens[i++]) !== undefined;) {
if (startTag === null) {
switch (token) {
case tmap.left:
case tmap.escapeLeft:
case tmap.returnLeft:
magicInTag = htmlTag && 1;
case tmap.commentLeft:
// A new line -- just add whatever content within a clean.
// Reset everything.
startTag = token;
if (content.length) {
put(content);
}
content = '';
break;
case tmap.escapeFull:
// This is a full line escape (a line that contains only whitespace and escaped logic)
// Break it up into escape left and right
magicInTag = htmlTag && 1;
rescan = 1;
startTag = tmap.escapeLeft;
if (content.length) {
put(content);
}
rescan = tokens[i++];
content = rescan.content || rescan;
if (rescan.before) {
put(rescan.before);
}
tokens.splice(i, 0, tmap.right);
break;
case tmap.commentFull:
// Ignore full line comments.
break;
case tmap.templateLeft:
content += tmap.left;
break;
case '<':
// Make sure we are not in a comment.
if (tokens[i].indexOf("!--") !== 0) {
htmlTag = 1;
magicInTag = 0;
}
content += token;
break;
case '>':
htmlTag = 0;
// content.substr(-1) doesn't work in IE7/8
var emptyElement = content.substr(content.length - 1) == "/" || content.substr(content.length - 2) == "--";
// if there was a magic tag
// or it's an element that has text content between its tags,
// but content is not other tags add a hookup
// TODO: we should only add `can.EJS.pending()` if there's a magic tag
// within the html tags.
if (magicInTag || !popTagName && elements.tagToContentPropMap[tagNames[tagNames.length - 1]]) {
// make sure / of /> is on the left of pending
if (emptyElement) {
put(content.substr(0, content.length - 1), ",can.view.pending(),\"/>\"");
} else {
put(content, ",can.view.pending(),\">\"");
}
content = '';
magicInTag = 0;
} else {
content += token;
}
// if it's a tag like <input/>
if (emptyElement || popTagName) {
// remove the current tag in the stack
tagNames.pop();
// set the current tag to the previous parent
tagName = tagNames[tagNames.length - 1];
// Don't pop next time
popTagName = false;
}
break;
case "'":
case '"':
// If we are in an html tag, finding matching quotes.
if (htmlTag) {
// We have a quote and it matches.
if (quote && quote === token) {
// We are exiting the quote.
quote = null;
// Otherwise we are creating a quote.
// TODO: does this handle `\`?
} else if (quote === null) {
quote = token;
beforeQuote = lastToken;
}
}
default:
// Track the current tag
if (lastToken === '<') {
tagName = token.split(/\s/)[0];
if (tagName.indexOf("/") === 0 && tagNames[tagNames.length - 1] === tagName.substr(1)) {
// set tagName to the last tagName
// if there are no more tagNames, we'll rely on getTag.
tagName = tagNames[tagNames.length - 1];
popTagName = true;
} else {
tagNames.push(tagName);
}
}
content += token;
break;
}
} else {
// We have a start tag.
switch (token) {
case tmap.right:
case tmap.returnRight:
switch (startTag) {
case tmap.left:
// Get the number of `{ minus }`
bracketCount = bracketNum(content);
// We are ending a block.
if (bracketCount == 1) {
// We are starting on.
buff.push(insert_cmd, "can.view.txt(0,'" + getTag(tagName, tokens, i) + "'," + status() + ",this,function(){", startTxt, content);
endStack.push({
before: "",
after: finishTxt + "}));\n"
});
} else {
// How are we ending this statement?
last = // If the stack has value and we are ending a block...
endStack.length && bracketCount == -1 ? // Use the last item in the block stack.
endStack.pop() : // Or use the default ending.
{
after: ";"
};
// If we are ending a returning block,
// add the finish text which returns the result of the
// block.
if (last.before) {
buff.push(last.before);
}
// Add the remaining content.
buff.push(content, ";", last.after);
}
break;
case tmap.escapeLeft:
case tmap.returnLeft:
// We have an extra `{` -> `block`.
// Get the number of `{ minus }`.
bracketCount = bracketNum(content);
// If we have more `{`, it means there is a block.
if (bracketCount) {
// When we return to the same # of `{` vs `}` end with a `doubleParent`.
endStack.push({
before: finishTxt,
after: "}));"
});
}
var escaped = startTag === tmap.escapeLeft ? 1 : 0,
commands = {
insert: insert_cmd,
tagName: getTag(tagName, tokens, i),
status: status()
};
for (var ii = 0; ii < this.helpers.length; ii++) {
// Match the helper based on helper
// regex name value
var helper = this.helpers[ii];
if (helper.name.test(content)) {
content = helper.fn(content, commands);
// dont escape partials
if (helper.name.source == /^>[\s]*\w*/.source) {
escaped = 0;
}
break;
}
}
// Handle special cases
if (typeof content == 'object') {
if (content.raw) {
buff.push(content.raw);
}
} else {
// If we have `<%== a(function(){ %>` then we want
// `can.EJS.text(0,this, function(){ return a(function(){ var _v1ew = [];`.
buff.push(insert_cmd, "can.view.txt(" + escaped + ",'" + tagName + "'," + status() + ",this,function(){ " + (this.text.escape || '') + "return ", content,
// If we have a block.
bracketCount ?
// Start with startTxt `"var _v1ew = [];"`.
startTxt :
// If not, add `doubleParent` to close push and text.
"}));");
}
if (rescan && rescan.after && rescan.after.length) {
put(rescan.after.length);
rescan = null;
}
break;
}
startTag = null;
content = '';
break;
case tmap.templateLeft:
content += tmap.left;
break;
default:
content += token;
break;
}
}
lastToken = token;
}
// Put it together...
if (content.length) {
// Should be `content.dump` in Ruby.
put(content);
}
buff.push(";");
var template = buff.join(''),
out = {
out: 'with(_VIEW) { with (_CONTEXT) {' + template + " " + finishTxt + "}}"
};
// Use `eval` instead of creating a function, because it is easier to debug.
myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js");
return out;
}
};
return Scanner;
})(__m11, __m14);
// ## can/view/node_lists.js
var __m17 = (function(can) {
// text node expando test
var canExpando = true;
try {
document.createTextNode('')._ = 0;
} catch (ex) {
canExpando = false;
}
// a mapping of element ids to nodeList ids
var nodeMap = {},
// a mapping of ids to text nodes
textNodeMap = {},
// a mapping of nodeList ids to nodeList
nodeListMap = {},
expando = "ejs_" + Math.random(),
_id = 0,
id = function(node) {
if (canExpando || node.nodeType !== 3) {
if (node[expando]) {
return node[expando];
} else {
return node[expando] = (node.nodeName ? "element_" : "obj_") + (++_id);
}
} else {
for (var textNodeID in textNodeMap) {
if (textNodeMap[textNodeID] === node) {
return textNodeID;
}
}
textNodeMap["text_" + (++_id)] = node;
return "text_" + _id;
}
},
// removes a nodeListId from a node's nodeListIds
removeNodeListId = function(node, nodeListId) {
var nodeListIds = nodeMap[id(node)];
if (nodeListIds) {
var index = can.inArray(nodeListId, nodeListIds);
if (index >= 0) {
nodeListIds.splice(index, 1);
}
if (!nodeListIds.length) {
delete nodeMap[id(node)];
}
}
},
addNodeListId = function(node, nodeListId) {
var nodeListIds = nodeMap[id(node)];
if (!nodeListIds) {
nodeListIds = nodeMap[id(node)] = [];
}
nodeListIds.push(nodeListId);
};
var nodeLists = {
id: id,
// replaces the contents of one node list with the nodes in another list
replace: function(oldNodeList, newNodes) {
// for each node in the node list
oldNodeList = can.makeArray(oldNodeList);
// try every set
//can.each( oldNodeList, function(node){
var node = oldNodeList[0]
// for each nodeList the node is in
can.each(can.makeArray(nodeMap[id(node)]), function(nodeListId) {
// if startNode to endNode is
// within list, replace that list
// I think the problem is not the WHOLE part is being
// matched
var nodeList = nodeListMap[nodeListId],
startIndex = can.inArray(node, nodeList),
endIndex = can.inArray(oldNodeList[oldNodeList.length - 1], nodeList);
// remove this nodeListId from each node
if (startIndex >= 0 && endIndex >= 0) {
for (var i = startIndex; i <= endIndex; i++) {
var n = nodeList[i];
removeNodeListId(n, nodeListId);
}
// swap in new nodes into the nodeLIst
nodeList.splice.apply(nodeList, [startIndex, endIndex - startIndex + 1].concat(newNodes));
// tell these new nodes they belong to the nodeList
can.each(newNodes, function(node) {
addNodeListId(node, nodeListId);
});
} else {
nodeLists.unregister(nodeList);
}
});
//});
},
// registers a list of nodes
register: function(nodeList) {
var nLId = id(nodeList);
nodeListMap[nLId] = nodeList;
can.each(nodeList, function(node) {
addNodeListId(node, nLId);
});
},
// removes mappings
unregister: function(nodeList) {
var nLId = id(nodeList);
can.each(nodeList, function(node) {
removeNodeListId(node, nLId);
});
delete nodeListMap[nLId];
},
nodeMap: nodeMap,
nodeListMap: nodeListMap
}
var ids = function(nodeList) {
return nodeList.map(function(n) {
return id(n) + ":" + (n.innerHTML || n.nodeValue)
})
}
return nodeLists;
})(__m3);
// ## can/view/live.js
var __m16 = (function(can, elements, view, nodeLists) {
// ## live.js
// The live module provides live binding for computes
// and can.Observe.List.
// Currently, it's API is designed for `can/view/render`, but
// it could easily be used for other purposes.
// ### Helper methods
// #### setup
// `setup(HTMLElement, bind(data), unbind(data)) -> data`
// Calls bind right away, but will call unbind
// if the element is "destroyed" (removed from the DOM).
var setup = function(el, bind, unbind) {
var teardown = function() {
unbind(data)
can.unbind.call(el, 'destroyed', teardown);
},
data = {
teardownCheck: function(parent) {
if (!parent) {
teardown();
}
}
}
can.bind.call(el, 'destroyed', teardown);
bind(data)
return data;
},
// #### listen
// Calls setup, but presets bind and unbind to
// operate on a compute
listen = function(el, compute, change) {
return setup(el, function() {
compute.bind("change", change);
}, function(data) {
compute.unbind("change", change);
if (data.nodeList) {
nodeLists.unregister(data.nodeList);
}
});
},
// #### getAttributeParts
// Breaks up a string like foo='bar' into ["foo","'bar'""]
getAttributeParts = function(newVal) {
return (newVal || "").replace(/['"]/g, '').split('=')
}
// #### insertElementsAfter
// Appends elements after the last item in oldElements.
insertElementsAfter = function(oldElements, newFrag) {
var last = oldElements[oldElements.length - 1];
// Insert it in the `document` or `documentFragment`
if (last.nextSibling) {
last.parentNode.insertBefore(newFrag, last.nextSibling);
} else {
last.parentNode.appendChild(newFrag);
}
};
var live = {
nodeLists: nodeLists,
list: function(el, list, func, context, parentNode) {
// A mapping of the index to an array
// of elements that represent the item.
// Each array is registered so child or parent
// live structures can update the elements
var nodesMap = [],
add = function(ev, items, index) {
// Collect new html and mappings
var frag = document.createDocumentFragment(),
newMappings = [];
can.each(items, function(item) {
var itemHTML = func.call(context, item),
itemFrag = can.view.frag(itemHTML, parentNode);
newMappings.push(can.makeArray(itemFrag.childNodes));
frag.appendChild(itemFrag);
})
// Inserting at the end of the list
if (!nodesMap[index]) {
insertElementsAfter(
index == 0 ? [text] :
nodesMap[index - 1], frag)
} else {
var el = nodesMap[index][0];
el.parentNode.insertBefore(frag, el)
}
// register each item
can.each(newMappings, function(nodeList) {
nodeLists.register(nodeList)
});
[].splice.apply(nodesMap, [index, 0].concat(newMappings));
},
remove = function(ev, items, index) {
var removedMappings = nodesMap.splice(index, items.length),
itemsToRemove = [];
can.each(removedMappings, function(nodeList) {
// add items that we will remove all at once
[].push.apply(itemsToRemove, nodeList)
// Update any parent lists to remove these items
nodeLists.replace(nodeList, []);
// unregister the list
nodeLists.unregister(nodeList);
});
can.remove(can.$(itemsToRemove));
},
parentNode = elements.getParentNode(el, parentNode),
text = document.createTextNode("");
// Setup binding and teardown to add and remove events
setup(parentNode, function() {
list.bind("add", add).bind("remove", remove)
}, function() {
list.unbind("add", add).unbind("remove", remove);
can.each(nodesMap, function(nodeList) {
nodeLists.unregister(nodeList);
})
})
insertElementsAfter([el], text);
can.remove(can.$(el));
add({}, list, 0);
},
html: function(el, compute, parentNode) {
var parentNode = elements.getParentNode(el, parentNode),
data = listen(parentNode, compute, function(ev, newVal, oldVal) {
var attached = nodes[0].parentNode;
// update the nodes in the DOM with the new rendered value
if (attached) {
makeAndPut(newVal);
}
data.teardownCheck(nodes[0].parentNode);
});
var nodes,
makeAndPut = function(val) {
// create the fragment, but don't hook it up
// we need to insert it into the document first
var frag = can.view.frag(val, parentNode),
// keep a reference to each node
newNodes = can.makeArray(frag.childNodes);
// Insert it in the `document` or `documentFragment`
insertElementsAfter(nodes || [el], frag)
// nodes hasn't been set yet
if (!nodes) {
can.remove(can.$(el));
nodes = newNodes;
// set the teardown nodeList
data.nodeList = nodes;
nodeLists.register(nodes);
} else {
// Update node Array's to point to new nodes
// and then remove the old nodes.
// It has to be in this order for Mootools
// and IE because somehow, after an element
// is removed from the DOM, it loses its
// expando values.
var nodesToRemove = can.makeArray(nodes);
nodeLists.replace(nodes, newNodes);
can.remove(can.$(nodesToRemove));
}
};
makeAndPut(compute(), [el]);
},
text: function(el, compute, parentNode) {
var parent = elements.getParentNode(el, parentNode);
// setup listening right away so we don't have to re-calculate value
var data = listen(el.parentNode !== parent ? el.parentNode : parent, compute, function(ev, newVal, oldVal) {
// Sometimes this is 'unknown' in IE and will throw an exception if it is
if (typeof node.nodeValue != 'unknown') {
node.nodeValue = "" + newVal;
}
data.teardownCheck(node.parentNode);
});
var node = document.createTextNode(compute());
if (el.parentNode !== parent) {
parent = el.parentNode;
parent.insertBefore(node, el);
parent.removeChild(el);
} else {
parent.insertBefore(node, el);
parent.removeChild(el);
}
},
attributes: function(el, compute, currentValue) {
var setAttrs = function(newVal) {
var parts = getAttributeParts(newVal),
newAttrName = parts.shift();
// Remove if we have a change and used to have an `attrName`.
if ((newAttrName != attrName) && attrName) {
elements.removeAttr(el, attrName);
}
// Set if we have a new `attrName`.
if (newAttrName) {
elements.setAttr(el, newAttrName, parts.join('='));
attrName = newAttrName;
}
}
listen(el, compute, function(ev, newVal) {
setAttrs(newVal)
})
// current value has been set
if (arguments.length >= 3) {
var attrName = getAttributeParts(currentValue)[0]
} else {
setAttrs(compute())
}
},
attributePlaceholder: '__!!__',
attributeReplace: /__!!__/g,
attribute: function(el, attributeName, compute) {
listen(el, compute, function(ev, newVal) {
elements.setAttr(el, attributeName, hook.render());
})
var wrapped = can.$(el),
hooks;
// Get the list of hookups or create one for this element.
// Hooks is a map of attribute names to hookup `data`s.
// Each hookup data has:
// `render` - A `function` to render the value of the attribute.
// `funcs` - A list of hookup `function`s on that attribute.
// `batchNum` - The last event `batchNum`, used for performance.
hooks = can.data(wrapped, 'hooks');
if (!hooks) {
can.data(wrapped, 'hooks', hooks = {});
}
// Get the attribute value.
var attr = elements.getAttr(el, attributeName),
// Split the attribute value by the template.
// Only split out the first __!!__ so if we have multiple hookups in the same attribute,
// they will be put in the right spot on first render
parts = attr.split(live.attributePlaceholder),
goodParts = [],
hook;
goodParts.push(parts.shift(),
parts.join(live.attributePlaceholder));
// If we already had a hookup for this attribute...
if (hooks[attributeName]) {
// Just add to that attribute's list of `function`s.
hooks[attributeName].computes.push(compute);
} else {
// Create the hookup data.
hooks[attributeName] = {
render: function() {
var i = 0,
// attr doesn't have a value in IE
newAttr = attr ? attr.replace(live.attributeReplace, function() {
return elements.contentText(hook.computes[i++]());
}) : elements.contentText(hook.computes[i++]());
return newAttr;
},
computes: [compute],
batchNum: undefined
};
}
// Save the hook for slightly faster performance.
hook = hooks[attributeName];
// Insert the value in parts.
goodParts.splice(1, 0, compute());
// Set the attribute.
elements.setAttr(el, attributeName, goodParts.join(""));
}
}
return live;
})(__m3, __m14, __m11, __m17);
// ## can/view/render.js
var __m15 = (function(can, elements, live) {
var pendingHookups = [],
tagChildren = function(tagName) {
var newTag = elements.tagMap[tagName] || "span";
if (newTag === "span") {
//innerHTML in IE doesn't honor leading whitespace after empty elements
return "@@!!@@";
}
return "<" + newTag + ">" + tagChildren(newTag) + "</" + newTag + ">";
},
contentText = function(input, tag) {
// If it's a string, return.
if (typeof input == 'string') {
return input;
}
// If has no value, return an empty string.
if (!input && input !== 0) {
return '';
}
// If it's an object, and it has a hookup method.
var hook = (input.hookup &&
// Make a function call the hookup method.
function(el, id) {
input.hookup.call(input, el, id);
}) ||
// Or if it's a `function`, just use the input.
(typeof input == 'function' && input);
// Finally, if there is a `function` to hookup on some dom,
// add it to pending hookups.
if (hook) {
if (tag) {
return "<" + tag + " " + can.view.hook(hook) + "></" + tag + ">"
} else {
pendingHookups.push(hook);
}
return '';
}
// Finally, if all else is `false`, `toString()` it.
return "" + input;
},
// Returns escaped/sanatized content for anything other than a live-binding
contentEscape = function(txt) {
return (typeof txt == 'string' || typeof txt == 'number') ?
can.esc(txt) :
contentText(txt);
};
var current;
can.extend(can.view, {
live: live,
setupLists: function() {
var old = can.view.lists,
data;
can.view.lists = function(list, renderer) {
data = {
list: list,
renderer: renderer
}
}
return function() {
can.view.lists = old;
return data;
}
},
pending: function() {
// TODO, make this only run for the right tagName
var hooks = pendingHookups.slice(0);
lastHookups = hooks;
pendingHookups = [];
return can.view.hook(function(el) {
can.each(hooks, function(fn) {
fn(el);
});
});
},
txt: function(escape, tagName, status, self, func) {
var listTeardown = can.view.setupLists(),
emptyHandler = function() {},
unbind = function() {
compute.unbind("change", emptyHandler)
};
var compute = can.compute(func, self, false);
// bind to get and temporarily cache the value
compute.bind("change", emptyHandler);
// call the "wrapping" function and get the binding information
var tag = (elements.tagMap[tagName] || "span"),
listData = listTeardown(),
value = compute();
if (listData) {
return "<" + tag + can.view.hook(function(el, parentNode) {
live.list(el, listData.list, listData.renderer, self, parentNode);
}) + "></" + tag + ">";
}
// If we had no observes just return the value returned by func.
if (!compute.hasDependencies) {
unbind();
return (escape || status !== 0 ? contentEscape : contentText)(value, status === 0 && tag);
}
// the property (instead of innerHTML elements) to adjust. For
// example options should use textContent
var contentProp = elements.tagToContentPropMap[tagName];
// The magic tag is outside or between tags.
if (status === 0 && !contentProp) {
// Return an element tag with a hookup in place of the content
return "<" + tag + can.view.hook(
escape ?
// If we are escaping, replace the parentNode with
// a text node who's value is `func`'s return value.
function(el, parentNode) {
live.text(el, compute, parentNode);
unbind();
} :
// If we are not escaping, replace the parentNode with a
// documentFragment created as with `func`'s return value.
function(el, parentNode) {
live.html(el, compute, parentNode);
unbind();
//children have to be properly nested HTML for buildFragment to work properly
}) + ">" + tagChildren(tag) + "</" + tag + ">";
// In a tag, but not in an attribute
} else if (status === 1) {
// remember the old attr name
pendingHookups.push(function(el) {
live.attributes(el, compute, compute());
unbind();
});
return compute();
} else { // In an attribute...
var attributeName = status === 0 ? contentProp : status;
// if the magic tag is inside the element, like `<option><% TAG %></option>`,
// we add this hookup to the last element (ex: `option`'s) hookups.
// Otherwise, the magic tag is in an attribute, just add to the current element's
// hookups.
(status === 0 ? lastHookups : pendingHookups).push(function(el) {
live.attribute(el, attributeName, compute);
unbind();
});
return live.attributePlaceholder;
}
}
});
return can;
})(__m11, __m14, __m16, __m2);
// ## can/view/ejs/ejs.js
var __m12 = (function(can) {
// ## ejs.js
// `can.EJS`
// _Embedded JavaScript Templates._
// Helper methods.
var extend = can.extend,
EJS = function(options) {
// Supports calling EJS without the constructor
// This returns a function that renders the template.
if (this.constructor != EJS) {
var ejs = new EJS(options);
return function(data, helpers) {
return ejs.render(data, helpers);
};
}
// If we get a `function` directly, it probably is coming from
// a `steal`-packaged view.
if (typeof options == "function") {
this.template = {
fn: options
};
return;
}
// Set options on self.
extend(this, options);
this.template = this.scanner.scan(this.text, this.name);
};
can.EJS = EJS;
EJS.prototype.
render = function(object, extraHelpers) {
object = object || {};
return this.template.fn.call(object, object, new EJS.Helpers(object, extraHelpers || {}));
};
extend(EJS.prototype, {
scanner: new can.view.Scanner({
tokens: [
["templateLeft", "<%%"], // Template
["templateRight", "%>"], // Right Template
["returnLeft", "<%=="], // Return Unescaped
["escapeLeft", "<%="], // Return Escaped
["commentLeft", "<%#"], // Comment
["left", "<%"], // Run --- this is hack for now
["right", "%>"], // Right -> All have same FOR Mustache ...
["returnRight", "%>"]
],
transform: function(source) {
return source.replace(/<%([\s\S]+?)%>/gm, function(whole, part) {
var brackets = [],
foundBracketPair,
i;
// Look for brackets (for removing self-contained blocks)
part.replace(/[{}]/gm, function(bracket, offset) {
brackets.push([bracket, offset]);
});
// Remove bracket pairs from the list of replacements
do {
foundBracketPair = false;
for (i = brackets.length - 2; i >= 0; i--) {
if (brackets[i][0] == '{' && brackets[i + 1][0] == '}') {
brackets.splice(i, 2);
foundBracketPair = true;
break;
}
}
} while (foundBracketPair);
// Unmatched brackets found, inject EJS tags
if (brackets.length >= 2) {
var result = ['<%'],
bracket,
last = 0;
for (i = 0; bracket = brackets[i]; i++) {
result.push(part.substring(last, last = bracket[1]));
if ((bracket[0] == '{' && i < brackets.length - 1) || (bracket[0] == '}' && i > 0)) {
result.push(bracket[0] == '{' ? '{ %><% ' : ' %><% }');
} else {
result.push(bracket[0]);
}
++last;
}
result.push(part.substring(last), '%>');
return result.join('');
}
// Otherwise return the original
else {
return '<%' + part + '%>';
}
});
}
})
});
EJS.Helpers = function(data, extras) {
this._data = data;
this._extras = extras;
extend(this, extras);
};
EJS.Helpers.prototype = {
// TODO Deprecated!!
list: function(list, cb) {
can.each(list, function(item, i) {
cb(item, i, list)
})
},
each: function(list, cb) {
// Normal arrays don't get live updated
if (can.isArray(list)) {
this.list(list, cb);
} else {
can.view.lists(list, cb);
}
}
};
// Options for `steal`'s build.
can.view.register({
suffix: "ejs",
// returns a `function` that renders the view.
script: function(id, src) {
return "can.EJS(function(_CONTEXT,_VIEW) { " + new EJS({
text: src,
name: id
}).template.out + " })";
},
renderer: function(id, text) {
return EJS({
text: text,
name: id
});
}
});
return can;
})(__m3, __m11, __m2, __m9, __m13, __m15);
// ## can/control/control.js
var __m18 = (function(can) {
// ## 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();
// Gets 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() {
//Control already destroyed
if (this.element === null) {
return;
}
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;
});
return Control;
})(__m3, __m1);
// ## can/util/string/deparam/deparam.js
var __m20 = (function(can) {
// ## 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;
}
});
return can;
})(__m3, __m2);
// ## can/route/route.js
var __m19 = (function(can) {
// ## 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 "<a " + makeProps(
extend({
href: can.route.url(options, merge)
}, props)) + ">" + name + "</a>";
},
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() {
// `delegate` and `undelegate` require
// the `can/observe/delegate` plugin
if (!can.route.data[name]) {
return;
}
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;
return can.route;
})(__m3, __m7, __m20);
// ## can/control/route/route.js
var __m21 = (function(can) {
// ## 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);
};
};
return can;
})(__m3, __m19, __m18);
// ## can/util/object/object.js
var __m23 = (function(can) {
var isArray = can.isArray,
// essentially returns an object that has all the must have comparisons ...
// must haves, do not return true when provided undefined
cleanSet = function(obj, compares) {
var copy = can.extend({}, obj);
for (var prop in copy) {
var compare = compares[prop] === undefined ? compares["*"] : compares[prop];
if (same(copy[prop], undefined, compare)) {
delete copy[prop]
}
}
return copy;
},
propCount = function(obj) {
var count = 0;
for (var prop in obj) count++;
return count;
};
can.Object = {};
var same = can.Object.same = function(a, b, compares, aParent, bParent, deep) {
var aType = typeof a,
aArray = isArray(a),
comparesType = typeof compares,
compare;
if (comparesType == 'string' || compares === null) {
compares = compareMethods[compares];
comparesType = 'function'
}
if (comparesType == 'function') {
return compares(a, b, aParent, bParent)
}
compares = compares || {};
if (a instanceof Date) {
return a === b;
}
if (deep === -1) {
return aType === 'object' || a === b;
}
if (aType !== typeof b || aArray !== isArray(b)) {
return false;
}
if (a === b) {
return true;
}
if (aArray) {
if (a.length !== b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
compare = compares[i] === undefined ? compares["*"] : compares[i]
if (!same(a[i], b[i], a, b, compare)) {
return false;
}
};
return true;
} else if (aType === "object" || aType === 'function') {
var bCopy = can.extend({}, b);
for (var prop in a) {
compare = compares[prop] === undefined ? compares["*"] : compares[prop];
if (!same(a[prop], b[prop], compare, a, b, deep === false ? -1 : undefined)) {
return false;
}
delete bCopy[prop];
}
// go through bCopy props ... if there is no compare .. return false
for (prop in bCopy) {
if (compares[prop] === undefined || !same(undefined, b[prop], compares[prop], a, b, deep === false ? -1 : undefined)) {
return false;
}
}
return true;
}
return false;
};
can.Object.subsets = function(checkSet, sets, compares) {
var len = sets.length,
subsets = [],
checkPropCount = propCount(checkSet),
setLength;
for (var i = 0; i < len; i++) {
//check this subset
var set = sets[i];
if (can.Object.subset(checkSet, set, compares)) {
subsets.push(set)
}
}
return subsets;
};
can.Object.subset = function(subset, set, compares) {
// go through set {type: 'folder'} and make sure every property
// is in subset {type: 'folder', parentId :5}
// then make sure that set has fewer properties
// make sure we are only checking 'important' properties
// in subset (ones that have to have a value)
var setPropCount = 0,
compares = compares || {};
for (var prop in set) {
if (!same(subset[prop], set[prop], compares[prop], subset, set)) {
return false;
}
}
return true;
}
var compareMethods = {
"null": function() {
return true;
},
i: function(a, b) {
return ("" + a).toLowerCase() == ("" + b).toLowerCase()
}
}
return can.Object;
})(__m3);
// ## can/observe/backup/backup.js
var __m22 = (function(can) {
var flatProps = function(a) {
var obj = {};
for (var prop in a) {
if (typeof a[prop] !== 'object' || a[prop] === null || a[prop] instanceof Date) {
obj[prop] = a[prop]
}
}
return obj;
};
can.extend(can.Observe.prototype, {
backup: function() {
this._backupStore = this._attrs();
return this;
},
isDirty: function(checkAssociations) {
return this._backupStore && !can.Object.same(this._attrs(),
this._backupStore,
undefined,
undefined,
undefined, !! checkAssociations);
},
restore: function(restoreAssociations) {
var props = restoreAssociations ? this._backupStore : flatProps(this._backupStore)
if (this.isDirty(restoreAssociations)) {
this._attrs(props);
}
return this;
}
})
return can.Observe;
})(__m3, __m7, __m23);
window['can'] = __m5;
})();