Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Template renderer for Moodle. Load and render Moodle templates with Mustache.
18
 *
19
 * @module     core/templates
20
 * @copyright  2015 Damyon Wiese <damyon@moodle.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 * @since      2.9
23
 */
24
 
25
import $ from 'jquery';
26
import * as config from 'core/config';
27
import * as filterEvents from 'core_filters/events';
28
import * as Y from 'core/yui';
29
import Renderer from './local/templates/renderer';
30
import {getNormalisedComponent} from 'core/utils';
31
 
32
/**
33
 * Execute a block of JS returned from a template.
34
 * Call this AFTER adding the template HTML into the DOM so the nodes can be found.
35
 *
36
 * @method runTemplateJS
37
 * @param {string} source - A block of javascript.
38
 */
39
const runTemplateJS = (source) => {
40
    if (source.trim() !== '') {
41
        // Note. We continue to use jQuery here because people are doing some dumb things
42
        // and we need to find, seek, and destroy first.
43
        // In particular, people are providing a mixture of JS, and HTML content here.
44
        // jQuery is someohow, magically, detecting this and putting tags into tags.
45
        const newScript = $('<script>').attr('type', 'text/javascript').html(source);
46
        $('head').append(newScript);
47
        if (newScript.find('script').length) {
48
            window.console.error(
49
                'Template JS contains a script tag. This is not allowed. Only raw JS should be present here.',
50
                source,
51
            );
52
        }
53
    }
54
};
55
 
56
/**
57
 * Do some DOM replacement and trigger correct events and fire javascript.
58
 *
59
 * @method domReplace
60
 * @param {JQuery} element - Element or selector to replace.
61
 * @param {String} newHTML - HTML to insert / replace.
62
 * @param {String} newJS - Javascript to run after the insertion.
63
 * @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.
64
 * @return {Array} The list of new DOM Nodes
65
 * @fires event:filterContentUpdated
66
 */
67
const domReplace = (element, newHTML, newJS, replaceChildNodes) => {
68
    const replaceNode = $(element);
69
    if (!replaceNode.length) {
70
        return [];
71
    }
72
    // First create the dom nodes so we have a reference to them.
73
    const newNodes = $(newHTML);
74
    // Do the replacement in the page.
75
    if (replaceChildNodes) {
76
        // Cleanup any YUI event listeners attached to any of these nodes.
77
        const yuiNodes = new Y.NodeList(replaceNode.children().get());
78
        yuiNodes.destroy(true);
79
 
80
        // JQuery will cleanup after itself.
81
        replaceNode.empty();
82
        replaceNode.append(newNodes);
83
    } else {
84
        // Cleanup any YUI event listeners attached to any of these nodes.
85
        const yuiNodes = new Y.NodeList(replaceNode.get());
86
        yuiNodes.destroy(true);
87
 
88
        // JQuery will cleanup after itself.
89
        replaceNode.replaceWith(newNodes);
90
    }
91
    // Run any javascript associated with the new HTML.
92
    runTemplateJS(newJS);
93
    // Notify all filters about the new content.
94
    filterEvents.notifyFilterContentUpdated(newNodes);
95
 
96
    return newNodes.get();
97
};
98
 
99
/**
100
 * Prepend some HTML to a node and trigger events and fire javascript.
101
 *
102
 * @method domPrepend
103
 * @param {jQuery|String} element - Element or selector to prepend HTML to
104
 * @param {String} html - HTML to prepend
105
 * @param {String} js - Javascript to run after we prepend the html
106
 * @return {Array} The list of new DOM Nodes
107
 * @fires event:filterContentUpdated
108
 */
109
const domPrepend = (element, html, js) => {
110
    const node = $(element);
111
    if (!node.length) {
112
        return [];
113
    }
114
 
115
    // Prepend the html.
116
    const newContent = $(html);
117
    node.prepend(newContent);
118
    // Run any javascript associated with the new HTML.
119
    runTemplateJS(js);
120
    // Notify all filters about the new content.
121
    filterEvents.notifyFilterContentUpdated(node);
122
 
123
    return newContent.get();
124
};
125
 
126
/**
127
 * Append some HTML to a node and trigger events and fire javascript.
128
 *
129
 * @method domAppend
130
 * @param {jQuery|String} element - Element or selector to append HTML to
131
 * @param {String} html - HTML to append
132
 * @param {String} js - Javascript to run after we append the html
133
 * @return {Array} The list of new DOM Nodes
134
 * @fires event:filterContentUpdated
135
 */
136
const domAppend = (element, html, js) => {
137
    const node = $(element);
138
    if (!node.length) {
139
        return [];
140
    }
141
    // Append the html.
142
    const newContent = $(html);
143
    node.append(newContent);
144
    // Run any javascript associated with the new HTML.
145
    runTemplateJS(js);
146
    // Notify all filters about the new content.
147
    filterEvents.notifyFilterContentUpdated(node);
148
 
149
    return newContent.get();
150
};
151
 
152
const wrapPromiseInWhenable = (promise) => $.when(new Promise((resolve, reject) => {
153
    promise.then(resolve).catch(reject);
154
}));
155
 
156
export default {
157
    // Public variables and functions.
158
    /**
159
     * Every call to render creates a new instance of the class and calls render on it. This
160
     * means each render call has it's own class variables.
161
     *
162
     * @method render
163
     * @param {string} templateName - should consist of the component and the name of the template like this:
164
     *                              core/menu (lib/templates/menu.mustache) or
165
     *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
166
     * @param {Object} context - Could be array, string or simple value for the context of the template.
167
     * @param {string} themeName - Name of the current theme.
168
     * @return {Promise} JQuery promise object resolved when the template has been rendered.
169
     */
170
    render: (templateName, context, themeName = config.theme) => {
171
        const renderer = new Renderer();
172
 
173
        // Turn the Native Promise into a jQuery Promise for backwards compatability.
174
        return $.when(new Promise((resolve, reject) => {
175
            renderer.render(templateName, context, themeName)
176
            .then(resolve)
177
            .catch(reject);
178
        }))
179
        .then(({html, js}) => $.Deferred().resolve(html, js));
180
    },
181
 
182
    /**
183
     * Prefetch a set of templates without rendering them.
184
     *
185
     * @method getTemplate
186
     * @param {Array} templateNames The list of templates to fetch
187
     * @param {String} [themeName=config.themeName] The name of the theme to use
188
     * @returns {Promise}
189
     */
190
    prefetchTemplates: (templateNames, themeName = config.theme) => {
191
        const Loader = Renderer.getLoader();
192
 
193
        return Loader.prefetchTemplates(templateNames, themeName);
194
    },
195
 
196
    /**
197
     * Every call to render creates a new instance of the class and calls render on it. This
198
     * means each render call has it's own class variables.
199
     *
200
     * This alernate to the standard .render() function returns the html and js in a single object suitable for a
201
     * native Promise.
202
     *
203
     * @method renderForPromise
204
     * @param {string} templateName - should consist of the component and the name of the template like this:
205
     *                              core/menu (lib/templates/menu.mustache) or
206
     *                              tool_bananas/yellow (admin/tool/bananas/templates/yellow.mustache)
207
     * @param {Object} context - Could be array, string or simple value for the context of the template.
208
     * @param {string} themeName - Name of the current theme.
209
     * @return {Promise} JQuery promise object resolved when the template has been rendered.
210
     */
211
    renderForPromise: (templateName, context, themeName) => {
212
        const renderer = new Renderer();
213
        return renderer.render(templateName, context, themeName);
214
    },
215
 
216
    /**
217
     * Every call to renderIcon creates a new instance of the class and calls renderIcon on it. This
218
     * means each render call has it's own class variables.
219
     *
220
     * @method renderPix
221
     * @param {string} key - Icon key.
222
     * @param {string} component - Icon component
223
     * @param {string} title - Icon title
224
     * @return {Promise} JQuery promise object resolved when the pix has been rendered.
225
     */
226
    renderPix: (key, component, title) => {
227
        const renderer = new Renderer();
228
        return wrapPromiseInWhenable(renderer.renderIcon(
229
            key,
230
            getNormalisedComponent(component),
231
            title
232
        ));
233
    },
234
 
235
    /**
236
     * Execute a block of JS returned from a template.
237
     * Call this AFTER adding the template HTML into the DOM so the nodes can be found.
238
     *
239
     * @method runTemplateJS
240
     * @param {string} source - A block of javascript.
241
     */
242
    runTemplateJS: runTemplateJS,
243
 
244
    /**
245
     * Replace a node in the page with some new HTML and run the JS.
246
     *
247
     * @method replaceNodeContents
248
     * @param {JQuery} element - Element or selector to replace.
249
     * @param {String} newHTML - HTML to insert / replace.
250
     * @param {String} newJS - Javascript to run after the insertion.
251
     * @return {Array} The list of new DOM Nodes
252
     */
253
    replaceNodeContents: (element, newHTML, newJS) => domReplace(element, newHTML, newJS, true),
254
 
255
    /**
256
     * Insert a node in the page with some new HTML and run the JS.
257
     *
258
     * @method replaceNode
259
     * @param {JQuery} element - Element or selector to replace.
260
     * @param {String} newHTML - HTML to insert / replace.
261
     * @param {String} newJS - Javascript to run after the insertion.
262
     * @return {Array} The list of new DOM Nodes
263
     */
264
    replaceNode: (element, newHTML, newJS) => domReplace(element, newHTML, newJS, false),
265
 
266
    /**
267
     * Prepend some HTML to a node and trigger events and fire javascript.
268
     *
269
     * @method prependNodeContents
270
     * @param {jQuery|String} element - Element or selector to prepend HTML to
271
     * @param {String} html - HTML to prepend
272
     * @param {String} js - Javascript to run after we prepend the html
273
     * @return {Array} The list of new DOM Nodes
274
     */
275
    prependNodeContents: (element, html, js) => domPrepend(element, html, js),
276
 
277
    /**
278
     * Append some HTML to a node and trigger events and fire javascript.
279
     *
280
     * @method appendNodeContents
281
     * @param {jQuery|String} element - Element or selector to append HTML to
282
     * @param {String} html - HTML to append
283
     * @param {String} js - Javascript to run after we append the html
284
     * @return {Array} The list of new DOM Nodes
285
     */
286
    appendNodeContents: (element, html, js) => domAppend(element, html, js),
287
};