Source: checkbox-modal.js

/*
 * Copyright 2013-2017 Hewlett Packard Enterprise Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

/**
 * @module js-whatever/js/checkbox-modal
 */
define([
    'underscore',
    'jquery',
    'backbone',
    'js-whatever/js/ensure-array',
    'text!js-whatever/templates/checkbox-modal/checkbox-modal.html',
    'text!js-whatever/templates/checkbox-modal/checkbox-table.html'
], function(_, $, Backbone, ensureArray, checkboxModal, checkboxTable) {
    'use strict';

    /**
     * @typedef CheckboxModalTable
     * @property {boolean|string[]} initialState The initial state of the checkboxes. Can be either a boolean, or a list of
     * row names which should start checked
     * @property {string} tableHeader The i18n key for the table header.
     * @property {string} [inputName] The name of the radio input. Only used if checkboxOrRadio is set to radio
     * @property {string[]} rows The names of the rows.  These must be unique
     */
    /**
     * @typedef CheckboxModalOptions
     * @property {String} [templateModal] The template for the modal
     * @property {String} [templateTableModal] The template for the table.
     * @property {Array.<CheckboxModalTable>} tables The configuration for the tables
     * @property {object} [parameters=[]] Additional parameters passed to templateModal
     * @property {function} [okCallback=$.noop] Callback called when the OK button is pressed
     * @property {string} [okIcon='icon-refresh'] Icon for the OK button
     * @property {string} [okClass='btn-success'] Class for the OK button
     * @property {string} [cancelClass=''] Class for the cancel button
     * @property {boolean} [atLeastOneElementSelected=false] Set this to true if a checkbox
     * @property {object} i18n Object containing i18n strings
     * @property {module:js-whatever/js/vent-constructor.Vent} vent Instance of Vent used to track resize events
     * @property {string} [checkboxOrRadio=checkbox] Should be set to 'checkbox' or 'radio' according to the inputs
     * used in your template
     */
    /**
     * @name module:js-whatever/js/checkbox-modal.CheckboxModal
     * @desc Bootstrap modal which displays tables containing checkboxes or radio buttons, which can be toggled individually or
     * through a global checkbox
     * @constructor
     * @param {CheckboxModalOptions} init
     * @extends Backbone.View
     * @example
     * new CheckboxModal({
     *     i18n: i18n,
     *     vent: vent,
     *     parameters: {
     *         modalHeader: 'modalHeader'
     *     },
     *     tables: [{
     *         initialState: true,
     *         tableHeader: 'tableTitle',
     *         rows: [
     *             'one',
     *             'two',
     *             'three'
     *         ]
     *     }]
     * });
     */
    return Backbone.View.extend(/** @lends module:js-whatever/js/checkbox-modal.CheckboxModal.prototype */{
        initialize: function(init) {
            _.bindAll(this, 'render', 'setRows', 'setRow', 'getSelectedRows', 'remove', 'resizeModal',
                'getModalTemplateParams', 'getElementValue', 'restorePrevState', 'setGlobalCheckboxes');

            this.templateModal = _.template(init.templateModal || checkboxModal);
            this.templateTableModal = _.template(init.templateTableModal || checkboxTable);
            this.tables = init.tables;
            this.parameters = ensureArray(init.parameters);
            this.okCallback = init.okCallback || $.noop;
            this.okIcon = init.okIcon || 'icon-refresh';
            this.okClass = init.okClass || 'btn-success';
            this.cancelClass = init.cancelClass || '';
            this.atLeastOneElementSelected = init.atLeastOneElementSelected || false;

            this.i18n = init.i18n;
            this.vent = init.vent;

            this.checkboxOrRadio = init.checkboxOrRadio || 'checkbox';

            if(this.checkboxOrRadio === 'radio') {
                this.atLeastOneElementSelected = true;
            }

            this.setTableInitialState();

            this.storePrevConfig = true;
            this.render();
            this.listenTo(this.vent, 'vent:resize', this.resizeModal);
        },

        /**
         * @desc Called when the vent fires a vent:resize event. Sets the max-height of the modal body to 80% of the window
         * height, less the height of the modal-header and modal-footer, clamped to [1, 400].
         * @protected
         */
        resizeModal: function() {
            var header = this.editModal.find('.modal-header').outerHeight(true);
            var footer = this.editModal.find('.modal-footer').outerHeight(true);

            this.editModal.find('.modal-body').css('max-height',
                Math.min(400, Math.max(1, 0.8 * $(window).height() - header - footer))
            );
        },

        /**
         * @desc Initialises the internal state of the tables from the configuration
         * @protected
         */
        setTableInitialState: function() {
            _.each(this.tables, function(table) {
                table.initialState = table.initialState || false;

                table.columnStates = _.reduce(table.rows, function(memo, row) {
                    if(_.isArray(table.initialState)) {
                        memo[row] = _.contains(table.initialState, row);
                    } else {
                        memo[row] = table.initialState;
                    }

                    return memo;
                }, {});

                table.prevConfig = '';
            });
        },

        /**
         * @desc Sets the internal state of a row
         * @param {number} index The index of the table
         * @param {string} name The name of the row
         * @param {boolean} value The new value of the checkbox
         * @protected
         */
        setRow: function(index, name, value) {
            this.tables[index].columnStates[name] = value;
        },

        /**
         * @desc Sets the internal state of all rows for the given table
         * @param {number} index The index of the table
         * @param {boolean} value The new value of the checkbox
         * @protected
         */
        setRows: function(index, value) {
            _.each(this.tables[index].columnStates, function(oldValue, field) {
                this.setRow(index, field, value);
            }, this);
        },

        /**
         * @desc Clear the internal state of all rows for all tables
         * @protected
         */
        clearAllRows: function() {
            _.each(this.tables, function(table, index) {
                this.setRows(index, false);
            }, this);
        },

        /**
         * @desc Set the checked property of the nth input in the given table
         * @param {number} index The index of the table
         * @param {number} n The index of the checkbox in the table.  This includes the global checkbox and other inputs.
         * @param {boolean} value The new value of the checkbox
         * @deprecated Prefer setCheckbox
         */
        setNthCheckbox: function(index, n, value) {
            this.editModal.find('table:eq(' + index + ') input:eq(' + n + ')').prop('checked', value);
        },

        /**
         * @desc Set the checked property of the named checkbox in the given table
         * @param {number} index The index of the table
         * @param {string} rowName The name of the row
         * @param {boolean} value The new value of the checkbox
         */
        setCheckbox: function(index, rowName, value) {
            this.editModal.find('table:eq(' + index + ') input[data-row-name="' + rowName + '"]').prop('checked', value);
        },

        /**
         * @desc Set the checked property of all checkboxes in the given table
         * @param {number} index The index of the table
         * @param {boolean} value The new value of the checkbox
         */
        setCheckboxes: function(index, value) {
            this.editModal.find('table:eq(' + index + ') input').prop('checked', value);
        },

        /**
         * @desc clear all checkboxes in all tables
         */
        clearAllCheckboxes: function() {
            this.editModal.find('th input[type="' + this.checkboxOrRadio + '"]').prop('indeterminate', false);
            this.editModal.find('input[type="' + this.checkboxOrRadio + '"]').prop('checked', false);
        },

        /**
         * @desc Sets the value of the indeterminate property of the checkbox with the given index
         * @param {number} index The index of the checkbox
         * @param {boolean} value The value of the indeterminate property
         */
        setIndeterminateCheckbox: function(index, value) {
            this.editModal.find('table:eq(' + index + ') input[data-row-name=""]').prop('indeterminate', value);
        },

        /**
         * @desc Sets the internal state and the checkboxes for a given table
         * @param {number} index The index of the table
         * @param {boolean} valueCheckbox The new value of the checkbox
         * @param {boolean} valueGlobalCheckbox The new indeterminate state of the global checkbox
         */
        setCheckboxesAndRows: function(index, valueCheckbox, valueGlobalCheckbox) {
            this.setCheckboxes(index, valueCheckbox);
            this.setRows(index, valueCheckbox);
            if(valueGlobalCheckbox !== undefined) {
                this.setIndeterminateCheckbox(index, valueGlobalCheckbox);
            }
        },

        /**
         * @desc sets the value of the global checkbox in each table in accordance with the values of the checkboxes in
         * the table
         */
        setGlobalCheckboxes: function() {
            _.each(this.editModal.find('table'), function(table, index) {
                var tempCheck = '';

                _.each($(table).find('td input[type="' + this.checkboxOrRadio + '"]'), function(check) {
                    if(tempCheck === '') {
                        tempCheck = $(check).prop('checked');
                    }

                    if(tempCheck !== $(check).prop('checked')) {
                        tempCheck = 'indeterminate';
                    }
                });

                if(tempCheck === 'indeterminate') {
                    this.setIndeterminateCheckbox(index, true);
                } else {
                    this.setCheckbox(index, '', tempCheck);
                }
            }, this);
        },

        /**
         * Get the selected rows for each table. Updates the internal state of the tables
         * @returns {string[]} The names of the selected rows
         */
        getSelectedRows: function() {
            return _.map(this.editModal.find('table'), function(table, index) {
                var checked = $(table).find('td input[type="' + this.checkboxOrRadio + '"]:checked');
                var notChecked = $(table).find('td input[type="' + this.checkboxOrRadio + '"]:not(:checked)');
                var globalCheckbox = $(table).find('input[data-row-name=""]');
                var innerCheckedRows = [];

                if(checked.length && (!globalCheckbox.length || globalCheckbox.prop('indeterminate'))) {
                    this.setRow(index, '', 'indeterminate');

                    _.each(notChecked, function(elem) {
                        this.setRow(index, $(elem).data('row-name'), false);
                    }, this);

                    innerCheckedRows = _.map(checked, function(elem) {
                        this.setRow(index, $(elem).data('row-name'), true);
                        return $(elem).data('row-name');
                    }, this);
                } else {
                    if(globalCheckbox.prop('checked')) {
                        innerCheckedRows = _.map(checked, function(elem) {
                            return $(elem).data('row-name');
                        }, this);

                        this.setRows(index, true);
                    } else {
                        this.setRows(index, false);
                    }
                }

                return innerCheckedRows;
            }, this);
        },

        /**
         * @typedef TableConfig
         * @property {string} inputName The name of the input if using radio buttons
         * @property {object} i18n strings
         * @property {string} i18nTableHeader A key in i18n which will be used as the table header
         * @property {object} rows A map from row names to booleans representing the checkbox state
         */
        /**
         * @typedef CheckboxModalTemplateParameters
         * @property {Array.<TableConfig>} tableConfigs The configuration for the tables
         * @property {function} tableTemplate The template for the table
         * @property {object} i18n i18n strings
         * @property {string} okIcon The icon for the OK butto
         * @property {string} okClass The class for the OK button
         * @property {string} cancelClass The class for the cancel button
         */
        /**
         * Gets the parameters which are passed to templateModal.  The tables configs are passed to each table.
         * @returns {CheckboxModalTemplateParameters} template parameters augemented with the additional parameters
         * passed to initialize
         * @protected
         */
        getModalTemplateParams: function() {
            var tableConfig = _.map(this.tables, function(table) {
                return {
                    inputName: table.inputName || '',
                    i18n: this.i18n,
                    i18nTableHeader: table.tableHeader,
                    rows: table.columnStates
                };
            }, this);

            var templateParams = {
                tableConfigs: tableConfig,
                tableTemplate: this.templateTableModal,
                i18n: this.i18n,
                okIcon: this.okIcon,
                okClass: this.okClass,
                cancelClass: this.cancelClass
            };

            _.each(this.parameters, function(entry) {
                _.each(entry, function(value, key) {
                    templateParams[key] = value;
                });
            });

            return templateParams;
        },

        /**
         * @desc Get the value of an input. Don't use this to get the value of checkboxes or you will have a bad time
         * @param {string} selector A css selector for locating the input
         * @returns {*} The value of the input
         */
        getElementValue: function(selector) {
            return this.editModal.find(selector).val();
        },

        /**
         * @desc Get the value of an input. Don't use this to get the value of checkboxes or you will have a bad time
         * @param {string} selector A css selector for locating the input
         * @param {*} value The new value of the input
         */
        setElementValue: function(selector, value) {
            this.editModal.find(selector).val(value);
        },

        /**
         * @desc Shows the modal
         */
        showModal: function() {
            this.editModal.modal('show');
            if(!this.storePrevConfig) {
                this.restorePrevState();
            }
        },

        /**
         * @desc Hides the modal
         */
        hideModal: function() {
            this.editModal.modal('hide');
        },

        /**
         * @desc Restores the previous state of the modal
         * @protected
         */
        restorePrevState: function() {
            _.each(this.tables, function(table) {
                _.each(table.prevConfig, function(state, element) {
                    if(state === 'indeterminate') {
                        this.editModal.find('input[data-row-name="' + element + '"]').prop('indeterminate', true);
                    } else {
                        this.editModal.find('input[data-row-name="' + element + '"]').prop('checked', state);
                    }
                }, this);
            }, this);
        },

        /**
         * @desc renders the modal and adds the event handlers
         * @returns this
         */
        render: function() {
            this.editModal = $(this.templateModal(this.getModalTemplateParams()));
            this.editModal.on('shown', this.resizeModal);
            this.setGlobalCheckboxes();

            this.editModal.find('table input[type="' + this.checkboxOrRadio + '"]').change(_.bind(this.handleCheckBoxChange, this));
            this.editModal.find('button.ok').click(_.bind(this.onOkClick, this));

            this.editModal.on('shown', function() {
                document.activeElement.blur();
            });

            return this;
        },

        /**
         * @desc Handler called when a checkbox is clicked. This will set the state of the corresponding global checkbox
         * accordingly
         * @param {object} e jQuery event object
         * @protected
         */
        handleCheckBoxChange: function(e) {
            var $target = $(e.target);
            var rowName = $target.data('row-name');
            var checkboxes = $target.closest('table').find('td input[type="' + this.checkboxOrRadio + '"]');
            var checked = $target.closest('table').find('td input[type="' + this.checkboxOrRadio + '"]:checked');
            var globalCheckbox = $target.closest('table').find('input[data-row-name=""]');

            if(this.storePrevConfig) {
                this.prevConfig = this.columnStates;
                this.storePrevConfig = false;
            }

            if(rowName === '') {
                if($target.prop('checked') || this.atLeastOneElementSelected) {
                    checkboxes.prop('checked', true);
                    globalCheckbox.prop('indeterminate', false);

                    if(this.atLeastOneElementSelected) {
                        globalCheckbox.prop('checked', true);
                    }
                } else {
                    checkboxes.prop('checked', false);
                    globalCheckbox.prop('indeterminate', false);
                }
            } else {
                if($target.prop('checked')) {
                    if(checkboxes.length === checked.length) {
                        globalCheckbox.prop('checked', true);
                        globalCheckbox.prop('indeterminate', false);
                    } else {
                        globalCheckbox.prop('indeterminate', true);
                    }
                } else {
                    if(checked.length === 0) {
                        if(this.atLeastOneElementSelected) {
                            $(e.delegateTarget).prop('checked', true);
                        } else {
                            globalCheckbox.prop('indeterminate', false);
                            globalCheckbox.prop('checked', false);
                        }
                    } else {
                        globalCheckbox.prop('indeterminate', true);
                    }
                }
            }
        },

        /**
         * @desc Called when the OK button is pressed.  This will call okCallback and close the modal
         * @protected
         */
        onOkClick: function() {
            this.okCallback(this.getSelectedRows(), this.editModal);
            this.storePrevConfig = true;

            _.each(this.tables, function(table) {
                table.prevConfig = table.columnStates;
            });

            this.hideModal();
        },

        /**
         * @desc removes the modal
         */
        remove: function() {
            this.editModal.remove();
            Backbone.View.prototype.remove.call(this);
        }
    });
});
Copyright 2013-2018 Micro Focus International plc. Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
Documentation generated by JSDoc 3.4.3 on Thu Jun 21st 18:04 2018 using the DocStrap template.