Commit 46668992 authored by Romain Courteaud's avatar Romain Courteaud

Always create an empty DOM element for a new gadget.

This reduces the number of DOM modifications (only one replace).

This also ensure that the element content is empty.

Previous event listener will also be discarded (and so, reduce memory usage).
parent ffd3d0c2
......@@ -888,11 +888,9 @@
/////////////////////////////////////////////////////////////////
// privateDeclarePublicGadget
/////////////////////////////////////////////////////////////////
function createPrivateInstanceFromKlass(Klass, options, parent_gadget) {
function createPrivateInstanceFromKlass(Klass, options, parent_gadget,
old_element) {
// Get the gadget class and instanciate it
if (options.element === undefined) {
options.element = document.createElement("div");
}
var i,
gadget_instance,
template_node_list = Klass.__template_element.body.childNodes,
......@@ -907,10 +905,17 @@
}
gadget_instance.element.appendChild(fragment);
setAqParent(gadget_instance, parent_gadget);
if (old_element !== undefined) {
// Add gadget to the DOM if needed
// Do it when all DOM modifications are done
old_element.parentNode.replaceChild(options.element,
old_element);
}
return gadget_instance;
}
function privateDeclarePublicGadget(url, options, parent_gadget) {
function privateDeclarePublicGadget(url, options, parent_gadget,
old_element) {
var klass = renderJS.declareGadgetKlass(url);
// gadget loading should not be interrupted
// if not, gadget's definition will not be complete
......@@ -918,10 +923,12 @@
//so loading_klass_promise can't be cancel
if (typeof klass.then === 'function') {
return klass.then(function createAsyncPrivateInstanceFromKlass(Klass) {
return createPrivateInstanceFromKlass(Klass, options, parent_gadget);
return createPrivateInstanceFromKlass(Klass, options, parent_gadget,
old_element);
});
}
return createPrivateInstanceFromKlass(klass, options, parent_gadget);
return createPrivateInstanceFromKlass(klass, options, parent_gadget,
old_element);
}
/////////////////////////////////////////////////////////////////
......@@ -953,17 +960,18 @@
/////////////////////////////////////////////////////////////////
// privateDeclareIframeGadget
/////////////////////////////////////////////////////////////////
function privateDeclareIframeGadget(url, options, parent_gadget) {
function privateDeclareIframeGadget(url, options, parent_gadget,
old_element) {
var gadget_instance,
iframe,
iframe_loading_deferred = RSVP.defer();
if (options.element === undefined) {
if (old_element === undefined) {
throw new Error("DOM element is required to create Iframe Gadget " +
url);
}
// Check if the element is attached to the DOM
if (!document.contains(options.element)) {
if (!document.contains(old_element)) {
throw new Error("The parent element is not attached to the DOM for " +
url);
}
......@@ -987,8 +995,11 @@
gadget_instance.__path = url;
gadget_instance.element = options.element;
gadget_instance.state = {};
// Attach it to the DOM
options.element.appendChild(iframe);
// Add gadget to the DOM if needed
// Do it when all DOM modifications are done
old_element.parentNode.replaceChild(options.element,
old_element);
// XXX Manage unbind when deleting the gadget
......@@ -1056,7 +1067,8 @@
/////////////////////////////////////////////////////////////////
// privateDeclareDataUrlGadget
/////////////////////////////////////////////////////////////////
function privateDeclareDataUrlGadget(url, options, parent_gadget) {
function privateDeclareDataUrlGadget(url, options, parent_gadget,
old_element) {
return new RSVP.Queue()
.push(function waitForDataUrlAjax() {
......@@ -1076,7 +1088,8 @@
return readBlobAsDataURL(blob);
})
.push(function handleDataURL(data_url) {
return privateDeclareIframeGadget(data_url, options, parent_gadget);
return privateDeclareIframeGadget(data_url, options, parent_gadget,
old_element);
});
}
......@@ -1084,29 +1097,12 @@
// RenderJSGadget.declareGadget
/////////////////////////////////////////////////////////////////
function setGadgetInstanceHTMLContext(gadget_instance, options,
parent_gadget, url) {
parent_gadget, url,
old_element, scope) {
var i,
scope,
queue;
clearGadgetInternalParameters(gadget_instance);
// Store local reference to the gadget instance
scope = options.scope;
if (scope === undefined) {
scope = 'RJS_' + scope_increment;
scope_increment += 1;
while (parent_gadget.__sub_gadget_dict.hasOwnProperty(scope)) {
scope = 'RJS_' + scope_increment;
scope_increment += 1;
}
}
gadget_instance.element.setAttribute("data-gadget-scope",
scope);
// Put some attribute to ease page layout comprehension
gadget_instance.element.setAttribute("data-gadget-url", url);
gadget_instance.element.setAttribute("data-gadget-sandbox",
options.sandbox);
gadget_instance.element._gadget = gadget_instance;
function ready_executable_wrapper(fct) {
......@@ -1116,9 +1112,6 @@
}
function ready_wrapper() {
if (document.contains(gadget_instance.element)) {
startService(gadget_instance);
}
// Always set the parent reference when all ready are finished
// in case the gadget declaration is cancelled
// (and ready are not finished)
......@@ -1147,7 +1140,9 @@
.declareMethod('declareGadget', function declareGadget(url, options) {
var parent_gadget = this,
method,
result;
result,
scope,
old_element;
if (options === undefined) {
options = {};
......@@ -1155,10 +1150,34 @@
if (options.sandbox === undefined) {
options.sandbox = "public";
}
if (options.element === undefined) {
options.element = document.createElement('div');
} else {
old_element = options.element;
// Clean up the element content
// Remove all existing event listener
options.element = old_element.cloneNode(false);
}
// transform url to absolute url if it is relative
url = renderJS.getAbsoluteURL(url, this.__path);
// Store local reference to the gadget instance
scope = options.scope;
if (scope === undefined) {
scope = 'RJS_' + scope_increment;
scope_increment += 1;
while (parent_gadget.__sub_gadget_dict.hasOwnProperty(scope)) {
scope = 'RJS_' + scope_increment;
scope_increment += 1;
}
}
options.element.setAttribute("data-gadget-scope", scope);
// Put some attribute to ease page layout comprehension
options.element.setAttribute("data-gadget-url", url);
options.element.setAttribute("data-gadget-sandbox", options.sandbox);
if (options.sandbox === "public") {
method = privateDeclarePublicGadget;
} else if (options.sandbox === "iframe") {
......@@ -1169,7 +1188,7 @@
throw new Error("Unsupported sandbox options '" +
options.sandbox + "'");
}
result = method(url, options, parent_gadget);
result = method(url, options, parent_gadget, old_element);
// Set the HTML context
if (typeof result.then === 'function') {
return new RSVP.Queue()
......@@ -1178,11 +1197,13 @@
})
.push(function setAsyncGadgetInstanceHTMLContext(gadget_instance) {
return setGadgetInstanceHTMLContext(gadget_instance, options,
parent_gadget, url);
parent_gadget, url,
old_element, scope);
});
}
return setGadgetInstanceHTMLContext(result, options,
parent_gadget, url);
parent_gadget, url, old_element,
scope);
})
.declareMethod('getDeclaredGadget',
function getDeclaredGadget(gadget_scope) {
......
......@@ -3940,6 +3940,7 @@
// Check that gadget is not created if klass is can not be loaded
var gadget = new RenderJSGadget(),
html_url = 'http://example.org/files/qunittest/test3.html';
gadget.__sub_gadget_dict = {};
this.server.respondWith("GET", html_url, [404, {
"Content-Type": "text/html"
......@@ -4331,24 +4332,27 @@
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test98.html';
html_url = 'https://example.org/files/qunittest/test98.html',
previous_fixture = document.getElementById('qunit-fixture');
gadget.__sub_gadget_dict = {};
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body><p>foo</p></body></html>"]);
document.getElementById('qunit-fixture').textContent = "";
previous_fixture.innerHTML = "<div>bar</div>";
stop();
expect(1);
expect(3);
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')}
{element: previous_fixture}
);
})
.then(function () {
.then(function (g) {
notEqual(document.getElementById('qunit-fixture'), previous_fixture);
equal(document.getElementById('qunit-fixture'), g.element);
equal(
document.getElementById('qunit-fixture').innerHTML,
'<p>foo</p>'
......@@ -5012,6 +5016,7 @@
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test98.html';
gadget.__sub_gadget_dict = {};
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
......@@ -5659,6 +5664,7 @@
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
url = "./embedded_fail.html";
gadget.__sub_gadget_dict = {};
stop();
expect(1);
......@@ -5681,6 +5687,7 @@
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
url = "./embedded_empty.html";
gadget.__sub_gadget_dict = {};
stop();
expect(2);
......@@ -5704,6 +5711,7 @@
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
url = "./embedded_404.html";
gadget.__sub_gadget_dict = {};
stop();
expect(2);
......@@ -5754,6 +5762,7 @@
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
url = "./embedded_non_renderjs.html";
gadget.__sub_gadget_dict = {};
stop();
expect(2);
......@@ -6254,7 +6263,7 @@
return new RSVP.Promise(function (resolve, reject) {
// listen for an event fired in the ready function of the parent
// gadget
parentDiv.addEventListener("rjsready", function (e) {
parentDiv.parentNode.addEventListener("rjsready", function (e) {
resolve();
});
// if no event is fired within 500ms, just resolve and fail later
......
......@@ -22,9 +22,9 @@
(function (window, rJS) {
"use strict";
rJS(window)
.ready(function (gadget) {
return gadget.element.dispatchEvent(new Event("rjsready"));
return gadget.element.dispatchEvent(new Event("rjsready",
{bubbles: true}));
});
}(window, rJS));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment