<commit_before>var germanMapRenderer = {

    width: 960,
    height: 750,
    scale: 500,

    setUpMap: function setUpMap(width, height, pluginFilePrefix, ajaxIdentifier, initialYear, red, green, blue){

        var projection = d3.geo.mercator()
            //.center([10.5, 51.35])
            .scale(500)
            .translate([width / 2, height / 2]);

        var path = d3.geo.path()
            .projection(projection);

        var svg = d3.select("#germanMap")
            .attr("width", width)
            .attr("height", height);

        d3.json(pluginFilePrefix + "de.json", function(error, de) {

            var states = topojson.feature(de, de.objects.states);

            svg.selectAll(".state")
                .data(states.features)
                .enter().append("path")
                .attr("class", function(d) { return d.id + " germanState"; })
                .attr("d", path)
                .on("click", germanMapRenderer.onClickState);

            germanMapRenderer.updateMapPopulationDisplay(ajaxIdentifier, initialYear, red, green, blue);

            var generatedChart = document.getElementsByTagName("svg")[0];

            var bbox = generatedChart.getBBox();
            var viewBox = [bbox.x, bbox.y, bbox.width, bbox.height].join(" ");
            generatedChart.setAttribute("viewBox", viewBox);
        });

    },

    onClickState: function clickedState(d){
        apex.event.trigger(document, 'showstateinfo', {adm1_code: d.id});
    },

    registerChangeTime: function registerChangeTime(ajaxIdentifier, red, green, blue){

        apex.jQuery('#timeline li').click(function(){

            var $period = apex.jQuery(this);
            var clickedTimePeriodYear = apex.jQuery(this).text();

            $('#timeline li').removeClass('active');
            $period.addClass('active');

            germanMapRenderer.updateMapPopulationDisplay(ajaxIdentifier, clickedTimePeriodYear, red, green, blue);

            //Send a DA event should any further logic want to be introduced
            apex.event.trigger($period, 'gsm_timeclicked', { year: clickedTimePeriodYear });

        });
    },

    updateMapPopulationDisplay: function updateMapPopulationDisplay(ajaxIdentifier, year, red, green, blue){
        apex.server.plugin(
            ajaxIdentifier,
            {
                "x01" : year
            },
            {
                dataType: 'json',
                success: function(statePcts) {
                    for (state in statePcts){

                        //Format the number with spacing for every 3 digits
                        //Idea grabbed from: http://stackoverflow.com/questions/16637051/adding-space-between-numbers
                        var formattedPopulation =
                            statePcts[state]
                                .totalPopulation
                                .toString()
                                .replace(/\B(?=(\d{3})+(?!\d))/g, " ");

                        apex.jQuery('.'+state)
                            .attr('style', 'fill: rgba(' + red + ','+ green +','+ blue +',' + statePcts[state].pctOfMax + ')')
                            .attr('title', ''
                                + statePcts[state].stateName
                                + '\nPopulation: '
                                + formattedPopulation
                                + '\nClick for more info...');//Map region switched with charts)
                    }
                }
            }
        );
    }

};
<commit_msg>Calculate map height + update timeline selector So that the map appropriately fits to the page on smaller screens<commit_after>var germanMapRenderer = {

    width: 960,
    height: 750,
    scale: 500,

    setUpMap: function setUpMap(width, height, pluginFilePrefix, ajaxIdentifier, initialYear, red, green, blue){

        var projection = d3.geo.mercator()
            //.center([10.5, 51.35])
            .scale(500)
            .translate([width / 2, height / 2]);

        var path = d3.geo.path()
            .projection(projection);

        //pull heights of all page components, to assign an appropriate height for the map region
        var headerHeight = apex.jQuery('.t-Header-navBar').outerHeight(true);
        var timelineHeaderHeight = apex.jQuery('#mapTimeline h4').outerHeight(true);
        var timelinePointsHeight = apex.jQuery('#mapTimeline #timelinePoints').outerHeight(true);

        var legendHeaderHeight = apex.jQuery('#legend h4').outerHeight(true);
        var legendColourGrid = apex.jQuery('#legend #colourGrid').outerHeight(true);
        var legendCaption = apex.jQuery('#legend #colourGridCaption').outerHeight(true);

        //buffer so the legend isn't right on the page border
        var buffer = 30;

        //figure out the height to make the map so it fits on the page
        var computedHeight = apex.jQuery(window).height()-headerHeight-timelineHeaderHeight-timelinePointsHeight-legendHeaderHeight-legendColourGrid-legendCaption-buffer;

        var svg = d3.select("#germanMap")
            .attr("height", computedHeight);

        d3.json(pluginFilePrefix + "de.json", function(error, de) {

            var states = topojson.feature(de, de.objects.states);

            svg.selectAll(".state")
                .data(states.features)
                .enter().append("path")
                .attr("class", function(d) { return d.id + " germanState"; })
                .attr("d", path)
                .on("click", germanMapRenderer.onClickState);

            germanMapRenderer.updateMapPopulationDisplay(ajaxIdentifier, initialYear, red, green, blue);

            var generatedChart = document.getElementsByTagName("svg")[0];

            var bbox = generatedChart.getBBox();
            var viewBox = [bbox.x, bbox.y, bbox.width, bbox.height].join(" ");
            generatedChart.setAttribute("viewBox", viewBox);
        });

    },

    onClickState: function clickedState(d){
        apex.event.trigger(document, 'showstateinfo', {adm1_code: d.id});
    },

    registerChangeTime: function registerChangeTime(ajaxIdentifier, red, green, blue){

        apex.jQuery('#timelinePoints li').click(function(){

            var $period = apex.jQuery(this);
            var clickedTimePeriodYear = apex.jQuery(this).text();

            $('#timelinePoints li').removeClass('active');
            $period.addClass('active');

            germanMapRenderer.updateMapPopulationDisplay(ajaxIdentifier, clickedTimePeriodYear, red, green, blue);

            //Send a DA event should any further logic want to be introduced
            apex.event.trigger($period, 'gsm_timeclicked', { year: clickedTimePeriodYear });

        });
    },

    updateMapPopulationDisplay: function updateMapPopulationDisplay(ajaxIdentifier, year, red, green, blue){

        apex.server.plugin(
            ajaxIdentifier,
            {
                "x01" : year
            },
            {
                dataType: 'json',
                success: function(statePcts) {

                    for (state in statePcts){

                        //Format the number with spacing for every 3 digits
                        //Idea grabbed from: http://stackoverflow.com/questions/16637051/adding-space-between-numbers
                        var formattedPopulation =
                            statePcts[state]
                                .totalPopulation
                                .toString()
                                .replace(/\B(?=(\d{3})+(?!\d))/g, " ");

                        apex.jQuery('.'+state)
                            .attr('style', 'fill: rgba(' + red + ','+ green +','+ blue +',' + statePcts[state].pctOfMax + ')')
                            .attr('title', ''
                                + statePcts[state].stateName
                                + '\nPopulation: '
                                + formattedPopulation
                                + '\nClick for more info...');//Map region switched with charts)
                    }
                }
            }
        );
    }

};
<|endoftext|>


<commit_before>(function(window, _, Backbone){
    'use strict';

    var Monaco = window.Monaco = (window.Monaco || {});

    // Local Cache Application setup
    Monaco.on('app:built', function(app, options) {
        // set a reference to the application into the local cache
        app.local._app = app;

        // check the global cacheLocal property for the application
        if (_.has(options, 'cacheLocal')) {
            app.local.autoCache = Boolean(_.result(options, 'cacheLocal'));
        }

        // check the global cacheExpire property for the application
        if (_.has(options, 'cacheExpire')) {
            var cacheExpire = _.result(options, 'cacheExpire');
            app.local.cacheExpire = (_.isNull(cacheExpire) || _.isNumber(cacheExpire)) ? cacheExpire : void 0;
        }

        // cache any prefetched data for later use on the application
        if (options.prefetched) {
            _.each(options.prefetched, function(value, key) {
                if (!value.data) {
                    return;
                }
                this.local.set({resource: key, models:[]}, value.data, value.expire);
            }, app);
            delete app.options.prefetched;
        }
    });

    // overrides Monaco.Application add method to check for the required `resources` property
    var applicationAdd = Monaco.Application.prototype.add;
    Monaco.Application.prototype.add = function(className, Class) {
        if (Class.prototype.namespace === 'collections' && (!Class.prototype.resource || Class.prototype.resource === '')) {
            throw new Error('required `resource` property missing from this collection definition');
        }
        return applicationAdd.apply(this, arguments);
    };

    // Application Local Cache Object
    Monaco.Application.prototype.local = {
        // by default the application won't auto cache your api calls
        autoCache : false,

        // default cache expire (in minutes) if you enable local caching
        cacheExpire : 30,

        // get an object from local cache
        // return null instead of undefined to comply with the localStorage getItem api
        get : function( obj ) {
            var isCollection = _.has(obj, 'models'),
                collection = isCollection ? obj : (obj.collection || null),
                resource = collection ? _.result(collection, 'resource') : null;

            if (resource) {
                var data = this._getLocalData(resource);
                if (data) {
                    data.resp = this.decompress(data.resp);
                    if (!isCollection) {
                        data = _.find(data.resp, function(item) {
                            return item[obj.idAttribute] === obj.id;
                        });
                        if (data && (!data._ts || !this._isExpired(data))) {
                            delete data._ts; // remove possible timestamp property
                            return data;
                        }
                    } else {
                        return data.resp;
                    }
                }
            }
            return void 0;
        },

        // set an object in local cache
        set : function(obj, data, expire) {
            var isCollection = _.has(obj, 'models'),
                collection = isCollection ? obj : obj.collection || null,
                resource = collection ? _.result(collection, 'resource') : null;
            expire = (expire !== void 0) ? expire : false;

            if (!resource) {
                // todo: find a way to log  - 'unable to identify object\'s resource'
                return;
            }
            if (!collection) {
                //todo: find a way to log - 'unable to identify object\'s collection'
                return;
            }

            if (!isCollection) {
                // in case the model has expireLocal set for individual models
                var modelExpire = expire;
                if (modelExpire !== false || (modelExpire = _.result(obj, 'expireLocal')) !== void 0) {
                    data = this._setExpire(data, modelExpire, true);
                }
                data = this._addToCollectionData(obj, data, collection);
            }

            data = this.compress(data);
            data = this._setExpire(data, (expire !== false ? expire : _.result(collection, 'expireLocal')));

            this._storageSet(resource, data);
            this._memorySet(resource, data);
            return true;
        },

        // clear a specific resource or all resources
        clear : function(resource) {
            resource = resource || null;
            if (!resource) {
                return this._clearAll();
            }
            return this._clearItem(resource);
        },

        // compress data to be stored - override this with your own data compress implementation
        compress : function(data) { return data; },

        // decompress data - override this with your own data decompress implementation
        decompress : function(data) { return data; },

        // get the local data for a collection resource
        _getLocalData : function(resource) {
            var localData = null,
                caching = ['memory', 'storage'];

            for (var i = 0, j = caching.length; i < j; i++) {
                localData = this['_'+caching[i]+'Get'](resource);
                if (localData && !this._isExpired(localData)) {
                    if (caching[i] === 'storage') {
                       this._memorySet(resource, localData);
                    }
                    return _.clone(localData);
                }
                // todo: add an option on the local caching setup that allows it to control
                //       the following behaviour
                // clean up the expired data
                else if(localData) {
                    this.clear(resource);
                }
            }

            return null;
        },

        // merge the model with the collection data
        _addToCollectionData : function(model, data, collection) {
            var collectionData = this.get(collection);
            if (!collectionData) {
                collectionData = collection.toJSON();
            }
            var rejectObj = {};
            rejectObj[model.idAttribute] = model.id;
            collectionData = _.reject(collectionData, rejectObj);
            collectionData.push(data);
            return collectionData;
        },

        // get the resource if it is available in memory
        // returns the object or if the object is not found it returns
        // null ( to comply with the localStorage specs)
        _memoryGet : function(resource) {
            return this._memory ? this._memory[resource] : null;
        },

        // set the resource data in memory
        _memorySet : function(resource, data) {
            this._memory = this._memory || {};
            this._memory[resource] = data;
        },

        // get the resource if it is available in localStorage
        // returns the object or if the object is not found null
        _storageGet : function(resource) {
            var result;
            try {
                result = window.localStorage.getItem(this._getKey(resource));
            } catch(e) {
                return null;
            }
            return (result === undefined || result === 'undefined' || result === null) ? null : JSON.parse(result);
        },

        // set the resource data in localStorage
        _storageSet : function(resource, data) {
            var key = this._getKey(resource),
                keys = window.localStorage.getItem('monaco-' + this._app.name + ':keys') || '{}';

            var newKeys = JSON.parse(keys);
            newKeys[key] = data._ts;
            try {
                window.localStorage.setItem('monaco-' + this._app.name + ':keys', JSON.stringify(newKeys));
                window.localStorage.setItem(key, JSON.stringify(data));
            } catch(e) {
                // todo: add a warning here
                try {
                    // resetting the keys to its original value if either the new keys or data failed
                    window.localStorage.setItem('monaco-' + this._app.name + ':keys', keys);
                } catch(exception) {
                    // fail silently - todo: add a warning here
                }
            }
        },

        // add the expire timestamp to the data object
        _setExpire : function(data, expire, asProperty) {
            var timestamp = null;
            expire = ( _.isNumber( expire ) || _.isNull( expire ) ) ? expire : void 0;
            if ( !_.isNull( expire ) ) {
                var date = new Date();
                date.setMinutes( date.getMinutes() + ( expire || this.cacheExpire ) );
                timestamp = date.getTime();
            }
            // if asProperty is set to true then append the timestamp to the data
            // usefull for setting expire for individual methods
            if (asProperty === true) {
                data._ts = timestamp;
                return data;
            }
            return {
                _ts : timestamp,
                resp : data
            };
        },

        // check if the data object is expired
        _isExpired : function(data) {
            var expire = data._ts,
                now = new Date();
            return ( !_.isNull( expire ) && now.getTime() > expire );
        },

        // clear a resource item from localStorage and memory
        _clearItem : function(resource) {
            var key = this._getKey(resource);
            // remove the item from local storage
            try {
                window.localStorage.removeItem(key);
            } catch(e) {
                throw new Error('unable to remove the localStorage key: ' + key);
            }

            // remove the item from memory
            if ( _.has(this._memory, resource) ) {
                delete this._memory[resource];
            }

            // clean-up the application keys
            var keys = JSON.parse(window.localStorage.getItem('monaco-' + this._app.name + ':keys')) || {};
            delete keys[key];
            return this._app.set('monaco-' + this._app.name + ':keys', keys, true);
        },

        // clear all resources associated with this application from localStorage and memory
        _clearAll : function() {
            // clean up localStorage
            var keys = JSON.parse(window.localStorage.getItem('monaco-' + this._app.name + ':keys')) || {};
            _.each(keys, function(value, key) {
                try {
                    var resource = key.split('#');
                    resource = resource[1];
                    window.localStorage.removeItem(key);
                } catch(e) {
                    // do nothing
                }
            }, this);

            // clean up memory
            this._memory = {};

            // clean-up the application keys
            this._app.set('monaco-' + this._app.name + ':keys', {}, true);
        },

        // get a key based on the resource name and the application
        _getKey : function(resource) {
            return this._app.name + '#' + resource;
        }
    };

    var collectionInitialize = Monaco.Collection.prototype.initialize;
    Monaco.Collection.prototype.initialize = function() {
        // todo: the `remove` and `add` events are called once per each model what causes
        //       this method to reset the collection multiple times, if there was a way of
        //       knowing that a certain event is the last in a series of events fired then
        //       we could minimize the number of times we reset the collection local cache

        // info: don't need to listen for the `destroy` model event, because it will
        //       trigger a remove from the collection
        this.on('add remove change reset', function() {
            var options = arguments.length > 0 ? arguments[arguments.length - 1] : {};
            if (!options.fromLocal &&
                (_.result(this, 'cacheLocal') === true || this._app.local.autoCache === true)) {
                this._app.local.set(this, this.toJSON());
            }
        }, this);
        return collectionInitialize.apply(this, arguments);
    };

    /* -- SYNC ------------------------------------------------------------- */
    Monaco.sync = Backbone.sync;

    // override the Monaco (Backbone) sync method, so that read calls make
    // usage of the local caching data for Monaco Models or Collections
    Backbone.sync = function(method, model, options) {
        options = options || {};
        var app = model._app, // A Monaco Model or Collection will have a reference to the application
            localOnly = (options.localOnly === true || model.localOnly === true);
        if (app && method === 'read') {
            // Attempt to retrieve the data from local cache and if succeed it will call the appropriated success method
            var data = (options.fresh === true ) ? null : app.local.get(model);
            if (data) {
                options.fromLocal = true;
                if (options.success) {
                    options.success(data);
                }
                return true;
            }

            var isCollection = _.has(model, 'models');

            // call the `error` callback if no data is found and `localOnly` is set to true
            if (localOnly === true) {
                if (options.error) {
                    options.error({}, "Error: resource not found locally", {});
                }
                return;
            }
            // Check the configuration levels and wrap the success call back if at any level we have cacheLocal defined
            else if ((_.result(options, 'cacheLocal') === true) || // fetch level
                (!_.has(options, 'cacheLocal') &&  isCollection && _.result(model, 'cacheLocal') === true) || // model/collection level
                (!_.has(options, 'cacheLocal') && !isCollection && _.result(model.collection, 'cacheLocal') === true) || // model/collection level
                (!_.has(options, 'cacheLocal') && (!_.has(model, 'cacheLocal')) && app.local.autoCache === true)) { // app level
                var success = options.success;
                options.success = function(resp, status, xhr) {
                    app.local.set(model, resp, _.result(options, 'expireLocal'));
                    if (success) {
                        success.apply(this, arguments);
                    }
                };
            }
        }

        // return earlier for model/collections or request options set to localOnly
        if (localOnly === true) {
            return;
        }
        return Monaco.sync(method, model, options);
    };
}(window, window._, window.Backbone));
<commit_msg>monaco local added the ability to cache models<commit_after>(function(window, _, Backbone){
    'use strict';

    var Monaco = window.Monaco = (window.Monaco || {});

    // Local Cache Application setup
    Monaco.on('app:built', function(app, options) {
        // set a reference to the application into the local cache
        app.local._app = app;

        // check the global cacheLocal property for the application
        if (_.has(options, 'cacheLocal')) {
            app.local.autoCache = Boolean(_.result(options, 'cacheLocal'));
        }

        // check the global cacheExpire property for the application
        if (_.has(options, 'cacheExpire')) {
            var cacheExpire = _.result(options, 'cacheExpire');
            app.local.cacheExpire = (_.isNull(cacheExpire) || _.isNumber(cacheExpire)) ? cacheExpire : void 0;
        }

        // cache any prefetched data for later use on the application
        if (options.prefetched) {
            _.each(options.prefetched, function(value, key) {
                if (!value.data) {
                    return;
                }
                this.local.set({resource: key, models:[]}, value.data, value.expire);
            }, app);
            delete app.options.prefetched;
        }
    });

    // overrides Monaco.Application add method to check for the required `resources` property
    var applicationAdd = Monaco.Application.prototype.add;
    Monaco.Application.prototype.add = function(className, Class) {
        if (Class.prototype.namespace === 'collections' && (!Class.prototype.resource || Class.prototype.resource === '')) {
            throw new Error('required `resource` property missing from this collection definition');
        }
        return applicationAdd.apply(this, arguments);
    };

    // Application Local Cache Object
    Monaco.Application.prototype.local = {
        // by default the application won't auto cache your api calls
        autoCache : false,

        // default cache expire (in minutes) if you enable local caching
        cacheExpire : 30,

        // get an object from local cache
        // return null instead of undefined to comply with the localStorage getItem api
        get : function( obj ) {
            var isCollection = _.has(obj, 'models'),
                collection = isCollection ? obj : (obj.collection || null),
                resource = collection ? _.result(collection, 'resource') : _.result(obj, 'resource');

            if (resource) {
                var data = this._getLocalData(resource);
                if (data) {
                    data.resp = this.decompress(data.resp);

                    // get cached model
                    if (!isCollection) {
                        // extract data from model linked with a collection
                        if (collection) {
                            data = _.find(collection.parse(data.resp), function(item) {
                                return item[obj.idAttribute] === obj.id;
                            });

                            // only this case needs to retest the expiration, since the expiration tested
                            // in _getLocalData was from the collection, and the model might have its own
                            // expiration rule
                            if (data && (!data._ts || !this._isExpired(data))) {
                                delete data._ts; // remove possible timestamp property
                                return data;
                            }
                        }

                        // extract data from model not linked with any collection
                        else if(data.resp[obj.idAttribute] === obj.id) {
                            return data.resp;
                        }
                    }
                    // get cached collection
                    else {
                        return data.resp;
                    }
                }
            }
            return void 0;
        },

        // set an object in local cache
        set : function(obj, data, expire) {
            var isCollection = _.has(obj, 'models'),
                collection = isCollection ? obj : obj.collection || null,
                resource = collection ? _.result(collection, 'resource') : _.result(obj, 'resource');
            expire = (expire !== void 0) ? expire : false;

            if (!resource) {
                // todo: find a way to log  - 'unable to identify object\'s resource'
                return;
            }

            // caching Model linked with Collection
            if (!isCollection && collection) {
                // in case the model has expireLocal set for
                var modelExpire = expire || _.result(obj, 'expireLocal') ;
                if (modelExpire) {
                    data = this._setExpire(data, modelExpire, true);
                }
                data = this._addToCollectionData(obj, data, collection);

                expire = (expire !== false) ? expire : _.result(collection, 'expireLocal');
            }
            // Collections or Models NOT linked with any Collection
            else {
                expire = (expire !== false) ? expire : _.result(obj, 'expireLocal');
            }

            data = this.compress(data);
            data = this._setExpire(data, expire);

            this._storageSet(resource, data);
            this._memorySet(resource, data);
            return true;
        },

        // clear a specific resource or all resources
        clear : function(resource) {
            resource = resource || null;
            if (!resource) {
                return this._clearAll();
            }
            return this._clearItem(resource);
        },

        // compress data to be stored - override this with your own data compress implementation
        compress : function(data) { return data; },

        // decompress data - override this with your own data decompress implementation
        decompress : function(data) { return data; },

        // get the local data for a collection resource
        _getLocalData : function(resource) {
            var localData = null,
                caching = ['memory', 'storage'];

            for (var i = 0, j = caching.length; i < j; i++) {
                localData = this['_'+caching[i]+'Get'](resource);
                if (localData && !this._isExpired(localData)) {
                    if (caching[i] === 'storage') {
                       this._memorySet(resource, localData);
                    }
                    return _.clone(localData);
                }
                // todo: add an option on the local caching setup that allows it to control
                //       the following behaviour
                // clean up the expired data
                else if(localData) {
                    this.clear(resource);
                }
            }

            return null;
        },

        // merge the model with the collection data
        _addToCollectionData : function(model, data, collection) {
            var restfulData,
                collectionData = this.get(collection);
            if (collectionData) {
                restfulData = collection.parse(collectionData);
                collection.reset(restfulData, { silent: true });
            }

            collection.add( data, { silent: true, merge: true } );
            return collection.revertParse && typeof collection.revertParse === 'function' ? collection.revertParse() : collection.toJSON();
        },

        // get the resource if it is available in memory
        // returns the object or if the object is not found it returns
        // null ( to comply with the localStorage specs)
        _memoryGet : function(resource) {
            return this._memory ? this._memory[resource] : null;
        },

        // set the resource data in memory
        _memorySet : function(resource, data) {
            this._memory = this._memory || {};
            this._memory[resource] = data;
        },

        // get the resource if it is available in localStorage
        // returns the object or if the object is not found null
        _storageGet : function(resource) {
            var result;
            try {
                result = window.localStorage.getItem(this._getKey(resource));
            } catch(e) {
                return null;
            }
            return (result === undefined || result === 'undefined' || result === null) ? null : JSON.parse(result);
        },

        // set the resource data in localStorage
        _storageSet : function(resource, data) {
            var key = this._getKey(resource),
                keys;
            try {
                keys = window.localStorage.getItem('monaco-' + this._app.name + ':keys') || '{}';
            } catch(e) {
                return; // fail silently
            }

            var newKeys = JSON.parse(keys);
            newKeys[key] = data._ts;
            try {
                window.localStorage.setItem('monaco-' + this._app.name + ':keys', JSON.stringify(newKeys));
                window.localStorage.setItem(key, JSON.stringify(data));
            } catch(e) {
                // todo: add a warning here
                try {
                    // resetting the keys to its original value if either the new keys or data failed
                    window.localStorage.setItem('monaco-' + this._app.name + ':keys', keys);
                } catch(exception) {
                    // fail silently - todo: add a warning here
                }
            }
        },

        // add the expire timestamp to the data object
        _setExpire : function(data, expire, asProperty) {
            var timestamp = null;
            expire = ( _.isNumber( expire ) || _.isNull( expire ) ) ? expire : void 0;
            if ( !_.isNull( expire ) ) {
                var date = new Date();
                date.setMinutes( date.getMinutes() + ( expire || this.cacheExpire ) );
                timestamp = date.getTime();
            }
            // if asProperty is set to true then append the timestamp to the data
            // usefull for setting expire for individual methods
            if (asProperty === true) {
                data._ts = timestamp;
                return data;
            }
            return {
                _ts : timestamp,
                resp : data
            };
        },

        // check if the data object is expired
        _isExpired : function(data) {
            var expire = data._ts,
                now = new Date();
            return ( !_.isNull( expire ) && now.getTime() > expire );
        },

        // clear a resource item from localStorage and memory
        _clearItem : function(resource) {
            var key = this._getKey(resource);
            // remove the item from local storage
            try {
                window.localStorage.removeItem(key);
            } catch(e) {
                throw new Error('unable to remove the localStorage key: ' + key);
            }

            // remove the item from memory
            if ( _.has(this._memory, resource) ) {
                delete this._memory[resource];
            }

            // clean-up the application keys
            var keys;
            try {
                keys = JSON.parse(window.localStorage.getItem('monaco-' + this._app.name + ':keys')) || {};
            } catch(e) {}
            if (keys) {
                delete keys[key];
                return this._app.set('monaco-' + this._app.name + ':keys', keys, true);
            }
        },

        // clear all resources associated with this application from localStorage and memory
        _clearAll : function() {
            // clean up localStorage
            var keys = {};
            try {
                keys = JSON.parse(window.localStorage.getItem('monaco-' + this._app.name + ':keys')) || {};
            } catch(e) {}
            _.each(keys, function(value, key) {
                try {
                    var resource = key.split('#');
                    resource = resource[1];
                    window.localStorage.removeItem(key);
                } catch(e) {
                    // do nothing
                }
            }, this);

            // clean up memory
            this._memory = {};

            // clean-up the application keys
            this._app.set('monaco-' + this._app.name + ':keys', {}, true);
        },

        // get a key based on the resource name and the application
        _getKey : function(resource) {
            return this._app.name + '#' + resource;
        }
    };

    var collectionInitialize = Monaco.Collection.prototype.initialize;
    Monaco.Collection.prototype.initialize = function() {
        // todo: the `remove` and `add` events are called once per each model what causes
        //       this method to reset the collection multiple times, if there was a way of
        //       knowing that a certain event is the last in a series of events fired then
        //       we could minimize the number of times we reset the collection local cache

        // info: don't need to listen for the `destroy` model event, because it will
        //       trigger a remove from the collection

        // listen to collection events and updates local caching accordingly
        this.on('add remove change reset', function() {
            var options = arguments.length > 0 ? arguments[arguments.length - 1] : {},
                data;

            // if original call IS NOT a fetch from local storage and this collection
            // should be cached
            if (!options.fromLocal &&
                (_.result(this, 'cacheLocal') === true || this._app.local.autoCache === true)) {

                // ????
                // this.fetch({localOnly: true, cacheLocal: false, silent: true});

                // make sure the data has the same structure as the original server request
                data = ( this.revertParse && typeof this.revertParse === 'function' ) ? this.revertParse() : this.toJSON();

                // replace the local data with the new data
                this._app.local.set(this, data);
            }
        }, this);
        return collectionInitialize.apply(this, arguments);
    };

    var modelInitialize = Monaco.Model.prototype.initialize;
    Monaco.Model.prototype.initialize = function() {
        // listen to collection events and updates local caching accordingly
        this.on('change', function() {
            var options = arguments.length > 0 ? arguments[arguments.length - 1] : {},
                data;

            // if original call IS NOT a fetch from local storage and this collection
            // should be cached
            if (!options.fromLocal &&
                (_.result(this, 'cacheLocal') === true || (this._app && this._app.local.autoCache === true))) {

                // ????
                // this.fetch({localOnly: true, cacheLocal: false, silent: true});

                // make sure the data has the same structure as the original server request
                data = ( this.revertParse && typeof this.revertParse === 'function' ) ? this.revertParse() : this.toJSON();

                // replace the local data with the new data
                this._app.local.set(this, data);
            }
        }, this);
        return modelInitialize.apply(this, arguments);
    };

    /* -- SYNC ------------------------------------------------------------- */
    Monaco.sync = Backbone.sync;

    // override the Monaco (Backbone) sync method, so that read calls make
    // usage of the local caching data for Monaco Models or Collections
    Backbone.sync = function(method, model, options) {
        options = options || {};
        var app = model._app, // A Monaco Model or Collection will have a reference to the application
            localOnly = (options.localOnly === true || model.localOnly === true);
        if (app && method === 'read') {
            // Attempt to retrieve the data from local cache and if succeed it will call the appropriated success method
            var data = (options.fresh === true ) ? null : app.local.get(model);
            if (data) {
                options.fromLocal = true;
                if (options.success) {
                    options.success(data);
                }
                return true;
            }

            var isCollection = _.has(model, 'models');

            // call the `error` callback if no data is found and `localOnly` is set to true
            if (localOnly === true) {
                if (options.error) {
                    options.error({}, "Error: resource not found locally", {});
                }
                return;
            }
            // Check the configuration levels and wrap the success call back if at any level we have cacheLocal defined
            // If we have a custom cache control key, we want to cache regardless of whether the object wants to cache or not
            else {
                var cacheLocal = false,
                    success;

                var customCachePolicy = (isCollection && model.cachePolicy) ? model.cachePolicy :
                                          (!isCollection && model.collection) ? model.collection.cachePolicy :
                                            (app.options.cachePolicy) ? app.options.cachePolicy : void 0;

                //Determine if we need to do local caching

                //Case 1: Custom Caching Policy supposedly sent by server response
                cacheLocal = (customCachePolicy && customCachePolicy !== 'local');

                //Case 2: cacheLocal specified to be true at app/collection/model/fetch level
                if ((_.result(options, 'cacheLocal') === true) || // fetch level
                    (!_.has(options, 'cacheLocal') &&  isCollection && _.result(model, 'cacheLocal') === true) || // collection level
                      (!_.has(options, 'cacheLocal') && (!isCollection && model.collection) && _.result(model.collection, 'cacheLocal') === true) || // model (with collection) level
                        (!_.has(options, 'cacheLocal') && (!isCollection && !model.collection) && _.result(model, 'cacheLocal') === true) || // model (no collection) level
                            (!_.has(options, 'cacheLocal') && (!_.has(model, 'cacheLocal')) && app.local.autoCache === true)) { // app level
                    cacheLocal = true;
                }

                if (cacheLocal) {
                    success = options.success;
                    options.success = function(resp, status, xhr) {

                        // local caching policy will use the expiration defined or Monaco's default
                        if (!customCachePolicy || customCachePolicy === 'local') {
                            app.local.set(model, resp, _.result(options, 'expireLocal'));
                        }

                        // custom caching policy will use the expiration sent by the server
                        // and if not available or too small it won't cache the resource
                        else {
                          var expire = xhr.getResponseHeader(customCachePolicy);
                            if (expire) {
                                // Cache-Control sends expire time in seconds, but Monaco uses minutes
                                expire = parseInt(expire.match(/max-age=([\d]+)/)[1], 10);
                                if (expire && ((expire / 60) > 0)) {
                                    app.local.set(model, resp, (expire / 60));
                                }
                            }
                        }

                        if (success) {
                            success.apply(this, arguments);
                        }
                    };
                }
            }
        }

        // return earlier for model/collections or request options set to localOnly
        if (localOnly === true) {
            return;
        }
        return Monaco.sync(method, model, options);
    };
}(window, window._, window.Backbone));
<|endoftext|>


<commit_before>(function() {
/**
 * PLUTO
 * Native HTML Template Rendering
 *
 *
 * The MIT License (MIT)
 * Copyright (c) 2013 Roi Avinoam
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
class Template extends HTMLTemplateElement {
    render(obj) {
        var { content, exprs, items } = this.compile()
        return new Renderer(content, exprs, items).render(obj)
    }

    _renderIn(obj, el) {
        if (el.__plutoRenderer) {
            el.__plutoRenderer.render(obj)
        } else {
            var doc = this.render(obj)
            el.replaceWith(doc)
            el.__plutoRenderer = doc
            el.remove = doc.remove.bind(doc)
        }
    }

    compile() {
        var exprs = []
        var clone = this.cloneNode(true)
        var elements = [{ el: clone.content, path: [] }]
        while (elements.length > 0) {
            var { el, path } = elements.shift()

            if (el.localName === 'template') {
                // nested templates are coerced to Pluto Templates. This
                // prevents the need to repeatedly mark templates with pluto-tpl
                // with the trade-off of not supporting a mixture of template
                // libraries.
                var render = (el, _, obj) => pluto(el)._renderIn(obj, el)
                exprs.push({ path, render })
                continue
            }

            // inner content expressions
            if (el.nodeName === '#text') {
                var expr = isExpressions(el.textContent)
                if (expr) {
                    el.textContent = ''
                    var render = (el, v) => (el.textContent = v || '')
                    exprs.push({ expr, path, render })
                }
            }

            // attributes
            Array.from(el.attributes || []).forEach(function(attr) {
                var expr = isExpressions(attr.value)
                if (expr === null) {
                    return
                }

                // avoid having the attribute on import as it might arrive
                // un-rendered to web-components. NB: this means that the
                // constructor of elements wouldn't have access to the rendered
                // values initially. Perhaps we should initially render into a
                // clone before finally importing.
                el.removeAttribute(attr.name)
                attr = attr.name
                var evName, prop
                if (attr.startsWith('on-')) {
                    evName = attr.slice(3)
                    var render = function(el, v) {
                        var evs = el.__plutoEvs || (el.__plutoEvs = {})
                        if (evs[evName] !== v) {
                            el.removeEventListener(evName, evs[evName])
                            el.addEventListener(evName, evs[evName] = v)
                        }
                    }
                } else if (['style', 'class'].indexOf(attr) !== -1) {
                    var render = (el, v) => v
                        ? el.setAttribute(attr, v)
                        : el.removeAttribute(attr)
                } else {
                    prop = snakeToCamelCase(attr)
                    var render = (el, v) => el[prop] = v
                }

                exprs.push({ expr, path, attr, evName, prop, render })
            }, this)

            // enqueue children
            el.childNodes.forEach(function(el, i) {
                elements.push({ el, path: path.concat(['childNodes', i]) })
            })
        }

        var repeat = this.getAttribute('repeat') || this.getAttribute('for')
        if (repeat) {
            var fn = compileExpressions([{ expr: repeat }])
            clone.items = (function(fn) {
                return (obj) => fn(obj)[0] || []
            })(fn)
        }

        var elseIf = this.getAttribute('else-if')
        var cond = this.getAttribute('if') || elseIf
        if (cond) {
            var fn = compileExpressions([{ expr: cond }])
            clone.items = (function(fn) {
                return (obj) => Boolean(fn(obj)[0])
            })(fn)
        }

        var else_ = this.hasAttribute('else') || elseIf
        if (else_) {
            var fn = clone.items || Array
            clone.items = (function(fn) {
                return (obj) => obj.__plutoElse ? [] : fn(obj)
            })(fn)
        }

        // we opt to compile the repeat/cond expressions separately than the
        // rest of this template - because (a) the template might relay on a
        // repeated ${item} property that doesn't yet exist in the repeat
        // expression, and (b) it's must smaller/faster than the complete
        // expressions list.
        // NB: It might not be that beneficial for cond though.
        clone.exprs = Object.assign(exprs, { eval: compileExpressions(exprs) })
        return clone
    }
}

class Renderer extends DocumentFragment {
    constructor(content, exprs, items) {
        super()
        this.tpl = content
        this.exprs = exprs
        this.items = items

        this.elements = []
        this.paths = {}

        if (items) {
            this.placeholder = document.createTextNode('')
            this.appendChild(this.placeholder)
        } else {
            this.appendChild(document.importNode(this.tpl, true))

            // copy the list of generated elements from the template in order
            // to support removals
            this.elements = [].map.call(this.childNodes, child => child)
            this.paths = this.exprs.map((expr) => select(this, expr.path))
        }
    }

    remove() {
        while (this.elements.length > 0) {
            this.elements.pop().remove()
        }
    }

    render(obj) {
        if (!this.items) {
            return this._renderOne(obj)
        }

        var items = this.items(obj)
        var item = obj.item
        if (!Array.isArray(items) && typeof items === 'object') {
            items = Object.keys(items).map(function(k) {
                return { key: k, value: items[k] }
            })
        }

        if (typeof items === 'boolean') {
            items = Number(items) // 0 or 1
        }

        if (typeof items === 'number') {
            items = new Array(items) // range-items, repeat N times.
            items = Array.from(items).map(() => item)
        }

        obj.__plutoElse = items && items.length > 0

        // remove obsolete items
        while (this.elements.length > items.length) {
            this.elements.pop().remove()
        }

        // update existing items
        for (var i = 0; i < this.elements.length; i += 1) {
            obj.item = items[i]
            this.elements[i].render(obj)
        }

        // create new items
        while (this.elements.length < items.length) {
            var i = this.elements.length
            obj.item = items[i]
            var doc = new Renderer(this.tpl, this.exprs).render(obj)
            this.elements.push(doc)
            this.placeholder.before(doc)
        }

        obj.item = item // restore previous item value.
        return this
    }

    _renderOne(obj) {
        var else_ = obj.__plutoElse
        obj.__plutoElse = false

        var values = this.exprs.eval(obj)
        var subtpls = []
        for (var i = 0 ; i < this.exprs.length ; i += 1) {
            var expr = this.exprs[i]
            var el = this.paths[i]
            var v = values[i]
            expr.render(el, v, obj)
        }

        obj.__plutoElse = else_
        return this
    }
}

// -- HELPER FUNCTIONS

// Searches for an element from root based on the property-path to the child
// example: root = <body>, path = childNodes.3.childNode.7. Resolved by walking
// the path down to the child.
function select(root, path) {
    var el = root
    for (var i = 0; el !== undefined && i < path.length; i += 1) {
        el = el[path[i]]
    }
    return el
}

const SNAKE_RE = /-([a-z])/g
function snakeToCamelCase(s) {
    return s.replace(SNAKE_RE, g => g[1].toUpperCase())
}

// extract expressions in template-literal syntax out of a string
var EXPR_RE = /\$\{[^\}]*\}/
function isExpressions(s) {
    return EXPR_RE.test(s) ? s : null
}

// compile a list of template-literal expressions into a function that evaluates
// these expression for the provided input object
function compileExpressions(exprs) {
    var refs = []
    var code = 'this.__plutoT || (this.__plutoT = T.bind(this));\n'
    code += exprs.map(function(expr, i) {
        if (expr.expr) {
            refs = refs.concat(getIdentifiers(expr.expr))
        }

        var tagFn = ({
            'class': 'classNameT',
            'style': 'styleT'
        })[expr.attr] || 'this.__plutoT'

        return `arguments[0][${i}] = ${tagFn}\`${expr.expr}\``
    }).join(';\n')

    var keys = refs.reduce((keys, k) => (keys[k] = true, keys), {})
    var locals = `var { ${Object.keys(keys)} } = this`
    var fn = eval('(function () {\n' + locals + '\n' + code + '\n})')
    return function(obj) {
        var res = []
        try {
            fn.call(obj, res)
        } catch (e) {
            console.warn(fn)
            throw e
        }

        return res
    }

    function T(s, v) {
        if (arguments.length > 2 || typeof v === 'string') {
            return String.raw.apply(null, arguments)
        }

        if (typeof v === 'function' && this[v.name] === v) {
            if (v._plutoBound !== this) {
                v._plutoBound = v.bind(this)
            }
            return v._plutoBound
        }

        return v
    }

    function classNameT(s, v) {
        if (typeof v === 'object') {
            if (!Array.isArray(v)) {
                v = Object.keys(v).filter((k) => v[k])
            }

            return v.join(' ')
        }

        return String.raw.apply(null, arguments)
    }

    function styleT(s, v) {
        if (typeof v === 'object') {
            return Object.keys(v).map((k) => k + ':' + v[k]).join('; ')
        }

        return String.raw.apply(null, arguments)
    }
}

// generate the list of identifiers found in the code.
function getIdentifiers(expr) {
    var re = /[$A-Z_][0-9A-Z_$]*/ig
    var whitespace = ' \n\r\t'
    var disallowed = '\'\".'
    var skip = ['true', 'false', 'if', 'for', 'while', 'do', 'try', 'catch',
        'break', 'continue', 'switch', 'throw', 'this', 'instanceof', 'in',
        'function', 'delete', 'default', 'case', 'debugger', 'const', 'var',
        'with', 'typeof', 'super', 'class', 'new', 'null', 'return', 'let',
        'import', 'else', 'enum', 'extends', 'finally', '$']

    // We first match for the valid identifier, and then check the previous
    // non-whitespace character preceeding the identifier to verify that it's
    // not a string or nested element.
    var refs = {}
    var match
    while (match = re.exec(expr)) {
        if (skip.indexOf(match[0]) !== -1) {
            continue // skipped or reserved keyword
        }

        if (window[match[0]] !== undefined) {
            continue // keep global functions (Object, Array, etc.)
        }

        var lastChar = undefined
        do {
            match.index -= 1
            if (whitespace.indexOf(expr[match.index]) === -1) {
                lastChar = expr[match.index]
            }
        } while (match.index > -1 && !lastChar)

        if (disallowed.indexOf(lastChar) === -1) {
            refs[match[0]] = true
        }
    }

    return Object.keys(refs)
}

// upgrade an element
// NOTE this will be unnecessary with customElements
function pluto(el) {
    if (typeof el === 'string') {
        // support HTML-imported selections
        var doc = document._currentScript // used by pollyfills
            || document.currentScript // native.
            || document // not an import.
        el = (doc.ownerDocument || doc).querySelector(el)
    }

    return !el || el instanceof Template
        ? el
        : Object.setPrototypeOf(el, Template.prototype)
}

pluto.Template = Template
pluto.RepeatRenderer = Renderer
window.pluto = pluto
})();

(function() {

    // placed here in order to have its own scope clear of any of the pluto
    // local variables.
    pluto._eval = function(code) {
        return eval(code)
    }
})();
<commit_msg>Brevity<commit_after>(function() {
/**
 * PLUTO
 * Native HTML Template Rendering
 *
 *
 * The MIT License (MIT)
 * Copyright (c) 2013 Roi Avinoam
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
class Template extends HTMLTemplateElement {
    render(obj) {
        var { content, exprs, items } = this.compile()
        return new Renderer(content, exprs, items).render(obj)
    }

    _renderIn(obj, el) {
        if (el.__plutoRenderer) {
            el.__plutoRenderer.render(obj)
        } else {
            var doc = this.render(obj)
            el.replaceWith(doc)
            el.__plutoRenderer = doc
            el.remove = doc.remove.bind(doc)
        }
    }

    compile() {
        var exprs = []
        var clone = this.cloneNode(true)
        var elements = [{ el: clone.content, path: [] }]
        while (elements.length > 0) {
            var { el, path } = elements.shift()

            if (el.localName === 'template') {
                // nested templates are coerced to Pluto Templates. This
                // prevents the need to repeatedly mark templates with pluto-tpl
                // with the trade-off of not supporting a mixture of template
                // libraries.
                var render = (el, _, obj) => pluto(el)._renderIn(obj, el)
                exprs.push({ path, render })
                continue
            }

            // inner content expressions
            if (el.nodeName === '#text') {
                var expr = isExpressions(el.textContent)
                if (expr) {
                    el.textContent = ''
                    var render = (el, v) => (el.textContent = v || '')
                    exprs.push({ expr, path, render })
                }
            }

            // attributes
            Array.from(el.attributes || []).forEach(function(attr) {
                var expr = isExpressions(attr.value)
                if (expr === null) {
                    return
                }

                // avoid having the attribute on import as it might arrive
                // un-rendered to web-components. NB: this means that the
                // constructor of elements wouldn't have access to the rendered
                // values initially. Perhaps we should initially render into a
                // clone before finally importing.
                el.removeAttribute(attr.name)
                attr = attr.name
                var evName, prop
                if (attr.startsWith('on-')) {
                    evName = attr.slice(3)
                    var render = function(el, v) {
                        var evs = el.__plutoEvs || (el.__plutoEvs = {})
                        if (evs[evName] !== v) {
                            el.removeEventListener(evName, evs[evName])
                            el.addEventListener(evName, evs[evName] = v)
                        }
                    }
                } else if (['style', 'class'].indexOf(attr) !== -1) {
                    var render = (el, v) => v
                        ? el.setAttribute(attr, v)
                        : el.removeAttribute(attr)
                } else {
                    prop = snakeToCamelCase(attr)
                    var render = (el, v) => el[prop] = v
                }

                exprs.push({ expr, path, attr, evName, prop, render })
            }, this)

            // enqueue children
            el.childNodes.forEach(function(el, i) {
                elements.push({ el, path: path.concat(['childNodes', i]) })
            })
        }

        var repeat = this.getAttribute('repeat') || this.getAttribute('for')
        if (repeat) {
            repeat = compileExpressions([{ expr: repeat }])
            clone.items = (obj) => repeat(obj)[0] || []
        }

        var elseIf = this.getAttribute('else-if')
        var cond = this.getAttribute('if') || elseIf
        if (cond) {
            cond = compileExpressions([{ expr: cond }])
            clone.items = (obj) => Boolean(cond(obj)[0])
        }

        var else_ = this.hasAttribute('else') || elseIf
        if (else_) {
            var else_ = clone.items || Array
            clone.items = (obj) => obj.__plutoElse ? [] : else_(obj)
        }

        // we opt to compile the repeat/cond expressions separately than the
        // rest of this template - because (a) the template might relay on a
        // repeated ${item} property that doesn't yet exist in the repeat
        // expression, and (b) it's must smaller/faster than the complete
        // expressions list.
        // NB: It might not be that beneficial for cond though.
        clone.exprs = Object.assign(exprs, { eval: compileExpressions(exprs) })
        return clone
    }
}

class Renderer extends DocumentFragment {
    constructor(content, exprs, items) {
        super()
        this.tpl = content
        this.exprs = exprs
        this.items = items

        this.elements = []
        this.paths = {}

        if (items) {
            this.placeholder = document.createTextNode('')
            this.appendChild(this.placeholder)
        } else {
            this.appendChild(document.importNode(this.tpl, true))

            // copy the list of generated elements from the template in order
            // to support removals
            this.elements = [].map.call(this.childNodes, child => child)
            this.paths = this.exprs.map((expr) => select(this, expr.path))
        }
    }

    remove() {
        while (this.elements.length > 0) {
            this.elements.pop().remove()
        }
    }

    render(obj) {
        if (!this.items) {
            return this._renderOne(obj)
        }

        var items = this.items(obj)
        var item = obj.item
        if (!Array.isArray(items) && typeof items === 'object') {
            items = Object.keys(items).map(function(k) {
                return { key: k, value: items[k] }
            })
        }

        if (typeof items === 'boolean') {
            items = Number(items) // 0 or 1
        }

        if (typeof items === 'number') {
            items = new Array(items) // range-items, repeat N times.
            items = Array.from(items).map(() => item)
        }

        obj.__plutoElse = items && items.length > 0

        // remove obsolete items
        while (this.elements.length > items.length) {
            this.elements.pop().remove()
        }

        // update existing items
        for (var i = 0; i < this.elements.length; i += 1) {
            obj.item = items[i]
            this.elements[i].render(obj)
        }

        // create new items
        while (this.elements.length < items.length) {
            var i = this.elements.length
            obj.item = items[i]
            var doc = new Renderer(this.tpl, this.exprs).render(obj)
            this.elements.push(doc)
            this.placeholder.before(doc)
        }

        obj.item = item // restore previous item value.
        return this
    }

    _renderOne(obj) {
        var else_ = obj.__plutoElse
        obj.__plutoElse = false

        var values = this.exprs.eval(obj)
        var subtpls = []
        for (var i = 0 ; i < this.exprs.length ; i += 1) {
            var expr = this.exprs[i]
            var el = this.paths[i]
            var v = values[i]
            expr.render(el, v, obj)
        }

        obj.__plutoElse = else_
        return this
    }
}

// -- HELPER FUNCTIONS

// Searches for an element from root based on the property-path to the child
// example: root = <body>, path = childNodes.3.childNode.7. Resolved by walking
// the path down to the child.
function select(root, path) {
    var el = root
    for (var i = 0; el !== undefined && i < path.length; i += 1) {
        el = el[path[i]]
    }
    return el
}

const SNAKE_RE = /-([a-z])/g
function snakeToCamelCase(s) {
    return s.replace(SNAKE_RE, g => g[1].toUpperCase())
}

// extract expressions in template-literal syntax out of a string
var EXPR_RE = /\$\{[^\}]*\}/
function isExpressions(s) {
    return EXPR_RE.test(s) ? s : null
}

// compile a list of template-literal expressions into a function that evaluates
// these expression for the provided input object
function compileExpressions(exprs) {
    var refs = []
    var code = 'this.__plutoT || (this.__plutoT = T.bind(this));\n'
    code += exprs.map(function(expr, i) {
        if (expr.expr) {
            refs = refs.concat(getIdentifiers(expr.expr))
        }

        var tagFn = ({
            'class': 'classNameT',
            'style': 'styleT'
        })[expr.attr] || 'this.__plutoT'

        return `arguments[0][${i}] = ${tagFn}\`${expr.expr}\``
    }).join(';\n')

    var keys = refs.reduce((keys, k) => (keys[k] = true, keys), {})
    var locals = `var { ${Object.keys(keys)} } = this`
    var fn = eval('(function () {\n' + locals + '\n' + code + '\n})')
    return function(obj) {
        var res = []
        try {
            fn.call(obj, res)
        } catch (e) {
            console.warn(fn)
            throw e
        }

        return res
    }

    function T(s, v) {
        if (arguments.length > 2 || typeof v === 'string') {
            return String.raw.apply(null, arguments)
        }

        if (typeof v === 'function' && this[v.name] === v) {
            if (v._plutoBound !== this) {
                v._plutoBound = v.bind(this)
            }
            return v._plutoBound
        }

        return v
    }

    function classNameT(s, v) {
        if (typeof v === 'object') {
            if (!Array.isArray(v)) {
                v = Object.keys(v).filter((k) => v[k])
            }

            return v.join(' ')
        }

        return String.raw.apply(null, arguments)
    }

    function styleT(s, v) {
        if (typeof v === 'object') {
            return Object.keys(v).map((k) => k + ':' + v[k]).join('; ')
        }

        return String.raw.apply(null, arguments)
    }
}

// generate the list of identifiers found in the code.
function getIdentifiers(expr) {
    var re = /[$A-Z_][0-9A-Z_$]*/ig
    var whitespace = ' \n\r\t'
    var disallowed = '\'\".'
    var skip = ['true', 'false', 'if', 'for', 'while', 'do', 'try', 'catch',
        'break', 'continue', 'switch', 'throw', 'this', 'instanceof', 'in',
        'function', 'delete', 'default', 'case', 'debugger', 'const', 'var',
        'with', 'typeof', 'super', 'class', 'new', 'null', 'return', 'let',
        'import', 'else', 'enum', 'extends', 'finally', '$']

    // We first match for the valid identifier, and then check the previous
    // non-whitespace character preceeding the identifier to verify that it's
    // not a string or nested element.
    var refs = {}
    var match
    while (match = re.exec(expr)) {
        if (skip.indexOf(match[0]) !== -1) {
            continue // skipped or reserved keyword
        }

        if (window[match[0]] !== undefined) {
            continue // keep global functions (Object, Array, etc.)
        }

        var lastChar = undefined
        do {
            match.index -= 1
            if (whitespace.indexOf(expr[match.index]) === -1) {
                lastChar = expr[match.index]
            }
        } while (match.index > -1 && !lastChar)

        if (disallowed.indexOf(lastChar) === -1) {
            refs[match[0]] = true
        }
    }

    return Object.keys(refs)
}

// upgrade an element
// NOTE this will be unnecessary with customElements
function pluto(el) {
    if (typeof el === 'string') {
        // support HTML-imported selections
        var doc = document._currentScript // used by pollyfills
            || document.currentScript // native.
            || document // not an import.
        el = (doc.ownerDocument || doc).querySelector(el)
    }

    return !el || el instanceof Template
        ? el
        : Object.setPrototypeOf(el, Template.prototype)
}

pluto.Template = Template
pluto.RepeatRenderer = Renderer
window.pluto = pluto
})();

(function() {

    // placed here in order to have its own scope clear of any of the pluto
    // local variables.
    pluto._eval = function(code) {
        return eval(code)
    }
})();
<|endoftext|>


<commit_before>var set = Ember.set, get = Ember.get,
    resolve = Ember.RSVP.resolve,
    rethrow = Ember.RSVP.rethrow,
    not = Ember.computed.not,
    or = Ember.computed.or;

/**
  @module ember
  @submodule ember-runtime
 */

function observePromise(proxy, promise) {
  promise.then(function(value) {
    set(proxy, 'isFulfilled', true);
    set(proxy, 'content', value);
  }, function(reason) {
    set(proxy, 'isRejected', true);
    set(proxy, 'reason', reason);
    throw reason;
  }, "Ember: PromiseProxy");
}

/**
  A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.

  ```javascript
  var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);

  var controller = ObjectPromiseController.create({
    promise: $.getJSON('/some/remote/data.json')
  });

  controller.then(function(json){
     // the json
  }, function(reason) {
     // the reason why you have no json
  });
  ```

  the controller has bindable attributes which
  track the promises life cycle

  ```javascript
  controller.get('isPending')   //=> true
  controller.get('isSettled')  //=> false
  controller.get('isRejected')  //=> false
  controller.get('isFulfilled') //=> false
  ```

  When the the $.getJSON completes, and the promise is fulfilled
  with json, the life cycle attributes will update accordingly.

  ```javascript
  controller.get('isPending')   //=> false
  controller.get('isSettled')   //=> true
  controller.get('isRejected')  //=> false
  controller.get('isFulfilled') //=> true
  ```

  As the controller is an ObjectController, and the json now its content,
  all the json properties will be available directly from the controller.

  ```javascript
  // Assuming the following json:
  {
    firstName: 'Stefan',
    lastName: 'Penner'
  }

  // both properties will accessible on the controller
  controller.get('firstName') //=> 'Stefan'
  controller.get('lastName')  //=> 'Penner'
  ```

  If the controller is backing a template, the attributes are
  bindable from within that template

  ```handlebars
  {{#if isPending}}
    loading...
  {{else}}
    firstName: {{firstName}}
    lastName: {{lastName}}
  {{/if}}
  ```
  @class Ember.PromiseProxyMixin
*/
Ember.PromiseProxyMixin = Ember.Mixin.create({
  reason:    null,
  isPending:  not('isSettled').readOnly(),
  isSettled:  or('isRejected', 'isFulfilled').readOnly(),
  isRejected:  false,
  isFulfilled: false,

  promise: Ember.computed(function(key, promise) {
    if (arguments.length === 2) {
      promise = resolve(promise);
      observePromise(this, promise);
      return promise.then(); // fork the promise.
    } else {
      throw new Ember.Error("PromiseProxy's promise must be set");
    }
  }),

  then: function(fulfill, reject, label) {
    return get(this, 'promise').then(fulfill, reject, label);
  }
});

<commit_msg>Prevent PromiseProxy from re-throwing.<commit_after>var set = Ember.set, get = Ember.get,
    resolve = Ember.RSVP.resolve,
    rethrow = Ember.RSVP.rethrow,
    not = Ember.computed.not,
    or = Ember.computed.or;

/**
  @module ember
  @submodule ember-runtime
 */

function observePromise(proxy, promise) {
  promise.then(function(value) {
    set(proxy, 'isFulfilled', true);
    set(proxy, 'content', value);
  }, function(reason) {
    set(proxy, 'isRejected', true);
    set(proxy, 'reason', reason);
    // don't re-throw, as we are merely observing
  }, "Ember: PromiseProxy");
}

/**
  A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.

  ```javascript
  var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);

  var controller = ObjectPromiseController.create({
    promise: $.getJSON('/some/remote/data.json')
  });

  controller.then(function(json){
     // the json
  }, function(reason) {
     // the reason why you have no json
  });
  ```

  the controller has bindable attributes which
  track the promises life cycle

  ```javascript
  controller.get('isPending')   //=> true
  controller.get('isSettled')  //=> false
  controller.get('isRejected')  //=> false
  controller.get('isFulfilled') //=> false
  ```

  When the the $.getJSON completes, and the promise is fulfilled
  with json, the life cycle attributes will update accordingly.

  ```javascript
  controller.get('isPending')   //=> false
  controller.get('isSettled')   //=> true
  controller.get('isRejected')  //=> false
  controller.get('isFulfilled') //=> true
  ```

  As the controller is an ObjectController, and the json now its content,
  all the json properties will be available directly from the controller.

  ```javascript
  // Assuming the following json:
  {
    firstName: 'Stefan',
    lastName: 'Penner'
  }

  // both properties will accessible on the controller
  controller.get('firstName') //=> 'Stefan'
  controller.get('lastName')  //=> 'Penner'
  ```

  If the controller is backing a template, the attributes are
  bindable from within that template

  ```handlebars
  {{#if isPending}}
    loading...
  {{else}}
    firstName: {{firstName}}
    lastName: {{lastName}}
  {{/if}}
  ```
  @class Ember.PromiseProxyMixin
*/
Ember.PromiseProxyMixin = Ember.Mixin.create({
  reason:    null,
  isPending:  not('isSettled').readOnly(),
  isSettled:  or('isRejected', 'isFulfilled').readOnly(),
  isRejected:  false,
  isFulfilled: false,

  promise: Ember.computed(function(key, promise) {
    if (arguments.length === 2) {
      promise = resolve(promise);
      observePromise(this, promise);
      return promise.then(); // fork the promise.
    } else {
      throw new Ember.Error("PromiseProxy's promise must be set");
    }
  }),

  then: function(fulfill, reject, label) {
    return get(this, 'promise').then(fulfill, reject, label);
  }
});

<|endoftext|>


<commit_before>'use strict';

/**
 * Trello connector.
 * (C) 2015 Alex Fernández.
 */

// requires
require('prototypes');
var testing = require('testing');
var https = require('https');
var Log = require('log');
var credentials = require('../credentials.json');

// globals
var log = new Log('info');

// constants

exports.createCard = function(name, description, options, callback)
{
	var variables = {
		name: name,
		description: description,
		labels: 'orange',
		token: options.token,
		key: options.key,
	};
	var params = {
		host: 'trello.com',
		path: '/1/lists/' + options.inProgress + '/cards?' + convert(variables),
		method: 'POST',
		agent: false,
	};
	var request = https.request(params, function(response)
	{
		log.info('Received response to %s with status code: %s', params.path, response.statusCode);
		return callback(null);
	});
	request.end();
	request.on('error', function(error)
	{
		callback('Could not create card: ' + error);
	});
};

function convert(map)
{
	var results = [];
	for (var key in map)
	{
		results.push(key + '=' + encodeURIComponent(map[key]));
	}
	return results.join('&');
}

function testCreateCard(callback)
{
	exports.createCard('name', 'description', credentials, function(error)
	{
		testing.check(error, 'Could not create board', callback);
		testing.success(callback);
	});
}

exports.readList = function(id, options, callback)
{
	var variables = {
		token: options.token,
		key: options.key,
	};
	var params = {
		host: 'trello.com',
		path: '/1/lists/' + id + '/cards?' + convert(variables),
		method: 'GET',
		agent: false,
	};
	var request = https.request(params, function(response)
	{
		log.info('Received response to %s with status code: %s', params.path, response.statusCode);
		var body = '';
		response.on('data', function(data)
		{
			body += String(data);
		});
		response.on('error', function(error)
		{
			callback('Could not listen to response: %s', error);
		});
		response.on('end', function()
		{
			try
			{
				var json = JSON.parse(body);
				return callback(null, json);
			}
			catch(exception)
			{
				return callback('Could not parse JSON: ' + exception);
			}
		});
	});
	request.end();
	request.on('error', function(error)
	{
		callback('Could not read list: ' + error);
	});
};

function testReadList(callback)
{
	exports.readList(credentials.underReview, credentials, function(error, result)
	{
		testing.check(error, 'Could not read list', callback);
		log.info('Got %j', result);
		testing.success(callback);
	});
}

/**
 * Run the tests.
 */
exports.test = function(callback)
{
	testing.run([
		testCreateCard,
		testReadList,
	], 5000, callback);
};

// start server if invoked directly
if (__filename == process.argv[1])
{
	log = new Log('debug');
	exports.test(testing.show);
}

<commit_msg>Read list cards.<commit_after>'use strict';

/**
 * Trello connector.
 * (C) 2015 Alex Fernández.
 */

// requires
require('prototypes');
var testing = require('testing');
var https = require('https');
var Log = require('log');
var credentials = require('../credentials.json');

// globals
var log = new Log('info');

// constants

exports.createCard = function(name, description, options, callback)
{
	var variables = {
		name: name,
		description: description,
		labels: 'orange',
		token: options.token,
		key: options.key,
	};
	var params = {
		host: 'trello.com',
		path: '/1/lists/' + options.inProgress + '/cards?' + convert(variables),
		method: 'POST',
		agent: false,
	};
	var request = https.request(params, function(response)
	{
		log.info('Received response to %s with status code: %s', params.path, response.statusCode);
		return callback(null);
	});
	request.end();
	request.on('error', function(error)
	{
		callback('Could not create card: ' + error);
	});
};

function convert(map)
{
	var results = [];
	for (var key in map)
	{
		results.push(key + '=' + encodeURIComponent(map[key]));
	}
	return results.join('&');
}

function testCreateCard(callback)
{
	exports.createCard('name', 'description', credentials, function(error)
	{
		testing.check(error, 'Could not create board', callback);
		testing.success(callback);
	});
}

exports.readListCards = function(id, options, callback)
{
	var variables = {
		token: options.token,
		key: options.key,
	};
	var params = {
		host: 'trello.com',
		path: '/1/lists/' + id + '/cards?' + convert(variables),
		method: 'GET',
		agent: false,
	};
	var request = https.request(params, function(response)
	{
		log.info('Received response to %s with status code: %s', params.path, response.statusCode);
		var body = '';
		response.on('data', function(data)
		{
			body += String(data);
		});
		response.on('error', function(error)
		{
			callback('Could not listen to response: %s', error);
		});
		response.on('end', function()
		{
			try
			{
				var json = JSON.parse(body);
				return callback(null, json);
			}
			catch(exception)
			{
				return callback('Could not parse JSON: ' + exception);
			}
		});
	});
	request.end();
	request.on('error', function(error)
	{
		callback('Could not read list: ' + error);
	});
};

function testReadListCards(callback)
{
	exports.readListCards(credentials.underReview, credentials, function(error, result)
	{
		testing.check(error, 'Could not read list', callback);
		log.info('Got %j', result);
		testing.success(callback);
	});
}

/**
 * Run the tests.
 */
exports.test = function(callback)
{
	testing.run([
		testCreateCard,
		testReadListCards,
	], 5000, callback);
};

// start server if invoked directly
if (__filename == process.argv[1])
{
	log = new Log('debug');
	exports.test(testing.show);
}

<|endoftext|>


<commit_before>xdescribe('Unit: fixedTimerPluginWidget widget app', function () {
  describe('Unit: app routes', function () {
      beforeEach(module('fixedTimerPluginWidget'));
      var location, route, rootScope, compile, scope;
      beforeEach(inject(function (_$rootScope_, _$compile_, $rootScope) {
          // route = _$route_;
          rootScope = _$rootScope_;
          compile = _$compile_;
          scope = $rootScope.$new();
      }));

      describe('Home route', function () {
          beforeEach(inject(
              function ($httpBackend) {
                  $httpBackend.expectGET('/')
                      .respond(200);
                  $httpBackend.expectGET('/')
                      .respond(200);
              }));
          it('should load the home page on successful load of location path /', function () {

          });

      });

  });
    describe('Test the filter', function () {
        'use strict';

        var $filter;

        beforeEach(function () {
            module('fixedTimerPluginWidget');

            inject(function (_$filter_) {
                $filter = _$filter_;
            });
        });

        it('should change the format of date', function () {
            // Arrange.
            var time = new Date(100000), result;
//            var updatedUrl = 80200000;
            // Act.
            result = $filter('secondsToDateTime')(time);

            // Assert.
              expect(result).toBeDefined();
//            expect(result).toEqual(100000000);
        });
    });

    describe('calling the buildfire.messaging.onReceivedMessage for AddNewItem condition', function () {
        var $rootScope;
        beforeEach(module('fixedTimerPluginWidget'));
        beforeEach(inject(function (_$rootScope_) {
            $rootScope = _$rootScope_;
        }));
        var msg = {
            type:'AddNewItem',
            data:{
                pointsToRedeem:5
            }
        };
        it('it should pass when buildfire.messaging.onReceivedMessage', function () {
            buildfire.messaging.onReceivedMessage(msg)
            $rootScope.$apply();
        });
    });

    describe('calling the buildfire.messaging.onReceivedMessage for UpdateItem condition', function () {
        var $rootScope;
        beforeEach(module('fixedTimerPluginWidget'));
        beforeEach(inject(function (_$rootScope_) {
            $rootScope = _$rootScope_;
        }));
        var msg = {
            type:'UpdateItem',
            data:{
                pointsToRedeem:5
            }
        };
        it('it should pass when buildfire.messaging.onReceivedMessage', function () {
            buildfire.messaging.onReceivedMessage(msg)
            $rootScope.$apply();
        });
    });

    describe('calling the buildfire.messaging.onReceivedMessage for ArrangeItems condition', function () {
        var $rootScope;
        beforeEach(module('fixedTimerPluginWidget'));
        beforeEach(inject(function (_$rootScope_) {
            $rootScope = _$rootScope_;
        }));
        var msg = {
            type:'ArrangeItems',
            data:{
                pointsToRedeem:5
            }
        };
        it('it should pass when buildfire.messaging.onReceivedMessage', function () {
            buildfire.messaging.onReceivedMessage(msg)
            $rootScope.$apply();
        });
    });
});<commit_msg>Fixed test cases of app.js<commit_after>describe('Unit: fixedTimerPluginWidget widget app', function () {
  describe('Unit: app routes', function () {
      beforeEach(module('fixedTimerPluginWidget'));
      var location, route, rootScope, compile, scope;
      beforeEach(inject(function (_$rootScope_, _$compile_, $rootScope) {
          // route = _$route_;
          rootScope = _$rootScope_;
          compile = _$compile_;
          scope = $rootScope.$new();
      }));

      describe('Home route', function () {
          beforeEach(inject(
              function ($httpBackend) {
                  $httpBackend.expectGET('/')
                      .respond(200);
                  $httpBackend.expectGET('/')
                      .respond(200);
              }));
          it('should load the home page on successful load of location path /', function () {

          });

      });

  });
    describe('Test the filter', function () {
        'use strict';

        var $filter;

        beforeEach(function () {
            module('fixedTimerPluginWidget');

            inject(function (_$filter_) {
                $filter = _$filter_;
            });
        });

        it('should change the format of date', function () {
            // Arrange.
            var time = new Date(100000), result;
//            var updatedUrl = 80200000;
            // Act.
            result = $filter('secondsToDateTime')(time);

            // Assert.
              expect(result).toBeDefined();
//            expect(result).toEqual(100000000);
        });
    });

    describe('calling the buildfire.messaging.onReceivedMessage for AddNewItem condition', function () {
        var $rootScope;
        beforeEach(module('fixedTimerPluginWidget'));
        beforeEach(inject(function (_$rootScope_) {
            $rootScope = _$rootScope_;
        }));
        var msg = {
            type:'AddNewItem',
            data:{
                pointsToRedeem:5
            }
        };
        it('it should pass when buildfire.messaging.onReceivedMessage', function () {
            buildfire.messaging.onReceivedMessage(msg)
            $rootScope.$apply();
        });
    });

    describe('calling the buildfire.messaging.onReceivedMessage for UpdateItem condition', function () {
        var $rootScope;
        beforeEach(module('fixedTimerPluginWidget'));
        beforeEach(inject(function (_$rootScope_) {
            $rootScope = _$rootScope_;
        }));
        var msg = {
            type:'UpdateItem',
            data:{
                pointsToRedeem:5
            }
        };
        it('it should pass when buildfire.messaging.onReceivedMessage', function () {
            buildfire.messaging.onReceivedMessage(msg)
            $rootScope.$apply();
        });
    });

    describe('calling the buildfire.messaging.onReceivedMessage for ArrangeItems condition', function () {
        var $rootScope;
        beforeEach(module('fixedTimerPluginWidget'));
        beforeEach(inject(function (_$rootScope_) {
            $rootScope = _$rootScope_;
        }));
        var msg = {
            type:'ArrangeItems',
            data:{
                pointsToRedeem:5
            }
        };
        it('it should pass when buildfire.messaging.onReceivedMessage', function () {
            buildfire.messaging.onReceivedMessage(msg)
            $rootScope.$apply();
        });
    });
});<|endoftext|>