lib/amd/src/fragment.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 way to call HTML fragments to be inserted as required via JavaScript.
 *
 * @module     core/fragment
 * @copyright  2016 Adrian Greeve <adrian@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @since      3.1
 */
define(['jquery', 'core/ajax'], function($, ajax) {

    /**
     * Loads an HTML fragment through a callback.
     *
     * @method loadFragment
     * @param {string} component Component where callback is located.
     * @param {string} callback Callback function name.
     * @param {integer} contextid Context ID of the fragment.
     * @param {object} params Parameters for the callback.
     * @return {Promise} JQuery promise object resolved when the fragment has been loaded.
     */
    var loadFragment = function(component, callback, contextid, params) {
        // Change params into required webservice format.
        var formattedparams = [];
        for (var index in params) {
            formattedparams.push({
                name: index,
                value: params[index]
            });
        }

        return ajax.call([{
            methodname: 'core_get_fragment',
            args: {
                component: component,
                callback: callback,
                contextid: contextid,
                args: formattedparams
            }
        }])[0];
    };

    /**
     * Converts the JS that was received from collecting JS requirements on the $PAGE so it can be added to the existing page
     *
     * @param {string} js
     * @return {string}
     */
    var processCollectedJavascript = function(js) {
        var jsNodes = $(js);
        var allScript = '';
        jsNodes.each(function(index, scriptNode) {
            scriptNode = $(scriptNode);
            var tagName = scriptNode.prop('tagName');
            if (tagName && (tagName.toLowerCase() == 'script')) {
                if (scriptNode.attr('src')) {
                    // We only reload the script if it was not loaded already.
                    var exists = false;
                    $('script').each(function(index, s) {
                        if ($(s).attr('src') == scriptNode.attr('src')) {
                            exists = true;
                        }
                        return !exists;
                    });
                    if (!exists) {
                        allScript += ' { ';
                        allScript += ' node = document.createElement("script"); ';
                        allScript += ' node.type = "text/javascript"; ';
                        allScript += ' node.src = decodeURI("' + encodeURI(scriptNode.attr('src')) + '"); ';
                        allScript += ' document.getElementsByTagName("head")[0].appendChild(node); ';
                        allScript += ' } ';
                    }
                } else {
                    allScript += ' ' + scriptNode.text();
                }
            }
        });
        return allScript;
    };

    return {
        /**
         * Appends HTML and JavaScript fragments to specified nodes.
         * Callbacks called by this AMD module are responsible for doing the appropriate security checks
         * to access the information that is returned. This only does minimal validation on the context.
         *
         * @method fragmentAppend
         * @param {string} component Component where callback is located.
         * @param {string} callback Callback function name.
         * @param {integer} contextid Context ID of the fragment.
         * @param {object} params Parameters for the callback.
         * @return {Deferred} new promise that is resolved with the html and js.
         */
        loadFragment: function(component, callback, contextid, params) {
            var promise = $.Deferred();
            loadFragment(component, callback, contextid, params).then(function(data) {
                promise.resolve(data.html, processCollectedJavascript(data.javascript));
            }).fail(function(ex) {
                promise.reject(ex);
            });
            return promise.promise();
        },

        /**
         * Converts the JS that was received from collecting JS requirements on the $PAGE so it can be added to the existing page
         *
         * @param {string} js
         * @return {string}
         */
        processCollectedJavascript: function(js) {
            return processCollectedJavascript(js);
        }
    };
});