Working and clean-up 'on-slide' specification.
[hacks/simpleWebSlides.git] / simpleWebSlides.js
1 /*
2   Namespace object.
3 */
4
5 var 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     self.slideActivate = function (o) {
48         if (!(o.hasClass("sws-active-slide"))){
49             o.removeClass("sws-inactive-slide").addClass("sws-active-slide");
50         };
51     };
52
53     self.slideDeactivate = function (o) {
54         if (!(o.hasClass("sws-inactive-slide"))){
55             o.removeClass("sws-active-slide").addClass("sws-inactive-slide");
56         };
57     };
58
59     self.slideChange = function (from, to) {
60         var canvas = $(".sws-canvas");
61         self.slideDeactivate($(canvas[from]));
62         self.slideActivate($(canvas[to]));
63     };
64
65     self.objectActivate = function (o) {
66         if (!(o.hasClass("sws-active-object"))){
67             o.removeClass("sws-inactive-object").addClass("sws-active-object");
68             o.css({'visibility':'visible'});
69             return true;
70         };
71         return false;
72     };
73
74     self.objectDeactivate = function (o) {
75         if (!(o.hasClass("sws-inactive-object"))){
76             o.addClass("sws-inactive-object").removeClass("sws-active-object");
77             o.css({'visibility':'hidden'});
78             return true;
79         };
80         return false;
81     };
82     self.updateFooter = function (o) {
83         var footer = o.find(".sws-footer");
84         if (footer.length && (footer.children("*").length == 0)) {
85             var i = SWS.Presentation.getCurrentSlide();
86             var cur = $( "<span class='sws-current-slide-number'>"
87                          + (i + 1)
88                          +"</span>");
89             var sep = $( "<span class='sws-slide-num-sep' />");
90             var tot = $( "<span class='sws-current-slide-number'>"
91                          + (SWS.Presentation.getNumSlides())
92                          +"</span>");
93             footer.append(cur).append(sep).append(tot);
94         };
95     };
96     self.updateHeader = function (o) {};
97 };
98
99
100 SWS.Effects = new function () {
101     var self = this;
102
103     self.objectDeactivateFadeOut = function (o) {
104         o.animate({'opacity': '0'}, 150,
105                   function () { SWS.Utils.objectDeactivate(o)});
106     };
107
108     self.objectActivateFadeIn = function (o) {
109
110         if (SWS.Utils.objectActivate(o)){
111             o.animate({'opacity': '1' }, 150);
112         };
113
114     };
115
116     self.slideChangeHorizontalFlip = function (from, to){
117         var f = SWS.Presentation.getSlide(from);
118         var t = SWS.Presentation.getSlide(to);
119         f.animate({ 'left': '50%', 'width': '0pt', 'opacity':'0' }, 150,
120                   function  () {
121                       SWS.Utils.slideDeactivate(f);
122                       f.css({'left':'0%', 'width': '100%'});
123                       t.css({ 'left': '50%', 'width': '0pt','opacity':'0' });
124                       SWS.Utils.slideActivate(t);
125                       t.animate({'left':'0%', 'width': '100%','opacity':'1'});
126                   });
127     };
128     self.slideChangeFadeOutIn = function (from, to) {
129         var f = SWS.Presentation.getSlide(from);
130         var t = SWS.Presentation.getSlide(to);
131         f.animate({ 'opacity': '0'}, 150,
132                   function () { SWS.Utils.slideDeactivate(f);
133                                 SWS.Utils.slideActivate(t);
134                                 t.css('opacity', '0');
135                                 t.animate({ 'opacity': '1'}, 150);
136                               });
137     };
138     self.slideChangeHorizontalSlide = function (from, to) {
139         var f = SWS.Presentation.getSlide(from);
140         var t = SWS.Presentation.getSlide(to);
141         if (from < to) {
142             t.css('left', '100%');
143             SWS.Utils.slideActivate(t);
144             f.animate({ 'left': '-100%' }, 250, function () { SWS.Utils.slideDeactivate(f); });
145             t.animate({ 'left': '0%' }, 250);
146         } else {
147             t.css('left', '-100%');
148             SWS.Utils.slideActivate(t);
149             f.animate({ 'left': '100%' }, 250, function () { SWS.Utils.slideDeactivate(f); });
150             t.animate({ 'left': '0%' }, 250);
151         };
152     };
153
154
155     self.slideChangeVerticalSlide = function (from, to) {
156         var f = SWS.Presentation.getSlide(from);
157         var t = SWS.Presentation.getSlide(to);
158         if (from < to) {
159             t.css('top', '100%');
160             SWS.Utils.slideActivate(t);
161             f.animate({ 'top': '-100%' }, 250, function () { SWS.Utils.slideDeactivate(f); });
162             t.animate({ 'top': '0%' }, 250);
163         } else {
164             t.css('top', '-100%');
165             SWS.Utils.slideActivate(t);
166             f.animate({ 'top': '100%' }, 250, function () { SWS.Utils.slideDeactivate(f); });
167             t.animate({ 'top': '0%' }, 250);
168         };
169     };
170
171 };
172
173
174 SWS.Presentation = new function () {
175
176
177     var self = this;
178
179     //TODO move outside of the Presentation object
180     var templates = {};
181
182
183     var _total_slides;
184     var _initialized = false;
185     var _disable_input_events = false;
186
187     var _slide_callbacks = new Array ();
188
189
190
191
192     //Public methods
193     self.setTemplate = function (tn, fn) {
194         templates[tn] = fn;
195     };
196
197     self.getNumSlides = function () { return _total_slides; };
198
199     self.getSlide = function(i) {
200         return $($(".sws-canvas")[i]);
201     };
202
203     self.registerCallback = function (i, f) {
204         if (_initialized) return;
205         //jQuery does not seem to work well
206         //on a partial DOM
207
208         var slide_num = $(".sws-slide").length - 1;
209
210         SWS.Utils.push2(_slide_callbacks, slide_num,{ 'fn': f, 'frame': i });
211
212     };
213
214     if (typeof(Storage)!=="undefined"){
215         self.getCurrentSlide = function () {
216             //unary + casts to integer
217             var i = +(sessionStorage.current_slide);
218             if (!(i >= 0 && i < self.getNumSlides())){
219                 return 0;
220             } else {
221                 return i;
222             };
223         };
224
225         self.setCurrentSlide = function (i) {
226             sessionStorage.current_slide = i;
227         };
228
229     } else {
230         var _current_slide = 0;
231         self.getCurrentSlide = function () { return _current_slide; };
232         self.setCurrentSlide = function (i) { _current_slide = i; };
233
234     };
235     self.firstSlide = function () { return 0; };
236     self.lastSlide = function () { return self.getNumSlides() - 1; };
237
238     self.refresh = function () {
239         /* block upcoming input event until all animations are finished */
240         _disable_input_events = true;
241
242         var canvas = $(".sws-canvas");
243         var from_slide_num = canvas.index($(".sws-active-slide"));
244         var to_slide_num = self.getCurrentSlide();
245         var watch_slide_anim = false;
246         var to_slide = $(canvas[to_slide_num]);
247         var slide_change = (from_slide_num != to_slide_num);
248
249         var info = to_slide.data("sws-frame-info");
250         if (slide_change) {
251             //Launch a slide transition:
252             templates['sws-slide-change'](from_slide_num, to_slide_num);
253             watch_slide_anim = true;
254             templates['sws-update-header'](to_slide);
255             templates['sws-update-footer'](to_slide);
256             for (var i = 0; i < info.callbacks.at_slide.length;i++){
257                 info.callbacks.at_slide[i](to_slide);
258             };
259         };
260
261
262         var cur = info.current;
263         var custom = info.custom;
264         var real_slide = to_slide.children(".sws-slide");
265
266         to_slide.children(".sws-slide").find("*").andSelf().each(function (i){
267             var frameset = $(this).data("sws-frame-set") || {};
268             if (frameset[cur])
269                 templates['sws-object-activate']($(this));
270             else
271                 templates['sws-object-deactivate']($(this));
272         });
273
274         var callbacks;
275         if (callbacks = info.callbacks.at_frame[self.getCurrentFrame()]){
276             for (var k = 0; k < callbacks.length; k++)
277                 callbacks[k]($(to_slide));
278         };
279
280         var to_watch = $(to_slide).find("*");
281         if (watch_slide_anim) {
282             to_watch = to_watch.add(to_slide).add($(canvas[from_slide_num]));
283         };
284
285         to_watch.find("*").promise().done(function() {
286             _disable_input_events = false;
287         });
288     };
289
290     self.nextSlide = function () {
291         self.setCurrentSlide(Math.min(self.getCurrentSlide()+1,
292                                       self.lastSlide()));
293     };
294
295     self.previousSlide = function () {
296         self.setCurrentSlide(Math.max(self.getCurrentSlide()-1,
297                                       self.firstSlide()));
298     };
299
300     self.getFrameInfo = function () {
301
302         var i = self.getCurrentSlide();
303         var canvas = $($(".sws-canvas")[i]);
304         var infos = canvas.data("sws-frame-info");
305         return infos;
306     };
307     self.getCurrentFrame = function () { return self.getFrameInfo().current; };
308     self.setCurrentFrame = function (i) { self.getFrameInfo().current = i; };
309     self.firstFrame = function () { return 0; };
310     self.lastFrame = function () { return self.getFrameInfo().last; };
311
312     self.nextFrame = function () {
313         self.setCurrentFrame(Math.min(self.getCurrentFrame()+1,
314                                       self.lastFrame()));
315
316     };
317     self.previousFrame = function () {
318         self.setCurrentFrame(Math.max(self.getCurrentFrame()-1,
319                                        self.firstFrame()));
320     };
321
322     self.next = function () {
323         var i = self.getCurrentFrame();
324         if (i == self.lastFrame())
325             self.nextSlide();
326         else
327             self.nextFrame();
328     };
329
330     self.previous = function () {
331         var i = self.getCurrentFrame();
332         if (i == self.firstFrame())
333             self.previousSlide();
334         else
335             self.previousFrame();
336     };
337
338     self.inputHandler = function (event) {
339         if (_disable_input_events) return;
340         switch (event.which) {
341         case 36:/* Home */
342             self.setCurrentSlide(self.firstSlide());
343             break;
344
345         case 35:/* End */
346             self.setCurrentSlide(self.lastSlide());
347             break;
348
349         case 32: /* space */
350         case 39: /* -> */
351         case 1: /* mouse button 1 */
352             self.next();
353             break;
354         case 34: /* PgDown */
355         case 78: /* n */
356             self.nextSlide();
357             break;
358         case 8: /* backspace */
359         case 37: /* <-   */
360             self.previous();
361             break;
362         case 33: /* PgUp */
363         case 80: /* p */
364             self.previousSlide();
365             break;
366         default:
367             return;
368         };
369         self.refresh();
370     };
371
372
373
374     function init_canvas(canvas, custom) {
375         var cur_frame = 0;
376         var last_frame = canvas.find(".sws-pause").length;
377         //Add all regular elements to the frame list
378         var slide = $(canvas.children(".sws-slide")[0]);
379
380         var callbacks = { at_slide : new Array(),
381                           at_frame : new Array() }
382
383         if (SWS.Utils.isUndefined(custom)) {
384             custom = new Array ();
385         };
386
387         for (var i = 0; i < custom.length; i++) {
388             if (isFinite(custom[i].frame)){
389                 var num = +(custom[i].frame);
390                 if (num > last_frame) last_frame = num;
391                 SWS.Utils.push2(callbacks.at_frame, num, custom[i].fn);
392             } else if (custom[i].frame == "slide")
393                 callbacks.at_slide.push(custom[i].fn);
394             else {
395                 var frame_set = SWS.Utils.parseFrameSpec(custom[i].frame);
396                 for(var f in frame_set){
397                     if (f > last_frame) last_frame = f;
398                     SWS.Utils.push2(callbacks.at_frame, +(f), custom[i].fn);
399                 };
400             }
401         };
402
403         var specials = slide.find('*[class*="sws-onslide-"]').find("*").andSelf();
404         specials.each(function(i) {
405             var cls = $(this).attr('class');
406             var idx = cls.indexOf("sws-onslide-");
407             if (idx >= 0) {
408                 var end = cls.indexOf(" ", idx);
409                 end = (end == -1) ? cls.length : end;
410                 var spec = cls.substring(idx+12, end);
411                 var o = SWS.Utils.parseFrameSpec(spec);
412                 for(var f in o)
413                     if (f > last_frame) last_frame = f;
414
415                 if (!SWS.Utils.isEmpty(o))
416                     $(this).data("sws-frame-set", o);
417             };
418         });
419
420         slide.find("*").andSelf().not(specials).each(function(i) {
421             if ($(this).hasClass("sws-pause"))  cur_frame++;
422             var o = {};
423             for (var j = cur_frame; j <= last_frame; j++)
424                 o[ j ] = true;
425             if (!SWS.Utils.isEmpty(o))
426                 $(this).data("sws-frame-set", o);
427         });
428
429         canvas.data("sws-frame-info", { current: 0,
430                                         last: last_frame,
431                                         callbacks: callbacks
432                                       });
433
434     };
435
436     function init () {
437
438         _total_slides = $(".sws-slide").length;
439
440         $(document).keydown(self.inputHandler);
441         $(document).mousedown(self.inputHandler);
442
443         var cur = self.getCurrentSlide();
444         $(".sws-slide").each (function (i) {
445             var par = $(this).parent();
446
447             $(this).remove();
448             var canvas = $('<div class="sws-canvas"/>');
449
450             if (!($(this).hasClass("sws-option-noheader"))) {
451                 canvas.append($('<div class="sws-header"/>'));
452             };
453             $(this).find('script[type="text/javascript"]').remove();
454             canvas.append($(this));
455             if (!($(this).hasClass("sws-option-nofooter"))) {
456                 canvas.append($('<div class="sws-footer"/>'));
457             };
458             par.append(canvas);
459
460             if (i == cur) {
461                 canvas
462                     .addClass("sws-active-slide")
463                     .removeClass("sws-inacitve-slide");
464             } else {
465                 canvas
466                     .addClass("sws-inactive-slide")
467                     .removeClass("sws-active-slide");
468             };
469             init_canvas(canvas,_slide_callbacks[i]);
470
471         });
472         _slide_callbacks = null; /* avoid a leak */
473         self.refresh();
474         _initialized = true;
475
476     };
477
478     self.init = function () {
479         $(document).ready(init);
480     };
481
482
483
484     self.setTemplate('sws-object-activate', SWS.Utils.objectActivate);
485     self.setTemplate('sws-object-deactivate', SWS.Utils.objectDeactivate);
486     self.setTemplate('sws-slide-change', SWS.Utils.slideChange);
487     self.setTemplate('sws-update-footer', SWS.Utils.updateFooter);
488     self.setTemplate('sws-update-header', SWS.Utils.updateHeader);
489
490 };