lib/form/amd/src/showadvanced.js

// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * A class to help show and hide advanced form content.
 *
 * @module     core_form/showadvanced
 * @copyright  2016 Damyon Wiese <damyon@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
define(['jquery', 'core/log', 'core/str', 'core/notification'], function($, Log, Strings, Notification) {

    var SELECTORS = {
            FIELDSETCONTAINSADVANCED: 'fieldset.containsadvancedelements',
            DIVFITEMADVANCED: 'div.fitem.advanced',
            DIVADVANCEDSECTION: 'div#form-advanced-div',
            DIVFCONTAINER: 'div.fcontainer',
            MORELESSLINK: 'fieldset.containsadvancedelements .moreless-toggler'
        },
        CSS = {
            SHOW: 'show',
            MORELESSACTIONS: 'moreless-actions',
            MORELESSTOGGLER: 'moreless-toggler',
            SHOWLESS: 'moreless-less'
        },
        WRAPPERS = {
            FITEM: '<div class="fitem"></div>',
            FELEMENT: '<div class="felement"></div>',
            ADVANCEDDIV: '<div id="form-advanced-div"></div>'
        },
        IDPREFIX = 'showadvancedid-';

    /** @property {Integer} uniqIdSeed Auto incrementing number used to generate ids. */
    var uniqIdSeed = 0;

    /**
     * ShowAdvanced behaviour class.
     *
     * @class core_form/showadvanced
     * @param {String} id The id of the form.
     */
    var ShowAdvanced = function(id) {
        this.id = id;

        var form = $(document.getElementById(id));
        this.enhanceForm(form);
    };

    /** @property {String} id The form id to enhance. */
    ShowAdvanced.prototype.id = '';

    /**
     * @method enhanceForm
     * @param {JQuery} form JQuery selector representing the form
     * @return {ShowAdvanced}
     */
    ShowAdvanced.prototype.enhanceForm = function(form) {
        var fieldsets = form.find(SELECTORS.FIELDSETCONTAINSADVANCED);

        // Enhance each fieldset in the form matching the selector.
        fieldsets.each(function(index, item) {
            this.enhanceFieldset($(item));
        }.bind(this));

        // Attach some event listeners.
        // Subscribe more/less links to click event.
        form.on('click', SELECTORS.MORELESSLINK, this.switchState);

        // Subscribe to key events but filter for space or enter.
        form.on('keydown', SELECTORS.MORELESSLINK, function(e) {
            // Enter or space.
            if (e.which == 13 || e.which == 32) {
                return this.switchState(e);
            }
            return true;
        }.bind(this));
        return this;
    };


    /**
     * Generates a uniq id for the dom element it's called on unless the element already has an id.
     * The id is set on the dom node before being returned.
     *
     * @method generateId
     * @param {JQuery} node JQuery selector representing a single DOM Node.
     * @return {String}
     */
    ShowAdvanced.prototype.generateId = function(node) {
        var id = node.prop('id');
        if (typeof id === 'undefined') {
            id = IDPREFIX + (uniqIdSeed++);
            node.prop('id', id);
        }
        return id;
    };

    /**
     * @method enhanceFieldset
     * @param {JQuery} fieldset JQuery selector representing a fieldset
     * @return {ShowAdvanced}
     */
    ShowAdvanced.prototype.enhanceFieldset = function(fieldset) {
        var statuselement = $('input[name=mform_showmore_' + fieldset.prop('id') + ']');
        if (!statuselement.length) {
            Log.debug("M.form.showadvanced::processFieldset was called on an fieldset without a status field: '" +
                fieldset.prop('id') + "'");
            return this;
        }

        // Fetch some strings.
        Strings.get_strings([{
            key: 'showmore',
            component: 'core_form'
        }, {
            key: 'showless',
            component: 'core_form'
        }]).then(function(results) {
            var showmore = results[0],
                showless = results[1];

            // Generate more/less links.
            var morelesslink = $('<a href="#"></a>');
            morelesslink.addClass(CSS.MORELESSTOGGLER);
            if (statuselement.val() === '0') {
                morelesslink.html(showmore);
                morelesslink.attr('aria-expanded', 'false');
            } else {
                morelesslink.html(showless);
                morelesslink.attr('aria-expanded', 'true');
                morelesslink.addClass(CSS.SHOWLESS);
                fieldset.find(SELECTORS.DIVFITEMADVANCED).addClass(CSS.SHOW);
            }
            // Build a list of advanced fieldsets.
            var idlist = [];
            fieldset.find(SELECTORS.DIVFITEMADVANCED).each(function(index, node) {
                idlist[idlist.length] = this.generateId($(node));
            }.bind(this));

            // Set aria attributes.
            morelesslink.attr('role', 'button');
            morelesslink.attr('aria-controls', 'form-advanced-div');

            var formadvancedsection = $(WRAPPERS.ADVANCEDDIV);
            fieldset.find(SELECTORS.DIVFITEMADVANCED).wrapAll(formadvancedsection);
            // Add elements to the DOM.
            var fitem = $(WRAPPERS.FITEM);
            fitem.addClass(CSS.MORELESSACTIONS);
            var felement = $(WRAPPERS.FELEMENT);
            felement.append(morelesslink);
            fitem.append(felement);

            fieldset.find(SELECTORS.DIVADVANCEDSECTION).before(fitem);
            return true;
        }.bind(this)).fail(Notification.exception);

        return this;
    };

    /**
     * @method switchState
     * @param {Event} e Event that triggered this action.
     * @return {Boolean}
     */
    ShowAdvanced.prototype.switchState = function(e) {
        e.preventDefault();

        // Fetch some strings.
        Strings.get_strings([{
            key: 'showmore',
            component: 'core_form'
        }, {
            key: 'showless',
            component: 'core_form'
        }]).then(function(results) {
            var showmore = results[0],
                showless = results[1],
                fieldset = $(e.target).closest(SELECTORS.FIELDSETCONTAINSADVANCED);

            // Toggle collapsed class.
            fieldset.find(SELECTORS.DIVFITEMADVANCED).toggleClass(CSS.SHOW);

            // Get corresponding hidden variable.
            var statuselement = $('input[name=mform_showmore_' + fieldset.prop('id') + ']');

            // Invert it and change the link text.
            if (statuselement.val() === '0') {
                statuselement.val(1);
                $(e.target).addClass(CSS.SHOWLESS);
                $(e.target).html(showless);
                $(e.target).attr('aria-expanded', 'true');
            } else {
                statuselement.val(0);
                $(e.target).removeClass(CSS.SHOWLESS);
                $(e.target).html(showmore);
                $(e.target).attr('aria-expanded', 'false');
            }
            return true;
        }).fail(Notification.exception);

        return this;
    };

    return {
        /**
         * Initialise this module.
         * @method init
         * @param {String} formid
         * @return {ShowAdvanced}
         */
        init: function(formid) {
            return new ShowAdvanced(formid);
        }
    };
});