Commit ba356131 authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

CribJS uses RenderJS and a loader

parent 69199231
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', {scope: './'}).then(function() {
// Registration was successful. Now, check to see whether the Service Worker is controlling the page.
if (navigator.serviceWorker.controller) {
// If .controller is set, then this page is being actively controlled by the Service Worker.
// Show the interface for sending messages to the service worker.
showCommands();
} else {
// If .controller isn't set, then prompt the user to reload the page so that the Service Worker can take
// control. Until that happens, the Service Worker's message handler won't be used.
document.querySelector('#status').textContent =
'Please reload this page to allow the service worker to take control.';
}
}).catch(function(error) {
// Something went wrong during registration. The service-worker.js file
// might be unavailable or contain a syntax error.
document.querySelector('#status').textContent = error;
});
} else {
// The current browser doesn't support Service Workers.
var aElement = document.createElement('a');
aElement.href = 'http://www.chromium.org/blink/serviceworker/service-worker-faq';
aElement.textContent = 'Service Workers are not supported in the current browser.';
document.querySelector('#status').appendChild(aElement);
}
//jio = jIO.createJIO({type: "indexeddb", database: "cribjs"});
jio = jIO.createJIO({type:"dav", url: "https://cedriclendav.node.vifib.com/toto", basic_login: btoa("cedriclen:foo")})
function setStatus(statusMessage) {
document.querySelector('#status').textContent = statusMessage;
}
function getExtension(url) {
var extension = url.split('.').pop();
if (extension === "/") {
return ".html";
}
return "." + extension;
}
function showCommands() {
document.querySelector('#add').addEventListener('click', function() {
var url = document.querySelector('#url').value;
sendMessage({
command: 'add',
url: url,
information: new Blob([document.querySelector('#information').value], {
type: document.querySelector('#mimetype').value
/*jslint nomen: true, indent: 2, maxerr: 3 */
/*global window, rJS */
(function (window, rJS) {
"use strict";
function loadCribJS (gadget, event) {
var jio_cribjs_gadget,
jio_configurator_gadget;
return RSVP.Queue()
.push(function() {
return RSVP.all([
gadget.getDeclaredGadget("jio_cribjs"),
gadget.getDeclaredGadget("jio_configurator")]);
})
.push(function(gadget_list) {
jio_cribjs_gadget = gadget_list[0];
jio_configurator_gadget = gadget_list[1];
return jio_configurator_gadget.getContent();
})
.push(function (jio_string) {
return jio_cribjs_gadget.load({
path: document.location.origin,
jio_config: JSON.parse(jio_string),
application_id: "cribjs"
})
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Added to cache: ' + url + ' at ' + Date());
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#delete').addEventListener('click', function() {
sendMessage({
command: 'delete',
url: document.querySelector('#url').value
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Deleted from cache.');
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#list-contents').addEventListener('click', function() {
sendMessage({
command: 'keys'
}).then(function(data) {
var contentsElement = document.querySelector('#contents');
// Clear out the existing items from the list.
while (contentsElement.firstChild) {
contentsElement.removeChild(contentsElement.firstChild);
}
// Add each cached URL to the list, one by one.
data.urls.forEach(function(url) {
var liElement = document.createElement('li'),
aElement = document.createElement('a');
aElement.setAttribute('href', url);
aElement.textContent = url;
liElement.innerHTML = aElement.outerHTML;
contentsElement.appendChild(liElement);
});
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#get').addEventListener('click', function() {
console.log("bar");
getContent(document.querySelector('#url').value,
function() {
document.querySelector('#mimetype').value = this.getResponseHeader("content-type");
document.querySelector('#information').value = this.responseText;
}
);
})
})
.push(function (url_list) {
console.log(url_list);
document.location = document.location.origin + "/crib-editor/";
})
}
document.querySelector('#save-path').value = document.location.origin;
document.querySelector('#save-contents').addEventListener('click', function() {
console.log('asking for the keys');
var path_to_save, path_to_save_length, application_id;
path_to_save = document.querySelector('#save-path').value
application_id = document.querySelector('#save-id').value
path_to_save_length = path_to_save.length
sendMessage({
command: 'keys'
}).then(function(data) {
// Add each cached URL to the list, one by one.
console.log("foo");
return new RSVP.Queue()
.push(function() {
console.log("put");
return RSVP.all(
[jio.put("/" + application_id + ".attachment/", {
// url: path_to_save
}),
jio.putAttachment("/", application_id, new Blob([JSON.stringify({url: path_to_save})],{type: "application/json"}))
]);
})
.push(function() {
var promise_list = [],
i, i_len, url;
for (i = 0, i_len = data.urls.length; i < i_len; i += 1) {
url = new String(data.urls[i]);
if (url.indexOf(path_to_save) === 0) {
promise_list.push(jIO.util.ajax({
'url': url,
dataType: "blob"
}));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, response, extension;
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
response = response_list[i];
url = response.target.responseURL.substr(path_to_save_length)
extension = getExtension(url)
console.log("Pushing attachment " + url + " to " + path_to_save);
promise_list.push(
jio.putAttachment("/" + application_id + ".attachment/", btoa(url) + extension,
response.target.response
)
);
};
return RSVP.all(promise_list)
})
.push(undefined, function(error) {
console.log(error);
});
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
rJS(window)
document.querySelector('#load-path').value = document.location.origin;
document.querySelector('#load-contents').addEventListener('click', function() {
console.log('asking for the keys');
var url_list = [],
application_id;
application_id = document.querySelector('#load-id').value
return new RSVP.Queue()
.push(function() {
console.log("put");
return jio.allAttachments("/" + application_id + ".attachment/")
})
.push(function(response) {
var promise_list = [],
key, extension;
for (key in response) {
if (response.hasOwnProperty(key)) {
extension = getExtension(key);
console.log(key);
url_list.push(atob(key.substr(0, key.length - extension.length)))
promise_list.push(jio.getAttachment("/" + application_id + ".attachment/", key));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, index, response, location, location_len;
location = document.location.origin;
location_len = location.length
console.log(url_list);
console.log(response_list);
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
url = url_list[i]
index = url.indexOf(location);
if (index != -1)
url = url.substr(index + location_len);
sendMessage({
command: 'add',
url: document.querySelector('#load-path').value + url,
information: response_list[i]
})
}
})
.push(undefined, function(error) {
console.log(error);
});
});
document.querySelector('#mass-remove').addEventListener('click', function() {
var url_list = document.querySelector('#mass-remove-list').value.match(/[^\r\n]+/g),
url_list_length = url_list.length,
i;
console.log(url_list)
for (i = 0; i < url_list_length; i += 1) {
console.log(url_list[i])
sendMessage({
command: 'delete',
url: url_list[i],
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Deleted from cache.');
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
.declareMethod('render', function (options) {
var gadget = this,
jio_configuration_string = "";
if (localStorage.hasOwnProperty("crib_js_loader_jio")) {
jio_configuration_string = localStorage.getItem("crib_js_loader_jio");
} else {
jio_configuration_string = '{"type": "dav", "url": "https://cedriclendav.node.vifib.com/public/cribjs-storage/"}';
}
});
document.querySelector('#commands').style.display = 'block';
}
return RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget('jio_configurator');
})
.push(function (jio_configurator_gadget) {
return jio_configurator_gadget.render({value: jio_configuration_string});
})
.push(function () {
return loopEventListener(
gadget.props.element.querySelector("form.loading"),
'submit',
false,
function (event) {loadCribJS(gadget, event)}
);
})
.fail(function(e){console.log(e)})
})
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function getContent(url, reqListener) {
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.open("get", url, true);
oReq.send();
}
\ No newline at end of file
}(window, rJS));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Service worker demo</title>
<script src="lib/sha256.js"></script>
<script src="lib/sha256.amd.js"></script>
<script src="lib/rsvp.js"></script>
<script src="lib/jio-latest.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<h1>Free Web Initiative: Start your Website</h1>
<p>You can start building this website by editing the content of the input here:</p>
<ul>
<li>First field: the path</li>
<li>First field: mimetype</li>
<li>First field: the content</li>
</ul>
<p>Have fun building the web :) <a href="todo.html">TODO</a></p>
<div class="output">
<div id="status"></div>
<div id="commands" style="display: none">
<h3>Edit</h3>
<div>
<label for="url">Resource URL:</label>
<input id="url" type="text" size="50" value="test.html">
<button id="get">Get from Cache</button>
<input id="mimetype" type="text" size="50" value="text/html">
<div>
<textarea id="information" cols="35" wrap="soft"></textarea>
</div>
<div>
<button id="add">Add to Cache</button>
<button id="delete">Delete from Cache</button>
</div>
<h3>Save</h3>
<div>
<label for="save-path">Save:</label>
<input id="save-path" type="text" size="30" value="">
<label for="save-id"> to:</label>
<input id="save-id" type="text" size="30" value="cribjs">
<button id="save-contents">Save Cache</button>
</div>
<h3>Load</h3>
<div>
<label for="load-id">Load :</label>
<input id="load-id" type="text" size="30" value="cribjs">
<label for="load-path"> to path:</label>
<input id="load-path" type="text" size="30" value="cribeditor/v1.1">
<button id="load-contents">Load Cache</button>
</div>
<h3>Mass removal</h3>
<div>
<textarea id="mass-remove-list" cols="35" wrap="soft"></textarea>
</div>
<div>
<button id="mass-remove">Mass remove from Cache</button>
</div>
<h3>List Cache content</h3>
<div>
<button id="list-contents">List Cache Contents</button>
</div>
<ul id="contents"></ul>
</div>
</div>
<script src="base.js"></script>
</body>
</html>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', {scope: './'}).then(function() {
// Registration was successful. Now, check to see whether the Service Worker is controlling the page.
if (navigator.serviceWorker.controller) {
// If .controller is set, then this page is being actively controlled by the Service Worker.
// Show the interface for sending messages to the service worker.
showCommands();
} else {
// If .controller isn't set, then prompt the user to reload the page so that the Service Worker can take
// control. Until that happens, the Service Worker's message handler won't be used.
document.querySelector('#status').textContent =
'Please reload this page to allow the service worker to take control.';
}
}).catch(function(error) {
// Something went wrong during registration. The service-worker.js file
// might be unavailable or contain a syntax error.
document.querySelector('#status').textContent = error;
});
} else {
// The current browser doesn't support Service Workers.
var aElement = document.createElement('a');
aElement.href = 'http://www.chromium.org/blink/serviceworker/service-worker-faq';
aElement.textContent = 'Service Workers are not supported in the current browser.';
document.querySelector('#status').appendChild(aElement);
}
//jio = jIO.createJIO({type: "indexeddb", database: "cribjs"});
jio = jIO.createJIO({type:"dav", url: "https://cedriclendav.node.vifib.com/toto", basic_login: btoa("cedriclen:foo")})
function setStatus(statusMessage) {
document.querySelector('#status').textContent = statusMessage;
}
function getExtension(url) {
var extension = url.split('.').pop();
if (extension === "/") {
return ".html";
}
return "." + extension;
}
function showCommands() {
document.querySelector('#add').addEventListener('click', function() {
var url = document.querySelector('#url').value;
sendMessage({
command: 'add',
url: url,
information: new Blob([document.querySelector('#information').value], {
type: document.querySelector('#mimetype').value
})
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Added to cache: ' + url + ' at ' + Date());
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#delete').addEventListener('click', function() {
sendMessage({
command: 'delete',
url: document.querySelector('#url').value
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Deleted from cache.');
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#list-contents').addEventListener('click', function() {
sendMessage({
command: 'keys'
}).then(function(data) {
var contentsElement = document.querySelector('#contents');
// Clear out the existing items from the list.
while (contentsElement.firstChild) {
contentsElement.removeChild(contentsElement.firstChild);
}
// Add each cached URL to the list, one by one.
data.urls.forEach(function(url) {
var liElement = document.createElement('li'),
aElement = document.createElement('a');
aElement.setAttribute('href', url);
aElement.textContent = url;
liElement.innerHTML = aElement.outerHTML;
contentsElement.appendChild(liElement);
});
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#get').addEventListener('click', function() {
console.log("bar");
getContent(document.querySelector('#url').value,
function() {
document.querySelector('#mimetype').value = this.getResponseHeader("content-type");
document.querySelector('#information').value = this.responseText;
}
);
})
document.querySelector('#save-path').value = document.location.origin;
document.querySelector('#save-contents').addEventListener('click', function() {
console.log('asking for the keys');
var path_to_save, path_to_save_length, application_id;
path_to_save = document.querySelector('#save-path').value
application_id = document.querySelector('#save-id').value
path_to_save_length = path_to_save.length
sendMessage({
command: 'keys'
}).then(function(data) {
// Add each cached URL to the list, one by one.
console.log("foo");
return new RSVP.Queue()
.push(function() {
console.log("put");
return RSVP.all(
[jio.put("/" + application_id + ".attachment/", {
// url: path_to_save
}),
jio.putAttachment("/", application_id, new Blob([JSON.stringify({url: path_to_save})],{type: "application/json"}))
]);
})
.push(function() {
var promise_list = [],
i, i_len, url;
for (i = 0, i_len = data.urls.length; i < i_len; i += 1) {
url = new String(data.urls[i]);
if (url.indexOf(path_to_save) === 0) {
promise_list.push(jIO.util.ajax({
'url': url,
dataType: "blob"
}));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, response, extension;
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
response = response_list[i];
url = response.target.responseURL.substr(path_to_save_length)
extension = getExtension(url)
console.log("Pushing attachment " + url + " to " + path_to_save);
promise_list.push(
jio.putAttachment("/" + application_id + ".attachment/", btoa(url) + extension,
response.target.response
)
);
};
return RSVP.all(promise_list)
})
.push(undefined, function(error) {
console.log(error);
});
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#load-path').value = document.location.origin;
document.querySelector('#load-contents').addEventListener('click', function() {
console.log('asking for the keys');
var url_list = [],
application_id;
application_id = document.querySelector('#load-id').value
return new RSVP.Queue()
.push(function() {
console.log("put");
return jio.allAttachments("/" + application_id + ".attachment/")
})
.push(function(response) {
var promise_list = [],
key, extension;
for (key in response) {
if (response.hasOwnProperty(key)) {
extension = getExtension(key);
console.log(key);
url_list.push(atob(key.substr(0, key.length - extension.length)))
promise_list.push(jio.getAttachment("/" + application_id + ".attachment/", key));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, index, response, location, location_len;
location = document.location.origin;
location_len = location.length
console.log(url_list);
console.log(response_list);
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
url = url_list[i]
index = url.indexOf(location);
if (index != -1)
url = url.substr(index + location_len);
sendMessage({
command: 'add',
url: document.querySelector('#load-path').value + url,
information: response_list[i]
})
}
})
.push(undefined, function(error) {
console.log(error);
});
});
document.querySelector('#mass-remove').addEventListener('click', function() {
var url_list = document.querySelector('#mass-remove-list').value.match(/[^\r\n]+/g),
url_list_length = url_list.length,
i;
console.log(url_list)
for (i = 0; i < url_list_length; i += 1) {
console.log(url_list[i])
sendMessage({
command: 'delete',
url: url_list[i],
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Deleted from cache.');
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
}
});
document.querySelector('#commands').style.display = 'block';
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function getContent(url, reqListener) {
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.open("get", url, true);
oReq.send();
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Crib SW interface Gadget</title>
<!-- renderjs -->
<script src="/lib/rsvp.js" type="text/javascript"></script>
<script src="/lib/renderjs.js" type="text/javascript"></script>
<!-- custom script -->
<script src="crib-sw-gadget.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP */
/*jslint indent: 2, maxerr: 3 */
(function(window, rJS, RSVP) {
"use strict";
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', {scope: './'})
}
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function(resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.addEventListener("load", function(e) {
var answer = {};
if (e.target.status >= 400) {
return reject(e);
}
answer.responseText = this.responseText;
answer.responseType = this.getResponseHeader("content-type");
answer.responseURL = param.url
resolve(answer);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
}
}
}
xhr.send();
}, function() {
xhr.abort();
});
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new RSVP.Promise(function(resolve, reject, notify) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
console.log(event);
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
return navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function setStatus(statusMessage) {
console.log(statusMessage);
}
rJS(window)
.ready(function(gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
if (navigator.serviceWorker.controller === null) {
document.location = document.location.origin;
}
})
.declareMethod('allDocs', function() {
return new RSVP.Queue()
.push(function() {
return sendMessage({
command: 'keys'
})
})
})
.declareMethod('get', function(url) {
return ajax({
url: url
})
})
.declareMethod('put', function(url, parameter) {
return new RSVP.Queue()
.push(function() {
if (parameter.blob !== undefined) {
return sendMessage({
command: 'add',
url: url,
information: parameter.blob
})
}
return sendMessage({
command: 'add',
url: url,
information: new Blob([parameter.content], {
type: parameter.type,
})
})
}).push(function() {
// If the promise resolves, just display a success message.
console.log("Done adding "+ url);
return 'Added to cache: ' + url + ' at ' + Date();
}).fail(setStatus);
})
.declareMethod('remove', function(url) {
return new RSVP.Queue()
.push(function() {
return sendMessage({
command: 'delete',
url: url
})
})
});
}(window, rJS, RSVP));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="gadget_cribjs_loader.js"></script>
</head>
<body>
<h1>CribJS Loader</h1>
<div data-gadget-url="/gadget/gadget_jio_cribjs.html"
data-gadget-scope="jio_cribjs"
data-gadget-sandbox="public"></div>
<div data-gadget-url="/gadget/gadget_jio_configurator.html"
data-gadget-scope="jio_configurator"
data-gadget-sandbox="public"></div>
<form>
<button type="submit">Load</button>
</form>
<ul>
</ul>
</body>
</html>
\ No newline at end of file
/*jslint nomen: true, indent: 2, maxerr: 3 */
/*global window, rJS */
(function (window, rJS) {
"use strict";
function loadCribJS (gadget, event) {
var jio_cribjs_gadget,
jio_configurator_gadget;
return RSVP.Queue()
.push(function() {
return RSPV.all([
gadget.getDeclaredGadget("jio_cribjs"),
gadget.getDeclaredGadget("jio_configurator")]);
})
.push(function(gadget_list) {
jio_cribjs_gadget = gadget_list[0];
jio_configurator_gadget = gadget_list[1];
return jio_configurator_gadget.getContent();
})
.push(function (jio_string) {
return jio_cribjs_gadget.load({
path: document.location.origin,
jio_config: JSON.parse(jio_string),
application_id: "cribjs"
})
})
.push(function (url_list) {
console.log(url_list);
document.location = document.location.origin + "/crib-editor/";
})
}
rJS(window)
.declareMethod('render', function (options) {
var gadget = this,
jio_configuration_string = "";
console.log("rendering");
if (localStorage.hasOwnProperty("crib_js_loader_jio")) {
jio_configuration_string = localStorage.getItem("crib_js_loader_jio");
} else {
jio_configuration_string = '{"type": "dav", "url": "https://cedriclendav.node.vifib.com/public/cribjs-storage/"}';
}
return RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget('jio_configurator');
})
.push(function (jio_configurator_gadget) {
console.log(jio_configuration_string);
return jio_configurator_gadget.render({value: jio_configuration_string});
})
.push(function () {
return loopEventListener(
gadget.props.element.querySelector("form"),
'submit',
false,
function (event) {loadCribJS(gadget, event)}
);
})
.fail(function(e){console.log(e)})
})
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
});
}(window, rJS));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Jio Gadget</title>
<!-- renderjs -->
<script src="/lib/rsvp.js" type="text/javascript"></script>
<script src="/lib/renderjs.js" type="text/javascript"></script>
<script src="/lib/jio-latest.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_jio.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*global window, rJS, jIO */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, jIO) {
"use strict";
rJS(window)
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
})
.declareMethod('createJio', function (jio_options) {
this.state_parameter_dict.jio_storage = jIO.createJIO(jio_options);
})
.declareMethod('allDocs', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.allDocs.apply(storage, arguments);
})
.declareMethod('allAttachments', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.allAttachments.apply(storage, arguments);
})
.declareMethod('get', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.get.apply(storage, arguments);
})
.declareMethod('put', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.put.apply(storage, arguments);
})
.declareMethod('post', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.post.apply(storage, arguments);
})
.declareMethod('remove', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.remove.apply(storage, arguments);
})
.declareMethod('getAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.getAttachment.apply(storage, arguments);
})
.declareMethod('putAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.putAttachment.apply(storage, arguments);
})
.declareMethod('removeAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.removeAttachment.apply(storage, arguments);
})
.declareMethod('repair', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.repair.apply(storage, arguments);
});
}(window, rJS, jIO));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="gadget_jio_configurator.js"></script>
</head>
<body>
<form>
<textarea name="jio_json_configuration"></textarea>
</form>
</body>
</html>
\ No newline at end of file
/*jslint nomen: true, indent: 2, maxerr: 3 */
/*global window, rJS */
(function (window, rJS) {
"use strict";
rJS(window)
.declareAcquiredMethod("saveContent", "editor_saveContent")
.declareMethod('render', function (options) {
if (options && options.hasOwnProperty("value"))
this.props.element.querySelector("textarea").value = options.value || "";
})
.declareMethod('getContent', function () {
return this.props.element.querySelector("textarea").value;
})
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
});
}(window, rJS));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="gadget_jio_cribjs.js"></script>
</head>
<body>
<div data-gadget-url="/gadget/crib-sw-gadget.html"
data-gadget-scope="crib_sw_gadget"
data-gadget-sandbox="public"></div>
<div data-gadget-url="/gadget/gadget_jio.html"
data-gadget-scope="jio_gadget"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
/*globals window, document, RSVP, rJS,
location, console*/
/*jslint indent: 2, maxlen: 80*/
(function (window, document, RSVP, rJS, location, console) {
"use strict";
function getExtension(url) {
var extension = url.split('.').pop();
if (extension.endsWith('/')) {
return ".html";
}
return "." + extension;
}
rJS(window)
.ready(function (g) {
g.props = {};
})
.declareMethod('load', function (options) {
var path_to_load, path_to_load_length, application_id, crib_sw_gadget, jio_config,
jio_gadget, url_list = [], gadget = this;
path_to_load = options.path;
application_id = options.application_id;
jio_config = options.jio_config;
path_to_load_length = path_to_load.length;
return new RSVP.Queue()
.push(function () {
return RSVP.all([
gadget.getDeclaredGadget('crib_sw_gadget'),
gadget.getDeclaredGadget('jio_gadget')]);
})
.push(function (gadget_list) {
crib_sw_gadget = gadget_list[0];
jio_gadget = gadget_list[1];
return jio_gadget.createJio(jio_config);
})
.push(function () {
return jio_gadget.allAttachments("/" + application_id + ".attachment/");
})
.push(function(response) {
var promise_list = [],
key, extension;
for (key in response) {
if (response.hasOwnProperty(key)) {
extension = getExtension(key);
console.log(key);
url_list.push(atob(key.substr(0, key.length - extension.length)))
promise_list.push(jio_gadget.getAttachment("/" + application_id + ".attachment/", key));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, index, response, location, location_len;
location = document.location.origin;
location_len = location.length
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
url = url_list[i]
index = url.indexOf(location);
if (index != -1)
url = url.substr(index + location_len);
console.log(path_to_load + url);
promise_list.push(
crib_sw_gadget.put(path_to_load + url, {blob: response_list[i]})
)
}
return RSVP.all(promise_list);
})
.push(function() {
return url_list
});
})
.declareMethod('save', function (options) {
var path_to_save, path_to_save_length, application_id, crib_sw_gadget, jio_config,
jio_gadget, url_list = [], saved_url_list = [], gadget = this;
path_to_save = options.path;
application_id = options.application_id;
jio_config = options.jio_config;
path_to_save_length = path_to_save.length;
return new RSVP.Queue()
.push(function () {
return RSVP.all([
gadget.getDeclaredGadget('crib_sw_gadget'),
gadget.getDeclaredGadget('jio_gadget')]);
})
.push(function (gadget_list) {
crib_sw_gadget = gadget_list[0];
jio_gadget = gadget_list[1];
return jio_gadget.createJio(jio_config);
})
.push(function (){
return crib_sw_gadget.allDocs();
})
.push(function(data) {
url_list = data.urls;
// This is buggy, it fails if the document already exists with dav storage
// XX Should be able to add some metadata here such as url, contributor, version ...
return RSVP.all(
[jio_gadget.put("/" + application_id + ".attachment/", {
}),
jio_gadget.putAttachment("/", application_id, new Blob([JSON.stringify({url: path_to_save})],{type: "application/json"}))
]);
})
.push(function() {
var promise_list = [],
i, i_len, url;
for (i = 0, i_len = url_list.length; i < i_len; i += 1) {
url = new String(url_list[i]);
if (url.indexOf(path_to_save) === 0) {
saved_url_list.push({url: url});
promise_list.push(jIO.util.ajax({
url: url,
dataType: "blob"
}));
};
};
return RSVP.all(promise_list);
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, response, extension;
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
response = response_list[i];
url = response.target.responseURL.substr(path_to_save_length);
// This is tricky here, we use the extension for file storages in order to get the correct blob
// It should be fixed
saved_url_list[i].target = url;
extension = getExtension(url);
saved_url_list[i].target = extension;
console.log("Will push attachment " + saved_url_list[i].url + " to " + url);
promise_list.push(
jio_gadget.putAttachment("/" + application_id + ".attachment/", btoa(url) + extension,
response.target.response
)
);
};
return RSVP.all(promise_list);
})
.push(function() {
return saved_url_list;
})
.fail(function(error) {
gadget.props.element.querySelector(".crib-save-to-jio-status").textContent = error;
console.log(error);
});
})
}(window, document, RSVP, rJS, location, console));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
......@@ -8,73 +5,24 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Service worker demo</title>
<script src="lib/sha256.js"></script>
<script src="lib/sha256.amd.js"></script>
<script src="lib/rsvp.js"></script>
<script src="lib/jio-latest.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="app.js"></script>
</head>
<body>
<h1>Free Web Initiative: Start your Website</h1>
<p>You can start building this website by editing the content of the input here:</p>
<ul>
<li>First field: the path</li>
<li>First field: mimetype</li>
<li>First field: the content</li>
</ul>
<p>Have fun building the web :) <a href="todo.html">TODO</a></p>
<div class="output">
<div id="status"></div>
<div id="commands" style="display: none">
<h3>Edit</h3>
<div>
<label for="url">Resource URL:</label>
<input id="url" type="text" size="50" value="test.html">
<button id="get">Get from Cache</button>
<input id="mimetype" type="text" size="50" value="text/html">
<div>
<textarea id="information" cols="35" wrap="soft"></textarea>
</div>
<div>
<button id="add">Add to Cache</button>
<button id="delete">Delete from Cache</button>
</div>
<h3>Save</h3>
<div>
<label for="save-path">Save:</label>
<input id="save-path" type="text" size="30" value="">
<label for="save-id"> to:</label>
<input id="save-id" type="text" size="30" value="cribjs">
<button id="save-contents">Save Cache</button>
</div>
<h3>Load</h3>
<div>
<label for="load-id">Load :</label>
<input id="load-id" type="text" size="30" value="cribjs">
<label for="load-path"> to path:</label>
<input id="load-path" type="text" size="30" value="cribeditor/v1.1">
<button id="load-contents">Load Cache</button>
</div>
<h3>Mass removal</h3>
<div>
<textarea id="mass-remove-list" cols="35" wrap="soft"></textarea>
</div>
<div>
<button id="mass-remove">Mass remove from Cache</button>
</div>
<h3>List Cache content</h3>
<div>
<button id="list-contents">List Cache Contents</button>
</div>
<ul id="contents"></ul>
</div>
</div>
<script src="app.js"></script>
<h1>CribJS Loader</h1>
<p>You can load your crib from here or start with the default one</p>
<div data-gadget-url="/gadget/gadget_jio_cribjs.html"
data-gadget-scope="jio_cribjs"
data-gadget-sandbox="public"></div>
<div data-gadget-url="/gadget/gadget_jio_configurator.html"
data-gadget-scope="jio_configurator"
data-gadget-sandbox="public"></div>
<form class="loading">
<button type="submit">Load</button>
</form>
<p>You can check where it started, and start a crib your own way from the beginning: <a href="jungle.html">The Jungle :)</a>. If you want to play it for real, only use what you are doing to develop.</p>
<p>A simple editor can be found <a href="base.html">here</a></p>
</body>
</html>
/*
* js_channel is a very lightweight abstraction on top of
* postMessage which defines message formats and semantics
* to support interactions more rich than just message passing
* js_channel supports:
* + query/response - traditional rpc
* + query/update/response - incremental async return of results
* to a query
* + notifications - fire and forget
* + error handling
*
* js_channel is based heavily on json-rpc, but is focused at the
* problem of inter-iframe RPC.
*
* Message types:
* There are 5 types of messages that can flow over this channel,
* and you may determine what type of message an object is by
* examining its parameters:
* 1. Requests
* + integer id
* + string method
* + (optional) any params
* 2. Callback Invocations (or just "Callbacks")
* + integer id
* + string callback
* + (optional) params
* 3. Error Responses (or just "Errors)
* + integer id
* + string error
* + (optional) string message
* 4. Responses
* + integer id
* + (optional) any result
* 5. Notifications
* + string method
* + (optional) any params
*/
;var Channel = (function() {
"use strict";
// current transaction id, start out at a random *odd* number between 1 and a million
// There is one current transaction counter id per page, and it's shared between
// channel instances. That means of all messages posted from a single javascript
// evaluation context, we'll never have two with the same id.
var s_curTranId = Math.floor(Math.random()*1000001);
// no two bound channels in the same javascript evaluation context may have the same origin, scope, and window.
// futher if two bound channels have the same window and scope, they may not have *overlapping* origins
// (either one or both support '*'). This restriction allows a single onMessage handler to efficiently
// route messages based on origin and scope. The s_boundChans maps origins to scopes, to message
// handlers. Request and Notification messages are routed using this table.
// Finally, channels are inserted into this table when built, and removed when destroyed.
var s_boundChans = { };
// add a channel to s_boundChans, throwing if a dup exists
function s_addBoundChan(win, origin, scope, handler) {
function hasWin(arr) {
for (var i = 0; i < arr.length; i++) if (arr[i].win === win) return true;
return false;
}
// does she exist?
var exists = false;
if (origin === '*') {
// we must check all other origins, sadly.
for (var k in s_boundChans) {
if (!s_boundChans.hasOwnProperty(k)) continue;
if (k === '*') continue;
if (typeof s_boundChans[k][scope] === 'object') {
exists = hasWin(s_boundChans[k][scope]);
if (exists) break;
}
}
} else {
// we must check only '*'
if ((s_boundChans['*'] && s_boundChans['*'][scope])) {
exists = hasWin(s_boundChans['*'][scope]);
}
if (!exists && s_boundChans[origin] && s_boundChans[origin][scope])
{
exists = hasWin(s_boundChans[origin][scope]);
}
}
if (exists) throw "A channel is already bound to the same window which overlaps with origin '"+ origin +"' and has scope '"+scope+"'";
if (typeof s_boundChans[origin] != 'object') s_boundChans[origin] = { };
if (typeof s_boundChans[origin][scope] != 'object') s_boundChans[origin][scope] = [ ];
s_boundChans[origin][scope].push({win: win, handler: handler});
}
function s_removeBoundChan(win, origin, scope) {
var arr = s_boundChans[origin][scope];
for (var i = 0; i < arr.length; i++) {
if (arr[i].win === win) {
arr.splice(i,1);
}
}
if (s_boundChans[origin][scope].length === 0) {
delete s_boundChans[origin][scope];
}
}
function s_isArray(obj) {
if (Array.isArray) return Array.isArray(obj);
else {
return (obj.constructor.toString().indexOf("Array") != -1);
}
}
// No two outstanding outbound messages may have the same id, period. Given that, a single table
// mapping "transaction ids" to message handlers, allows efficient routing of Callback, Error, and
// Response messages. Entries are added to this table when requests are sent, and removed when
// responses are received.
var s_transIds = { };
// class singleton onMessage handler
// this function is registered once and all incoming messages route through here. This
// arrangement allows certain efficiencies, message data is only parsed once and dispatch
// is more efficient, especially for large numbers of simultaneous channels.
var s_onMessage = function(e) {
try {
var m = JSON.parse(e.data);
if (typeof m !== 'object' || m === null) throw "malformed";
} catch(e) {
// just ignore any posted messages that do not consist of valid JSON
return;
}
var w = e.source;
var o = e.origin;
var s, i, meth;
if (typeof m.method === 'string') {
var ar = m.method.split('::');
if (ar.length == 2) {
s = ar[0];
meth = ar[1];
} else {
meth = m.method;
}
}
if (typeof m.id !== 'undefined') i = m.id;
// w is message source window
// o is message origin
// m is parsed message
// s is message scope
// i is message id (or undefined)
// meth is unscoped method name
// ^^ based on these factors we can route the message
// if it has a method it's either a notification or a request,
// route using s_boundChans
if (typeof meth === 'string') {
var delivered = false;
if (s_boundChans[o] && s_boundChans[o][s]) {
for (var j = 0; j < s_boundChans[o][s].length; j++) {
if (s_boundChans[o][s][j].win === w) {
s_boundChans[o][s][j].handler(o, meth, m);
delivered = true;
break;
}
}
}
if (!delivered && s_boundChans['*'] && s_boundChans['*'][s]) {
for (var j = 0; j < s_boundChans['*'][s].length; j++) {
if (s_boundChans['*'][s][j].win === w) {
s_boundChans['*'][s][j].handler(o, meth, m);
break;
}
}
}
}
// otherwise it must have an id (or be poorly formed
else if (typeof i != 'undefined') {
if (s_transIds[i]) s_transIds[i](o, meth, m);
}
};
// Setup postMessage event listeners
if (window.addEventListener) window.addEventListener('message', s_onMessage, false);
else if(window.attachEvent) window.attachEvent('onmessage', s_onMessage);
/* a messaging channel is constructed from a window and an origin.
* the channel will assert that all messages received over the
* channel match the origin
*
* Arguments to Channel.build(cfg):
*
* cfg.window - the remote window with which we'll communicate
* cfg.origin - the expected origin of the remote window, may be '*'
* which matches any origin
* cfg.scope - the 'scope' of messages. a scope string that is
* prepended to message names. local and remote endpoints
* of a single channel must agree upon scope. Scope may
* not contain double colons ('::').
* cfg.debugOutput - A boolean value. If true and window.console.log is
* a function, then debug strings will be emitted to that
* function.
* cfg.debugOutput - A boolean value. If true and window.console.log is
* a function, then debug strings will be emitted to that
* function.
* cfg.postMessageObserver - A function that will be passed two arguments,
* an origin and a message. It will be passed these immediately
* before messages are posted.
* cfg.gotMessageObserver - A function that will be passed two arguments,
* an origin and a message. It will be passed these arguments
* immediately after they pass scope and origin checks, but before
* they are processed.
* cfg.onReady - A function that will be invoked when a channel becomes "ready",
* this occurs once both sides of the channel have been
* instantiated and an application level handshake is exchanged.
* the onReady function will be passed a single argument which is
* the channel object that was returned from build().
*/
return {
build: function(cfg) {
var debug = function(m) {
if (cfg.debugOutput && window.console && window.console.log) {
// try to stringify, if it doesn't work we'll let javascript's built in toString do its magic
try { if (typeof m !== 'string') m = JSON.stringify(m); } catch(e) { }
console.log("["+chanId+"] " + m);
}
};
/* browser capabilities check */
if (!window.postMessage) throw("jschannel cannot run this browser, no postMessage");
if (!window.JSON || !window.JSON.stringify || ! window.JSON.parse) {
throw("jschannel cannot run this browser, no JSON parsing/serialization");
}
/* basic argument validation */
if (typeof cfg != 'object') throw("Channel build invoked without a proper object argument");
if (!cfg.window || !cfg.window.postMessage) throw("Channel.build() called without a valid window argument");
/* we'd have to do a little more work to be able to run multiple channels that intercommunicate the same
* window... Not sure if we care to support that */
if (window === cfg.window) throw("target window is same as present window -- not allowed");
// let's require that the client specify an origin. if we just assume '*' we'll be
// propagating unsafe practices. that would be lame.
var validOrigin = false;
if (typeof cfg.origin === 'string') {
var oMatch;
if (cfg.origin === "*") validOrigin = true;
// allow valid domains under http and https. Also, trim paths off otherwise valid origins.
else if (null !== (oMatch = cfg.origin.match(/^https?:\/\/(?:[-a-zA-Z0-9_\.])+(?::\d+)?/))) {
cfg.origin = oMatch[0].toLowerCase();
validOrigin = true;
}
}
if (!validOrigin) throw ("Channel.build() called with an invalid origin");
if (typeof cfg.scope !== 'undefined') {
if (typeof cfg.scope !== 'string') throw 'scope, when specified, must be a string';
if (cfg.scope.split('::').length > 1) throw "scope may not contain double colons: '::'";
}
/* private variables */
// generate a random and psuedo unique id for this channel
var chanId = (function () {
var text = "";
var alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for(var i=0; i < 5; i++) text += alpha.charAt(Math.floor(Math.random() * alpha.length));
return text;
})();
// registrations: mapping method names to call objects
var regTbl = { };
// current oustanding sent requests
var outTbl = { };
// current oustanding received requests
var inTbl = { };
// are we ready yet? when false we will block outbound messages.
var ready = false;
var pendingQueue = [ ];
var createTransaction = function(id,origin,callbacks) {
var shouldDelayReturn = false;
var completed = false;
return {
origin: origin,
invoke: function(cbName, v) {
// verify in table
if (!inTbl[id]) throw "attempting to invoke a callback of a nonexistent transaction: " + id;
// verify that the callback name is valid
var valid = false;
for (var i = 0; i < callbacks.length; i++) if (cbName === callbacks[i]) { valid = true; break; }
if (!valid) throw "request supports no such callback '" + cbName + "'";
// send callback invocation
postMessage({ id: id, callback: cbName, params: v});
},
error: function(error, message) {
completed = true;
// verify in table
if (!inTbl[id]) throw "error called for nonexistent message: " + id;
// remove transaction from table
delete inTbl[id];
// send error
postMessage({ id: id, error: error, message: message });
},
complete: function(v) {
completed = true;
// verify in table
if (!inTbl[id]) throw "complete called for nonexistent message: " + id;
// remove transaction from table
delete inTbl[id];
// send complete
postMessage({ id: id, result: v });
},
delayReturn: function(delay) {
if (typeof delay === 'boolean') {
shouldDelayReturn = (delay === true);
}
return shouldDelayReturn;
},
completed: function() {
return completed;
}
};
};
var setTransactionTimeout = function(transId, timeout, method) {
return window.setTimeout(function() {
if (outTbl[transId]) {
// XXX: what if client code raises an exception here?
var msg = "timeout (" + timeout + "ms) exceeded on method '" + method + "'";
(1,outTbl[transId].error)("timeout_error", msg);
delete outTbl[transId];
delete s_transIds[transId];
}
}, timeout);
};
var onMessage = function(origin, method, m) {
// if an observer was specified at allocation time, invoke it
if (typeof cfg.gotMessageObserver === 'function') {
// pass observer a clone of the object so that our
// manipulations are not visible (i.e. method unscoping).
// This is not particularly efficient, but then we expect
// that message observers are primarily for debugging anyway.
try {
cfg.gotMessageObserver(origin, m);
} catch (e) {
debug("gotMessageObserver() raised an exception: " + e.toString());
}
}
// now, what type of message is this?
if (m.id && method) {
// a request! do we have a registered handler for this request?
if (regTbl[method]) {
var trans = createTransaction(m.id, origin, m.callbacks ? m.callbacks : [ ]);
inTbl[m.id] = { };
try {
// callback handling. we'll magically create functions inside the parameter list for each
// callback
if (m.callbacks && s_isArray(m.callbacks) && m.callbacks.length > 0) {
for (var i = 0; i < m.callbacks.length; i++) {
var path = m.callbacks[i];
var obj = m.params;
var pathItems = path.split('/');
for (var j = 0; j < pathItems.length - 1; j++) {
var cp = pathItems[j];
if (typeof obj[cp] !== 'object') obj[cp] = { };
obj = obj[cp];
}
obj[pathItems[pathItems.length - 1]] = (function() {
var cbName = path;
return function(params) {
return trans.invoke(cbName, params);
};
})();
}
}
var resp = regTbl[method](trans, m.params);
if (!trans.delayReturn() && !trans.completed()) trans.complete(resp);
} catch(e) {
// automagic handling of exceptions:
var error = "runtime_error";
var message = null;
// * if it's a string then it gets an error code of 'runtime_error' and string is the message
if (typeof e === 'string') {
message = e;
} else if (typeof e === 'object') {
// either an array or an object
// * if it's an array of length two, then array[0] is the code, array[1] is the error message
if (e && s_isArray(e) && e.length == 2) {
error = e[0];
message = e[1];
}
// * if it's an object then we'll look form error and message parameters
else if (typeof e.error === 'string') {
error = e.error;
if (!e.message) message = "";
else if (typeof e.message === 'string') message = e.message;
else e = e.message; // let the stringify/toString message give us a reasonable verbose error string
}
}
// message is *still* null, let's try harder
if (message === null) {
try {
message = JSON.stringify(e);
/* On MSIE8, this can result in 'out of memory', which
* leaves message undefined. */
if (typeof(message) == 'undefined')
message = e.toString();
} catch (e2) {
message = e.toString();
}
}
trans.error(error,message);
}
}
} else if (m.id && m.callback) {
if (!outTbl[m.id] ||!outTbl[m.id].callbacks || !outTbl[m.id].callbacks[m.callback])
{
debug("ignoring invalid callback, id:"+m.id+ " (" + m.callback +")");
} else {
// XXX: what if client code raises an exception here?
outTbl[m.id].callbacks[m.callback](m.params);
}
} else if (m.id) {
if (!outTbl[m.id]) {
debug("ignoring invalid response: " + m.id);
} else {
// XXX: what if client code raises an exception here?
if (m.error) {
(1,outTbl[m.id].error)(m.error, m.message);
} else {
if (m.result !== undefined) (1,outTbl[m.id].success)(m.result);
else (1,outTbl[m.id].success)();
}
delete outTbl[m.id];
delete s_transIds[m.id];
}
} else if (method) {
// tis a notification.
if (regTbl[method]) {
// yep, there's a handler for that.
// transaction has only origin for notifications.
regTbl[method]({ origin: origin }, m.params);
// if the client throws, we'll just let it bubble out
// what can we do? Also, here we'll ignore return values
}
}
};
// now register our bound channel for msg routing
s_addBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage);
// scope method names based on cfg.scope specified when the Channel was instantiated
var scopeMethod = function(m) {
if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::");
return m;
};
// a small wrapper around postmessage whose primary function is to handle the
// case that clients start sending messages before the other end is "ready"
var postMessage = function(msg, force) {
if (!msg) throw "postMessage called with null message";
// delay posting if we're not ready yet.
var verb = (ready ? "post " : "queue ");
debug(verb + " message: " + JSON.stringify(msg));
if (!force && !ready) {
pendingQueue.push(msg);
} else {
if (typeof cfg.postMessageObserver === 'function') {
try {
cfg.postMessageObserver(cfg.origin, msg);
} catch (e) {
debug("postMessageObserver() raised an exception: " + e.toString());
}
}
cfg.window.postMessage(JSON.stringify(msg), cfg.origin);
}
};
var onReady = function(trans, type) {
debug('ready msg received');
if (ready) throw "received ready message while in ready state. help!";
if (type === 'ping') {
chanId += '-R';
} else {
chanId += '-L';
}
obj.unbind('__ready'); // now this handler isn't needed any more.
ready = true;
debug('ready msg accepted.');
if (type === 'ping') {
obj.notify({ method: '__ready', params: 'pong' });
}
// flush queue
while (pendingQueue.length) {
postMessage(pendingQueue.pop());
}
// invoke onReady observer if provided
if (typeof cfg.onReady === 'function') cfg.onReady(obj);
};
var obj = {
// tries to unbind a bound message handler. returns false if not possible
unbind: function (method) {
if (regTbl[method]) {
if (!(delete regTbl[method])) throw ("can't delete method: " + method);
return true;
}
return false;
},
bind: function (method, cb) {
if (!method || typeof method !== 'string') throw "'method' argument to bind must be string";
if (!cb || typeof cb !== 'function') throw "callback missing from bind params";
if (regTbl[method]) throw "method '"+method+"' is already bound!";
regTbl[method] = cb;
return this;
},
call: function(m) {
if (!m) throw 'missing arguments to call function';
if (!m.method || typeof m.method !== 'string') throw "'method' argument to call must be string";
if (!m.success || typeof m.success !== 'function') throw "'success' callback missing from call";
// now it's time to support the 'callback' feature of jschannel. We'll traverse the argument
// object and pick out all of the functions that were passed as arguments.
var callbacks = { };
var callbackNames = [ ];
var pruneFunctions = function (path, obj) {
if (typeof obj === 'object') {
for (var k in obj) {
if (!obj.hasOwnProperty(k)) continue;
var np = path + (path.length ? '/' : '') + k;
if (typeof obj[k] === 'function') {
callbacks[np] = obj[k];
callbackNames.push(np);
delete obj[k];
} else if (typeof obj[k] === 'object') {
pruneFunctions(np, obj[k]);
}
}
}
};
pruneFunctions("", m.params);
// build a 'request' message and send it
var msg = { id: s_curTranId, method: scopeMethod(m.method), params: m.params };
if (callbackNames.length) msg.callbacks = callbackNames;
if (m.timeout)
// XXX: This function returns a timeout ID, but we don't do anything with it.
// We might want to keep track of it so we can cancel it using clearTimeout()
// when the transaction completes.
setTransactionTimeout(s_curTranId, m.timeout, scopeMethod(m.method));
// insert into the transaction table
outTbl[s_curTranId] = { callbacks: callbacks, error: m.error, success: m.success };
s_transIds[s_curTranId] = onMessage;
// increment current id
s_curTranId++;
postMessage(msg);
},
notify: function(m) {
if (!m) throw 'missing arguments to notify function';
if (!m.method || typeof m.method !== 'string') throw "'method' argument to notify must be string";
// no need to go into any transaction table
postMessage({ method: scopeMethod(m.method), params: m.params });
},
destroy: function () {
s_removeBoundChan(cfg.window, cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''));
if (window.removeEventListener) window.removeEventListener('message', onMessage, false);
else if(window.detachEvent) window.detachEvent('onmessage', onMessage);
ready = false;
regTbl = { };
inTbl = { };
outTbl = { };
cfg.origin = null;
pendingQueue = [ ];
debug("channel destroyed");
chanId = "";
}
};
obj.bind('__ready', onReady);
setTimeout(function() {
postMessage({ method: scopeMethod('__ready'), params: "ping" }, true);
}, 0);
return obj;
}
};
})();
;/*
* DOMParser HTML extension
* 2012-09-04
*
* By Eli Grey, http://eligrey.com
* Public domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
/*! @source https://gist.github.com/1129031 */
(function (DOMParser) {
"use strict";
var DOMParser_proto = DOMParser.prototype,
real_parseFromString = DOMParser_proto.parseFromString;
// Firefox/Opera/IE throw errors on unsupported types
try {
// WebKit returns null on unsupported types
if ((new DOMParser()).parseFromString("", "text/html")) {
// text/html parsing is natively supported
return;
}
} catch (ignore) {}
DOMParser_proto.parseFromString = function (markup, type) {
var result, doc, doc_elt, first_elt;
if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
doc = document.implementation.createHTMLDocument("");
doc_elt = doc.documentElement;
doc_elt.innerHTML = markup;
first_elt = doc_elt.firstElementChild;
if (doc_elt.childElementCount === 1
&& first_elt.localName.toLowerCase() === "html") {
doc.replaceChild(first_elt, doc_elt);
}
result = doc;
} else {
result = real_parseFromString.apply(this, arguments);
}
return result;
};
}(DOMParser));
;// IE does not support have Document.prototype.contains.
if (typeof document.contains !== 'function') {
Document.prototype.contains = function(node) {
if (node === this || node.parentNode === this)
return true;
return this.documentElement.contains(node);
}
}
;/*! RenderJs */
/*global console*/
/*jslint nomen: true*/
function loopEventListener(target, type, useCapture, callback) {
"use strict";
//////////////////////////
// Infinite event listener (promise is never resolved)
// eventListener is removed when promise is cancelled/rejected
//////////////////////////
var handle_event_callback,
callback_promise;
function cancelResolver() {
if ((callback_promise !== undefined) &&
(typeof callback_promise.cancel === "function")) {
callback_promise.cancel();
}
}
function canceller() {
if (handle_event_callback !== undefined) {
target.removeEventListener(type, handle_event_callback, useCapture);
}
cancelResolver();
}
function itsANonResolvableTrap(resolve, reject) {
handle_event_callback = function (evt) {
evt.stopPropagation();
evt.preventDefault();
cancelResolver();
callback_promise = new RSVP.Queue()
.push(function () {
return callback(evt);
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
};
target.addEventListener(type, handle_event_callback, useCapture);
}
return new RSVP.Promise(itsANonResolvableTrap, canceller);
}
/*
* renderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation
*/
(function (document, window, RSVP, DOMParser, Channel, MutationObserver,
Node) {
"use strict";
var gadget_model_dict = {},
javascript_registration_dict = {},
stylesheet_registration_dict = {},
gadget_loading_klass,
loading_klass_promise,
renderJS,
Monitor;
/////////////////////////////////////////////////////////////////
// Helper functions
/////////////////////////////////////////////////////////////////
function listenHashChange(gadget) {
function extractHashAndDispatch(evt) {
var hash = (evt.newURL || window.location.toString()).split('#')[1],
subhashes,
subhash,
keyvalue,
index,
options = {};
if (hash === undefined) {
hash = "";
} else {
hash = hash.split('?')[0];
}
function optionalize(key, value, dict) {
var key_list = key.split("."),
kk,
i;
for (i = 0; i < key_list.length; i += 1) {
kk = key_list[i];
if (i === key_list.length - 1) {
dict[kk] = value;
} else {
if (!dict.hasOwnProperty(kk)) {
dict[kk] = {};
}
dict = dict[kk];
}
}
}
subhashes = hash.split('&');
for (index in subhashes) {
if (subhashes.hasOwnProperty(index)) {
subhash = subhashes[index];
if (subhash !== '') {
keyvalue = subhash.split('=');
if (keyvalue.length === 2) {
optionalize(decodeURIComponent(keyvalue[0]),
decodeURIComponent(keyvalue[1]),
options);
}
}
}
}
if (gadget.render !== undefined) {
return gadget.render(options);
}
}
var result = loopEventListener(window, 'hashchange', false,
extractHashAndDispatch),
event = document.createEvent("Event");
event.initEvent('hashchange', true, true);
event.newURL = window.location.toString();
window.dispatchEvent(event);
return result;
}
function removeHash(url) {
var index = url.indexOf('#');
if (index > 0) {
url = url.substring(0, index);
}
return url;
}
function letsCrash(e) {
if (e.constructor === XMLHttpRequest) {
e = {
readyState: e.readyState,
status: e.status,
statusText: e.statusText,
response_headers: e.getAllResponseHeaders()
};
}
if (e.constructor === Array ||
e.constructor === String ||
e.constructor === Object) {
try {
e = JSON.stringify(e);
} catch (ignore) {
}
}
document.getElementsByTagName('body')[0].textContent = e;
// XXX Do not crash the application if it fails
// Where to write the error?
/*global console*/
console.error(e.stack);
console.error(e);
}
/////////////////////////////////////////////////////////////////
// Service Monitor promise
/////////////////////////////////////////////////////////////////
function ResolvedMonitorError(message) {
this.name = "resolved";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
ResolvedMonitorError.prototype = new Error();
ResolvedMonitorError.prototype.constructor = ResolvedMonitorError;
Monitor = function () {
var monitor = this,
promise_list = [],
promise,
reject,
notify,
resolved;
if (!(this instanceof Monitor)) {
return new Monitor();
}
function canceller() {
var len = promise_list.length,
i;
for (i = 0; i < len; i += 1) {
promise_list[i].cancel();
}
// Clean it to speed up other canceller run
promise_list = [];
}
promise = new RSVP.Promise(function (done, fail, progress) {
reject = function (rejectedReason) {
if (resolved) {
return;
}
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
resolved = true;
canceller();
return fail(rejectedReason);
};
notify = progress;
}, canceller);
monitor.cancel = function () {
if (resolved) {
return;
}
resolved = true;
promise.cancel();
promise.fail(function (rejectedReason) {
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
});
};
monitor.then = function () {
return promise.then.apply(promise, arguments);
};
monitor.fail = function () {
return promise.fail.apply(promise, arguments);
};
monitor.monitor = function (promise_to_monitor) {
if (resolved) {
throw new ResolvedMonitorError();
}
var queue = new RSVP.Queue()
.push(function () {
return promise_to_monitor;
})
.push(function (fulfillmentValue) {
// Promise to monitor is fullfilled, remove it from the list
var len = promise_list.length,
sub_promise_to_monitor,
new_promise_list = [],
i;
for (i = 0; i < len; i += 1) {
sub_promise_to_monitor = promise_list[i];
if (!(sub_promise_to_monitor.isFulfilled ||
sub_promise_to_monitor.isRejected)) {
new_promise_list.push(sub_promise_to_monitor);
}
}
promise_list = new_promise_list;
}, function (rejectedReason) {
if (rejectedReason instanceof RSVP.CancellationError) {
if (!(promise_to_monitor.isFulfilled &&
promise_to_monitor.isRejected)) {
// The queue could be cancelled before the first push is run
promise_to_monitor.cancel();
}
}
reject(rejectedReason);
throw rejectedReason;
}, function (notificationValue) {
notify(notificationValue);
return notificationValue;
});
promise_list.push(queue);
return this;
};
};
Monitor.prototype = Object.create(RSVP.Promise.prototype);
Monitor.prototype.constructor = Monitor;
/////////////////////////////////////////////////////////////////
// RenderJSGadget
/////////////////////////////////////////////////////////////////
function RenderJSGadget() {
if (!(this instanceof RenderJSGadget)) {
return new RenderJSGadget();
}
}
RenderJSGadget.prototype.__title = "";
RenderJSGadget.prototype.__interface_list = [];
RenderJSGadget.prototype.__path = "";
RenderJSGadget.prototype.__html = "";
RenderJSGadget.prototype.__required_css_list = [];
RenderJSGadget.prototype.__required_js_list = [];
function createMonitor(g) {
if (g.__monitor !== undefined) {
g.__monitor.cancel();
}
g.__monitor = new Monitor();
g.__monitor.fail(function (error) {
if (!(error instanceof RSVP.CancellationError)) {
return g.aq_reportServiceError(error);
}
}).fail(function (error) {
// Crash the application if the acquisition generates an error.
return letsCrash(error);
});
}
function clearGadgetInternalParameters(g) {
g.__sub_gadget_dict = {};
createMonitor(g);
}
function loadSubGadgetDOMDeclaration(g) {
var element_list = g.__element.querySelectorAll('[data-gadget-scope]'),
element,
promise_list = [],
scope,
url,
sandbox,
i;
for (i = 0; i < element_list.length; i += 1) {
element = element_list[i];
scope = element.getAttribute("data-gadget-scope");
url = element.getAttribute("data-gadget-url");
sandbox = element.getAttribute("data-gadget-sandbox");
if ((scope !== null) && (url !== null)) {
promise_list.push(g.declareGadget(url, {
element: element,
scope: scope || undefined,
sandbox: sandbox || undefined
}));
}
}
return RSVP.all(promise_list);
}
RenderJSGadget.__ready_list = [clearGadgetInternalParameters,
loadSubGadgetDOMDeclaration];
RenderJSGadget.ready = function (callback) {
this.__ready_list.push(callback);
return this;
};
RenderJSGadget.__service_list = [];
RenderJSGadget.declareService = function (callback) {
this.__service_list.push(callback);
return this;
};
function startService(gadget) {
gadget.__monitor.monitor(new RSVP.Queue()
.push(function () {
var i,
service_list = gadget.constructor.__service_list;
for (i = 0; i < service_list.length; i += 1) {
gadget.__monitor.monitor(service_list[i].apply(gadget));
}
})
);
}
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareMethod
/////////////////////////////////////////////////////////////////
RenderJSGadget.declareMethod = function (name, callback) {
this.prototype[name] = function () {
var context = this,
argument_list = arguments;
return new RSVP.Queue()
.push(function () {
return callback.apply(context, argument_list);
});
};
// Allow chain
return this;
};
RenderJSGadget
.declareMethod('getInterfaceList', function () {
// Returns the list of gadget prototype
return this.__interface_list;
})
.declareMethod('getRequiredCSSList', function () {
// Returns a list of CSS required by the gadget
return this.__required_css_list;
})
.declareMethod('getRequiredJSList', function () {
// Returns a list of JS required by the gadget
return this.__required_js_list;
})
.declareMethod('getPath', function () {
// Returns the path of the code of a gadget
return this.__path;
})
.declareMethod('getTitle', function () {
// Returns the title of a gadget
return this.__title;
})
.declareMethod('getElement', function () {
// Returns the DOM Element of a gadget
if (this.__element === undefined) {
throw new Error("No element defined");
}
return this.__element;
});
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareAcquiredMethod
/////////////////////////////////////////////////////////////////
function acquire(child_gadget, method_name, argument_list) {
var gadget = this,
key,
gadget_scope;
for (key in gadget.__sub_gadget_dict) {
if (gadget.__sub_gadget_dict.hasOwnProperty(key)) {
if (gadget.__sub_gadget_dict[key] === child_gadget) {
gadget_scope = key;
}
}
}
return new RSVP.Queue()
.push(function () {
// Do not specify default __acquired_method_dict on prototype
// to prevent modifying this default value (with
// allowPublicAcquiredMethod for example)
var aq_dict = gadget.__acquired_method_dict || {};
if (aq_dict.hasOwnProperty(method_name)) {
return aq_dict[method_name].apply(gadget,
[argument_list, gadget_scope]);
}
throw new renderJS.AcquisitionError("aq_dynamic is not defined");
})
.push(undefined, function (error) {
if (error instanceof renderJS.AcquisitionError) {
return gadget.__aq_parent(method_name, argument_list);
}
throw error;
});
}
RenderJSGadget.declareAcquiredMethod =
function (name, method_name_to_acquire) {
this.prototype[name] = function () {
var argument_list = Array.prototype.slice.call(arguments, 0),
gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.__aq_parent(method_name_to_acquire, argument_list);
});
};
// Allow chain
return this;
};
RenderJSGadget.declareAcquiredMethod("aq_reportServiceError",
"reportServiceError");
RenderJSGadget.declareAcquiredMethod("aq_pleasePublishMyState",
"pleasePublishMyState");
/////////////////////////////////////////////////////////////////
// RenderJSGadget.allowPublicAcquisition
/////////////////////////////////////////////////////////////////
RenderJSGadget.allowPublicAcquisition =
function (method_name, callback) {
this.prototype.__acquired_method_dict[method_name] = callback;
// Allow chain
return this;
};
// Set aq_parent on gadget_instance which call acquire on parent_gadget
function setAqParent(gadget_instance, parent_gadget) {
gadget_instance.__aq_parent = function (method_name, argument_list) {
return acquire.apply(parent_gadget, [gadget_instance, method_name,
argument_list]);
};
}
function pleasePublishMyState(param_list, child_gadget_scope) {
var new_param = {},
key;
for (key in this.state_parameter_dict) {
if (this.state_parameter_dict.hasOwnProperty(key)) {
new_param[key] = this.state_parameter_dict[key];
}
}
if (child_gadget_scope === undefined) {
throw new Error("gadget scope is mandatory");
}
new_param[child_gadget_scope] = param_list[0];
param_list = [new_param];
return this.aq_pleasePublishMyState.apply(this, param_list);
}
/////////////////////////////////////////////////////////////////
// RenderJSEmbeddedGadget
/////////////////////////////////////////////////////////////////
// Class inheritance
function RenderJSEmbeddedGadget() {
if (!(this instanceof RenderJSEmbeddedGadget)) {
return new RenderJSEmbeddedGadget();
}
RenderJSGadget.call(this);
}
RenderJSEmbeddedGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSEmbeddedGadget.__service_list =
RenderJSGadget.__service_list.slice();
RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready;
RenderJSEmbeddedGadget.declareService =
RenderJSGadget.declareService;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
/////////////////////////////////////////////////////////////////
// privateDeclarePublicGadget
/////////////////////////////////////////////////////////////////
function privateDeclarePublicGadget(url, options, parent_gadget) {
var gadget_instance;
if (options.element === undefined) {
options.element = document.createElement("div");
}
function loadDependency(method, url) {
return function () {
return method(url);
};
}
return new RSVP.Queue()
.push(function () {
return renderJS.declareGadgetKlass(url);
})
// Get the gadget class and instanciate it
.push(function (Klass) {
var i,
template_node_list = Klass.__template_element.body.childNodes;
gadget_loading_klass = Klass;
gadget_instance = new Klass();
gadget_instance.__element = options.element;
for (i = 0; i < template_node_list.length; i += 1) {
gadget_instance.__element.appendChild(
template_node_list[i].cloneNode(true)
);
}
setAqParent(gadget_instance, parent_gadget);
// Load dependencies if needed
return RSVP.all([
gadget_instance.getRequiredJSList(),
gadget_instance.getRequiredCSSList()
]);
})
// Load all JS/CSS
.push(function (all_list) {
var q = new RSVP.Queue(),
i;
// Load JS
for (i = 0; i < all_list[0].length; i += 1) {
q.push(loadDependency(renderJS.declareJS, all_list[0][i]));
}
// Load CSS
for (i = 0; i < all_list[1].length; i += 1) {
q.push(loadDependency(renderJS.declareCSS, all_list[1][i]));
}
return q;
})
.push(function () {
return gadget_instance;
});
}
/////////////////////////////////////////////////////////////////
// RenderJSIframeGadget
/////////////////////////////////////////////////////////////////
function RenderJSIframeGadget() {
if (!(this instanceof RenderJSIframeGadget)) {
return new RenderJSIframeGadget();
}
RenderJSGadget.call(this);
}
RenderJSIframeGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSIframeGadget.ready =
RenderJSGadget.ready;
RenderJSIframeGadget.__service_list = RenderJSGadget.__service_list.slice();
RenderJSIframeGadget.declareService =
RenderJSGadget.declareService;
RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
/////////////////////////////////////////////////////////////////
// privateDeclareIframeGadget
/////////////////////////////////////////////////////////////////
function privateDeclareIframeGadget(url, options, parent_gadget) {
var gadget_instance,
iframe,
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
if (!document.contains(options.element)) {
throw new Error("The parent element is not attached to the DOM for " +
url);
}
gadget_instance = new RenderJSIframeGadget();
setAqParent(gadget_instance, parent_gadget);
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,
wait_promise = 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 new RSVP.Queue()
.push(function () {
return wait_promise;
});
};
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";
});
gadget_instance.__chan.bind("acquire", function (trans, params) {
gadget_instance.__aq_parent.apply(gadget_instance, params)
.then(function (g) {
trans.complete(g);
}).fail(function (e) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
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
.declareMethod('declareGadget', function (url, options) {
var queue,
parent_gadget = this,
local_loading_klass_promise,
previous_loading_klass_promise = loading_klass_promise;
if (options === undefined) {
options = {};
}
if (options.sandbox === undefined) {
options.sandbox = "public";
}
// transform url to absolute url if it is relative
url = renderJS.getAbsoluteURL(url, this.__path);
// Change the global variable to update the loading queue
loading_klass_promise = new RSVP.Queue()
// Wait for previous gadget loading to finish first
.push(function () {
return previous_loading_klass_promise;
})
.push(undefined, function () {
// Forget previous declareGadget error
return;
})
.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, parent_gadget);
})
// Set the HTML context
.push(function (gadget_instance) {
// Drop the current loading klass info used by selector
gadget_loading_klass = undefined;
return gadget_instance;
})
.push(undefined, function (e) {
// Drop the current loading klass info used by selector
// even in case of error
gadget_loading_klass = undefined;
throw e;
});
local_loading_klass_promise = loading_klass_promise;
queue = new RSVP.Queue()
.push(function () {
return local_loading_klass_promise;
})
// Set the HTML context
.push(function (gadget_instance) {
var i;
// Trigger calling of all ready callback
function ready_wrapper() {
return gadget_instance;
}
for (i = 0; i < gadget_instance.constructor.__ready_list.length;
i += 1) {
// Put a timeout?
queue.push(gadget_instance.constructor.__ready_list[i]);
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
}
// Store local reference to the gadget instance
if (options.scope !== undefined) {
parent_gadget.__sub_gadget_dict[options.scope] = gadget_instance;
gadget_instance.__element.setAttribute("data-gadget-scope",
options.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;
if (document.contains(gadget_instance.__element)) {
// Put a timeout
queue.push(startService);
}
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
return gadget_instance;
});
return queue;
})
.declareMethod('getDeclaredGadget', function (gadget_scope) {
if (!this.__sub_gadget_dict.hasOwnProperty(gadget_scope)) {
throw new Error("Gadget scope '" + gadget_scope + "' is not known.");
}
return this.__sub_gadget_dict[gadget_scope];
})
.declareMethod('dropGadget', function (gadget_scope) {
if (!this.__sub_gadget_dict.hasOwnProperty(gadget_scope)) {
throw new Error("Gadget scope '" + gadget_scope + "' is not known.");
}
// http://perfectionkills.com/understanding-delete/
delete this.__sub_gadget_dict[gadget_scope];
});
/////////////////////////////////////////////////////////////////
// renderJS selector
/////////////////////////////////////////////////////////////////
renderJS = function (selector) {
var result;
if (selector === window) {
// window is the 'this' value when loading a javascript file
// In this case, use the current loading gadget constructor
result = gadget_loading_klass;
}
if (result === undefined) {
throw new Error("Unknown selector '" + selector + "'");
}
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.AcquisitionError
/////////////////////////////////////////////////////////////////
renderJS.AcquisitionError = function (message) {
this.name = "AcquisitionError";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Acquisition failed";
};
renderJS.AcquisitionError.prototype = new Error();
renderJS.AcquisitionError.prototype.constructor =
renderJS.AcquisitionError;
/////////////////////////////////////////////////////////////////
// renderJS.getAbsoluteURL
/////////////////////////////////////////////////////////////////
renderJS.getAbsoluteURL = function (url, base_url) {
var doc, base, link,
html = "<!doctype><html><head></head></html>",
isAbsoluteOrDataURL = new RegExp('^(?:[a-z]+:)?//|data:', 'i');
if (url && base_url && !isAbsoluteOrDataURL.test(url)) {
doc = (new DOMParser()).parseFromString(html, 'text/html');
base = doc.createElement('base');
link = doc.createElement('link');
doc.head.appendChild(base);
doc.head.appendChild(link);
base.href = base_url;
link.href = url;
return link.href;
}
return url;
};
/////////////////////////////////////////////////////////////////
// renderJS.declareJS
/////////////////////////////////////////////////////////////////
renderJS.declareJS = function (url) {
// Prevent infinite recursion if loading render.js
// more than once
var result;
if (javascript_registration_dict.hasOwnProperty(url)) {
result = RSVP.resolve();
} else {
result = new RSVP.Promise(function (resolve, reject) {
var newScript;
newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = url;
newScript.onload = function () {
javascript_registration_dict[url] = null;
resolve();
};
newScript.onerror = function (e) {
reject(e);
};
document.head.appendChild(newScript);
});
}
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
// So, always resolve the promise...
// http://requirejs.org/docs/faq-advanced.html#css
var result;
if (stylesheet_registration_dict.hasOwnProperty(url)) {
result = RSVP.resolve();
} else {
result = new RSVP.Promise(function (resolve, reject) {
var link;
link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
link.onload = function () {
stylesheet_registration_dict[url] = null;
resolve();
};
link.onerror = function (e) {
reject(e);
};
document.head.appendChild(link);
});
}
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.declareGadgetKlass
/////////////////////////////////////////////////////////////////
renderJS.declareGadgetKlass = function (url) {
var result,
xhr;
function parse() {
var tmp_constructor,
key,
parsed_html;
if (!gadget_model_dict.hasOwnProperty(url)) {
// Class inheritance
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareMethod =
RenderJSGadget.declareMethod;
tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url;
tmp_constructor.prototype.__acquired_method_dict = {};
tmp_constructor.allowPublicAcquisition("pleasePublishMyState",
pleasePublishMyState);
// https://developer.mozilla.org/en-US/docs/HTML_in_XMLHttpRequest
// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
// https://developer.mozilla.org/en-US/docs/Code_snippets/HTML_to_DOM
tmp_constructor.__template_element =
(new DOMParser()).parseFromString(xhr.responseText, "text/html");
parsed_html = renderJS.parseGadgetHTMLDocument(
tmp_constructor.__template_element,
url
);
for (key in parsed_html) {
if (parsed_html.hasOwnProperty(key)) {
tmp_constructor.prototype['__' + key] = parsed_html[key];
}
}
gadget_model_dict[url] = tmp_constructor;
}
return gadget_model_dict[url];
}
function resolver(resolve, reject) {
function handler() {
var tmp_result;
try {
if (xhr.readyState === 0) {
// UNSENT
reject(xhr);
} else if (xhr.readyState === 4) {
// DONE
if ((xhr.status < 200) || (xhr.status >= 300) ||
(!/^text\/html[;]?/.test(
xhr.getResponseHeader("Content-Type") || ""
))) {
reject(xhr);
} else {
tmp_result = parse();
resolve(tmp_result);
}
}
} catch (e) {
reject(e);
}
}
xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = handler;
xhr.setRequestHeader('Accept', 'text/html');
xhr.withCredentials = true;
xhr.send();
}
function canceller() {
if ((xhr !== undefined) && (xhr.readyState !== xhr.DONE)) {
xhr.abort();
}
}
if (gadget_model_dict.hasOwnProperty(url)) {
// Return klass object if it already exists
result = RSVP.resolve(gadget_model_dict[url]);
} else {
// Fetch the HTML page and parse it
result = new RSVP.Promise(resolver, canceller);
}
return result;
};
/////////////////////////////////////////////////////////////////
// renderJS.clearGadgetKlassList
/////////////////////////////////////////////////////////////////
// For test purpose only
renderJS.clearGadgetKlassList = function () {
gadget_model_dict = {};
javascript_registration_dict = {};
stylesheet_registration_dict = {};
};
/////////////////////////////////////////////////////////////////
// renderJS.parseGadgetHTMLDocument
/////////////////////////////////////////////////////////////////
renderJS.parseGadgetHTMLDocument = function (document_element, url) {
var settings = {
title: "",
interface_list: [],
required_css_list: [],
required_js_list: []
},
i,
element,
isAbsoluteURL = new RegExp('^(?:[a-z]+:)?//', 'i');
if (!url || !isAbsoluteURL.test(url)) {
throw new Error("The url should be absolute: " + url);
}
if (document_element.nodeType === 9) {
settings.title = document_element.title;
if (document_element.head !== null) {
for (i = 0; i < document_element.head.children.length; i += 1) {
element = document_element.head.children[i];
if (element.href !== null) {
// XXX Manage relative URL during extraction of URLs
// element.href returns absolute URL in firefox but "" in chrome;
if (element.rel === "stylesheet") {
settings.required_css_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
);
} else if (element.nodeName === "SCRIPT" &&
(element.type === "text/javascript" ||
!element.type)) {
settings.required_js_list.push(
renderJS.getAbsoluteURL(element.getAttribute("src"), url)
);
} else if (element.rel ===
"http://www.renderjs.org/rel/interface") {
settings.interface_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
);
}
}
}
}
} else {
throw new Error("The first parameter should be an HTMLDocument");
}
return settings;
};
/////////////////////////////////////////////////////////////////
// global
/////////////////////////////////////////////////////////////////
window.rJS = window.renderJS = renderJS;
window.__RenderJSGadget = RenderJSGadget;
window.__RenderJSEmbeddedGadget = RenderJSEmbeddedGadget;
window.__RenderJSIframeGadget = RenderJSIframeGadget;
///////////////////////////////////////////////////
// Bootstrap process. Register the self gadget.
///////////////////////////////////////////////////
function mergeSubDict(dict) {
var subkey,
subkey2,
subresult2,
value,
result = {};
for (subkey in dict) {
if (dict.hasOwnProperty(subkey)) {
value = dict[subkey];
if (value instanceof Object) {
subresult2 = mergeSubDict(value);
for (subkey2 in subresult2) {
if (subresult2.hasOwnProperty(subkey2)) {
// XXX key should not have an . inside
if (result.hasOwnProperty(subkey + "." + subkey2)) {
throw new Error("Key " + subkey + "." +
subkey2 + " already present");
}
result[subkey + "." + subkey2] = subresult2[subkey2];
}
}
} else {
if (result.hasOwnProperty(subkey)) {
throw new Error("Key " + subkey + " already present");
}
result[subkey] = value;
}
}
}
return result;
}
function bootstrap() {
var url = removeHash(window.location.href),
tmp_constructor,
root_gadget,
loading_gadget_promise = new RSVP.Queue(),
declare_method_count = 0,
embedded_channel,
notifyReady,
notifyDeclareMethod,
gadget_ready = false,
iframe_top_gadget,
last_acquisition_gadget;
// Create the gadget class for the current url
if (gadget_model_dict.hasOwnProperty(url)) {
throw new Error("bootstrap should not be called twice");
}
loading_klass_promise = new RSVP.Promise(function (resolve, reject) {
last_acquisition_gadget = new RenderJSGadget();
last_acquisition_gadget.__acquired_method_dict = {
getTopURL: function () {
return url;
},
reportServiceError: function (param_list) {
letsCrash(param_list[0]);
},
pleaseRedirectMyHash: function (param_list) {
window.location.replace(param_list[0]);
},
pleasePublishMyState: function (param_list) {
var key,
first = true,
hash = "#";
param_list[0] = mergeSubDict(param_list[0]);
for (key in param_list[0]) {
if (param_list[0].hasOwnProperty(key)) {
if (!first) {
hash += "&";
}
hash += encodeURIComponent(key) + "=" +
encodeURIComponent(param_list[0][key]);
first = false;
}
}
return hash;
}
};
// Stop acquisition on the last acquisition gadget
// Do not put this on the klass, as their could be multiple instances
last_acquisition_gadget.__aq_parent = function (method_name) {
throw new renderJS.AcquisitionError(
"No gadget provides " + method_name
);
};
//we need to determine tmp_constructor's value before exit bootstrap
//because of function : renderJS
//but since the channel checking is async,
//we can't use code structure like:
// if channel communication is ok
// tmp_constructor = RenderJSGadget
// else
// tmp_constructor = RenderJSEmbeddedGadget
if (window.self === window.top) {
// XXX Copy/Paste from declareGadgetKlass
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url;
gadget_model_dict[url] = tmp_constructor;
// Create the root gadget instance and put it in the loading stack
root_gadget = new gadget_model_dict[url]();
tmp_constructor.declareService(function () {
return listenHashChange(this);
});
setAqParent(root_gadget, last_acquisition_gadget);
} 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;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.prototype.__path = url;
root_gadget = new RenderJSEmbeddedGadget();
// 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;
};
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition;
//Default: Define __aq_parent to inform parent window
tmp_constructor.prototype.__aq_parent = function (method_name,
argument_list, time_out) {
return new RSVP.Promise(function (resolve, reject) {
embedded_channel.call({
method: "acquire",
params: [
method_name,
argument_list
],
success: function (s) {
resolve(s);
},
error: function (e) {
reject(e);
},
timeout: time_out
});
});
};
}
tmp_constructor.prototype.__acquired_method_dict = {};
tmp_constructor.allowPublicAcquisition("pleasePublishMyState",
pleasePublishMyState);
gadget_loading_klass = tmp_constructor;
function init() {
// XXX HTML properties can only be set when the DOM is fully loaded
var settings = renderJS.parseGadgetHTMLDocument(document, url),
j,
key;
for (key in settings) {
if (settings.hasOwnProperty(key)) {
tmp_constructor.prototype['__' + key] = settings[key];
}
}
tmp_constructor.__template_element = document.createElement("div");
root_gadget.__element = document.body;
for (j = 0; j < root_gadget.__element.childNodes.length; j += 1) {
tmp_constructor.__template_element.appendChild(
root_gadget.__element.childNodes[j].cloneNode(true)
);
}
RSVP.all([root_gadget.getRequiredJSList(),
root_gadget.getRequiredCSSList()])
.then(function (all_list) {
var i,
js_list = all_list[0],
css_list = all_list[1];
for (i = 0; i < js_list.length; i += 1) {
javascript_registration_dict[js_list[i]] = null;
}
for (i = 0; i < css_list.length; i += 1) {
stylesheet_registration_dict[css_list[i]] = null;
}
gadget_loading_klass = undefined;
}).then(function () {
// select the target node
var target = document.querySelector('body'),
// create an observer instance
observer = new MutationObserver(function (mutations) {
var i, k, len, len2, node, added_list;
mutations.forEach(function (mutation) {
if (mutation.type === 'childList') {
len = mutation.removedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.removedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
createMonitor(node._gadget);
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (node._gadget !== undefined) {
createMonitor(node._gadget);
}
}
}
}
len = mutation.addedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.addedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
if (document.contains(node)) {
startService(node._gadget);
}
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (document.contains(node)) {
if (node._gadget !== undefined) {
startService(node._gadget);
}
}
}
}
}
}
});
}),
// configuration of the observer:
config = {
childList: true,
subtree: true,
attributes: false,
characterData: false
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
return root_gadget;
}).then(resolve, function (e) {
reject(e);
console.error(e);
throw e;
});
}
document.addEventListener('DOMContentLoaded', init, false);
});
loading_gadget_promise
.push(function () {
return loading_klass_promise;
})
.push(function (root_gadget) {
var i;
function ready_wrapper() {
return root_gadget;
}
if (window.top !== window.self) {
//checking channel should be done before sub gadget's declaration
//__ready_list:
//0: clearGadgetInternalParameters
//1: loadSubGadgetDOMDeclaration
//.....
tmp_constructor.__ready_list.splice(1, 0, function () {
return root_gadget.__aq_parent('getTopURL', [], 100)
.then(function (topURL) {
var base = document.createElement('base');
base.href = topURL;
base.target = "_top";
document.head.appendChild(base);
//the channel is ok
//so 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) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
})
.fail(function (error) {
if (error === "timeout_error") {
//the channel fail
//we consider current gadget is parent gadget
//redifine last acquisition gadget
iframe_top_gadget = true;
tmp_constructor.declareService(function () {
return listenHashChange(this);
});
setAqParent(root_gadget, last_acquisition_gadget);
} else {
throw error;
}
});
});
}
tmp_constructor.ready(function (g) {
return startService(g);
});
loading_gadget_promise.push(ready_wrapper);
for (i = 0; i < tmp_constructor.__ready_list.length; i += 1) {
// Put a timeout?
loading_gadget_promise
.push(tmp_constructor.__ready_list[i])
// Always return the gadget instance after ready function
.push(ready_wrapper);
}
});
if (window.self === window.top) {
loading_gadget_promise
.fail(function (e) {
letsCrash(e);
throw e;
});
} else {
// Inform parent window that gadget is correctly loaded
loading_gadget_promise
.then(function () {
gadget_ready = true;
notifyReady();
})
.fail(function (e) {
//top gadget in iframe
if (iframe_top_gadget) {
letsCrash(e);
} else {
embedded_channel.notify({method: "failed", params: e.toString()});
}
throw e;
});
}
}
bootstrap();
}(document, window, RSVP, DOMParser, Channel, MutationObserver, Node));
\ No newline at end of file
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