mirror of
https://github.com/danbee/mpd-client
synced 2025-03-04 08:39:09 +00:00
7078 lines
300 KiB
JavaScript
Executable File
7078 lines
300 KiB
JavaScript
Executable File
/*!
|
|
* CanJS - 2.0.4
|
|
* http://canjs.us/
|
|
* Copyright (c) 2013 Bitovi
|
|
* Mon, 23 Dec 2013 19:49:25 GMT
|
|
* Licensed MIT
|
|
* Includes: can/component,can/construct,can/map,can/list,can/observe,can/compute,can/model,can/view,can/control,can/route,can/control/route,can/view/mustache,can/view/bindings,can/view/live,can/view/scope,can/util/string
|
|
* Download from: http://canjs.com
|
|
*/
|
|
(function(undefined) {
|
|
|
|
// ## util/can.js
|
|
var __m4 = (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 = '2.0.4';
|
|
|
|
can.simpleExtend = function(d, s) {
|
|
for (var prop in s) {
|
|
d[prop] = s[prop]
|
|
}
|
|
return d;
|
|
}
|
|
|
|
return can;
|
|
})();
|
|
|
|
// ## util/array/each.js
|
|
var __m5 = (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) {
|
|
if (can.Map && elements instanceof can.Map) {
|
|
can.__reading && can.__reading(elements, '__keys');
|
|
elements = elements.__get()
|
|
}
|
|
|
|
|
|
for (key in elements) {
|
|
if (elements.hasOwnProperty(key)) {
|
|
if (callback.call(context || elements[key], elements[key], key, elements) === false) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return elements;
|
|
};
|
|
|
|
return can;
|
|
})(__m4);
|
|
|
|
// ## util/inserted/inserted.js
|
|
var __m6 = (function(can) {
|
|
// Given a list of elements, check if they are in the dom, if they
|
|
// are in the dom, trigger inserted on them.
|
|
can.inserted = function(elems) {
|
|
// prevent mutations from changing the looping
|
|
elems = can.makeArray(elems);
|
|
var inDocument = false,
|
|
checked = false,
|
|
children;
|
|
for (var i = 0, elem;
|
|
(elem = elems[i]) !== undefined; i++) {
|
|
if (!inDocument) {
|
|
if (elem.getElementsByTagName) {
|
|
if (can.has(can.$(document), elem).length) {
|
|
inDocument = true;
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (inDocument && elem.getElementsByTagName) {
|
|
children = can.makeArray(elem.getElementsByTagName("*"));
|
|
can.trigger(elem, "inserted", [], false);
|
|
for (var j = 0, child;
|
|
(child = children[j]) !== undefined; j++) {
|
|
// Trigger the destroyed event
|
|
can.trigger(child, "inserted", [], false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
can.appendChild = function(el, child) {
|
|
if (child.nodeType === 11) {
|
|
var children = can.makeArray(child.childNodes);
|
|
} else {
|
|
var children = [child]
|
|
}
|
|
el.appendChild(child);
|
|
can.inserted(children)
|
|
}
|
|
can.insertBefore = function(el, child, ref) {
|
|
if (child.nodeType === 11) {
|
|
var children = can.makeArray(child.childNodes);
|
|
} else {
|
|
var children = [child];
|
|
}
|
|
el.insertBefore(child, ref);
|
|
can.inserted(children)
|
|
}
|
|
|
|
})(__m4);
|
|
|
|
// ## util/event.js
|
|
var __m7 = (function(can) {
|
|
|
|
// event.js
|
|
// ---------
|
|
// _Basic event wrapper._
|
|
can.addEvent = function(event, fn) {
|
|
var allEvents = this.__bindEvents || (this.__bindEvents = {}),
|
|
eventList = allEvents[event] || (allEvents[event] = []);
|
|
|
|
eventList.push({
|
|
handler: fn,
|
|
name: event
|
|
});
|
|
return this;
|
|
};
|
|
|
|
// can.listenTo works without knowing how bind works
|
|
// the API was heavily influenced by BackboneJS:
|
|
// http://backbonejs.org/
|
|
can.listenTo = function(other, event, handler) {
|
|
|
|
|
|
var idedEvents = this.__listenToEvents;
|
|
if (!idedEvents) {
|
|
idedEvents = this.__listenToEvents = {};
|
|
}
|
|
var otherId = can.cid(other);
|
|
var othersEvents = idedEvents[otherId];
|
|
if (!othersEvents) {
|
|
othersEvents = idedEvents[otherId] = {
|
|
obj: other,
|
|
events: {}
|
|
};
|
|
}
|
|
var eventsEvents = othersEvents.events[event]
|
|
if (!eventsEvents) {
|
|
eventsEvents = othersEvents.events[event] = []
|
|
}
|
|
eventsEvents.push(handler);
|
|
can.bind.call(other, event, handler);
|
|
}
|
|
|
|
can.stopListening = function(other, event, handler) {
|
|
var idedEvents = this.__listenToEvents,
|
|
iterIdedEvents = idedEvents,
|
|
i = 0;
|
|
if (!idedEvents) {
|
|
return this;
|
|
}
|
|
if (other) {
|
|
var othercid = can.cid(other);
|
|
(iterIdedEvents = {})[othercid] = idedEvents[othercid];
|
|
// you might be trying to listen to something that is not there
|
|
if (!idedEvents[othercid]) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
|
|
for (var cid in iterIdedEvents) {
|
|
var othersEvents = iterIdedEvents[cid],
|
|
eventsEvents;
|
|
other = idedEvents[cid].obj;
|
|
if (!event) {
|
|
eventsEvents = othersEvents.events;
|
|
} else {
|
|
(eventsEvents = {})[event] = othersEvents.events[event]
|
|
}
|
|
for (var eventName in eventsEvents) {
|
|
var handlers = eventsEvents[eventName] || [];
|
|
i = 0;
|
|
while (i < handlers.length) {
|
|
if ((handler && handler === handlers[i]) || (!handler)) {
|
|
can.unbind.call(other, eventName, handlers[i])
|
|
handlers.splice(i, 1);
|
|
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
// no more handlers?
|
|
if (!handlers.length) {
|
|
delete othersEvents.events[eventName]
|
|
}
|
|
}
|
|
if (can.isEmptyObject(othersEvents.events)) {
|
|
delete idedEvents[cid]
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
can.removeEvent = function(event, fn) {
|
|
if (!this.__bindEvents) {
|
|
return this;
|
|
}
|
|
|
|
var events = this.__bindEvents[event] || [],
|
|
i = 0,
|
|
ev,
|
|
isFunction = typeof fn == 'function';
|
|
|
|
while (i < events.length) {
|
|
ev = events[i]
|
|
if ((isFunction && ev.handler === fn) || (!isFunction && ev.cid === fn)) {
|
|
events.splice(i, 1);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
return this;
|
|
};
|
|
|
|
can.dispatch = function(event, args) {
|
|
if (!this.__bindEvents) {
|
|
return;
|
|
}
|
|
if (typeof event == "string") {
|
|
event = {
|
|
type: event
|
|
}
|
|
}
|
|
var eventName = event.type,
|
|
handlers = (this.__bindEvents[eventName] || []).slice(0),
|
|
args = [event].concat(args || []),
|
|
ev;
|
|
|
|
for (var i = 0, len = handlers.length; i < len; i++) {
|
|
ev = handlers[i];
|
|
ev.handler.apply(this, args);
|
|
}
|
|
}
|
|
|
|
return can;
|
|
|
|
})(__m4);
|
|
|
|
// ## util/jquery/jquery.js
|
|
var __m2 = (function($, can) {
|
|
var isBindableElement = function(node) {
|
|
//console.log((node.nodeName && (node.nodeType == 1 || node.nodeType == 9) || node === window))
|
|
return (node.nodeName && (node.nodeType == 1 || node.nodeType == 9) || node == window);
|
|
};
|
|
|
|
// _jQuery node list._
|
|
$.extend(can, $, {
|
|
trigger: function(obj, event, args) {
|
|
if (obj.nodeName || obj === window) {
|
|
$.event.trigger(event, args, obj, true);
|
|
} else if (obj.trigger) {
|
|
obj.trigger(event, args);
|
|
} else {
|
|
if (typeof event === 'string') {
|
|
event = {
|
|
type: event
|
|
}
|
|
}
|
|
event.target = event.target || obj;
|
|
can.dispatch.call(obj, event, args);
|
|
}
|
|
},
|
|
addEvent: can.addEvent,
|
|
removeEvent: can.removeEvent,
|
|
// 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,
|
|
bind: function(ev, cb) {
|
|
// If we can bind to it...
|
|
if (this.bind && this.bind !== can.bind) {
|
|
this.bind(ev, cb)
|
|
} else if (isBindableElement(this)) {
|
|
$.event.add(this, ev, cb);
|
|
} else {
|
|
// Make it bind-able...
|
|
can.addEvent.call(this, ev, cb)
|
|
}
|
|
return this;
|
|
},
|
|
unbind: function(ev, cb) {
|
|
// If we can bind to it...
|
|
if (this.unbind && this.unbind !== can.unbind) {
|
|
this.unbind(ev, cb)
|
|
} else if (isBindableElement(this)) {
|
|
$.event.remove(this, ev, cb);
|
|
} else {
|
|
// Make it bind-able...
|
|
can.removeEvent.call(this, ev, cb)
|
|
}
|
|
return this;
|
|
},
|
|
delegate: function(selector, ev, cb) {
|
|
if (this.delegate) {
|
|
this.delegate(selector, ev, cb)
|
|
} else if (isBindableElement(this)) {
|
|
$(this).delegate(selector, ev, cb)
|
|
} else {
|
|
// make it bind-able ...
|
|
}
|
|
return this;
|
|
},
|
|
undelegate: function(selector, ev, cb) {
|
|
if (this.undelegate) {
|
|
this.undelegate(selector, ev, cb)
|
|
} else if (isBindableElement(this)) {
|
|
$(this).undelegate(selector, ev, cb)
|
|
} else {
|
|
// make it bind-able ...
|
|
|
|
}
|
|
return this;
|
|
},
|
|
proxy: function(fn, context) {
|
|
return function() {
|
|
return fn.apply(context, arguments)
|
|
}
|
|
}
|
|
});
|
|
|
|
// Wrap binding functions.
|
|
|
|
|
|
// Aliases
|
|
can.on = can.bind;
|
|
can.off = can.unbind;
|
|
|
|
// Wrap modifier functions.
|
|
$.each(["append", "filter", "addClass", "remove", "data", "get", "has"], 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, "removed", [], false);
|
|
}
|
|
});
|
|
oldClean(elems);
|
|
};
|
|
|
|
var oldDomManip = $.fn.domManip,
|
|
cbIndex;
|
|
|
|
// feature detect which domManip we are using
|
|
$.fn.domManip = function(args, cb1, cb2) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
if (typeof arguments[i] === "function") {
|
|
cbIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
return oldDomManip.apply(this, arguments)
|
|
}
|
|
$(document.createElement("div")).append(document.createElement("div"))
|
|
|
|
$.fn.domManip = (cbIndex == 2 ? function(args, table, callback) {
|
|
return oldDomManip.call(this, args, table, function(elem) {
|
|
if (elem.nodeType === 11) {
|
|
var elems = can.makeArray(elem.childNodes);
|
|
}
|
|
var ret = callback.apply(this, arguments);
|
|
can.inserted(elems ? elems : [elem]);
|
|
return ret;
|
|
})
|
|
} : function(args, callback) {
|
|
return oldDomManip.call(this, args, function(elem) {
|
|
if (elem.nodeType === 11) {
|
|
var elems = can.makeArray(elem.childNodes);
|
|
}
|
|
var ret = callback.apply(this, arguments);
|
|
can.inserted(elems ? elems : [elem]);
|
|
return ret;
|
|
})
|
|
})
|
|
|
|
$.event.special.inserted = {};
|
|
$.event.special.removed = {};
|
|
|
|
return can;
|
|
})(jQuery, __m4, __m5, __m6, __m7);
|
|
|
|
// ## util/string/string.js
|
|
var __m10 = (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,
|
|
strHyphenMatch = /-+(.)?/g,
|
|
strCamelMatch = /[a-z][A-Z]/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);
|
|
},
|
|
convertBadValues = function(content) {
|
|
// Convert bad values into empty strings
|
|
var isInvalid = content === null || content === undefined || (isNaN(content) && ("" + content === 'NaN'));
|
|
return ("" + (isInvalid ? '' : content))
|
|
};
|
|
|
|
can.extend(can, {
|
|
// Escapes strings for HTML.
|
|
esc: function(content) {
|
|
return convertBadValues(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);
|
|
},
|
|
|
|
|
|
camelize: function(str) {
|
|
return convertBadValues(str).replace(strHyphenMatch, function(match, chr) {
|
|
return chr ? chr.toUpperCase() : ''
|
|
})
|
|
},
|
|
|
|
|
|
hyphenate: function(str) {
|
|
return convertBadValues(str).replace(strCamelMatch, function(str, offset) {
|
|
return str.charAt(0) + '-' + str.charAt(1).toLowerCase();
|
|
});
|
|
},
|
|
|
|
// 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 || ob === null) {
|
|
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;
|
|
})(__m2);
|
|
|
|
// ## construct/construct.js
|
|
var __m9 = (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, {
|
|
|
|
constructorExtends: true,
|
|
|
|
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 && Constructor.constructorExtends ?
|
|
// We are being called without `new` or we are extending.
|
|
arguments.callee.extend.apply(arguments.callee, arguments) :
|
|
// We are being called with `new`.
|
|
Constructor.newInstance.apply(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;
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
can.Construct.prototype.setup = function() {};
|
|
|
|
can.Construct.prototype.init = function() {};
|
|
|
|
|
|
return can.Construct;
|
|
})(__m10);
|
|
|
|
// ## control/control.js
|
|
var __m8 = (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, this._lookup(options)) : 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
|
|
};
|
|
}
|
|
},
|
|
_lookup: function(options) {
|
|
return [options, window]
|
|
},
|
|
// 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, "removed", destroyCB);
|
|
bindings.push(function(el) {
|
|
can.unbind.call(el, "removed", 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;
|
|
})(__m2, __m9);
|
|
|
|
// ## util/bind/bind.js
|
|
var __m13 = (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);
|
|
|
|
if (this._bindings == null) {
|
|
this._bindings = 0;
|
|
} else {
|
|
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;
|
|
|
|
})(__m2);
|
|
|
|
// ## util/batch/batch.js
|
|
var __m14 = (function(can) {
|
|
|
|
// Which batch of events this is for -- might not want to send multiple
|
|
// messages on the same batch. This is mostly for event delegation.
|
|
var batchNum = 1,
|
|
// how many times has start been called without a stop
|
|
transactions = 0,
|
|
// an array of events within a transaction
|
|
batchEvents = [],
|
|
stopCallbacks = [];
|
|
|
|
|
|
can.batch = {
|
|
|
|
start: function(batchStopHandler) {
|
|
transactions++;
|
|
batchStopHandler && stopCallbacks.push(batchStopHandler);
|
|
},
|
|
|
|
stop: 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 && can.batch.start();
|
|
can.each(items, function(args) {
|
|
can.trigger.apply(can, args);
|
|
});
|
|
can.each(callbacks, function(cb) {
|
|
cb();
|
|
});
|
|
}
|
|
},
|
|
|
|
trigger: 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
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
})(__m4);
|
|
|
|
// ## map/map.js
|
|
var __m12 = (function(can, bind) {
|
|
// ## map.js
|
|
// `can.Map`
|
|
// _Provides the observable pattern for JavaScript Objects._
|
|
// Removes all listeners.
|
|
var bindToChildAndBubbleToParent = function(child, prop, parent) {
|
|
can.listenTo.call(parent, child, "change", 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 map
|
|
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 map.
|
|
observeId = 0,
|
|
attrParts = function(attr, keepKey) {
|
|
if (keepKey) {
|
|
return [attr];
|
|
}
|
|
return can.isArray(attr) ? attr : ("" + attr).split(".");
|
|
},
|
|
makeBindSetup = function(wildcard) {
|
|
return function() {
|
|
var parent = this;
|
|
this._each(function(child, prop) {
|
|
if (child && child.bind) {
|
|
bindToChildAndBubbleToParent(child, wildcard || prop, parent)
|
|
}
|
|
})
|
|
};
|
|
},
|
|
// A map that temporarily houses a reference
|
|
// to maps that have already been made for a plain ole JS object
|
|
madeMap = null,
|
|
teardownMap = function() {
|
|
for (var cid in madeMap) {
|
|
if (madeMap[cid].added) {
|
|
delete madeMap[cid].obj._cid;
|
|
}
|
|
}
|
|
madeMap = null;
|
|
},
|
|
getMapFromObject = function(obj) {
|
|
return madeMap && madeMap[obj._cid] && madeMap[obj._cid].instance
|
|
};
|
|
|
|
|
|
var Map = can.Map = can.Construct.extend({
|
|
|
|
setup: function() {
|
|
|
|
can.Construct.setup.apply(this, arguments);
|
|
|
|
|
|
if (can.Map) {
|
|
if (!this.defaults) {
|
|
this.defaults = {};
|
|
}
|
|
// a list of the compute properties
|
|
this._computes = [];
|
|
for (var prop in this.prototype) {
|
|
if (typeof this.prototype[prop] !== "function") {
|
|
this.defaults[prop] = this.prototype[prop];
|
|
} else if (this.prototype[prop].isComputed) {
|
|
this._computes.push(prop)
|
|
}
|
|
}
|
|
}
|
|
// if we inerit from can.Map, but not can.List
|
|
if (can.List && !(this.prototype instanceof can.List)) {
|
|
this.List = Map.List({
|
|
Map: this
|
|
}, {});
|
|
}
|
|
|
|
},
|
|
_computes: [],
|
|
// keep so it can be overwritten
|
|
bind: can.bindAndSetup,
|
|
on: can.bindAndSetup,
|
|
unbind: can.unbindAndTeardown,
|
|
off: can.unbindAndTeardown,
|
|
id: "id",
|
|
helpers: {
|
|
addToMap: function(obj, instance) {
|
|
var teardown;
|
|
if (!madeMap) {
|
|
teardown = teardownMap;
|
|
madeMap = {}
|
|
}
|
|
// record if it has a Cid before we add one
|
|
var hasCid = obj._cid;
|
|
var cid = can.cid(obj);
|
|
|
|
// only update if there already isn't one
|
|
if (!madeMap[cid]) {
|
|
|
|
madeMap[cid] = {
|
|
obj: obj,
|
|
instance: instance,
|
|
added: !hasCid
|
|
}
|
|
}
|
|
return teardown;
|
|
},
|
|
|
|
canMakeObserve: function(obj) {
|
|
return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject(obj) || (obj instanceof can.Map));
|
|
},
|
|
unhookup: function(items, parent) {
|
|
return can.each(items, function(item) {
|
|
if (item && item.unbind) {
|
|
can.stopListening.call(parent, item, "change");
|
|
}
|
|
});
|
|
},
|
|
// 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 Map object constructor
|
|
// `list` - (optional) The observable list constructor
|
|
hookupBubble: function(child, prop, parent, Ob, List) {
|
|
Ob = Ob || Map;
|
|
List = List || Map.List;
|
|
|
|
// If it's an `array` make a list, otherwise a child.
|
|
if (child instanceof Map) {
|
|
// We have an `map` already...
|
|
// Make sure it is not listening to this already
|
|
// It's only listening if it has bindings already.
|
|
parent._bindings && Map.helpers.unhookup([child], parent);
|
|
} else if (can.isArray(child)) {
|
|
child = getMapFromObject(child) || new List(child);
|
|
} else {
|
|
child = getMapFromObject(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;
|
|
},
|
|
// A helper used to serialize an `Map` or `Map.List`.
|
|
// `map` - The observable.
|
|
// `how` - To serialize with `attr` or `serialize`.
|
|
// `where` - To put properties, in an `{}` or `[]`.
|
|
serialize: function(map, how, where) {
|
|
// Go through each property.
|
|
map.each(function(val, name) {
|
|
// If the value is an `object`, and has an `attrs` or `serialize` function.
|
|
where[name] = Map.helpers.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;
|
|
},
|
|
makeBindSetup: makeBindSetup
|
|
},
|
|
|
|
// starts collecting events
|
|
// takes a callback for after they are updated
|
|
// how could you hook into after ejs
|
|
|
|
keys: function(map) {
|
|
var keys = [];
|
|
can.__reading && can.__reading(map, '__keys');
|
|
for (var keyName in map._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, ".map");
|
|
// Sets all `attrs`.
|
|
this._init = 1;
|
|
this._setupComputes();
|
|
var teardownMapping = obj && can.Map.helpers.addToMap(obj, this);
|
|
|
|
var data = can.extend(can.extend(true, {}, this.constructor.defaults || {}), obj)
|
|
this.attr(data);
|
|
|
|
teardownMapping && teardownMapping()
|
|
|
|
this.bind('change', can.proxy(this._changes, this));
|
|
|
|
delete this._init;
|
|
},
|
|
|
|
_setupComputes: function() {
|
|
var computes = this.constructor._computes;
|
|
this._computedBindings = {};
|
|
for (var i = 0, len = computes.length, prop; i < len; i++) {
|
|
prop = computes[i];
|
|
this[prop] = this[prop].clone(this);
|
|
this._computedBindings[prop] = {
|
|
count: 0
|
|
}
|
|
}
|
|
},
|
|
_bindsetup: makeBindSetup(),
|
|
_bindteardown: function() {
|
|
var self = this;
|
|
this._each(function(child) {
|
|
Map.helpers.unhookup([child], self)
|
|
})
|
|
},
|
|
_changes: function(ev, attr, how, newVal, oldVal) {
|
|
can.batch.trigger(this, {
|
|
type: attr,
|
|
batchNum: ev.batchNum
|
|
}, [newVal, oldVal]);
|
|
},
|
|
_triggerChange: function(attr, how, newVal, oldVal) {
|
|
can.batch.trigger(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.
|
|
can.__reading && can.__reading(this, attr)
|
|
return this._get(attr)
|
|
} else {
|
|
// Otherwise we are setting.
|
|
this._set(attr, val);
|
|
return this;
|
|
}
|
|
},
|
|
|
|
each: function() {
|
|
can.__reading && can.__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 = can.List && this instanceof can.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
|
|
can.batch.trigger(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) {
|
|
if (attr) {
|
|
if (this[attr] && this[attr].isComputed && can.isFunction(this.constructor.prototype[attr])) {
|
|
return this[attr]()
|
|
} else {
|
|
return this._data[attr]
|
|
}
|
|
} else {
|
|
return 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 (Map.helpers.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.Map: 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.
|
|
Map.helpers.canMakeObserve(value) ?
|
|
|
|
// Hook it up to send event.
|
|
Map.helpers.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
|
|
|
|
can.batch.trigger(this, "__keys", undefined);
|
|
|
|
}
|
|
// `batchTrigger` the change event.
|
|
this._triggerChange(prop, changeType, value, current);
|
|
|
|
//can.batch.trigger(this, prop, [value, current]);
|
|
// If we can stop listening to our old value, do it.
|
|
current && Map.helpers.unhookup([current], this);
|
|
}
|
|
|
|
},
|
|
// Directly sets a property on this `object`.
|
|
___set: function(prop, val) {
|
|
|
|
if (this[prop] && this[prop].isComputed && can.isFunction(this.constructor.prototype[prop])) {
|
|
this[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 (!(can.isFunction(this.constructor.prototype[prop]))) {
|
|
this[prop] = val
|
|
}
|
|
},
|
|
|
|
|
|
bind: function(eventName, handler) {
|
|
var computedBinding = this._computedBindings && this._computedBindings[eventName]
|
|
if (computedBinding) {
|
|
if (!computedBinding.count) {
|
|
computedBinding.count = 1;
|
|
var self = this;
|
|
computedBinding.handler = function(ev, newVal, oldVal) {
|
|
can.batch.trigger(self, {
|
|
type: eventName,
|
|
batchNum: ev.batchNum
|
|
}, [newVal, oldVal])
|
|
}
|
|
this[eventName].bind("change", computedBinding.handler)
|
|
} else {
|
|
computedBinding.count++
|
|
}
|
|
|
|
}
|
|
return can.bindAndSetup.apply(this, arguments);
|
|
|
|
},
|
|
|
|
unbind: function(eventName, handler) {
|
|
var computedBinding = this._computedBindings && this._computedBindings[eventName]
|
|
if (computedBinding) {
|
|
if (computedBinding.count == 1) {
|
|
computedBinding.count = 0;
|
|
this[eventName].unbind("change", computedBinding.handler);
|
|
delete computedBinding.handler;
|
|
} else {
|
|
computedBinding.count++
|
|
}
|
|
|
|
}
|
|
return can.unbindAndTeardown.apply(this, arguments);
|
|
|
|
},
|
|
|
|
serialize: function() {
|
|
return can.Map.helpers.serialize(this, 'serialize', {});
|
|
},
|
|
|
|
_attrs: function(props, remove) {
|
|
|
|
if (props === undefined) {
|
|
return Map.helpers.serialize(this, 'attr', {})
|
|
}
|
|
|
|
props = can.simpleExtend({}, props);
|
|
var prop,
|
|
self = this,
|
|
newVal;
|
|
can.batch.start();
|
|
this.each(function(curVal, prop) {
|
|
// you can not have a _cid property!
|
|
if (prop === "_cid") {
|
|
return;
|
|
}
|
|
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.Map) {
|
|
self.__set(prop, newVal, curVal)
|
|
// if its an object, let attr merge
|
|
} else if (Map.helpers.canMakeObserve(curVal) && Map.helpers.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) {
|
|
if (prop !== "_cid") {
|
|
newVal = props[prop];
|
|
this._set(prop, newVal, true)
|
|
}
|
|
|
|
}
|
|
can.batch.stop()
|
|
return this;
|
|
},
|
|
|
|
|
|
compute: function(prop) {
|
|
if (can.isFunction(this.constructor.prototype[prop])) {
|
|
return can.compute(this[prop], this);
|
|
} else {
|
|
return can.compute(this, prop);
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
Map.prototype.on = Map.prototype.bind;
|
|
Map.prototype.off = Map.prototype.unbind;
|
|
|
|
return Map;
|
|
})(__m2, __m13, __m9, __m14);
|
|
|
|
// ## list/list.js
|
|
var __m15 = (function(can, Map) {
|
|
|
|
|
|
|
|
// Helpers for `observable` lists.
|
|
var splice = [].splice,
|
|
// test if splice works correctly
|
|
spliceRemovesProps = (function() {
|
|
// IE's splice doesn't remove properties
|
|
var obj = {
|
|
0: "a",
|
|
length: 1
|
|
};
|
|
splice.call(obj, 0, 1);
|
|
return !obj[0];
|
|
})(),
|
|
|
|
list = Map(
|
|
|
|
{
|
|
|
|
Map: Map
|
|
|
|
},
|
|
|
|
{
|
|
setup: function(instances, options) {
|
|
this.length = 0;
|
|
can.cid(this, ".map")
|
|
this._init = 1;
|
|
instances = instances || [];
|
|
|
|
|
|
if (can.isDeferred(instances)) {
|
|
this.replace(instances)
|
|
} else {
|
|
var teardownMapping = instances.length && can.Map.helpers.addToMap(instances, this);
|
|
this.push.apply(this, can.makeArray(instances || []));
|
|
}
|
|
|
|
teardownMapping && teardownMapping();
|
|
|
|
// this change needs to be ignored
|
|
this.bind('change', can.proxy(this._changes, this));
|
|
can.simpleExtend(this, options);
|
|
delete this._init;
|
|
},
|
|
_triggerChange: function(attr, how, newVal, oldVal) {
|
|
|
|
Map.prototype._triggerChange.apply(this, arguments)
|
|
// `batchTrigger` direct add and remove events...
|
|
if (!~attr.indexOf('.')) {
|
|
|
|
if (how === 'add') {
|
|
can.batch.trigger(this, how, [newVal, +attr]);
|
|
can.batch.trigger(this, 'length', [this.length]);
|
|
} else if (how === 'remove') {
|
|
can.batch.trigger(this, how, [oldVal, +attr]);
|
|
can.batch.trigger(this, 'length', [this.length]);
|
|
} else {
|
|
can.batch.trigger(this, how, [newVal, +attr])
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
__get: function(attr) {
|
|
return attr ? this[attr] : this;
|
|
},
|
|
___set: function(attr, val) {
|
|
this[attr] = val;
|
|
if (+attr >= this.length) {
|
|
this.length = (+attr + 1)
|
|
}
|
|
},
|
|
_each: function(callback) {
|
|
var data = this.__get();
|
|
for (var i = 0; i < data.length; i++) {
|
|
callback(data[i], i)
|
|
}
|
|
},
|
|
_bindsetup: Map.helpers.makeBindSetup("*"),
|
|
// Returns the serialized form of this list.
|
|
|
|
serialize: function() {
|
|
return Map.helpers.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 (Map.helpers.canMakeObserve(val)) {
|
|
args[i] = Map.helpers.hookupBubble(val, "*", this, this.constructor.Map, this.constructor)
|
|
}
|
|
}
|
|
if (howMany === undefined) {
|
|
howMany = args[1] = this.length - index;
|
|
}
|
|
var removed = splice.apply(this, args);
|
|
|
|
if (!spliceRemovesProps) {
|
|
for (var i = this.length; i < removed.length + this.length; i++) {
|
|
delete this[i]
|
|
}
|
|
}
|
|
|
|
can.batch.start();
|
|
if (howMany > 0) {
|
|
this._triggerChange("" + index, "remove", undefined, removed);
|
|
Map.helpers.unhookup(removed, this);
|
|
}
|
|
if (args.length > 2) {
|
|
this._triggerChange("" + index, "add", args.slice(2), removed);
|
|
}
|
|
can.batch.stop();
|
|
return removed;
|
|
},
|
|
|
|
_attrs: function(items, remove) {
|
|
if (items === undefined) {
|
|
return Map.helpers.serialize(this, 'attr', []);
|
|
}
|
|
|
|
// Create a copy.
|
|
items = can.makeArray(items);
|
|
|
|
can.batch.start();
|
|
this._updateAttrs(items, remove);
|
|
can.batch.stop()
|
|
},
|
|
|
|
_updateAttrs: function(items, remove) {
|
|
var len = Math.min(items.length, this.length);
|
|
|
|
for (var prop = 0; prop < len; prop++) {
|
|
var curVal = this[prop],
|
|
newVal = items[prop];
|
|
|
|
if (Map.helpers.canMakeObserve(curVal) && Map.helpers.canMakeObserve(newVal)) {
|
|
curVal.attr(newVal, remove)
|
|
} else if (curVal != newVal) {
|
|
this._set(prop, newVal)
|
|
} else {
|
|
|
|
}
|
|
}
|
|
if (items.length > this.length) {
|
|
// Add in the remaining props.
|
|
this.push.apply(this, items.slice(this.length));
|
|
} else if (items.length < this.length && remove) {
|
|
this.splice(items.length)
|
|
}
|
|
}
|
|
}),
|
|
|
|
// Converts to an `array` of arguments.
|
|
getArgs = function(args) {
|
|
return args[0] && can.isArray(args[0]) ?
|
|
args[0] :
|
|
can.makeArray(args);
|
|
};
|
|
// Create `push`, `pop`, `shift`, and `unshift`
|
|
can.each({
|
|
|
|
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 `map` that needs to be converted.
|
|
while (i--) {
|
|
val = arguments[i];
|
|
args[i] = Map.helpers.canMakeObserve(val) ?
|
|
Map.helpers.hookupBubble(val, "*", this, this.constructor.Map, this.constructor) :
|
|
val;
|
|
}
|
|
|
|
// Call the original method.
|
|
res = orig.apply(this, args);
|
|
|
|
if (!this.comparator || args.length) {
|
|
|
|
this._triggerChange("" + len, "add", args, undefined);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
});
|
|
|
|
can.each({
|
|
|
|
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) {
|
|
can.stopListening.call(this, res, "change");
|
|
}
|
|
return res;
|
|
}
|
|
});
|
|
|
|
can.extend(list.prototype, {
|
|
|
|
indexOf: function(item, fromIndex) {
|
|
this.attr('length')
|
|
return can.inArray(item, this, fromIndex)
|
|
},
|
|
|
|
|
|
join: function() {
|
|
return [].join.apply(this.attr(), arguments)
|
|
},
|
|
|
|
|
|
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.List ? arg.serialize() : arg;
|
|
});
|
|
return new this.constructor(Array.prototype.concat.apply(this.serialize(), args));
|
|
},
|
|
|
|
|
|
forEach: function(cb, thisarg) {
|
|
return 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 = Map.List = list;
|
|
return can.List;
|
|
})(__m2, __m12);
|
|
|
|
// ## compute/compute.js
|
|
var __m16 = (function(can, bind) {
|
|
|
|
var names = ["__reading", "__clearReading", "__setReading"],
|
|
setup = function(observed) {
|
|
var old = {};
|
|
for (var i = 0; i < names.length; i++) {
|
|
old[names[i]] = can[names[i]]
|
|
}
|
|
can.__reading = function(obj, attr) {
|
|
// Add the observe and attr that was read
|
|
// to `observed`
|
|
observed.push({
|
|
obj: obj,
|
|
attr: attr + ""
|
|
});
|
|
};
|
|
can.__clearReading = function() {
|
|
return observed.splice(0, observed.length);
|
|
}
|
|
can.__setReading = function(o) {
|
|
[].splice.apply(observed, [0, observed.length].concat(o))
|
|
}
|
|
return old;
|
|
},
|
|
// empty default function
|
|
k = function() {};
|
|
|
|
// 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 observed = [],
|
|
old = setup(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.
|
|
can.simpleExtend(can, old);
|
|
|
|
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,
|
|
ob;
|
|
matched = !matched;
|
|
|
|
// go through every attribute read by this observe
|
|
for (var i = 0, len = newObserveSet.length; i < len; i++) {
|
|
ob = newObserveSet[i];
|
|
// 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 = k,
|
|
// a method to teardown listening
|
|
off = k,
|
|
// 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,
|
|
// save for clone
|
|
args = can.makeArray(arguments),
|
|
updater = function(newValue, oldValue) {
|
|
value = newValue;
|
|
// might need a way to look up new and oldVal
|
|
can.batch.trigger(computed, "change", [newValue, oldValue])
|
|
},
|
|
// the form of the arguments
|
|
form;
|
|
|
|
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.batch.trigger(computed, "change", [value, old]);
|
|
}
|
|
return value;
|
|
} else {
|
|
// Another compute wants to bind to this compute
|
|
if (can.__reading && canReadForChangeEvent) {
|
|
// Tell the compute to listen to change on this computed
|
|
can.__reading(computed, 'change');
|
|
// We are going to bind on this compute.
|
|
// If we are not bound, we should bind so that
|
|
// we don't have to re-read to get the value of this compute.
|
|
!computeState.bound && can.compute.temporarilyBind(computed)
|
|
}
|
|
// 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.Map;
|
|
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;
|
|
context = eventName;
|
|
form = "setter";
|
|
} 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;
|
|
}
|
|
|
|
|
|
can.cid(computed, "compute")
|
|
|
|
return can.simpleExtend(computed, {
|
|
|
|
isComputed: true,
|
|
_bindsetup: function() {
|
|
computeState.bound = true;
|
|
// setup live-binding
|
|
// while binding, this does not count as a read
|
|
var oldReading = can.__reading;
|
|
delete can.__reading;
|
|
on.call(this, updater);
|
|
can.__reading = oldReading;
|
|
},
|
|
_bindteardown: function() {
|
|
off.call(this, updater)
|
|
computeState.bound = false;
|
|
},
|
|
|
|
bind: can.bindAndSetup,
|
|
|
|
unbind: can.unbindAndTeardown,
|
|
clone: function(context) {
|
|
if (context) {
|
|
if (form == "setter") {
|
|
args[2] = context
|
|
} else {
|
|
args[1] = context
|
|
}
|
|
}
|
|
return can.compute.apply(can, args);
|
|
}
|
|
});
|
|
};
|
|
|
|
// a list of temporarily bound computes
|
|
var computes,
|
|
unbindComputes = function() {
|
|
for (var i = 0, len = computes.length; i < len; i++) {
|
|
computes[i].unbind("change", k)
|
|
}
|
|
computes = null;
|
|
}
|
|
|
|
// Binds computes for a moment to retain their value and prevent caching
|
|
can.compute.temporarilyBind = function(compute) {
|
|
compute.bind("change", k)
|
|
if (!computes) {
|
|
computes = [];
|
|
setTimeout(unbindComputes, 10)
|
|
}
|
|
computes.push(compute)
|
|
};
|
|
|
|
can.compute.binder = computeBinder;
|
|
can.compute.truthy = function(compute) {
|
|
return can.compute(function() {
|
|
var res = compute();
|
|
if (typeof res === "function") {
|
|
res = res()
|
|
}
|
|
return !!res;
|
|
})
|
|
}
|
|
return can.compute;
|
|
})(__m2, __m13, __m14);
|
|
|
|
// ## observe/observe.js
|
|
var __m11 = (function(can) {
|
|
can.Observe = can.Map;
|
|
can.Observe.startBatch = can.batch.start;
|
|
can.Observe.stopBatch = can.batch.stop;
|
|
can.Observe.triggerBatch = can.batch.trigger;
|
|
return can;
|
|
})(__m2, __m12, __m15, __m16);
|
|
|
|
// ## view/view.js
|
|
var __m19 = (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, if a renderer function is passed in, then we just use that to render the data
|
|
result = isFunction(view) ? view(data, helpers, wrapCallback) : $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;
|
|
},
|
|
|
|
|
|
// auj
|
|
|
|
// heir
|
|
|
|
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 {
|
|
// get is called async but in
|
|
// ff will be async so we need to temporarily reset
|
|
if (can.__reading) {
|
|
var reading = can.__reading;
|
|
can.__reading = null;
|
|
}
|
|
|
|
// 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 (can.Map && can.__reading) {
|
|
can.__reading = reading;
|
|
}
|
|
|
|
// 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(obj, async) {
|
|
var url = typeof obj === 'string' ? obj : obj.url,
|
|
suffix = obj.engine || 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;
|
|
}
|
|
|
|
return $view.preload(id, info.renderer(id, text));
|
|
}
|
|
},
|
|
registerScript: function(type, id, src) {
|
|
return "can.view.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");";
|
|
},
|
|
preload: function(id, renderer) {
|
|
var def = $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;
|
|
|
|
// set cache references (otherwise preloaded recursive views won't recurse properly)
|
|
def.__view_id = id;
|
|
$view.cachedRenderers[id] = renderer;
|
|
|
|
return frag;
|
|
}
|
|
|
|
});
|
|
|
|
return can;
|
|
})(__m2);
|
|
|
|
// ## view/scope/scope.js
|
|
var __m18 = (function(can) {
|
|
|
|
|
|
|
|
var isObserve = function(obj) {
|
|
return obj instanceof can.Map || (obj && obj.__get);
|
|
},
|
|
getProp = function(obj, prop) {
|
|
var val = obj[prop];
|
|
|
|
if (typeof val !== "function" && obj.__get) {
|
|
return obj.__get(prop);
|
|
} else {
|
|
return val;
|
|
}
|
|
},
|
|
escapeReg = /(\\)?\./g,
|
|
escapeDotReg = /\\\./g,
|
|
getNames = function(attr) {
|
|
var names = [],
|
|
last = 0;
|
|
attr.replace(escapeReg, function($0, $1, index) {
|
|
if (!$1) {
|
|
names.push(attr.slice(last, index).replace(escapeDotReg, '.'));
|
|
last = index + $0.length;
|
|
}
|
|
});
|
|
names.push(attr.slice(last).replace(escapeDotReg, '.'));
|
|
return names;
|
|
}
|
|
|
|
|
|
|
|
var Scope = can.Construct.extend(
|
|
|
|
|
|
{
|
|
// reads properties from a parent. A much more complex version of getObject.
|
|
|
|
read: function(parent, reads, options) {
|
|
options = options || {};
|
|
// `cur` is the current value.
|
|
var cur = parent,
|
|
type,
|
|
// `prev` is the object we are reading from.
|
|
prev,
|
|
// `foundObs` did we find an observable.
|
|
foundObs;
|
|
for (var i = 0, readLength = reads.length; i < readLength; i++) {
|
|
// Update what we are reading from.
|
|
prev = cur;
|
|
// Read from the compute. We can't read a property yet.
|
|
if (prev && prev.isComputed) {
|
|
options.foundObservable && options.foundObservable(prev, i)
|
|
prev = prev()
|
|
}
|
|
// Look to read a property from something.
|
|
if (isObserve(prev)) {
|
|
!foundObs && options.foundObservable && options.foundObservable(prev, i);
|
|
foundObs = 1;
|
|
// is it a method on the prototype?
|
|
if (typeof prev[reads[i]] === "function" && prev.constructor.prototype[reads[i]] === prev[reads[i]]) {
|
|
// call that method
|
|
if (options.returnObserveMethods) {
|
|
cur = cur[reads[i]]
|
|
} else {
|
|
cur = prev[reads[i]].apply(prev, options.args || [])
|
|
}
|
|
|
|
} else {
|
|
// use attr to get that value
|
|
cur = cur.attr(reads[i]);
|
|
}
|
|
|
|
} else {
|
|
// just do the dot operator
|
|
cur = prev[reads[i]]
|
|
}
|
|
// If it's a compute, get the compute's value
|
|
// unless we are at the end of the
|
|
if (cur && cur.isComputed && (!options.isArgument && i < readLength - 1)) {
|
|
!foundObs && options.foundObservable && options.foundObservable(prev, i + 1)
|
|
cur = cur()
|
|
}
|
|
|
|
type = typeof cur;
|
|
// if there are properties left to read, and we don't have an object, early exit
|
|
if (i < reads.length - 1 && (cur == null || (type != "function" && type != "object"))) {
|
|
options.earlyExit && options.earlyExit(prev, i, cur);
|
|
// return undefined so we know this isn't the right value
|
|
return {
|
|
value: undefined,
|
|
parent: prev
|
|
};
|
|
}
|
|
}
|
|
// if we don't have a value, exit early.
|
|
if (cur === undefined) {
|
|
options.earlyExit && options.earlyExit(prev, i - 1)
|
|
}
|
|
// handle an ending function
|
|
if (typeof cur === "function") {
|
|
if (options.isArgument) {
|
|
if (!cur.isComputed && options.proxyMethods !== false) {
|
|
cur = can.proxy(cur, prev)
|
|
}
|
|
} else {
|
|
|
|
cur.isComputed && !foundObs && options.foundObservable && options.foundObservable(cur, i)
|
|
|
|
|
|
cur = cur.call(prev)
|
|
}
|
|
|
|
}
|
|
return {
|
|
value: cur,
|
|
parent: prev
|
|
};
|
|
}
|
|
},
|
|
|
|
{
|
|
init: function(context, parent) {
|
|
this._context = context;
|
|
this._parent = parent;
|
|
},
|
|
|
|
attr: function(key) {
|
|
return this.read(key, {
|
|
isArgument: true,
|
|
returnObserveMethods: true,
|
|
proxyMethods: false
|
|
}).value
|
|
},
|
|
|
|
add: function(context) {
|
|
if (context !== this._context) {
|
|
return new this.constructor(context, this);
|
|
} else {
|
|
return this;
|
|
}
|
|
},
|
|
|
|
computeData: function(key, options) {
|
|
options = options || {
|
|
args: []
|
|
};
|
|
var self = this,
|
|
rootObserve,
|
|
rootReads,
|
|
computeData = {
|
|
compute: can.compute(function(newVal) {
|
|
if (arguments.length) {
|
|
// check that there's just a compute with nothing from it ...
|
|
if (rootObserve.isComputed && !rootReads.length) {
|
|
rootObserve(newVal)
|
|
} else {
|
|
var last = rootReads.length - 1;
|
|
Scope.read(rootObserve, rootReads.slice(0, last)).value.attr(rootReads[last], newVal)
|
|
}
|
|
} else {
|
|
if (rootObserve) {
|
|
return Scope.read(rootObserve, rootReads, options).value
|
|
}
|
|
// otherwise, go get the value
|
|
var data = self.read(key, options);
|
|
rootObserve = data.rootObserve;
|
|
rootReads = data.reads;
|
|
computeData.scope = data.scope;
|
|
computeData.initialValue = data.value;
|
|
return data.value;
|
|
}
|
|
})
|
|
};
|
|
return computeData
|
|
|
|
},
|
|
|
|
read: function(attr, options) {
|
|
|
|
// check if we should be running this on a parent.
|
|
if (attr.substr(0, 3) === "../") {
|
|
return this._parent.read(attr.substr(3), options)
|
|
} else if (attr == "..") {
|
|
return {
|
|
value: this._parent._context
|
|
}
|
|
} else if (attr == "." || attr == "this") {
|
|
return {
|
|
value: this._context
|
|
};
|
|
}
|
|
|
|
// Split the name up.
|
|
var names = attr.indexOf('\\.') == -1
|
|
// Reference doesn't contain escaped periods
|
|
? attr.split('.')
|
|
// Reference contains escaped periods (`a.b\c.foo` == `a["b.c"].foo)
|
|
: getNames(attr),
|
|
namesLength = names.length,
|
|
j,
|
|
// The current context (a scope is just data and a parent scope).
|
|
context,
|
|
// The current scope.
|
|
scope = this,
|
|
// While we are looking for a value, we track the most likely place this value will be found.
|
|
// This is so if there is no me.name.first, we setup a listener on me.name.
|
|
// The most likely canidate is the one with the most "read matches" "lowest" in the
|
|
// context chain.
|
|
// By "read matches", we mean the most number of values along the key.
|
|
// By "lowest" in the context chain, we mean the closest to the current context.
|
|
// We track the starting position of the likely place with `defaultObserve`.
|
|
defaultObserve,
|
|
// Tracks how to read from the defaultObserve.
|
|
defaultReads = [],
|
|
// Tracks the highest found number of "read matches".
|
|
defaultPropertyDepth = -1,
|
|
// `scope.read` is designed to be called within a compute, but
|
|
// for performance reasons only listens to observables within one context.
|
|
// This is to say, if you have me.name in the current context, but me.name.first and
|
|
// we are looking for me.name.first, we don't setup bindings on me.name and me.name.first.
|
|
// To make this happen, we clear readings if they do not find a value. But,
|
|
// if that path turns out to be the default read, we need to restore them. This
|
|
// variable remembers those reads so they can be restored.
|
|
defaultComputeReadings,
|
|
// Tracks the default's scope.
|
|
defaultScope,
|
|
// Tracks the first found observe.
|
|
currentObserve,
|
|
// Tracks the reads to get the value for a scope.
|
|
currentReads;
|
|
|
|
// While there is a scope/context to look in.
|
|
while (scope) {
|
|
|
|
// get the context
|
|
context = scope._context;
|
|
|
|
if (context != null) {
|
|
|
|
|
|
// Lets try this context
|
|
var data = Scope.read(context, names, can.simpleExtend({
|
|
// Called when an observable is found.
|
|
foundObservable: function(observe, nameIndex) {
|
|
// Save the current observe.
|
|
currentObserve = observe;
|
|
currentReads = names.slice(nameIndex);
|
|
},
|
|
// Called when we were unable to find a value.
|
|
earlyExit: function(parentValue, nameIndex) {
|
|
// If this has more matching values,
|
|
if (nameIndex > defaultPropertyDepth) {
|
|
// save the state.
|
|
defaultObserve = currentObserve;
|
|
defaultReads = currentReads;
|
|
defaultPropertyDepth = nameIndex;
|
|
defaultScope = scope;
|
|
// Clear and save readings so next attempt does not use these readings
|
|
defaultComputeReadings = can.__clearReading && can.__clearReading();
|
|
}
|
|
|
|
}
|
|
}, options));
|
|
|
|
// Found a matched reference.
|
|
if (data.value !== undefined) {
|
|
return {
|
|
scope: scope,
|
|
rootObserve: currentObserve,
|
|
value: data.value,
|
|
reads: currentReads
|
|
};
|
|
}
|
|
}
|
|
// Prevent prior readings.
|
|
can.__clearReading && can.__clearReading();
|
|
// Move up to the next scope.
|
|
scope = scope._parent;
|
|
}
|
|
// If there was a likely observe.
|
|
if (defaultObserve) {
|
|
// Restore reading for previous compute
|
|
can.__setReading && can.__setReading(defaultComputeReadings)
|
|
return {
|
|
scope: defaultScope,
|
|
rootObserve: defaultObserve,
|
|
reads: defaultReads,
|
|
value: undefined
|
|
}
|
|
} else {
|
|
// we found nothing and no observable
|
|
return {
|
|
names: names,
|
|
value: undefined
|
|
};
|
|
}
|
|
|
|
}
|
|
});
|
|
can.view.Scope = Scope;
|
|
return Scope;
|
|
|
|
})(__m2, __m9, __m12, __m15, __m19, __m16);
|
|
|
|
// ## view/elements.js
|
|
var __m21 = (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,
|
|
src: function(el, val) {
|
|
if (val == null || val == "") {
|
|
el.removeAttribute("src")
|
|
} else {
|
|
el.setAttribute("src", val)
|
|
}
|
|
}
|
|
},
|
|
// matches the attrName of a regexp
|
|
attrReg: /([^\s]+)[\s]*=[\s]*/,
|
|
// 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"
|
|
},
|
|
// Used to determine the parentNode if el is directly within a documentFragment
|
|
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 (typeof prop === "function") {
|
|
prop(el, val)
|
|
} else 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) {
|
|
var setter = elements.attrMap[attrName];
|
|
if (typeof prop === "function") {
|
|
prop(el, undefined)
|
|
}
|
|
if (setter === true) {
|
|
el[attrName] = false;
|
|
} else if (typeof setter === "string") {
|
|
el[setter] = "";
|
|
} else {
|
|
el.removeAttribute(attrName);
|
|
}
|
|
},
|
|
// Gets a "pretty" value for something
|
|
contentText: function(text) {
|
|
if (typeof text == 'string') {
|
|
return text;
|
|
}
|
|
// If has no value, return an empty string.
|
|
if (!text && text !== 0) {
|
|
return '';
|
|
}
|
|
return "" + text;
|
|
},
|
|
|
|
after: function(oldElements, newFrag) {
|
|
var last = oldElements[oldElements.length - 1];
|
|
|
|
// Insert it in the `document` or `documentFragment`
|
|
if (last.nextSibling) {
|
|
can.insertBefore(last.parentNode, newFrag, last.nextSibling)
|
|
} else {
|
|
can.appendChild(last.parentNode, newFrag);
|
|
}
|
|
},
|
|
|
|
replace: function(oldElements, newFrag) {
|
|
elements.after(oldElements, newFrag);
|
|
can.remove(can.$(oldElements));
|
|
}
|
|
};
|
|
// TODO: this doesn't seem to be doing anything
|
|
// feature detect if setAttribute works with styles
|
|
(function() {
|
|
// feature detect if
|
|
var div = document.createElement('div')
|
|
div.setAttribute("style", "width: 5px")
|
|
div.setAttribute("style", "width: 10px");
|
|
// make style use cssText
|
|
elements.attrMap.style = function(el, val) {
|
|
el.style.cssText = val || ""
|
|
}
|
|
})();
|
|
|
|
|
|
return elements;
|
|
})();
|
|
|
|
// ## view/scanner.js
|
|
var __m20 = (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(\n",
|
|
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,
|
|
getAttrName = function() {
|
|
var matches = beforeQuote.match(attrReg);
|
|
return matches && matches[1];
|
|
},
|
|
// Used to mark where the element is.
|
|
status = function() {
|
|
// `t` - `1`.
|
|
// `h` - `0`.
|
|
// `q` - String `beforeQuote`.
|
|
return quote ? "'" + getAttrName() + "'" : (htmlTag ? 1 : 0);
|
|
},
|
|
// returns the top of a stack
|
|
top = function(stack) {
|
|
return stack[stack.length - 1]
|
|
},
|
|
// characters that automatically mean a custom element
|
|
automaticCustomElementCharacters = /[-\:]/,
|
|
Scanner;
|
|
|
|
can.view.Scanner = Scanner = function(options) {
|
|
// Set options on self
|
|
can.extend(this, {
|
|
|
|
text: {},
|
|
tokens: []
|
|
}, options);
|
|
// make sure it's an empty string if it's not
|
|
this.text.options = this.text.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.attributes = {};
|
|
Scanner.regExpAttributes = {};
|
|
|
|
Scanner.attribute = function(attribute, callback) {
|
|
if (typeof attribute == "string") {
|
|
Scanner.attributes[attribute] = callback;
|
|
} else {
|
|
Scanner.regExpAttributes[attribute] = {
|
|
match: attribute,
|
|
callback: callback
|
|
};
|
|
}
|
|
|
|
}
|
|
Scanner.hookupAttributes = function(options, el) {
|
|
can.each(options && options.attrs || [], function(attr) {
|
|
options.attr = attr;
|
|
if (Scanner.attributes[attr]) {
|
|
Scanner.attributes[attr](options, el);
|
|
} else {
|
|
can.each(Scanner.regExpAttributes, function(attrMatcher) {
|
|
if (attrMatcher.match.test(attr)) {
|
|
attrMatcher.callback(options, el)
|
|
}
|
|
})
|
|
}
|
|
|
|
})
|
|
}
|
|
Scanner.tag = function(tagName, callback) {
|
|
// if we have html5shive ... re-generate
|
|
if (window.html5) {
|
|
html5.elements += " " + tagName
|
|
html5.shivDocument();
|
|
}
|
|
|
|
Scanner.tags[tagName.toLowerCase()] = callback;
|
|
}
|
|
Scanner.tags = {};
|
|
// This is called when there is a special tag
|
|
Scanner.hookupTag = function(hookupOptions) {
|
|
// we need to call any live hookups
|
|
// so get that and return the hook
|
|
// a better system will always be called with the same stuff
|
|
var hooks = can.view.getHooks();
|
|
return can.view.hook(function(el) {
|
|
can.each(hooks, function(fn) {
|
|
fn(el);
|
|
});
|
|
|
|
var tagName = hookupOptions.tagName,
|
|
helperTagCallback = hookupOptions.options.read('helpers._tags.' + tagName, {
|
|
isArgument: true,
|
|
proxyMethods: false
|
|
}).value,
|
|
tagCallback = helperTagCallback || Scanner.tags[tagName];
|
|
|
|
// If this was an element like <foo-bar> that doesn't have a component, just render its content
|
|
var scope = hookupOptions.scope,
|
|
res = tagCallback ? tagCallback(el, hookupOptions) : scope;
|
|
|
|
// If the tagCallback gave us something to render with, and there is content within that element
|
|
// render it!
|
|
if (res && hookupOptions.subtemplate) {
|
|
|
|
if (scope !== res) {
|
|
scope = scope.add(res)
|
|
}
|
|
var frag = can.view.frag(hookupOptions.subtemplate(scope, hookupOptions.options));
|
|
can.appendChild(el, frag);
|
|
}
|
|
can.view.Scanner.hookupAttributes(hookupOptions, el);
|
|
});
|
|
|
|
}
|
|
|
|
Scanner.prototype = {
|
|
// a default that can be overwritten
|
|
helpers: [],
|
|
|
|
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,
|
|
// was there a special state
|
|
specialStates = {
|
|
attributeHookups: [],
|
|
// a stack of tagHookups
|
|
tagHookups: []
|
|
},
|
|
// The current tag name.
|
|
tagName = '',
|
|
// stack of tagNames
|
|
tagNames = [],
|
|
// Pop from tagNames?
|
|
popTagName = false,
|
|
// Declared here.
|
|
bracketCount,
|
|
|
|
// in a special attr like src= or style=
|
|
specialAttribute = false,
|
|
|
|
i = 0,
|
|
token,
|
|
tmap = this.tokenMap,
|
|
attrName;
|
|
|
|
// 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) == "--"),
|
|
attrs = "";
|
|
// 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 (specialStates.attributeHookups.length) {
|
|
attrs = "attrs: ['" + specialStates.attributeHookups.join("','") + "'], ";
|
|
specialStates.attributeHookups = [];
|
|
}
|
|
// this is the > of a special tag
|
|
if (tagName === top(specialStates.tagHookups)) {
|
|
// If it's a self closing tag (like <content/>) make sure we put the / at the end.
|
|
if (emptyElement) {
|
|
content = content.substr(0, content.length - 1)
|
|
}
|
|
// Put the start of the end
|
|
buff.push(put_cmd,
|
|
'"', clean(content), '"',
|
|
",can.view.Scanner.hookupTag({tagName:'" + tagName + "'," + (attrs) + "scope: " + (this.text.scope || "this") + this.text.options)
|
|
|
|
|
|
|
|
|
|
// if it's a self closing tag (like <content/>) close and end the tag
|
|
if (emptyElement) {
|
|
buff.push("}));");
|
|
content = "/>";
|
|
specialStates.tagHookups.pop()
|
|
}
|
|
// if it's an empty tag
|
|
else if (tokens[i] === "<" && tokens[i + 1] === "/" + tagName) {
|
|
buff.push("}));");
|
|
content = token;
|
|
specialStates.tagHookups.pop()
|
|
} else {
|
|
// it has content
|
|
buff.push(",subtemplate: function(" + this.text.argNames + "){\n" + startTxt + (this.text.start || ''));
|
|
content = '';
|
|
}
|
|
|
|
} else if (magicInTag || (!popTagName && elements.tagToContentPropMap[tagNames[tagNames.length - 1]]) || attrs) {
|
|
// make sure / of /> is on the right of pending
|
|
var pendingPart = ",can.view.pending({" + attrs + "scope: " + (this.text.scope || "this") + this.text.options + "}),\"";
|
|
if (emptyElement) {
|
|
put(content.substr(0, content.length - 1), pendingPart + "/>\"");
|
|
} else {
|
|
put(content, pendingPart + ">\"");
|
|
}
|
|
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;
|
|
}
|
|
specialStates.attributeHookups = [];
|
|
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 `\`?
|
|
var attr = getAttrName();
|
|
if (Scanner.attributes[attr]) {
|
|
specialStates.attributeHookups.push(attr);
|
|
} else {
|
|
can.each(Scanner.regExpAttributes, function(attrMatcher) {
|
|
if (attrMatcher.match.test(attr)) {
|
|
specialStates.attributeHookups.push(attr);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (specialAttribute) {
|
|
|
|
content += token;
|
|
put(content);
|
|
buff.push(finishTxt, "}));\n")
|
|
content = ""
|
|
specialAttribute = false;
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
} else if (quote === null) {
|
|
quote = token;
|
|
beforeQuote = lastToken;
|
|
attrName = getAttrName()
|
|
// TODO: check if there's magic!!!!
|
|
if ((tagName == "img" && attrName == "src") || attrName === "style") {
|
|
// put content that was before the attr name, but don't include the src=
|
|
put(content.replace(attrReg, ""));
|
|
content = "";
|
|
|
|
specialAttribute = true;
|
|
|
|
buff.push(insert_cmd, "can.view.txt(2,'" + getTag(tagName, tokens, i) + "'," + status() + ",this,function(){", startTxt);
|
|
put(attrName + "=" + token);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
default:
|
|
// Track the current tag
|
|
if (lastToken === '<') {
|
|
|
|
tagName = token.substr(0, 3) === "!--" ?
|
|
"!--" : token.split(/\s/)[0];
|
|
|
|
var isClosingTag = false;
|
|
|
|
if (tagName.indexOf("/") === 0) {
|
|
isClosingTag = true;
|
|
var cleanedTagName = tagName.substr(1);
|
|
}
|
|
|
|
if (isClosingTag) { // </tag>
|
|
|
|
// when we enter a new tag, pop the tag name stack
|
|
if (top(tagNames) === cleanedTagName) {
|
|
// set tagName to the last tagName
|
|
// if there are no more tagNames, we'll rely on getTag.
|
|
tagName = cleanedTagName;
|
|
popTagName = true;
|
|
}
|
|
|
|
// if we are in a closing tag of a custom tag
|
|
if (top(specialStates.tagHookups) == cleanedTagName) {
|
|
|
|
// remove the last < from the content
|
|
put(content.substr(0, content.length - 1));
|
|
|
|
// finish the "section"
|
|
buff.push(finishTxt + "}}) );");
|
|
|
|
// the < belongs to the outside
|
|
content = "><"
|
|
specialStates.tagHookups.pop()
|
|
}
|
|
|
|
} else {
|
|
if (tagName.lastIndexOf("/") === tagName.length - 1) {
|
|
tagName = tagName.substr(0, tagName.length - 1);
|
|
|
|
|
|
}
|
|
|
|
if (tagName !== "!--" && (Scanner.tags[tagName] || automaticCustomElementCharacters.test(tagName))) {
|
|
// if the content tag is inside something it doesn't belong ...
|
|
if (tagName === "content" && elements.tagMap[top(tagNames)]) {
|
|
// convert it to an element that will work
|
|
token = token.replace("content", elements.tagMap[top(tagNames)])
|
|
}
|
|
// we will hookup at the ending tag>
|
|
specialStates.tagHookups.push(tagName);
|
|
}
|
|
|
|
|
|
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: "}));\n"
|
|
});
|
|
}
|
|
|
|
var escaped = startTag === tmap.escapeLeft ? 1 : 0,
|
|
commands = {
|
|
insert: insert_cmd,
|
|
tagName: getTag(tagName, tokens, i),
|
|
status: status(),
|
|
specialAttribute: specialAttribute
|
|
};
|
|
|
|
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 (specialAttribute) {
|
|
buff.push(insert_cmd, content, ');');
|
|
} 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(\n" + escaped + ",\n'" + tagName + "',\n" + status() + ",\nthis,\nfunction(){ " + (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.
|
|
"}));\n");
|
|
}
|
|
|
|
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: (this.text.outStart || "") + template + " " + finishTxt + (this.text.outEnd || "")
|
|
};
|
|
// Use `eval` instead of creating a function, because it is easier to debug.
|
|
myEval.call(out, 'this.fn = (function(' + this.text.argNames + '){' + out.out + '});\r\n//@ sourceURL=' + name + ".js");
|
|
|
|
return out;
|
|
}
|
|
};
|
|
|
|
can.view.Scanner.tag("content", function(el, options) {
|
|
return options.scope;
|
|
})
|
|
|
|
return Scanner;
|
|
})(__m19, __m21);
|
|
|
|
// ## view/node_lists/node_lists.js
|
|
var __m24 = (function(can) {
|
|
|
|
// In some browsers, text nodes can not take expando properties.
|
|
// We test that here.
|
|
var canExpando = true;
|
|
try {
|
|
document.createTextNode('')._ = 0;
|
|
} catch (ex) {
|
|
canExpando = false;
|
|
}
|
|
|
|
// A mapping of element ids to nodeList id
|
|
var nodeMap = {},
|
|
// A mapping of ids to text nodes
|
|
textNodeMap = {},
|
|
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;
|
|
}
|
|
},
|
|
splice = [].splice;
|
|
|
|
|
|
var nodeLists = {
|
|
id: id,
|
|
|
|
|
|
update: function(nodeList, newNodes) {
|
|
// Unregister all childNodes.
|
|
can.each(nodeList.childNodeLists, function(nodeList) {
|
|
nodeLists.unregister(nodeList)
|
|
})
|
|
nodeList.childNodeLists = [];
|
|
|
|
// Remove old node pointers to this list.
|
|
can.each(nodeList, function(node) {
|
|
delete nodeMap[id(node)];
|
|
});
|
|
|
|
var newNodes = can.makeArray(newNodes);
|
|
|
|
// indicate the new nodes belong to this list
|
|
can.each(newNodes, function(node) {
|
|
nodeMap[id(node)] = nodeList;
|
|
});
|
|
|
|
|
|
var oldListLength = nodeList.length,
|
|
firstNode = nodeList[0];
|
|
// Replace oldNodeLists's contents'
|
|
splice.apply(nodeList, [0, oldListLength].concat(newNodes));
|
|
|
|
// update all parent nodes so they are able to replace the correct elements
|
|
var parentNodeList = nodeList;
|
|
while (parentNodeList = parentNodeList.parentNodeList) {
|
|
splice.apply(parentNodeList, [can.inArray(firstNode, parentNodeList), oldListLength].concat(newNodes));
|
|
}
|
|
|
|
|
|
},
|
|
|
|
register: function(nodeList, unregistered, parent) {
|
|
|
|
// add an id to the nodeList
|
|
nodeList.unregistered = unregistered,
|
|
|
|
nodeList.childNodeLists = [];
|
|
|
|
if (!parent) {
|
|
// find the parent by looking up where this node is
|
|
if (nodeList.length > 1) {
|
|
throw "does not work"
|
|
}
|
|
var nodeId = id(nodeList[0]);
|
|
parent = nodeMap[nodeId];
|
|
|
|
}
|
|
nodeList.parentNodeList = parent;
|
|
parent && parent.childNodeLists.push(nodeList);
|
|
return nodeList;
|
|
},
|
|
// removes node in all parent nodes and unregisters all childNodes
|
|
|
|
unregister: function(nodeList) {
|
|
if (!nodeList.isUnregistered) {
|
|
nodeList.isUnregistered = true;
|
|
// unregister all childNodeLists
|
|
delete nodeList.parentNodeList;
|
|
can.each(nodeList, function(node) {
|
|
var nodeId = id(node);
|
|
delete nodeMap[nodeId]
|
|
});
|
|
// this can unbind which will call itself
|
|
nodeList.unregistered && nodeList.unregistered();
|
|
can.each(nodeList.childNodeLists, function(nodeList) {
|
|
nodeLists.unregister(nodeList)
|
|
});
|
|
}
|
|
},
|
|
nodeMap: nodeMap,
|
|
}
|
|
return nodeLists;
|
|
|
|
})(__m2, __m21);
|
|
|
|
// ## view/live/live.js
|
|
var __m23 = (function(can, elements, view, nodeLists) {
|
|
// ## live.js
|
|
// The live module provides live binding for computes
|
|
// and can.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 setupCount = 0;
|
|
var setup = function(el, bind, unbind) {
|
|
// Removing an element can call teardown which
|
|
// unregister the nodeList which calls teardown
|
|
var tornDown = false,
|
|
teardown = function() {
|
|
if (!tornDown) {
|
|
tornDown = true;
|
|
unbind(data)
|
|
can.unbind.call(el, 'removed', teardown);
|
|
|
|
}
|
|
|
|
return true
|
|
},
|
|
data = {
|
|
// returns true if no parent
|
|
teardownCheck: function(parent) {
|
|
return parent ? false : teardown();
|
|
}
|
|
}
|
|
|
|
can.bind.call(el, 'removed', 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('=')
|
|
},
|
|
splice = [].splice;
|
|
|
|
|
|
var live = {
|
|
|
|
list: function(el, compute, render, context, parentNode) {
|
|
|
|
|
|
// A nodeList of all elements this live-list manages.
|
|
// This is here so that if this live list is within another section
|
|
// that section is able to remove the items in this list.
|
|
var masterNodeList = [el],
|
|
// A mapping of the index of an item to an array
|
|
// of elements that represent the item.
|
|
// Each array is registered so child or parent
|
|
// live structures can update the elements.
|
|
itemIndexToNodeListsMap = [],
|
|
// A mapping of items to their indicies'
|
|
indexMap = [],
|
|
|
|
// Called when items are added to the list.
|
|
add = function(ev, items, index) {
|
|
|
|
// Collect new html and mappings
|
|
var frag = document.createDocumentFragment(),
|
|
newNodeLists = [],
|
|
newIndicies = [];
|
|
|
|
// For each new item,
|
|
can.each(items, function(item, key) {
|
|
|
|
var itemIndex = can.compute(key + index),
|
|
// get its string content
|
|
itemHTML = render.call(context, item, itemIndex),
|
|
// and convert it into elements.
|
|
itemFrag = can.view.fragment(itemHTML)
|
|
|
|
|
|
// Add those elements to the mappings.
|
|
newNodeLists.push(
|
|
// Register those nodes with nodeLists.
|
|
nodeLists.register(can.makeArray(itemFrag.childNodes), undefined, masterNodeList));
|
|
|
|
// Hookup the fragment (which sets up child live-bindings) and
|
|
// add it to the collection of all added elements.
|
|
frag.appendChild(can.view.hookup(itemFrag));
|
|
newIndicies.push(itemIndex)
|
|
})
|
|
|
|
// Check if we are adding items at the end
|
|
if (!itemIndexToNodeListsMap[index]) {
|
|
|
|
elements.after(
|
|
// If we are adding items to an empty list
|
|
index == 0 ?
|
|
// add those items after the placeholder text element.
|
|
[text] :
|
|
// otherwise, add them after the last element in the previous index.
|
|
itemIndexToNodeListsMap[index - 1], frag)
|
|
} else {
|
|
// Add elements before the next index's first element.
|
|
var el = itemIndexToNodeListsMap[index][0];
|
|
can.insertBefore(el.parentNode, frag, el);
|
|
}
|
|
|
|
splice.apply(itemIndexToNodeListsMap, [index, 0].concat(newNodeLists));
|
|
|
|
// update indices after insert point
|
|
splice.apply(indexMap, [index, 0].concat(newIndicies));
|
|
for (var i = index + newIndicies.length, len = indexMap.length; i < len; i++) {
|
|
indexMap[i](i)
|
|
}
|
|
|
|
},
|
|
|
|
// Called when items are removed or when the bindings are torn down.
|
|
remove = function(ev, items, index, duringTeardown) {
|
|
|
|
// If this is because an element was removed, we should
|
|
// check to make sure the live elements are still in the page.
|
|
// If we did this during a teardown, it would cause an infinite loop.
|
|
if (!duringTeardown && data.teardownCheck(text.parentNode)) {
|
|
return
|
|
}
|
|
|
|
var removedMappings = itemIndexToNodeListsMap.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.update(nodeList, []);
|
|
// unregister the list
|
|
nodeLists.unregister(nodeList);
|
|
|
|
});
|
|
// update indices after remove point
|
|
indexMap.splice(index, items.length)
|
|
for (var i = index, len = indexMap.length; i < len; i++) {
|
|
indexMap[i](i)
|
|
}
|
|
|
|
can.remove(can.$(itemsToRemove));
|
|
},
|
|
parentNode = elements.getParentNode(el, parentNode),
|
|
text = document.createTextNode(""),
|
|
// The current list.
|
|
list,
|
|
|
|
// Called when the list is replaced with a new list or the binding is torn-down.
|
|
teardownList = function() {
|
|
// there might be no list right away, and the list might be a plain
|
|
// array
|
|
list && list.unbind && list.unbind("add", add).unbind("remove", remove);
|
|
// use remove to clean stuff up for us
|
|
remove({}, {
|
|
length: itemIndexToNodeListsMap.length
|
|
}, 0, true);
|
|
},
|
|
// Called when the list is replaced or setup.
|
|
updateList = function(ev, newList, oldList) {
|
|
teardownList();
|
|
// make an empty list if the compute returns null or undefined
|
|
list = newList || [];
|
|
// list might be a plain array
|
|
list.bind && list.bind("add", add).bind("remove", remove);
|
|
add({}, list, 0);
|
|
}
|
|
|
|
|
|
// Setup binding and teardown to add and remove events
|
|
var data = setup(parentNode, function() {
|
|
can.isFunction(compute) && compute.bind("change", updateList)
|
|
}, function() {
|
|
can.isFunction(compute) && compute.unbind("change", updateList)
|
|
teardownList()
|
|
});
|
|
|
|
live.replace(masterNodeList, text, data.teardownCheck)
|
|
|
|
// run the list setup
|
|
updateList({}, can.isFunction(compute) ? compute() : compute)
|
|
|
|
|
|
},
|
|
|
|
html: function(el, compute, parentNode) {
|
|
var parentNode = elements.getParentNode(el, parentNode),
|
|
data = listen(parentNode, compute, function(ev, newVal, oldVal) {
|
|
// TODO: remove teardownCheck in 2.1
|
|
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 = [el],
|
|
makeAndPut = function(val) {
|
|
var frag = can.view.fragment("" + val),
|
|
oldNodes = can.makeArray(nodes);
|
|
|
|
// We need to mark each node as belonging to the node list.
|
|
nodeLists.update(nodes, frag.childNodes)
|
|
|
|
frag = can.view.hookup(frag, parentNode)
|
|
|
|
elements.replace(oldNodes, frag)
|
|
};
|
|
|
|
data.nodeList = nodes;
|
|
// register the span so nodeLists knows the parentNodeList
|
|
nodeLists.register(nodes, data.teardownCheck)
|
|
makeAndPut(compute());
|
|
|
|
},
|
|
|
|
replace: function(nodes, val, teardown) {
|
|
var oldNodes = nodes.slice(0),
|
|
frag;
|
|
|
|
nodeLists.register(nodes, teardown);
|
|
if (typeof val === "string") {
|
|
frag = can.view.fragment(val)
|
|
} else if (val.nodeType !== 11) {
|
|
frag = document.createDocumentFragment();
|
|
frag.appendChild(val)
|
|
} else {
|
|
frag = val;
|
|
}
|
|
|
|
// We need to mark each node as belonging to the node list.
|
|
nodeLists.update(nodes, frag.childNodes)
|
|
|
|
if (typeof val === "string") {
|
|
// if it was a string, check for hookups
|
|
frag = can.view.hookup(frag, nodes[0].parentNode);
|
|
}
|
|
elements.replace(oldNodes, frag);
|
|
|
|
return nodes;
|
|
},
|
|
|
|
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(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;
|
|
}
|
|
// TODO: remove in 2.1
|
|
data.teardownCheck(node.parentNode);
|
|
}),
|
|
// The text node that will be updated
|
|
node = document.createTextNode(compute());
|
|
|
|
// Replace the placeholder with the live node and do the nodeLists thing.
|
|
live.replace([el], node, data.teardownCheck);
|
|
},
|
|
|
|
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(""));
|
|
|
|
},
|
|
specialAttribute: function(el, attributeName, compute) {
|
|
|
|
listen(el, compute, function(ev, newVal) {
|
|
elements.setAttr(el, attributeName, getValue(newVal));
|
|
});
|
|
|
|
elements.setAttr(el, attributeName, getValue(compute()));
|
|
}
|
|
}
|
|
var newLine = /(\r|\n)+/g;
|
|
var getValue = function(val) {
|
|
val = val.replace(elements.attrReg, "").replace(newLine, "");
|
|
// check if starts and ends with " or '
|
|
return /^["'].*["']$/.test(val) ? val.substr(1, val.length - 2) : val
|
|
}
|
|
can.view.live = live;
|
|
can.view.nodeLists = nodeLists;
|
|
can.view.elements = elements;
|
|
return live;
|
|
|
|
})(__m2, __m21, __m19, __m24);
|
|
|
|
// ## view/render.js
|
|
var __m22 = (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, tag) {
|
|
return (typeof txt == 'string' || typeof txt == 'number') ?
|
|
can.esc(txt) :
|
|
contentText(txt, tag);
|
|
},
|
|
// A flag to indicate if .txt was called within a live section within an element like the {{name}}
|
|
// within `<div {{#person}}{{name}}{{/person}}/>`.
|
|
withinTemplatedSectionWithinAnElement = false,
|
|
emptyHandler = function() {};
|
|
|
|
var current, lastHookups;
|
|
|
|
can.extend(can.view, {
|
|
live: live,
|
|
// called in text to make a temporary
|
|
// can.view.lists function that can be called with
|
|
// the list to iterate over and the template
|
|
// used to produce the content within the list
|
|
setupLists: function() {
|
|
|
|
var old = can.view.lists,
|
|
data;
|
|
|
|
can.view.lists = function(list, renderer) {
|
|
data = {
|
|
list: list,
|
|
renderer: renderer
|
|
}
|
|
return Math.random()
|
|
}
|
|
// sets back to the old data
|
|
return function() {
|
|
can.view.lists = old;
|
|
return data;
|
|
}
|
|
},
|
|
pending: function(data) {
|
|
// TODO, make this only run for the right tagName
|
|
var hooks = can.view.getHooks();
|
|
return can.view.hook(function(el) {
|
|
can.each(hooks, function(fn) {
|
|
fn(el);
|
|
});
|
|
can.view.Scanner.hookupAttributes(data, el);
|
|
});
|
|
},
|
|
getHooks: function() {
|
|
var hooks = pendingHookups.slice(0);
|
|
lastHookups = hooks;
|
|
pendingHookups = [];
|
|
return hooks;
|
|
},
|
|
onlytxt: function(self, func) {
|
|
return contentEscape(func.call(self))
|
|
},
|
|
|
|
txt: function(escape, tagName, status, self, func) {
|
|
// the temporary tag needed for any live setup
|
|
var tag = (elements.tagMap[tagName] || "span"),
|
|
// should live-binding be setup
|
|
setupLiveBinding = false,
|
|
// the compute's value
|
|
value;
|
|
|
|
|
|
// Are we currently within a live section within an element like the {{name}}
|
|
// within `<div {{#person}}{{name}}{{/person}}/>`.
|
|
if (withinTemplatedSectionWithinAnElement) {
|
|
value = func.call(self);
|
|
} else {
|
|
|
|
// If this magic tag is within an attribute or an html element,
|
|
// set the flag to true so we avoid trying to live bind
|
|
// anything that func might be setup.
|
|
// TODO: the scanner should be able to set this up.
|
|
if (typeof status === "string" || status === 1) {
|
|
withinTemplatedSectionWithinAnElement = true;
|
|
}
|
|
|
|
// Sets up a listener so we know any can.view.lists called
|
|
// when func is called
|
|
var listTeardown = can.view.setupLists(),
|
|
unbind = function() {
|
|
compute.unbind("change", emptyHandler)
|
|
};
|
|
// Create a compute that calls func and looks for dependencies.
|
|
// By passing `false`, this compute can not be a dependency of other
|
|
// computes. This is because live-bits are nested, but
|
|
// handle their own updating. For example:
|
|
// {{#if items.length}}{{#items}}{{.}}{{/items}}{{/if}}
|
|
// We do not want `{{#if items.length}}` changing the DOM if
|
|
// `{{#items}}` text changes.
|
|
var compute = can.compute(func, self, false);
|
|
|
|
// Bind to get and temporarily cache the value of the compute.
|
|
compute.bind("change", emptyHandler);
|
|
|
|
// Call the "wrapping" function and get the binding information
|
|
var listData = listTeardown();
|
|
|
|
// Get the value of the compute
|
|
value = compute();
|
|
|
|
// Let people know we are no longer within an element.
|
|
withinTemplatedSectionWithinAnElement = false;
|
|
|
|
// If we should setup live-binding.
|
|
setupLiveBinding = compute.hasDependencies;
|
|
}
|
|
|
|
if (listData) {
|
|
unbind && unbind();
|
|
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 (!setupLiveBinding || typeof value === "function") {
|
|
unbind && unbind();
|
|
return ((escape || typeof status === 'string') && escape !== 2 ? 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(
|
|
// if value is an object, it's likely something returned by .safeString
|
|
escape && typeof value != "object" ?
|
|
// 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 if (escape === 2) { // In a special attribute like src or style
|
|
|
|
var attributeName = status;
|
|
pendingHookups.push(function(el) {
|
|
live.specialAttribute(el, attributeName, 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;
|
|
})(__m19, __m21, __m23, __m10);
|
|
|
|
// ## view/mustache/mustache.js
|
|
var __m17 = (function(can) {
|
|
|
|
// # mustache.js
|
|
// `can.Mustache`: The Mustache templating engine.
|
|
// See the [Transformation](#section-29) section within *Scanning Helpers* for a detailed explanation
|
|
// of the runtime render code design. The majority of the Mustache engine implementation
|
|
// occurs within the *Transformation* scanning helper.
|
|
|
|
// ## Initialization
|
|
// Define the view extension.
|
|
can.view.ext = ".mustache";
|
|
|
|
// ### Setup internal helper variables and functions.
|
|
// An alias for the context variable used for tracking a stack of contexts.
|
|
// This is also used for passing to helper functions to maintain proper context.
|
|
var SCOPE = 'scope',
|
|
// An alias for the variable used for the hash object that can be passed
|
|
// to helpers via `options.hash`.
|
|
HASH = '___h4sh',
|
|
// An alias for the most used context stacking call.
|
|
CONTEXT_OBJ = '{scope:' + SCOPE + ',options:options}',
|
|
// argument names used to start the function (used by scanner and steal)
|
|
ARG_NAMES = SCOPE + ",options",
|
|
|
|
// matches arguments inside a {{ }}
|
|
argumentsRegExp = /((([^\s]+?=)?('.*?'|".*?"))|.*?)\s/g,
|
|
|
|
// matches a literal number, string, null or regexp
|
|
literalNumberStringBooleanRegExp = /^(('.*?'|".*?"|[0-9]+\.?[0-9]*|true|false|null|undefined)|((.+?)=(('.*?'|".*?"|[0-9]+\.?[0-9]*|true|false)|(.+))))$/,
|
|
|
|
// returns an object literal that we can use to look up a value in the current scope
|
|
makeLookupLiteral = function(type) {
|
|
return '{get:"' + type.replace(/"/g, '\\"') + '"}'
|
|
},
|
|
// returns if the object is a lookup
|
|
isLookup = function(obj) {
|
|
return obj && typeof obj.get == "string"
|
|
},
|
|
|
|
|
|
isObserveLike = function(obj) {
|
|
return obj instanceof can.Map || (obj && !! obj._get);
|
|
},
|
|
|
|
|
|
isArrayLike = function(obj) {
|
|
return obj && obj.splice && typeof obj.length == 'number';
|
|
},
|
|
// used to make sure .fn and .inverse are always called with a Scope like object
|
|
makeConvertToScopes = function(orignal, scope, options) {
|
|
return function(updatedScope, updatedOptions) {
|
|
if (updatedScope !== undefined && !(updatedScope instanceof can.view.Scope)) {
|
|
updatedScope = scope.add(updatedScope)
|
|
}
|
|
if (updatedOptions !== undefined && !(updatedOptions instanceof OptionsScope)) {
|
|
updatedOptions = options.add(updatedOptions)
|
|
}
|
|
return orignal(updatedScope, updatedOptions || options)
|
|
}
|
|
};
|
|
|
|
|
|
// ## Mustache
|
|
|
|
Mustache = function(options, helpers) {
|
|
// Support calling Mustache without the constructor.
|
|
// This returns a function that renders the template.
|
|
if (this.constructor != Mustache) {
|
|
var mustache = new Mustache(options);
|
|
return function(data, options) {
|
|
return mustache.render(data, options);
|
|
};
|
|
}
|
|
|
|
// 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.
|
|
can.extend(this, options);
|
|
this.template = this.scanner.scan(this.text, this.name);
|
|
};
|
|
|
|
|
|
// Put Mustache on the `can` object.
|
|
can.Mustache = window.Mustache = Mustache;
|
|
|
|
|
|
Mustache.prototype.
|
|
|
|
render = function(data, options) {
|
|
if (!(data instanceof can.view.Scope)) {
|
|
data = new can.view.Scope(data || {});
|
|
}
|
|
if (!(options instanceof OptionsScope)) {
|
|
options = new OptionsScope(options || {})
|
|
}
|
|
options = options || {};
|
|
|
|
return this.template.fn.call(data, data, options);
|
|
};
|
|
|
|
can.extend(Mustache.prototype, {
|
|
// Share a singleton scanner for parsing templates.
|
|
scanner: new can.view.Scanner({
|
|
// A hash of strings for the scanner to inject at certain points.
|
|
text: {
|
|
// This is the logic to inject at the beginning of a rendered template.
|
|
// This includes initializing the `context` stack.
|
|
start: "", //"var "+SCOPE+"= this instanceof can.view.Scope? this : new can.view.Scope(this);\n",
|
|
scope: SCOPE,
|
|
options: ",options: options",
|
|
argNames: ARG_NAMES
|
|
},
|
|
|
|
// An ordered token registry for the scanner.
|
|
// This needs to be ordered by priority to prevent token parsing errors.
|
|
// Each token follows the following structure:
|
|
// [
|
|
// // Which key in the token map to match.
|
|
// "tokenMapName",
|
|
// // A simple token to match, like "{{".
|
|
// "token",
|
|
// // Optional. A complex (regexp) token to match that
|
|
// // overrides the simple token.
|
|
// "[\\s\\t]*{{",
|
|
// // Optional. A function that executes advanced
|
|
// // manipulation of the matched content. This is
|
|
// // rarely used.
|
|
// function(content){
|
|
// return content;
|
|
// }
|
|
// ]
|
|
tokens: [
|
|
|
|
// Return unescaped
|
|
["returnLeft", "{{{", "{{[{&]"],
|
|
// Full line comments
|
|
["commentFull", "{{!}}", "^[\\s\\t]*{{!.+?}}\\n"],
|
|
|
|
// Inline comments
|
|
["commentLeft", "{{!", "(\\n[\\s\\t]*{{!|{{!)"],
|
|
|
|
// Full line escapes
|
|
// This is used for detecting lines with only whitespace and an escaped tag
|
|
["escapeFull", "{{}}", "(^[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}$)",
|
|
function(content) {
|
|
return {
|
|
before: /^\n.+?\n$/.test(content) ? '\n' : '',
|
|
content: content.match(/\{\{(.+?)\}\}/)[1] || ''
|
|
};
|
|
}
|
|
],
|
|
// Return escaped
|
|
["escapeLeft", "{{"],
|
|
// Close return unescaped
|
|
["returnRight", "}}}"],
|
|
// Close tag
|
|
["right", "}}"]
|
|
],
|
|
|
|
// ## Scanning Helpers
|
|
// This is an array of helpers that transform content that is within escaped tags like `{{token}}`. These helpers are solely for the scanning phase; they are unrelated to Mustache/Handlebars helpers which execute at render time. Each helper has a definition like the following:
|
|
// {
|
|
// // The content pattern to match in order to execute.
|
|
// // Only the first matching helper is executed.
|
|
// name: /pattern to match/,
|
|
// // The function to transform the content with.
|
|
// // @param {String} content The content to transform.
|
|
// // @param {Object} cmd Scanner helper data.
|
|
// // {
|
|
// // insert: "insert command",
|
|
// // tagName: "div",
|
|
// // status: 0
|
|
// // }
|
|
// fn: function(content, cmd) {
|
|
// return 'for text injection' ||
|
|
// { raw: 'to bypass text injection' };
|
|
// }
|
|
// }
|
|
helpers: [
|
|
// ### Partials
|
|
// Partials begin with a greater than sign, like {{> box}}.
|
|
// Partials are rendered at runtime (as opposed to compile time),
|
|
// so recursive partials are possible. Just avoid infinite loops.
|
|
// For example, this template and partial:
|
|
// base.mustache:
|
|
// <h2>Names</h2>
|
|
// {{#names}}
|
|
// {{> user}}
|
|
// {{/names}}
|
|
// user.mustache:
|
|
// <strong>{{name}}</strong>
|
|
{
|
|
name: /^>[\s]*\w*/,
|
|
fn: function(content, cmd) {
|
|
// Get the template name and call back into the render method,
|
|
// passing the name and the current context.
|
|
var templateName = can.trim(content.replace(/^>\s?/, '')).replace(/["|']/g, "");
|
|
return "can.Mustache.renderPartial('" + templateName + "'," + ARG_NAMES + ")";
|
|
}
|
|
},
|
|
|
|
// ### Data Hookup
|
|
// This will attach the data property of `this` to the element
|
|
// its found on using the first argument as the data attribute
|
|
// key.
|
|
// For example:
|
|
// <li id="nameli" {{ data 'name' }}></li>
|
|
// then later you can access it like:
|
|
// can.$('#nameli').data('name');
|
|
|
|
{
|
|
name: /^\s*data\s/,
|
|
fn: function(content, cmd) {
|
|
var attr = content.match(/["|'](.*)["|']/)[1];
|
|
// return a function which calls `can.data` on the element
|
|
// with the attribute name with the current context.
|
|
return "can.proxy(function(__){" +
|
|
// "var context = this[this.length-1];" +
|
|
// "context = context." + STACKED + " ? context[context.length-2] : context; console.warn(this, context);" +
|
|
"can.data(can.$(__),'" + attr + "', this.attr('.')); }, " + SCOPE + ")";
|
|
}
|
|
}, {
|
|
name: /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
|
|
fn: function(content) {
|
|
var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
|
|
parts = content.match(quickFunc);
|
|
|
|
//find
|
|
return "can.proxy(function(__){var " + parts[1] + "=can.$(__);with(" + SCOPE + ".attr('.')){" + parts[2] + "}}, this);";
|
|
}
|
|
},
|
|
// ### Transformation (default)
|
|
// This transforms all content to its interpolated equivalent,
|
|
// including calls to the corresponding helpers as applicable.
|
|
// This outputs the render code for almost all cases.
|
|
// #### Definitions
|
|
// * `context` - This is the object that the current rendering context operates within.
|
|
// Each nested template adds a new `context` to the context stack.
|
|
// * `stack` - Mustache supports nested sections,
|
|
// each of which add their own context to a stack of contexts.
|
|
// Whenever a token gets interpolated, it will check for a match against the
|
|
// last context in the stack, then iterate through the rest of the stack checking for matches.
|
|
// The first match is the one that gets returned.
|
|
// * `Mustache.txt` - This serializes a collection of logic, optionally contained within a section.
|
|
// If this is a simple interpolation, only the interpolation lookup will be passed.
|
|
// If this is a section, then an `options` object populated by the truthy (`options.fn`) and
|
|
// falsey (`options.inverse`) encapsulated functions will also be passed. This section handling
|
|
// exists to support the runtime context nesting that Mustache supports.
|
|
// * `Mustache.get` - This resolves an interpolation reference given a stack of contexts.
|
|
// * `options` - An object containing methods for executing the inner contents of sections or helpers.
|
|
// `options.fn` - Contains the inner template logic for a truthy section.
|
|
// `options.inverse` - Contains the inner template logic for a falsey section.
|
|
// `options.hash` - Contains the merged hash object argument for custom helpers.
|
|
// #### Design
|
|
// This covers the design of the render code that the transformation helper generates.
|
|
// ##### Pseudocode
|
|
// A detailed explanation is provided in the following sections, but here is some brief pseudocode
|
|
// that gives a high level overview of what the generated render code does (with a template similar to
|
|
// `"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"`).
|
|
// *Initialize the render code.*
|
|
// view = []
|
|
// context = []
|
|
// stack = fn { context.concat([this]) }
|
|
// *Render the root section.*
|
|
// view.push( "string" )
|
|
// view.push( can.view.txt(
|
|
// *Render the nested section with `can.Mustache.txt`.*
|
|
// txt(
|
|
// *Add the current context to the stack.*
|
|
// stack(),
|
|
// *Flag this for truthy section mode.*
|
|
// "#",
|
|
// *Interpolate and check the `a` variable for truthyness using the stack with `can.Mustache.get`.*
|
|
// get( "a", stack() ),
|
|
// *Include the nested section's inner logic.
|
|
// The stack argument is usually the parent section's copy of the stack,
|
|
// but it can be an override context that was passed by a custom helper.
|
|
// Sections can nest `0..n` times -- **NESTCEPTION**.*
|
|
// { fn: fn(stack) {
|
|
// *Render the nested section (everything between the `{{#a}}` and `{{/a}}` tokens).*
|
|
// view = []
|
|
// view.push( "string" )
|
|
// view.push(
|
|
// *Add the current context to the stack.*
|
|
// stack(),
|
|
// *Flag this as interpolation-only mode.*
|
|
// null,
|
|
// *Interpolate the `b.c.d.e.name` variable using the stack.*
|
|
// get( "b.c.d.e.name", stack() ),
|
|
// )
|
|
// view.push( "string" )
|
|
// *Return the result for the nested section.*
|
|
// return view.join()
|
|
// }}
|
|
// )
|
|
// ))
|
|
// view.push( "string" )
|
|
// *Return the result for the root section, which includes all nested sections.*
|
|
// return view.join()
|
|
// ##### Initialization
|
|
// Each rendered template is started with the following initialization code:
|
|
// var ___v1ew = [];
|
|
// var ___c0nt3xt = [];
|
|
// ___c0nt3xt.__sc0pe = true;
|
|
// var __sc0pe = function(context, self) {
|
|
// var s;
|
|
// if (arguments.length == 1 && context) {
|
|
// s = !context.__sc0pe ? [context] : context;
|
|
// } else {
|
|
// s = context && context.__sc0pe
|
|
// ? context.concat([self])
|
|
// : __sc0pe(context).concat([self]);
|
|
// }
|
|
// return (s.__sc0pe = true) && s;
|
|
// };
|
|
// The `___v1ew` is the the array used to serialize the view.
|
|
// The `___c0nt3xt` is a stacking array of contexts that slices and expands with each nested section.
|
|
// The `__sc0pe` function is used to more easily update the context stack in certain situations.
|
|
// Usually, the stack function simply adds a new context (`self`/`this`) to a context stack.
|
|
// However, custom helpers will occasionally pass override contexts that need their own context stack.
|
|
// ##### Sections
|
|
// Each section, `{{#section}} content {{/section}}`, within a Mustache template generates a section
|
|
// context in the resulting render code. The template itself is treated like a root section, with the
|
|
// same execution logic as any others. Each section can have `0..n` nested sections within it.
|
|
// Here's an example of a template without any descendent sections.
|
|
// Given the template: `"{{a.b.c.d.e.name}}" == "Phil"`
|
|
// Would output the following render code:
|
|
// ___v1ew.push("\"");
|
|
// ___v1ew.push(can.view.txt(1, '', 0, this, function() {
|
|
// return can.Mustache.txt(__sc0pe(___c0nt3xt, this), null,
|
|
// can.Mustache.get("a.b.c.d.e.name",
|
|
// __sc0pe(___c0nt3xt, this))
|
|
// );
|
|
// }));
|
|
// ___v1ew.push("\" == \"Phil\"");
|
|
// The simple strings will get appended to the view. Any interpolated references (like `{{a.b.c.d.e.name}}`)
|
|
// will be pushed onto the view via `can.view.txt` in order to support live binding.
|
|
// The function passed to `can.view.txt` will call `can.Mustache.txt`, which serializes the object data by doing
|
|
// a context lookup with `can.Mustache.get`.
|
|
// `can.Mustache.txt`'s first argument is a copy of the context stack with the local context `this` added to it.
|
|
// This stack will grow larger as sections nest.
|
|
// The second argument is for the section type. This will be `"#"` for truthy sections, `"^"` for falsey,
|
|
// or `null` if it is an interpolation instead of a section.
|
|
// The third argument is the interpolated value retrieved with `can.Mustache.get`, which will perform the
|
|
// context lookup and return the approriate string or object.
|
|
// Any additional arguments, if they exist, are used for passing arguments to custom helpers.
|
|
// For nested sections, the last argument is an `options` object that contains the nested section's logic.
|
|
// Here's an example of a template with a single nested section.
|
|
// Given the template: `"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"`
|
|
// Would output the following render code:
|
|
// ___v1ew.push("\"");
|
|
// ___v1ew.push(can.view.txt(0, '', 0, this, function() {
|
|
// return can.Mustache.txt(__sc0pe(___c0nt3xt, this), "#",
|
|
// can.Mustache.get("a", __sc0pe(___c0nt3xt, this)),
|
|
// [{
|
|
// _: function() {
|
|
// return ___v1ew.join("");
|
|
// }
|
|
// }, {
|
|
// fn: function(___c0nt3xt) {
|
|
// var ___v1ew = [];
|
|
// ___v1ew.push(can.view.txt(1, '', 0, this,
|
|
// function() {
|
|
// return can.Mustache.txt(
|
|
// __sc0pe(___c0nt3xt, this),
|
|
// null,
|
|
// can.Mustache.get("b.c.d.e.name",
|
|
// __sc0pe(___c0nt3xt, this))
|
|
// );
|
|
// }
|
|
// ));
|
|
// return ___v1ew.join("");
|
|
// }
|
|
// }]
|
|
// )
|
|
// }));
|
|
// ___v1ew.push("\" == \"Phil\"");
|
|
// This is specified as a truthy section via the `"#"` argument. The last argument includes an array of helper methods used with `options`.
|
|
// These act similarly to custom helpers: `options.fn` will be called for truthy sections, `options.inverse` will be called for falsey sections.
|
|
// The `options._` function only exists as a dummy function to make generating the section nesting easier (a section may have a `fn`, `inverse`,
|
|
// or both, but there isn't any way to determine that at compilation time).
|
|
// Within the `fn` function is the section's render context, which in this case will render anything between the `{{#a}}` and `{{/a}}` tokens.
|
|
// This function has `___c0nt3xt` as an argument because custom helpers can pass their own override contexts. For any case where custom helpers
|
|
// aren't used, `___c0nt3xt` will be equivalent to the `__sc0pe(___c0nt3xt, this)` stack created by its parent section. The `inverse` function
|
|
// works similarly, except that it is added when `{{^a}}` and `{{else}}` are used. `var ___v1ew = []` is specified in `fn` and `inverse` to
|
|
// ensure that live binding in nested sections works properly.
|
|
// All of these nested sections will combine to return a compiled string that functions similar to EJS in its uses of `can.view.txt`.
|
|
// #### Implementation
|
|
{
|
|
name: /^.*$/,
|
|
fn: function(content, cmd) {
|
|
var mode = false,
|
|
result = [];
|
|
|
|
// Trim the content so we don't have any trailing whitespace.
|
|
content = can.trim(content);
|
|
|
|
// Determine what the active mode is.
|
|
// * `#` - Truthy section
|
|
// * `^` - Falsey section
|
|
// * `/` - Close the prior section
|
|
// * `else` - Inverted section (only exists within a truthy/falsey section)
|
|
if (content.length && (mode = content.match(/^([#^/]|else$)/))) {
|
|
mode = mode[0];
|
|
switch (mode) {
|
|
|
|
// Open a new section.
|
|
case '#':
|
|
|
|
case '^':
|
|
if (cmd.specialAttribute) {
|
|
result.push(cmd.insert + 'can.view.onlytxt(this,function(){ return ');
|
|
} else {
|
|
result.push(cmd.insert + 'can.view.txt(0,\'' + cmd.tagName + '\',' + cmd.status + ',this,function(){ return ');
|
|
}
|
|
break;
|
|
// Close the prior section.
|
|
|
|
case '/':
|
|
return {
|
|
raw: 'return ___v1ew.join("");}}])}));'
|
|
};
|
|
break;
|
|
}
|
|
|
|
// Trim the mode off of the content.
|
|
content = content.substring(1);
|
|
}
|
|
|
|
// `else` helpers are special and should be skipped since they don't
|
|
// have any logic aside from kicking off an `inverse` function.
|
|
if (mode != 'else') {
|
|
var args = [],
|
|
i = 0,
|
|
hashing = false,
|
|
arg, split, m;
|
|
|
|
// Start the content render block.
|
|
result.push('can.Mustache.txt(\n' + CONTEXT_OBJ + ',\n' + (mode ? '"' + mode + '"' : 'null') + ',');
|
|
|
|
// Parse the helper arguments.
|
|
// This needs uses this method instead of a split(/\s/) so that
|
|
// strings with spaces can be correctly parsed.
|
|
var args = [],
|
|
hashes = [];
|
|
|
|
(can.trim(content) + ' ').replace(argumentsRegExp, function(whole, arg) {
|
|
|
|
// Check for special helper arguments (string/number/boolean/hashes).
|
|
if (i && (m = arg.match(literalNumberStringBooleanRegExp))) {
|
|
// Found a native type like string/number/boolean.
|
|
if (m[2]) {
|
|
args.push(m[0]);
|
|
}
|
|
// Found a hash object.
|
|
else {
|
|
// Addd to the hash object.
|
|
|
|
hashes.push(m[4] + ":" + (m[6] ? m[6] : makeLookupLiteral(m[5])))
|
|
}
|
|
}
|
|
// Otherwise output a normal interpolation reference.
|
|
else {
|
|
args.push(makeLookupLiteral(arg));
|
|
}
|
|
i++;
|
|
});
|
|
|
|
result.push(args.join(","));
|
|
if (hashes.length) {
|
|
result.push(",{" + HASH + ":{" + hashes.join(",") + "}}")
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// Create an option object for sections of code.
|
|
mode && mode != 'else' && result.push(',[\n\n');
|
|
switch (mode) {
|
|
// Truthy section
|
|
case '#':
|
|
result.push('{fn:function(' + ARG_NAMES + '){var ___v1ew = [];');
|
|
break;
|
|
// If/else section
|
|
// Falsey section
|
|
|
|
case 'else':
|
|
result.push('return ___v1ew.join("");}},\n{inverse:function(' + ARG_NAMES + '){\nvar ___v1ew = [];');
|
|
break;
|
|
case '^':
|
|
result.push('{inverse:function(' + ARG_NAMES + '){\nvar ___v1ew = [];');
|
|
break;
|
|
|
|
// Not a section, no mode
|
|
default:
|
|
result.push(')');
|
|
break;
|
|
}
|
|
|
|
// Return a raw result if there was a section, otherwise return the default string.
|
|
result = result.join('');
|
|
return mode ? {
|
|
raw: result
|
|
} : result;
|
|
}
|
|
}
|
|
]
|
|
})
|
|
});
|
|
|
|
// Add in default scanner helpers first.
|
|
// We could probably do this differently if we didn't 'break' on every match.
|
|
var helpers = can.view.Scanner.prototype.helpers;
|
|
for (var i = 0; i < helpers.length; i++) {
|
|
Mustache.prototype.scanner.helpers.unshift(helpers[i]);
|
|
};
|
|
|
|
|
|
Mustache.txt = function(scopeAndOptions, mode, name) {
|
|
var scope = scopeAndOptions.scope,
|
|
options = scopeAndOptions.options,
|
|
args = [],
|
|
helperOptions = {
|
|
fn: function() {},
|
|
inverse: function() {}
|
|
},
|
|
hash,
|
|
context = scope.attr("."),
|
|
getHelper = true;
|
|
|
|
// convert lookup values to actual values in name, arguments, and hash
|
|
for (var i = 3; i < arguments.length; i++) {
|
|
var arg = arguments[i]
|
|
if (mode && can.isArray(arg)) {
|
|
// merge into options
|
|
helperOptions = can.extend.apply(can, [helperOptions].concat(arg))
|
|
} else if (arg && arg[HASH]) {
|
|
hash = arg[HASH];
|
|
// get values on hash
|
|
for (var prop in hash) {
|
|
if (isLookup(hash[prop])) {
|
|
hash[prop] = Mustache.get(hash[prop].get, scopeAndOptions)
|
|
}
|
|
}
|
|
} else if (arg && isLookup(arg)) {
|
|
args.push(Mustache.get(arg.get, scopeAndOptions, false, true));
|
|
} else {
|
|
args.push(arg)
|
|
}
|
|
}
|
|
|
|
if (isLookup(name)) {
|
|
var get = name.get;
|
|
name = Mustache.get(name.get, scopeAndOptions, args.length, false);
|
|
|
|
// Base whether or not we will get a helper on whether or not the original
|
|
// name.get and Mustache.get resolve to the same thing. Saves us from running
|
|
// into issues like {{text}} / {text: 'with'}
|
|
getHelper = (get === name);
|
|
}
|
|
|
|
// overwrite fn and inverse to always convert to scopes
|
|
helperOptions.fn = makeConvertToScopes(helperOptions.fn, scope, options);
|
|
helperOptions.inverse = makeConvertToScopes(helperOptions.inverse, scope, options)
|
|
|
|
// Check for a registered helper or a helper-like function.
|
|
if (helper = (getHelper && (typeof name === "string" && Mustache.getHelper(name, options)) || (can.isFunction(name) && !name.isComputed && {
|
|
fn: name
|
|
}))) {
|
|
// Add additional data to be used by helper functions
|
|
|
|
can.extend(helperOptions, {
|
|
context: context,
|
|
scope: scope,
|
|
contexts: scope,
|
|
hash: hash
|
|
})
|
|
|
|
args.push(helperOptions)
|
|
// Call the helper.
|
|
return helper.fn.apply(context, args) || '';
|
|
}
|
|
|
|
|
|
if (can.isFunction(name)) {
|
|
if (name.isComputed) {
|
|
name = name();
|
|
}
|
|
}
|
|
|
|
// An array of arguments to check for truthyness when evaluating sections.
|
|
var validArgs = args.length ? args : [name],
|
|
// Whether the arguments meet the condition of the section.
|
|
valid = true,
|
|
result = [],
|
|
i, helper, argIsObserve, arg;
|
|
// Validate the arguments based on the section mode.
|
|
if (mode) {
|
|
for (i = 0; i < validArgs.length; i++) {
|
|
arg = validArgs[i];
|
|
argIsObserve = typeof arg !== 'undefined' && isObserveLike(arg);
|
|
// Array-like objects are falsey if their length = 0.
|
|
if (isArrayLike(arg)) {
|
|
// Use .attr to trigger binding on empty lists returned from function
|
|
if (mode == '#') {
|
|
valid = valid && !! (argIsObserve ? arg.attr('length') : arg.length);
|
|
} else if (mode == '^') {
|
|
valid = valid && !(argIsObserve ? arg.attr('length') : arg.length);
|
|
}
|
|
}
|
|
// Otherwise just check if it is truthy or not.
|
|
else {
|
|
valid = mode == '#' ? valid && !! arg : mode == '^' ? valid && !arg : valid;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise interpolate like normal.
|
|
if (valid) {
|
|
switch (mode) {
|
|
// Truthy section.
|
|
case '#':
|
|
// Iterate over arrays
|
|
if (isArrayLike(name)) {
|
|
var isObserveList = isObserveLike(name);
|
|
|
|
// Add the reference to the list in the contexts.
|
|
for (i = 0; i < name.length; i++) {
|
|
result.push(helperOptions.fn(name[i]));
|
|
|
|
// Ensure that live update works on observable lists
|
|
isObserveList && name.attr('' + i);
|
|
}
|
|
return result.join('');
|
|
}
|
|
// Normal case.
|
|
else {
|
|
return helperOptions.fn(name || {}) || '';
|
|
}
|
|
break;
|
|
// Falsey section.
|
|
case '^':
|
|
return helperOptions.inverse(name || {}) || '';
|
|
break;
|
|
default:
|
|
// Add + '' to convert things like numbers to strings.
|
|
// This can cause issues if you are trying to
|
|
// eval on the length but this is the more
|
|
// common case.
|
|
return '' + (name != undefined ? name : '');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
|
|
Mustache.get = function(key, scopeAndOptions, isHelper, isArgument) {
|
|
|
|
// Cache a reference to the current context and options, we will use them a bunch.
|
|
var context = scopeAndOptions.scope.attr('.'),
|
|
options = scopeAndOptions.options || {}
|
|
|
|
// If key is called as a helper,
|
|
if (isHelper) {
|
|
// try to find a registered helper.
|
|
if (Mustache.getHelper(key, options)) {
|
|
return key
|
|
}
|
|
// Support helper-like functions as anonymous helpers.
|
|
// Check if there is a method directly in the "top" context.
|
|
if (scopeAndOptions.scope && can.isFunction(context[key])) {
|
|
return context[key];
|
|
}
|
|
|
|
}
|
|
|
|
// Get a compute (and some helper data) that represents key's value in the current scope
|
|
var computeData = scopeAndOptions.scope.computeData(key, {
|
|
isArgument: isArgument,
|
|
args: [context, scopeAndOptions.scope]
|
|
}),
|
|
compute = computeData.compute;
|
|
|
|
// Bind on the compute to cache its value. We will unbind in a timeout later.
|
|
can.compute.temporarilyBind(compute);
|
|
|
|
// computeData gives us an initial value
|
|
var initialValue = computeData.initialValue;
|
|
|
|
// Use helper over the found value if the found value isn't in the current context
|
|
if ((initialValue === undefined || computeData.scope != scopeAndOptions.scope) && Mustache.getHelper(key, options)) {
|
|
return key
|
|
}
|
|
|
|
|
|
// If there are no dependencies, just return the value.
|
|
if (!compute.hasDependencies) {
|
|
return initialValue;
|
|
} else {
|
|
return compute;
|
|
}
|
|
};
|
|
|
|
|
|
Mustache.resolve = function(value) {
|
|
if (isObserveLike(value) && isArrayLike(value) && value.attr('length')) {
|
|
return value;
|
|
} else if (can.isFunction(value)) {
|
|
return value();
|
|
} else {
|
|
return value;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
var OptionsScope = can.view.Scope.extend({
|
|
init: function(data, parent) {
|
|
if (!data.helpers && !data.partials) {
|
|
data = {
|
|
helpers: data
|
|
}
|
|
}
|
|
can.view.Scope.prototype.init.apply(this, arguments)
|
|
}
|
|
})
|
|
|
|
|
|
// ## Helpers
|
|
// Helpers are functions that can be called from within a template.
|
|
// These helpers differ from the scanner helpers in that they execute
|
|
// at runtime instead of during compilation.
|
|
// Custom helpers can be added via `can.Mustache.registerHelper`,
|
|
// but there are also some built-in helpers included by default.
|
|
// Most of the built-in helpers are little more than aliases to actions
|
|
// that the base version of Mustache simply implies based on the
|
|
// passed in object.
|
|
// Built-in helpers:
|
|
// * `data` - `data` is a special helper that is implemented via scanning helpers.
|
|
// It hooks up the active element to the active data object: `<div {{data "key"}} />`
|
|
// * `if` - Renders a truthy section: `{{#if var}} render {{/if}}`
|
|
// * `unless` - Renders a falsey section: `{{#unless var}} render {{/unless}}`
|
|
// * `each` - Renders an array: `{{#each array}} render {{this}} {{/each}}`
|
|
// * `with` - Opens a context section: `{{#with var}} render {{/with}}`
|
|
Mustache._helpers = {};
|
|
|
|
Mustache.registerHelper = function(name, fn) {
|
|
this._helpers[name] = {
|
|
name: name,
|
|
fn: fn
|
|
};
|
|
};
|
|
|
|
|
|
Mustache.getHelper = function(name, options) {
|
|
var helper = options.attr("helpers." + name)
|
|
return helper ? {
|
|
fn: helper
|
|
} : this._helpers[name];
|
|
};
|
|
|
|
|
|
Mustache.render = function(partial, context, options) {
|
|
// Make sure the partial being passed in
|
|
// isn't a variable like { partial: "foo.mustache" }
|
|
if (!can.view.cached[partial] && context.attr('partial')) {
|
|
partial = context.attr('partial');
|
|
}
|
|
|
|
// Call into `can.view.render` passing the
|
|
// partial and context.
|
|
return can.view.render(partial, context);
|
|
};
|
|
|
|
|
|
Mustache.safeString = function(str) {
|
|
return {
|
|
toString: function() {
|
|
return str;
|
|
}
|
|
}
|
|
};
|
|
|
|
Mustache.renderPartial = function(partialName, scope, options) {
|
|
var partial = options.attr("partials." + partialName)
|
|
if (partial) {
|
|
return partial.render ? partial.render(scope, options) :
|
|
partial(scope, options);
|
|
} else {
|
|
return can.Mustache.render(partialName, scope, options);
|
|
}
|
|
};
|
|
|
|
// The built-in Mustache helpers.
|
|
can.each({
|
|
// Implements the `if` built-in helper.
|
|
|
|
'if': function(expr, options) {
|
|
var value;
|
|
// if it's a function, wrap its value in a compute
|
|
// that will only change values from true to false
|
|
if (can.isFunction(expr)) {
|
|
value = can.compute.truthy(expr)()
|
|
} else {
|
|
value = !! Mustache.resolve(expr)
|
|
}
|
|
|
|
if (value) {
|
|
return options.fn(options.contexts || this);
|
|
} else {
|
|
return options.inverse(options.contexts || this);
|
|
}
|
|
},
|
|
// Implements the `unless` built-in helper.
|
|
|
|
'unless': function(expr, options) {
|
|
if (!Mustache.resolve(expr)) {
|
|
return options.fn(options.contexts || this);
|
|
}
|
|
},
|
|
|
|
// Implements the `each` built-in helper.
|
|
|
|
'each': function(expr, options) {
|
|
// Check if this is a list or a compute that resolves to a list, and setup
|
|
// the incremental live-binding
|
|
|
|
|
|
// First, see what we are dealing with. It's ok to read the compute
|
|
// because can.view.text is only temporarily binding to what is going on here.
|
|
// Calling can.view.lists prevents anything from listening on that compute.
|
|
var resolved = Mustache.resolve(expr);
|
|
|
|
if (resolved instanceof can.List) {
|
|
return can.view.lists && can.view.lists(expr, function(item, index) {
|
|
return options.fn(options.scope.add({
|
|
"@index": index
|
|
}).add(item));
|
|
});
|
|
}
|
|
expr = resolved;
|
|
|
|
if ( !! expr && isArrayLike(expr)) {
|
|
var result = [];
|
|
for (var i = 0; i < expr.length; i++) {
|
|
var index = function() {
|
|
return i;
|
|
};
|
|
|
|
result.push(options.fn(options.scope.add({
|
|
"@index": index
|
|
}).add(expr[i])));
|
|
}
|
|
return result.join('');
|
|
} else if (isObserveLike(expr)) {
|
|
var result = [],
|
|
// listen to keys changing so we can livebind lists of attributes.
|
|
keys = can.Map.keys(expr);
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var key = keys[i];
|
|
result.push(options.fn(options.scope.add({
|
|
"@key": key
|
|
}).add(expr[key])));
|
|
}
|
|
return result.join('');
|
|
} else if (expr instanceof Object) {
|
|
var result = [];
|
|
for (var key in expr) {
|
|
result.push(options.fn(options.scope.add({
|
|
"@key": key
|
|
}).add(expr[key])));
|
|
}
|
|
return result.join('');
|
|
|
|
}
|
|
},
|
|
// Implements the `with` built-in helper.
|
|
|
|
'with': function(expr, options) {
|
|
var ctx = expr;
|
|
expr = Mustache.resolve(expr);
|
|
if ( !! expr) {
|
|
return options.fn(ctx);
|
|
}
|
|
},
|
|
|
|
'log': function(expr, options) {
|
|
if (console !== undefined) {
|
|
if (!options) {
|
|
console.log(expr.context);
|
|
} else {
|
|
console.log(expr, options.context);
|
|
}
|
|
}
|
|
}
|
|
|
|
}, function(fn, name) {
|
|
Mustache.registerHelper(name, fn);
|
|
});
|
|
|
|
// ## Registration
|
|
// Registers Mustache with can.view.
|
|
can.view.register({
|
|
suffix: "mustache",
|
|
|
|
contentType: "x-mustache-template",
|
|
|
|
// Returns a `function` that renders the view.
|
|
script: function(id, src) {
|
|
return "can.Mustache(function(" + ARG_NAMES + ") { " + new Mustache({
|
|
text: src,
|
|
name: id
|
|
}).template.out + " })";
|
|
},
|
|
|
|
renderer: function(id, text) {
|
|
return Mustache({
|
|
text: text,
|
|
name: id
|
|
});
|
|
}
|
|
});
|
|
|
|
return can;
|
|
})(__m2, __m18, __m19, __m20, __m16, __m22);
|
|
|
|
// ## view/bindings/bindings.js
|
|
var __m25 = (function(can) {
|
|
|
|
|
|
|
|
|
|
can.view.Scanner.attribute("can-value", function(data, el) {
|
|
|
|
var attr = el.getAttribute("can-value"),
|
|
value = data.scope.computeData(attr, {
|
|
args: []
|
|
}).compute;
|
|
|
|
if (el.nodeName.toLowerCase() === "input") {
|
|
if (el.type === "checkbox") {
|
|
if (el.hasAttribute("can-true-value")) {
|
|
var trueValue = data.scope.compute(el.getAttribute("can-true-value"))
|
|
} else {
|
|
var trueValue = can.compute(true)
|
|
}
|
|
if (el.hasAttribute("can-false-value")) {
|
|
var falseValue = data.scope.compute(el.getAttribute("can-false-value"))
|
|
} else {
|
|
var falseValue = can.compute(false)
|
|
}
|
|
}
|
|
|
|
if (el.type === "checkbox" || el.type === "radio") {
|
|
new Checked(el, {
|
|
value: value,
|
|
trueValue: trueValue,
|
|
falseValue: falseValue
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
new Value(el, {
|
|
value: value
|
|
})
|
|
});
|
|
|
|
var special = {
|
|
enter: function(data, el, original) {
|
|
return {
|
|
event: "keyup",
|
|
handler: function(ev) {
|
|
if (ev.keyCode == 13) {
|
|
return original.call(this, ev)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
can.view.Scanner.attribute(/can-[\w\.]+/, function(data, el) {
|
|
|
|
var attributeName = data.attr,
|
|
event = data.attr.substr("can-".length),
|
|
handler = function(ev) {
|
|
var attr = el.getAttribute(attributeName),
|
|
scopeData = data.scope.read(attr, {
|
|
returnObserveMethods: true,
|
|
isArgument: true
|
|
});
|
|
return scopeData.value.call(scopeData.parent, data.scope._context, can.$(this), ev)
|
|
};
|
|
|
|
if (special[event]) {
|
|
var specialData = special[event](data, el, handler);
|
|
handler = specialData.handler;
|
|
event = specialData.event;
|
|
}
|
|
|
|
can.bind.call(el, event, handler);
|
|
});
|
|
|
|
|
|
var Value = can.Control.extend({
|
|
init: function() {
|
|
if (this.element[0].nodeName.toUpperCase() === "SELECT") {
|
|
// need to wait until end of turn ...
|
|
setTimeout(can.proxy(this.set, this), 1)
|
|
} else {
|
|
this.set()
|
|
}
|
|
|
|
},
|
|
"{value} change": "set",
|
|
set: function() {
|
|
//this may happen in some edgecases, esp. with selects that are not in DOM after the timeout has fired
|
|
if (!this.element) return;
|
|
|
|
var val = this.options.value();
|
|
this.element[0].value = (typeof val === 'undefined' ? '' : val);
|
|
},
|
|
"change": function() {
|
|
//this may happen in some edgecases, esp. with selects that are not in DOM after the timeout has fired
|
|
if (!this.element) return;
|
|
|
|
this.options.value(this.element[0].value)
|
|
}
|
|
})
|
|
|
|
var Checked = can.Control.extend({
|
|
init: function() {
|
|
this.isCheckebox = (this.element[0].type.toLowerCase() == "checkbox");
|
|
this.check()
|
|
},
|
|
"{value} change": "check",
|
|
"{trueValue} change": "check",
|
|
"{falseValue} change": "check",
|
|
check: function() {
|
|
if (this.isCheckebox) {
|
|
var value = this.options.value(),
|
|
trueValue = this.options.trueValue() || true,
|
|
falseValue = this.options.falseValue() || false;
|
|
|
|
this.element[0].checked = (value == trueValue)
|
|
} else {
|
|
if (this.options.value() === this.element[0].value) {
|
|
this.element[0].checked = true //.prop("checked", true)
|
|
} else {
|
|
this.element[0].checked = false //.prop("checked", false)
|
|
}
|
|
}
|
|
|
|
|
|
},
|
|
"change": function() {
|
|
|
|
if (this.isCheckebox) {
|
|
this.options.value(this.element[0].checked ? this.options.trueValue() : this.options.falseValue());
|
|
} else {
|
|
if (this.element[0].checked) {
|
|
this.options.value(this.element[0].value);
|
|
}
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
})(__m2, __m17, __m8);
|
|
|
|
// ## component/component.js
|
|
var __m1 = (function(can) {
|
|
|
|
var ignoreAttributesRegExp = /dataViewId|class|id/i
|
|
|
|
var Component = can.Component = can.Construct.extend(
|
|
|
|
{
|
|
setup: function() {
|
|
can.Construct.setup.apply(this, arguments);
|
|
|
|
if (can.Component) {
|
|
var self = this;
|
|
this.Control = can.Control.extend({
|
|
_lookup: function(options) {
|
|
return [options.scope, options, window]
|
|
}
|
|
}, can.extend({
|
|
setup: function(el, options) {
|
|
var res = can.Control.prototype.setup.call(this, el, options)
|
|
this.scope = options.scope;
|
|
// call on() whenever scope changes
|
|
var self = this;
|
|
this.on(this.scope, "change", function() {
|
|
self.on();
|
|
self.on(self.scope, "change", arguments.callee);
|
|
});
|
|
return res;
|
|
}
|
|
}, this.prototype.events));
|
|
|
|
var attributeScopeMappings = {};
|
|
// go through scope and get attribute ones
|
|
can.each(this.prototype.scope, function(val, prop) {
|
|
if (val === "@") {
|
|
attributeScopeMappings[prop] = prop;
|
|
}
|
|
})
|
|
this.attributeScopeMappings = attributeScopeMappings;
|
|
|
|
// If scope is an object,
|
|
if (!this.prototype.scope || typeof this.prototype.scope === "object") {
|
|
// use that object as the prototype of an extened Map constructor function.
|
|
// A new instance of that Map constructor function will be created and
|
|
// set as this.scope.
|
|
this.Map = can.Map.extend(this.prototype.scope || {});
|
|
}
|
|
// If scope is a can.Map constructor function,
|
|
else if (this.prototype.scope.prototype instanceof can.Map) {
|
|
// just use that.
|
|
this.Map = this.prototype.scope;
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.prototype.template) {
|
|
if (typeof this.prototype.template == "function") {
|
|
var temp = this.prototype.template
|
|
this.renderer = function() {
|
|
return can.view.frag(temp.apply(null, arguments))
|
|
}
|
|
} else {
|
|
this.renderer = can.view.mustache(this.prototype.template);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
can.view.Scanner.tag(this.prototype.tag, function(el, options) {
|
|
new self(el, options)
|
|
});
|
|
}
|
|
|
|
}
|
|
}, {
|
|
|
|
setup: function(el, hookupOptions) {
|
|
// Setup values passed to component
|
|
var initalScopeData = {},
|
|
component = this,
|
|
twoWayBindings = {},
|
|
// what scope property is currently updating
|
|
scopePropertyUpdating,
|
|
// the object added to the scope
|
|
componentScope;
|
|
|
|
// scope prototype properties marked with an "@" are added here
|
|
can.each(this.constructor.attributeScopeMappings, function(val, prop) {
|
|
initalScopeData[prop] = el.getAttribute(can.hyphenate(val));
|
|
})
|
|
|
|
// get the value in the scope for each attribute
|
|
// the hookup should probably happen after?
|
|
can.each(can.makeArray(el.attributes), function(node, index) {
|
|
|
|
var name = can.camelize(node.nodeName.toLowerCase()),
|
|
value = node.value;
|
|
// ignore attributes already in ScopeMappings
|
|
if (component.constructor.attributeScopeMappings[name] || ignoreAttributesRegExp.test(name)) {
|
|
return;
|
|
}
|
|
|
|
// Cross-bind the value in the scope to this
|
|
// component's scope
|
|
var computeData = hookupOptions.scope.computeData(value, {
|
|
args: []
|
|
}),
|
|
compute = computeData.compute;
|
|
|
|
// bind on this, check it's value, if it has dependencies
|
|
var handler = function(ev, newVal) {
|
|
scopePropertyUpdating = name;
|
|
componentScope.attr(name, newVal);
|
|
scopePropertyUpdating = null;
|
|
}
|
|
// compute only returned if bindable
|
|
|
|
compute.bind("change", handler);
|
|
|
|
// set the value to be added to the scope
|
|
initalScopeData[name] = compute();
|
|
|
|
if (!compute.hasDependencies) {
|
|
compute.unbind("change", handler);
|
|
} else {
|
|
// make sure we unbind (there's faster ways of doing this)
|
|
can.bind.call(el, "removed", function() {
|
|
compute.unbind("change", handler);
|
|
})
|
|
// setup two-way binding
|
|
twoWayBindings[name] = computeData
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (this.constructor.Map) {
|
|
componentScope = new this.constructor.Map(initalScopeData);
|
|
} else if (this.scope instanceof can.Map) {
|
|
componentScope = this.scope;
|
|
} else if (can.isFunction(this.scope)) {
|
|
|
|
var scopeResult = this.scope(initalScopeData, hookupOptions.scope, el);
|
|
// if the function returns a can.Map, use that as the scope
|
|
if (scopeResult instanceof can.Map) {
|
|
componentScope = scopeResult
|
|
} else if (scopeResult.prototype instanceof can.Map) {
|
|
componentScope = new scopeResult(initalScopeData);
|
|
} else {
|
|
componentScope = new(can.Map.extend(scopeResult))(initalScopeData);
|
|
}
|
|
|
|
}
|
|
var handlers = {};
|
|
// setup reverse bindings
|
|
can.each(twoWayBindings, function(computeData, prop) {
|
|
handlers[prop] = function(ev, newVal) {
|
|
// check that this property is not being changed because
|
|
// it's source value just changed
|
|
if (scopePropertyUpdating !== prop) {
|
|
computeData.compute(newVal)
|
|
}
|
|
}
|
|
componentScope.bind(prop, handlers[prop])
|
|
});
|
|
// teardown reverse bindings when element is removed
|
|
can.bind.call(el, "removed", function() {
|
|
can.each(handlers, function(handler, prop) {
|
|
componentScope.unbind(prop, handlers[prop])
|
|
})
|
|
})
|
|
|
|
this.scope = componentScope;
|
|
can.data(can.$(el), "scope", this.scope)
|
|
|
|
// create a real Scope object out of the scope property
|
|
var renderedScope = hookupOptions.scope.add(this.scope),
|
|
|
|
// setup helpers to callback with `this` as the component
|
|
helpers = {};
|
|
|
|
can.each(this.helpers || {}, function(val, prop) {
|
|
if (can.isFunction(val)) {
|
|
helpers[prop] = function() {
|
|
return val.apply(componentScope, arguments)
|
|
}
|
|
}
|
|
});
|
|
|
|
// create a control to listen to events
|
|
this._control = new this.constructor.Control(el, {
|
|
scope: this.scope
|
|
});
|
|
|
|
|
|
var self = this;
|
|
|
|
// if this component has a template (that we've already converted to a renderer)
|
|
if (this.constructor.renderer) {
|
|
// add content to tags
|
|
if (!helpers._tags) {
|
|
helpers._tags = {};
|
|
}
|
|
|
|
// we need be alerted to when a <content> element is rendered so we can put the original contents of the widget in its place
|
|
helpers._tags.content = function(el, rendererOptions) {
|
|
// first check if there was content within the custom tag
|
|
// otherwise, render what was within <content>, the default code
|
|
var subtemplate = hookupOptions.subtemplate || rendererOptions.subtemplate;
|
|
|
|
if (subtemplate) {
|
|
|
|
|
|
// rendererOptions.options is a scope of helpers where `<content>` was found, so
|
|
// the right helpers should already be available.
|
|
// However, _tags.content is going to point to this current content callback. We need to
|
|
// remove that so it will walk up the chain
|
|
|
|
delete helpers._tags.content;
|
|
|
|
can.view.live.replace([el], subtemplate(
|
|
// This is the context of where `<content>` was found
|
|
// which will have the the component's context
|
|
rendererOptions.scope,
|
|
|
|
|
|
|
|
rendererOptions.options));
|
|
|
|
// restore the content tag so it could potentially be used again (as in lists)
|
|
helpers._tags.content = arguments.callee;
|
|
}
|
|
}
|
|
// render the component's template
|
|
var frag = this.constructor.renderer(renderedScope, hookupOptions.options.add(helpers));
|
|
} else {
|
|
// otherwise render the contents between the
|
|
var frag = can.view.frag(hookupOptions.subtemplate ? hookupOptions.subtemplate(renderedScope, hookupOptions.options.add(helpers)) : "");
|
|
}
|
|
can.appendChild(el, frag);
|
|
}
|
|
})
|
|
|
|
if (window.$ && $.fn) {
|
|
$.fn.scope = function(attr) {
|
|
if (attr) {
|
|
return this.data("scope").attr(attr)
|
|
} else {
|
|
return this.data("scope")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
can.scope = function(el, attr) {
|
|
var el = can.$(el);
|
|
if (attr) {
|
|
return can.data(el, "scope").attr(attr)
|
|
} else {
|
|
return can.data(el, "scope")
|
|
}
|
|
}
|
|
|
|
return Component;
|
|
})(__m2, __m8, __m11, __m17, __m25);
|
|
|
|
// ## model/model.js
|
|
var __m26 = (function(can) {
|
|
|
|
// ## model.js
|
|
// `can.Model`
|
|
// _A `can.Map` 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),
|
|
success = true;
|
|
try {
|
|
args[0] = model[func](args[0]);
|
|
} catch (e) {
|
|
success = false;
|
|
d.rejectWith(d, [e].concat(args));
|
|
}
|
|
if (success) {
|
|
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.__reading && can.__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;
|
|
|
|
// `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;
|
|
},
|
|
initializers = {
|
|
// makes a models function that looks up the data in a particular property
|
|
models: function(prop) {
|
|
return 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.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.
|
|
can.getObject(prop || "data", instancesRawData)),
|
|
i = 0;
|
|
|
|
if (typeof raw === 'undefined') {
|
|
throw new Error('Could not get any raw data while converting using .models');
|
|
}
|
|
|
|
|
|
|
|
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(prop) {
|
|
return function(attributes) {
|
|
if (!attributes) {
|
|
return;
|
|
}
|
|
if (typeof attributes.serialize === 'function') {
|
|
attributes = attributes.serialize();
|
|
}
|
|
if (prop) {
|
|
attributes = can.getObject(prop || "data", attributes);
|
|
}
|
|
|
|
var id = attributes[this.id],
|
|
model = (id || id === 0) && this.store[id] ?
|
|
this.store[id].attr(attributes, this.removeAttr || false) : new this(attributes);
|
|
|
|
return model;
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
// 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, attrs) {
|
|
attrs = attrs || {};
|
|
attrs.id = attrs[this.id] = id;
|
|
return attrs;
|
|
}
|
|
},
|
|
|
|
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.Map({
|
|
fullName: "can.Model",
|
|
_reqs: 0,
|
|
|
|
setup: function(base) {
|
|
// create store here if someone wants to use model without inheriting from it
|
|
this.store = {};
|
|
can.Map.setup.apply(this, arguments);
|
|
// Set default list as model list
|
|
if (!can.Model) {
|
|
return;
|
|
}
|
|
|
|
this.List = ML({
|
|
Map: 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;
|
|
})
|
|
}
|
|
});
|
|
can.each(initializers, function(makeInitializer, name) {
|
|
if (typeof self[name] === "string") {
|
|
can.Construct._overwrite(self, base, name, makeInitializer(self[name]))
|
|
}
|
|
})
|
|
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: initializers.models("data"),
|
|
|
|
model: initializers.model()
|
|
},
|
|
|
|
|
|
{
|
|
setup: function(attrs) {
|
|
// try to add things as early as possible to the store (#457)
|
|
// we add things to the store before any properties are even set
|
|
var id = attrs && attrs[this.constructor.id];
|
|
if (can.Model._reqs && id != null) {
|
|
this.constructor.store[id] = this;
|
|
}
|
|
can.Map.prototype.setup.apply(this, arguments)
|
|
},
|
|
|
|
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.Map.prototype._bindsetup.apply(this, arguments);
|
|
},
|
|
|
|
_bindteardown: function() {
|
|
delete this.constructor.store[getId(this)];
|
|
return can.Map.prototype._bindteardown.apply(this, arguments)
|
|
},
|
|
// Change `id`.
|
|
___set: function(prop, val) {
|
|
can.Map.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 `Map.List` except that when their items are
|
|
// destroyed, it automatically gets removed from the list.
|
|
var ML = can.Model.List = can.List({
|
|
setup: function(params) {
|
|
if (can.isPlainObject(params) && !can.isArray(params)) {
|
|
can.List.prototype.setup.apply(this);
|
|
this.replace(this.constructor.Map.findAll(params))
|
|
} else {
|
|
can.List.prototype.setup.apply(this, arguments);
|
|
}
|
|
},
|
|
_changes: function(ev, attr) {
|
|
can.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;
|
|
})(__m2, __m12, __m15);
|
|
|
|
// ## util/string/deparam/deparam.js
|
|
var __m28 = (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;
|
|
})(__m2, __m10);
|
|
|
|
// ## route/route.js
|
|
var __m27 = (function(can) {
|
|
|
|
// ## route.js
|
|
// `can.route`
|
|
// _Helps manage browser history (and client state) by synchronizing the
|
|
// `window.location.hash` with a `can.Map`._
|
|
// 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,
|
|
// Helper for convert any object (or value) to stringified object (or value)
|
|
stringify = function(obj) {
|
|
// Object is array, plain object, Map or List
|
|
if (obj && typeof obj === "object") {
|
|
// Get native object or array from Map or List
|
|
if (obj instanceof can.Map) {
|
|
obj = obj.attr()
|
|
// Clone object to prevent change original values
|
|
} else {
|
|
obj = can.isFunction(obj.slice) ? obj.slice() : can.extend({}, obj)
|
|
}
|
|
// Convert each object property or array item into stringified new
|
|
can.each(obj, function(val, prop) {
|
|
obj[prop] = stringify(val)
|
|
})
|
|
// Object supports toString function
|
|
} else if (obj !== undefined && obj !== null && can.isFunction(obj.toString)) {
|
|
obj = obj.toString()
|
|
}
|
|
|
|
return obj
|
|
},
|
|
removeBackslash = function(str) {
|
|
return str.replace(/\\/g, "")
|
|
},
|
|
// 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,
|
|
// 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.
|
|
onRouteDataChange = function(ev, attr, how, newval) {
|
|
// 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(),
|
|
path = can.route.param(serialized, true);
|
|
can.route._call("setURL", path);
|
|
|
|
lastHash = path
|
|
}, 10);
|
|
};
|
|
|
|
can.route = function(url, defaults) {
|
|
// if route ends with a / and url starts with a /, remove the leading / of the url
|
|
var root = can.route._call("root");
|
|
|
|
if (root.lastIndexOf("/") == root.length - 1 &&
|
|
url.indexOf("/") === 0) {
|
|
url = url.substr(1);
|
|
}
|
|
|
|
|
|
defaults = defaults || {};
|
|
// Extract the variable names and replace with `RegExp` that will match
|
|
// an atual URL with values.
|
|
var names = [],
|
|
res,
|
|
test = "",
|
|
lastIndex = matcher.lastIndex = 0,
|
|
next,
|
|
querySeparator = can.route._call("querySeparator");
|
|
|
|
// res will be something like [":foo","foo"]
|
|
while (res = matcher.exec(url)) {
|
|
names.push(res[1]);
|
|
test += removeBackslash(url.substring(lastIndex, matcher.lastIndex - res[0].length));
|
|
next = "\\" + (removeBackslash(url.substr(matcher.lastIndex, 1)) || 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.
|
|
test += "([^" + next + "]" + (defaults[res[1]] ? "*" : "+") + ")";
|
|
lastIndex = matcher.lastIndex;
|
|
}
|
|
test += url.substr(lastIndex).replace("\\", "")
|
|
// 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(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, {
|
|
|
|
|
|
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]);
|
|
}).replace("\\", ""),
|
|
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._call("querySeparator") + after : "");
|
|
}
|
|
// If no route was found, there is no hash URL, only paramters.
|
|
return can.isEmptyObject(data) ? "" : can.route._call("querySeparator") + can.param(data);
|
|
},
|
|
|
|
deparam: function(url) {
|
|
|
|
// remove the url
|
|
var root = can.route._call("root");
|
|
if (root.lastIndexOf("/") == root.length - 1 &&
|
|
url.indexOf("/") === 0) {
|
|
url = url.substr(1);
|
|
}
|
|
|
|
// 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
|
|
},
|
|
querySeparator = can.route._call("querySeparator"),
|
|
paramsMatcher = can.route._call("paramsMatcher");
|
|
|
|
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] === querySeparator ? 1 : 0)),
|
|
// If there is a remainder and it contains a `&key=value` list deparam it.
|
|
obj = (remainder && 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 !== 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) !== querySeparator) {
|
|
url = querySeparator + url;
|
|
}
|
|
return paramsMatcher.test(url) ? can.deparam(url.slice(1)) : {};
|
|
},
|
|
|
|
data: new can.Map({}),
|
|
|
|
routes: {},
|
|
|
|
ready: function(val) {
|
|
if (val !== true) {
|
|
can.route._setup();
|
|
can.route.setState();
|
|
}
|
|
return can.route;
|
|
},
|
|
|
|
url: function(options, merge) {
|
|
|
|
if (merge) {
|
|
options = can.extend({}, can.route.deparam(can.route._call("matchingPartOfURL")), options);
|
|
}
|
|
return can.route._call("root") + 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 this._call("matchingPartOfURL") === can.route.param(options);
|
|
},
|
|
bindings: {
|
|
hashchange: {
|
|
paramsMatcher: paramsMatcher,
|
|
querySeparator: "&",
|
|
bind: function() {
|
|
can.bind.call(window, 'hashchange', setState);
|
|
},
|
|
unbind: function() {
|
|
can.unbind.call(window, 'hashchange', setState);
|
|
},
|
|
// Gets the part of the url we are determinging the route from.
|
|
// For hashbased routing, it's everything after the #, for
|
|
// pushState it's configurable
|
|
matchingPartOfURL: function() {
|
|
return location.href.split(/#!?/)[1] || "";
|
|
},
|
|
// gets called with the serialized can.route data after a route has changed
|
|
// returns what the url has been updated to (for matching purposes)
|
|
setURL: function(path) {
|
|
location.hash = "#!" + path;
|
|
return path;
|
|
},
|
|
root: "#!"
|
|
}
|
|
},
|
|
defaultBinding: "hashchange",
|
|
currentBinding: null,
|
|
// ready calls setup
|
|
// setup binds and listens to data changes
|
|
// bind listens to whatever you should be listening to
|
|
// data changes tries to set the path
|
|
|
|
// we need to be able to
|
|
// easily kick off calling setState
|
|
// teardown whatever is there
|
|
// turn on a particular binding
|
|
|
|
// called when the route is ready
|
|
_setup: function() {
|
|
if (!can.route.currentBinding) {
|
|
can.route._call("bind");
|
|
can.route.bind("change", onRouteDataChange);
|
|
can.route.currentBinding = can.route.defaultBinding;
|
|
}
|
|
},
|
|
_teardown: function() {
|
|
if (can.route.currentBinding) {
|
|
can.route._call("unbind");
|
|
can.route.unbind("change", onRouteDataChange);
|
|
can.route.currentBinding = null;
|
|
}
|
|
clearTimeout(timer);
|
|
changingData = 0;
|
|
},
|
|
// a helper to get stuff from the current or default bindings
|
|
_call: function() {
|
|
var args = can.makeArray(arguments),
|
|
prop = args.shift(),
|
|
binding = can.route.bindings[can.route.currentBinding || can.route.defaultBinding]
|
|
method = binding[prop];
|
|
if (typeof method === "function") {
|
|
return method.apply(binding, args)
|
|
} else {
|
|
return method;
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// 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', 'on', 'off', 'delegate', 'undelegate', 'removeAttr', 'compute', '_get', '__get'], function(name) {
|
|
can.route[name] = function() {
|
|
// `delegate` and `undelegate` require
|
|
// the `can/map/delegate` plugin
|
|
if (!can.route.data[name]) {
|
|
return;
|
|
}
|
|
|
|
return can.route.data[name].apply(can.route.data, arguments);
|
|
}
|
|
})
|
|
|
|
// Because everything in hashbang is in fact a string this will automaticaly convert new values to string. Works with single value, or deep hashes.
|
|
// Main motivation for this is to prevent double route event call for same value.
|
|
// Example (the problem):
|
|
// When you load page with hashbang like #!&some_number=2 and bind 'some_number' on routes.
|
|
// It will fire event with adding of "2" (string) to 'some_number' property
|
|
// But when you after this set can.route.attr({some_number: 2}) or can.route.attr('some_number', 2). it fires another event with change of 'some_number' from "2" (string) to 2 (integer)
|
|
// This wont happen again with this normalization
|
|
can.route.attr = function(attr, val) {
|
|
var type = typeof attr,
|
|
newArguments;
|
|
|
|
// Reading
|
|
if (val === undefined) {
|
|
newArguments = arguments;
|
|
// Sets object
|
|
} else if (type !== "string" && type !== "number") {
|
|
newArguments = [stringify(attr), val];
|
|
// Sets key - value
|
|
} else {
|
|
newArguments = [attr, stringify(val)];
|
|
}
|
|
|
|
return can.route.data.attr.apply(can.route.data, newArguments)
|
|
}
|
|
|
|
var // 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._call("matchingPartOfURL");
|
|
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);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return can.route;
|
|
})(__m2, __m12, __m28);
|
|
|
|
// ## control/route/route.js
|
|
var __m29 = (function(can) {
|
|
|
|
// ## control/route.js
|
|
// _Controller route integration._
|
|
|
|
can.Control.processors.route = function(el, event, selector, funcName, controller) {
|
|
selector = selector || "";
|
|
if (!can.route.routes[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;
|
|
})(__m2, __m27, __m8);
|
|
|
|
window['can'] = __m4;
|
|
})();
|