<commit_before>/*
 * Tabler.js, renders well-formed HTML tables dynamically
 *
 * Dependencies: jQuery, underscore.js
 *
 * License: MIT
**/
(function(root, factory){
    'use strict';

// Full MicroEvents library @ 54e85c036c3f903b963a0e4a671f72c1089ae4d4
// (added some missing semi-colons etc, that's it)
/**
 * MicroEvent - to make any js object an event emitter (server or browser)
 *
 * - pure javascript - server compatible, browser compatible
 * - dont rely on the browser doms
 * - super simple - you get it immediatly, no mistery, no magic involved
 *
 * - create a MicroEventDebug with goodies to debug
 *   - make it safer to use
*/

var MicroEvent  = function(){};
MicroEvent.prototype    = {
    bind    : function(event, fct){
        this._events = this._events || {};
        this._events[event] = this._events[event]   || [];
        this._events[event].push(fct);
    },
    unbind  : function(event, fct){
        this._events = this._events || {};
        if( event in this._events === false  ) { return; }
        this._events[event].splice(this._events[event].indexOf(fct), 1);
    },
    trigger : function(event /* , args... */){
        this._events = this._events || {};
        if( event in this._events === false  ) { return; }
        for(var i = 0; i < this._events[event].length; i++){
            this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
        }
    }
};

/**
 * mixin will delegate all MicroEvent.js function in the destination object
 *
 * - require('MicroEvent').mixin(Foobar) will make Foobar able to use MicroEvent
 *
 * @param {Object} the object which will support MicroEvent
*/
MicroEvent.mixin    = function(destObject){
    var props   = ['bind', 'unbind', 'trigger'];
    for(var i = 0; i < props.length; i ++){
        destObject.prototype[props[i]]  = MicroEvent.prototype[props[i]];
    }
};
/// END MicroEvents

    // Work with AMD or plain-ol script tag
    if(typeof define === 'function' && define.amd){
        // If window.jQuery or window._ are not defined, then assume we're using AMD modules
        define(['jquery', 'underscore'], function($, _){
            return factory($ || root.jQuery, _ || root._, MicroEvent);
        });
    }else{
        root.tabler = factory(root.jQuery, root._, MicroEvent);
    }
})(this, function($, _, MicroEvent){
    'use strict';

    if(!$){
        throw new Error('tabler requires jQuery to be loaded');
    }
    if(!_){
        throw new Error('tabler requires underscore to be loaded');
    }

    /*
     * If you initialize a tabler without a spec (tabler.create()) then it will build a spec for you
    **/
    function buildDefaultSpec(data){
        return _(data).chain()
            .map(function(row){
                return _(row).keys();
            })
            .flatten()
            .uniq()
            .reduce(function(memo, key){
                memo[key] = {field: key};
                return memo;
            }, {})
            .values()
            .value();
    }

    function validateAndAddToSpec(table, spec){
        var i, s, name, fields = [];

        for(i =0; i < spec.length; i++){
            s = spec[i];
            name = s.id = s.id || s.field || s.name;

            if(!name){
                throw new Error('Spec field at index ' + i + ' does not have a field, id or name property');
            }

            if(fields.indexOf(name) > -1){
                throw new Error('Spec field at index ' + i + ' has duplicate "field" or "id" property of "' + name +
                    '", if it has no "id" property add one, if it has an "id" property ensure it is unique');
            }

            fields.push(name);
            table.spec.push(s);
        }
    }

    /*
     * Constructor doesn't do much
    **/
    function Tabler(options){
        this.$el = $(document.createElement('table'));
        this._plugins = [];

        this.cellClassName = options && options.cellClassName;
        this.headerCellClassName = options && options.headerCellClassName;
        this.headRowClassName = options && options.headRowClassName;
        this.bodyRowClassName = options && options.bodyRowClassName;
        this.footRowClassName = options && options.footRowClassName;
    }

    /*
     * How you create a tabler
     *
     * - spec: An array of spec objects for each column of the table
     * - options: An options hash, at the moment only {plugin: Array} is supported
    **/
    Tabler.create = function(spec, options){
        var table = new Tabler(options);

        if(!options && !_.isArray(spec)){
            options = spec;
            spec = undefined;
        }

        options = options || {};

        _(options.plugins).forEach(function(plugin){
            table.addPlugin(plugin, options[plugin.pluginName]);
        });

        if(options.className){
            table.$el.addClass(options.className);
        }

        if(options.fetch){
            table.fetch = options.fetch;
        }

        if(spec){
            table.spec = [];
            table.addToSpec(spec);
        }

        return table;
    };

    // Add the following to the prototype
    _.extend(Tabler.prototype, {
        /*
         * Registers a plugin with this tabler instance
         *
         * Plugin: The Plugins constructor
         * options: An options hash unique to the plugin (or undefined)
        **/
        addPlugin: function(Plugin, options){
            if(!Plugin.pluginName){
                throw new Error('Plugin ' + Plugin + ' must have a pluginName property');
            }

            var plugin = new Plugin(options);
            plugin.attach(this);

            this[Plugin.pluginName] = plugin;
            this._plugins.push(Plugin.pluginName);
        },
        addToSpec: function(spec){
            var specs = [].concat(spec);

            validateAndAddToSpec(this, specs);
        },
        removeFromSpec: function(matcher){
            var spec = this.getField(matcher);

            this.spec.splice(this.spec.indexOf(spec), 1);
        },
        /*
         * Load the table with some data
        **/
        load: function(data){
            this._data = {
                totalResults: data.length,
                items: _(data).map(function(row){
                    return row;
                })
            };
        },
        fetch: function(options, callback){
            var data = _.clone(this._data);

            data.items = _.map(data.items, _.clone);

            callback(data);
        },
        getFetchOptions: function(){
            return {};
        },
        /*
         * Destroys this instance
        **/
        destroy: function(){
            this._events = undefined;
            this.fetch = undefined;
            this.$el.off().empty().remove();

            this.spec = [];
            _(this._plugins).forEach(function(pluginName){
                if(this[pluginName].detach){
                    this[pluginName].detach(this);
                }
                this[pluginName] = undefined;
            }, this);
            this._plugins = [];
        },
        /*
         * Retrieve a given field spec by field id, which will equal either the "field" or "name" attribute
         * if no "id" is explicitly given on creation
         *
         * Can also supply a hash of desired attributes, eg {name: 'Name'} or {customValue: 'value'}, in which case
         * this method will only return the first field that matches the given hash
        **/
        getField: function(matcher){
            if(_.isUndefined(matcher)){
                throw new Error('matcher must be defined (a string or an object literal)');
            }

            // Can call with no fieldname and just {..vars..}
            if(!_.isObject(matcher)){
                matcher = {
                    id: matcher
                };
            }

            return _(this.spec).detect(function(spec){
                if(_(matcher).all(function(value, key){
                                return spec[key] === value;
                            })){
                    return spec;
                }
            });
        },
        /*
         * Builds the standard attributes for a column - here mostly for plugins to be able to override
        **/
        makeColumnAttrs: function(colSpec){
            var className = (colSpec.className || '');
            if(this.cellClassName){
                className += ' ' + this.cellClassName;
            }
            return {
                width: colSpec.width,
                'class': className.trim()
            };
        },

        makeHeaderAttrs: function(colSpec){
            var className = (colSpec.headerClassName || colSpec.className || '');
            if(this.headerCellClassName){
                className += ' ' + this.headerCellClassName;
            }
            return {
                width: colSpec.width,
                'class': className.trim()
            };
        },
        /*
         * Builds a tag
         *
         * tag: the tag to build ('td', 'td', 'a' etc)
         * attrs: a hash of the attributes to put on the tag
        **/
        makeTag: function(tag, text, attrs){
            var builder = ['<', tag];

            _(attrs).forEach(function(value, attr){
                if(value){
                    builder = builder.concat([' ', attr, '="', _.escape(value), '"']);
                }
            });

            builder = builder.concat(['>', text, '</' + tag + '>']);

            return builder.join('');
        },
        /*
         * Formats a particular column value of a particular row
        **/
        formatValue: function(row, colSpec, index){
            var value = row[colSpec.field];

            if(value === '' || value === undefined || value === null || _.isNaN(value)){
                value = colSpec.defaultText || value;
            }
            value = _.escape(value);

            if(typeof colSpec.formatter === 'function'){
                value = colSpec.formatter.call(this, value, colSpec, row, index);
            }

            return value;
        },
        getSpec: function(data){
            var spec = this.spec;

            if(!this.spec){
                this.spec = [];
                this.addToSpec(buildDefaultSpec(data.items));
            }

            spec = _(this.spec).filter(function(colSpec){
                return !colSpec.disabled;
            });

            return spec;
        },
        /*
         * Where the magic happens
        **/
        render: function(){
            var options = this.getFetchOptions();

            this.$el.addClass('loading');
            this.fetch(options, _(this.renderTable).bind(this));
        },
        update: function(rowIndex, newItem, options){
            var spec = this.getSpec(),
                $row = this.$('> tbody tr').eq(rowIndex),
                $tds, updateFieldNames = Object.keys(newItem);

            if(options && options.invalidateRow){
                return $row.replaceWith(this.renderRow(newItem, rowIndex, spec));
            }
            $tds = $row.find('td');

            _(spec).forEach(function(spec, cellIndex){
                if(spec.disabled){
                    return;
                }
                var fields = [spec.field].concat(spec.updateFields || []);
                if(_.intersection(updateFieldNames, fields).length > 0){
                    $tds.eq(cellIndex).replaceWith(this.renderCell(newItem, spec, rowIndex));
                }
            }, this);
        },
        renderTable: function(data){
            var spec = this.getSpec(data),
                head, body, foot;

            if(!data.items){
                throw new Error('no data.items (not even an empty array)');
            }
            this.$el.removeClass('loading');

            head = this.renderHead(data, spec);
            body = this.renderBody(data, spec);
            foot = this.renderFoot(data, spec);

            this.$el.empty();
            // Only render the bits that actually returned any HTML - we deal with wrapping in thead, tfoot etc
            if(head){
                this.$el.append('
' + head + '
');
            }
            if(body){
                this.$el.append('
' + body + '
');
            }
            if(foot){
                this.$el.append('
' + foot + '
');
            }

/*DEBUG
            var self = this;
            if(!this.renderedThisTick){
                this.renderedThisTick = 0;
            }
            if(this.renderedThisTick++){
                console.warn('tabler', this, 'rendered', this.renderedThisTick, 'times this tick!');
            }
            _.defer(function(){
                self.renderedThisTick = 0;
            });
///DEBUG*/
        },
        renderHeadTr: function(){
            if (this.headRowClassName){
                return '
';
            }
            return '
';
        },
        /*
         * Renders row(s) for the thead of the table
         *
         * data: the data being rendered, with data.items [] and totalResults item
         * spec: the spec for the table
        **/
        renderHead: function(data, spec){
            var head = '';

            if(_(spec).any(function(col){return !!col.name || !!col.headerFormatter;})){
                // Main headings
                head += this.renderHeadRow(spec);
            }
            return head;
        },
        renderHeadRow: function(spec){
            return this.renderHeadTr() + _(spec)
                .map(function(colSpec){
                    return this.renderHeadCell(colSpec);
                }, this)
                .join('\n') + '
';
        },
        renderHeadCell: function(colSpec){
            var title = _.isString(colSpec.title) ? colSpec.title : colSpec.name;
            title = _.escape(title);

            if(typeof colSpec.headerFormatter === 'function'){
                title = colSpec.headerFormatter.call(this, colSpec, title);
            }

            return this.makeTag('th', title, this.makeHeaderAttrs(colSpec));
        },
        renderBodyTr: function(/*row*/){
            if (this.bodyRowClassName){
                return '
';
            }
            return '
';
        },
        renderFootTr: function(){
            if (this.footRowClassName){
                return '
';
            }
            return '
';
        },
        /*
         * Renders row(s) for the tbody of the table
         *
         * data: the data being rendered, with data.items [] and totalResults item
         * spec: the spec for the table
        **/
        renderBody: function(data, spec){
            var self = this,
                body = _(data.items).map(function(row, i){
                    return self.renderRow(row, i, spec);
                }).join('\n');
            return body;
        },
        renderRow: function(row, i, spec){
            var self = this;

            return [this.renderBodyTr(row)].concat(_(spec).map(function(colSpec){
                return self.renderCell(row, _.clone(colSpec), i);
            })).concat('
').join('\n');
        },
        renderCell: function(row, colSpec, index){
            return this.makeTag('td', this.formatValue(row, colSpec, index), this.makeColumnAttrs(colSpec));
        },
        /*
         * Renders row(s) for the tfoot of the table
         *
         * data: the data being rendered, with data.items [] and totalResults item
         * spec: the spec for the table
        **/
        renderFoot: function(/*data, spec*/){
            return '';
        },
        /*
         * Wrapper function for querying the DOM of the table
         * Note you can always access the main
 element through the $el property
        **/
        $: function(){
            return this.$el.find.apply(this.$el, arguments);
        }
    });

    MicroEvent.mixin(Tabler);

    return Tabler;
});
<commit_msg>Optimised tabler plugin attachment and detachment<commit_after>/*
 * Tabler.js, renders well-formed HTML tables dynamically
 *
 * Dependencies: jQuery, underscore.js
 *
 * License: MIT
**/
(function(root, factory){
    'use strict';

// Full MicroEvents library @ 54e85c036c3f903b963a0e4a671f72c1089ae4d4
// (added some missing semi-colons etc, that's it)
/**
 * MicroEvent - to make any js object an event emitter (server or browser)
 *
 * - pure javascript - server compatible, browser compatible
 * - dont rely on the browser doms
 * - super simple - you get it immediatly, no mistery, no magic involved
 *
 * - create a MicroEventDebug with goodies to debug
 *   - make it safer to use
*/

var MicroEvent  = function(){};
MicroEvent.prototype    = {
    bind    : function(event, fct){
        this._events = this._events || {};
        this._events[event] = this._events[event]   || [];
        this._events[event].push(fct);
    },
    unbind  : function(event, fct){
        this._events = this._events || {};
        if( event in this._events === false  ) { return; }
        this._events[event].splice(this._events[event].indexOf(fct), 1);
    },
    trigger : function(event /* , args... */){
        this._events = this._events || {};
        if( event in this._events === false  ) { return; }
        for(var i = 0; i < this._events[event].length; i++){
            this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
        }
    }
};

/**
 * mixin will delegate all MicroEvent.js function in the destination object
 *
 * - require('MicroEvent').mixin(Foobar) will make Foobar able to use MicroEvent
 *
 * @param {Object} the object which will support MicroEvent
*/
MicroEvent.mixin    = function(destObject){
    var props   = ['bind', 'unbind', 'trigger'];
    for(var i = 0; i < props.length; i ++){
        destObject.prototype[props[i]]  = MicroEvent.prototype[props[i]];
    }
};
/// END MicroEvents

    // Work with AMD or plain-ol script tag
    if(typeof define === 'function' && define.amd){
        // If window.jQuery or window._ are not defined, then assume we're using AMD modules
        define(['jquery', 'underscore'], function($, _){
            return factory($ || root.jQuery, _ || root._, MicroEvent);
        });
    }else{
        root.tabler = factory(root.jQuery, root._, MicroEvent);
    }
})(this, function($, _, MicroEvent){
    'use strict';

    if(!$){
        throw new Error('tabler requires jQuery to be loaded');
    }
    if(!_){
        throw new Error('tabler requires underscore to be loaded');
    }

    /*
     * If you initialize a tabler without a spec (tabler.create()) then it will build a spec for you
    **/
    function buildDefaultSpec(data){
        return _(data).chain()
            .map(function(row){
                return _(row).keys();
            })
            .flatten()
            .uniq()
            .reduce(function(memo, key){
                memo[key] = {field: key};
                return memo;
            }, {})
            .values()
            .value();
    }

    function validateAndAddToSpec(table, spec){
        var i, s, name, fields = [];

        for(i =0; i < spec.length; i++){
            s = spec[i];
            name = s.id = s.id || s.field || s.name;

            if(!name){
                throw new Error('Spec field at index ' + i + ' does not have a field, id or name property');
            }

            if(fields.indexOf(name) > -1){
                throw new Error('Spec field at index ' + i + ' has duplicate "field" or "id" property of "' + name +
                    '", if it has no "id" property add one, if it has an "id" property ensure it is unique');
            }

            fields.push(name);
            table.spec.push(s);
        }
    }

    /*
     * Constructor doesn't do much
    **/
    function Tabler(options){
        this.$el = $(document.createElement('table'));
        this._plugins = [];

        this.cellClassName = options && options.cellClassName;
        this.headerCellClassName = options && options.headerCellClassName;
        this.headRowClassName = options && options.headRowClassName;
        this.bodyRowClassName = options && options.bodyRowClassName;
        this.footRowClassName = options && options.footRowClassName;
    }

    /*
     * How you create a tabler
     *
     * - spec: An array of spec objects for each column of the table
     * - options: An options hash, at the moment only {plugin: Array} is supported
    **/
    Tabler.create = function(spec, options){
        var table = new Tabler(options),
            plugins = (options && options.plugins) || [],
            i;

        if(!options && !_.isArray(spec)){
            options = spec;
            spec = undefined;
        }

        options = options || {};

        for(i = 0; i < plugins.length; i++){
            table.addPlugin(plugins[i], options[plugins[i].pluginName]);
        }

        if(options.className){
            table.$el.addClass(options.className);
        }

        if(options.fetch){
            table.fetch = options.fetch;
        }

        if(spec){
            table.spec = [];
            table.addToSpec(spec);
        }

        return table;
    };

    // Add the following to the prototype
    _.extend(Tabler.prototype, {
        /*
         * Registers a plugin with this tabler instance
         *
         * Plugin: The Plugins constructor
         * options: An options hash unique to the plugin (or undefined)
        **/
        addPlugin: function(Plugin, options){
            if(!Plugin.pluginName){
                throw new Error('Plugin ' + Plugin + ' must have a pluginName property');
            }

            var plugin = new Plugin(options);
            plugin.attach(this);

            this[Plugin.pluginName] = plugin;
            this._plugins.push(Plugin.pluginName);
        },
        addToSpec: function(spec){
            var specs = [].concat(spec);

            validateAndAddToSpec(this, specs);
        },
        removeFromSpec: function(matcher){
            var spec = this.getField(matcher);

            this.spec.splice(this.spec.indexOf(spec), 1);
        },
        /*
         * Load the table with some data
        **/
        load: function(data){
            this._data = {
                totalResults: data.length,
                items: _(data).map(function(row){
                    return row;
                })
            };
        },
        fetch: function(options, callback){
            var data = _.clone(this._data);

            data.items = _.map(data.items, _.clone);

            callback(data);
        },
        getFetchOptions: function(){
            return {};
        },
        /*
         * Destroys this instance
        **/
        destroy: function(){
            /*jshint boss:true*/
            var pluginName;

            this._events = undefined;
            this.fetch = undefined;
            this.$el.off().empty().remove();

            this.spec = [];

            while(pluginName = this._plugins.shift()){
                if(this[pluginName].detach){
                    this[pluginName].detach(this);
                }
                this[pluginName] = undefined;
            }
            this._plugins = [];
        },
        /*
         * Retrieve a given field spec by field id, which will equal either the "field" or "name" attribute
         * if no "id" is explicitly given on creation
         *
         * Can also supply a hash of desired attributes, eg {name: 'Name'} or {customValue: 'value'}, in which case
         * this method will only return the first field that matches the given hash
        **/
        getField: function(matcher){
            if(_.isUndefined(matcher)){
                throw new Error('matcher must be defined (a string or an object literal)');
            }

            // Can call with no fieldname and just {..vars..}
            if(!_.isObject(matcher)){
                matcher = {
                    id: matcher
                };
            }

            return _(this.spec).detect(function(spec){
                if(_(matcher).all(function(value, key){
                                return spec[key] === value;
                            })){
                    return spec;
                }
            });
        },
        /*
         * Builds the standard attributes for a column - here mostly for plugins to be able to override
        **/
        makeColumnAttrs: function(colSpec){
            var className = (colSpec.className || '');
            if(this.cellClassName){
                className += ' ' + this.cellClassName;
            }
            return {
                width: colSpec.width,
                'class': className.trim()
            };
        },

        makeHeaderAttrs: function(colSpec){
            var className = (colSpec.headerClassName || colSpec.className || '');
            if(this.headerCellClassName){
                className += ' ' + this.headerCellClassName;
            }
            return {
                width: colSpec.width,
                'class': className.trim()
            };
        },
        /*
         * Builds a tag
         *
         * tag: the tag to build ('td', 'td', 'a' etc)
         * attrs: a hash of the attributes to put on the tag
        **/
        makeTag: function(tag, text, attrs){
            var builder = ['<', tag];

            _(attrs).forEach(function(value, attr){
                if(value){
                    builder = builder.concat([' ', attr, '="', _.escape(value), '"']);
                }
            });

            builder = builder.concat(['>', text, '</' + tag + '>']);

            return builder.join('');
        },
        /*
         * Formats a particular column value of a particular row
        **/
        formatValue: function(row, colSpec, index){
            var value = row[colSpec.field];

            if(value === '' || value === undefined || value === null || _.isNaN(value)){
                value = colSpec.defaultText || value;
            }
            value = _.escape(value);

            if(typeof colSpec.formatter === 'function'){
                value = colSpec.formatter.call(this, value, colSpec, row, index);
            }

            return value;
        },
        getSpec: function(data){
            var spec = this.spec;

            if(!this.spec){
                this.spec = [];
                this.addToSpec(buildDefaultSpec(data.items));
            }

            spec = _(this.spec).filter(function(colSpec){
                return !colSpec.disabled;
            });

            return spec;
        },
        /*
         * Where the magic happens
        **/
        render: function(){
            var options = this.getFetchOptions();

            this.$el.addClass('loading');
            this.fetch(options, _(this.renderTable).bind(this));
        },
        update: function(rowIndex, newItem, options){
            var spec = this.getSpec(),
                $row = this.$('> tbody tr').eq(rowIndex),
                $tds, updateFieldNames = Object.keys(newItem);

            if(options && options.invalidateRow){
                return $row.replaceWith(this.renderRow(newItem, rowIndex, spec));
            }
            $tds = $row.find('td');

            _(spec).forEach(function(spec, cellIndex){
                if(spec.disabled){
                    return;
                }
                var fields = [spec.field].concat(spec.updateFields || []);
                if(_.intersection(updateFieldNames, fields).length > 0){
                    $tds.eq(cellIndex).replaceWith(this.renderCell(newItem, spec, rowIndex));
                }
            }, this);
        },
        renderTable: function(data){
            var spec = this.getSpec(data),
                head, body, foot;

            if(!data.items){
                throw new Error('no data.items (not even an empty array)');
            }
            this.$el.removeClass('loading');

            head = this.renderHead(data, spec);
            body = this.renderBody(data, spec);
            foot = this.renderFoot(data, spec);

            this.$el.empty();
            // Only render the bits that actually returned any HTML - we deal with wrapping in thead, tfoot etc
            if(head){
                this.$el.append('
' + head + '
');
            }
            if(body){
                this.$el.append('
' + body + '
');
            }
            if(foot){
                this.$el.append('
' + foot + '
');
            }

/*DEBUG
            var self = this;
            if(!this.renderedThisTick){
                this.renderedThisTick = 0;
            }
            if(this.renderedThisTick++){
                console.warn('tabler', this, 'rendered', this.renderedThisTick, 'times this tick!');
            }
            _.defer(function(){
                self.renderedThisTick = 0;
            });
///DEBUG*/
        },
        renderHeadTr: function(){
            if (this.headRowClassName){
                return '
';
            }
            return '
';
        },
        /*
         * Renders row(s) for the thead of the table
         *
         * data: the data being rendered, with data.items [] and totalResults item
         * spec: the spec for the table
        **/
        renderHead: function(data, spec){
            var head = '';

            if(_(spec).any(function(col){return !!col.name || !!col.headerFormatter;})){
                // Main headings
                head += this.renderHeadRow(spec);
            }
            return head;
        },
        renderHeadRow: function(spec){
            return this.renderHeadTr() + _(spec)
                .map(function(colSpec){
                    return this.renderHeadCell(colSpec);
                }, this)
                .join('\n') + '
';
        },
        renderHeadCell: function(colSpec){
            var title = _.isString(colSpec.title) ? colSpec.title : colSpec.name;
            title = _.escape(title);

            if(typeof colSpec.headerFormatter === 'function'){
                title = colSpec.headerFormatter.call(this, colSpec, title);
            }

            return this.makeTag('th', title, this.makeHeaderAttrs(colSpec));
        },
        renderBodyTr: function(/*row*/){
            if (this.bodyRowClassName){
                return '
';
            }
            return '
';
        },
        renderFootTr: function(){
            if (this.footRowClassName){
                return '
';
            }
            return '
';
        },
        /*
         * Renders row(s) for the tbody of the table
         *
         * data: the data being rendered, with data.items [] and totalResults item
         * spec: the spec for the table
        **/
        renderBody: function(data, spec){
            var self = this,
                body = _(data.items).map(function(row, i){
                    return self.renderRow(row, i, spec);
                }).join('\n');
            return body;
        },
        renderRow: function(row, i, spec){
            var self = this;

            return [this.renderBodyTr(row)].concat(_(spec).map(function(colSpec){
                return self.renderCell(row, _.clone(colSpec), i);
            })).concat('
').join('\n');
        },
        renderCell: function(row, colSpec, index){
            return this.makeTag('td', this.formatValue(row, colSpec, index), this.makeColumnAttrs(colSpec));
        },
        /*
         * Renders row(s) for the tfoot of the table
         *
         * data: the data being rendered, with data.items [] and totalResults item
         * spec: the spec for the table
        **/
        renderFoot: function(/*data, spec*/){
            return '';
        },
        /*
         * Wrapper function for querying the DOM of the table
         * Note you can always access the main
 element through the $el property
        **/
        $: function(){
            return this.$el.find.apply(this.$el, arguments);
        }
    });

    MicroEvent.mixin(Tabler);

    return Tabler;
});
<|endoftext|>


<commit_before>'use strict';
var Vec3 = require( 'osg/Vec3' );
var TriangleIntersector = require( 'osgUtil/TriangleIntersector' );
var Notify = require( 'osg/Notify' );

var KdTreeRayIntersector = function () {

    if ( arguments && arguments.length ) {
        Notify.warn( 'using ctor as initialiser is deprecated, use init(intersections, start, end, nodePath) and/or     setKdtree: function ( vertices, nodes, triangles )' );
    }

    this._intersector = new TriangleIntersector();
    this._dInvX = Vec3.create();
    this._dInvY = Vec3.create();
    this._dInvZ = Vec3.create();

};

KdTreeRayIntersector.prototype = {
    setKdtree: function ( vertices, nodes, triangles ) {
        this._vertices = vertices;
        this._kdNodes = nodes;
        this._triangles = triangles;
    },
    init: ( function () {

        var dir = Vec3.create();

        return function ( intersections, start, end, nodePath ) {
            var d = Vec3.sub( end, start, dir );
            var len = Vec3.length( d );
            var invLen = 0.0;
            if ( len !== 0.0 )
                invLen = 1.0 / len;
            Vec3.mult( d, invLen, d );
            if ( d[ 0 ] !== 0.0 ) Vec3.mult( d, 1.0 / d[ 0 ], this._dInvX );
            if ( d[ 1 ] !== 0.0 ) Vec3.mult( d, 1.0 / d[ 1 ], this._dInvY );
            if ( d[ 2 ] !== 0.0 ) Vec3.mult( d, 1.0 / d[ 2 ], this._dInvZ );

            this._intersector._intersections = intersections;
            this._intersector.setNodePath( nodePath );
            this._intersector.set( start, end );
        };
    } )(),
    // Classic ray intersection test
    // If it's a leaf it does ray-triangles intersection with the triangles in the cell
    // If it's not a leaf, it descend in the tree in a recursive way as long as the ray
    // intersects the boundinbox of the nodes
    intersect: ( function () {

        var v0 = Vec3.create();
        var v1 = Vec3.create();
        var v2 = Vec3.create();

        return function ( node, ls, le ) {
            var first = node._first;
            var second = node._second;
            var triangles = this._triangles;
            var vertices = this._vertices;

            if ( first < 0 ) {
                // treat as a leaf
                var istart = -first - 1;
                var iend = istart + second;
                var intersector = this._intersector;
                intersector.index = istart;

                for ( var i = istart; i < iend; ++i ) {
                    var id = i * 3;
                    var iv0 = triangles[ id ];
                    var iv1 = triangles[ id + 1 ];
                    var iv2 = triangles[ id + 2 ];

                    var j = iv0 * 3;
                    v0[ 0 ] = vertices[ j ];
                    v0[ 1 ] = vertices[ j + 1 ];
                    v0[ 2 ] = vertices[ j + 2 ];

                    j = iv1 * 3;
                    v1[ 0 ] = vertices[ j ];
                    v1[ 1 ] = vertices[ j + 1 ];
                    v1[ 2 ] = vertices[ j + 2 ];

                    j = iv2 * 3;
                    v2[ 0 ] = vertices[ j ];
                    v2[ 1 ] = vertices[ j + 1 ];
                    v2[ 2 ] = vertices[ j + 2 ];

                    intersector.intersect( v0, v1, v2, iv0, iv1, iv2 );
                }
            } else {
                var s = node._nodeRayStart;
                var e = node._nodeRayEnd;
                Vec3.copy( ls, s );
                Vec3.copy( le, e );
                if ( first > 0 ) {
                    if ( this.intersectAndClip( s, e, this._kdNodes[ first ]._bb ) ) {
                        this.intersect( this._kdNodes[ first ], s, e );
                    }
                }
                if ( second > 0 ) {
                    Vec3.copy( ls, s );
                    Vec3.copy( le, e );
                    if ( this.intersectAndClip( s, e, this._kdNodes[ second ]._bb ) ) {
                        this.intersect( this._kdNodes[ second ], s, e );
                    }
                }
            }
        };
    } )(),
    // This method do 2 things
    // It test if the ray intersects the node
    // If so... it clip the ray so that the start and end point of the ray are
    // snapped to the bounding box of the nodes
    intersectAndClip: ( function () {
        var tmp = Vec3.create();
        return function ( s, e, bb ) {
            var min = bb._min;
            var xmin = min[ 0 ];
            var ymin = min[ 1 ];
            var zmin = min[ 2 ];

            var max = bb._max;
            var xmax = max[ 0 ];
            var ymax = max[ 1 ];
            var zmax = max[ 2 ];

            var invX = this._dInvX;
            var invY = this._dInvY;
            var invZ = this._dInvZ;

            if ( s[ 0 ] <= e[ 0 ] ) {
                // trivial reject of segment wholely outside.
                if ( e[ 0 ] < xmin ) return false;
                if ( s[ 0 ] > xmax ) return false;

                if ( s[ 0 ] < xmin ) {
                    // clip s to xMin.
                    Vec3.mult( invX, xmin - s[ 0 ], tmp );
                    Vec3.add( s, tmp, s );
                }

                if ( e[ 0 ] > xmax ) {
                    // clip e to xMax.
                    Vec3.mult( invX, xmax - s[ 0 ], tmp );
                    Vec3.add( s, tmp, e );
                }
            } else {
                if ( s[ 0 ] < xmin ) return false;
                if ( e[ 0 ] > xmax ) return false;

                if ( e[ 0 ] < xmin ) {
                    // clip s to xMin.
                    Vec3.mult( invX, xmin - s[ 0 ], tmp );
                    Vec3.add( s, tmp, e );
                }

                if ( s[ 0 ] > xmax ) {
                    // clip e to xMax.
                    Vec3.mult( invX, xmax - s[ 0 ], tmp );
                    Vec3.add( s, tmp, s );
                }
            }

            // compate s and e against the yMin to yMax range of bb.
            if ( s[ 1 ] <= e[ 1 ] ) {

                // trivial reject of segment wholely outside.
                if ( e[ 1 ] < ymin ) return false;
                if ( s[ 1 ] > ymax ) return false;

                if ( s[ 1 ] < ymin ) {
                    // clip s to yMin.
                    Vec3.mult( invY, ymin - s[ 1 ], tmp );
                    Vec3.add( s, tmp, s );
                }

                if ( e[ 1 ] > ymax ) {
                    // clip e to yMax.
                    Vec3.mult( invY, ymax - s[ 1 ], tmp );
                    Vec3.add( s, tmp, e );
                }
            } else {
                if ( s[ 1 ] < ymin ) return false;
                if ( e[ 1 ] > ymax ) return false;

                if ( e[ 1 ] < ymin ) {
                    // clip s to yMin.
                    Vec3.mult( invY, ymin - s[ 1 ], tmp );
                    Vec3.add( s, tmp, e );
                }

                if ( s[ 1 ] > ymax ) {
                    // clip e to yMax.
                    Vec3.mult( invY, ymax - s[ 1 ], tmp );
                    Vec3.add( s, tmp, s );
                }
            }

            // compate s and e against the zMin to zMax range of bb.
            if ( s[ 2 ] <= e[ 2 ] ) {
                // trivial reject of segment wholely outside.
                if ( e[ 2 ] < zmin ) return false;
                if ( s[ 2 ] > zmax ) return false;

                if ( s[ 2 ] < zmin ) {
                    // clip s to zMin.
                    Vec3.mult( invZ, zmin - s[ 2 ], tmp );
                    Vec3.add( s, tmp, s );
                }

                if ( e[ 2 ] > zmax ) {
                    // clip e to zMax.
                    Vec3.mult( invZ, zmax - s[ 2 ], tmp );
                    Vec3.add( s, tmp, e );
                }
            } else {
                if ( s[ 2 ] < zmin ) return false;
                if ( e[ 2 ] > zmax ) return false;

                if ( e[ 2 ] < zmin ) {
                    // clip s to zMin.
                    Vec3.mult( invZ, zmin - s[ 2 ], tmp );
                    Vec3.add( s, tmp, e );
                }

                if ( s[ 2 ] > zmax ) {
                    // clip e to zMax.
                    Vec3.mult( invZ, zmax - s[ 2 ], tmp );
                    Vec3.add( s, tmp, s );
                }
            }
            return true;
        };
    } )()
};

module.exports = KdTreeRayIntersector;
<commit_msg>Fixes precision picking<commit_after>'use strict';
var Vec3 = require( 'osg/Vec3' );
var TriangleIntersector = require( 'osgUtil/TriangleIntersector' );
var Notify = require( 'osg/Notify' );

var KdTreeRayIntersector = function () {

    if ( arguments && arguments.length ) {
        Notify.warn( 'using ctor as initialiser is deprecated, use init(intersections, start, end, nodePath) and/or     setKdtree: function ( vertices, nodes, triangles )' );
    }

    this._intersector = new TriangleIntersector();
    this._dInvX = Vec3.create();
    this._dInvY = Vec3.create();
    this._dInvZ = Vec3.create();

};

KdTreeRayIntersector.prototype = {
    setKdtree: function ( vertices, nodes, triangles ) {
        this._vertices = vertices;
        this._kdNodes = nodes;
        this._triangles = triangles;
    },
    init: ( function () {

        var dir = Vec3.create();

        return function ( intersections, start, end, nodePath ) {
            var d = Vec3.sub( end, start, dir );
            var len = Vec3.length( d );
            var invLen = 0.0;
            if ( len !== 0.0 )
                invLen = 1.0 / len;
            Vec3.mult( d, invLen, d );
            if ( d[ 0 ] !== 0.0 ) Vec3.mult( d, 1.0 / d[ 0 ], this._dInvX );
            if ( d[ 1 ] !== 0.0 ) Vec3.mult( d, 1.0 / d[ 1 ], this._dInvY );
            if ( d[ 2 ] !== 0.0 ) Vec3.mult( d, 1.0 / d[ 2 ], this._dInvZ );

            this._intersector._intersections = intersections;
            this._intersector.setNodePath( nodePath );
            this._intersector.set( start, end );
        };
    } )(),
    // Classic ray intersection test
    // If it's a leaf it does ray-triangles intersection with the triangles in the cell
    // If it's not a leaf, it descend in the tree in a recursive way as long as the ray
    // intersects the boundinbox of the nodes
    intersect: ( function () {

        var v0 = Vec3.create();
        var v1 = Vec3.create();
        var v2 = Vec3.create();

        return function ( node, ls, le ) {
            var first = node._first;
            var second = node._second;
            var triangles = this._triangles;
            var vertices = this._vertices;

            if ( first < 0 ) {
                // treat as a leaf
                var istart = -first - 1;
                var iend = istart + second;
                var intersector = this._intersector;
                intersector.index = istart;

                for ( var i = istart; i < iend; ++i ) {
                    var id = i * 3;
                    var iv0 = triangles[ id ];
                    var iv1 = triangles[ id + 1 ];
                    var iv2 = triangles[ id + 2 ];

                    var j = iv0 * 3;
                    v0[ 0 ] = vertices[ j ];
                    v0[ 1 ] = vertices[ j + 1 ];
                    v0[ 2 ] = vertices[ j + 2 ];

                    j = iv1 * 3;
                    v1[ 0 ] = vertices[ j ];
                    v1[ 1 ] = vertices[ j + 1 ];
                    v1[ 2 ] = vertices[ j + 2 ];

                    j = iv2 * 3;
                    v2[ 0 ] = vertices[ j ];
                    v2[ 1 ] = vertices[ j + 1 ];
                    v2[ 2 ] = vertices[ j + 2 ];

                    intersector.intersect( v0, v1, v2, iv0, iv1, iv2 );
                }
            } else {
                var s = node._nodeRayStart;
                var e = node._nodeRayEnd;
                Vec3.copy( ls, s );
                Vec3.copy( le, e );
                if ( first > 0 ) {
                    if ( this.intersectAndClip( s, e, this._kdNodes[ first ]._bb ) ) {
                        this.intersect( this._kdNodes[ first ], s, e );
                    }
                }
                if ( second > 0 ) {
                    Vec3.copy( ls, s );
                    Vec3.copy( le, e );
                    if ( this.intersectAndClip( s, e, this._kdNodes[ second ]._bb ) ) {
                        this.intersect( this._kdNodes[ second ], s, e );
                    }
                }
            }
        };
    } )(),
    // This method do 2 things
    // It test if the ray intersects the node
    // If so... it clip the ray so that the start and end point of the ray are
    // snapped to the bounding box of the nodes
    intersectAndClip: ( function () {

        // needed because of precision picking
        var tmp = new Float64Array( 3 );

        return function ( s, e, bb ) {
            var min = bb._min;
            var xmin = min[ 0 ];
            var ymin = min[ 1 ];
            var zmin = min[ 2 ];

            var max = bb._max;
            var xmax = max[ 0 ];
            var ymax = max[ 1 ];
            var zmax = max[ 2 ];

            var invX = this._dInvX;
            var invY = this._dInvY;
            var invZ = this._dInvZ;

            if ( s[ 0 ] <= e[ 0 ] ) {
                // trivial reject of segment wholely outside.
                if ( e[ 0 ] < xmin ) return false;
                if ( s[ 0 ] > xmax ) return false;

                if ( s[ 0 ] < xmin ) {
                    // clip s to xMin.
                    Vec3.mult( invX, xmin - s[ 0 ], tmp );
                    Vec3.add( s, tmp, s );
                }

                if ( e[ 0 ] > xmax ) {
                    // clip e to xMax.
                    Vec3.mult( invX, xmax - s[ 0 ], tmp );
                    Vec3.add( s, tmp, e );
                }
            } else {
                if ( s[ 0 ] < xmin ) return false;
                if ( e[ 0 ] > xmax ) return false;

                if ( e[ 0 ] < xmin ) {
                    // clip s to xMin.
                    Vec3.mult( invX, xmin - s[ 0 ], tmp );
                    Vec3.add( s, tmp, e );
                }

                if ( s[ 0 ] > xmax ) {
                    // clip e to xMax.
                    Vec3.mult( invX, xmax - s[ 0 ], tmp );
                    Vec3.add( s, tmp, s );
                }
            }

            // compate s and e against the yMin to yMax range of bb.
            if ( s[ 1 ] <= e[ 1 ] ) {

                // trivial reject of segment wholely outside.
                if ( e[ 1 ] < ymin ) return false;
                if ( s[ 1 ] > ymax ) return false;

                if ( s[ 1 ] < ymin ) {
                    // clip s to yMin.
                    Vec3.mult( invY, ymin - s[ 1 ], tmp );
                    Vec3.add( s, tmp, s );
                }

                if ( e[ 1 ] > ymax ) {
                    // clip e to yMax.
                    Vec3.mult( invY, ymax - s[ 1 ], tmp );
                    Vec3.add( s, tmp, e );
                }
            } else {
                if ( s[ 1 ] < ymin ) return false;
                if ( e[ 1 ] > ymax ) return false;

                if ( e[ 1 ] < ymin ) {
                    // clip s to yMin.
                    Vec3.mult( invY, ymin - s[ 1 ], tmp );
                    Vec3.add( s, tmp, e );
                }

                if ( s[ 1 ] > ymax ) {
                    // clip e to yMax.
                    Vec3.mult( invY, ymax - s[ 1 ], tmp );
                    Vec3.add( s, tmp, s );
                }
            }

            // compate s and e against the zMin to zMax range of bb.
            if ( s[ 2 ] <= e[ 2 ] ) {
                // trivial reject of segment wholely outside.
                if ( e[ 2 ] < zmin ) return false;
                if ( s[ 2 ] > zmax ) return false;

                if ( s[ 2 ] < zmin ) {
                    // clip s to zMin.
                    Vec3.mult( invZ, zmin - s[ 2 ], tmp );
                    Vec3.add( s, tmp, s );
                }

                if ( e[ 2 ] > zmax ) {
                    // clip e to zMax.
                    Vec3.mult( invZ, zmax - s[ 2 ], tmp );
                    Vec3.add( s, tmp, e );
                }
            } else {
                if ( s[ 2 ] < zmin ) return false;
                if ( e[ 2 ] > zmax ) return false;

                if ( e[ 2 ] < zmin ) {
                    // clip s to zMin.
                    Vec3.mult( invZ, zmin - s[ 2 ], tmp );
                    Vec3.add( s, tmp, e );
                }

                if ( s[ 2 ] > zmax ) {
                    // clip e to zMax.
                    Vec3.mult( invZ, zmax - s[ 2 ], tmp );
                    Vec3.add( s, tmp, s );
                }
            }
            return true;
        };
    } )()
};

module.exports = KdTreeRayIntersector;
<|endoftext|>


<commit_before>import moment from 'moment';
import Ember from 'ember';
import momentFormat from 'ember-moment/computeds/format';

const { Component, computed, isPresent, RSVP, inject } = Ember;
const { service } = inject;
const { all, Promise } = RSVP;

export default Component.extend({
  init() {
    this._super(...arguments);
    //do these on setup otherwise tests were failing because
    //the old filter value hung around
    this.set('selectedSessionTypes', []);
    this.set('selectedCourseLevels', []);
    this.set('selectedCohorts', []);
    this.set('selectedCourses', []);
  },
  userEvents: service(),
  schoolEvents: service(),
  currentUser: service(),
  store: service(),
  i18n: service(),
  activeFilters: null,
  schoolPickedByUser: null,
  selectedDate: null,
  selectedView: null,
  selectedCohorts: [],
  selectedCourseLevels: [],
  selectedCourses: [],
  selectedSessionTypes: [],

  dueTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.dueThisDay');
  }),
  dayTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.day');
  }),
  weekTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.week');
  }),
  monthTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.month');
  }),
  loadingEventsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.loadingEvents');
  }),
  icsInstructionsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.icsInstructions');
  }),
  fromTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).startOf(this.get('selectedView')).subtract(this.get('skew'), 'days').unix();
  }),
  toTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).endOf(this.get('selectedView')).add(this.get('skew'), 'days').unix();
  }),
  clockSkew: computed('selectedView', function(){
    if(this.set('selectedView') === 'month'){
      return 6;
    }

    return 1;
  }),
  calendarDate: momentFormat('selectedDate', 'YYYY-MM-DD'),

  /**
   * @property academicYears
   * @type {Ember.computed}
   * @public
   */
  academicYears: computed('allAcademicYears.[]', 'academicYearSelectedByUser', function(){
    return new Promise(resolve => {
      this.get('allAcademicYears').then(years => {
        resolve(years.sortBy('title'));
      });
    });
  }),

  /**
   * @property cohorts
   * @type {Ember.computed}
   * @public
   * @todo Check if this can be replaced with allCohorts(). [ST 2017/02/07]
   */
  cohorts: computed('allCohorts.[].displayTitle', 'selectedCohorts.[]', function(){
    return this.get('allCohorts');
  }),

  /**
   * @property courseLevels
   * @type {Ember.computed}
   * @public
   * @todo Review whether this needs to be a computed property, get rid of it if applicable. [ST 2017/02/07]
   */
  courseLevels: computed('selectedCourseLevels.[]', function(){
    let levels = [];
    for(let i =1; i <=5; i++){
      levels.pushObject(i);
    }
    return levels;
  }),

  /**
   * @property courses
   * @type {Ember.computed}
   * @public
   * @todo Check if this can be replaced with allCourses(). [ST 2017/02/07]
   */
  courses: computed('allCourses.[]', 'selectedCourses.[]', function(){
    return this.get('allCourses');
  }),

  /**
   * @property filteredEvents
   * @type {Ember.computed}
   * @public
   */
  filteredEvents: computed(
    'ourEvents.[]',
    'eventsWithSelectedSessionTypes.[]',
    'eventsWithSelectedCourseLevels.[]',
    'eventsWithSelectedCohorts.[]',
    'eventsWithSelectedCourses.[]',
    function() {
      return new Promise(resolve => {
        let promises = [];
        let eventTypes = [
          'eventsWithSelectedSessionTypes',
          'eventsWithSelectedCourseLevels',
          'eventsWithSelectedCohorts',
          'eventsWithSelectedCourses',
        ];
        let allFilteredEvents = [];
        eventTypes.forEach(name => {
          promises.pushObject(this.get(name).then(events => {
            allFilteredEvents.pushObject(events);
          }));
        });

        all(promises).then(()=> {
          this.get('ourEvents').then(events => {
            let filteredEvents = events.filter(event => {
              return allFilteredEvents.every(arr => {
                return arr.includes(event);
              });
            });
            resolve(filteredEvents);
          });
        });
      });
    }
  ),

  /**
   * @property filterTags
   * @type {Ember.computed}
   * @public
   */
  filterTags: computed('activeFilters', {
    get() {
      const activeFilters = this.get('activeFilters');

      return activeFilters.map((filter) => {
        let hash = {};
        hash.filter = filter;

        if (typeof filter === 'number') {
          hash.class = 'tag-course-level';
          hash.name = `Course Level ${filter}`;
        } else {
          let model = filter.get('constructor.modelName');

          switch (model) {
          case 'session-type':
            hash.class = 'tag-session-type';
            hash.name = filter.get('title');
            break;
          case 'cohort':
            hash.class = 'tag-cohort';
            hash.name = `${filter.get('displayTitle')} ${filter.get('programYear.program.title')}`;
            break;
          case 'course':
            hash.class = 'tag-course';
            hash.name = filter.get('title');
            break;
          }
        }
        return hash;
      });
    }
  }),

  /**
   * @property selectedSchool
   * @type {Ember.computed}
   * @public
   */
  selectedSchool: computed('schoolPickedByUser', 'currentUser.model.school', function(){
    const schoolPickedByUser = this.get('schoolPickedByUser');
    return new Promise(resolve => {
      if (schoolPickedByUser)  {
        resolve(schoolPickedByUser);
      } else {
        this.get('currentUser').get('model').then(user => {
          user.get('school').then(school => {
            resolve(school);
          });
        });
      }
    });
  }),

  /**
   * @property schools
   * @type {Ember.computed}
   * @public
   */
  schools: computed('allSchools.[]', 'selectedSchool', function(){
    return new Promise(resolve => {
      this.get('allSchools').then(schools => {
        resolve(schools.sortBy('title'));
      });
    });
  }),

  /**
   * @property selectedAcademicYear
   * @type {Ember.computed}
   * @public
   */
  selectedAcademicYear: computed('academicYearSelectedByUser', 'allAcademicYears.[]', function(){
    const academicYearSelectedByUser = this.get('academicYearSelectedByUser');
    return new Promise(resolve => {
      if (academicYearSelectedByUser)  {
        resolve(academicYearSelectedByUser);
      } else {
        this.get('allAcademicYears').then(years => {
          const year = years.sortBy('title').get('lastObject');
          resolve(year);
        });
      }
    });
  }),

  /**
   * @property sessionTypes
   * @type {Ember.computed}
   * @public
   */
  sessionTypes: computed('selectedSchool.sessionTypes.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        school.get('sessionTypes').then(types => {
          resolve(types.sortBy('title'));
        });
      });
    });
  }),

  /**
   * @property showClearFilters
   * @type {Ember.computed}
   * @public
   */
  showClearFilters: computed('selectedCourses.[]', 'selectedSessionTypes.[]', 'selectedCourseLevels.[]', 'selectedCohorts.[]', {
    get() {
      const a = this.get('selectedSessionTypes');
      const b = this.get('selectedCourseLevels');
      const c = this.get('selectedCohorts');
      const d = this.get('selectedCourses');

      let results = a.concat(b, c, d);
      this.set('activeFilters', results);

      return isPresent(results);
    }
  }).readOnly(),

  /**
   * @property ourEvents
   * @type {Ember.computed}
   * @protected
   */
  ourEvents: computed('mySchedule', 'fromTimeStamp', 'toTimeStamp', 'selectedSchool', 'selectedView', function(){
    return new Promise(resolve => {
      if(this.get('mySchedule')) {
        this.get('userEvents').getEvents(this.get('fromTimeStamp'), this.get('toTimeStamp')).then(userEvents => {
          resolve(userEvents);
        });
      } else {
        this.get('selectedSchool').then(school => {
          this.get('schoolEvents').getEvents(
            school.get('id'),
            this.get('fromTimeStamp'),
            this.get('toTimeStamp')
          ).then(schoolEvents => {
            resolve(schoolEvents);
          });
        });
      }
    });
  }),

  /**
   * @property eventsWithSelectedSessionTypes
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedSessionTypes: computed('ourEvents.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedSessionTypes = this.get('selectedSessionTypes').mapBy('id');
        if(selectedSessionTypes.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getSessionTypeIdForEvent(event).then(id => {
              if (selectedSessionTypes.includes(id)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourseLevels
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourseLevels: computed('ourEvents.[]', 'selectedCourseLevels.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCourseLevels = this.get('selectedCourseLevels');
        if(selectedCourseLevels.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseLevelForEvent(event).then(level => {
              if (selectedCourseLevels.includes(level)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCohorts
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCohorts: computed('ourEvents.[]', 'selectedCohorts.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCohorts = this.get('selectedCohorts').mapBy('id');
        if(selectedCohorts.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCohortIdsForEvent(event).then(cohorts => {
              if (cohorts.any(cohortId => {
                return selectedCohorts.includes(cohortId);
              })) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourses
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourses: computed('ourEvents.[]', 'selectedCourses.[]', function(){
    return new Promise(resolve => {
      let selectedCourses = this.get('selectedCourses').mapBy('id');
      this.get('ourEvents').then(events => {
        if(selectedCourses.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseIdForEvent(event).then(courseId => {
              if (selectedCourses.includes(courseId)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property allCohorts
   * @type {Ember.computed}
   * @protected
   */
  allCohorts: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        this.get('selectedAcademicYear').then(year => {
          school.getCohortsForYear(year.get('title')).then(cohorts => {
            resolve(cohorts.sortBy('displayTitle'));
          });
        });
      });
    });
  }),

  /**
   * @property allCourses
   * @type {Ember.computed}
   * @protected
   */
  allCourses: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then((school) => {
        this.get('selectedAcademicYear').then((year) => {
          this.get('store').query('course', {
            filters: {
              school: school.get('id'),
              year: year.get('title')
            },
            limit: 1000
          }).then((courses) => {
            resolve(courses.sortBy('title'));
          });
        });
      });
    });
  }),

  /**
   * @property allSchools
   * @type {Ember.computed}
   * @protected
   */
  allSchools: computed(function(){
    return new Promise(resolve => {
      this.get('currentUser.model').then(user => {
        user.get('schools').then(schools => {
          resolve(schools);
        });
      });
    });
  }),

  /**
   * @property allAcademicYears
   * @type {Ember.computed}
   * @protected
   */
  allAcademicYears: computed(function(){
    return this.get('store').findAll('academic-year');
  }),

  actions: {
    toggleSessionType(sessionType){
      if(this.get('selectedSessionTypes').includes(sessionType)){
        this.get('selectedSessionTypes').removeObject(sessionType);
      } else {
        this.get('selectedSessionTypes').pushObject(sessionType);
      }
    },
    toggleCourseLevel(level){
      if(this.get('selectedCourseLevels').includes(level)){
        this.get('selectedCourseLevels').removeObject(level);
      } else {
        this.get('selectedCourseLevels').pushObject(level);
      }
    },
    toggleCohort(cohort){
      if(this.get('selectedCohorts').includes(cohort)){
        this.get('selectedCohorts').removeObject(cohort);
      } else {
        this.get('selectedCohorts').pushObject(cohort);
      }
    },
    toggleCourse(course){
      if(this.get('selectedCourses').includes(course)){
        this.get('selectedCourses').removeObject(course);
      } else {
        this.get('selectedCourses').pushObject(course);
      }
    },

    clearFilters() {
      const selectedCourses = [];
      const selectedSessionTypes = [];
      const selectedCourseLevels = [];
      const selectedCohorts = [];

      this.setProperties({ selectedCourses, selectedSessionTypes, selectedCourseLevels, selectedCohorts });
    },

    removeFilter(filter) {
      if (typeof filter === 'number') {
        this.send('toggleCourseLevel', filter);
      } else {
        let model = filter.get('constructor.modelName');

        switch (model) {
        case 'session-type':
          this.send('toggleSessionType', filter);
          break;
        case 'cohort':
          this.send('toggleCohort', filter);
          break;
        case 'course':
          this.send('toggleCourse', filter);
          break;
        }
      }
    }
  }
});
<commit_msg>initialized component's props to NULL to ensure proper cleanup.<commit_after>import moment from 'moment';
import Ember from 'ember';
import momentFormat from 'ember-moment/computeds/format';

const { Component, computed, isPresent, RSVP, inject } = Ember;
const { service } = inject;
const { all, Promise } = RSVP;

export default Component.extend({
  init() {
    this._super(...arguments);
    //do these on setup otherwise tests were failing because
    //the old filter value hung around
    this.set('selectedSessionTypes', []);
    this.set('selectedCourseLevels', []);
    this.set('selectedCohorts', []);
    this.set('selectedCourses', []);
  },
  userEvents: service(),
  schoolEvents: service(),
  currentUser: service(),
  store: service(),
  i18n: service(),
  activeFilters: null,
  schoolPickedByUser: null,
  selectedDate: null,
  selectedView: null,
  selectedCohorts: null,
  selectedCourseLevels: null,
  selectedCourses: null,
  selectedSessionTypes: null,

  dueTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.dueThisDay');
  }),
  dayTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.day');
  }),
  weekTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.week');
  }),
  monthTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.month');
  }),
  loadingEventsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.loadingEvents');
  }),
  icsInstructionsTranslation: computed('i18n.locale', function(){
    return this.get('i18n').t('general.icsInstructions');
  }),
  fromTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).startOf(this.get('selectedView')).subtract(this.get('skew'), 'days').unix();
  }),
  toTimeStamp: computed('selectedDate', 'selectedView', function(){
    return moment(this.get('selectedDate')).endOf(this.get('selectedView')).add(this.get('skew'), 'days').unix();
  }),
  clockSkew: computed('selectedView', function(){
    if(this.set('selectedView') === 'month'){
      return 6;
    }

    return 1;
  }),
  calendarDate: momentFormat('selectedDate', 'YYYY-MM-DD'),

  /**
   * @property academicYears
   * @type {Ember.computed}
   * @public
   */
  academicYears: computed('allAcademicYears.[]', 'academicYearSelectedByUser', function(){
    return new Promise(resolve => {
      this.get('allAcademicYears').then(years => {
        resolve(years.sortBy('title'));
      });
    });
  }),

  /**
   * @property cohorts
   * @type {Ember.computed}
   * @public
   * @todo Check if this can be replaced with allCohorts(). [ST 2017/02/07]
   */
  cohorts: computed('allCohorts.[].displayTitle', 'selectedCohorts.[]', function(){
    return this.get('allCohorts');
  }),

  /**
   * @property courseLevels
   * @type {Ember.computed}
   * @public
   * @todo Review whether this needs to be a computed property, get rid of it if applicable. [ST 2017/02/07]
   */
  courseLevels: computed('selectedCourseLevels.[]', function(){
    let levels = [];
    for(let i =1; i <=5; i++){
      levels.pushObject(i);
    }
    return levels;
  }),

  /**
   * @property courses
   * @type {Ember.computed}
   * @public
   * @todo Check if this can be replaced with allCourses(). [ST 2017/02/07]
   */
  courses: computed('allCourses.[]', 'selectedCourses.[]', function(){
    return this.get('allCourses');
  }),

  /**
   * @property filteredEvents
   * @type {Ember.computed}
   * @public
   */
  filteredEvents: computed(
    'ourEvents.[]',
    'eventsWithSelectedSessionTypes.[]',
    'eventsWithSelectedCourseLevels.[]',
    'eventsWithSelectedCohorts.[]',
    'eventsWithSelectedCourses.[]',
    function() {
      return new Promise(resolve => {
        let promises = [];
        let eventTypes = [
          'eventsWithSelectedSessionTypes',
          'eventsWithSelectedCourseLevels',
          'eventsWithSelectedCohorts',
          'eventsWithSelectedCourses',
        ];
        let allFilteredEvents = [];
        eventTypes.forEach(name => {
          promises.pushObject(this.get(name).then(events => {
            allFilteredEvents.pushObject(events);
          }));
        });

        all(promises).then(()=> {
          this.get('ourEvents').then(events => {
            let filteredEvents = events.filter(event => {
              return allFilteredEvents.every(arr => {
                return arr.includes(event);
              });
            });
            resolve(filteredEvents);
          });
        });
      });
    }
  ),

  /**
   * @property filterTags
   * @type {Ember.computed}
   * @public
   */
  filterTags: computed('activeFilters', {
    get() {
      const activeFilters = this.get('activeFilters');

      return activeFilters.map((filter) => {
        let hash = {};
        hash.filter = filter;

        if (typeof filter === 'number') {
          hash.class = 'tag-course-level';
          hash.name = `Course Level ${filter}`;
        } else {
          let model = filter.get('constructor.modelName');

          switch (model) {
          case 'session-type':
            hash.class = 'tag-session-type';
            hash.name = filter.get('title');
            break;
          case 'cohort':
            hash.class = 'tag-cohort';
            hash.name = `${filter.get('displayTitle')} ${filter.get('programYear.program.title')}`;
            break;
          case 'course':
            hash.class = 'tag-course';
            hash.name = filter.get('title');
            break;
          }
        }
        return hash;
      });
    }
  }),

  /**
   * @property selectedSchool
   * @type {Ember.computed}
   * @public
   */
  selectedSchool: computed('schoolPickedByUser', 'currentUser.model.school', function(){
    const schoolPickedByUser = this.get('schoolPickedByUser');
    return new Promise(resolve => {
      if (schoolPickedByUser)  {
        resolve(schoolPickedByUser);
      } else {
        this.get('currentUser').get('model').then(user => {
          user.get('school').then(school => {
            resolve(school);
          });
        });
      }
    });
  }),

  /**
   * @property schools
   * @type {Ember.computed}
   * @public
   */
  schools: computed('allSchools.[]', 'selectedSchool', function(){
    return new Promise(resolve => {
      this.get('allSchools').then(schools => {
        resolve(schools.sortBy('title'));
      });
    });
  }),

  /**
   * @property selectedAcademicYear
   * @type {Ember.computed}
   * @public
   */
  selectedAcademicYear: computed('academicYearSelectedByUser', 'allAcademicYears.[]', function(){
    const academicYearSelectedByUser = this.get('academicYearSelectedByUser');
    return new Promise(resolve => {
      if (academicYearSelectedByUser)  {
        resolve(academicYearSelectedByUser);
      } else {
        this.get('allAcademicYears').then(years => {
          const year = years.sortBy('title').get('lastObject');
          resolve(year);
        });
      }
    });
  }),

  /**
   * @property sessionTypes
   * @type {Ember.computed}
   * @public
   */
  sessionTypes: computed('selectedSchool.sessionTypes.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        school.get('sessionTypes').then(types => {
          resolve(types.sortBy('title'));
        });
      });
    });
  }),

  /**
   * @property showClearFilters
   * @type {Ember.computed}
   * @public
   */
  showClearFilters: computed('selectedCourses.[]', 'selectedSessionTypes.[]', 'selectedCourseLevels.[]', 'selectedCohorts.[]', {
    get() {
      const a = this.get('selectedSessionTypes');
      const b = this.get('selectedCourseLevels');
      const c = this.get('selectedCohorts');
      const d = this.get('selectedCourses');

      let results = a.concat(b, c, d);
      this.set('activeFilters', results);

      return isPresent(results);
    }
  }).readOnly(),

  /**
   * @property ourEvents
   * @type {Ember.computed}
   * @protected
   */
  ourEvents: computed('mySchedule', 'fromTimeStamp', 'toTimeStamp', 'selectedSchool', 'selectedView', function(){
    return new Promise(resolve => {
      if(this.get('mySchedule')) {
        this.get('userEvents').getEvents(this.get('fromTimeStamp'), this.get('toTimeStamp')).then(userEvents => {
          resolve(userEvents);
        });
      } else {
        this.get('selectedSchool').then(school => {
          this.get('schoolEvents').getEvents(
            school.get('id'),
            this.get('fromTimeStamp'),
            this.get('toTimeStamp')
          ).then(schoolEvents => {
            resolve(schoolEvents);
          });
        });
      }
    });
  }),

  /**
   * @property eventsWithSelectedSessionTypes
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedSessionTypes: computed('ourEvents.[]', 'selectedSessionTypes.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedSessionTypes = this.get('selectedSessionTypes').mapBy('id');
        if(selectedSessionTypes.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getSessionTypeIdForEvent(event).then(id => {
              if (selectedSessionTypes.includes(id)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourseLevels
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourseLevels: computed('ourEvents.[]', 'selectedCourseLevels.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCourseLevels = this.get('selectedCourseLevels');
        if(selectedCourseLevels.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseLevelForEvent(event).then(level => {
              if (selectedCourseLevels.includes(level)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCohorts
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCohorts: computed('ourEvents.[]', 'selectedCohorts.[]', function(){
    return new Promise(resolve => {
      this.get('ourEvents').then(events => {
        let selectedCohorts = this.get('selectedCohorts').mapBy('id');
        if(selectedCohorts.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCohortIdsForEvent(event).then(cohorts => {
              if (cohorts.any(cohortId => {
                return selectedCohorts.includes(cohortId);
              })) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property eventsWithSelectedCourses
   * @type {Ember.computed}
   * @protected
   */
  eventsWithSelectedCourses: computed('ourEvents.[]', 'selectedCourses.[]', function(){
    return new Promise(resolve => {
      let selectedCourses = this.get('selectedCourses').mapBy('id');
      this.get('ourEvents').then(events => {
        if(selectedCourses.length === 0) {
          resolve(events);
          return;
        }
        let matchingEvents = [];
        let promises = [];
        events.forEach(event => {
          if (event.ilmSession || event.offering) {
            promises.pushObject(this.get('userEvents').getCourseIdForEvent(event).then(courseId => {
              if (selectedCourses.includes(courseId)) {
                matchingEvents.pushObject(event);
              }
            }));
          }
        });
        all(promises).then(()=> {
          resolve(matchingEvents);
        });
      });
    });
  }),

  /**
   * @property allCohorts
   * @type {Ember.computed}
   * @protected
   */
  allCohorts: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then(school => {
        this.get('selectedAcademicYear').then(year => {
          school.getCohortsForYear(year.get('title')).then(cohorts => {
            resolve(cohorts.sortBy('displayTitle'));
          });
        });
      });
    });
  }),

  /**
   * @property allCourses
   * @type {Ember.computed}
   * @protected
   */
  allCourses: computed('selectedSchool', 'selectedAcademicYear', function(){
    return new Promise(resolve => {
      this.get('selectedSchool').then((school) => {
        this.get('selectedAcademicYear').then((year) => {
          this.get('store').query('course', {
            filters: {
              school: school.get('id'),
              year: year.get('title')
            },
            limit: 1000
          }).then((courses) => {
            resolve(courses.sortBy('title'));
          });
        });
      });
    });
  }),

  /**
   * @property allSchools
   * @type {Ember.computed}
   * @protected
   */
  allSchools: computed(function(){
    return new Promise(resolve => {
      this.get('currentUser.model').then(user => {
        user.get('schools').then(schools => {
          resolve(schools);
        });
      });
    });
  }),

  /**
   * @property allAcademicYears
   * @type {Ember.computed}
   * @protected
   */
  allAcademicYears: computed(function(){
    return this.get('store').findAll('academic-year');
  }),

  actions: {
    toggleSessionType(sessionType){
      if(this.get('selectedSessionTypes').includes(sessionType)){
        this.get('selectedSessionTypes').removeObject(sessionType);
      } else {
        this.get('selectedSessionTypes').pushObject(sessionType);
      }
    },
    toggleCourseLevel(level){
      if(this.get('selectedCourseLevels').includes(level)){
        this.get('selectedCourseLevels').removeObject(level);
      } else {
        this.get('selectedCourseLevels').pushObject(level);
      }
    },
    toggleCohort(cohort){
      if(this.get('selectedCohorts').includes(cohort)){
        this.get('selectedCohorts').removeObject(cohort);
      } else {
        this.get('selectedCohorts').pushObject(cohort);
      }
    },
    toggleCourse(course){
      if(this.get('selectedCourses').includes(course)){
        this.get('selectedCourses').removeObject(course);
      } else {
        this.get('selectedCourses').pushObject(course);
      }
    },

    clearFilters() {
      const selectedCourses = [];
      const selectedSessionTypes = [];
      const selectedCourseLevels = [];
      const selectedCohorts = [];

      this.setProperties({ selectedCourses, selectedSessionTypes, selectedCourseLevels, selectedCohorts });
    },

    removeFilter(filter) {
      if (typeof filter === 'number') {
        this.send('toggleCourseLevel', filter);
      } else {
        let model = filter.get('constructor.modelName');

        switch (model) {
        case 'session-type':
          this.send('toggleSessionType', filter);
          break;
        case 'cohort':
          this.send('toggleCohort', filter);
          break;
        case 'course':
          this.send('toggleCourse', filter);
          break;
        }
      }
    }
  }
});
<|endoftext|>


<commit_before>"use strict";

/* @Author: Zakeria Hassan <zak.hassan1010@gmail.com>
 * @Date: Nov. 6, 2013
 * @Usage: Generally StorageIO uses IndexedDB http://www.w3.org/TR/IndexedDB/.
 * If IndexedDB isn't isn't supported then it use as an alternative
 * WebSQL http://www.w3.org/TR/webdatabase/.
 *
 * The MIT License (MIT)
 * Copyright (c) 2013 Zakeria Hassan
 * 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.
 *
 *  Dependencies:
 *  require.js  - http://requirejs.org/docs/release/2.1.9/comments/require.js
 *  jquery.js - http://jquery.com
 *  backbone.js - http://backbonejs.org/
 *  underscore.js - http://underscorejs.org/
 *
 */

/*
 * @parm global : 'this' object
 * @parm runner : 'callback' function
 */
(function (global, runner) {

  if (typeof exports === 'object' && typeof require === 'function') {
    module.exports = runner(require("underscore"), require("backbone"), require("idbfs"));
  } else if (typeof define === "function" && define.amd) {
    define(["underscore", "backbone","idbfs"], function (_, Backbone, IDBFS) {
      return runner(_ || global._, Backbone || global.Backbone, IDBFS || global.IDBFS);
    });
  } else {
    // RequireJS isn't being used. Make sure underscore and backbone are loaded in script tags
    runner(_, Backbone,IDBFS);
  }
})(this, function (_, Backbone, IDBFS ) {
  var global = window;
  // Use Backbone and RequireJS to improve code
  var StorageIO = function (_dbname) {

        // USE IDBFS SOMEWHERE HERE.
          //Setting backbone models
          //1) Directory
          //2) File
 var Directory = Backbone.Model.extend({
        initialize: function(){
            // Triggered when you create a directory
        }
    });

 var File = Backbone.Model.extend({
        initialize: function(){
          // Triggered when you create a File
        }
 });
    return (global.indexedDB || global.mozIndexedDB || global.webkitIndexedDB || global.msIndexedDB) ?
         {
          indexedDB: global.indexedDB || global.mozIndexedDB || global.webkitIndexedDB || global.msIndexedDB,
          IDBTransaction: global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction,
          IDBKeyRange: global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange,
          req: null,
          result: null,
          _fs: IDBFS,
          dbName: _dbname || "test",
          error: function (event) {
            // Should do some error handling
            // TODO: This code is not complete.. Will finish soon
            console.log("request failed!");
          },
          success: function (event) {
            this.result = this.req.result;
          },
          read: function () {
            //TODO:This code is not complete.. Will finish soon
          },
          rm: function () {
            //TODO:This code is not complete.. Will finish soon
          },
          /**
           *  @param {Object} has three keys and looks like this: {      "key" : string,
           *                                                           "value" : string,
           *                                                        "isUnique" : bool   }
           * Establishs a database connection and if this is a a new database
           * It creates the columns that will be used. It sets up the functions
           * that are executed if the req's fail or succeed
           */
          open: function (columns) {
            this.req = this.indexedDB.open(this.dbname);
            this.req.onerror = this.error;
            this.req.success = this.success;
            // Comment: the "onupgradeneeded" is triggered when a new database is created and
            // TODO: Must be able to write key value pairs to the indexedDB.
            // TODO: This code is not complete.. Will finish soon
            this.req.onupgradeneeded = function (event) {
              try {
                var st = event.currentTarget.result.createObjectStore(this.dbname, {
                  keypath: 'id',
                  autoIncrement: true
                });
                if (!columns) throw new Error("Error code: 999 - Missing column variable in open function");
                // Setting columns up
                for (var i = 0; i < obj.length; i++) {
                  st.createIndex(columns[i]["key"], columns[i]["value"], {
                    unique: columns[i]["isUnique"]
                  });
                }
              } catch (e) {
                  // TODO: Must handle exception
              } finally {
                  // TODO: clean up
              }
            }
            return this;
          },
          /**
           * @param {Object} has three keys and looks like this: { "key":string, value":string, "isUnique":bool}
           */
          write: function (obj) {

          },
          close: function () {}

        } : { read: function () {
          /*
         * As a fallback solution to acheive cross browser compatibility
         * I've included a Web SQL Backed solution.
         * TODO:This code is not complete.. Will finish soon
         */
        },
            write: function () {},
               rm: function () {}  };
      };

  _.StorageIO = StorageIO;
});<commit_msg>Setting up backbone Models and fixing syntax<commit_after>"use strict";

/* @Author: Zakeria Hassan <zak.hassan1010@gmail.com>
 * @Date: Nov. 6, 2013
 * @Usage: Generally StorageIO uses IndexedDB http://www.w3.org/TR/IndexedDB/.
 * If IndexedDB isn't isn't supported then it use as an alternative
 * WebSQL http://www.w3.org/TR/webdatabase/.
 *
 * The MIT License (MIT)
 * Copyright (c) 2013 Zakeria Hassan
 * 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.
 *
 *  Dependencies:
 *  require.js  - http://requirejs.org/docs/release/2.1.9/comments/require.js
 *  jquery.js - http://jquery.com
 *  backbone.js - http://backbonejs.org/
 *  underscore.js - http://underscorejs.org/
 *
 */

/*
 * @parm global : 'this' object
 * @parm runner : 'callback' function
 */
(function (global, runner) {

  if (typeof exports === 'object' && typeof require === 'function') {
    module.exports = runner(require("underscore"), require("backbone"), require("idbfs"));
  } else if (typeof define === "function" && define.amd) {
    define(["underscore", "backbone", "idbfs"], function (_, Backbone, IDBFS) {
      return runner(_ || global._, Backbone || global.Backbone, IDBFS || global.IDBFS);
    });
  } else {
    // RequireJS isn't being used. Make sure underscore and backbone are loaded in script tags
    runner(_, Backbone, IDBFS);
  }
})(this, function (_, Backbone, IDBFS) {
  var global = window;
  // Use Backbone and RequireJS to improve code
  var StorageIO = function (_dbname) {

      // USE IDBFS SOMEWHERE HERE.
      //Setting backbone models
      //1) Directory
      //2) File
      var Directory = Backbone.Model.extend({
        initialize: function () {
          // Triggered when you create a directory
        }
      });

      var File = Backbone.Model.extend({
        initialize: function () {
          // Triggered when you create a File
        }
      });
      return (global.indexedDB || global.mozIndexedDB || global.webkitIndexedDB || global.msIndexedDB) ? {
        indexedDB: global.indexedDB || global.mozIndexedDB || global.webkitIndexedDB || global.msIndexedDB,
        IDBTransaction: global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction,
        IDBKeyRange: global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange,
        req: null,
        result: null,
        _fs: IDBFS,
        dbName: _dbname || "test",
        error: function (event) {
          // Should do some error handling
          // TODO: This code is not complete.. Will finish soon
          console.log("request failed!");
        },
        success: function (event) {
          this.result = this.req.result;
        },
        read: function () {
          //TODO:This code is not complete.. Will finish soon
        },
        rm: function () {
          //TODO:This code is not complete.. Will finish soon
        },
        /**
         *  @param {Object} has three keys and looks like this: {      "key" : string,
         *                                                           "value" : string,
         *                                                        "isUnique" : bool   }
         * Establishs a database connection and if this is a a new database
         * It creates the columns that will be used. It sets up the functions
         * that are executed if the req's fail or succeed
         */
        open: function (columns) {
          this.req = this.indexedDB.open(this.dbname);
          this.req.onerror = this.error;
          this.req.success = this.success;
          // Comment: the "onupgradeneeded" is triggered when a new database is created and
          // TODO: Must be able to write key value pairs to the indexedDB.
          // TODO: This code is not complete.. Will finish soon
          this.req.onupgradeneeded = function (event) {
            try {
              var st = event.currentTarget.result.createObjectStore(this.dbname, {
                keypath: 'id',
                autoIncrement: true
              });
              if (!columns) throw new Error("Error code: 999 - Missing column variable in open function");
              // Setting columns up
              for (var i = 0; i < obj.length; i++) {
                st.createIndex(columns[i]["key"], columns[i]["value"], {
                  unique: columns[i]["isUnique"]
                });
              }
            } catch (e) {
              // TODO: Must handle exception
            } finally {
              // TODO: clean up
            }
          }
          return this;
        },
        /**
         * @param {Object} has three keys and looks like this: { "key":string, value":string, "isUnique":bool}
         */
        write: function (obj) {

        },
        close: function () {}

      } : {
        read: function () {
          /*
           * As a fallback solution to acheive cross browser compatibility
           * I've included a Web SQL Backed solution.
           * TODO:This code is not complete.. Will finish soon
           */
        },
        write: function () {},
        rm: function () {}
      };
    };

  _.StorageIO = StorageIO;
});<|endoftext|>