Contact: fumanchu@aminus.org

Log in as guest/misc to create tickets

root/jitter.js

Revision 171 (checked in by fumanchu, 3 years ago)

Jitter: fixed a bug in jump_backward.

  • Property svn:eol-style set to native
Line 
1 // Jitter, a jQuery-powered presentation library.
2
3 String.prototype.resolve = function (vars) {
4     function repl(s, v) { return vars[v]; }
5     return this.replace(/\{([^}]+)\}/g, repl);
6 };
7
8 function set(a) {
9     // Convert the given array into a set.
10     var o = {};
11     for (var i=0; i < a.length; i++) o[a[i]] = '';
12     return o;
13 }
14
15 function items(obj) {
16     // Return a list of (key, value) pairs for the given object
17     var result = [];
18     if (typeof(obj) == 'object') {
19         for (var key in obj) result[result.length] = [key, obj[key]];
20     } else {
21         for (var i=0; i < obj.length; i++) result.push(obj[i]);
22     }
23     return result;
24 };
25
26
27 // Some jQuery plugins:
28
29 jQuery.fn.vcenter = function(cssFloat) {
30     return this.each(function() {
31         var j = $(this);
32         if (cssFloat) this.style.cssFloat = cssFloat;
33         var h = j.height();
34         var ph = j.parent().height();
35         var mt = ((ph / 2) - (h / 2));
36         this.style.marginTop = mt.toString() + 'px';
37     });
38 };
39
40 jQuery.fn.center = function() {
41     // Returns position() plus half height/width.
42     var results;
43     if (this[0]) {
44         results = this.position();
45         var mrg = this.marginPos();
46         results.top = results.top + mrg.top + (this.height() / 2);
47         results.left = results.left + mrg.left + (this.width() / 2);
48     }
49     return results;
50 };
51
52 jQuery.fn.stackpos = function(target) {
53     // Return a position()-like {top, left} object for stacking this object
54     // on the given target.
55     var results;
56     if (this[0] && target[0]) {
57         results = target.center();
58         results.top -= (this.height() / 2);
59         results.left -= (this.width() / 2);
60     }
61     return results;
62 };
63
64 jQuery.fn.stack = function(target) {
65     // Stack the (absolutely-positioned) self on the given target
66     return this.each(function() {
67         var elem = $(this);
68         elem.css(elem.stackpos(target));
69     });
70 };
71
72 jQuery.fn.marginPos = function(recurse) {
73     // Return a position()-like {top, left} object for marginTop/Left.
74     var results;
75     if (this[0]) {
76         var mt = 0, ml = 0;
77         if (this[0].style) {
78             mt = this[0].style.marginTop.match(/([0-9.-]+)px/);
79             mt = mt ? parseInt(mt[1]) : 0;
80             ml = this[0].style.marginLeft.match(/([0-9.-]+)px/);
81             ml = ml ? parseInt(ml[1]) : 0;
82         }
83        
84         if (recurse) {
85             if (this[0].parentNode != null) {
86                 var pmrg = $(this[0].parentNode).marginPos(recurse);
87                 mt = mt + pmrg.top;
88                 ml = ml + pmrg.left;
89             }
90         }
91        
92         results = {top: mt, left: ml};
93     }
94     return results;
95 };
96
97 jQuery.fn.detach = function(rehome) {
98     // Make all matching elements into position:absolute ones.
99     // Keep them in the same window position.
100     // If 'rehome' is true, unhook the element from its current
101     // container and re-attach it to the body element.
102     return this.each(function() {
103         var j = $(this);
104         var pos = j.position();
105         var mrg = j.marginPos(rehome);
106         j.css({position: 'absolute', cssFloat: 'none',
107                top: pos.top + mrg.top, left: pos.left + mrg.left,
108                marginTop: 0, marginLeft: 0
109                });
110         if (rehome) j.remove().appendTo("body");
111     });
112 };
113
114 jQuery.fn.fadeAway = function(speed, callback) {
115     var self = this;
116     return self.fadeOut(speed, function () { self.remove(); if (callback) callback(); });
117 };
118
119 jQuery.fn.grow = function(factor, speed) {
120     return this.each(function() {
121         var j = $(this);
122         j.animate({width: (j.width() * factor), height: (j.height() * factor)}, speed);
123     });
124 };
125
126
127 // The Jitter namespace itself.
128
129 var Jitter = {};
130
131
132 // ----------------------- The Jitter.Script class ----------------------- //
133
134 Jitter.Script = function () {
135     this.scenes = [];
136     this.scene_pointer = -1;
137     // The cues.indexOf the most recently executed cue. Range: -1 to cues.length.
138     // That is, if cue_pointer is 1, then advance() will execute cues[2]
139     // and retreat() will execute cues[1].undo.
140     this.cue_pointer = -1;
141 };
142
143 Jitter.Script.prototype.advance = function () {
144     if (this.scenes.length) {
145         if (this.scene_pointer >= this.scenes.length) return;
146         if (this.scene_pointer < 0) {
147             this.scene_pointer = 0;
148             this.cue_pointer = -1;
149             this.scenes[this.scene_pointer].begin();
150         }
151         var scene = this.scenes[this.scene_pointer];
152        
153         if (this.cue_pointer < 0) this.cue_pointer = -1; // just in case
154         
155         while (this.cue_pointer >= (scene.cues.length - 1)) {
156             // End the current scene and begin the next
157             scene.end()
158             this.scene_pointer++;
159             this.cue_pointer = -1;
160             if (this.scene_pointer < this.scenes.length) {
161                 scene = this.scenes[this.scene_pointer];
162                 scene.begin();
163             } else {
164                 return;
165             }
166         }
167        
168         // Run the current cue
169         this.cue_pointer++;
170         scene.cues[this.cue_pointer]();
171     }
172 };
173
174 Jitter.Script.prototype.retreat = function () {
175     if (this.scenes.length) {
176         if (this.scene_pointer < 0) return;
177         if (this.scene_pointer >= this.scenes.length) {
178             this.scene_pointer = this.scenes.length - 1;
179             var scene = this.scenes[this.scene_pointer];
180             this.cue_pointer = scene.cues.length - 1;
181             scene.unend();
182         } else {
183             var scene = this.scenes[this.scene_pointer];
184         }
185        
186         if (this.cue_pointer >= scene.cues.length)
187             this.cue_pointer = scene.cues.length - 1; // just in case
188         
189         // Run the current cue.undo
190         if (scene.cues.length && this.cue_pointer >= 0) {
191             var cue = scene.cues[this.cue_pointer];
192             if (cue.undo != null) cue.undo();
193             this.cue_pointer--;
194         }
195        
196         while (this.cue_pointer < 0) {
197             // Unbegin the current scene and move to the previous
198             scene.unbegin();
199             this.scene_pointer--;
200             if (this.scene_pointer >= 0) {
201                 scene = this.scenes[this.scene_pointer];
202                 this.cue_pointer = scene.cues.length - 1;
203                 scene.unend();
204             } else {
205                 this.cue_pointer = -1;
206                 return;
207             }
208         }
209     }
210 };
211
212 Jitter.Script.prototype.jump_forward = function () {
213     if (this.scenes) {
214         if (this.scene_pointer < 0) {
215             this.scene_pointer = 0;
216         } else if (this.scene_pointer >= this.scenes.length) {
217             this.scene_pointer = this.scenes.length; // Just in case
218         } else {
219             this.scenes[this.scene_pointer].end();
220             this.scene_pointer++;
221             this.cue_pointer = -1;
222             if (this.scene_pointer < this.scenes.length) {
223                 scene = this.scenes[this.scene_pointer];
224                 scene.begin();
225             }
226         }
227     }
228 };
229
230 Jitter.Script.prototype.jump_backward = function () {
231     if (this.scenes) {
232         if (this.scene_pointer < 0) {
233             this.scene_pointer = -1;
234         } else if (this.scene_pointer >= this.scenes.length) {
235             this.scene_pointer = this.scenes.length - 1;
236         } else {
237             if (this.cue_pointer == -1) {
238                 // At the first cue. Jump up one scene.
239                 this.scenes[this.scene_pointer].unbegin();
240                 this.scene_pointer -= 1;
241                 if (this.scene_pointer >= 0) {
242                     this.scenes[this.scene_pointer].unend();
243                     this.scenes[this.scene_pointer].unbegin();
244                 }
245             } else {
246                 // Jump before the first cue in this scene.
247                 this.scenes[this.scene_pointer].unbegin();
248             }
249             this.cue_pointer = -1;
250         }
251     }
252 };
253
254 Jitter.Script.prototype.present = function () {
255     // Bind mouse and key events to run this script.
256     var self = this;
257     $(document).click(function () { self.advance(); });
258     $(document).keypress(function (e) {
259         if (!e) var e = window.event;
260         if (e.keyCode) { var code = e.keyCode; }
261         else { if (e.which) var code = e.which; }
262         if (code == 37) return self.retreat();
263         if (code == 38 || code == 33) return self.jump_backward();
264         if (code == 39) return self.advance();
265         if (code == 40 || code == 34) return self.jump_forward();
266     });
267 };
268
269 Jitter.Script.prototype.addScene = function () {
270     var scene = new Jitter.Scene();
271     this.scenes.push(scene);
272     return scene;
273 };
274
275
276 // ----------------------- The Jitter.Scene class ----------------------- //
277
278 Jitter.Scene = function () {
279     this.cues = [];
280 };
281
282 Jitter.Scene.prototype.begin = function () {
283     $(".jitter-prop").remove();
284 };
285
286 Jitter.Scene.prototype.unbegin = function () {
287     $(".jitter-prop").remove();
288 };
289
290 Jitter.Scene.prototype.end = function () {
291     $(".jitter-prop").remove();
292 };
293
294 Jitter.Scene.prototype.unend = function () {
295     $(".jitter-prop").remove();
296 };
297
298 Jitter.Scene.prototype.add = function (func, undo) {
299     func.undo = undo;
300     this.cues.push(func);
301 };
302
303 Jitter.Scene.prototype.fadeIn = function (prop, speed) {
304     // Add a cue to fadeIn the given property (and an undo which fades it out).
305     this.add(function () { prop.$().fadeIn(speed); },
306              function () { prop.$().fadeOut(speed); });
307 };
308
309 Jitter.Scene.prototype.fadeReplace = function (target, replacement, speed, callback) {
310     // Add a cue which fades out the target Prop and fades the replacement
311     // Prop into its existing position.
312     this.add(
313         function () {
314             replacement.placeAfter("#" + target.id);
315             target.detach().fadeAway(speed);
316             replacement.fadeIn(speed, callback);
317         },
318         function () {
319             target.placeBefore("#" + replacement.id);
320             replacement.detach().fadeAway(speed);
321             target.fadeIn(speed, callback);
322         });
323 };
324
325 Jitter.Scene.prototype.fadeAppend = function (prop, target, speed, callback) {
326     // Add a cue to append the Prop to the target (selector) and fade it in.
327     this.add(function () { prop.appendTo(target).fadeIn(speed, callback); },
328              function () { prop.fadeAway(speed, callback); });
329 };
330
331 Jitter.Scene.prototype.fadePrepend = function (prop, target, speed, callback) {
332     // Add a cue to prepend the Prop to the target (selector) and fade it in.
333     this.add(function () { prop.prependTo(target).fadeIn(speed, callback); },
334              function () { prop.fadeAway(speed, callback); });
335 };
336
337 Jitter.Scene.prototype.fadeBefore = function (prop, target, speed, callback) {
338     // Add a cue to place the Prop before the target prop and fade it in.
339     this.add(function () { prop.placeBefore("#" + target.id).fadeIn(speed, callback); },
340              function () { prop.fadeAway(speed, callback); });
341 };
342
343 Jitter.Scene.prototype.fadeAfter = function (prop, target, speed, callback) {
344     // Add a cue to place the Prop after the target prop and fade it in.
345     this.add(function () { prop.placeAfter("#" + target.id).fadeIn(speed, callback); },
346              function () { prop.fadeAway(speed, callback); });
347 };
348
349 Jitter.Scene.prototype.fadeAway = function (prop, target, speed, callback) {
350     // Add a cue to remove the Prop from the target (selector) and fade it out.
351     this.add(function () { prop.fadeAway(speed, callback); },
352              function () { prop.appendTo(target).fadeIn(speed, callback); });
353 };
354
355 // MarkedScene, a subclass of Scene that updates #scenemarker.html
356
357 Jitter.MarkedScene = function () {
358     this.cues = [];
359     Jitter.MarkedScene.scene_count += 1;
360     this.scene_number = Jitter.MarkedScene.scene_count;
361 };
362 Jitter.MarkedScene.scene_count = 0;
363 Jitter.MarkedScene.prototype = new Jitter.Scene;
364
365 Jitter.MarkedScene.prototype.begin = function () {
366     $(".jitter-prop").remove();
367     $("#scene-marker").html(this.scene_number);
368 };
369
370 Jitter.MarkedScene.prototype.unend = function () {
371     $(".jitter-prop").remove();
372     $("#scene-marker").html(this.scene_number);
373 };
374
375
376 // -------------------- The Jitter.manager object --------------------- //
377
378 Jitter.manager = {
379     props: [],
380     // Number of times per second to run the manager.
381     rate: 5,
382     cycle: null,
383     recycle: function (rate) {
384         if (this.cycle) {
385             clearInterval(this.cycle);
386             this.cycle = null;
387         }
388         this.rate = rate;
389         if (rate) this.cycle = setInterval("Jitter.manager.main()", (1 / rate));
390     },
391     main: function () {
392         for (var i in this.props) {
393             var prop = this.props[i];
394             if (prop.tick) prop.tick();
395         }
396     }
397 };
398 Jitter.manager.recycle(5);
399
400 // ----------------------- The Jitter.Prop class ----------------------- //
401
402 Jitter.Prop = function (id, content, initialStyle, wrapper) {
403     this.id = id;
404     if (wrapper == null) wrapper = 'div';
405     this.content = content;
406     this.element = ("<{wrapper} id='{id}' class='jitter-prop' style='display: none'>{content}" +
407                     "</{wrapper}>").resolve({id:id, content:content, wrapper:wrapper});
408     this.initialStyle = initialStyle;
409     Jitter.manager.props.push(this);
410 };
411
412 Jitter.Prop.prototype.$ = function () {
413     return $("#" + this.id);
414 };
415
416 Jitter.Prop.prototype.toString = function () {
417     return this.element;
418 };
419
420 Jitter.Prop.prototype.appendTo = function (target) {
421     // Append this Prop to the given target.
422     // The 'target' argument may be a selector string, a jQuery object,
423     // or another Jitter.Prop.
424     // If target is null, the Prop will be appended to document.body.
425     if (target == null) target = "body";
426     if (typeof(target) == 'string') target = $(target);
427     target.append(this.element);
428     if (this.initialStyle) this.css(this.initialStyle);
429     return this;
430 };
431
432 Jitter.Prop.prototype.prependTo = function (target) {
433     // Prepend this Prop to the given target.
434     // The 'target' argument may be a selector string, a jQuery object,
435     // or another Jitter.Prop.
436     // If target is null, the Prop will be prepended to document.body.
437     if (target == null) target = "body";
438     if (typeof(target) == 'string') target = $(target);
439     target.prepend(this.element);
440     if (this.initialStyle) this.css(this.initialStyle);
441     return this;
442 };
443
444 Jitter.Prop.prototype.placeBefore = function (target) {
445     // Create this Prop and place it before the given target.
446     // The 'target' argument may be a selector string, a jQuery object,
447     // or another Jitter.Prop.
448     if (typeof(target) == 'string') target = $(target);
449     target.before(this.element);
450     if (this.initialStyle) this.css(this.initialStyle);
451     return this;
452 };
453
454 Jitter.Prop.prototype.placeAfter = function (target) {
455     // Create this Prop and place it after the given target.
456     // The 'target' argument may be a selector string, a jQuery object,
457     // or another Jitter.Prop.
458     if (typeof(target) == 'string') target = $(target);
459     target.after(this.element);
460     if (this.initialStyle) this.css(this.initialStyle);
461     return this;
462 };
463
464 Jitter.Prop.prototype.final = function () {
465     if (this.final_content) this.$().html(this.final_content);
466     return this;
467 };
468
469 // Copy most jQuery object functions onto the Jitter.Prop class.
470 for (var key in set([
471     "prepend", "append", "before", "after",
472     "attr", "css", "text", "clone", "val", "html", "eq", "parent",
473     "parents", "next", "prev", "nextAll", "prevAll", "siblings",
474     "children", "contents", "removeAttr", "addClass", "removeClass",
475     "toggleClass", "remove", "empty", "data", "removeData",
476     "queue", "dequeue", "bind", "one", "unbind", "trigger",
477     "triggerHandler", "toggle", "hover", "ready", "live", "die",
478     "blur", "focus", "load", "resize", "scroll", "unload",
479     "click", "dblclick", "mousedown", "mouseup", "mousemove",
480     "mouseover", "mouseout", "mouseenter", "mouseleave", "change",
481     "select", "submit", "keydown", "keypress", "keyup", "error",
482     "show", "hide", "fadeTo", "animate", "stop",
483     "slideDown", "slideUp", "slideToggle", "fadeIn", "fadeOut",
484     "offset", "position", "offsetParent", "scrollLeft", "scrollTop",
485     "innerHeight", "outerHeight", "height", "innerWidth", "outerWidth", "width",
486     "vcenter", "center", "stackpos", "stack", "marginPos", "detach",
487     "fadeAway", "grow"])) {
488     Jitter.Prop.prototype[key] = function () {
489         // Bind the methodname into the closure
490         var methodname = key;
491         return function () {
492             if ('$' in this) {
493                 var j = this.$();
494                 var method = j[methodname];
495                 var result = method.apply(j, arguments);
496                 // If possible, return the Prop object instead of the $()
497                 // one so extension methods like final() work.
498                 if (result == j) result = this;
499                 return result;
500             } else {
501                 alert([methodname, this, arguments].toString());
502             }
503         };
504     }();
505 }
506
507
508 Jitter.Props = function (id, lines, initialStyle, wrapper) {
509     // Return an array of (numbered) Prop objects from the given array of content.
510     var result = [];
511     for (var i in lines) result[i] = new Jitter.Prop(id + i, lines[i], initialStyle, wrapper);
512     return result;
513 };
514
515 Jitter.vcenter = function (cssFloat) {
516     // Return a Prop.tick function which keeps the object vertically centered.
517     return function () { return this.vcenter(cssFloat); };
518 }
519
Note: See TracBrowser for help on using the browser.