Commit 524e83b7 authored by Romain Courteaud's avatar Romain Courteaud

Readd support for iframe gadget.

parent 9a2ca855
......@@ -6,6 +6,7 @@
<script src="../../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="jqte/jquery-te-1.4.0.js" type="text/javascript"></script>
<script src="jqteditor.js" type="text/javascript"></script>
<link rel="http://www.renderjs.org/rel/interface"
......
......@@ -47,24 +47,14 @@
}
function createLoadNewEditorCallback(g, editor_path, e_c, io_path, i_c) {
// throw new Error("nutnut");
console.log("createLoadNewEditorCallback");
return function () {
// var new_element = document.createElement("div");
// // console.log(e_c);
// // e_c[0].innerHTML = '';
e_c.empty();
// e_c[0].appendChild(new_element);
console.log("inside");
return RSVP.all([
g.declareGadget(editor_path, {element: e_c[0]}),
// g.declareGadget(editor_path),
g.declareGadget(editor_path, {element: e_c[0], sandbox: 'iframe'}),
g.declareGadget(io_path),
"officejs"
])
.then(function (all_param) {
// e_c.empty();
// e_c[0].appendChild(all_param[0].element);
i_c.empty();
i_c[0].appendChild(all_param[1].element);
return attachIOToEditor(all_param);
......@@ -101,18 +91,16 @@
i;
// Load 1 editor and 1 IO and plug them
console.log(io_list[0].path);
editor_a_context.empty();
return RSVP.all([
g.declareGadget(editor_list[0].path),// editor_a_context),
g.declareGadget(
editor_list[0].path,
{element: editor_a_context[0], sandbox: 'iframe'}
),
g.declareGadget(io_list[0].path),// io_a_context),
"officejs"
])
.then(function (all_param) {
editor_a_context.empty();
console.log("first G");
console.log(all_param[0].element);
editor_a_context[0].appendChild(all_param[0].element);
console.log(editor_a_context[0]);
io_a_context.empty();
io_a_context[0].appendChild(all_param[1].element);
return attachIOToEditor(all_param);
......
/*! RenderJs v0.3 */
/*! RenderJs v0.3.1 */
/*global RSVP, window, document, DOMParser, Channel, XMLHttpRequest, alert */
/*jslint unparam: true, maxlen: 150 */
"use strict";
......@@ -60,6 +60,9 @@
loading_gadget_promise,
renderJS;
/////////////////////////////////////////////////////////////////
// RenderJSGadget
/////////////////////////////////////////////////////////////////
function RenderJSGadget() {
if (!(this instanceof RenderJSGadget)) {
return new RenderJSGadget();
......@@ -78,6 +81,9 @@
return this;
};
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareMethod
/////////////////////////////////////////////////////////////////
RenderJSGadget.declareMethod = function (name, callback) {
this.prototype[name] = function () {
var context = this,
......@@ -121,24 +127,31 @@
return this.element;
});
RenderJSGadget.prototype.declareGadget = function (url, options) {
var gadget_instance,
queue,
previous_loading_gadget_promise = loading_gadget_promise;
if (options === undefined) {
options = {};
/////////////////////////////////////////////////////////////////
// RenderJSEmbeddedGadget
/////////////////////////////////////////////////////////////////
// Class inheritance
function RenderJSEmbeddedGadget() {
if (!(this instanceof RenderJSEmbeddedGadget)) {
return new RenderJSEmbeddedGadget();
}
RenderJSGadget.call(this);
}
RenderJSEmbeddedGadget.ready_list = [];
RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
/////////////////////////////////////////////////////////////////
// privateDeclarePublicGadget
/////////////////////////////////////////////////////////////////
function privateDeclarePublicGadget(url, options) {
var gadget_instance;
if (options.element === undefined) {
options.element = document.createElement("div");
}
// Change the global variable to update the loading queue
queue = new RSVP.Queue()
// Wait for previous gadget loading to finish first
.push(function () {
return previous_loading_gadget_promise;
})
return new RSVP.Queue()
.push(function () {
return renderJS.declareGadgetKlass(url);
})
......@@ -172,8 +185,142 @@
}
return RSVP.all(parameter_list);
})
// Set the HTML context
.push(function () {
return gadget_instance;
});
}
/////////////////////////////////////////////////////////////////
// RenderJSIframeGadget
/////////////////////////////////////////////////////////////////
function RenderJSIframeGadget() {
if (!(this instanceof RenderJSIframeGadget)) {
return new RenderJSIframeGadget();
}
RenderJSGadget.call(this);
}
RenderJSIframeGadget.ready_list = [];
RenderJSIframeGadget.ready =
RenderJSGadget.ready;
RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
/////////////////////////////////////////////////////////////////
// privateDeclareIframeGadget
/////////////////////////////////////////////////////////////////
function privateDeclareIframeGadget(url, options) {
var gadget_instance,
iframe,
node,
iframe_loading_deferred = RSVP.defer();
if (options.element === undefined) {
throw new Error("DOM element is required to create Iframe Gadget " + url);
}
// Check if the element is attached to the DOM
node = options.element.parentNode;
while (node !== null) {
if (node === document) {
break;
}
node = node.parentNode;
}
if (node === null) {
throw new Error("The parent element is not attached to the DOM for " + url);
}
gadget_instance = new RenderJSIframeGadget();
iframe = document.createElement("iframe");
// gadget_instance.element.setAttribute("seamless", "seamless");
iframe.setAttribute("src", url);
gadget_instance.path = url;
gadget_instance.element = options.element;
// Attach it to the DOM
options.element.appendChild(iframe);
// XXX Manage unbind when deleting the gadget
// Create the communication channel with the iframe
gadget_instance.chan = Channel.build({
window: iframe.contentWindow,
origin: "*",
scope: "renderJS"
});
// Create new method from the declareMethod call inside the iframe
gadget_instance.chan.bind("declareMethod", function (trans, method_name) {
gadget_instance[method_name] = function () {
var argument_list = arguments;
return new RSVP.Promise(function (resolve, reject) {
gadget_instance.chan.call({
method: "methodCall",
params: [
method_name,
Array.prototype.slice.call(argument_list, 0)],
success: function (s) {
resolve(s);
},
error: function (e) {
reject(e);
}
});
});
};
return "OK";
});
// Wait for the iframe to be loaded before continuing
gadget_instance.chan.bind("ready", function (trans) {
iframe_loading_deferred.resolve(gadget_instance);
return "OK";
});
gadget_instance.chan.bind("failed", function (trans, params) {
iframe_loading_deferred.reject(params);
return "OK";
});
return RSVP.any([
iframe_loading_deferred.promise,
// Timeout to prevent non renderJS embeddable gadget
// XXX Maybe using iframe.onload/onerror would be safer?
RSVP.timeout(5000)
]);
}
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareGadget
/////////////////////////////////////////////////////////////////
RenderJSGadget.prototype.declareGadget = function (url, options) {
var queue,
previous_loading_gadget_promise = loading_gadget_promise;
if (options === undefined) {
options = {};
}
if (options.sandbox === undefined) {
options.sandbox = "public";
}
// Change the global variable to update the loading queue
queue = new RSVP.Queue()
// Wait for previous gadget loading to finish first
.push(function () {
return previous_loading_gadget_promise;
})
.push(function () {
var method;
if (options.sandbox === "public") {
method = privateDeclarePublicGadget;
} else if (options.sandbox === "iframe") {
method = privateDeclareIframeGadget;
} else {
throw new Error("Unsupported sandbox options '" + options.sandbox + "'");
}
return method(url, options);
})
// Set the HTML context
.push(function (gadget_instance) {
var i;
// Drop the current loading klass info used by selector
gadget_loading_klass = undefined;
......@@ -202,6 +349,9 @@
return loading_gadget_promise;
};
/////////////////////////////////////////////////////////////////
// renderJS selector
/////////////////////////////////////////////////////////////////
renderJS = function (selector) {
var result;
if (selector === window) {
......@@ -217,6 +367,9 @@
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.declareJS
/////////////////////////////////////////////////////////////////
renderJS.declareJS = function (url) {
// Prevent infinite recursion if loading render.js
// more than once
......@@ -242,6 +395,9 @@
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.declareCSS
/////////////////////////////////////////////////////////////////
renderJS.declareCSS = function (url) {
// https://github.com/furf/jquery-getCSS/blob/master/jquery.getCSS.js
// No way to cleanly check if a css has been loaded
......@@ -270,6 +426,9 @@
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.declareGadgetKlass
/////////////////////////////////////////////////////////////////
renderJS.declareGadgetKlass = function (url) {
var result,
xhr;
......@@ -359,6 +518,9 @@
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.clearGadgetKlassList
/////////////////////////////////////////////////////////////////
// For test purpose only
renderJS.clearGadgetKlassList = function () {
gadget_model_dict = {};
......@@ -366,6 +528,9 @@
stylesheet_registration_dict = {};
};
/////////////////////////////////////////////////////////////////
// renderJS.parseGadgetHTMLDocument
/////////////////////////////////////////////////////////////////
renderJS.parseGadgetHTMLDocument = function (document_element) {
var settings = {
title: "",
......@@ -397,8 +562,14 @@
}
return settings;
};
/////////////////////////////////////////////////////////////////
// global
/////////////////////////////////////////////////////////////////
window.rJS = window.renderJS = renderJS;
window.RenderJSGadget = RenderJSGadget;
window.RenderJSEmbeddedGadget = RenderJSEmbeddedGadget;
window.RenderJSIframeGadget = RenderJSIframeGadget;
///////////////////////////////////////////////////
// Bootstrap process. Register the self gadget.
......@@ -407,7 +578,12 @@
function bootstrap() {
var url = window.location.href,
tmp_constructor,
root_gadget;
root_gadget,
declare_method_count = 0,
embedded_channel,
notifyReady,
notifyDeclareMethod,
gadget_ready = false;
// Create the gadget class for the current url
......@@ -430,6 +606,64 @@
// Create the root gadget instance and put it in the loading stack
root_gadget = new gadget_model_dict[url]();
} else {
// Create the communication channel
embedded_channel = Channel.build({
window: window.parent,
origin: "*",
scope: "renderJS"
});
// Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget;
root_gadget = new RenderJSEmbeddedGadget();
// Bind calls to renderJS method on the instance
embedded_channel.bind("methodCall", function (trans, v) {
root_gadget[v[0]].apply(root_gadget, v[1]).then(function (g) {
trans.complete(g);
}).fail(function (e) {
console.warn(e);
trans.error(e.toString());
});
trans.delayReturn(true);
});
// Notify parent about gadget instanciation
notifyReady = function () {
if ((declare_method_count === 0) && (gadget_ready === true)) {
embedded_channel.notify({method: "ready"});
}
};
// Inform parent gadget about declareMethod calls here.
notifyDeclareMethod = function (name) {
declare_method_count += 1;
embedded_channel.call({
method: "declareMethod",
params: name,
success: function () {
declare_method_count -= 1;
notifyReady();
},
error: function () {
declare_method_count -= 1;
},
});
};
notifyDeclareMethod("getInterfaceList");
notifyDeclareMethod("getRequiredCSSList");
notifyDeclareMethod("getRequiredJSList");
notifyDeclareMethod("getPath");
notifyDeclareMethod("getTitle");
// Surcharge declareMethod to inform parent window
tmp_constructor.declareMethod = function (name, callback) {
var result = RenderJSGadget.declareMethod.apply(this, [name, callback]);
notifyDeclareMethod(name);
return result;
};
}
gadget_loading_klass = tmp_constructor;
......@@ -489,6 +723,18 @@
document.addEventListener('DOMContentLoaded', init, false);
});
if (window.self !== window.top) {
// Inform parent window that gadget is correctly loaded
loading_gadget_promise.then(function () {
gadget_ready = true;
notifyReady();
}).fail(function (e) {
console.warn(e);
embedded_channel.notify({method: "failed", params: e.toString()});
throw e;
});
}
}
bootstrap();
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Embedded page for renderJS test</title>
<meta name="viewport" content="width=device-width, height=device-height"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../lib/rsvp/rsvp.js" type="text/javascript"></script>
<script src="../../lib/jschannel/jschannel.js" type="text/javascript"></script>
<script src="../renderjs.js" type="text/javascript"></script>
<script src="./embedded.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
/*global window, rJS */
"use strict";
(function (window, rJS) {
var gk = rJS(window),
ready_called = false;
gk.ready(function (g) {
ready_called = true;
})
.declareMethod('wasReadyCalled', function () {
return ready_called;
})
.declareMethod('triggerError', function (value) {
throw new Error("Manually triggered embedded error");
})
.declareMethod('setContent', function (value) {
rJS(this).embedded_property = value;
})
.declareMethod('getContent', function () {
return rJS(this).embedded_property;
});
}(window, rJS));
/*jslint indent: 2, maxerr: 3, maxlen: 79 */
/*global window, document, QUnit, jQuery, renderJS, RenderJSGadget, sinon,
RSVP, DOMParser */
/*jslint unparam: true, maxlen: 150 */
RSVP, DOMParser, RenderJSIframeGadget, RenderJSEmbeddedGadget */
/*jslint unparam: true */
"use strict";
(function (document, $, renderJS, QUnit, sinon) {
......@@ -41,8 +41,8 @@
}
});
test('Not valid HTML string', function () {
// Check that parseGadgetHTMLDocument returns the default value if the string is
// not a valid xml
// Check that parseGadgetHTMLDocument returns the default value
// if the string is not a valid xml
deepEqual(parseGadgetHTML(""), {
title: "",
interface_list: [],
......@@ -1296,7 +1296,89 @@
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareGadget
// RenderJSIframeGadget
/////////////////////////////////////////////////////////////////
module("RenderJSIframeGadget");
test('should be a constructor', function () {
var gadget = new RenderJSIframeGadget();
equal(
Object.getPrototypeOf(gadget),
RenderJSIframeGadget.prototype,
'[[Prototype]] equals RenderJSIframeGadget.prototype'
);
equal(
gadget.constructor,
RenderJSIframeGadget,
'constructor property of instances is set correctly'
);
equal(
RenderJSIframeGadget.prototype.constructor,
RenderJSIframeGadget,
'constructor property of prototype is set correctly'
);
});
test('should not accept parameter', function () {
equal(RenderJSIframeGadget.length, 0);
});
test('should work without new', function () {
var gadgetKlass = RenderJSIframeGadget,
gadget = gadgetKlass();
equal(
gadget.constructor,
RenderJSIframeGadget,
'constructor property of instances is set correctly'
);
ok(gadget instanceof RenderJSGadget);
ok(gadget instanceof RenderJSIframeGadget);
ok(RenderJSIframeGadget !== RenderJSGadget);
});
/////////////////////////////////////////////////////////////////
// RenderJSEmbeddedGadget
/////////////////////////////////////////////////////////////////
module("RenderJSEmbeddedGadget");
test('should be a constructor', function () {
var gadget = new RenderJSEmbeddedGadget();
equal(
Object.getPrototypeOf(gadget),
RenderJSEmbeddedGadget.prototype,
'[[Prototype]] equals RenderJSEmbeddedGadget.prototype'
);
equal(
gadget.constructor,
RenderJSEmbeddedGadget,
'constructor property of instances is set correctly'
);
equal(
RenderJSEmbeddedGadget.prototype.constructor,
RenderJSEmbeddedGadget,
'constructor property of prototype is set correctly'
);
});
test('should not accept parameter', function () {
equal(RenderJSEmbeddedGadget.length, 0);
});
test('should work without new', function () {
var gadgetKlass = RenderJSEmbeddedGadget,
gadget = gadgetKlass();
equal(
gadget.constructor,
RenderJSEmbeddedGadget,
'constructor property of instances is set correctly'
);
ok(gadget instanceof RenderJSGadget);
ok(gadget instanceof RenderJSEmbeddedGadget);
ok(RenderJSEmbeddedGadget !== RenderJSGadget);
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareGadget (public)
/////////////////////////////////////////////////////////////////
module("RenderJSGadget.declareGadget", {
setup: function () {
......@@ -1358,6 +1440,7 @@
gadget.declareGadget(url)//, $('#qunit-fixture'))
.then(function (new_gadget) {
equal(new_gadget.path, url);
ok(new_gadget instanceof RenderJSGadget);
})
.always(function () {
start();
......@@ -1424,7 +1507,7 @@
$('#qunit-fixture').html("<div></div><div>bar</div>");
stop();
gadget.declareGadget(html_url)//, $('#qunit-fixture').find("div").last()[0])
gadget.declareGadget(html_url)
.then(function (new_gadget) {
equal($('#qunit-fixture').html(),
"<div>youhou2</div><div>bar</div>");
......@@ -1545,7 +1628,7 @@
stop();
$('#qunit-fixture').html("<div></div><div></div>");
gadget.declareGadget(html_url)//, $('#qunit-fixture').find("div").last()[0])
gadget.declareGadget(html_url)
.always(function () {
equal($('#qunit-fixture').html(),
"<div>youhou</div><div></div>");
......@@ -1661,7 +1744,10 @@
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
return gadget.declareGadget(html_url, {element: $('#qunit-fixture')[0]});
return gadget.declareGadget(
html_url,
{element: $('#qunit-fixture')[0]}
);
})
.then(function () {
equal($('#qunit-fixture').html(), '<p>foo</p>');
......@@ -1676,6 +1762,159 @@
});
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareGadget (iframe)
/////////////////////////////////////////////////////////////////
test('Require the element options', function () {
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test98.html';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "<html><body><p>foo</p></body></html>"]);
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
return gadget.declareGadget(html_url, {sandbox: 'iframe'});
})
.then(function () {
ok(false);
})
.fail(function (e) {
ok(e instanceof Error);
equal(
e.message,
"DOM element is required to create Iframe Gadget " + html_url
);
})
.always(function () {
start();
server.restore();
});
});
test('Require a DOM element as option', function () {
// Subclass RenderJSGadget to not pollute its namespace
var gadget = new RenderJSGadget(),
server = sinon.fakeServer.create(),
html_url = 'https://example.org/files/qunittest/test98.html';
server.autoRespond = true;
server.autoRespondAfter = 5;
server.respondWith("GET", html_url, [200, {
"Content-Type": "text/html",
}, "<html><body><p>foo</p></body></html>"]);
stop();
renderJS.declareGadgetKlass(html_url)
.then(function (Klass) {
return gadget.declareGadget(html_url, {
sandbox: 'iframe',
element: document.createElement("div")
});
})
.then(function () {
ok(false);
})
.fail(function (e) {
ok(e instanceof Error);
equal(
e.message,
"The parent element is not attached to the DOM for " + html_url
);
})
.always(function () {
start();
server.restore();
});
});
test('provide an iframed gadget as callback parameter', function () {
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
url = "./embedded.html";
$('#qunit-fixture').text('');
stop();
gadget.declareGadget(url, {
sandbox: 'iframe',
element: $('#qunit-fixture')[0]
})
.then(function (new_gadget) {
equal(new_gadget.path, url);
ok(new_gadget instanceof RenderJSIframeGadget);
equal(
$(new_gadget.element).html(),
'<iframe src="' + url + '"></iframe>'
);
ok(new_gadget.chan !== undefined);
})
.always(function () {
start();
});
});
test('checking working iframe gadget', function () {
// Check that declare gadget returns the gadget
var gadget = new RenderJSGadget(),
url = "./embedded.html";
$('#qunit-fixture').text('');
stop();
gadget.declareGadget(url, {
sandbox: 'iframe',
element: $('#qunit-fixture')[0]
})
.then(function (new_gadget) {
return new RSVP.Queue()
// Check that ready function are called
.push(function () {
return new_gadget.wasReadyCalled();
})
.push(function (result) {
equal(result, true);
})
// Custom method accept parameter
// and return value
.push(function () {
return new_gadget.setContent("foobar");
})
.push(function (result) {
return new_gadget.getContent();
})
.push(function (result) {
equal(result, "foobar");
})
// Method are propagated
.push(function () {
return new_gadget.triggerError();
})
.push(function () {
ok(false, "triggerError should fail");
}, function (e) {
equal(e, "Error: Manually triggered embedded error");
});
})
.fail(function () {
ok(false);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget bootstrap
/////////////////////////////////////////////////////////////////
......
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