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