summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--loader.js86
-rw-r--r--minotaur.js67
-rw-r--r--test.html2
-rw-r--r--undo/09-test-undo.js165
-rw-r--r--undo/undostack.js53
5 files changed, 373 insertions, 0 deletions
diff --git a/loader.js b/loader.js
new file mode 100644
index 0000000..9de5cb3
--- /dev/null
+++ b/loader.js
@@ -0,0 +1,86 @@
+/*****************************************************
+
+// sample config
+
+var config = {};
+config.images = [];
+
+// sample app
+
+function app = {};
+app.init = function(){
+ app.load();
+}
+app.load = function(){
+ app.loader = new Loader ();
+ app.loader.register("loading");
+
+ // register loaders here
+
+ app.loader.ready("loading");
+}
+app.ready = function(){
+}
+******************************************************/
+
+function Loader (readyCallback){
+ this.assets = {};
+ this.readyCallback = readyCallback || app.ready;
+}
+
+// Register an asset as loading
+Loader.prototype.register = function(s){
+ this.assets[s] = false;
+}
+
+// Signal that an asset has loaded
+Loader.prototype.ready = function(s){
+ console.log("ready >> " + s);
+
+ this.assets[s] = true;
+ if (this.loaded) return;
+ if (! this.isReady()) return;
+
+ this.loaded = true;
+ this.readyCallback();
+}
+
+// (boolean) Is the loader ready?
+Loader.prototype.isReady = function(){
+ for (var s in this.assets) {
+ if (this.assets.hasOwnProperty(s) && this.assets[s] != true) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// (int) Number of assets remaining
+Loader.prototype.remainingAssets = function(){
+ var n = 0;
+ for (var s in this.assets) {
+ if (this.assets.hasOwnProperty(s) && this.assets[s] != true) {
+ n++;
+ console.log('remaining: ' + s);
+ }
+ }
+ return n;
+}
+
+// Preload the images in config.images
+Loader.prototype.preloadImages = function(){
+ for (var i = 0; i < config.images.length; i++) {
+ this.preloadImage(config.images[i]);
+ }
+}
+Loader.prototype.preloadImage = function(src){
+ var _this = this;
+ this.register(src);
+ var img = new Image();
+ img.onload = function(){
+ _this.ready(src);
+ }
+ img.src = src;
+ if (img.complete) img.onload();
+}
+
diff --git a/minotaur.js b/minotaur.js
new file mode 100644
index 0000000..7a4b5f5
--- /dev/null
+++ b/minotaur.js
@@ -0,0 +1,67 @@
+//jules 5-May-2013
+//Minotaur is a thing I wrote for brbprint that defers model updates.
+//The idea is you set some keys on the model, and the Minotaur defers the actual Ajax POST/PUT.
+//Also updates a save button for you, letting you know when the changes finish posting.
+//
+//Expects that your model has a "save" method that accepts a callback, to inform the Minotaur that the save happened.
+//
+//The name is a pun on Monitor but is more badass!!!! 38c (minotaur emoticon)
+$(function(){
+
+ var Monitor = function () {
+ var base = this;
+ base.$el = $("#save");
+ base.timeout = null;
+ base.delay = 500;
+ base.objects = {};
+
+ base.init = function () {
+ base.$el.addClass('saved').html('Save');
+ base.$el.click(base.save);
+ }
+
+ base.watch = function (object) {
+ base.objects[object.type] = base.objects[object.type] || {};
+ base.objects[object.type][object.id] = object;
+ base.clear();
+ base.timeout = setTimeout(base.save, base.delay);
+ };
+
+ base.clear = function () {
+ if (base.timeout) clearTimeout(base.timeout);
+ base.timeout = false;
+ };
+
+ base.save = function () {
+ var saving = false;
+ base.clear();
+
+ for (var type in base.objects) {
+ for (var id in base.objects[type]) {
+ if (base.timeout)
+ return;
+ var obj = base.objects[type][id];
+ if (obj) obj.save(function(){
+ base.$el.removeClass('unsaved saving').addClass('saved').html('Saved');
+ saving = true;
+ });
+ base.objects[type][id] = false;
+ }
+ }
+
+ if (saving) {
+ base.$el.removeClass('unsaved saved').addClass('saving').html('Saving');
+ }
+ else {
+ base.$el.removeClass('unsaved saving').addClass('saved').html('Saved');
+ }
+
+ base.objects = {};
+ };
+
+ base.init();
+ };
+
+ window.Minotaur = new Monitor ();
+});
+
diff --git a/test.html b/test.html
index 00dcf7d..c0cfd4c 100644
--- a/test.html
+++ b/test.html
@@ -47,6 +47,8 @@
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="underscore.js"></script>
+<script type="text/javascript" src="loader.js" ></script>
+<script type="text/javascript" src="minotaur.js" ></script>
<script type="text/javascript" src="View.js" ></script>
<script type="text/javascript" src="ModalView.js" ></script>
<script type="text/javascript" src="FormView.js" ></script>
diff --git a/undo/09-test-undo.js b/undo/09-test-undo.js
new file mode 100644
index 0000000..2cceb55
--- /dev/null
+++ b/undo/09-test-undo.js
@@ -0,0 +1,165 @@
+var assert = require("assert")
+var UndoStack = require("./undo.js")
+UndoStack.debug = false
+
+describe('undo', function(){
+
+ var state = "zero"
+
+ describe('#register()', function(){
+
+ UndoStack.register({
+ type: "demo",
+ undo: function(myState){
+ state = myState
+ },
+ redo: function(myState){
+ state = myState
+ },
+ })
+
+ it('registers undoable actions', function(){
+ assert( UndoStack.types.hasOwnProperty("demo") )
+ })
+ })
+
+ describe('#push()', function(){
+
+ it('starts empty', function(){
+ assert.equal(0, UndoStack.stack.length)
+ assert.equal(-1, UndoStack.pointer)
+ })
+
+ it('pushes some actions', function(){
+
+ UndoStack.push({
+ type: "demo",
+ undo: state,
+ redo: "one"
+ })
+ state = "one"
+
+ UndoStack.push({
+ type: "demo",
+ undo: state,
+ redo: "two"
+ })
+ state = "two"
+
+ UndoStack.push({
+ type: "demo",
+ undo: state,
+ redo: "three"
+ })
+ state = "three"
+
+ assert.equal(3, UndoStack.stack.length)
+ assert.equal(2, UndoStack.pointer)
+ })
+ })
+
+ describe('#undo()', function(){
+
+ it('retrieves old state', function(){
+ assert.equal("three", state)
+ UndoStack.undo()
+ assert.equal("two", state)
+ assert.equal(1, UndoStack.pointer)
+ })
+
+ it('can only undo so far', function(){
+ var canUndo
+
+ canUndo = UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(true, canUndo)
+
+ canUndo = UndoStack.undo()
+ assert.equal("zero", state)
+ assert.equal(-1, UndoStack.pointer)
+ assert.equal(false, canUndo)
+
+ canUndo = UndoStack.undo()
+ assert.equal("zero", state)
+ assert.equal(-1, UndoStack.pointer)
+ assert.equal(false, canUndo)
+ })
+
+ })
+
+ describe('#redo()', function(){
+
+ it('reassigns new state', function(){
+ UndoStack.redo()
+ assert.equal("one", state)
+ })
+
+ it('can only redo so far', function(){
+ var canRedo
+
+ canRedo = UndoStack.redo()
+ assert.equal("two", state)
+ assert.equal(true, canRedo)
+
+ canRedo = UndoStack.redo()
+ assert.equal("three", state)
+ assert.equal(false, canRedo)
+
+ canRedo = UndoStack.redo()
+ assert.equal("three", state)
+ assert.equal(false, canRedo)
+ })
+
+ it('clobbers old state if we undo then do something else', function(){
+ UndoStack.undo()
+ assert.equal("two", state)
+ assert.equal(1, UndoStack.pointer)
+
+ UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(3, UndoStack.stack.length)
+
+ UndoStack.push({
+ type: "demo",
+ undo: state,
+ redo: "four"
+ })
+ state = "four"
+
+ assert.equal(1, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+
+ UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+
+ UndoStack.redo()
+ assert.equal("four", state)
+ assert.equal(1, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+ })
+ })
+
+})
+
+// 1) push action
+// 2) push action
+// 3) push action
+// undo 3
+// undo 2
+// undo 1
+// undo * can't undo anymore
+// redo 1
+// redo 2
+// redo 3
+// redo * can't redo anymore
+// undo 3
+// 4) push action (clobbers action 3)
+// undo 4
+// undo 2
+// redo 2
+// redo 4
+
diff --git a/undo/undostack.js b/undo/undostack.js
new file mode 100644
index 0000000..5ba97e1
--- /dev/null
+++ b/undo/undostack.js
@@ -0,0 +1,53 @@
+(function(){
+
+ var UndoStack = function(){
+ this.debug = true
+ this.stack = []
+ this.types = {}
+ this.pointer = -1
+ }
+ UndoStack.prototype.push = function(action){
+ this.pointer++
+ this.stack[this.pointer] = action
+ this.purge()
+ }
+ UndoStack.prototype.purge = function(){
+ if (this.stack.length-1 == this.pointer) return
+ this.stack.length = this.pointer+1
+ }
+ UndoStack.prototype.undo = function(){
+ if (this.pointer == -1) return false
+ var action = this.stack[this.pointer]
+ this.debug && console.log("undo", action.type)
+ this.types[ action.type ].undo(action.undo)
+ this.pointer--
+ return this.pointer > -1
+ }
+ UndoStack.prototype.redo = function(){
+ if (this.pointer == this.stack.length-1) return false
+ this.pointer++
+ var action = this.stack[this.pointer]
+ this.debug && console.log("redo", action.type)
+ this.types[ action.type ].redo(action.redo)
+ return this.pointer < this.stack.length-1
+ }
+ UndoStack.prototype.register = function(actionType){
+ if (actionType.length) {
+ actionType.forEach(this.registerOne.bind(this))
+ }
+ else {
+ this.registerOne(actionType)
+ }
+ }
+ UndoStack.prototype.registerOne = function(actionType){
+ this.types[ actionType.type ] = actionType
+ }
+ if ('window' in this) {
+ window.UndoStack = new UndoStack
+ }
+ else {
+ module.exports = new UndoStack
+ }
+
+})()
+