MDL-51222 Javascript: Trigger events for filters on DOM insertion

When nodes are added to the dom, they may need to be re-processed by a JS based
filter. To do this we need to trigger the legacy YUI event filter-content-updated.

To make this easier I added some wrappers to template that will insert the node, run any
JS and trigger the event.

I also changed existing yui code to call the amd function to trigger the event. This way
all jquery and yui listeners will always be notified.
This commit is contained in:
Damyon Wiese 2015-08-27 17:15:17 +08:00
parent 74ede2d89a
commit 28de777199
17 changed files with 284 additions and 135 deletions

1
lib/amd/build/event.min.js vendored Normal file
View file

@ -0,0 +1 @@
define(["jquery","core/yui"],function(a,b){return{notifyFilterContentUpdated:function(c){c=a(c),b.use("event","moodle-core-event",function(b){a("document").trigger(M.core.event.FILTER_CONTENT_UPDATED,c);var d=new b.NodeList(c.get());b.fire(M.core.event.FILTER_CONTENT_UPDATED,{nodes:d})})}}});

View file

@ -1 +1 @@
define(["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage"],function(a,b,c,d,e,f,g,h){var i={},j=[],k=[],l=1,m="",n=function(b,c){var d,e=b.split(","),g="",h="",j="";e.length>0&&(g=e.shift().trim()),e.length>0&&(h=e.shift().trim()),e.length>0&&(j=e.join(",").trim());var k=f.imageUrl(g,h),l={attributes:[{name:"src",value:k},{name:"alt",value:c(j)},{name:"class",value:"smallicon"}]},n=i[m+"/core/pix_icon"];return d=a.render(n,l,o),d.trim()},o=function(a){var b="";return u(a,!1).done(function(a){b=a}).fail(e.exception),b},p=function(a,b){return k.push(b(a,this)),""},q=function(a,b){var c=a.split(","),d="",e="",f="";c.length>0&&(d=c.shift().trim()),c.length>0&&(e=c.shift().trim()),c.length>0&&(f=c.join(",").trim()),""!==f&&(f=b(f,this)),0===f.indexOf("{")&&0!==f.indexOf("{{")&&(f=JSON.parse(f));var g=j.length;return j.push({key:d,component:e,param:f}),"{{_s"+g+"}}"},r=function(a,b){m=b,j=[],k=[],a.uniqid=l++,a.str=function(){return q},a.pix=function(){return n},a.js=function(){return p},a.globals={config:g},a.currentTheme=b},s=function(a){var b="";k.length>0&&(b=k.join(";\n"));var c=0;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);return b},t=function(c,e,f){var g=b.Deferred();m=f;var h=u("core/pix_icon",!0);return h.done(function(){r(e,f);var b="";try{b=a.render(c,e,o)}catch(h){g.reject(h)}j.length>0?d.get_strings(j).done(function(a){var c;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);g.resolve(b.trim(),s(a))}).fail(function(a){g.reject(a)}):g.resolve(b.trim(),s([]))}).fail(function(a){g.reject(a)}),g.promise()},u=function(a,d){var e=b.Deferred(),f=a.split("/"),g=f.shift(),j=f.shift(),k=m+"/"+a;if(k in i)return e.resolve(i[k]),e.promise();var l=h.get("core_template/"+k);if(l)return e.resolve(l),i[k]=l,e.promise();var n=c.call([{methodname:"core_output_load_template",args:{component:g,template:j,themename:m}}],d,!1);return n[0].done(function(a){h.set("core_template/"+k,a),i[k]=a,e.resolve(a)}).fail(function(a){e.reject(a)}),e.promise()};return{render:function(a,c,d){var e=b.Deferred();"undefined"==typeof d&&(d=g.theme),m=d;var f=u(a,!0);return f.done(function(a){var b=t(a,c,d);b.done(function(a,b){e.resolve(a,b)}).fail(function(a){e.reject(a)})}).fail(function(a){e.reject(a)}),e.promise()},runTemplateJS:function(a){var c=b("<script>").attr("type","text/javascript").html(a);b("head").append(c)}}});
define(["core/mustache","jquery","core/ajax","core/str","core/notification","core/url","core/config","core/localstorage","core/event"],function(a,b,c,d,e,f,g,h,i){var j={},k=[],l=[],m=1,n="",o=function(b,c){var d,e=b.split(","),g="",h="",i="";e.length>0&&(g=e.shift().trim()),e.length>0&&(h=e.shift().trim()),e.length>0&&(i=e.join(",").trim());var k=f.imageUrl(g,h),l={attributes:[{name:"src",value:k},{name:"alt",value:c(i)},{name:"class",value:"smallicon"}]},m=j[n+"/core/pix_icon"];return d=a.render(m,l,p),d.trim()},p=function(a){var b="";return v(a,!1).done(function(a){b=a}).fail(e.exception),b},q=function(a,b){return l.push(b(a,this)),""},r=function(a,b){var c=a.split(","),d="",e="",f="";c.length>0&&(d=c.shift().trim()),c.length>0&&(e=c.shift().trim()),c.length>0&&(f=c.join(",").trim()),""!==f&&(f=b(f,this)),0===f.indexOf("{")&&0!==f.indexOf("{{")&&(f=JSON.parse(f));var g=k.length;return k.push({key:d,component:e,param:f}),"{{_s"+g+"}}"},s=function(a,b){n=b,k=[],l=[],a.uniqid=m++,a.str=function(){return r},a.pix=function(){return o},a.js=function(){return q},a.globals={config:g},a.currentTheme=b},t=function(a){var b="";l.length>0&&(b=l.join(";\n"));var c=0;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);return b},u=function(c,e,f){var g=b.Deferred();n=f;var h=v("core/pix_icon",!0);return h.done(function(){s(e,f);var b="";try{b=a.render(c,e,p)}catch(h){g.reject(h)}k.length>0?d.get_strings(k).done(function(a){var c;for(c=0;c<a.length;c++)b=b.replace("{{_s"+c+"}}",a[c]);g.resolve(b.trim(),t(a))}).fail(function(a){g.reject(a)}):g.resolve(b.trim(),t([]))}).fail(function(a){g.reject(a)}),g.promise()},v=function(a,d){var e=b.Deferred(),f=a.split("/"),g=f.shift(),i=f.shift(),k=n+"/"+a;if(k in j)return e.resolve(j[k]),e.promise();var l=h.get("core_template/"+k);if(l)return e.resolve(l),j[k]=l,e.promise();var m=c.call([{methodname:"core_output_load_template",args:{component:g,template:i,themename:n}}],d,!1);return m[0].done(function(a){h.set("core_template/"+k,a),j[k]=a,e.resolve(a)}).fail(function(a){e.reject(a)}),e.promise()},w=function(a){if(""!==a.trim()){var c=b("<script>").attr("type","text/javascript").html(a);b("head").append(c)}},x=function(a,c,d,e){var f=b(a);if(f.length){var g=b(c);e?(f.empty(),f.append(g)):f.replaceWith(g),w(d),i.notifyFilterContentUpdated(g)}};return{render:function(a,c,d){var e=b.Deferred();"undefined"==typeof d&&(d=g.theme),n=d;var f=v(a,!0);return f.done(function(a){var b=u(a,c,d);b.done(function(a,b){e.resolve(a,b)}).fail(function(a){e.reject(a)})}).fail(function(a){e.reject(a)}),e.promise()},runTemplateJS:w,replaceNodeContents:function(a,b,c){return x(a,b,c,!0)},replaceNode:function(a,b,c){return x(a,b,c,!1)}}});

52
lib/amd/src/event.js Normal file
View file

@ -0,0 +1,52 @@
// 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/>.
/**
* Global registry of core events that can be triggered/listened for.
*
* @module core/event
* @package core
* @class event
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.0
*/
define([ 'jquery', 'core/yui' ],
function($, Y) {
return /** @alias module:core/event */ {
// Public variables and functions.
/**
* Trigger an event using both JQuery and YUI
*
* @method notifyFilterContentUpdated
* @param {string}|{JQuery} nodes - Selector or list of elements that were inserted.
*/
notifyFilterContentUpdated: function(nodes) {
nodes = $(nodes);
Y.use('event', 'moodle-core-event', function(Y) {
// Trigger it the JQuery way.
$('document').trigger(M.core.event.FILTER_CONTENT_UPDATED, nodes);
// Create a YUI NodeList from our JQuery Object.
var yuiNodes = new Y.NodeList(nodes.get());
// And again for YUI.
Y.fire(M.core.event.FILTER_CONTENT_UPDATED, { nodes: yuiNodes });
});
},
};
});

View file

@ -30,9 +30,10 @@ define([ 'core/mustache',
'core/notification',
'core/url',
'core/config',
'core/localstorage'
'core/localstorage',
'core/event'
],
function(mustache, $, ajax, str, notification, coreurl, config, storage) {
function(mustache, $, ajax, str, notification, coreurl, config, storage, event) {
// Private variables and functions.
@ -326,6 +327,50 @@ define([ 'core/mustache',
return deferred.promise();
};
/**
* Execute a block of JS returned from a template.
* Call this AFTER adding the template HTML into the DOM so the nodes can be found.
*
* @method runTemplateJS
* @param {string} source - A block of javascript.
*/
var runTemplateJS = function(source) {
if (source.trim() !== '') {
var newscript = $('<script>').attr('type','text/javascript').html(source);
$('head').append(newscript);
}
};
/**
* Do some DOM replacement and trigger correct events and fire javascript.
*
* @method domReplace
* @private
* @param {JQuery} element - Element or selector to replace.
* @param {String} newHTML - HTML to insert / replace.
* @param {String} newJS - Javascript to run after the insertion.
* @param {Boolean} replaceChildNodes - Replace only the childnodes, alternative is to replace the entire node.
*/
var domReplace = function(element, newHTML, newJS, replaceChildNodes) {
var replaceNode = $(element);
if (replaceNode.length) {
// First create the dom nodes so we have a reference to them.
var newNodes = $(newHTML);
// Do the replacement in the page.
if (replaceChildNodes) {
replaceNode.empty();
replaceNode.append(newNodes);
} else {
replaceNode.replaceWith(newNodes);
}
// Run any javascript associated with the new HTML.
runTemplateJS(newJS);
// Notify all filters about the new content.
event.notifyFilterContentUpdated(newNodes);
}
};
return /** @alias module:core/templates */ {
// Public variables and functions.
/**
@ -379,12 +424,28 @@ define([ 'core/mustache',
* Call this AFTER adding the template HTML into the DOM so the nodes can be found.
*
* @method runTemplateJS
* @private
* @param {string} source - A block of javascript.
*/
runTemplateJS: function(source) {
var newscript = $('<script>').attr('type','text/javascript').html(source);
$('head').append(newscript);
runTemplateJS: runTemplateJS,
/**
* Replace a node in the page with some new HTML and run the JS.
*
* @method replaceNodeContents
* @param {string} source - A block of javascript.
*/
replaceNodeContents: function(element, newHTML, newJS) {
return domReplace(element, newHTML, newJS, true);
},
/**
* Insert a node in the page with some new HTML and run the JS.
*
* @method replaceNode
* @param {string} source - A block of javascript.
*/
replaceNode: function(element, newHTML, newJS) {
return domReplace(element, newHTML, newJS, false);
}
};
});