From 013d79d721460b245860d18a34e17743d5cdaed5 Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Thu, 19 Dec 2013 14:50:31 +0000 Subject: [PATCH] Root panel works as a component. --- assets/css/library.css.sass | 5 +- assets/js/components/panel.js | 39 + assets/js/controls/library.js | 2 +- assets/js/libs/can.ejs.js | 2 +- assets/js/libs/can.jquery.js | 6951 ++++++++++++++++++++++++++++ assets/js/libs/can.jquery.min.js | 8 +- assets/js/mpd-client.js | 3 +- public/views/library.ejs | 2 +- public/views/library.mustache | 5 + public/views/library/root.mustache | 5 + public/views/panel.mustache | 6 + 11 files changed, 7019 insertions(+), 9 deletions(-) create mode 100644 assets/js/components/panel.js create mode 100644 assets/js/libs/can.jquery.js create mode 100644 public/views/library.mustache create mode 100644 public/views/library/root.mustache create mode 100644 public/views/panel.mustache diff --git a/assets/css/library.css.sass b/assets/css/library.css.sass index 321de6a..389a607 100644 --- a/assets/css/library.css.sass +++ b/assets/css/library.css.sass @@ -32,12 +32,15 @@ .browser position: absolute height: 100% + width: 100% @include transition(left 0.25s ease-in-out) + ul, ol + width: 100% ul.root, ol.artists, ol.albums @extend .list ol.songs @extend .song-list - div + panel position: absolute width: $interface-width height: 100% diff --git a/assets/js/components/panel.js b/assets/js/components/panel.js new file mode 100644 index 0000000..8e55378 --- /dev/null +++ b/assets/js/components/panel.js @@ -0,0 +1,39 @@ +can.Component.extend({ + + init: function() { + console.log('Initializing'); + }, + + tag: 'panel', + + template: can.view('views/panel.mustache'), + + scope: { + show: '@', + artist: '@', + album: '@', + title: function() { + if (this.show == 'root') + return 'Library'; + }, + fetchItems: { + root: new can.Map, + artists: Artist.findAll({}), + albums: Album.findAll({ artist: this.artist }), + songs: Song.findAll({ artist: this.artist, album: this.album }) + } + }, + + events: { + inserted: function() { + console.log('Panel inserted.'); + } + }, + + helpers: { + renderItems: function() { + return can.view.render('views/library/' + this.show, {}); + } + } + +}); diff --git a/assets/js/controls/library.js b/assets/js/controls/library.js index f54db52..80a1902 100644 --- a/assets/js/controls/library.js +++ b/assets/js/controls/library.js @@ -4,7 +4,7 @@ var Library = can.Control.extend({ this.element = element; this.browser = new can.Model({ title: 'Library', currentPane: 0 }); element.html( - can.view('views/library.ejs', { browser: this.browser }) + can.view('views/library.mustache', { browser: this.browser }) ); var rootControl = new Pane('#library .root', { show: 'root' }); this.panes = new can.List([rootControl]); diff --git a/assets/js/libs/can.ejs.js b/assets/js/libs/can.ejs.js index ba25d30..1d3c9e3 100755 --- a/assets/js/libs/can.ejs.js +++ b/assets/js/libs/can.ejs.js @@ -2,7 +2,7 @@ * CanJS - 2.0.3 * http://canjs.us/ * Copyright (c) 2013 Bitovi - * Tue, 26 Nov 2013 18:21:40 GMT + * Thu, 19 Dec 2013 10:56:42 GMT * Licensed MIT * Includes: can/view/ejs * Download from: http://canjs.com diff --git a/assets/js/libs/can.jquery.js b/assets/js/libs/can.jquery.js new file mode 100644 index 0000000..80ea2b2 --- /dev/null +++ b/assets/js/libs/can.jquery.js @@ -0,0 +1,6951 @@ +/*! + * CanJS - 2.0.3 + * http://canjs.us/ + * Copyright (c) 2013 Bitovi + * Thu, 19 Dec 2013 10:56:41 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.3'; + + 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) { + 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) { + 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) { + can.trigger(elem, "inserted", [], false); + children = can.makeArray(elem.getElementsByTagName("*")); + 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; + } + }); + + // 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(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, + addToMap = function(obj, instance) { + var teardown = false; + if (!madeMap) { + teardown = true; + 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; + }, + 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 = {}; + } + for (var prop in this.prototype) { + if (typeof this.prototype[prop] !== "function") { + this.defaults[prop] = this.prototype[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 + }, {}); + } + + }, + // keep so it can be overwritten + bind: can.bindAndSetup, + on: can.bindAndSetup, + unbind: can.unbindAndTeardown, + off: can.unbindAndTeardown, + id: "id", + helpers: { + 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 && addToMap(obj, this); + + var data = can.extend(can.extend(true, {}, this.constructor.defaults || {}), obj) + this.attr(data); + if (teardownMapping) { + teardownMap() + } + this.bind('change', can.proxy(this._changes, this)); + + delete this._init; + }, + + _setupComputes: function() { + var prototype = this.constructor.prototype; + this._computedBindings = {} + for (var prop in prototype) { + if (prototype[prop] && prototype[prop].isComputed) { + this[prop] = prototype[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, + + list = Map( + + { + + Map: Map + + }, + + { + setup: function(instances, options) { + this.length = 0; + can.cid(this, ".map") + this._init = 1; + if (can.isDeferred(instances)) { + this.replace(instances) + } else { + this.push.apply(this, can.makeArray(instances || [])); + } + // this change needs to be ignored + this.bind('change', 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); + 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 `