// Jitter, a jQuery-powered presentation library. String.prototype.resolve = function (vars) { function repl(s, v) { return vars[v]; } return this.replace(/\{([^}]+)\}/g, repl); }; function set(a) { // Convert the given array into a set. var o = {}; for (var i=0; i < a.length; i++) o[a[i]] = ''; return o; } function items(obj) { // Return a list of (key, value) pairs for the given object var result = []; if (typeof(obj) == 'object') { for (var key in obj) result[result.length] = [key, obj[key]]; } else { for (var i=0; i < obj.length; i++) result.push(obj[i]); } return result; }; // Some jQuery plugins: jQuery.fn.vcenter = function(cssFloat) { return this.each(function() { var j = $(this); if (cssFloat) this.style.cssFloat = cssFloat; var h = j.height(); var ph = j.parent().height(); var mt = ((ph / 2) - (h / 2)); this.style.marginTop = mt.toString() + 'px'; }); }; jQuery.fn.center = function() { // Returns position() plus half height/width. var results; if (this[0]) { results = this.position(); var mrg = this.marginPos(); results.top = results.top + mrg.top + (this.height() / 2); results.left = results.left + mrg.left + (this.width() / 2); } return results; }; jQuery.fn.stackpos = function(target) { // Return a position()-like {top, left} object for stacking this object // on the given target. var results; if (this[0] && target[0]) { results = target.center(); results.top -= (this.height() / 2); results.left -= (this.width() / 2); } return results; }; jQuery.fn.stack = function(target) { // Stack the (absolutely-positioned) self on the given target return this.each(function() { var elem = $(this); elem.css(elem.stackpos(target)); }); }; jQuery.fn.marginPos = function(recurse) { // Return a position()-like {top, left} object for marginTop/Left. var results; if (this[0]) { var mt = 0, ml = 0; if (this[0].style) { mt = this[0].style.marginTop.match(/([0-9.-]+)px/); mt = mt ? parseInt(mt[1]) : 0; ml = this[0].style.marginLeft.match(/([0-9.-]+)px/); ml = ml ? parseInt(ml[1]) : 0; } if (recurse) { if (this[0].parentNode != null) { var pmrg = $(this[0].parentNode).marginPos(recurse); mt = mt + pmrg.top; ml = ml + pmrg.left; } } results = {top: mt, left: ml}; } return results; }; jQuery.fn.detach = function(rehome) { // Make all matching elements into position:absolute ones. // Keep them in the same window position. // If 'rehome' is true, unhook the element from its current // container and re-attach it to the body element. return this.each(function() { var j = $(this); var pos = j.position(); var mrg = j.marginPos(rehome); j.css({position: 'absolute', cssFloat: 'none', top: pos.top + mrg.top, left: pos.left + mrg.left, marginTop: 0, marginLeft: 0 }); if (rehome) j.remove().appendTo("body"); }); }; jQuery.fn.fadeAway = function(speed, callback) { var self = this; return self.fadeOut(speed, function () { self.remove(); if (callback) callback(); }); }; jQuery.fn.grow = function(factor, speed) { return this.each(function() { var j = $(this); j.animate({width: (j.width() * factor), height: (j.height() * factor)}, speed); }); }; // The Jitter namespace itself. var Jitter = {}; // ----------------------- The Jitter.Script class ----------------------- // Jitter.Script = function () { this.scenes = []; this.scene_pointer = -1; // The cues.indexOf the most recently executed cue. Range: -1 to cues.length. // That is, if cue_pointer is 1, then advance() will execute cues[2] // and retreat() will execute cues[1].undo. this.cue_pointer = -1; }; Jitter.Script.prototype.advance = function () { if (this.scenes.length) { if (this.scene_pointer >= this.scenes.length) return; if (this.scene_pointer < 0) { this.scene_pointer = 0; this.cue_pointer = -1; this.scenes[this.scene_pointer].begin(); } var scene = this.scenes[this.scene_pointer]; if (this.cue_pointer < 0) this.cue_pointer = -1; // just in case while (this.cue_pointer >= (scene.cues.length - 1)) { // End the current scene and begin the next scene.end() this.scene_pointer++; this.cue_pointer = -1; if (this.scene_pointer < this.scenes.length) { scene = this.scenes[this.scene_pointer]; scene.begin(); } else { return; } } // Run the current cue this.cue_pointer++; scene.cues[this.cue_pointer](); } }; Jitter.Script.prototype.retreat = function () { if (this.scenes.length) { if (this.scene_pointer < 0) return; if (this.scene_pointer >= this.scenes.length) { this.scene_pointer = this.scenes.length - 1; var scene = this.scenes[this.scene_pointer]; this.cue_pointer = scene.cues.length - 1; scene.unend(); } else { var scene = this.scenes[this.scene_pointer]; } if (this.cue_pointer >= scene.cues.length) this.cue_pointer = scene.cues.length - 1; // just in case // Run the current cue.undo if (scene.cues.length && this.cue_pointer >= 0) { var cue = scene.cues[this.cue_pointer]; if (cue.undo != null) cue.undo(); this.cue_pointer--; } while (this.cue_pointer < 0) { // Unbegin the current scene and move to the previous scene.unbegin(); this.scene_pointer--; if (this.scene_pointer >= 0) { scene = this.scenes[this.scene_pointer]; this.cue_pointer = scene.cues.length - 1; scene.unend(); } else { this.cue_pointer = -1; return; } } } }; Jitter.Script.prototype.jump_forward = function () { if (this.scenes) { if (this.scene_pointer < 0) { this.scene_pointer = 0; } else if (this.scene_pointer >= this.scenes.length) { this.scene_pointer = this.scenes.length; // Just in case } else { this.scenes[this.scene_pointer].end(); this.scene_pointer++; this.cue_pointer = -1; if (this.scene_pointer < this.scenes.length) { scene = this.scenes[this.scene_pointer]; scene.begin(); } } } }; Jitter.Script.prototype.jump_backward = function () { if (this.scenes) { if (this.scene_pointer < 0) { this.scene_pointer = -1; } else if (this.scene_pointer >= this.scenes.length) { this.scene_pointer = this.scenes.length - 1; } else { if (this.cue_pointer == -1) { // At the first cue. Jump up one scene. this.scenes[this.scene_pointer].unbegin(); this.scene_pointer -= 1; if (this.scene_pointer >= 0) { this.scenes[this.scene_pointer].unend(); this.scenes[this.scene_pointer].unbegin(); } } else { // Jump before the first cue in this scene. this.scenes[this.scene_pointer].unbegin(); } this.cue_pointer = -1; } } }; Jitter.Script.prototype.present = function () { // Bind mouse and key events to run this script. var self = this; $(document).click(function () { self.advance(); }); $(document).keypress(function (e) { if (!e) var e = window.event; if (e.keyCode) { var code = e.keyCode; } else { if (e.which) var code = e.which; } if (code == 37) return self.retreat(); if (code == 38 || code == 33) return self.jump_backward(); if (code == 39) return self.advance(); if (code == 40 || code == 34) return self.jump_forward(); }); }; Jitter.Script.prototype.addScene = function () { var scene = new Jitter.Scene(); this.scenes.push(scene); return scene; }; // ----------------------- The Jitter.Scene class ----------------------- // Jitter.Scene = function () { this.cues = []; }; Jitter.Scene.prototype.begin = function () { $(".jitter-prop").remove(); }; Jitter.Scene.prototype.unbegin = function () { $(".jitter-prop").remove(); }; Jitter.Scene.prototype.end = function () { $(".jitter-prop").remove(); }; Jitter.Scene.prototype.unend = function () { $(".jitter-prop").remove(); }; Jitter.Scene.prototype.add = function (func, undo) { func.undo = undo; this.cues.push(func); }; Jitter.Scene.prototype.fadeIn = function (prop, speed) { // Add a cue to fadeIn the given property (and an undo which fades it out). this.add(function () { prop.$().fadeIn(speed); }, function () { prop.$().fadeOut(speed); }); }; Jitter.Scene.prototype.fadeReplace = function (target, replacement, speed, callback) { // Add a cue which fades out the target Prop and fades the replacement // Prop into its existing position. this.add( function () { replacement.placeAfter("#" + target.id); target.detach().fadeAway(speed); replacement.fadeIn(speed, callback); }, function () { target.placeBefore("#" + replacement.id); replacement.detach().fadeAway(speed); target.fadeIn(speed, callback); }); }; Jitter.Scene.prototype.fadeAppend = function (prop, target, speed, callback) { // Add a cue to append the Prop to the target (selector) and fade it in. this.add(function () { prop.appendTo(target).fadeIn(speed, callback); }, function () { prop.fadeAway(speed, callback); }); }; Jitter.Scene.prototype.fadePrepend = function (prop, target, speed, callback) { // Add a cue to prepend the Prop to the target (selector) and fade it in. this.add(function () { prop.prependTo(target).fadeIn(speed, callback); }, function () { prop.fadeAway(speed, callback); }); }; Jitter.Scene.prototype.fadeBefore = function (prop, target, speed, callback) { // Add a cue to place the Prop before the target prop and fade it in. this.add(function () { prop.placeBefore("#" + target.id).fadeIn(speed, callback); }, function () { prop.fadeAway(speed, callback); }); }; Jitter.Scene.prototype.fadeAfter = function (prop, target, speed, callback) { // Add a cue to place the Prop after the target prop and fade it in. this.add(function () { prop.placeAfter("#" + target.id).fadeIn(speed, callback); }, function () { prop.fadeAway(speed, callback); }); }; Jitter.Scene.prototype.fadeAway = function (prop, target, speed, callback) { // Add a cue to remove the Prop from the target (selector) and fade it out. this.add(function () { prop.fadeAway(speed, callback); }, function () { prop.appendTo(target).fadeIn(speed, callback); }); }; // MarkedScene, a subclass of Scene that updates #scenemarker.html Jitter.MarkedScene = function () { this.cues = []; Jitter.MarkedScene.scene_count += 1; this.scene_number = Jitter.MarkedScene.scene_count; }; Jitter.MarkedScene.scene_count = 0; Jitter.MarkedScene.prototype = new Jitter.Scene; Jitter.MarkedScene.prototype.begin = function () { $(".jitter-prop").remove(); $("#scene-marker").html(this.scene_number); }; Jitter.MarkedScene.prototype.unend = function () { $(".jitter-prop").remove(); $("#scene-marker").html(this.scene_number); }; // -------------------- The Jitter.manager object --------------------- // Jitter.manager = { props: [], // Number of times per second to run the manager. rate: 5, cycle: null, recycle: function (rate) { if (this.cycle) { clearInterval(this.cycle); this.cycle = null; } this.rate = rate; if (rate) this.cycle = setInterval("Jitter.manager.main()", (1 / rate)); }, main: function () { for (var i in this.props) { var prop = this.props[i]; if (prop.tick) prop.tick(); } } }; Jitter.manager.recycle(5); // ----------------------- The Jitter.Prop class ----------------------- // Jitter.Prop = function (id, content, initialStyle, wrapper) { this.id = id; if (wrapper == null) wrapper = 'div'; this.content = content; this.element = ("<{wrapper} id='{id}' class='jitter-prop' style='display: none'>{content}" + "").resolve({id:id, content:content, wrapper:wrapper}); this.initialStyle = initialStyle; Jitter.manager.props.push(this); }; Jitter.Prop.prototype.$ = function () { return $("#" + this.id); }; Jitter.Prop.prototype.toString = function () { return this.element; }; Jitter.Prop.prototype.appendTo = function (target) { // Append this Prop to the given target. // The 'target' argument may be a selector string, a jQuery object, // or another Jitter.Prop. // If target is null, the Prop will be appended to document.body. if (target == null) target = "body"; if (typeof(target) == 'string') target = $(target); target.append(this.element); if (this.initialStyle) this.css(this.initialStyle); return this; }; Jitter.Prop.prototype.prependTo = function (target) { // Prepend this Prop to the given target. // The 'target' argument may be a selector string, a jQuery object, // or another Jitter.Prop. // If target is null, the Prop will be prepended to document.body. if (target == null) target = "body"; if (typeof(target) == 'string') target = $(target); target.prepend(this.element); if (this.initialStyle) this.css(this.initialStyle); return this; }; Jitter.Prop.prototype.placeBefore = function (target) { // Create this Prop and place it before the given target. // The 'target' argument may be a selector string, a jQuery object, // or another Jitter.Prop. if (typeof(target) == 'string') target = $(target); target.before(this.element); if (this.initialStyle) this.css(this.initialStyle); return this; }; Jitter.Prop.prototype.placeAfter = function (target) { // Create this Prop and place it after the given target. // The 'target' argument may be a selector string, a jQuery object, // or another Jitter.Prop. if (typeof(target) == 'string') target = $(target); target.after(this.element); if (this.initialStyle) this.css(this.initialStyle); return this; }; Jitter.Prop.prototype.final = function () { if (this.final_content) this.$().html(this.final_content); return this; }; // Copy most jQuery object functions onto the Jitter.Prop class. for (var key in set([ "prepend", "append", "before", "after", "attr", "css", "text", "clone", "val", "html", "eq", "parent", "parents", "next", "prev", "nextAll", "prevAll", "siblings", "children", "contents", "removeAttr", "addClass", "removeClass", "toggleClass", "remove", "empty", "data", "removeData", "queue", "dequeue", "bind", "one", "unbind", "trigger", "triggerHandler", "toggle", "hover", "ready", "live", "die", "blur", "focus", "load", "resize", "scroll", "unload", "click", "dblclick", "mousedown", "mouseup", "mousemove", "mouseover", "mouseout", "mouseenter", "mouseleave", "change", "select", "submit", "keydown", "keypress", "keyup", "error", "show", "hide", "fadeTo", "animate", "stop", "slideDown", "slideUp", "slideToggle", "fadeIn", "fadeOut", "offset", "position", "offsetParent", "scrollLeft", "scrollTop", "innerHeight", "outerHeight", "height", "innerWidth", "outerWidth", "width", "vcenter", "center", "stackpos", "stack", "marginPos", "detach", "fadeAway", "grow"])) { Jitter.Prop.prototype[key] = function () { // Bind the methodname into the closure var methodname = key; return function () { if ('$' in this) { var j = this.$(); var method = j[methodname]; var result = method.apply(j, arguments); // If possible, return the Prop object instead of the $() // one so extension methods like final() work. if (result == j) result = this; return result; } else { alert([methodname, this, arguments].toString()); } }; }(); } Jitter.Props = function (id, lines, initialStyle, wrapper) { // Return an array of (numbered) Prop objects from the given array of content. var result = []; for (var i in lines) result[i] = new Jitter.Prop(id + i, lines[i], initialStyle, wrapper); return result; }; Jitter.vcenter = function (cssFloat) { // Return a Prop.tick function which keeps the object vertically centered. return function () { return this.vcenter(cssFloat); }; }