{
                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.pop()); }, " + CONTEXT_STACK + ")";
                    }
                },
  • 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

                {
                    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 '^':
                                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;
  • Parse the helper arguments. This needs uses this method instead of a split(/\s/) so that strings with spaces can be correctly parsed.

                            (can.trim(content) + ' ').replace(/((([^\s]+?=)?('.*?'|".*?"))|.*?)\s/g, function (whole, part) {
                                args.push(part);
                            });
  • Start the content render block.

                            result.push('can.Mustache.txt(' + CONTEXT_OBJ + ',' + (mode ? '"' + mode + '"' : 'null') + ',');
  • Iterate through the helper arguments, if there are any.

                            for (; arg = args[i]; i++) {
                                i && result.push(',');
  • Check for special helper arguments (string/number/boolean/hashes).

                                if (i && (m = arg.match(/^(('.*?'|".*?"|[0-9.]+|true|false)|((.+?)=(('.*?'|".*?"|[0-9.]+|true|false)|(.+))))$/))) {
  • Found a native type like string/number/boolean.

                                    if (m[2]) {
                                        result.push(m[0]);
                                    }
  • Found a hash object.

                                    else {
  • Open the hash object.

                                        if (!hashing) {
                                            hashing = true;
                                            result.push('{' + HASH + ':{');
                                        }
  • Add the key/value.

                                        result.push(m[4], ':', m[6] ? m[6] : 'can.Mustache.get("' + m[5].replace(/"/g, '\\"') + '",' + CONTEXT_OBJ + ')');
  • Close the hash if this was the last argument.

                                        if (i == args.length - 1) {
                                            result.push('}}');
                                        }
                                    }
                                }
  • Otherwise output a normal interpolation reference.

                                else {
                                    result.push('can.Mustache.get("' +
  • Include the reference name.

                                    arg.replace(/"/g, '\\"') + '",' +
  • Then the stack of context.

                                    CONTEXT_OBJ +
  • Flag as a helper method to aid performance, if it is a known helper (anything with > 0 arguments).

                                    (i == 0 && args.length > 1 ? ',true' : ',false') + (i > 0 ? ',true' : ',false') + ')');
                                }
                            }
                        }
  • Create an option object for sections of code.

                        mode && mode != 'else' && result.push(',[{_:function(){');
                        switch (mode) {
  • Truthy section

                        case '#':
                            result.push('return ___v1ew.join("");}},{fn:function(' + CONTEXT + '){var ___v1ew = [];');
                            break;
  • If/else section Falsey section

                        case 'else':
                        case '^':
                            result.push('return ___v1ew.join("");}},{inverse:function(' + CONTEXT + '){var ___v1ew = [];');
                            break;
  • Not a section

                        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 (context, mode, name) {
  • Grab the extra arguments to pass to helpers.

            var args = Array.prototype.slice.call(arguments, 3),
  • Create a default options object to pass to the helper.

                options = can.extend.apply(can, [{
                    fn: function () {},
                    inverse: function () {}
                }].concat(mode ? args.pop() : []));
    
    
            var extra = {};
            if (context.context) {
                extra = context.options;
                context = context.context;
            }
  • Check for a registered helper or a helper-like function.

            if (helper = (Mustache.getHelper(name, extra) || (can.isFunction(name) && !name.isComputed && {
                fn: name
            }))) {
  • Use the most recent context as this for the helper.

                var stack = context[STACKED] && context,
                    context = (stack && context[context.length - 1]) || context,
  • Update the options with a function/inverse (the inner templates of a section).

                    opts = {
                        fn: can.proxy(options.fn, context),
                        inverse: can.proxy(options.inverse, context)
                    },
                    lastArg = args[args.length - 1];
  • Store the context stack in the options if one exists

                if (stack) {
                    opts.contexts = stack;
                }
  • Add the hash to options if one exists

                if (lastArg && lastArg[HASH]) {
                    opts.hash = args.pop()[HASH];
                }
                args.push(opts);
  • Call the helper.

                return helper.fn.apply(context, args) || '';
            }
  • if a compute, get the value

            if (can.isFunction(name) && 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' && isObserve(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 = isObserve(name);
  • Add the reference to the list in the contexts.

                        for (i = 0; i < name.length; i++) {
                            result.push(options.fn.call(name[i], context) || '');
  • Ensure that live update works on observable lists

                            isObserveList && name.attr('' + i);
                        }
                        return result.join('');
                    }
  • Normal case.

                    else {
                        return options.fn.call(name || {}, context) || '';
                    }
                    break;
  • Falsey section.

                case '^':
                    return options.inverse.call(name || {}, context) || '';
                    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 (ref, contexts, isHelper, isArgument) {
            var options = contexts.options || {};
            contexts = contexts.context || contexts;
  • Assume the local object is the last context in the stack.

            var obj = contexts[contexts.length - 1],
  • Assume the parent context is the second to last context in the stack.

                context = contexts[contexts.length - 2],
  • Split the reference (like a.b.c) into an array of key names.

                names = ref.split('.'),
                namesLength = names.length,
                value, lastValue, name, i, j,
  • if we walk up and don't find a property, we default to listening on an undefined property of the first context that is an observe

                defaultObserve, defaultObserveName;
  • Handle this references for list iteration: {{.}} or {{this}}

            if (/^\.|this$/.test(ref)) {
  • If context isn't an object, then it was a value passed by a helper so use it as an override.

                if (!/^object|undefined$/.test(typeof context)) {
                    return context || '';
                }
  • Otherwise just return the closest object.

                else {
                    while (value = contexts.pop()) {
                        if (typeof value !== 'undefined') {
                            return value;
                        }
                    }
                    return '';
                }
            }
  • Handle object resolution (like a.b.c).

            else if (!isHelper) {
  • Reverse iterate through the contexts (last in, first out).

                for (i = contexts.length - 1; i >= 0; i--) {
  • Check the context for the reference

                    value = contexts[i];
  • Is the value a compute?

                    if (can.isFunction(value) && value.isComputed) {
                        value = value();
                    }
  • Make sure the context isn't a failed object before diving into it.

                    if (typeof value !== 'undefined' && value !== null) {
                        var isHelper = Mustache.getHelper(ref, options);
                        for (j = 0; j < namesLength; j++) {
  • Keep running up the tree while there are matches.

                            if (typeof value[names[j]] !== 'undefined' && value[names[j]] !== null) {
                                lastValue = value;
                                value = value[name = names[j]];
                            }
  • if there's a name conflict between property and helper property wins

                            else if (isHelper) {
                                return ref;
                            }
  • If it's undefined, still match if the parent is an Observe.

                            else if (isObserve(value)) {
                                defaultObserve = value;
                                defaultObserveName = names[j];
                                lastValue = value = undefined;
                                break;
                            }
                            else {
                                lastValue = value = undefined;
                                break;
                            }
                        }
                    }
  • Found a matched reference.

                    if (value !== undefined) {
                        return Mustache.resolve(value, lastValue, name, isArgument);
                    }
                }
            }
    
            if (defaultObserve &&
  • if there's not a helper by this name and no attribute with this name

            !(Mustache.getHelper(ref) && can.inArray(defaultObserveName, can.Observe.keys(defaultObserve)) === -1)) {
                return defaultObserve.compute(defaultObserveName);
            }
  • Support helpers without arguments, but only if there wasn't a matching data reference. Helpers have priority over local function, see https://github.com/bitovi/canjs/issues/258

            if (value = Mustache.getHelper(ref, options)) {
                return ref;
            } else if (typeof obj !== 'undefined' && obj !== null && can.isFunction(obj[ref])) {
  • Support helper-like functions as anonymous helpers

                return obj[ref];
            }
    
            return '';
        };
    
    
        Mustache.resolve = function (value, lastValue, name, isArgument) {
            if (lastValue && can.isFunction(lastValue[name]) && isArgument) {
                if (lastValue[name].isComputed) {
                    return lastValue[name];
                }
  • Don't execute functions if they are parameters for a helper and are not a can.compute Need to bind it to the original context so that that information doesn't get lost by the helper

                return function () {
                    return lastValue[name].apply(lastValue, arguments);
                };
            } else if (lastValue && can.isFunction(lastValue[name])) {
  • Support functions stored in objects.

                return lastValue[name]();
            }
  • Invoke the length to ensure that Observe.List events fire.

            else if (isObserve(value) && isArrayLike(value) && value.attr('length')) {
                return value;
            }
  • Add support for observes

            else if (lastValue && isObserve(lastValue)) {
                return lastValue.compute(name);
            }
            else if (can.isFunction(value)) {
                return value();
            }
            else {
                return value;
            }
        };
  • 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) {
            return options && options.helpers && options.helpers[name] && {
                fn: options.helpers[name]
            } || this._helpers[name]
            for (var i = 0, helper; helper = [i]; i++) {
  • Find the correct helper

                if (helper.name == name) {
                    return helper;
                }
            }
            return null;
        };
    
    
        Mustache.render = function (partial, context) {
  • Make sure the partial being passed in isn't a variable like { partial: "foo.mustache" }

            if (!can.view.cached[partial] && context[partial]) {
                partial = context[partial];
            }
  • Call into can.view.render passing the partial and context.

            return can.view.render(partial, context);
        };
    
        Mustache.renderPartial = function (partial, context, options) {
            return partial.render ? partial.render(context, options) : partial(context, options);
        };
  • The built-in Mustache helpers.

        can.each({
  • Implements the if built-in helper.

            'if': function (expr, options) {
                if ( !! Mustache.resolve(expr)) {
                    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) {
                expr = Mustache.resolve(expr);
                if ( !! expr && expr.length) {
                    var result = [];
                    for (var i = 0; i < expr.length; i++) {
                        result.push(options.fn(expr[i]));
                    }
                    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);
                }
            }
    
        }, 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(_CONTEXT,_VIEW) { " + new Mustache({
                    text: src,
                    name: id
                }).template.out + " })";
            },
    
            renderer: function (id, text) {
                return Mustache({
                    text: text,
                    name: id
                });
            }
        });
    
        return can;
    })(module["can/util/jquery/jquery.js"], module["can/view/view.js"], module["can/view/scanner.js"], module["can/observe/compute/compute.js"], module["can/view/render.js"]); // ## can/view/modifiers/modifiers.js
    module['can/view/modifiers/modifiers.js'] = (function ($, can) {
  • ---- ADD jQUERY HELPERS ----- converts jquery functions to use views

        var convert, modify, isTemplate, isHTML, isDOM, getCallback,
  • text and val cannot produce an element, so don't run hookups on them

        noHookup = {
            'val': true,
            'text': true
        };
    
        convert = function (func_name) {
  • save the old jQuery helper

            var old = $.fn[func_name];
  • replace it with our new helper

            $.fn[func_name] = function () {
    
                var args = can.makeArray(arguments),
                    callbackNum, callback, self = this,
                    result;
  • if the first arg is a deferred wait until it finishes, and call modify with the result

                if (can.isDeferred(args[0])) {
                    args[0].done(function (res) {
                        modify.call(self, [res], old);
                    })
                    return this;
                }
  • check if a template

                else if (isTemplate(args)) {
  • if we should operate async

                    if ((callbackNum = getCallback(args))) {
                        callback = args[callbackNum];
                        args[callbackNum] = function (result) {
                            modify.call(self, [result], old);
                            callback.call(self, result);
                        };
                        can.view.apply(can.view, args);
                        return this;
                    }
  • call view with args (there might be deferreds)

                    result = can.view.apply(can.view, args);
  • if we got a string back

                    if (!can.isDeferred(result)) {
  • we are going to call the old method with that string

                        args = [result];
                    } else {
  • if there is a deferred, wait until it is done before calling modify

                        result.done(function (res) {
                            modify.call(self, [res], old);
                        })
                        return this;
                    }
                }
                return noHookup[func_name] ? old.apply(this, args) : modify.call(this, args, old);
            };
        };
  • modifies the content of the element but also will run any hookup

        modify = function (args, old) {
            var res, stub, hooks;
  • check if there are new hookups

            for (var hasHookups in can.view.hookups) {
                break;
            }
  • if there are hookups, turn into a frag and insert that by using a frag, the element can be recursively hooked up before insterion

            if (hasHookups && args[0] && isHTML(args[0])) {
                args[0] = can.view.frag(args[0]).childNodes;
            }
  • then insert into DOM

            res = old.apply(this, args);
    
            return res;
        };
  • returns true or false if the args indicate a template is being used $('#foo').html('/path/to/template.ejs',{data}) in general, we want to make sure the first arg is a string and the second arg is data

        isTemplate = function (args) {
  • save the second arg type

            var secArgType = typeof args[1];
  • the first arg is a string

            return typeof args[0] == "string" &&
  • the second arg is an object or function

            (secArgType == 'object' || secArgType == 'function') &&
  • but it is not a dom element

            !isDOM(args[1]);
        };
  • returns true if the arg is a jQuery object or HTMLElement

        isDOM = function (arg) {
            return arg.nodeType || (arg[0] && arg[0].nodeType)
        };
  • returns whether the argument is some sort of HTML data

        isHTML = function (arg) {
            if (isDOM(arg)) {
  • if jQuery object or DOM node we're good

                return true;
            } else if (typeof arg === "string") {
  • if string, do a quick sanity check that we're HTML

                arg = can.trim(arg);
                return arg.substr(0, 1) === "<" && arg.substr(arg.length - 1, 1) === ">" && arg.length >= 3;
            } else {
  • don't know what you are

                return false;
            }
        };
  • returns the callback arg number if there is one (for async view use)

        getCallback = function (args) {
            return typeof args[3] === 'function' ? 3 : typeof args[2] === 'function' && 2;
        };
    
    
        $.fn.hookup = function () {
            can.view.frag(this);
            return this;
        };
    
    
        can.each([
    
        "prepend",
    
        "append",
    
        "after",
    
        "before",
    
        "text",
    
        "html",
    
        "replaceWith", "val"], function (func) {
            convert(func);
        });
    
        return can;
    })(module["jquery/jquery.js"], module["can/view/view.js"]); // ## can/observe/observe.js
    module['can/observe/observe.js'] = (function (can) {
  • observe.js

    can.Observe
    Provides the observable pattern for JavaScript Objects.
    Returns true if something is an object with properties of its own.

        var canMakeObserve = function (obj) {
            return obj && (can.isArray(obj) || can.isPlainObject(obj) || (obj instanceof can.Observe));
        },
  • Removes all listeners.

            unhookup = function (items, namespace) {
                return can.each(items, function (item) {
                    if (item && item.unbind) {
                        item.unbind("change" + namespace);
                    }
                });
            },
  • Listens to changes on val and "bubbles" the event up.
    val - The object to listen for changes on.
    prop - The property name is at on.
    parent - The parent object of prop. ob - (optional) The Observe object constructor list - (optional) The observable list constructor

            hookupBubble = function (val, prop, parent, Ob, List) {
                Ob = Ob || Observe;
                List = List || Observe.List;
  • If it's an array make a list, otherwise a val.

                if (val instanceof Observe) {
  • We have an observe already... Make sure it is not listening to this already

                    unhookup([val], parent._cid);
                } else if (can.isArray(val)) {
                    val = new List(val);
                } else {
                    val = new Ob(val);
                }
  • Listen to all changes and batchTrigger upwards.

                val.bind("change" + parent._cid, function () {
  • batchTrigger the type on this...

                    var args = can.makeArray(arguments),
                        ev = args.shift();
                    args[0] = (prop === "*" ? [parent.indexOf(val), args[0]] : [prop, args[0]]).join(".");
  • track objects dispatched on this observe

                    ev.triggeredNS = ev.triggeredNS || {};
  • if it has already been dispatched exit

                    if (ev.triggeredNS[parent._cid]) {
                        return;
                    }
    
                    ev.triggeredNS[parent._cid] = true;
  • send change event with modified attr to parent

                    can.trigger(parent, ev, args);
  • send modified attr event to parent can.trigger(parent, args[0], args);

                });
    
                return val;
            },
  • An id to track events for a given observe.

            observeId = 0,
  • A helper used to serialize an Observe or Observe.List.
    observe - The observable.
    how - To serialize with attr or serialize.
    where - To put properties, in an {} or [].

            serialize = function (observe, how, where) {
  • Go through each property.

                observe.each(function (val, name) {
  • If the value is an object, and has an attrs or serialize function.

                    where[name] = canMakeObserve(val) && can.isFunction(val[how]) ?
  • Call attrs or serialize to get the original data back.

                    val[how]() :
  • Otherwise return the value.

                    val;
                });
                return where;
            },
            $method = function (name) {
                return function () {
                    return can[name].apply(this, arguments);
                };
            },
            bind = $method('addEvent'),
            unbind = $method('removeEvent'),
            attrParts = function (attr, keepKey) {
                if (keepKey) {
                    return [attr];
                }
                return can.isArray(attr) ? attr : ("" + attr).split(".");
            },
  • Which batch of events this is for -- might not want to send multiple messages on the same batch. This is mostly for event delegation.

            batchNum = 1,
  • how many times has start been called without a stop

            transactions = 0,
  • an array of events within a transaction

            batchEvents = [],
            stopCallbacks = [];
    
    
    
    
        var Observe = can.Observe = can.Construct({
  • keep so it can be overwritten

            bind: bind,
            unbind: unbind,
            id: "id",
            canMakeObserve: canMakeObserve,
  • starts collecting events takes a callback for after they are updated how could you hook into after ejs

            startBatch: function (batchStopHandler) {
                transactions++;
                batchStopHandler && stopCallbacks.push(batchStopHandler);
            },
    
            stopBatch: function (force, callStart) {
                if (force) {
                    transactions = 0;
                } else {
                    transactions--;
                }
    
                if (transactions == 0) {
                    var items = batchEvents.slice(0),
                        callbacks = stopCallbacks.slice(0);
                    batchEvents = [];
                    stopCallbacks = [];
                    batchNum++;
                    callStart && this.startBatch();
                    can.each(items, function (args) {
                        can.trigger.apply(can, args);
                    });
                    can.each(callbacks, function (cb) {
                        cb();
                    });
                }
            },
    
            triggerBatch: function (item, event, args) {
  • Don't send events if initalizing.

                if (!item._init) {
                    if (transactions == 0) {
                        return can.trigger(item, event, args);
                    } else {
                        event = typeof event === "string" ? {
                            type: event
                        } : event;
                        event.batchNum = batchNum;
                        batchEvents.push([
                        item, event, args]);
                    }
                }
            },
    
            keys: function (observe) {
                var keys = [];
                Observe.__reading && Observe.__reading(observe, '__keys');
                for (var keyName in observe._data) {
                    keys.push(keyName);
                }
                return keys;
            }
        },
    
        {
            setup: function (obj) {
  • _data is where we keep the properties.

                this._data = {};
  • The namespace this object uses to listen to events.

                can.cid(this, ".observe");
  • Sets all attrs.

                this._init = 1;
                this.attr(obj);
                this.bind('change' + this._cid, can.proxy(this._changes, this));
                delete this._init;
            },
            _changes: function (ev, attr, how, newVal, oldVal) {
                Observe.triggerBatch(this, {
                    type: attr,
                    batchNum: ev.batchNum
                }, [newVal, oldVal]);
            },
            _triggerChange: function (attr, how, newVal, oldVal) {
                Observe.triggerBatch(this, "change", can.makeArray(arguments))
            },
    
            attr: function (attr, val) {
  • This is super obfuscated for space -- basically, we're checking if the type of the attribute is not a number or a string.

                var type = typeof attr;
                if (type !== "string" && type !== "number") {
                    return this._attrs(attr, val)
                } else if (val === undefined) { // If we are getting a value.
  • Let people know we are reading.

                    Observe.__reading && Observe.__reading(this, attr)
                    return this._get(attr)
                } else {
  • Otherwise we are setting.

                    this._set(attr, val);
                    return this;
                }
            },
    
            each: function () {
                Observe.__reading && Observe.__reading(this, '__keys');
                return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments)))
            },
    
            removeAttr: function (attr) {
  • Info if this is List or not

                var isList = this instanceof can.Observe.List,
  • Convert the attr into parts (if nested).

                    parts = attrParts(attr),
  • The actual property to remove.

                    prop = parts.shift(),
  • The current value.

                    current = isList ? this[prop] : this._data[prop];
  • If we have more parts, call removeAttr on that part.

                if (parts.length) {
                    return current.removeAttr(parts)
                } else {
                    if (isList) {
                        this.splice(prop, 1)
                    } else if (prop in this._data) {
  • Otherwise, delete.

                        delete this._data[prop];
  • Create the event.

                        if (!(prop in this.constructor.prototype)) {
                            delete this[prop]
                        }
  • Let others know the number of keys have changed

                        Observe.triggerBatch(this, "__keys");
                        this._triggerChange(prop, "remove", undefined, current);
    
                    }
                    return current;
                }
            },
  • Reads a property from the object.

            _get: function (attr) {
                var value = typeof attr === 'string' && !! ~attr.indexOf('.') && this.__get(attr);
                if (value) {
                    return value;
                }
  • break up the attr ("foo.bar") into ["foo","bar"]

                var parts = attrParts(attr),
  • get the value of the first attr name ("foo")

                    current = this.__get(parts.shift());
  • if there are other attributes to read

                return parts.length ?
  • and current has a value

                current ?
  • lookup the remaining attrs on current

                current._get(parts) :
  • or if there's no current, return undefined

                undefined :
  • if there are no more parts, return current

                current;
            },
  • Reads a property directly if an attr is provided, otherwise returns the "real" data object itself.

            __get: function (attr) {
                return attr ? this._data[attr] : this._data;
            },
  • Sets attr prop as value on this object where. attr - Is a string of properties or an array of property values. value - The raw value to set.

            _set: function (attr, value, keepKey) {
  • Convert attr to attr parts (if it isn't already).

                var parts = attrParts(attr, keepKey),
  • The immediate prop we are setting.

                    prop = parts.shift(),
  • The current value.

                    current = this.__get(prop);
  • If we have an object and remaining parts.

                if (canMakeObserve(current) && parts.length) {
  • That object should set it (this might need to call attr).

                    current._set(parts, value)
                } else if (!parts.length) {
  • We're in "real" set territory.

                    if (this.__convert) {
                        value = this.__convert(prop, value)
                    }
                    this.__set(prop, value, current)
                } else {
                    throw "can.Observe: Object does not exist"
                }
            },
            __set: function (prop, value, current) {
  • Otherwise, we are setting it on this object. TODO: Check if value is object and transform are we changing the value.

                if (value !== current) {
  • Check if we are adding this for the first time -- if we are, we need to create an add event.

                    var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add";
  • Set the value on data.

                    this.___set(prop,
  • If we are getting an object.

                    canMakeObserve(value) ?
  • Hook it up to send event.

                    hookupBubble(value, prop, this) :
  • Value is normal.

                    value);
    
                    if (changeType == "add") {
  • If there is no current value, let others know that the the number of keys have changed

                        Observe.triggerBatch(this, "__keys", undefined);
    
                    }
  • batchTrigger the change event.

                    this._triggerChange(prop, changeType, value, current);
  • Observe.triggerBatch(this, prop, [value, current]); If we can stop listening to our old value, do it.

                    current && unhookup([current], this._cid);
                }
    
            },
  • Directly sets a property on this object.

            ___set: function (prop, val) {
                this._data[prop] = val;
  • Add property directly for easy writing. Check if its on the prototype so we don't overwrite methods like attrs.

                if (!(prop in this.constructor.prototype)) {
                    this[prop] = val
                }
            },
    
    
            bind: bind,
    
            unbind: unbind,
    
            serialize: function () {
                return serialize(this, 'serialize', {});
            },
    
            _attrs: function (props, remove) {
    
                if (props === undefined) {
                    return serialize(this, 'attr', {})
                }
    
                props = can.extend({}, props);
                var prop, self = this,
                    newVal;
                Observe.startBatch();
                this.each(function (curVal, prop) {
                    newVal = props[prop];
  • If we are merging...

                    if (newVal === undefined) {
                        remove && self.removeAttr(prop);
                        return;
                    }
    
                    if (self.__convert) {
                        newVal = self.__convert(prop, newVal)
                    }
  • if we're dealing with models, want to call _set to let converter run

                    if (newVal instanceof can.Observe) {
                        self.__set(prop, newVal, curVal)
  • if its an object, let attr merge

                    } else if (canMakeObserve(curVal) && canMakeObserve(newVal) && curVal.attr) {
                        curVal.attr(newVal, remove)
  • otherwise just set

                    } else if (curVal != newVal) {
                        self.__set(prop, newVal, curVal)
                    }
    
                    delete props[prop];
                })
  • Add remaining props.

                for (var prop in props) {
                    newVal = props[prop];
                    this._set(prop, newVal, true)
                }
                Observe.stopBatch()
                return this;
            },
    
    
            compute: function (prop) {
                var self = this,
                    computer = function (val) {
                        return self.attr(prop, val);
                    };
    
                return can.compute ? can.compute(computer) : computer;
            }
        });
  • Helpers for observable lists.

        var splice = [].splice,
            list = Observe(
    
            {
                setup: function (instances, options) {
                    this.length = 0;
                    can.cid(this, ".observe")
                    this._init = 1;
                    if (can.isDeferred(instances)) {
                        this.replace(instances)
                    } else {
                        this.push.apply(this, can.makeArray(instances || []));
                    }
                    this.bind('change' + this._cid, can.proxy(this._changes, this));
                    can.extend(this, options);
                    delete this._init;
                },
                _triggerChange: function (attr, how, newVal, oldVal) {
    
                    Observe.prototype._triggerChange.apply(this, arguments)
  • batchTrigger direct add and remove events...

                    if (!~attr.indexOf('.')) {
    
                        if (how === 'add') {
                            Observe.triggerBatch(this, how, [newVal, +attr]);
                            Observe.triggerBatch(this, 'length', [this.length]);
                        } else if (how === 'remove') {
                            Observe.triggerBatch(this, how, [oldVal, +attr]);
                            Observe.triggerBatch(this, 'length', [this.length]);
                        } else {
                            Observe.triggerBatch(this, how, [newVal, +attr])
                        }
    
                    }
    
                },
                __get: function (attr) {
                    return attr ? this[attr] : this;
                },
                ___set: function (attr, val) {
                    this[attr] = val;
                    if (+attr >= this.length) {
                        this.length = (+attr + 1)
                    }
                },
  • Returns the serialized form of this list.

                serialize: function () {
                    return serialize(this, 'serialize', []);
                },
    
                splice: function (index, howMany) {
                    var args = can.makeArray(arguments),
                        i;
    
                    for (i = 2; i < args.length; i++) {
                        var val = args[i];
                        if (canMakeObserve(val)) {
                            args[i] = hookupBubble(val, "*", this, this.constructor.Observe, this.constructor)
                        }
                    }
                    if (howMany === undefined) {
                        howMany = args[1] = this.length - index;
                    }
                    var removed = splice.apply(this, args);
                    can.Observe.startBatch();
                    if (howMany > 0) {
                        this._triggerChange("" + index, "remove", undefined, removed);
                        unhookup(removed, this._cid);
                    }
                    if (args.length > 2) {
                        this._triggerChange("" + index, "add", args.slice(2), removed);
                    }
                    can.Observe.stopBatch();
                    return removed;
                },
    
                _attrs: function (items, remove) {
                    if (items === undefined) {
                        return serialize(this, 'attr', []);
                    }
  • Create a copy.

                    items = can.makeArray(items);
    
                    Observe.startBatch();
                    this._updateAttrs(items, remove);
                    Observe.stopBatch()
                },
    
                _updateAttrs: function (items, remove) {
                    var len = Math.min(items.length, this.length);
    
                    for (var prop = 0; prop < len; prop++) {
                        var curVal = this[prop],
                            newVal = items[prop];
    
                        if (canMakeObserve(curVal) && canMakeObserve(newVal)) {
                            curVal.attr(newVal, remove)
                        } else if (curVal != newVal) {
                            this._set(prop, newVal)
                        } else {
    
                        }
                    }
                    if (items.length > this.length) {
  • Add in the remaining props.

                        this.push.apply(this, items.slice(this.length));
                    } else if (items.length < this.length && remove) {
                        this.splice(items.length)
                    }
                }
            }),
  • Converts to an array of arguments.

            getArgs = function (args) {
                return args[0] && can.isArray(args[0]) ? args[0] : can.makeArray(args);
            };
  • Create push, pop, shift, and unshift

        can.each({
    
            push: "length",
    
            unshift: 0
        },
  • Adds a method name - The method name. where - Where items in the array should be added.

        function (where, name) {
            var orig = [][name]
            list.prototype[name] = function () {
  • Get the items being added.

                var args = [],
  • Where we are going to add items.

                    len = where ? this.length : 0,
                    i = arguments.length,
                    res, val, constructor = this.constructor;
  • Go through and convert anything to an observe that needs to be converted.

                while (i--) {
                    val = arguments[i];
                    args[i] = canMakeObserve(val) ? hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) : val;
                }
  • Call the original method.

                res = orig.apply(this, args);
    
                if (!this.comparator || args.length) {
    
                    this._triggerChange("" + len, "add", args, undefined);
                }
    
                return res;
            }
        });
    
        can.each({
    
            pop: "length",
    
            shift: 0
        },
  • Creates a remove type method

        function (where, name) {
            list.prototype[name] = function () {
    
                var args = getArgs(arguments),
                    len = where && this.length ? this.length - 1 : 0;
    
                var res = [][name].apply(this, args)
  • Create a change where the args are * - Change on potentially multiple properties. remove - Items removed. undefined - The new values (there are none). res - The old, removed values (should these be unbound). len - Where these items were removed.

                this._triggerChange("" + len, "remove", undefined, [res])
    
                if (res && res.unbind) {
                    res.unbind("change" + this._cid)
                }
                return res;
            }
        });
    
        can.extend(list.prototype, {
    
            indexOf: function (item) {
                this.attr('length')
                return can.inArray(item, this)
            },
    
    
            join: [].join,
    
    
            reverse: [].reverse,
    
    
            slice: function () {
                var temp = Array.prototype.slice.apply(this, arguments);
                return new this.constructor(temp);
            },
    
    
            concat: function () {
                var args = [];
                can.each(can.makeArray(arguments), function (arg, i) {
                    args[i] = arg instanceof can.Observe.List ? arg.serialize() : arg;
                });
                return new this.constructor(Array.prototype.concat.apply(this.serialize(), args));
            },
    
    
            forEach: function (cb, thisarg) {
                can.each(this, cb, thisarg || this);
            },
    
    
            replace: function (newList) {
                if (can.isDeferred(newList)) {
                    newList.then(can.proxy(this.replace, this));
                } else {
                    this.splice.apply(this, [0, this.length].concat(can.makeArray(newList || [])));
                }
    
                return this;
            }
        });
    
        Observe.List = list;
        Observe.setup = function () {
            can.Construct.setup.apply(this, arguments);
  • I would prefer not to do it this way. It should be using the attributes plugin to do this type of conversion.

            this.List = Observe.List({
                Observe: this
            }, {});
        }
        return Observe;
    })(module["can/util/jquery/jquery.js"], module["can/construct/construct.js"]); // ## can/model/model.js
    module['can/model/model.js'] = (function (can) {
  • model.js

    can.Model
    A can.Observe that connects to a RESTful interface. Generic deferred piping function

        var pipe = function (def, model, func) {
            var d = new can.Deferred();
            def.then(function () {
                var args = can.makeArray(arguments);
                args[0] = model[func](args[0]);
                d.resolveWith(d, args);
            }, function () {
                d.rejectWith(this, arguments);
            });
    
            if (typeof def.abort === 'function') {
                d.abort = function () {
                    return def.abort();
                }
            }
    
            return d;
        },
            modelNum = 0,
            ignoreHookup = /change.observe\d+/,
            getId = function (inst) {
  • Instead of using attr, use __get for performance. Need to set reading

                can.Observe.__reading && can.Observe.__reading(inst, inst.constructor.id)
                return inst.__get(inst.constructor.id);
            },
  • Ajax options generator function

            ajax = function (ajaxOb, data, type, dataType, success, error) {
    
                var params = {};
  • If we get a string, handle it.

                if (typeof ajaxOb == "string") {
  • If there's a space, it's probably the type.

                    var parts = ajaxOb.split(/\s+/);
                    params.url = parts.pop();
                    if (parts.length) {
                        params.type = parts.pop();
                    }
                } else {
                    can.extend(params, ajaxOb);
                }
  • If we are a non-array object, copy to a new attrs.

                params.data = typeof data == "object" && !can.isArray(data) ? can.extend(params.data || {}, data) : data;
  • Get the url with any templated values filled out.

                params.url = can.sub(params.url, params.data, true);
    
                return can.ajax(can.extend({
                    type: type || "post",
                    dataType: dataType || "json",
                    success: success,
                    error: error
                }, params));
            },
            makeRequest = function (self, type, success, error, method) {
                var args;
  • if we pass an array as self it it means we are coming from the queued request, and we're passing already serialized data self's signature will be: [self, serializedData]

                if (can.isArray(self)) {
                    args = self[1];
                    self = self[0];
                } else {
                    args = self.serialize();
                }
                args = [args];
                var deferred,
  • The model.

                model = self.constructor,
                    jqXHR;
  • destroy does not need data.

                if (type == 'destroy') {
                    args.shift();
                }
  • update and destroy need the id.

                if (type !== 'create') {
                    args.unshift(getId(self));
                }
    
    
                jqXHR = model[type].apply(model, args);
    
                deferred = jqXHR.pipe(function (data) {
                    self[method || type + "d"](data, jqXHR);
                    return self;
                });
  • Hook up abort

                if (jqXHR.abort) {
                    deferred.abort = function () {
                        jqXHR.abort();
                    };
                }
    
                deferred.then(success, error);
                return deferred;
            },
  • This object describes how to make an ajax request for each ajax method.
    The available properties are: url - The default url to use as indicated as a property on the model. type - The default http request type data - A method that takes the arguments and returns data used for ajax.

            ajaxMethods = {
    
                create: {
                    url: "_shortName",
                    type: "post"
                },
    
                update: {
                    data: function (id, attrs) {
                        attrs = attrs || {};
                        var identity = this.id;
                        if (attrs[identity] && attrs[identity] !== id) {
                            attrs["new" + can.capitalize(id)] = attrs[identity];
                            delete attrs[identity];
                        }
                        attrs[identity] = id;
                        return attrs;
                    },
                    type: "put"
                },
    
                destroy: {
                    type: "delete",
                    data: function (id) {
                        var args = {};
                        args.id = args[this.id] = id;
                        return args;
                    }
                },
    
                findAll: {
                    url: "_shortName"
                },
    
                findOne: {}
            },
  • Makes an ajax request function from a string. ajaxMethod - The ajaxMethod object defined above. str - The string the user provided. Ex: findAll: "/recipes.json".

            ajaxMaker = function (ajaxMethod, str) {
  • Return a function that serves as the ajax method.

                return function (data) {
  • If the ajax method has it's own way of getting data, use that.

                    data = ajaxMethod.data ? ajaxMethod.data.apply(this, arguments) :
  • Otherwise use the data passed in.

                    data;
  • Return the ajax method with data and the type provided.

                    return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get")
                }
            }
    
    
    
            can.Model = can.Observe({
                fullName: "can.Model",
                setup: function (base) {
  • create store here if someone wants to use model without inheriting from it

                    this.store = {};
                    can.Observe.setup.apply(this, arguments);
  • Set default list as model list

                    if (!can.Model) {
                        return;
                    }
                    this.List = ML({
                        Observe: this
                    }, {});
                    var self = this,
                        clean = can.proxy(this._clean, self);
  • go through ajax methods and set them up

                    can.each(ajaxMethods, function (method, name) {
  • if an ajax method is not a function, it's either a string url like findAll: "/recipes" or an ajax options object like {url: "/recipes"}

                        if (!can.isFunction(self[name])) {
  • use ajaxMaker to convert that into a function that returns a deferred with the data

                            self[name] = ajaxMaker(method, self[name]);
                        }
  • check if there's a make function like makeFindAll these take deferred function and can do special behavior with it (like look up data in a store)

                        if (self["make" + can.capitalize(name)]) {
  • pass the deferred method to the make method to get back the "findAll" method.

                            var newMethod = self["make" + can.capitalize(name)](self[name]);
                            can.Construct._overwrite(self, base, name, function () {
  • increment the numer of requests

                                this._reqs++;
                                var def = newMethod.apply(this, arguments);
                                var then = def.then(clean, clean);
                                then.abort = def.abort;
  • attach abort to our then and return it

                                return then;
                            })
                        }
                    });
    
                    if (self.fullName == "can.Model" || !self.fullName) {
                        self.fullName = "Model" + (++modelNum);
                    }
  • Add ajax converters.

                    this._reqs = 0;
                    this._url = this._shortName + "/{" + this.id + "}"
                },
                _ajax: ajaxMaker,
                _makeRequest: makeRequest,
                _clean: function () {
                    this._reqs--;
                    if (!this._reqs) {
                        for (var id in this.store) {
                            if (!this.store[id]._bindings) {
                                delete this.store[id];
                            }
                        }
                    }
                    return arguments[0];
                },
    
                models: function (instancesRawData, oldList) {
    
                    if (!instancesRawData) {
                        return;
                    }
    
                    if (instancesRawData instanceof this.List) {
                        return instancesRawData;
                    }
  • Get the list type.

                    var self = this,
                        tmp = [],
                        res = oldList instanceof can.Observe.List ? oldList : new(self.List || ML),
  • Did we get an array?

                        arr = can.isArray(instancesRawData),
  • Did we get a model list?

                        ml = (instancesRawData instanceof ML),
  • Get the raw array of objects.

                        raw = arr ?
  • If an array, return the array.

                        instancesRawData :
  • Otherwise if a model list.

                        (ml ?
  • Get the raw objects from the list.

                        instancesRawData.serialize() :
  • Get the object's data.

                        instancesRawData.data),
                        i = 0;
    
    
    
                    if (res.length) {
                        res.splice(0);
                    }
    
                    can.each(raw, function (rawPart) {
                        tmp.push(self.model(rawPart));
                    });
  • We only want one change event so push everything at once

                    res.push.apply(res, tmp);
    
                    if (!arr) { // Push other stuff onto `array`.
                        can.each(instancesRawData, function (val, prop) {
                            if (prop !== 'data') {
                                res.attr(prop, val);
                            }
                        })
                    }
                    return res;
                },
    
                model: function (attributes) {
                    if (!attributes) {
                        return;
                    }
                    if (attributes instanceof this) {
                        attributes = attributes.serialize();
                    }
                    var id = attributes[this.id],
                        model = (id || id === 0) && this.store[id] ? this.store[id].attr(attributes, this.removeAttr || false) : new this(attributes);
                    if (this._reqs) {
                        this.store[attributes[this.id]] = model;
                    }
                    return model;
                }
            },
    
            {
    
                isNew: function () {
                    var id = getId(this);
                    return !(id || id === 0); // If `null` or `undefined`
                },
    
                save: function (success, error) {
                    return makeRequest(this, this.isNew() ? 'create' : 'update', success, error);
                },
    
                destroy: function (success, error) {
                    if (this.isNew()) {
                        var self = this;
                        return can.Deferred().done(function (data) {
                            self.destroyed(data)
                        }).resolve(self);
                    }
                    return makeRequest(this, 'destroy', success, error, 'destroyed');
                },
    
                bind: function (eventName) {
                    if (!ignoreHookup.test(eventName)) {
                        if (!this._bindings) {
                            this.constructor.store[this.__get(this.constructor.id)] = this;
                            this._bindings = 0;
                        }
                        this._bindings++;
                    }
    
                    return can.Observe.prototype.bind.apply(this, arguments);
                },
    
                unbind: function (eventName) {
                    if (!ignoreHookup.test(eventName)) {
                        this._bindings--;
                        if (!this._bindings) {
                            delete this.constructor.store[getId(this)];
                        }
                    }
                    return can.Observe.prototype.unbind.apply(this, arguments);
                },
  • Change id.

                ___set: function (prop, val) {
                    can.Observe.prototype.___set.call(this, prop, val)
  • If we add an id, move it to the store.

                    if (prop === this.constructor.id && this._bindings) {
                        this.constructor.store[getId(this)] = this;
                    }
                }
            });
    
        can.each({
            makeFindAll: "models",
            makeFindOne: "model"
        }, function (method, name) {
            can.Model[name] = function (oldFind) {
                return function (params, success, error) {
                    var def = pipe(oldFind.call(this, params), this, method);
                    def.then(success, error);
  • return the original promise

                    return def;
                };
            };
        });
    
        can.each([
    
        "created",
    
        "updated",
    
        "destroyed"], function (funcName) {
            can.Model.prototype[funcName] = function (attrs) {
                var stub, constructor = this.constructor;
  • Update attributes if attributes have been passed

                stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs);
  • Call event on the instance

                can.trigger(this, funcName);
  • triggers change event that bubble's like handler( 'change','1.destroyed' ). This is used to remove items on destroyed from Model Lists. but there should be a better way.

                can.trigger(this, "change", funcName)
  • Call event on the instance's Class

                can.trigger(constructor, funcName, this);
            };
        });
  • Model lists are just like Observe.List except that when their items are destroyed, it automatically gets removed from the list.

        var ML = can.Model.List = can.Observe.List({
            setup: function () {
                can.Observe.List.prototype.setup.apply(this, arguments);
  • Send destroy events.

                var self = this;
                this.bind('change', function (ev, how) {
                    if (/\w+\.destroyed/.test(how)) {
                        var index = self.indexOf(ev.target);
                        if (index != -1) {
                            self.splice(index, 1);
                        }
                    }
                })
            }
        })
    
        return can.Model;
    })(module["can/util/jquery/jquery.js"], module["can/observe/observe.js"]); // ## can/view/ejs/ejs.js
    module['can/view/ejs/ejs.js'] = (function (can) {
  • ejs.js

    can.EJS
    Embedded JavaScript Templates. Helper methods.

        var extend = can.extend,
            EJS = function (options) {
  • Supports calling EJS without the constructor This returns a function that renders the template.

                if (this.constructor != EJS) {
                    var ejs = new EJS(options);
                    return function (data, helpers) {
                        return ejs.render(data, helpers);
                    };
                }
  • If we get a function directly, it probably is coming from a steal-packaged view.

                if (typeof options == "function") {
                    this.template = {
                        fn: options
                    };
                    return;
                }
  • Set options on self.

                extend(this, options);
                this.template = this.scanner.scan(this.text, this.name);
            };
    
    
        can.EJS = EJS;
    
    
        EJS.prototype.
    
        render = function (object, extraHelpers) {
            object = object || {};
            return this.template.fn.call(object, object, new EJS.Helpers(object, extraHelpers || {}));
        };
    
        extend(EJS.prototype, {
    
            scanner: new can.view.Scanner({
    
                tokens: [
                    ["templateLeft", "<%%"], // Template
                    ["templateRight", "%>"], // Right Template
                    ["returnLeft", "<%=="], // Return Unescaped
                    ["escapeLeft", "<%="], // Return Escaped
                    ["commentLeft", "<%#"], // Comment
                    ["left", "<%"], // Run --- this is hack for now
                    ["right", "%>"], // Right -> All have same FOR Mustache ...
                    ["returnRight", "%>"]
                ]
            })
        });
    
    
        EJS.Helpers = function (data, extras) {
            this._data = data;
            this._extras = extras;
            extend(this, extras);
        };
    
        EJS.Helpers.prototype = {
  • TODO Deprecated!!

            list: function (list, cb) {
                can.each(list, function (item, i) {
                    cb(item, i, list)
                })
            }
        };
  • Options for steal's build.

        can.view.register({
            suffix: "ejs",
  • returns a function that renders the view.

            script: function (id, src) {
                return "can.EJS(function(_CONTEXT,_VIEW) { " + new EJS({
                    text: src,
                    name: id
                }).template.out + " })";
            },
            renderer: function (id, text) {
                return EJS({
                    text: text,
                    name: id
                });
            }
        });
    
        return can;
    })(module["can/util/jquery/jquery.js"], module["can/view/view.js"], module["can/util/string/string.js"], module["can/observe/compute/compute.js"], module["can/view/scanner.js"], module["can/view/render.js"]); // ## can/observe/attributes/attributes.js
    module['can/observe/attributes/attributes.js'] = (function (can, Observe) {
    
        can.each([can.Observe, can.Model], function (clss) {
  • in some cases model might not be defined quite yet.

            if (clss === undefined) {
                return;
            }
            var isObject = function (obj) {
                return typeof obj === 'object' && obj !== null && obj;
            };
    
            can.extend(clss, {
    
                attributes: {},
    
    
                convert: {
                    "date": function (str) {
                        var type = typeof str;
                        if (type === "string") {
                            return isNaN(Date.parse(str)) ? null : Date.parse(str)
                        } else if (type === 'number') {
                            return new Date(str)
                        } else {
                            return str
                        }
                    },
                    "number": function (val) {
                        return parseFloat(val);
                    },
                    "boolean": function (val) {
                        if (val === 'false' || val === '0' || !val) {
                            return false;
                        }
                        return true;
                    },
                    "default": function (val, oldVal, error, type) {
                        var construct = can.getObject(type),
                            context = window,
                            realType;
  • if type has a . we need to look it up

                        if (type.indexOf(".") >= 0) {
  • get everything before the last .

                            realType = type.substring(0, type.lastIndexOf("."));
  • get the object before the last .

                            context = can.getObject(realType);
                        }
                        return typeof construct == "function" ? construct.call(context, val, oldVal) : val;
                    }
                },
    
                serialize: {
                    "default": function (val, type) {
                        return isObject(val) && val.serialize ? val.serialize() : val;
                    },
                    "date": function (val) {
                        return val && val.getTime()
                    }
                }
            });
  • overwrite setup to do this stuff

            var oldSetup = clss.setup;
    
    
            clss.setup = function (superClass, stat, proto) {
                var self = this;
                oldSetup.call(self, superClass, stat, proto);
    
                can.each(["attributes"], function (name) {
                    if (!self[name] || superClass[name] === self[name]) {
                        self[name] = {};
                    }
                });
    
                can.each(["convert", "serialize"], function (name) {
                    if (superClass[name] != self[name]) {
                        self[name] = can.extend({}, superClass[name], self[name]);
                    }
                });
            };
        });
    
        var oldSetup = can.Observe.prototype.setup;
    
        can.Observe.prototype.setup = function (obj) {
    
            var diff = {};
    
            oldSetup.call(this, obj);
    
            can.each(this.constructor.defaults, function (value, key) {
                if (!this.hasOwnProperty(key)) {
                    diff[key] = value;
                }
            }, this);
    
            this._init = 1;
            this.attr(diff);
            delete this._init;
        };
    
        can.Observe.prototype.__convert = function (prop, value) {
  • check if there is a

            var Class = this.constructor,
                oldVal = this.attr(prop),
                type, converter;
    
            if (Class.attributes) {
  • the type of the attribute

                type = Class.attributes[prop];
                converter = Class.convert[type] || Class.convert['default'];
            }
    
            return value === null || !type ?
  • just use the value

            value :
  • otherwise, pass to the converter

            converter.call(Class, value, oldVal, function () {}, type);
        };
    
        can.Observe.prototype.serialize = function (attrName) {
            var where = {},
                Class = this.constructor,
                attrs = {};
    
            if (attrName != undefined) {
                attrs[attrName] = this[attrName];
            } else {
                attrs = this.__get();
            }
    
            can.each(attrs, function (val, name) {
                var type, converter;
    
                type = Class.attributes ? Class.attributes[name] : 0;
                converter = Class.serialize ? Class.serialize[type] : 0;
  • if the value is an object, and has a attrs or serialize function

                where[name] = val && typeof val.serialize == 'function' ?
  • call attrs or serialize to get the original data back

                val.serialize() :
  • otherwise if we have a converter

                converter ?
  • use the converter

                converter(val, type) :
  • or return the val

                val
            });
    
            return attrName != undefined ? where[attrName] : where;
        };
        return can.Observe;
    })(module["can/util/jquery/jquery.js"], module["can/observe/observe.js"]); // ## can/observe/delegate/delegate.js
    module['can/observe/delegate/delegate.js'] = (function (can) {
  • * - 'this' will be the deepest item changed - 'this' will be any changes within , but will be the this returned tells if the parts part of a delegate matches the broken up props of the event gives the prop to use as 'this' - parts - the attribute name of the delegate split in parts ['foo','*'] - props - the split props of the event that happened ['foo','bar','0'] - returns - the attribute to delegate too ('foo.bar'), or null if not a match

        var delegateMatches = function (parts, props) {
  • check props parts are the same or

            var len = parts.length,
                i = 0,
  • keeps the matched props we will use

                matchedProps = [],
                prop;
  • if the event matches

            for (i; i < len; i++) {
                prop = props[i]
  • if no more props (but we should be matching them) return null

                if (typeof prop !== 'string') {
                    return null;
                } else
  • if we have a "**", match everything

                if (parts[i] == "**") {
                    return props.join(".");
                } else
  • a match, but we want to delegate to "*"

                if (parts[i] == "*") {
  • only do this if there is nothing after ...

                    matchedProps.push(prop);
                }
                else if (prop === parts[i]) {
                    matchedProps.push(prop);
                } else {
                    return null;
                }
            }
            return matchedProps.join(".");
        },
  • gets a change event and tries to figure out which delegates to call

            delegateHandler = function (event, prop, how, newVal, oldVal) {
  • pre-split properties to save some regexp time

                var props = prop.split("."),
                    delegates = (this._observe_delegates || []).slice(0),
                    delegate, attr, matchedAttr, hasMatch, valuesEqual;
                event.attr = prop;
                event.lastAttr = props[props.length - 1];
  • for each delegate

                for (var i = 0; delegate = delegates[i++];) {
  • if there is a batchNum, this means that this event is part of a series of events caused by a single attrs call. We don't want to issue the same event multiple times setting the batchNum happens later

                    if ((event.batchNum && delegate.batchNum === event.batchNum) || delegate.undelegated) {
                        continue;
                    }
  • reset match and values tests

                    hasMatch = undefined;
                    valuesEqual = true;
  • yeah, all this under here has to be redone v for each attr in a delegate

                    for (var a = 0; a < delegate.attrs.length; a++) {
    
                        attr = delegate.attrs[a];
  • check if it is a match

                        if (matchedAttr = delegateMatches(attr.parts, props)) {
                            hasMatch = matchedAttr;
                        }
  • if it has a value, make sure it's the right value if it's set, we should probably check that it has a value no matter what

                        if (attr.value && valuesEqual) {
                            valuesEqual = attr.value === "" + this.attr(attr.attr)
                        } else if (valuesEqual && delegate.attrs.length > 1) {
  • if there are multiple attributes, each has to at least have some value

                            valuesEqual = this.attr(attr.attr) !== undefined
                        }
                    }
  • if there is a match and valuesEqual ... call back

                    if (hasMatch && valuesEqual) {
  • how to get to the changed property from the delegate

                        var from = prop.replace(hasMatch + ".", "");
  • if this event is part of a batch, set it on the delegate to only send one event

                        if (event.batchNum) {
                            delegate.batchNum = event.batchNum
                        }
  • if we listen to change, fire those with the same attrs TODO: the attrs should probably be using from

                        if (delegate.event === 'change') {
                            arguments[1] = from;
                            event.curAttr = hasMatch;
                            delegate.callback.apply(this.attr(hasMatch), can.makeArray(arguments));
                        } else if (delegate.event === how) {
  • if it's a match, callback with the location of the match

                            delegate.callback.apply(this.attr(hasMatch), [event, newVal, oldVal, from]);
                        } else if (delegate.event === 'set' && how == 'add') {
  • if we are listening to set, we should also listen to add

                            delegate.callback.apply(this.attr(hasMatch), [event, newVal, oldVal, from]);
                        }
                    }
    
                }
            };
    
        can.extend(can.Observe.prototype, {
    
            delegate: function (selector, event, handler) {
                selector = can.trim(selector);
                var delegates = this._observe_delegates || (this._observe_delegates = []),
                    attrs = [],
                    selectorRegex = /([^\s=,]+)(?:=("[^",]*"|'[^',]*'|[^\s"',]*))?(,?)\s*/g,
                    matches;
  • parse each property in the selector

                while (matches = selectorRegex.exec(selector)) {
  • we need to do a little doctoring to make up for the quotes.

                    if (matches[2] && $.inArray(matches[2].substr(0, 1), ['"', "'"]) >= 0) {
                        matches[2] = matches[2].substr(1, -1);
                    }
    
                    attrs.push({
  • the attribute name

                        attr: matches[1],
  • the attribute name, pre-split for speed

                        parts: matches[1].split('.'),
  • the value associated with this property (if there was one given)

                        value: matches[2],
  • whether this selector combines with the one after it with AND or OR

                        or: matches[3] === ','
                    });
                }
  • delegates has pre-processed info about the event

                delegates.push({
  • the attrs name for unbinding

                    selector: selector,
  • an object of attribute names and values {type: 'recipe',id: undefined} undefined means a value was not defined

                    attrs: attrs,
                    callback: handler,
                    event: event
                });
                if (delegates.length === 1) {
                    this.bind("change", delegateHandler)
                }
                return this;
            },
    
            undelegate: function (selector, event, handler) {
                selector = can.trim(selector);
    
                var i = 0,
                    delegates = this._observe_delegates || [],
                    delegateOb;
                if (selector) {
                    while (i < delegates.length) {
                        delegateOb = delegates[i];
                        if (delegateOb.callback === handler || (!handler && delegateOb.selector === selector)) {
                            delegateOb.undelegated = true;
                            delegates.splice(i, 1)
                        } else {
                            i++;
                        }
                    }
                } else {
  • remove all delegates

                    delegates = [];
                }
                if (!delegates.length) {
  • can.removeData(this, "_observe_delegates");

                    this.unbind("change", delegateHandler)
                }
                return this;
            }
        });
  • add helpers for testing ..

        can.Observe.prototype.delegate.matches = delegateMatches;
        return can.Observe;
    })(module["can/util/jquery/jquery.js"], module["can/observe/observe.js"]); // ## can/observe/setter/setter.js
    module['can/observe/setter/setter.js'] = (function (can) {
    
        can.classize = function (s, join) {
  • this can be moved out .. used for getter setter

            var parts = s.split(can.undHash),
                i = 0;
            for (; i < parts.length; i++) {
                parts[i] = can.capitalize(parts[i]);
            }
    
            return parts.join(join || '');
        }
    
        var classize = can.classize,
            proto = can.Observe.prototype,
            old = proto.__set;
    
        proto.__set = function (prop, value, current, success, error) {
  • check if there's a setter

            var cap = classize(prop),
                setName = "set" + cap,
                errorCallback = function (errors) {
                    var stub = error && error.call(self, errors);
  • if 'setter' is on the page it will trigger the error itself and we dont want to trigger the event twice. :)

                    if (stub !== false) {
                        can.trigger(self, "error", [prop, errors], true);
                    }
    
                    return false;
                },
                self = this;
  • if we have a setter

            if (this[setName] &&
  • call the setter, if returned value is undefined, this means the setter is async so we do not call update property and return right away

            (value = this[setName](value, function (value) {
                old.call(self, prop, value, current, success, errorCallback)
            }, errorCallback)) === undefined) {
                return;
            }
    
            old.call(self, prop, value, current, success, errorCallback);
    
            return this;
        };
        return can.Observe;
    })(module["can/util/jquery/jquery.js"], module["can/observe/attributes/attributes.js"]); // ## can/observe/validations/validations.js
    module['can/observe/validations/validations.js'] = (function (can) {
  • validations object is by property. You can have validations that span properties, but this way we know which ones to run. proc should return true if there's an error or the error message

        var validate = function (attrNames, options, proc) {
  • normalize argumetns

            if (!proc) {
                proc = options;
                options = {};
            }
    
            options = options || {};
            attrNames = can.makeArray(attrNames)
  • run testIf if it exists

            if (options.testIf && !options.testIf.call(this)) {
                return;
            }
    
            var self = this;
            can.each(attrNames, function (attrName) {
  • Add a test function for each attribute

                if (!self.validations[attrName]) {
                    self.validations[attrName] = [];
                }
    
                self.validations[attrName].push(function (newVal) {
  • if options has a message return that, otherwise, return the error

                    var res = proc.call(this, newVal, attrName);
                    return res === undefined ? undefined : (options.message || res);
                })
            });
        };
    
        var old = can.Observe.prototype.__set;
        can.Observe.prototype.__set = function (prop, value, current, success, error) {
            var self = this,
                validations = self.constructor.validations,
                errorCallback = function (errors) {
                    var stub = error && error.call(self, errors);
  • if 'setter' is on the page it will trigger the error itself and we dont want to trigger the event twice. :)

                    if (stub !== false) {
                        can.trigger(self, "error", [prop, errors], true);
                    }
    
                    return false;
                };
    
            old.call(self, prop, value, current, success, errorCallback);
    
            if (validations && validations[prop]) {
                var errors = self.errors(prop);
                errors && errorCallback(errors)
            }
    
            return this;
        }
    
        can.each([can.Observe, can.Model], function (clss) {
  • in some cases model might not be defined quite yet.

            if (clss === undefined) {
                return;
            }
            var oldSetup = clss.setup;
    
            can.extend(clss, {
                setup: function (superClass) {
                    oldSetup.apply(this, arguments);
                    if (!this.validations || superClass.validations === this.validations) {
                        this.validations = {};
                    }
                },
    
                validate: validate,
    
    
                validationMessages: {
                    format: "is invalid",
                    inclusion: "is not a valid option (perhaps out of range)",
                    lengthShort: "is too short",
                    lengthLong: "is too long",
                    presence: "can't be empty",
                    range: "is out of range"
                },
    
    
                validateFormatOf: function (attrNames, regexp, options) {
                    validate.call(this, attrNames, options, function (value) {
                        if ((typeof value !== 'undefined' && value !== null && value !== '') && String(value).match(regexp) == null) {
                            return this.constructor.validationMessages.format;
                        }
                    });
                },
    
    
                validateInclusionOf: function (attrNames, inArray, options) {
                    validate.call(this, attrNames, options, function (value) {
                        if (typeof value == 'undefined') {
                            return;
                        }
    
                        if (can.grep(inArray, function (elm) {
                            return (elm == value);
                        }).length == 0) {
                            return this.constructor.validationMessages.inclusion;
                        }
                    });
                },
    
    
                validateLengthOf: function (attrNames, min, max, options) {
                    validate.call(this, attrNames, options, function (value) {
                        if (((typeof value === 'undefined' || value === null) && min > 0) || (typeof value !== 'undefined' && value !== null && value.length < min)) {
                            return this.constructor.validationMessages.lengthShort + " (min=" + min + ")";
                        } else if (typeof value != 'undefined' && value !== null && value.length > max) {
                            return this.constructor.validationMessages.lengthLong + " (max=" + max + ")";
                        }
                    });
                },
    
    
                validatePresenceOf: function (attrNames, options) {
                    validate.call(this, attrNames, options, function (value) {
                        if (typeof value == 'undefined' || value === "" || value === null) {
                            return this.constructor.validationMessages.presence;
                        }
                    });
                },
    
    
                validateRangeOf: function (attrNames, low, hi, options) {
                    validate.call(this, attrNames, options, function (value) {
                        if (((typeof value == 'undefined' || value === null) && low > 0) || (typeof value !== 'undefined' && value !== null && (value < low || value > hi))) {
                            return this.constructor.validationMessages.range + " [" + low + "," + hi + "]";
                        }
                    });
                }
            });
        });
    
        can.extend(can.Observe.prototype, {
    
            errors: function (attrs, newVal) {
  • convert attrs to an array

                if (attrs) {
                    attrs = can.isArray(attrs) ? attrs : [attrs];
                }
    
                var errors = {},
                    self = this,
                    attr,
  • helper function that adds error messages to errors object attr - the name of the attribute funcs - the validation functions

                    addErrors = function (attr, funcs) {
                        can.each(funcs, function (func) {
                            var res = func.call(self, isTest ? (self.__convert ? self.__convert(attr, newVal) : newVal) : self[attr]);
                            if (res) {
                                if (!errors[attr]) {
                                    errors[attr] = [];
                                }
                                errors[attr].push(res);
                            }
    
                        });
                    },
                    validations = this.constructor.validations,
                    isTest = attrs && attrs.length === 1 && arguments.length === 2;
  • go through each attribute or validation and add any errors

                can.each(attrs || validations || {}, function (funcs, attr) {
  • if we are iterating through an array, use funcs as the attr name

                    if (typeof attr == 'number') {
                        attr = funcs;
                        funcs = validations[attr];
                    }
  • add errors to the

                    addErrors(attr, funcs || []);
                });
  • return errors as long as we have one

                return can.isEmptyObject(errors) ? null : isTest ? errors[attrs[0]] : errors;
            }
        });
        return can.Observe;
    })(module["can/util/jquery/jquery.js"], module["can/observe/attributes/attributes.js"]); // ## can/util/string/deparam/deparam.js
    module['can/util/string/deparam/deparam.js'] = (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;
    })(module["can/util/jquery/jquery.js"], module["can/util/string/string.js"]); // ## can/route/route.js
    module['can/route/route.js'] = (function (can) {
  • route.js

    can.route
    Helps manage browser history (and client state) by synchronizing the window.location.hash with a can.Observe.
    Helper methods used for matching routes.

        var
  • RegExp used to match route variables of the type ':name'. Any word character or a period is matched.

        matcher = /\:([\w\.]+)/g,
  • Regular expression for identifying &key=value lists.

            paramsMatcher = /^(?:&[^=]+=[^&]*)+/,
  • Converts a JS Object into a list of parameters that can be inserted into an html element tag.

            makeProps = function (props) {
                var tags = [];
                can.each(props, function (val, name) {
                    tags.push((name === 'className' ? 'class' : name) + '="' + (name === "href" ? val : can.esc(val)) + '"');
                });
                return tags.join(" ");
            },
  • Checks if a route matches the data provided. If any route variable is not present in the data, the route does not match. If all route variables are present in the data, the number of matches is returned to allow discerning between general and more specific routes.

            matchesData = function (route, data) {
                var count = 0,
                    i = 0,
                    defaults = {};
  • look at default values, if they match ...

                for (var name in route.defaults) {
                    if (route.defaults[name] === data[name]) {
  • mark as matched

                        defaults[name] = 1;
                        count++;
                    }
                }
                for (; i < route.names.length; i++) {
                    if (!data.hasOwnProperty(route.names[i])) {
                        return -1;
                    }
                    if (!defaults[route.names[i]]) {
                        count++;
                    }
    
                }
    
                return count;
            },
            onready = !0,
            location = window.location,
            wrapQuote = function (str) {
                return (str + '').replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1");
            },
            each = can.each,
            extend = can.extend;
    
        can.route = function (url, defaults) {
            defaults = defaults || {};
  • Extract the variable names and replace with RegExp that will match an atual URL with values.

            var names = [],
                test = url.replace(matcher, function (whole, name, i) {
                    names.push(name);
                    var next = "\\" + (url.substr(i + whole.length, 1) || can.route._querySeparator);
  • a name without a default value HAS to have a value a name that has a default value can be empty The \\ is for string-escaping giving single \ for RegExp escaping.

                    return "([^" + next + "]" + (defaults[name] ? "*" : "+") + ")";
                });
  • Add route in a form that can be easily figured out.

            can.route.routes[url] = {
  • A regular expression that will match the route when variable values are present; i.e. for :page/:type the RegExp is /([\w\.]*)/([\w\.]*)/ which will match for any value of :page and :type (word chars or period).

                test: new RegExp("^" + test + "($|" + wrapQuote(can.route._querySeparator) + ")"),
  • The original URL, same as the index for this entry in routes.

                route: url,
  • An array of all the variable names in this route.

                names: names,
  • Default values provided for the variables.

                defaults: defaults,
  • The number of parts in the URL separated by /.

                length: url.split('/').length
            };
            return can.route;
        };
    
        extend(can.route, {
    
            _querySeparator: '&',
            _paramsMatcher: paramsMatcher,
    
    
            param: function (data, _setRoute) {
  • Check if the provided data keys match the names in any routes; Get the one with the most matches.

                var route,
  • Need to have at least 1 match.

                matches = 0,
                    matchCount, routeName = data.route,
                    propCount = 0;
    
                delete data.route;
    
                each(data, function () {
                    propCount++;
                });
  • Otherwise find route.

                each(can.route.routes, function (temp, name) {
  • best route is the first with all defaults matching

                    matchCount = matchesData(temp, data);
                    if (matchCount > matches) {
                        route = temp;
                        matches = matchCount;
                    }
                    if (matchCount >= propCount) {
                        return false;
                    }
                });
  • If we have a route name in our can.route data, and it's just as good as what currently matches, use that

                if (can.route.routes[routeName] && matchesData(can.route.routes[routeName], data) === matches) {
                    route = can.route.routes[routeName];
                }
  • If this is match...

                if (route) {
                    var cpy = extend({}, data),
  • Create the url by replacing the var names with the provided data. If the default value is found an empty string is inserted.

                        res = route.route.replace(matcher, function (whole, name) {
                            delete cpy[name];
                            return data[name] === route.defaults[name] ? "" : encodeURIComponent(data[name]);
                        }),
                        after;
  • Remove matching default values

                    each(route.defaults, function (val, name) {
                        if (cpy[name] === val) {
                            delete cpy[name];
                        }
                    });
  • The remaining elements of data are added as &amp; separated parameters to the url.

                    after = can.param(cpy);
  • if we are paraming for setting the hash we also want to make sure the route value is updated

                    if (_setRoute) {
                        can.route.attr('route', route.route);
                    }
                    return res + (after ? can.route._querySeparator + after : "");
                }
  • If no route was found, there is no hash URL, only paramters.

                return can.isEmptyObject(data) ? "" : can.route._querySeparator + can.param(data);
            },
    
            deparam: function (url) {
  • See if the url matches any routes by testing it against the route.test RegExp. By comparing the URL length the most specialized route that matches is used.

                var route = {
                    length: -1
                };
                each(can.route.routes, function (temp, name) {
                    if (temp.test.test(url) && temp.length > route.length) {
                        route = temp;
                    }
                });
  • If a route was matched.

                if (route.length > -1) {
    
                    var // Since `RegExp` backreferences are used in `route.test` (parens)
  • the parts will contain the full matched string and each variable (back-referenced) value.

                    parts = url.match(route.test),
  • Start will contain the full matched string; parts contain the variable values.

                        start = parts.shift(),
  • The remainder will be the &amp;key=value list at the end of the URL.

                        remainder = url.substr(start.length - (parts[parts.length - 1] === can.route._querySeparator ? 1 : 0)),
  • If there is a remainder and it contains a &amp;key=value list deparam it.

                        obj = (remainder && can.route._paramsMatcher.test(remainder)) ? can.deparam(remainder.slice(1)) : {};
  • Add the default values for this route.

                    obj = extend(true, {}, route.defaults, obj);
  • Overwrite each of the default values in obj with those in parts if that part is not empty.

                    each(parts, function (part, i) {
                        if (part && part !== can.route._querySeparator) {
                            obj[route.names[i]] = decodeURIComponent(part);
                        }
                    });
                    obj.route = route.route;
                    return obj;
                }
  • If no route was matched, it is parsed as a &amp;key=value list.

                if (url.charAt(0) !== can.route._querySeparator) {
                    url = can.route._querySeparator + url;
                }
                return can.route._paramsMatcher.test(url) ? can.deparam(url.slice(1)) : {};
            },
    
            data: new can.Observe({}),
    
            routes: {},
    
            ready: function (val) {
                if (val === false) {
                    onready = val;
                }
                if (val === true || onready === true) {
                    can.route._setup();
                    setState();
                }
                return can.route;
            },
    
            url: function (options, merge) {
                if (merge) {
                    options = extend({}, curParams, options)
                }
                return "#!" + can.route.param(options);
            },
    
            link: function (name, options, props, merge) {
                return "<a " + makeProps(
                extend({
                    href: can.route.url(options, merge)
                }, props)) + ">" + name + "</a>";
            },
    
            current: function (options) {
                return location.hash == "#!" + can.route.param(options)
            },
            _setup: function () {
  • If the hash changes, update the can.route.data.

                can.bind.call(window, 'hashchange', setState);
            },
            _getHash: function () {
                return location.href.split(/#!?/)[1] || "";
            },
            _setHash: function (serialized) {
                var path = (can.route.param(serialized, true));
                location.hash = "#!" + path;
                return path;
            }
        });
  • The functions in the following list applied to can.route (e.g. can.route.attr('...')) will instead act on the can.route.data observe.

        each(['bind', 'unbind', 'delegate', 'undelegate', 'attr', 'removeAttr'], function (name) {
            can.route[name] = function () {
                return can.route.data[name].apply(can.route.data, arguments)
            }
        })
    
        var // A ~~throttled~~ debounced function called multiple times will only fire once the
  • timer runs down. Each call resets the timer.

        timer,
  • Intermediate storage for can.route.data.

        curParams,
  • Deparameterizes the portion of the hash of interest and assign the values to the can.route.data removing existing values no longer in the hash. setState is called typically by hashchange which fires asynchronously So it's possible that someone started changing the data before the hashchange event fired. For this reason, it will not set the route data if the data is changing or the hash already matches the hash that was set.

        setState = can.route.setState = function () {
            var hash = can.route._getHash();
            curParams = can.route.deparam(hash);
  • if the hash data is currently changing, or the hash is what we set it to anyway, do NOT change the hash

            if (!changingData || hash !== lastHash) {
                can.route.attr(curParams, true);
            }
        },
  • The last hash caused by a data change

            lastHash,
  • Are data changes pending that haven't yet updated the hash

            changingData;
  • If the can.route.data changes, update the hash. Using .serialize() retrieves the raw data contained in the observable. This function is throttled debounced so it only updates once even if multiple values changed. This might be able to use batchNum and avoid this.

        can.route.bind("change", function (ev, attr) {
  • indicate that data is changing

            changingData = 1;
            clearTimeout(timer);
            timer = setTimeout(function () {
  • indicate that the hash is set to look like the data

                changingData = 0;
                var serialized = can.route.data.serialize();
    
                lastHash = can.route._setHash(serialized);
            }, 1);
        });
  • onready event...

        can.bind.call(document, "ready", can.route.ready);
  • Libraries other than jQuery don't execute the document ready listener if we are already DOM ready

        if ((document.readyState === 'complete' || document.readyState === "interactive") && onready) {
            can.route.ready();
        }
  • extend route to have a similar property that is often checked in mustache to determine an object's observability

        can.route.constructor.canMakeObserve = can.Observe.canMakeObserve;
    
        return can.route;
    })(module["can/util/jquery/jquery.js"], module["can/observe/observe.js"], module["can/util/string/deparam/deparam.js"]); // ## can/util/object/object.js
    module['can/util/object/object.js'] = (function (can) {
    
        var isArray = can.isArray,
  • essentially returns an object that has all the must have comparisons ... must haves, do not return true when provided undefined

            cleanSet = function (obj, compares) {
                var copy = can.extend({}, obj);
                for (var prop in copy) {
                    var compare = compares[prop] === undefined ? compares["*"] : compares[prop];
                    if (same(copy[prop], undefined, compare)) {
                        delete copy[prop]
                    }
                }
                return copy;
            },
            propCount = function (obj) {
                var count = 0;
                for (var prop in obj) count++;
                return count;
            };
    
        can.Object = {};
    
        var same = can.Object.same = function (a, b, compares, aParent, bParent, deep) {
            var aType = typeof a,
                aArray = isArray(a),
                comparesType = typeof compares,
                compare;
    
            if (comparesType == 'string' || compares === null) {
                compares = compareMethods[compares];
                comparesType = 'function'
            }
            if (comparesType == 'function') {
                return compares(a, b, aParent, bParent)
            }
            compares = compares || {};
    
            if (a instanceof Date) {
                return a === b;
            }
            if (deep === -1) {
                return aType === 'object' || a === b;
            }
            if (aType !== typeof b || aArray !== isArray(b)) {
                return false;
            }
            if (a === b) {
                return true;
            }
            if (aArray) {
                if (a.length !== b.length) {
                    return false;
                }
                for (var i = 0; i < a.length; i++) {
                    compare = compares[i] === undefined ? compares["*"] : compares[i]
                    if (!same(a[i], b[i], a, b, compare)) {
                        return false;
                    }
                };
                return true;
            } else if (aType === "object" || aType === 'function') {
                var bCopy = can.extend({}, b);
                for (var prop in a) {
                    compare = compares[prop] === undefined ? compares["*"] : compares[prop];
                    if (!same(a[prop], b[prop], compare, a, b, deep === false ? -1 : undefined)) {
                        return false;
                    }
                    delete bCopy[prop];
                }
  • go through bCopy props ... if there is no compare .. return false

                for (prop in bCopy) {
                    if (compares[prop] === undefined || !same(undefined, b[prop], compares[prop], a, b, deep === false ? -1 : undefined)) {
                        return false;
                    }
                }
                return true;
            }
            return false;
        };
    
        can.Object.subsets = function (checkSet, sets, compares) {
            var len = sets.length,
                subsets = [],
                checkPropCount = propCount(checkSet),
                setLength;
    
            for (var i = 0; i < len; i++) {
  • check this subset

                var set = sets[i];
                if (can.Object.subset(checkSet, set, compares)) {
                    subsets.push(set)
                }
            }
            return subsets;
        };
    
        can.Object.subset = function (subset, set, compares) {
  • go through set {type: 'folder'} and make sure every property is in subset {type: 'folder', parentId :5} then make sure that set has fewer properties make sure we are only checking 'important' properties in subset (ones that have to have a value)

            var setPropCount = 0,
                compares = compares || {};
    
            for (var prop in set) {
    
                if (!same(subset[prop], set[prop], compares[prop], subset, set)) {
                    return false;
                }
            }
            return true;
        }
    
        var compareMethods = {
            "null": function () {
                return true;
            },
            i: function (a, b) {
                return ("" + a).toLowerCase() == ("" + b).toLowerCase()
            }
        }
    
        return can.Object;
    
    })(module["can/util/jquery/jquery.js"]); // ## can/observe/backup/backup.js
    module['can/observe/backup/backup.js'] = (function (can) {
        var flatProps = function (a) {
            var obj = {};
            for (var prop in a) {
                if (typeof a[prop] !== 'object' || a[prop] === null || a[prop] instanceof Date) {
                    obj[prop] = a[prop]
                }
            }
            return obj;
        };
    
        can.extend(can.Observe.prototype, {
    
    
            backup: function () {
                this._backupStore = this._attrs();
                return this;
            },
    
    
            isDirty: function (checkAssociations) {
                return this._backupStore && !can.Object.same(this._attrs(), this._backupStore, undefined, undefined, undefined, !! checkAssociations);
            },
    
    
            restore: function (restoreAssociations) {
                var props = restoreAssociations ? this._backupStore : flatProps(this._backupStore)
    
                if (this.isDirty(restoreAssociations)) {
                    this._attrs(props);
                }
    
                return this;
            }
    
        })
    
        return can.Observe;
    })(module["can/util/jquery/jquery.js"], module["can/observe/observe.js"], module["can/util/object/object.js"]);
    
    window.define = module._define;
    
    window.module = module._orig;