Typos.
[hacks/simpleWebSlides.git] / simpleWebSlides.js
1 /*
2   Namespace object.
3 */
4
5 var SWS = SWS || {};
6
7
8
9 SWS.Utils = new function () {
10     var self = this;
11
12     self.isUndefined = function (o) { return typeof o == "undefined"; };
13     self.push2 = function (t, i, v) {
14         if ((typeof t[i]) == 'undefined') {
15             t[i] = new Array();
16         };
17         var l = t[i].length;
18         t[i][l] = v;
19     };
20
21     self.isEmpty = function (o) {
22         for(var _ in o) return false;
23         return true;
24     };
25
26     self.parseFrameSpec = function (s) {
27         var elems = s.split("_");
28         var result = {};
29         var min_last = 10000;
30         var max_value = -1;
31         for(var i = 0; i < elems.length; i++){
32             var bounds = elems[i].split("-");
33             if (bounds.length > 2 || bounds.length == 0) return {};
34             if (bounds.length == 1) bounds[1] = bounds[0];
35             var a = parseInt(bounds[0]);
36             var b = parseInt(bounds[1])
37             if (!isFinite(a) || !isFinite(b)) return {};
38             a = Math.min(a, 1000); // don't allow more than 1000 frames/slide
39             b = Math.min(b, 1000);
40             if (b > max_value) max_value = b;
41             for (var j = a; j <= b; j++)
42                 result[j] = true;
43         };
44         return result;
45     };
46
47
48 };
49
50
51
52
53 SWS.Templates = new function () {
54     var self = this;
55     self.controlPanel = "<div id='sws-control-panel-canvas'><div id='sws-control-panel'>\
56 <div id='sws-control-panel-title-bar'>\
57 <a title='Toggle fullscreen' id='sws-control-panel-fullscreen' class='sws-symbol' onclick='SWS.Presentation.toggleFullScreen();'></a>\
58 <a title='Close panel' id='sws-control-panel-close' onclick='$(\"#sws-control-panel-canvas\").toggle();'>&#x2716;</a>\
59 </div>\
60 <div id='sws-control-panel-options'>\
61 <span title='Change the aspect ratio' class='sws-symbol' >&#x1f4bb;</span><select id='sws-aspect-select' onchange='SWS.Presentation.changeAspect();'>\
62 <option value='sws-aspect-4-3'>4:3</option>\
63 <option value='sws-aspect-16-9'>16:9</option>\
64 <option value='sws-aspect-16-10'>16:10</option>\
65 </select>\
66 <span title='Change the theme' class='sws-symbol'>&#x1f3a8;</span><select id='sws-theme-select' onchange='SWS.Presentation.changeTheme();'></select>\
67 </div>\
68 <div id='sws-control-panel-navigation'>\
69 <a title='First slide' class='sws-symbol' onclick='SWS.Presentation.goToSlide(SWS.Presentation.firstSlide());' >&#x23ee;</a>\
70 <a title='Previous slide' class='sws-symbol' onclick='SWS.Presentation.previousSlide();SWS.Presentation.refresh();'>&#x23ea;</a>\
71 <a title='Previous step' class='sws-symbol' style='-webkit-transform: scaleX(-1);' onclick='SWS.Presentation.previous();SWS.Presentation.refresh();'>&#x25b6;</a>\
72 <a title='Next step' class='sws-symbol' onclick='SWS.Presentation.next();SWS.Presentation.refresh();'>&#x25b6;</a>\
73 <a title='Next slide' class='sws-symbol' onclick='SWS.Presentation.nextSlide();SWS.Presentation.refresh();'>&#x23e9;</a>\
74 <a title='Last slide' class='sws-symbol' onclick='SWS.Presentation.goToSlide(SWS.Presentation.lastSlide());'>&#x23ed;</a>\
75 <br/>\
76 <span class='sws-symbol'>&#xe4ae;</span><input type='text' id='sws-control-panel-slide-input' oninput='SWS.Presentation.goToSlide($(\"#sws-control-panel-slide-input\").val()-1);'></input><span id='sws-control-panel-total-slides'></span>\
77 <input type='range' title='Navigate the presentation' id='sws-control-panel-navigation-bar' onchange='SWS.Presentation.navigate();' step='1'></input>\
78 </div>\
79 </div>\
80 </div>";
81     self.slideActivate = function (o) {
82         if (!(o.hasClass("sws-active-slide"))){
83             o.removeClass("sws-inactive-slide").addClass("sws-active-slide");
84         };
85     };
86
87     self.slideDeactivate = function (o) {
88         if (!(o.hasClass("sws-inactive-slide"))){
89             o.removeClass("sws-active-slide").addClass("sws-inactive-slide");
90         };
91     };
92
93     self.slideChange = function (from, to) {
94         var canvas = $(".sws-canvas");
95         self.slideDeactivate($(canvas[from]));
96         self.slideActivate($(canvas[to]));
97     };
98
99     self.objectActivate = function (o) {
100         if (!(o.hasClass("sws-active-object"))){
101             o.removeClass("sws-inactive-object").addClass("sws-active-object");
102             o.css({'visibility':'visible'});
103             return true;
104         };
105         return false;
106     };
107
108     self.objectDeactivate = function (o) {
109         if (!(o.hasClass("sws-inactive-object"))){
110             o.addClass("sws-inactive-object").removeClass("sws-active-object");
111             return true;
112         };
113         return false;
114     };
115
116     self.updateFooter = function (o) {
117         var footer = o.find(".sws-footer");
118         if (footer.length && (footer.children("*").length == 0)) {
119             var i = SWS.Presentation.getCurrentSlide();
120             var cur = $( "<span class='sws-current-slide-number'>"
121                          + (i + 1)
122                          +"</span>");
123             var sep = $( "<span class='sws-slide-num-sep' />");
124             var tot = $( "<span class='sws-last-slide-number'>"
125                          + (SWS.Presentation.getNumSlides())
126                          +"</span>");
127             footer.append(cur).append(sep).append(tot);
128         };
129     };
130     self.updateHeader = function (o) {};
131 };
132 SWS.ConfigBuilder = function () {
133     var self = this;
134     self['sws-object-activate'] = SWS.Templates.objectActivate;
135     self['sws-object-deactivate'] = SWS.Templates.objectDeactivate;
136     self['sws-slide-change'] = SWS.Templates.slideChange;
137     self['sws-update-footer'] = SWS.Templates.updateFooter;
138     self['sws-update-header'] = SWS.Templates.updateHeader;
139 };
140
141 SWS.Defaults = new SWS.ConfigBuilder ();
142
143 SWS.Config = new SWS.ConfigBuilder ();
144
145
146 SWS.Effects = new function () {
147     var self = this;
148
149     self.objectDeactivateFadeOut = function (o) {
150         o.animate({'opacity': '0'}, 200,
151                   function () { SWS.Templates.objectDeactivate(o)});
152     };
153
154     self.objectActivateFadeIn = function (o) {
155
156         if (SWS.Templates.objectActivate(o)){
157             o.animate({'opacity': '1' }, 200);
158         };
159
160     };
161
162     self.slideChangeHorizontalFlip = function (from, to){
163         var f = SWS.Presentation.getSlide(from);
164         var t = SWS.Presentation.getSlide(to);
165         f.animate({ 'left': '50%', 'width': '0pt', 'opacity':'0' }, 150,
166                   function  () {
167                       SWS.Templates.slideDeactivate(f);
168                       f.css({'left':'0%', 'width': '100%'});
169                       t.css({ 'left': '50%', 'width': '0pt','opacity':'0' });
170                       SWS.Templates.slideActivate(t);
171                       t.animate({'left':'0%', 'width': '100%','opacity':'1'});
172                   });
173     };
174     self.slideChangeFadeOutIn = function (from, to) {
175         var f = SWS.Presentation.getSlide(from);
176         var t = SWS.Presentation.getSlide(to);
177         f.animate({ 'opacity': '0'}, 150,
178                   function () { SWS.Templates.slideDeactivate(f);
179                                 SWS.Templates.slideActivate(t);
180                                 t.css('opacity', '0');
181                                 t.animate({ 'opacity': '1'}, 150);
182                               });
183     };
184     self.slideChangeHorizontalSlide = function (from, to) {
185         var f = SWS.Presentation.getSlide(from);
186         var t = SWS.Presentation.getSlide(to);
187         if (from < to) {
188             t.css('left', '100%');
189             t.css('opacity', '1');
190             SWS.Templates.slideActivate(t);
191             f.animate({ 'left': '-100%' }, 250, function () { SWS.Templates.slideDeactivate(f);
192                                                               f.css('opacity', '0');
193                                                               t.animate({ 'left': '0%' }, 250);
194                                                             });
195         } else {
196             t.css('left', '-100%');
197             SWS.Templates.slideActivate(t);
198             f.animate({ 'left': '100%' }, 250, function () { SWS.Templates.slideDeactivate(f);
199                                                              f.css('opacity', '0');
200                                                            });
201             t.css('opacity', '1');
202             t.animate({ 'left': '0%' }, 250);
203         };
204     };
205
206
207     self.slideChangeVerticalSlide = function (from, to) {
208         var f = SWS.Presentation.getSlide(from);
209         var t = SWS.Presentation.getSlide(to);
210         if (from < to) {
211             t.css('top', '100%');
212             SWS.Templates.slideActivate(t);
213             f.animate({ 'top': '-100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
214             t.animate({ 'top': '0%' }, 250);
215         } else {
216             t.css('top', '-100%');
217             SWS.Templates.slideActivate(t);
218             f.animate({ 'top': '100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
219             t.animate({ 'top': '0%' }, 250);
220         };
221     };
222
223 };
224
225 SWS.Fullscreen = new function () {
226     var self = this;
227
228     if (SWS.Utils.isUndefined(document.fullScreen)) {
229         if (SWS.Utils.isUndefined(document.mozfullScreen)) {
230             self.status = function () { return document.webkitIsFullScreen; };
231             self.enter = function(e) {
232                 e.webkitRequestFullScreen();
233             };
234             self.exit = function () {
235                 document.webkitCancelFullScreen();
236             };
237
238         } else {
239             self.status = function () { return document.mozfullScreen; };
240             self.enter = function(e) {
241                 e.mozRequestFullScreen();
242             };
243             self.exit = function () {
244                 document.mozCancelFullScreen();
245             };
246
247         };
248     } else {
249             self.status = function () { return document.fullScreen; };
250             self.enter = function(e) {
251                 e.requestFullScreen();
252             };
253             self.exit = function () {
254                 document.cancelFullScreen();
255             };
256
257     };
258
259
260 };
261
262 SWS.Presentation = new function () {
263
264
265     var self = this;
266
267     //TODO move outside of the Presentation object
268
269
270     var _total_slides;
271     var _initialized = false;
272     var _disable_input_events = false;
273
274     var _slide_callbacks = new Array ();
275
276
277     self.getNumSlides = function () { return _total_slides; };
278
279     self.getSlide = function(i) {
280         return $($(".sws-canvas")[i]);
281     };
282
283     self.registerCallback = function (i, f) {
284         if (_initialized) return;
285         //jQuery does not seem to work well
286         //on a partial DOM
287
288         var slide_num = $(".sws-slide").length - 1;
289
290         SWS.Utils.push2(_slide_callbacks, slide_num,{ 'fn': f, 'frame': i });
291
292     };
293
294     if (typeof(Storage)!=="undefined"){
295         self.getCurrentSlide = function () {
296             //unary + casts to integer
297             var i = +(sessionStorage.getItem("current_slide"));
298             if (!(i >= 0 && i < self.getNumSlides())){
299                 return 0;
300             } else {
301                 return i;
302             };
303         };
304
305         self.setCurrentSlide = function (i) {
306             sessionStorage.setItem("current_slide", i);
307         };
308
309     } else {
310         var _current_slide = 0;
311         self.getCurrentSlide = function () { return _current_slide; };
312         self.setCurrentSlide = function (i) { _current_slide = i; };
313
314     };
315     self.firstSlide = function () { return 0; };
316     self.lastSlide = function () { return self.getNumSlides() - 1; };
317     self.refresh = function () {
318         /* block upcoming input event until all animations are finished */
319         _disable_input_events = true;
320
321         var canvas = $(".sws-canvas");
322         var from_slide_num = canvas.index($(".sws-active-slide"));
323         var to_slide_num = self.getCurrentSlide();
324         var watch_slide_anim = false;
325         var to_slide = $(canvas[to_slide_num]);
326         var from_slide = $(canvas[from_slide_num]);
327         var slide_change = (from_slide_num != to_slide_num);
328
329         var info = to_slide.data("sws-frame-info");
330         SWS.Config['sws-update-header'](to_slide);
331         SWS.Config['sws-update-footer'](to_slide);
332
333         if (slide_change) {
334             //Launch a slide transition:
335             SWS.Config['sws-slide-change'](from_slide_num, to_slide_num);
336             watch_slide_anim = true;
337             for (var i = 0; i < info.callbacks.at_slide.length;i++){
338                 info.callbacks.at_slide[i](to_slide);
339             };
340         };
341
342
343         var cur = info.current;
344         var custom = info.custom;
345         var real_slide = to_slide.find(".sws-slide");
346
347         real_slide.find("*").andSelf().each(function (i){
348             var frameset = $(this).data("sws-frame-set") || {};
349             if (frameset[cur])
350                 SWS.Config['sws-object-activate']($(this));
351             else
352                 SWS.Config['sws-object-deactivate']($(this));
353         });
354
355         var callbacks;
356         if (callbacks = info.callbacks.at_frame[self.getCurrentFrame()]){
357             for (var k = 0; k < callbacks.length; k++)
358                 callbacks[k]($(to_slide));
359         };
360
361         var all = $(from_slide).add(to_slide);
362         all.find("*").addBack().promise().done(function() {
363             _disable_input_events = false;
364         });
365     };
366
367     self.nextSlide = function () {
368         self.setCurrentSlide(Math.min(self.getCurrentSlide()+1,
369                                       self.lastSlide()));
370         self.setCurrentFrame(self.firstFrame());
371     };
372
373     self.previousSlide = function () {
374         self.setCurrentSlide(Math.max(self.getCurrentSlide()-1,
375                                       self.firstSlide()));
376         self.setCurrentFrame(self.firstFrame());
377     };
378
379     self.getFrameInfo = function () {
380
381         var i = self.getCurrentSlide();
382         var canvas = $($(".sws-canvas")[i]);
383         var infos = canvas.data("sws-frame-info");
384         return infos;
385     };
386     self.getCurrentFrame = function () { return self.getFrameInfo().current; };
387     self.setCurrentFrame = function (i) { self.getFrameInfo().current = i; };
388     self.firstFrame = function () { return 0; };
389     self.lastFrame = function () { return self.getFrameInfo().last; };
390
391     self.nextFrame = function () {
392         self.setCurrentFrame(Math.min(self.getCurrentFrame()+1,
393                                       self.lastFrame()));
394
395     };
396     self.previousFrame = function () {
397         self.setCurrentFrame(Math.max(self.getCurrentFrame()-1,
398                                        self.firstFrame()));
399     };
400
401     self.next = function () {
402         var i = self.getCurrentFrame();
403         if (i == self.lastFrame()) {
404             self.nextSlide();
405             self.setCurrentFrame(self.firstFrame());
406         } else
407             self.nextFrame();
408     };
409
410     self.previous = function () {
411         var i = self.getCurrentFrame();
412         if (i == self.firstFrame()){
413             self.previousSlide();
414             self.setCurrentFrame(self.lastFrame());
415         }
416         else
417             self.previousFrame();
418     };
419
420     self.goToSlide = function (s, f) {
421         if (SWS.Utils.isUndefined(f))
422             f = 0;
423         if (!(s >= self.firstSlide() && s <= self.lastSlide())) return;
424         self.setCurrentSlide(s);
425         if (!(f >= self.firstFrame() && f <= self.lastFrame())) f = 0;
426         self.setCurrentFrame(f);
427         self.refresh();
428     };
429
430     self.cycleStyle = function() {
431         var styles = $("head").children('link[rel$="stylesheet"][title]');
432         var j = styles.index(styles.filter(':not(:disabled)'));
433         styles[j].disabled = true;
434         if (++j == styles.length) j = 0;
435         styles[j].disabled = false;
436     };
437
438     self.inputHandler = function (event) {
439         if (_disable_input_events) return;
440         switch (event.which) {
441         case 36:/* Home */
442             self.setCurrentSlide(self.firstSlide());
443             break;
444
445         case 35:/* End */
446             self.setCurrentSlide(self.lastSlide());
447             break;
448
449         case 32: /* space */
450         case 39: /* -> */
451             if (self.getCurrentSlide() == self.lastSlide()
452                 && self.getCurrentFrame() == self.lastFrame()) return;
453             self.next();
454             break;
455         case 34: /* PgDown */
456         case 78: /* n */
457             self.nextSlide();
458             break;
459         case 8: /* backspace */
460         case 37: /* <-   */
461             self.previous();
462             break;
463         case 33: /* PgUp */
464         case 80: /* p */
465             self.previousSlide();
466             break;
467         case 83: /* s */
468             self.cycleStyle();
469             return;
470         case 67: /* c */
471             $("#sws-control-panel-canvas").toggle();
472         default:
473             return;
474         };
475         self.refresh();
476     };
477
478
479
480     function init_canvas(canvas, custom) {
481         var cur_frame = 0;
482         var last_frame = canvas.find(".sws-pause").length;
483         //Add all regular elements to the frame list
484         var slide = $(canvas.find(".sws-slide")[0]);
485
486         var callbacks = { at_slide : new Array(),
487                           at_frame : new Array() }
488
489         if (SWS.Utils.isUndefined(custom)) {
490             custom = new Array ();
491         };
492
493         for (var i = 0; i < custom.length; i++) {
494             if (isFinite(custom[i].frame)){
495                 var num = +(custom[i].frame);
496                 if (num > last_frame) last_frame = num;
497                 SWS.Utils.push2(callbacks.at_frame, num, custom[i].fn);
498             } else if (custom[i].frame == "slide")
499                 callbacks.at_slide.push(custom[i].fn);
500             else {
501                 var frame_set = SWS.Utils.parseFrameSpec(custom[i].frame);
502                 for(var f in frame_set){
503                     if (f > last_frame) last_frame = f;
504                     SWS.Utils.push2(callbacks.at_frame, +(f), custom[i].fn);
505                 };
506             }
507         };
508
509         var specials = null;
510
511         slide.find('*[class*="sws-onframe-"]').each(function(_){
512             var cls = $(this).attr('class');
513             var idx = cls.indexOf("sws-onframe-");
514             if (idx >= 0) {
515                 var end = cls.indexOf(" ", idx);
516                 end = (end == -1) ? cls.length : end;
517                 var spec = cls.substring(idx+12, end);
518                 var o = SWS.Utils.parseFrameSpec(spec);
519                 for(var f in o)
520                     if (f > last_frame) last_frame = f;
521                 $(this).find("*").andSelf().each(function(_){
522                     if (!SWS.Utils.isEmpty(o))
523                         $(this).data("sws-frame-set", o);
524                     if (specials)
525                         specials.add($(this));
526                     else
527                         specials = $(this);
528                 });
529             };
530         });
531
532         slide.find("*").andSelf().not(specials).each(function(i) {
533             if ($(this).hasClass("sws-pause"))  cur_frame++;
534             var o = {};
535             for (var j = cur_frame; j <= last_frame; j++)
536                 o[ j ] = true;
537             if (!SWS.Utils.isEmpty(o))
538                 $(this).data("sws-frame-set", o);
539         });
540
541         canvas.data("sws-frame-info", { current: 0,
542                                         last: last_frame,
543                                         callbacks: callbacks
544                                       });
545
546     };
547
548     /* Forces redrawing the page without reloading */
549     self.redraw = function () {
550         $("body").hide();
551         $("body").show();
552     };
553
554     self.changeAspect = function() {
555         $("html").removeClass("sws-aspect-4-3")
556             .removeClass("sws-aspect-16-9")
557             .removeClass("sws-aspect-16-10")
558             .addClass($("#sws-aspect-select").val());
559         self.redraw();
560     };
561
562
563     self.changeTheme = function () {
564         var theme_name = $("#sws-theme-select").val();
565         $("link.sws-theme").each (function (i) {
566             var e = $(this);
567             var title =  e.attr("title");
568             e[0].disabled = (title != theme_name);
569         });
570         self.redraw();
571     };
572
573
574     var _fullscreen_icon_on = "&#xe746;";
575     var _fullscreen_icon_off = "&#xe744;";
576
577     self.toggleFullScreen = function () {
578         if (SWS.Fullscreen.status()) {
579             SWS.Fullscreen.exit();
580             $("a#sws-control-panel-fullscreen")
581                 .html(_fullscreen_icon_off);
582
583
584
585         } else {
586             SWS.Fullscreen.enter($("body")[0]);
587             $("a#sws-control-panel-fullscreen")
588                 .html(_fullscreen_icon_on);
589         };
590     };
591     function _update_ui() {
592         var nav = $('#sws-control-panel-navigation-bar');
593         nav.val(SWS.Presentation.getCurrentSlide() + 1);
594         $('#sws-control-panel-slide-input').val(nav.val());
595     }
596     self.navigate = function () {
597         self.goToSlide($("#sws-control-panel-navigation-bar").val()-1);
598         _update_ui();
599     };
600
601
602     self.init = function () {
603
604
605
606         $(window).resize(self.redraw);
607
608
609         $(window).bind('storage', function (e) {
610             console.log(e);
611         });
612
613         _total_slides = $(".sws-slide").length;
614
615         $(document).keydown(self.inputHandler);
616
617
618         var cur = self.getCurrentSlide();
619         $(".sws-slide").each (function (i) {
620             var par = $(this).parent();
621
622             $(this).remove();
623             var canvas = $('<div class="sws-canvas"/>');
624
625             if (!($(this).hasClass("sws-option-noheader"))) {
626                 canvas.append($('<div class="sws-header"/><br/>'));
627             };
628
629             var inner = $('<div class="sws-inner-canvas"/>');
630             var content = $('<div class="sws-content"/>');
631             $(this).find('script[type="text/javascript"]').remove();
632             content.append($(this));
633             inner.append(content);
634             inner.append($('<span class="sws-vertical-align"> </span><br/>'));
635             canvas.append(inner);
636
637             if (!($(this).hasClass("sws-option-nofooter"))) {
638                 canvas.append($('<div class="sws-footer"/>'));
639             };
640
641             par.append(canvas);
642
643             if (i == cur) {
644                 canvas
645                     .addClass("sws-active-slide")
646                     .removeClass("sws-inactive-slide");
647             } else {
648                 canvas
649                     .addClass("sws-inactive-slide")
650                     .removeClass("sws-active-slide");
651             };
652             init_canvas(canvas,_slide_callbacks[i]);
653
654         });
655
656
657
658         // Initialize the control panel
659         $("body").append($(SWS.Templates.controlPanel));
660         // Fill the theme switcher
661         $("link.sws-theme").each (function (i) {
662             var e = $(this);
663             var opt = "<option value='";
664             opt += e.attr("title");
665             opt += "' ";
666             if (e.attr("rel") == "stylesheet") {
667                 opt+= "selected='selected'";
668             };
669             opt += ">" + e.attr("title") + "</option>";
670             $("#sws-theme-select").append($(opt));
671         });
672         // Set the fullscreen icon
673         if (SWS.Fullscreen.status()) {
674             $("a#sws-control-panel-fullscreen")
675                 .html(_fullscreen_icon_on);
676         } else {
677             $("a#sws-control-panel-fullscreen")
678                 .html(_fullscreen_icon_off);
679         };
680         // Put the navigation range at the correct position
681         var nav = $('#sws-control-panel-navigation-bar');
682         nav.attr("min", SWS.Presentation.firstSlide() + 1);
683         nav.attr("max", SWS.Presentation.lastSlide() + 1);
684         $('#sws-control-panel-total-slides').text('/' + SWS.Presentation.getNumSlides());
685         _update_ui();
686
687         _slide_callbacks = null; /* avoid a leak */
688         self.refresh();
689         _initialized = true;
690
691
692     };
693
694 };