Commit d939a570 by 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).

declareGadget element parameter can now also be a tag name.
1 parent ffd3d0c2
......@@ -620,8 +620,9 @@
}
function startService(gadget) {
if ((gadget.constructor.__service_list.length === 0) &&
(!gadget.constructor.__job_declared)) {
if (((gadget.constructor.__service_list.length === 0) &&
(!gadget.constructor.__job_declared)) ||
(gadget.hasOwnProperty('__monitor'))) {
return;
}
createGadgetMonitor(gadget);
......@@ -888,11 +889,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 +906,18 @@
}
gadget_instance.element.appendChild(fragment);
setAqParent(gadget_instance, parent_gadget);
clearGadgetInternalParameters(gadget_instance);
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 +925,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 +962,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 +997,12 @@
gadget_instance.__path = url;
gadget_instance.element = options.element;
gadget_instance.state = {};
// Attach it to the DOM
options.element.appendChild(iframe);
clearGadgetInternalParameters(gadget_instance);
// 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 +1070,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 +1091,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,30 +1100,10 @@
// 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) {
return function executeReadyWrapper() {
......@@ -1116,13 +1112,14 @@
}
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)
gadget_instance.element._gadget = gadget_instance;
parent_gadget.__sub_gadget_dict[scope] = gadget_instance;
if (document.contains(gadget_instance.element)) {
startService(gadget_instance);
}
// Always return the gadget instance after ready function
return gadget_instance;
}
......@@ -1147,7 +1144,9 @@
.declareMethod('declareGadget', function declareGadget(url, options) {
var parent_gadget = this,
method,
result;
result,
scope,
old_element;
if (options === undefined) {
options = {};
......@@ -1155,10 +1154,39 @@
if (options.sandbox === undefined) {
options.sandbox = "public";
}
if (options.element === undefined) {
options.element = document.createElement('div');
} else if (typeof options.element === 'string') {
options.element = document.createElement(options.element);
} else if (options.element.parentNode) {
old_element = options.element;
// Clean up the element content
// Remove all existing event listener
options.element = old_element.cloneNode(false);
} else {
throw new Error('No need to manually provide a DOM element ' +
'without a parentNode: ' + url);
}
// 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 +1197,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 +1206,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) {
......
......@@ -2337,6 +2337,51 @@
});
});
test('service started after ready is finished', function () {
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
html_url = 'https://example.org/files/qunittest/test5011.html',
defer = RSVP.defer();
gadget.__sub_gadget_dict = {};
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
}, "<html><body></body></html>"]);
document.getElementById('qunit-fixture').innerHTML = "<div></div>";
stop();
expect(1);
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
Klass.declareService(function () {
defer.reject('Triggered before ready');
});
Klass.ready(function () {
return RSVP.delay(500)
.then(function () {
defer.resolve('Triggered before service');
});
});
return gadget.declareGadget(
html_url,
{element: document.getElementById('qunit-fixture')
.querySelector("div")}
);
})
.then(function () {
return defer.promise;
})
.then(function (result) {
equal(result, 'Triggered before service');
})
.fail(function (e) {
ok(false, e);
})
.always(function () {
start();
});
});
test('service started when gadget element added in DOM', function () {
// Subclass RenderJSGadget to not pollute its namespace
var service1 = {},
......@@ -3940,6 +3985,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 +4377,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>'
......@@ -5011,7 +5060,11 @@
test('Require a DOM element as option', function () {
// 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',
parent_element = document.createElement("div"),
gadget_element = document.createElement("div");
gadget.__sub_gadget_dict = {};
parent_element.appendChild(gadget_element);
this.server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html"
......@@ -5023,7 +5076,7 @@
.then(function (Klass) {
return gadget.declareGadget(html_url, {
sandbox: 'iframe',
element: document.createElement("div")
element: gadget_element
});
})
.then(function () {
......@@ -5659,6 +5712,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 +5735,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 +5759,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 +5810,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 +6311,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));
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!