htdocs/assets/js/views/collection.js

Summary

Maintainability
A
0 mins
Test Coverage
"use strict";
define(function(require) {
    var $ = require('jquery'),
        _ = require('underscore'),
        Util = require('util'),
        View = require('view');


    var SelectionMixin = {
        selectionIndex: -1,
        /**
         * Get the number of selectable items.
         */
        getSelectableCount: function() {
            return this.getSelectables().length;
        },
        /**
         * Get all selectables.
         */
        getSelectables: function() {
            return this.getView('collection[]');
        },
        /**
         * Set the selectable.
         * @param {int} i - The index of the selectable.
         * @param {boolean} selected - Whether the selectable was selected or deselected.
         * @param {boolean} down - Whether the motion was down or up.
         */
        setSelectable: function(i, selected, down) {
            this.selectionIndex = selected ? i:-1;

            var collection = this.getSelectables();
            var sel = collection[i];
            this.setSelectableDisplay(sel, selected, down);

            // Scroll page to show selectable.
            if(selected) {
                var rect = sel.el.getBoundingClientRect();

                var $window = $(window);
                var bounds = {top: 70, bottom: 70, left: 0, right: 0};
                if(!Util.visible(rect, bounds)) {
                    if(down) {
                        window.scrollTo($window.scrollLeft(), $window.scrollTop() + rect.top - bounds.top);
                    } else {
                        window.scrollTo($window.scrollLeft(), $window.scrollTop() - ($window.height() - rect.bottom - bounds.bottom));
                    }
                }
            }
        },
        /**
         * Set up any changes to the selectable when selected or deselected.
         * @param {int} i - The index of the selectable.
         * @param {boolean} selected - Whether the selectable was selected or deselected.
         * @param {boolean} down - Whether the motion was down or up.
         */
        setSelectableDisplay: function(sel, selected, down) {},
        /**
         * Notify the selectable that it has been selected..
         * @param {string} action - An action.
         * @param {int} i - The index of the selectable.
         */
        selectSelectable: function(action, i) {
            var collection = this.getSelectables();
            if(i >= collection.length) {
                return;
            }

            collection[i].selectAction(action);
        },
        render: function() {
            View.prototype.render.call(this);

            if(this.selectionIndex >= 0) {
                this.setSelectable(this.selectionIndex, true, true);
            }
        }
    };

    /**
     * Collection View
     * Base view for rendering multiple models.
     */
    var CollectionView = View.extend({
        tagName: 'div',
        className: 'col-xs-12',
        template: _.constant(''),

        // Title of the collection.
        title: '',
        // Whether to show a count of entries.
        hide_count: false,
        // The view to render for each model in the collection.
        subView: null,
        // Variables to pass to the view.
        vars: null,
        // Whether the inputs in this container are hidden from serializeForm.
        hiddenForm: false,
        // Whether to automatically register this as a selectable group.
        selectable: false,

        initialize: function() {
            if(this.selectable) {
                this.App.registerSelectableGroup(this);
            }
            this.vars = _.clone(this.vars) || {};
            if(this.collection) {
                this.listenTo(this.collection, 'sync', this.clearPendingChanges);
            }
        },
        _render: function(vars) {
            this.$el.html(this.template(_.extend(vars, {title: this.title, hide_count: this.hide_count}, this.vars)));
            if(this.hiddenForm) {
                this.$el.addClass('hidden-form');
            }
            this.update();
        },
        update: function(params) {
            this.dim();
            this.collection.update({
                success: $.proxy(this.initializeCollection, this, params),
                fail: $.proxy(this.App.hideLoader, this.App)
            });
        },
        clear: function(params) {
            // Clear the collection, but keep the lastTimestamp field.
            var ts = this.collection.lastTimestamp;
            this.collection.reset();
            this.collection.lastTimestamp = ts;
            this.destroyViews();
        },
        /**
         * Generate all the data to insert into the DOM.
         */
        initializeCollectionData: function(params) {
            var frag = document.createDocumentFragment();
            this.destroyViews();

            // Construct and add the Views to the fragment.
            var models = this.filterCollection(this.collection);
            for(var i = 0; i < models.length; ++i) {
                var view = this.initializeSubView(models[i]);
                frag.appendChild(view.el);
            }
            return [models, frag];
        },
        /**
         * (Re)initialize the collection and add it to the DOM.
         */
        initializeCollection: function(params) {
            var arr = this.initializeCollectionData(params);
            var models = arr[0],
                frag = arr[1];

            this.$el
                .text('')
                .append(frag);

            this.undim();
        },
        /**
         * Set up a sub View and return it.
         * @param {Model} model - The model to initialize the View with.
         * @return {View} The new View object.
         */
        initializeSubView: function(model, options) {
            options = options || {};
            options.model = model;
            var view = this.registerView(new this.subView(this.App, options), false, undefined, 'collection[]');
            view.load();
            return view;
        },
        /**
         * Determine what models will get displayed in the table.
         * @param {Collection} collection - The collection of models.
         * @return {Array} - An array of Models to use.
         */
        filterCollection: function(collection) {
            return collection.models;
        },
        /**
         * Clear the pendingChanges bit from any subviews.
         */
        clearPendingChanges: function() {
            var views = this.getView('collection[]');
            for(var i = 0; i < views.length; ++i) {
                if(!views[i].clearPendingChanges) {
                    continue;
                }
                views[i].clearPendingChanges();
            }
        },
        destroy: function() {
            // By default, we want to remove the root node.
            View.prototype.destroy.call(this, true);
        },
    }, {
        SelectionMixin: SelectionMixin
    });
    _.extend(CollectionView.prototype, SelectionMixin);

    return CollectionView;
});