Commit e0b99ed4 authored by Thibaut Frain's avatar Thibaut Frain

Added presentation viewer

parent a6bb8996
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
"title" : "Presentation Editor Gadget", "title" : "Presentation Editor Gadget",
"interface" : "http://www.renderjs.org/interface/editor" "interface" : "http://www.renderjs.org/interface/editor"
}, },
editor_6_dict = {
"path" : "./presentation-viewer/index.html",
"title" : "Presentation viewer Gadget",
"interface": "http://www.renderjs.org/interface/editor"
},
catalog_list = [ catalog_list = [
{ {
"path" : "./officejs.html", "path" : "./officejs.html",
...@@ -48,6 +53,7 @@ ...@@ -48,6 +53,7 @@
catalog_list.push(editor_3_dict); catalog_list.push(editor_3_dict);
catalog_list.push(editor_4_dict); catalog_list.push(editor_4_dict);
catalog_list.push(editor_5_dict); catalog_list.push(editor_5_dict);
catalog_list.push(editor_6_dict);
gk.declareMethod('allDocs', function (filter) { gk.declareMethod('allDocs', function (filter) {
var deferred = $.Deferred(); var deferred = $.Deferred();
...@@ -58,7 +64,7 @@ ...@@ -58,7 +64,7 @@
deferred.resolve([io_dict]); deferred.resolve([io_dict]);
} else if (filter.query === } else if (filter.query ===
'interface: "http://www.renderjs.org/interface/editor"') { 'interface: "http://www.renderjs.org/interface/editor"') {
deferred.resolve([editor_1_dict, editor_2_dict, editor_3_dict, editor_4_dict, editor_5_dict]); deferred.resolve([editor_1_dict, editor_2_dict, editor_3_dict, editor_4_dict, editor_5_dict, editor_6_dict]);
} else { } else {
deferred.reject("Unsupported filter"); deferred.reject("Unsupported filter");
} }
......
...@@ -2,5 +2,5 @@ iframe { ...@@ -2,5 +2,5 @@ iframe {
border:0; border:0;
margin:0; margin:0;
padding:0; padding:0;
width:80%; width:100%;
} }
...@@ -7,13 +7,12 @@ module.exports = function(grunt) { ...@@ -7,13 +7,12 @@ module.exports = function(grunt) {
jslint: { jslint: {
client: { client: {
src: [ src: [
'lib/presentation-editor.js' 'presentation-editor.js'
], ],
directives: { directives: {
browser: true, browser: true,
maxlen: 100, maxlen: 100,
indent: 2, indent: 2,
maxerr: 3,
unparam: true, unparam: true,
plusplus: true, plusplus: true,
predef: [ predef: [
......
{ {
"name": "prezedit", "name": "Presentation editor",
"version": "0.0.0", "version": "0.0.1",
"authors": [ "authors": [
"Thibaut Frain <thibaut.frain@tiolive.com>" "Thibaut Frain <thibaut.frain@tiolive.com>"
], ],
......
@charset "UTF-8";.animated{-webkit-animation-duration:0.5s;animation-duration:0.5s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{-webkit-transform:translateY(0);transform:translateY(0)}40%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px);transform:translateY(-15px)}}@keyframes bounce{0%,20%,50%,80%,100%{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}40%{-webkit-transform:translateY(-30px);-ms-transform:translateY(-30px);transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);transform:translateY(-15px)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(1.1);transform:scale(1.1)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes pulse{0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}100%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes rollOut{0%{opacity:1;-webkit-transform:translateX(0)rotate(0);transform:translateX(0)rotate(0)}100%{opacity:0;-webkit-transform:translateX(100%)rotate(120deg);transform:translateX(100%)rotate(120deg)}}@keyframes rollOut{0%{opacity:1;-webkit-transform:translateX(0)rotate(0);-ms-transform:translateX(0)rotate(0);transform:translateX(0)rotate(0)}100%{opacity:0;-webkit-transform:translateX(100%)rotate(120deg);-ms-transform:translateX(100%)rotate(120deg);transform:translateX(100%)rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}
\ No newline at end of file
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.block.slide-thumb { .block.slide-thumb {
background-color: white; background-color: white;
box-shadow: 2px 2px 2px 3px #ccc; /* box-shadow: 2px 2px 2px 3px #ccc; */
border: 2px solid grey; border: 2px solid grey;
} }
...@@ -73,6 +73,21 @@ ...@@ -73,6 +73,21 @@
display: none; display: none;
} }
body {
background: url('images/tweed.png');
background-repeat: repeat;
background-position: center center;
background-attachment: scroll;
background-size: 100% 100%;
}
.ui-page .ui-overlay-a, .ui-page-theme-a, .ui-page-theme-a .ui-panel-wrapper{
background: url('images/tweed.png');
}
.ui-content{
background: transparent;
}
@media (max-width: 1000px) { @media (max-width: 1000px) {
.ui-panel { .ui-panel {
width: 100%; width: 100%;
......
...@@ -4,28 +4,31 @@ ...@@ -4,28 +4,31 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Presentation editor</title> <title>Presentation editor</title>
<!-- Presentation editor --> <!-- Presentation editor dependencies -->
<script src="lib/jquery.min.js"></script> <script src="lib/jquery.min.js"></script>
<script src="lib/jquery-ui.min.js"></script> <script src="lib/jquery-ui.min.js"></script>
<script src="lib/jquery.mobile.min.js"></script> <script src="lib/jquery.mobile.min.js"></script>
<script src="lib/beautify-html.js"></script> <script src="lib/beautify-html.js"></script>
<script src="lib/presentation-editor.js"></script>
<!-- Renderjs --> <!-- Renderjs -->
<script src="lib/rsvp.min.js"></script> <script src="lib/rsvp.min.js"></script>
<script src="lib/jschannel.js"></script> <script src="lib/jschannel.js"></script>
<script src="lib/renderjs.js"></script> <script src="lib/renderjs.js"></script>
<!-- IO definitions for presentation editor gadget-->
<script src="gadget.js"></script>
<!-- Jquery ui and jquery-mobile stylesheet --> <!-- Jquery ui and jquery-mobile stylesheet -->
<link rel="stylesheet" href="css/jquery-ui.min.css"> <link rel="stylesheet" href="css/jquery-ui.min.css">
<link rel="stylesheet" href="css/jquery.mobile.min.css"> <link rel="stylesheet" href="css/jquery.mobile.min.css">
<link rel="stylesheet" href="css/animate.min.css">
<!-- Presentation editor stylesheet --> <!-- Presentation editor stylesheet -->
<link rel="stylesheet" href="css/presentation-editor.css"> <link rel="stylesheet" href="css/presentation-editor.css">
<!-- Presentation editor application -->
<script src="presentation-editor.js"></script>
<!-- IO definitions for presentation editor gadget-->
<script src="gadget.js"></script>
</head> </head>
<body> <body>
...@@ -62,10 +65,13 @@ ...@@ -62,10 +65,13 @@
<br/> <br/>
<div data-role="fieldcontain" id="image-field"> <div data-role="fieldcontain" id="image-field">
<label for="image"><strong>Image</strong></label> <label for="image"><strong>Image URL</strong></label>
<input type="file" name="image" id="image-input" class="ui-btn" /> <input type="text" name="image" id="image-url" />
<img id="image-preview" class="ui-shadow ui-corner-all"> <img id="image-preview" class="ui-shadow ui-corner-all">
<br/>
</div> </div>
<br/>
<div data-role="fieldcontain"> <div data-role="fieldcontain">
<label for="content"><strong>Slide text</strong></label> <label for="content"><strong>Slide text</strong></label>
......
/*globals window, document, $, html_beautify, FileReader */ /*globals window, document, $, html_beautify, FileReader, */
/*jslint unparam: true */ /*jslint unparam: true */
$(function () { $(function () {
"use strict"; "use strict";
...@@ -9,94 +9,114 @@ $(function () { ...@@ -9,94 +9,114 @@ $(function () {
formPanel = $('#form-panel'); formPanel = $('#form-panel');
formPanel.panel({ beforeclose: function () { formPanel.panel({ beforeclose: function () {
newSlideButton.show(); newSlideButton.show("fade");
slideForm.reset(); slideForm.bindToAdd();
}}); }});
function openForm() { function openForm() {
formPanel.panel("open"); formPanel.panel("open");
newSlideButton.hide(); newSlideButton.hide("fade");
} }
function closeForm() { function closeForm() {
formPanel.panel("close"); formPanel.panel("close");
} }
function animate(selector, animation) {
var $selector = $(selector);
$selector.off("animationend webkitAnimationEnd");
$selector.on("animationend webkitAnimationEnd", function () {
$selector.removeClass("animated " + animation);
});
$selector.addClass("animated " + animation);
}
function Slide(params) { function Slide(params) {
var that = this; var that = this;
this.html = document.importNode(this.htmlTemplate, true); this.html = document.importNode(this.htmlTemplate, true);
if (params.section) { this.update(params);
this.update({
title: params.section.querySelector('h1').textContent || "",
type: params.section.className || "",
content:
params
.section
.childNodes[2] ? (params.section.childNodes[2].textContent || "") : ""
});
} else {
this.update(params);
}
$(this.editBtn()).click(function () { $(this.editBtn()).click(function () {
slideForm.bindToEdit(that); if (slideForm.currentSlide !== that) {
openForm(); slideForm.bindToEdit(that);
openForm();
}
}); });
$(this.deleteBtn()).click(function () { $(this.deleteBtn()).click(function () {
presentation.deleteSlide(that); presentation.deleteSlide(that);
}); });
} }
function readSlide(domElement) {
var $el = $(domElement),
title = $el.find('h1').first().text(),
type = $el.attr('class'),
img,
content = "";
if (type === 'screenshot' || type === 'illustration') {
img = $el.find('img').first().attr('src');
}
$el.contents().filter(function () {
return $(this).is(':not(img, h1, details)') || this.nodeType === 3;
}).each(function () {
content += this.outerHTML || this.textContent;
});
return new Slide({
title: title,
type: type,
content: content,
image: img
});
}
Slide.prototype = { Slide.prototype = {
dataTemplate: document.querySelector('template#slide-data').content.firstElementChild, dataTemplate: document.querySelector('template#slide-data').content.firstElementChild,
htmlTemplate: document.querySelector('template#slide-html').content.firstElementChild, htmlTemplate: document.querySelector('template#slide-html').content.firstElementChild,
editBtn: function () {return this.html.querySelector("button.edit");}, editBtn: function () {return this.html.querySelector("button.edit"); },
deleteBtn: function () {return this.html.querySelector("button.delete");}, deleteBtn: function () {return this.html.querySelector("button.delete"); },
htmlContent: function () {return this.html.querySelector(".content");}, htmlContent: function () {return this.html.querySelector(".content"); },
htmlImage: function () {return this.html.querySelector("img");}, htmlImage: function () {return this.html.querySelector("img"); },
htmlTitle: function () {return this.html.querySelector("h1");}, htmlTitle: function () {return this.html.querySelector("h1"); },
data: function () { data: function () {
var res = document.importNode(this.dataTemplate, true); var res = document.importNode(this.dataTemplate, true), img;
res.className = this.type; res.className = this.type;
res.querySelector('h1').textContent = this.title; res.querySelector('h1').textContent = this.title;
res.appendChild(document.createTextNode(this.content)); $(res).append(this.content);
if (this.type === "screenshot" || this.type === "illustration") {
img = document.createElement('img');
img.src = this.image;
res.appendChild(img);
}
return res; return res;
}, },
update: function (params) { update: function (params) {
console.log(params);
$.extend(this, params); $.extend(this, params);
this.htmlTitle().textContent = this.title; this.htmlTitle().textContent = this.title;
this.htmlContent().innerHTML = this.content; this.htmlContent().innerHTML = this.content;
if (this.type === "screenshot" || this.type === "illustration") { if (this.type === "screenshot" || this.type === "illustration") {
this.htmlImage().src = this.image; this.htmlImage().src = this.image;
}else{ } else {
this.htmlImage().src= ""; this.htmlImage().src = "";
} }
} }
}; };
function SlideForm() { function SlideForm() {
var that = this; var that = this;
this.elt = document.querySelector("#slide-form"); this.elt = document.querySelector("#slide-form");
this.bindToAdd(); this.bindToAdd();
$(this.elt).find("#cancel").click(closeForm); $(this.elt).find("#cancel").click(closeForm);
$(this.elt).find('input[type="radio"]').click(function () { $(this.elt).find('input[type="radio"]').click(function () {
that.updateFieldVisibility(); that.updateFieldVisibility(true);
}); });
$(this.elt).find('#image-input').change(function (evt) { $("#image-url").on("change", function () {
var file = evt.target.files[0]; that.updatePreview(that.attrImageURL());
console.log(file);
if (file) {
var reader = new FileReader();
reader.onload = function (e) {
console.log(e.target);
that.attrImageFile(e.target.result);
};
reader.readAsDataURL(file);
}
}); });
} }
...@@ -105,104 +125,122 @@ $(function () { ...@@ -105,104 +125,122 @@ $(function () {
attrTextInput: function (inputElt, content) { attrTextInput: function (inputElt, content) {
if (content !== undefined) { if (content !== undefined) {
inputElt.value = content; inputElt.value = content;
}else{ } else {
return inputElt.value; return inputElt.value;
} }
}, },
attrTitle: function(content) { attrTitle: function (content) {
return this.attrTextInput(this.elt.querySelector('#title'), content); return this.attrTextInput(this.elt.querySelector('#title'), content);
}, },
attrContent: function(content) { attrContent: function (content) {
return this.attrTextInput(this.elt.querySelector('#content'), content); return this.attrTextInput(this.elt.querySelector('#content'), content);
}, },
attrDetails: function(content) { attrDetails: function (content) {
return this.attrTextInput(this.elt.querySelector('#details'), content); return this.attrTextInput(this.elt.querySelector('#details'), content);
}, },
attrType: function(type) { attrType: function (type) {
var radios = $(this.elt).find('input[type="radio"]'); var radios = $(this.elt).find('input[type="radio"]');
if (type !== undefined) { if (type !== undefined) {
radios.prop('checked', false); radios.prop('checked', false);
if (type === "") {type = "basic";} if (type === "") {type = "basic"; }
radios.filter("#"+type+"-type").prop('checked', true); radios.filter("#" + type + "-type").prop('checked', true);
radios.checkboxradio('refresh'); radios.checkboxradio('refresh');
this.updateFieldVisibility(); this.updateFieldVisibility();
}else{ } else {
return radios.filter(':checked').val(); return radios.filter(':checked').val();
} }
}, },
attrImageFile: function (imageFile) { attrImageURL: function (content) {
var input = this.elt.querySelector('#image-input'); return this.attrTextInput(this.elt.querySelector('#image-url'), content);
},
updatePreview: function (content) {
var preview = this.elt.querySelector('#image-preview'); var preview = this.elt.querySelector('#image-preview');
if (imageFile !== undefined) { if (content) {
input.value = ""; $(preview).show();
preview.src = imageFile; preview.src = content;
}else{ } else {
return preview.src; $(preview).hide();
} }
}, },
attrAll: function (slide) { attrAll: function (slide) {
if (slide !== undefined) { if (slide !== undefined) {
this.attrTitle(slide.title); this.attrTitle(slide.title);
this.attrType(slide.type); this.attrType(slide.type);
this.attrContent(slide.content); this.attrContent(slide.content);
this.attrDetails(slide.details); this.attrDetails(slide.details);
this.attrImageFile(slide.image); this.attrImageURL(slide.image);
}else{ this.updatePreview(slide.image);
} else {
return { return {
title: this.attrTitle(), title: this.attrTitle(),
type: this.attrType(), type: this.attrType(),
content: this.attrContent(), content: this.attrContent(),
details: this.attrDetails(), details: this.attrDetails(),
image: this.attrImageFile() image: this.attrImageURL()
}; };
} }
}, },
updateFieldVisibility: function () { updateFieldVisibility: function (withEffect) {
var type = this.attrType(); var type = this.attrType(),
$imageField = $(this.elt).find('#image-field'),
$imageInputURL = $(this.elt).find('input#image-url');
if (type === "screenshot" || type === "illustration") { if (type === "screenshot" || type === "illustration") {
$(this.elt).find('#image-field').show("blind", {direction: "up"}); if (withEffect) {
}else{ $imageField.show("blind", {direction: "up"});
$(this.elt).find('#image-field').hide("blind", {direction: "up"}); } else {
$imageField.show();
}
$imageInputURL.attr('required', true);
} else {
if (withEffect) {
$imageField.hide("blind", {direction: "up"});
} else {
$imageField.hide();
}
$imageInputURL.attr('required', false);
} }
}, },
setSubmitLabel: function (label) { setSubmitLabel: function (label) {
var submit = $(this.elt).find("#submit"); var submit = $(this.elt).find("#submit");
submit.prop("value", label).button('refresh'); submit.prop("value", label).button('refresh');
}, },
reset: function () { reset: function () {
this.attrAll({title: "", type: "basic", content: "", details: "", image: ""}); this.attrAll({title: "", type: "basic", content: "", details: "", image: ""});
this.currentSlide = null;
$(this.elt).off("submit");
}, },
bindToEdit: function (slide) { bindToEdit: function (slide) {
var that = this; var that = this;
this.reset();
animate(this.elt, "fadeIn");
that.currentSlide = slide; that.currentSlide = slide;
$(this.elt).off();
this.attrAll(slide); this.attrAll(slide);
$(this.elt).submit(function (e) { $(this.elt).submit(function (e) {
slide.update(that.attrAll()); slide.update(that.attrAll());
animate(slide.html, "bounce");
e.preventDefault(); e.preventDefault();
that.currentSlide = null; that.bindToAdd();
slideForm.bindToAdd();
}); });
this.setSubmitLabel("Save"); this.setSubmitLabel("Save");
}, },
bindToAdd: function () { bindToAdd: function () {
var that = this; var that = this;
$(this.elt).off();
this.reset(); this.reset();
$(this.elt).submit(function (e) { $(this.elt).submit(function (e) {
presentation.addSlide(new Slide(that.attrAll())); presentation.addSlide(new Slide(that.attrAll()));
that.reset(); that.bindToAdd();
e.preventDefault(); e.preventDefault();
}); });
this.setSubmitLabel("Add"); this.setSubmitLabel("Add");
...@@ -226,6 +264,7 @@ $(function () { ...@@ -226,6 +264,7 @@ $(function () {
addSlide: function (slide) { addSlide: function (slide) {
this.slides.push(slide); this.slides.push(slide);
this.html.appendChild(slide.html); this.html.appendChild(slide.html);
animate(slide.html, "pulse");
return slide; return slide;
}, },
...@@ -235,7 +274,10 @@ $(function () { ...@@ -235,7 +274,10 @@ $(function () {
} }
var index = this.slides.indexOf(slide); var index = this.slides.indexOf(slide);
this.slides.splice(index, 1); this.slides.splice(index, 1);
slide.html.remove(); animate(slide.html, 'rollOut');
$(slide.html).on("animationend webkitAnimationEnd", function () {
slide.html.remove();
});
return index; return index;
}, },
...@@ -267,7 +309,7 @@ $(function () { ...@@ -267,7 +309,7 @@ $(function () {
container.innerHTML = content; container.innerHTML = content;
sections = container.children; sections = container.children;
for (i = 0; i < sections.length; i++) { for (i = 0; i < sections.length; i++) {
this.addSlide(new Slide({section: sections[i]})); this.addSlide(readSlide(sections[i]));
} }
} }
}; };
......
/*global window, document, rJS, dZmain */
(function (window, document, rJS) {
"use strict";
rJS(window)
.declareMethod('setContent', function (content) {
document.body.innerHTML = content;
dZmain();
})
.declareMethod('getContent', function () {
return document.innerHTML;
});
}(window, document, rJS));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Oswald">
<link rel="stylesheet" href="slides.css">
<script src="slides.js"></script>
<!-- Renderjs -->
<script src="lib/rsvp.min.js"></script>
<script src="lib/jschannel.js"></script>
<script src="lib/renderjs.js"></script>
<!-- IO definitions for presentation editor gadget-->
<script src="gadget.js"></script>
<title></title>
</head>
<body></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;
}
};
})();
/*! RenderJs */
/*
* renderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation
*/
(function (document, window, RSVP, DOMParser, Channel, undefined) {
"use strict";
var gadget_model_dict = {},
javascript_registration_dict = {},
stylesheet_registration_dict = {},
gadget_loading_klass,
loading_gadget_promise,
renderJS;
/////////////////////////////////////////////////////////////////
// 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 = [];
RSVP.EventTarget.mixin(RenderJSGadget.prototype);
RenderJSGadget.ready_list = [];
RenderJSGadget.ready = function (callback) {
this.ready_list.push(callback);
return this;
};
/////////////////////////////////////////////////////////////////
// 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;
});
/////////////////////////////////////////////////////////////////
// RenderJSEmbeddedGadget
/////////////////////////////////////////////////////////////////
// Class inheritance
function RenderJSEmbeddedGadget() {
if (!(this instanceof RenderJSEmbeddedGadget)) {
return new RenderJSEmbeddedGadget();
}
RenderJSGadget.call(this);
}
RenderJSEmbeddedGadget.ready_list = [];
RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
/////////////////////////////////////////////////////////////////
// privateDeclarePublicGadget
/////////////////////////////////////////////////////////////////
function privateDeclarePublicGadget(url, options) {
var gadget_instance;
if (options.element === undefined) {
options.element = document.createElement("div");
}
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)
);
}
// Load dependencies if needed
return RSVP.all([
gadget_instance.getRequiredJSList(),
gadget_instance.getRequiredCSSList()
]);
})
// Load all JS/CSS
.push(function (all_list) {
var parameter_list = [],
i;
// Load JS
for (i = 0; i < all_list[0].length; i += 1) {
parameter_list.push(renderJS.declareJS(all_list[0][i]));
}
// Load CSS
for (i = 0; i < all_list[1].length; i += 1) {
parameter_list.push(renderJS.declareCSS(all_list[1][i]));
}
return RSVP.all(parameter_list);
})
.push(function () {
return gadget_instance;
});
}
/////////////////////////////////////////////////////////////////
// RenderJSIframeGadget
/////////////////////////////////////////////////////////////////
function RenderJSIframeGadget() {
if (!(this instanceof RenderJSIframeGadget)) {
return new RenderJSIframeGadget();
}
RenderJSGadget.call(this);
}
RenderJSIframeGadget.ready_list = [];
RenderJSIframeGadget.ready =
RenderJSGadget.ready;
RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
/////////////////////////////////////////////////////////////////
// privateDeclareIframeGadget
/////////////////////////////////////////////////////////////////
function privateDeclareIframeGadget(url, options) {
var gadget_instance,
iframe,
node,
iframe_loading_deferred = RSVP.defer();
if (options.element === undefined) {
throw new Error("DOM element is required to create Iframe Gadget " +
url);
}
// Check if the element is attached to the DOM
node = options.element.parentNode;
while (node !== null) {
if (node === document) {
break;
}
node = node.parentNode;
}
if (node === null) {
throw new Error("The parent element is not attached to the DOM for " +
url);
}
gadget_instance = new RenderJSIframeGadget();
iframe = document.createElement("iframe");
// gadget_instance.element.setAttribute("seamless", "seamless");
iframe.setAttribute("src", url);
gadget_instance.path = url;
gadget_instance.element = options.element;
// Attach it to the DOM
options.element.appendChild(iframe);
// XXX Manage unbind when deleting the gadget
// Create the communication channel with the iframe
gadget_instance.chan = Channel.build({
window: iframe.contentWindow,
origin: "*",
scope: "renderJS"
});
// Create new method from the declareMethod call inside the iframe
gadget_instance.chan.bind("declareMethod", function (trans, method_name) {
gadget_instance[method_name] = function () {
var argument_list = arguments;
return new RSVP.Promise(function (resolve, reject) {
gadget_instance.chan.call({
method: "methodCall",
params: [
method_name,
Array.prototype.slice.call(argument_list, 0)],
success: function (s) {
resolve(s);
},
error: function (e) {
reject(e);
}
});
});
};
return "OK";
});
// Wait for the iframe to be loaded before continuing
gadget_instance.chan.bind("ready", function (trans) {
iframe_loading_deferred.resolve(gadget_instance);
return "OK";
});
gadget_instance.chan.bind("failed", function (trans, params) {
iframe_loading_deferred.reject(params);
return "OK";
});
gadget_instance.chan.bind("trigger", function (trans, params) {
return gadget_instance.trigger(params.event_name, params.options);
});
return RSVP.any([
iframe_loading_deferred.promise,
// Timeout to prevent non renderJS embeddable gadget
// XXX Maybe using iframe.onload/onerror would be safer?
RSVP.timeout(5000)
]);
}
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareGadget
/////////////////////////////////////////////////////////////////
RenderJSGadget.prototype.declareGadget = function (url, options) {
var queue,
previous_loading_gadget_promise = loading_gadget_promise;
if (options === undefined) {
options = {};
}
if (options.sandbox === undefined) {
options.sandbox = "public";
}
// Change the global variable to update the loading queue
queue = new RSVP.Queue()
// Wait for previous gadget loading to finish first
.push(function () {
return previous_loading_gadget_promise;
})
.push(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);
})
// Set the HTML context
.push(function (gadget_instance) {
var i;
// Drop the current loading klass info used by selector
gadget_loading_klass = undefined;
// 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);
}
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;
});
loading_gadget_promise = queue;
return loading_gadget_promise;
};
/////////////////////////////////////////////////////////////////
// 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;
} else if (selector instanceof RenderJSGadget) {
result = selector;
}
if (result === undefined) {
throw new Error("Unknown selector '" + selector + "'");
}
return result;
};
/////////////////////////////////////////////////////////////////
// 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 = [];
tmp_constructor.declareMethod =
RenderJSGadget.declareMethod;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url;
// 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
);
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) {
var settings = {
title: "",
interface_list: [],
required_css_list: [],
required_js_list: []
},
i,
element;
if (document_element.nodeType === 9) {
settings.title = document_element.title;
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(element.getAttribute("href"));
} else if (element.type === "text/javascript") {
settings.required_js_list.push(element.getAttribute("src"));
} else if (element.rel === "http://www.renderjs.org/rel/interface") {
settings.interface_list.push(element.getAttribute("href"));
}
}
}
} 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 bootstrap() {
var url = window.location.href,
tmp_constructor,
root_gadget,
declare_method_count = 0,
embedded_channel,
notifyReady,
notifyDeclareMethod,
notifyTrigger,
gadget_ready = false;
// Create the gadget class for the current url
if (gadget_model_dict.hasOwnProperty(url)) {
throw new Error("bootstrap should not be called twice");
}
loading_gadget_promise = new RSVP.Promise(function (resolve, reject) {
if (window.self === window.top) {
// XXX Copy/Paste from declareGadgetKlass
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
tmp_constructor.ready_list = [];
tmp_constructor.ready = RenderJSGadget.ready;
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]();
} else {
// Create the communication channel
embedded_channel = Channel.build({
window: window.parent,
origin: "*",
scope: "renderJS"
});
// Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget;
root_gadget = new RenderJSEmbeddedGadget();
// Bind calls to renderJS method on the instance
embedded_channel.bind("methodCall", function (trans, v) {
root_gadget[v[0]].apply(root_gadget, v[1]).then(function (g) {
trans.complete(g);
}).fail(function (e) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
// Notify parent about gadget instanciation
notifyReady = function () {
if ((declare_method_count === 0) && (gadget_ready === true)) {
embedded_channel.notify({method: "ready"});
}
};
// Inform parent gadget about declareMethod calls here.
notifyDeclareMethod = function (name) {
declare_method_count += 1;
embedded_channel.call({
method: "declareMethod",
params: name,
success: function () {
declare_method_count -= 1;
notifyReady();
},
error: function () {
declare_method_count -= 1;
}
});
};
notifyDeclareMethod("getInterfaceList");
notifyDeclareMethod("getRequiredCSSList");
notifyDeclareMethod("getRequiredJSList");
notifyDeclareMethod("getPath");
notifyDeclareMethod("getTitle");
// Surcharge declareMethod to inform parent window
tmp_constructor.declareMethod = function (name, callback) {
var result = RenderJSGadget.declareMethod.apply(
this,
[name, callback]
);
notifyDeclareMethod(name);
return result;
};
notifyTrigger = function (eventName, options) {
embedded_channel.notify({
method: "trigger",
params: {
event_name: eventName,
options: options
}
});
};
// Surcharge trigger to inform parent window
tmp_constructor.prototype.trigger = function (eventName, options) {
var result = RenderJSGadget.prototype.trigger.apply(
this,
[eventName, options]
);
notifyTrigger(eventName, options);
return result;
};
}
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),
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],
queue;
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;
queue = new RSVP.Queue();
function ready_wrapper() {
return root_gadget;
}
queue.push(ready_wrapper);
for (i = 0; i < tmp_constructor.ready_list.length; i += 1) {
// Put a timeout?
queue.push(tmp_constructor.ready_list[i]);
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
}
queue.push(resolve, function (e) {
reject(e);
throw e;
});
return queue;
}).fail(function (e) {
reject(e);
/*global console */
console.error(e);
});
}
document.addEventListener('DOMContentLoaded', init, false);
});
if (window.self !== window.top) {
// Inform parent window that gadget is correctly loaded
loading_gadget_promise.then(function () {
gadget_ready = true;
notifyReady();
}).fail(function (e) {
embedded_channel.notify({method: "failed", params: e.toString()});
throw e;
});
}
}
bootstrap();
}(document, window, RSVP, DOMParser, Channel));
!function(){var a,b;!function(){var c={},d={};a=function(a,b,d){c[a]={deps:b,callback:d}},b=function(a){if(d[a])return d[a];d[a]={};var e=c[a];if(!e)throw new Error("Module '"+a+"' not found.");for(var f,g=e.deps,h=e.callback,i=[],j=0,k=g.length;k>j;j++)i.push("exports"===g[j]?f={}:b(g[j]));var l=h.apply(this,i);return d[a]=f||l}}(),a("rsvp/all",["rsvp/promise","exports"],function(a,b){"use strict";function c(a,b){function c(){for(var a,c=0;c<b.length;c++)a=b[c],a&&"function"==typeof a.then&&"function"==typeof a.cancel&&a.cancel()}if("[object Array]"!==Object.prototype.toString.call(b))throw new TypeError("You must pass an array to all.");return new f(function(d,e,f){function g(a){return function(b){h(a,b)}}function h(a,b){l[a]=b,--m===n&&(0===n?d(l):(d(b),c()))}function i(a){return function(b){f({index:a,value:b})}}function j(a){e(a),c()}var k,l=[],m=b.length,n=b.length-a;0===m&&(1===a?d():d([]));for(var o=0;o<b.length;o++)k=b[o],k&&"function"==typeof k.then?k.then(g(o),j,i(o)):h(o,k)},c)}function d(a){return c(a.length,a)}function e(a){return c(1,a)}var f=a.Promise;b.all=d,b.any=e}),a("rsvp/async",["exports"],function(a){"use strict";function b(){return function(a,b){process.nextTick(function(){a(b)})}}function c(){return function(a,b){setImmediate(function(){a(b)})}}function d(){var a=[],b=new h(function(){var b=a.slice();a=[],b.forEach(function(a){var b=a[0],c=a[1];b(c)})}),c=document.createElement("div");return b.observe(c,{attributes:!0}),window.addEventListener("unload",function(){b.disconnect(),b=null},!1),function(b,d){a.push([b,d]),c.setAttribute("drainQueue","drainQueue")}}function e(){return function(a,b){i.setTimeout(function(){a(b)},1)}}var f,g="undefined"!=typeof window?window:{},h=g.MutationObserver||g.WebKitMutationObserver,i="undefined"!=typeof global?global:this;f="function"==typeof setImmediate?c():"undefined"!=typeof process&&"[object process]"==={}.toString.call(process)?b():h?d():e(),a.async=f}),a("rsvp/cancellation_error",["exports"],function(a){"use strict";function b(a){if(this.name="cancel",void 0!==a&&"string"!=typeof a)throw new TypeError("You must pass a string.");this.message=a||"Default Message"}b.prototype=new Error,b.prototype.constructor=b,a.CancellationError=b}),a("rsvp/config",["rsvp/async","exports"],function(a,b){"use strict";var c=a.async,d={};d.async=c,b.config=d}),a("rsvp/defer",["rsvp/promise","exports"],function(a,b){"use strict";function c(){var a={resolve:void 0,reject:void 0,promise:void 0};return a.promise=new d(function(b,c){a.resolve=b,a.reject=c}),a}var d=a.Promise;b.defer=c}),a("rsvp/events",["exports"],function(a){"use strict";var b=function(a,b){this.type=a;for(var c in b)b.hasOwnProperty(c)&&(this[c]=b[c])},c=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c][0]===b)return c;return-1},d=function(a){var b=a._promiseCallbacks;return b||(b=a._promiseCallbacks={}),b},e={mixin:function(a){return a.on=this.on,a.off=this.off,a.trigger=this.trigger,a},on:function(a,b,e){var f,g,h=d(this);for(a=a.split(/\s+/),e=e||this;g=a.shift();)f=h[g],f||(f=h[g]=[]),-1===c(f,b)&&f.push([b,e])},off:function(a,b){var e,f,g,h=d(this);for(a=a.split(/\s+/);f=a.shift();)b?(e=h[f],g=c(e,b),-1!==g&&e.splice(g,1)):h[f]=[]},trigger:function(a,c){var e,f,g,h,i,j=d(this);if(e=j[a])for(var k=0;k<e.length;k++)f=e[k],g=f[0],h=f[1],"object"!=typeof c&&(c={detail:c}),i=new b(a,c),g.call(h,i)}};a.EventTarget=e}),a("rsvp/hash",["rsvp/defer","exports"],function(a,b){"use strict";function c(a){var b=0;for(var c in a)b++;return b}function d(a){var b={},d=e(),f=c(a);0===f&&d.resolve({});var g=function(a){return function(b){h(a,b)}},h=function(a,c){b[a]=c,0===--f&&d.resolve(b)},i=function(a){d.reject(a)};for(var j in a)a[j]&&"function"==typeof a[j].then?a[j].then(g(j),i):h(j,a[j]);return d.promise}var e=a.defer;b.hash=d}),a("rsvp/node",["rsvp/promise","rsvp/all","exports"],function(a,b,c){"use strict";function d(a,b){return function(c,d){c?b(c):a(arguments.length>2?Array.prototype.slice.call(arguments,1):d)}}function e(a){return function(){var b,c,e=Array.prototype.slice.call(arguments),h=this,i=new f(function(a,d){b=a,c=d});return g(e).then(function(e){e.push(d(b,c));try{a.apply(h,e)}catch(f){c(f)}}),i}}var f=a.Promise,g=b.all;c.denodeify=e}),a("rsvp/promise",["rsvp/config","rsvp/events","rsvp/cancellation_error","exports"],function(a,b,c,d){"use strict";function e(a){return f(a)||"object"==typeof a&&null!==a}function f(a){return"function"==typeof a}function g(a){m.onerror&&m.onerror(a.detail)}function h(a,b){a===b?j(a,b):i(a,b)||j(a,b)}function i(a,b){var c,d=null;try{if(a===b)throw new TypeError("A promises callback cannot return that same promise.");if(e(b)&&(d=b.then,f(d)))return a.on("promise:cancelled",function(){f(b.cancel)&&b.cancel()}),d.call(b,function(d){return c?!0:(c=!0,void(b!==d?h(a,d):j(a,d)))},function(b){return c?!0:(c=!0,void k(a,b))}),!0}catch(g){return k(a,g),!0}return!1}function j(a,b){m.async(function(){a.isFulfilled||a.isRejected||(a.trigger("promise:resolved",{detail:b}),a.isFulfilled=!0,a.fulfillmentValue=b)})}function k(a,b){m.async(function(){a.isFulfilled||a.isRejected||(a.trigger("promise:failed",{detail:b}),a.isRejected=!0,a.rejectedReason=b)})}function l(a,b){m.async(function(){a.trigger("promise:notified",{detail:b})})}var m=a.config,n=b.EventTarget,o=c.CancellationError,p=function(a,b){var c=this,d=!1;if("function"!=typeof a)throw new TypeError("You must pass a resolver function as the sole argument to the promise constructor");if(void 0!==b&&"function"!=typeof b)throw new TypeError("You can only pass a canceller function as the second argument to the promise constructor");if(!(c instanceof p))return new p(a,b);var e=function(a){d||(d=!0,h(c,a))},f=function(a){d||(d=!0,k(c,a))},i=function(a){d||l(c,a)};this.on("promise:failed",function(a){this.trigger("error",{detail:a.detail})},this),this.on("error",g),this.cancel=function(){if(!d){if(void 0!==b)try{b()}catch(a){return void f(a)}f(new o)}};try{a(e,f,i)}catch(j){f(j)}},q=function(a,b,c,d){var e,g,j,l,m=f(c);if(!b.isFulfilled&&!b.isRejected){if(m)try{e=c(d.detail),j=!0}catch(n){l=!0,g=n}else e=d.detail,j=!0;i(b,e)||(m&&j?h(b,e):l?k(b,g):"resolve"===a?h(b,e):"reject"===a&&k(b,e))}},r=function(a,b,c){var d;if("function"==typeof b){try{d=b(c.detail)}catch(e){return}l(a,d)}else l(a,c.detail)};p.prototype={constructor:p,isRejected:void 0,isFulfilled:void 0,rejectedReason:void 0,fulfillmentValue:void 0,then:function(a,b,c){this.off("error",g);var d=new this.constructor(function(){},function(){d.trigger("promise:cancelled",{})});return this.isFulfilled&&m.async(function(b){q("resolve",d,a,{detail:b.fulfillmentValue})},this),this.isRejected&&m.async(function(a){q("reject",d,b,{detail:a.rejectedReason})},this),this.on("promise:resolved",function(b){q("resolve",d,a,b)}),this.on("promise:failed",function(a){q("reject",d,b,a)}),this.on("promise:notified",function(a){r(d,c,a)}),d},fail:function(a){return this.then(null,a)},always:function(a){return this.then(a,a)}},n.mixin(p.prototype),d.Promise=p}),a("rsvp/queue",["rsvp/promise","rsvp/timeout","exports"],function(a,b,c){"use strict";function d(a){if(this.name="resolved",void 0!==a&&"string"!=typeof a)throw new TypeError("You must pass a string.");this.message=a||"Default Message"}var e=a.Promise,f=b.delay;d.prototype=new Error,d.prototype.constructor=d;var g=function(){function a(){for(var a=0;2>a;a++)k[a].cancel()}var b,c,h,i,j=this,k=[];return this instanceof g?(b=new e(function(a,b){c=function(b){return i?void 0:(j.isFulfilled=!0,j.fulfillmentValue=b,i=!0,a(b))},h=function(a){return i?void 0:(j.isRejected=!0,j.rejectedReason=a,i=!0,b(a))}},a),k.push(f()),k.push(k[0].then(function(){k.splice(0,2),0===k.length&&c()})),j.cancel=function(){i||(i=!0,b.cancel(),b.fail(function(a){j.isRejected=!0,j.rejectedReason=a}))},j.then=function(){return b.then.apply(b,arguments)},void(j.push=function(a,b){var e,f=k[k.length-1];if(i)throw new d;return e=f.then(a,b),k.push(e),k.push(e.then(function(a){return k.splice(0,2),0!==k.length?a:void c(a)},function(a){if(k.splice(0,2),0!==k.length)throw a;h(a)})),this})):new g};g.prototype=Object.create(e.prototype),g.prototype.constructor=g,c.Queue=g,c.ResolvedQueueError=d}),a("rsvp/reject",["rsvp/promise","exports"],function(a,b){"use strict";function c(a){return new d(function(b,c){c(a)})}var d=a.Promise;b.reject=c}),a("rsvp/resolve",["rsvp/promise","exports"],function(a,b){"use strict";function c(a){return new d(function(b,c){if("object"==typeof a&&null!==a){var d=a.then;if(void 0!==d&&"function"==typeof d)return d.apply(a,[b,c])}return b(a)},function(){void 0!==a&&void 0!==a.cancel&&a.cancel()})}var d=a.Promise;b.resolve=c}),a("rsvp/rethrow",["exports"],function(a){"use strict";function b(a){throw c.setTimeout(function(){throw a}),a}var c="undefined"==typeof global?this:global;a.rethrow=b}),a("rsvp/timeout",["rsvp/promise","exports"],function(a,b){"use strict";function c(a,b,c){function d(d,e){g=setTimeout(function(){b?e(c):d(c)},a)}function e(){clearTimeout(g)}var g;return new f(d,e)}function d(a,b){return c(a,!1,b)}function e(a){return c(a,!0,"Timed out after "+a+" ms")}var f=a.Promise;f.prototype.delay=function(a){return this.then(function(b){return d(a,b)})},b.delay=d,b.timeout=e}),a("rsvp",["rsvp/events","rsvp/cancellation_error","rsvp/promise","rsvp/node","rsvp/all","rsvp/queue","rsvp/timeout","rsvp/hash","rsvp/rethrow","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){"use strict";function o(a,b){C[a]=b}var p=a.EventTarget,q=b.CancellationError,r=c.Promise,s=d.denodeify,t=e.all,u=e.any,v=f.Queue,w=f.ResolvedQueueError,x=g.delay,y=g.timeout,z=h.hash,A=i.rethrow,B=j.defer,C=k.config,D=l.resolve,E=m.reject;n.CancellationError=q,n.Promise=r,n.EventTarget=p,n.all=t,n.any=u,n.Queue=v,n.ResolvedQueueError=w,n.delay=x,n.timeout=y,n.hash=z,n.rethrow=A,n.defer=B,n.denodeify=s,n.configure=o,n.resolve=D,n.reject=E}),window.RSVP=b("rsvp")}(window);
\ No newline at end of file
html { background-color: black; }
a { color: #ff0066; } a:hover {text-decoration: underline;}
footer { position: absolute; bottom: 50px; right: 50px; }
strong {color: #ff0066}
body {
font-family: 'oswald', arial, serif;
background-color: white;
color: white;
font-size: 2em;
background: #1c1c1c;
background-image: -moz-radial-gradient(center 45deg, #333 0%, #1c1c1c 50%);
background-image: -moz-radial-gradient(center 45deg, #333 0%, #1c1c1c 50%);
}
/* transition effect */
section {
-moz-transition: left 400ms linear 0s;
-webkit-transition: left 400ms linear 0s;
-o-transition: left 400ms linear 0s;
-ms-transition: left 400ms linear 0s;
transition: left 400ms linear 0s;
}
section { left: -150%; }
section[aria-selected] { left: 0; }
section[aria-selected] ~ section { left: +150% }
.chapter { background-color: black;}
.chapter h1 {line-height: 600px; vertical-align: middle; margin: 0; text-align: center; display: block}
h1 {
margin: 50px 100px 0 100px;
font-size: 50px;
text-shadow: 0px -1px 0px #000;
text-align: left;
}
h2 {
color: #fae50b;
margin: 70px 0 0 0;
font-size: 40px;
text-align: center;
}
ul {
margin-top: 70px;
font-size: 35px;
text-align: right;
border-right: 4px solid white;
padding-right: 40px;
min-width: 310px;
margin-left: 50px;
display: inline-block;
}
q, p {
margin: 50px auto 0 auto;
width: 500px;
}
q:after {content: ""}
q:before {content: ""}
q {
display: block;
margin-top: 140px;
}
video {
position: absolute;
top: 210px;
width: 260px;
left: 445px;
box-shadow: 0 0 10px black;
}
#arrow {
position: absolute;
top: 165px;
left: 460px;
font-size: 100px;
color: white;
}
li {list-style-type: none}
* { margin: 0; padding: 0; }
details {display: none;}
body {
width: 800px; height: 600px;
margin-left: -400px; margin-top: -300px;
position: absolute; top: 50%; left: 50%;
overflow: hidden;
}
section {
position: absolute;
pointer-events: none;
width: 100%; height: 100%;
}
section[aria-selected] { pointer-events: auto;}
body {display: none}
body.loaded {display: block}
section.code pre { margin: 20px 0 0 40px;font-size: 15px; font-weight: bold;}
section.code .Constant { color: #af5fff}
section.code .StorageClass { color: #ff8700}
section.code .Exception { color: #87ff00}
section.code .Identifier { color: #ff8700}
section.code .Title { color: #d75f00}
section.code .String { color: #afaf87}
section.code .Type { color: #5fd7ff}
section.code .Statement { color: #d7005f}
section.code .Function { color: #87ff00}
section.code .Comment { color: #CCC}
/* Cedric extensions */
section img {max-width: 100%; max-height: 80%; display: block; margin-left: auto; margin-right: auto;}
\ No newline at end of file
var friendWindows = [];
var idx = 1;
var slides;
/* main() */
function dZmain() {
slides = document.querySelectorAll("body > section");
onhashchange();
setSlide();
document.body.className = "loaded";
onresize();
}
window.onload = dZmain;
/* Handle keys */
window.onkeydown = function(e) {
// Don't intercept keyboard shortcuts
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return;
}
if ( e.keyCode == 37 // left arrow
|| e.keyCode == 33 // page up
) {
e.preventDefault();
back();
}
if ( e.keyCode == 39 // right arrow
|| e.keyCode == 34 // page down
) {
e.preventDefault();
forward();
}
if ( e.keyCode == 32) { // space
e.preventDefault();
toggleContent();
}
}
/* Adapt the size of the slides to the window */
window.onresize = function() {
var sx = document.body.clientWidth / window.innerWidth;
var sy = document.body.clientHeight / window.innerHeight;
var transform = "scale(" + (1/Math.max(sx, sy)) + ")";
document.body.style.MozTransform = transform;
document.body.style.WebkitTransform = transform;
document.body.style.OTransform = transform;
document.body.style.msTransform = transform;
document.body.style.transform = transform;
}
function getDetails(idx) {
var s = document.querySelector("section:nth-of-type("+ idx +")");
var d = s.querySelector("details");
return d?d.innerHTML:"";
}
window.onmessage = function(e) {
msg = e.data;
win = e.source;
if (msg === "register") {
friendWindows.push(win);
win.postMessage(JSON.stringify({method: "registered", title: document.title, count: slides.length}), document.location);
win.postMessage(JSON.stringify({method: "newslide", details: getDetails(idx), idx: idx}), document.location);
return;
}
if (msg === "back") back();
if (msg === "forward") forward();
if (msg === "toggleContent") toggleContent();
// setSlide(42)
var r = /setSlide\((\d+)\)/.exec(msg);
if (r) {
idx = r[1];
setSlide();
}
}
/* If a Video is present in this new slide, play it.
If a Video is present in the previous slide, stop it. */
function toggleContent() {
var s = document.querySelector("section[aria-selected]");
if (s) {
var video = s.querySelector("video");
if (video) {
if (video.ended || video.paused) {
video.play();
} else {
video.pause();
}
}
}
}
/* If the user change the slide number in the URL bar, jump
to this slide. */
window.onhashchange = function(e) {
var newidx = ~~window.location.hash.split("#")[1];
if (!newidx) newidx = 1;
if (newidx == idx) return;
idx = newidx;
setSlide();
}
/* Slide controls */
function back() {
if (idx == 1) return;
idx--;
setSlide();
}
function forward() {
if (idx >= slides.length) return;
idx++;
setSlide();
}
function setSlide() {
var old = document.querySelector("section[aria-selected]");
var next = document.querySelector("section:nth-of-type("+ idx +")");
if (old) {
old.removeAttribute("aria-selected");
var video = old.querySelector("video");
if (video) { video.pause(); }
}
if (next) {
next.setAttribute("aria-selected", "true");
var video = next.querySelector("video");
if (video) { video.play(); }
} else {
console.warn("No such slide: " + idx);
idx = 0;
for (var i = 0; i < slides.length; i++) {
if (slides[i].hasAttribute("aria-selected")) {
idx = i + 1;
}
}
}
window.location.hash = idx;
for (var i = 0; i < friendWindows.length; i++) {
friendWindows[i].postMessage(JSON.stringify({method: "newslide", details: getDetails(idx), idx: idx}), document.location);
}
}
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