From c4e0700d90523d7aff1515833c6ec458a2422da3 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Fri, 2 May 2014 15:15:19 -0600 Subject: [PATCH 01/19] added a note to the todos --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 59497ee..c50d0d1 100644 --- a/readme.md +++ b/readme.md @@ -15,6 +15,7 @@ So there are a variety of things that need to be completed for Action! to be rea 0. update documentation 0. example app 0. simplify model set function? +0. make the error output even prettier! https://developers.google.com/chrome-developer-tools/docs/console-api#consoleerrorobject_object ###Done 0. Tooling for troubleshooting events From 0550b9d29f7c0b5a0a3ad62e43c7b084f3094629 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Fri, 2 May 2014 15:16:01 -0600 Subject: [PATCH 02/19] added a note about templates --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c50d0d1..856c26b 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,7 @@ So there are a variety of things that need to be completed for Action! to be rea 0. dependency resolution integration with state machines 0. Figure out the testing side of event driven FED in Action 0. Revise the destroy function to make it really do what it needs to -0. Figure out how views really work... +0. Figure out how views really work... (https://github.com/twitter/hogan.js) 0. Consider web components ###TODOs generally From 1ef1188ff0c115d2fd28a832b2b2988a1c72b10d Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Fri, 2 May 2014 15:21:25 -0600 Subject: [PATCH 03/19] removed some more code that was commented out --- public/javascripts/action.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/javascripts/action.js b/public/javascripts/action.js index 4f20621..f25ee97 100644 --- a/public/javascripts/action.js +++ b/public/javascripts/action.js @@ -25,11 +25,6 @@ var action = function(){ eventStack = action.eventStore[eventNameIn]; } - // if(typeof eventDataIn !== 'undefined'){ - // //we have some event data - // eventData.payload = eventDataIn; - // } - //emit the event if(typeof eventStack !== 'undefined'){ for(i = 0; i < eventStack.length; i ++){ From 8dfb2cbeb2c91d30c960b02ab9f105bdbea622f9 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Mon, 5 May 2014 21:14:31 -0600 Subject: [PATCH 04/19] updated the gulpfile to modify the build a little --- Gulpfile.js | 3 + package.json | 2 +- packages/0.3.0/action-v0.3.0.js | 5 - packages/latest/action.js | 590 ++++++++++++++++++++++++++++++++ packages/latest/action.min.js | 1 + 5 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 packages/latest/action.js create mode 100644 packages/latest/action.min.js diff --git a/Gulpfile.js b/Gulpfile.js index 4336d3a..e3a096a 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -31,9 +31,12 @@ gulp.task('generateForPublish', function(){ 'use strict'; gulp.src('public/javascripts/action.js') + .pipe(gulp.dest('packages/latest/')) .pipe(rename('action-v' + pkg.version + '.js')) .pipe(gulp.dest('packages/' + pkg.version + '/')) .pipe(uglify()) + .pipe(rename('action.min.js')) + .pipe(gulp.dest('packages/latest/')) .pipe(rename('action-v' + pkg.version + '.min.js')) .pipe(gulp.dest('packages/' + pkg.version + '/')); }); diff --git a/package.json b/package.json index 5403c0a..855c97a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "action", - "version": "0.4.0", + "version": "0.3.0", "private": true, "scripts": { "start": "node app.js" diff --git a/packages/0.3.0/action-v0.3.0.js b/packages/0.3.0/action-v0.3.0.js index 4f20621..f25ee97 100644 --- a/packages/0.3.0/action-v0.3.0.js +++ b/packages/0.3.0/action-v0.3.0.js @@ -25,11 +25,6 @@ var action = function(){ eventStack = action.eventStore[eventNameIn]; } - // if(typeof eventDataIn !== 'undefined'){ - // //we have some event data - // eventData.payload = eventDataIn; - // } - //emit the event if(typeof eventStack !== 'undefined'){ for(i = 0; i < eventStack.length; i ++){ diff --git a/packages/latest/action.js b/packages/latest/action.js new file mode 100644 index 0000000..f25ee97 --- /dev/null +++ b/packages/latest/action.js @@ -0,0 +1,590 @@ +var action = function(){ + 'use strict'; + + var action = { + eventMe: function(objectIn){ + var returnObject = objectIn + , localEvents = {}; + + //set an emitter id for troubleshooting + returnObject.emitterId = Math.ceil(Math.random() * 10000); + + //create the lcoal event Store + returnObject.eventStore = {}; + + returnObject.emit = function(eventNameIn, eventDataIn, localFlag){ + var that = this + , eventStack + , functionToCall + , i + , isLocal = (typeof localFlag !== 'undefined' && localFlag); + + if(isLocal){ + eventStack = that.eventStore[eventNameIn]; + } else { + eventStack = action.eventStore[eventNameIn]; + } + + //emit the event + if(typeof eventStack !== 'undefined'){ + for(i = 0; i < eventStack.length; i ++){ + if(typeof eventStack[i].scope !== 'undefined'){ + eventStack[i].call.apply(eventStack[i].scope,[eventDataIn, that.emitterId]); + }else{ + eventStack[i].call(eventDataIn, that.emitterId); + } + + if(eventStack[i].once){ + that.silence(eventNameIn, eventStack[i].call, true, isLocal); + } + } + } + }; + + returnObject.emitLocal = function(eventNameIn, eventDataIn){ + var that = this; + + that.emit(eventNameIn, eventDataIn, true); + }; + + returnObject.listen = function(eventNameIn, handlerIn, scopeIn, onceIn, localFlagIn){ + var that = this + , i + , newCheck = true + + //attribute holders and such + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , local = localFlagIn + , once = onceIn + + //variables for later + , eventStack + , newEvent; + + if(typeof eventNameIn === 'object'){ + //we have an object to split up dude + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + once = eventNameIn.once; + local = eventNameIn.local; + } + + eventStack = (typeof local !== 'undefined' && local) ? that.eventStore[eventNameIn] : action.eventStore[eventNameIn]; + newEvent = (typeof local !== 'undefined' && local) ? that : action; + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === false){ + newCheck = false; + break; + } + } + + if(newCheck && typeof scopeIn !== 'undefined'){ + eventStack.push({once: false, call: handler, scope: scope}); + }else if(newCheck){ + eventStack.push({once: false, call:handler}); + } + + } else { + //new event + newEvent.eventStore[eventName] = []; //use an array to store functions + if(typeof scopeIn !== 'undefined'){ + newEvent.eventStore[eventName].push({once: false, call: handler, scope: scope}); + }else{ + newEvent.eventStore[eventName].push({once: false, call: handler}); + } + } + }; + + returnObject.listenLocal = function(eventNameIn, handlerIn, scopeIn, onceIn){ + var that = this; + + //convenience function for local listens + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listen(eventNameIn); + } else { + that.listen({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , once: onceIn + , local: true + }); + } + }; + + returnObject.listenOnce = function(eventNameIn, handlerIn, scopeIn, localFlagIn){ + //same thing as .listen() but is only triggered once + var that = this + , i + , newCheck = true + , eventStack + , newEvent + + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , localFlag = localFlagIn; + + if(typeof eventNameIn === 'object'){ + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + if(typeof localFlag !== 'undefined' && localFlag){ + //make it local! + eventStack = that.eventStore[eventName]; + newEvent = that; + }else{ + eventStack = action.eventStore[eventName]; + newEvent = action; + } + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === true){ + newCheck = false; + break; + } + } + + if(newCheck){ + eventStack.push({once:true, call: handler, scope: scope}); + } + + } else{ + //new event + newEvent.eventStore[eventNameIn] = []; //use an array to store functions + newEvent.eventStore[eventNameIn].push({once:true, call: handler, scope: scope}); + } + }; + + returnObject.listenOnceLocal = function(eventNameIn, handlerIn, scopeIn){ + var that = this; + + //same thing as .listen() but is only triggered once + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listenLocal(eventNameIn); + }else{ + that.listenLocal({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , local: true + }); + } + }; + + returnObject.silence = function(eventNameIn, handlerIn, onceIn, scopeIn, localFlagIn){ + //localize variables + var that = this + , i + , truthy = false + , eventName = eventNameIn + , handler = handlerIn + , once = onceIn + , scope = scopeIn + , localFlag = localFlagIn + , store; + + if(typeof eventNameIn === 'object'){ + // passed in a collection of params instead of params + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + once = eventNameIn.once; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + //local or global event store? + store = (typeof localFlag === 'undefined' || !localFlag) ? action : that; + + if(typeof store.eventStore[eventName] === 'undefined'){ + //if there is no event with a name... return nothing + return; + } + + //there is an event that matches... proceed + for(i = 0; i < store.eventStore[eventName].length; i ++){ + //reset this variable + truthy = false; + + if(typeof handler !== 'undefined'){ + //function is passed in + if(typeof scope !== 'undefined'){ + //scope is passed in... + if(typeof once === 'boolean'){ + // function + scope + once provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope && once === store.eventStore[eventName][i].once); + } else { + //function + scope provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope); + } + } else { + //function + once in for the match + if(typeof once === 'boolean'){ + truthy = (handler === store.eventStore[eventName][i].call && store.eventStore[eventName][i].once === once); + } else { + truthy = (handler === store.eventStore[eventName][i].call); + } + } + } else { + //no function unbind everything by resetting + store.eventStore[eventName] = []; + + //and exit + break; + } + + if(truthy){ + //remove this bad boy + store.eventStore[eventName].splice(i,1); + } + + } + }; + + returnObject.silenceLocal = function(eventNameIn, handlerIn, onceIn, scopeIn){ + var that = this; + + //essentially a convenience function. + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.silence(eventNameIn); + }else{ + that.silence({ + eventName: eventNameIn + , handler: handlerIn + , once: onceIn + , scope: scopeIn + , local: true + }); + } + }; + + //Event Based state machine + returnObject.requiredEvent = function(name, callback, context, fireMultipleIn){ + var that = this + , stateUpdate; + + that._fireMultiple = (typeof fireMultipleIn !== 'undefined') ? fireMultipleIn : false; + + //init some hidden storage if needed + if(typeof that.stateEvents === 'undefined'){ + that.stateEvents = {}; + } + + if(typeof that._triggeredStateReady === 'undefined'){ + that._triggeredStateReady = false; + } + + that.stateEvents[name] = false; + + stateUpdate = that.stateUpdate(name, that.stateEvents); + + that.listen(name, stateUpdate, that); + that.listen(name, callback, context); + }; + + returnObject.stateUpdate = function(nameIn, stateEventsIn){ + var name = nameIn + , stateEvents = stateEventsIn + , that = this; + + return function(){ + var truthy = true + , key; + + if(typeof stateEvents[name] !== 'undefined'){ + stateEvents[name] = true; + + for(key in stateEvents){ + truthy = truthy && stateEvents[key]; + } + + if(truthy){ + if(!that._triggeredStateReady || that._fireMultiple){ + //feels like a little bit of a hack. + // lets the data finish propogating before triggering the call + setTimeout(that.stateReady, 100); + that._triggeredStateReady = true; + } + } + } + }; + }; + + returnObject.stateReady = function(){ + //this is a default action when all required events have been completed. + // needs to be overridden if you want to do something real + console.log('ready!'); + }; + + returnObject.listen('system:trace', function(emitterIDIn){ + var that = this; + + if(that.emitterId === emitterIDIn){ + that.emit('system:addTraced', that); + } + }, returnObject); + + //execute the init function if it exists + if(typeof returnObject.init === 'function'){ + returnObject.init.apply(returnObject); + } + + return returnObject; + } + + , modelMe: function(objectIn){ + //this is the module for creating a data model object + var that = this + , newModel = that.eventMe({}) + , attributes = {} + , changes = []; + + newModel.get = function(attributeName){ + return attributes[attributeName]; + }; + + newModel.set = function(attributeName, attributeValue){ + var that = this + , key; + + if(typeof attributeName === 'object'){ + //well... this is an object... iterate and rock on + for(key in attributeName){ + if(attributeName.hasOwnProperty(key)){ + //this attribute does not belong to the prototype. Good. + + //TODO: maybe make this do a deep copy to prevent + // pass by reference or switch to clone() + if(key !== 'destroy' && key !== 'fetch' && key !== 'save' && typeof attributeName[key] !== 'function'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeName[key])) ? [] : {}; + action.clone(attributes[attributeName], attributeName[key]); + }else{ + attributes[key] = attributeName[key]; + } + that.emitLocal('attribute:changed', key); + } else { + that[key] = attributeName[key]; + } + } + } + } else{ + if(attributeName !== 'destroy' && attributeName !== 'fetch' && attributeName !== 'save'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeValue)) ? [] : {}; + action.clone(attributes[attributeName], attributeValue); + }else{ + attributes[attributeName] = attributeValue; + } + + that.emitLocal('attribute:changed', attributeName); + } else { + that[attributeName] = attributeValue; + } + } + } + + newModel.flatten = function(){ + return attributes; + } + + newModel.fetch = function(){ + var that = this + , requestUrl = that.get('url'); + + if(typeof requestUrl !== 'undefined'){ + //make the request for the model + $.ajax({ + type: 'get' + , url: requestUrl + , success: function(data, status){ + if(status){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + that.emit(that.get('dataEvent'), data); + } + , error: function(xhr, errorType, error){ + that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); + } + }); + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.save = function(){ + //TODO make this talk to a server with the URL + //TODO make it only mark the saved changes clear + var that = this + , requestUrl = that.get('url') + , id = that.get('id') + , type = (typeof id === 'undefined') ? 'post' : 'put'; + + if(typeof requestUrl !== 'undefined'){ + $.ajax({ + type: type + , url: requestUrl + '/' + id + , data: that.flatten() + , success: function(data, status){ + //only do this on success... + that.clearChanges(); + + //update the model with stuff from the server + that.set(data); + + //emit the data event for this model to refresh everyone's values + that.emit(that.get('dataEvent'), data); + } + , error: function(){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }); + } else { + action.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.clearChanges = function(){ + changes = []; + } + + newModel.getChanges = function(){ + return changes; + } + + newModel.clear = function(){ + attributes = {}; + } + + newModel.destroy = function(){ + //TODO not really working... should get rid of this thing + // and all of its parameters + var that = this + , key; + + setTimeout(function(){ + // delete me; + },0); // not quite working... + + for(key in that){ + // delete this[key]; + } + + //TODO this still doesn't kill the attributes or changes + // private data + } + + newModel.set(objectIn); //set the inital attributes + + newModel.listenLocal('attribute:changed', function(nameIn){ + changes.push(nameIn); + }, this); //maybe eliminate this 'this' + + if(typeof newModel.init === 'function'){ + newModel.init.apply(newModel); + } + + return newModel; + } + + //TODO: figure out if this is needed since the global:error... + // , trace: function(emitterIdIn){ + // //log out the function that has the emitterId attached + + // //create the traced object/stack + // action.traced = action.modelMe({ + // stack: [] + // , emitterId: emitterIdIn + // }); + + // action.traced.listen('system:addTraced', function(objectIn){ + // var that = this; + + // that.get('stack').push(objectIn); + // }, action.traced); + + // //trigger the event that will cause the trace + // action.emit('system:trace', emitterIdIn); + // } + + , Error: function(typeIn, messageIn, objectIn, errorObjectIn){ + return { + type: typeIn + , message: messageIn + , createdBy: objectIn + , errorObject: errorObjectIn + } + } + + , clone: function(objectIn, cloneMe){ + var key; + + for(key in cloneMe){ + if(cloneMe.hasOwnProperty(key)){ + //good to copy this one... + if (typeof cloneMe[key] === 'object'){ + //set up the object for iteration later + objectIn[key] = (Array.isArray(cloneMe[key])) ? [] : {}; + + action.clone(objectIn[key], cloneMe[key]); + }else{ + objectIn[key] = cloneMe[key]; + } + } + } + } + + , eventStore: {} + }; + + //add an events hook for global dealing with events... + action = action.eventMe(action); + + action.listen('global:error', function(errorIn) { + console.group('An Error occured in an object with emitterid: ' + errorIn.createdBy.emitterId); + console.log('It was a ' + errorIn.type + 'error.'); + + if(typeof errorIn.errorObject === 'string'){ + console.log('It says: ' + errorIn.errorObject); + console.log('and: ' + errorIn.message); + } else { + console.log('It says: ' + errorIn.message); + } + + console.log('The Whole Enchilada (object that caused this mess):'); + console.dir(errorIn.createdBy); + + if(typeof errorIn.createdBy.flatten === 'function'){ + console.log('Just the Lettuce (attributes):'); + console.dir(errorIn.createdBy.flatten()); + } + + if(typeof errorIn.errorObject === 'object'){ + console.log('Oh look an Error Object!:'); + console.dir(errorIn.errorObject); + } + + console.groupEnd(); + // action.trace(errorIn.createdBy.emitterId); + // throw errorIn; + }); + + //return the tweaked function + return action; +}(this); \ No newline at end of file diff --git a/packages/latest/action.min.js b/packages/latest/action.min.js new file mode 100644 index 0000000..ea2506a --- /dev/null +++ b/packages/latest/action.min.js @@ -0,0 +1 @@ +var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,c,i=this,a="undefined"!=typeof o&&o;if(r=a?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(c=0;c Date: Tue, 6 May 2014 01:41:08 -0600 Subject: [PATCH 05/19] fixed some bugs in action while working on the designfrontier site --- package.json | 2 +- packages/0.3.1/action-v0.3.1.js | 591 ++++++++++++++++++++++++++++ packages/0.3.1/action-v0.3.1.min.js | 1 + packages/latest/action.js | 9 +- packages/latest/action.min.js | 2 +- public/javascripts/action.js | 9 +- 6 files changed, 604 insertions(+), 10 deletions(-) create mode 100644 packages/0.3.1/action-v0.3.1.js create mode 100644 packages/0.3.1/action-v0.3.1.min.js diff --git a/package.json b/package.json index 855c97a..1be84d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "action", - "version": "0.3.0", + "version": "0.3.1", "private": true, "scripts": { "start": "node app.js" diff --git a/packages/0.3.1/action-v0.3.1.js b/packages/0.3.1/action-v0.3.1.js new file mode 100644 index 0000000..0c03c87 --- /dev/null +++ b/packages/0.3.1/action-v0.3.1.js @@ -0,0 +1,591 @@ +var action = function(){ + 'use strict'; + + var action = { + eventMe: function(objectIn){ + var returnObject = objectIn + , localEvents = {}; + + //set an emitter id for troubleshooting + returnObject.emitterId = Math.ceil(Math.random() * 10000); + + //create the lcoal event Store + returnObject.eventStore = {}; + + returnObject.emit = function(eventNameIn, eventDataIn, localFlag){ + var that = this + , eventStack + , functionToCall + , i + , isLocal = (typeof localFlag !== 'undefined' && localFlag); + + if(isLocal){ + eventStack = that.eventStore[eventNameIn]; + } else { + eventStack = action.eventStore[eventNameIn]; + } + + //emit the event + if(typeof eventStack !== 'undefined'){ + for(i = 0; i < eventStack.length; i ++){ + if(typeof eventStack[i].scope !== 'undefined'){ + eventStack[i].call.apply(eventStack[i].scope,[eventDataIn, that.emitterId]); + }else{ + eventStack[i].call(eventDataIn, that.emitterId); + } + + if(eventStack[i].once){ + that.silence(eventNameIn, eventStack[i].call, true, isLocal); + } + } + } + }; + + returnObject.emitLocal = function(eventNameIn, eventDataIn){ + var that = this; + + that.emit(eventNameIn, eventDataIn, true); + }; + + returnObject.listen = function(eventNameIn, handlerIn, scopeIn, onceIn, localFlagIn){ + var that = this + , i + , newCheck = true + + //attribute holders and such + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , local = localFlagIn + , once = onceIn + + //variables for later + , eventStack + , newEvent; + + if(typeof eventNameIn === 'object'){ + //we have an object to split up dude + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + once = eventNameIn.once; + local = eventNameIn.local; + } + + eventStack = (typeof local !== 'undefined' && local) ? that.eventStore[eventNameIn] : action.eventStore[eventNameIn]; + newEvent = (typeof local !== 'undefined' && local) ? that : action; + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === false){ + newCheck = false; + break; + } + } + + if(newCheck && typeof scopeIn !== 'undefined'){ + eventStack.push({once: false, call: handler, scope: scope}); + }else if(newCheck){ + eventStack.push({once: false, call:handler}); + } + + } else { + //new event + newEvent.eventStore[eventName] = []; //use an array to store functions + if(typeof scopeIn !== 'undefined'){ + newEvent.eventStore[eventName].push({once: false, call: handler, scope: scope}); + }else{ + newEvent.eventStore[eventName].push({once: false, call: handler}); + } + } + }; + + returnObject.listenLocal = function(eventNameIn, handlerIn, scopeIn, onceIn){ + var that = this; + + //convenience function for local listens + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listen(eventNameIn); + } else { + that.listen({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , once: onceIn + , local: true + }); + } + }; + + returnObject.listenOnce = function(eventNameIn, handlerIn, scopeIn, localFlagIn){ + //same thing as .listen() but is only triggered once + var that = this + , i + , newCheck = true + , eventStack + , newEvent + + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , localFlag = localFlagIn; + + if(typeof eventNameIn === 'object'){ + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + if(typeof localFlag !== 'undefined' && localFlag){ + //make it local! + eventStack = that.eventStore[eventName]; + newEvent = that; + }else{ + eventStack = action.eventStore[eventName]; + newEvent = action; + } + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === true){ + newCheck = false; + break; + } + } + + if(newCheck){ + eventStack.push({once:true, call: handler, scope: scope}); + } + + } else{ + //new event + newEvent.eventStore[eventNameIn] = []; //use an array to store functions + newEvent.eventStore[eventNameIn].push({once:true, call: handler, scope: scope}); + } + }; + + returnObject.listenOnceLocal = function(eventNameIn, handlerIn, scopeIn){ + var that = this; + + //same thing as .listen() but is only triggered once + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listenLocal(eventNameIn); + }else{ + that.listenLocal({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , local: true + }); + } + }; + + returnObject.silence = function(eventNameIn, handlerIn, onceIn, scopeIn, localFlagIn){ + //localize variables + var that = this + , i + , truthy = false + , eventName = eventNameIn + , handler = handlerIn + , once = onceIn + , scope = scopeIn + , localFlag = localFlagIn + , store; + + if(typeof eventNameIn === 'object'){ + // passed in a collection of params instead of params + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + once = eventNameIn.once; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + //local or global event store? + store = (typeof localFlag === 'undefined' || !localFlag) ? action : that; + + if(typeof store.eventStore[eventName] === 'undefined'){ + //if there is no event with a name... return nothing + return; + } + + //there is an event that matches... proceed + for(i = 0; i < store.eventStore[eventName].length; i ++){ + //reset this variable + truthy = false; + + if(typeof handler !== 'undefined'){ + //function is passed in + if(typeof scope !== 'undefined'){ + //scope is passed in... + if(typeof once === 'boolean'){ + // function + scope + once provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope && once === store.eventStore[eventName][i].once); + } else { + //function + scope provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope); + } + } else { + //function + once in for the match + if(typeof once === 'boolean'){ + truthy = (handler === store.eventStore[eventName][i].call && store.eventStore[eventName][i].once === once); + } else { + truthy = (handler === store.eventStore[eventName][i].call); + } + } + } else { + //no function unbind everything by resetting + store.eventStore[eventName] = []; + + //and exit + break; + } + + if(truthy){ + //remove this bad boy + store.eventStore[eventName].splice(i,1); + } + + } + }; + + returnObject.silenceLocal = function(eventNameIn, handlerIn, onceIn, scopeIn){ + var that = this; + + //essentially a convenience function. + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.silence(eventNameIn); + }else{ + that.silence({ + eventName: eventNameIn + , handler: handlerIn + , once: onceIn + , scope: scopeIn + , local: true + }); + } + }; + + //Event Based state machine + returnObject.requiredEvent = function(name, callback, context, fireMultipleIn){ + var that = this + , stateUpdate; + + that._fireMultiple = (typeof fireMultipleIn !== 'undefined') ? fireMultipleIn : false; + + //init some hidden storage if needed + if(typeof that.stateEvents === 'undefined'){ + that.stateEvents = {}; + } + + if(typeof that._triggeredStateReady === 'undefined'){ + that._triggeredStateReady = false; + } + + that.stateEvents[name] = false; + + stateUpdate = that.stateUpdate(name, that.stateEvents); + + that.listen(name, callback, context); + that.listen(name, stateUpdate, that); + }; + + returnObject.stateUpdate = function(nameIn, stateEventsIn){ + var name = nameIn + , stateEvents = stateEventsIn + , that = this; + + return function(){ + var truthy = true + , key; + + if(typeof stateEvents[name] !== 'undefined'){ + stateEvents[name] = true; + + for(key in stateEvents){ + truthy = truthy && stateEvents[key]; + } + + if(truthy){ + if(!that._triggeredStateReady || that._fireMultiple){ + //feels like a little bit of a hack. + // lets the data finish propogating before triggering the call + setTimeout(that.stateReady.apply(that), 100); + that._triggeredStateReady = true; + } + } + } + }; + }; + + returnObject.stateReady = function(){ + //this is a default action when all required events have been completed. + // needs to be overridden if you want to do something real + console.log('ready!'); + }; + + returnObject.listen('system:trace', function(emitterIDIn){ + var that = this; + + if(that.emitterId === emitterIDIn){ + // that.emit('system:addTraced', that); + action.traced = that; + } + }, returnObject); + + //execute the init function if it exists + if(typeof returnObject.init === 'function'){ + returnObject.init.apply(returnObject); + } + + return returnObject; + } + + , modelMe: function(objectIn){ + //this is the module for creating a data model object + var that = this + , newModel = that.eventMe({}) + , attributes = {} + , changes = []; + + newModel.get = function(attributeName){ + return attributes[attributeName]; + }; + + newModel.set = function(attributeName, attributeValue){ + var that = this + , key; + + if(typeof attributeName === 'object'){ + //well... this is an object... iterate and rock on + for(key in attributeName){ + if(attributeName.hasOwnProperty(key)){ + //this attribute does not belong to the prototype. Good. + + //TODO: maybe make this do a deep copy to prevent + // pass by reference or switch to clone() + if(key !== 'destroy' && key !== 'fetch' && key !== 'save' && typeof attributeName[key] !== 'function'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeName[key])) ? [] : {}; + action.clone(attributes[attributeName], attributeName[key]); + }else{ + attributes[key] = attributeName[key]; + } + that.emitLocal('attribute:changed', key); + } else { + that[key] = attributeName[key]; + } + } + } + } else{ + if(attributeName !== 'destroy' && attributeName !== 'fetch' && attributeName !== 'save'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeValue)) ? [] : {}; + action.clone(attributes[attributeName], attributeValue); + }else{ + attributes[attributeName] = attributeValue; + } + + that.emitLocal('attribute:changed', attributeName); + } else { + that[attributeName] = attributeValue; + } + } + } + + newModel.flatten = function(){ + return attributes; + } + + newModel.fetch = function(){ + var that = this + , requestUrl = that.get('url'); + + if(typeof requestUrl !== 'undefined'){ + //make the request for the model + $.ajax({ + type: 'get' + , url: requestUrl + , success: function(data, status){ + if(status !== 'success'){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + that.emit(that.get('dataEvent'), data); + } + , error: function(xhr, errorType, error){ + that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); + } + }); + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.save = function(){ + //TODO make this talk to a server with the URL + //TODO make it only mark the saved changes clear + var that = this + , requestUrl = that.get('url') + , id = that.get('id') + , type = (typeof id === 'undefined') ? 'post' : 'put'; + + if(typeof requestUrl !== 'undefined'){ + $.ajax({ + type: type + , url: requestUrl + '/' + id + , data: that.flatten() + , success: function(data, status){ + //only do this on success... + that.clearChanges(); + + //update the model with stuff from the server + that.set(data); + + //emit the data event for this model to refresh everyone's values + that.emit(that.get('dataEvent'), data); + } + , error: function(){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }); + } else { + action.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.clearChanges = function(){ + changes = []; + } + + newModel.getChanges = function(){ + return changes; + } + + newModel.clear = function(){ + attributes = {}; + } + + newModel.destroy = function(){ + //TODO not really working... should get rid of this thing + // and all of its parameters + var that = this + , key; + + setTimeout(function(){ + // delete me; + },0); // not quite working... + + for(key in that){ + // delete this[key]; + } + + //TODO this still doesn't kill the attributes or changes + // private data + } + + newModel.set(objectIn); //set the inital attributes + + newModel.listenLocal('attribute:changed', function(nameIn){ + changes.push(nameIn); + }, this); //maybe eliminate this 'this' + + if(typeof newModel.init === 'function'){ + newModel.init.apply(newModel); + } + + return newModel; + } + + //TODO: figure out if this is needed since the global:error... + // , trace: function(emitterIdIn){ + // //log out the function that has the emitterId attached + + // //create the traced object/stack + // action.traced = action.modelMe({ + // stack: [] + // , emitterId: emitterIdIn + // }); + + // action.traced.listen('system:addTraced', function(objectIn){ + // var that = this; + + // that.get('stack').push(objectIn); + // }, action.traced); + + // //trigger the event that will cause the trace + // action.emit('system:trace', emitterIdIn); + // } + + , Error: function(typeIn, messageIn, objectIn, errorObjectIn){ + return { + type: typeIn + , message: messageIn + , createdBy: objectIn + , errorObject: errorObjectIn + } + } + + , clone: function(objectIn, cloneMe){ + var key; + + for(key in cloneMe){ + if(cloneMe.hasOwnProperty(key)){ + //good to copy this one... + if (typeof cloneMe[key] === 'object'){ + //set up the object for iteration later + objectIn[key] = (Array.isArray(cloneMe[key])) ? [] : {}; + + action.clone(objectIn[key], cloneMe[key]); + }else{ + objectIn[key] = cloneMe[key]; + } + } + } + } + + , eventStore: {} + }; + + //add an events hook for global dealing with events... + action = action.eventMe(action); + + action.listen('global:error', function(errorIn) { + console.group('An Error occured in an object with emitterid: ' + errorIn.createdBy.emitterId); + console.log('It was a ' + errorIn.type + 'error.'); + + if(typeof errorIn.errorObject === 'string'){ + console.log('It says: ' + errorIn.errorObject); + console.log('and: ' + errorIn.message); + } else { + console.log('It says: ' + errorIn.message); + } + + console.log('The Whole Enchilada (object that caused this mess):'); + console.dir(errorIn.createdBy); + + if(typeof errorIn.createdBy.flatten === 'function'){ + console.log('Just the Lettuce (attributes):'); + console.dir(errorIn.createdBy.flatten()); + } + + if(typeof errorIn.errorObject === 'object'){ + console.log('Oh look an Error Object!:'); + console.dir(errorIn.errorObject); + } + + console.groupEnd(); + // action.trace(errorIn.createdBy.emitterId); + // throw errorIn; + }); + + //return the tweaked function + return action; +}(this); \ No newline at end of file diff --git a/packages/0.3.1/action-v0.3.1.min.js b/packages/0.3.1/action-v0.3.1.min.js new file mode 100644 index 0000000..f4c07fd --- /dev/null +++ b/packages/0.3.1/action-v0.3.1.min.js @@ -0,0 +1 @@ +var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,c,i=this,a="undefined"!=typeof o&&o;if(r=a?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(c=0;c Date: Fri, 16 May 2014 08:49:10 -0600 Subject: [PATCH 06/19] Added a super scope to objects with custom fetch, destroy, delete, save functions. That way you can still call them from your custom fetc (etc.). It encloses their scope so that they execute from the correct location as well. --- public/javascripts/action.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/public/javascripts/action.js b/public/javascripts/action.js index 0c03c87..392de88 100644 --- a/public/javascripts/action.js +++ b/public/javascripts/action.js @@ -1,3 +1,5 @@ +//TODO routing and pushstate +// view rendering on routing events var action = function(){ 'use strict'; @@ -356,6 +358,8 @@ var action = function(){ , attributes = {} , changes = []; + newModel.super = {}; + newModel.get = function(attributeName){ return attributes[attributeName]; }; @@ -381,6 +385,16 @@ var action = function(){ } that.emitLocal('attribute:changed', key); } else { + if(typeof that[key] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[key] = (function(me){ + var that = me; + + return me[key]; + })(that); + } + that[key] = attributeName[key]; } } @@ -396,6 +410,15 @@ var action = function(){ that.emitLocal('attribute:changed', attributeName); } else { + if(typeof that[attributeName] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[attributeName] = (function(me){ + var that = me; + + return me[attributeName]; + })(that); + } that[attributeName] = attributeValue; } } @@ -405,7 +428,7 @@ var action = function(){ return attributes; } - newModel.fetch = function(){ + newModel.fetch = function(setVariableName){ var that = this , requestUrl = that.get('url'); @@ -419,6 +442,11 @@ var action = function(){ that.emit('global:error', new action.Error('http', 'Error in request', that)); } that.emit(that.get('dataEvent'), data); + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } } , error: function(xhr, errorType, error){ that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); From 9f4e635a53a3b8f356ea8e915db85597c1528d5a Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Fri, 16 May 2014 08:49:34 -0600 Subject: [PATCH 07/19] revised the binding to use the Function.bind() method. Cleaner, and works for realz --- public/javascripts/action.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/public/javascripts/action.js b/public/javascripts/action.js index 392de88..4167b93 100644 --- a/public/javascripts/action.js +++ b/public/javascripts/action.js @@ -388,11 +388,7 @@ var action = function(){ if(typeof that[key] === 'function'){ //wrap the super version in a closure so that we can // still execute it correctly - that.super[key] = (function(me){ - var that = me; - - return me[key]; - })(that); + that.super[key] = that[key].bind(that); } that[key] = attributeName[key]; @@ -413,11 +409,7 @@ var action = function(){ if(typeof that[attributeName] === 'function'){ //wrap the super version in a closure so that we can // still execute it correctly - that.super[attributeName] = (function(me){ - var that = me; - - return me[attributeName]; - })(that); + that.super[attributeName] = that[attributeName].bind(that); } that[attributeName] = attributeValue; } From 1a9086e4797a45f018598eb259b9315bd8ea4a79 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Fri, 16 May 2014 08:50:43 -0600 Subject: [PATCH 08/19] bumped the revision --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1be84d7..b70bfaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "action", - "version": "0.3.1", + "version": "0.3.2", "private": true, "scripts": { "start": "node app.js" From 73c03d977df11815d2ed92c4e4e0b3ca08076ff4 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Fri, 16 May 2014 08:51:40 -0600 Subject: [PATCH 09/19] built for use --- packages/0.3.2/action-v0.3.2.js | 611 ++++++++++++++++++++++++++++ packages/0.3.2/action-v0.3.2.min.js | 1 + packages/latest/action.js | 22 +- packages/latest/action.min.js | 2 +- 4 files changed, 634 insertions(+), 2 deletions(-) create mode 100644 packages/0.3.2/action-v0.3.2.js create mode 100644 packages/0.3.2/action-v0.3.2.min.js diff --git a/packages/0.3.2/action-v0.3.2.js b/packages/0.3.2/action-v0.3.2.js new file mode 100644 index 0000000..4167b93 --- /dev/null +++ b/packages/0.3.2/action-v0.3.2.js @@ -0,0 +1,611 @@ +//TODO routing and pushstate +// view rendering on routing events +var action = function(){ + 'use strict'; + + var action = { + eventMe: function(objectIn){ + var returnObject = objectIn + , localEvents = {}; + + //set an emitter id for troubleshooting + returnObject.emitterId = Math.ceil(Math.random() * 10000); + + //create the lcoal event Store + returnObject.eventStore = {}; + + returnObject.emit = function(eventNameIn, eventDataIn, localFlag){ + var that = this + , eventStack + , functionToCall + , i + , isLocal = (typeof localFlag !== 'undefined' && localFlag); + + if(isLocal){ + eventStack = that.eventStore[eventNameIn]; + } else { + eventStack = action.eventStore[eventNameIn]; + } + + //emit the event + if(typeof eventStack !== 'undefined'){ + for(i = 0; i < eventStack.length; i ++){ + if(typeof eventStack[i].scope !== 'undefined'){ + eventStack[i].call.apply(eventStack[i].scope,[eventDataIn, that.emitterId]); + }else{ + eventStack[i].call(eventDataIn, that.emitterId); + } + + if(eventStack[i].once){ + that.silence(eventNameIn, eventStack[i].call, true, isLocal); + } + } + } + }; + + returnObject.emitLocal = function(eventNameIn, eventDataIn){ + var that = this; + + that.emit(eventNameIn, eventDataIn, true); + }; + + returnObject.listen = function(eventNameIn, handlerIn, scopeIn, onceIn, localFlagIn){ + var that = this + , i + , newCheck = true + + //attribute holders and such + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , local = localFlagIn + , once = onceIn + + //variables for later + , eventStack + , newEvent; + + if(typeof eventNameIn === 'object'){ + //we have an object to split up dude + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + once = eventNameIn.once; + local = eventNameIn.local; + } + + eventStack = (typeof local !== 'undefined' && local) ? that.eventStore[eventNameIn] : action.eventStore[eventNameIn]; + newEvent = (typeof local !== 'undefined' && local) ? that : action; + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === false){ + newCheck = false; + break; + } + } + + if(newCheck && typeof scopeIn !== 'undefined'){ + eventStack.push({once: false, call: handler, scope: scope}); + }else if(newCheck){ + eventStack.push({once: false, call:handler}); + } + + } else { + //new event + newEvent.eventStore[eventName] = []; //use an array to store functions + if(typeof scopeIn !== 'undefined'){ + newEvent.eventStore[eventName].push({once: false, call: handler, scope: scope}); + }else{ + newEvent.eventStore[eventName].push({once: false, call: handler}); + } + } + }; + + returnObject.listenLocal = function(eventNameIn, handlerIn, scopeIn, onceIn){ + var that = this; + + //convenience function for local listens + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listen(eventNameIn); + } else { + that.listen({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , once: onceIn + , local: true + }); + } + }; + + returnObject.listenOnce = function(eventNameIn, handlerIn, scopeIn, localFlagIn){ + //same thing as .listen() but is only triggered once + var that = this + , i + , newCheck = true + , eventStack + , newEvent + + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , localFlag = localFlagIn; + + if(typeof eventNameIn === 'object'){ + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + if(typeof localFlag !== 'undefined' && localFlag){ + //make it local! + eventStack = that.eventStore[eventName]; + newEvent = that; + }else{ + eventStack = action.eventStore[eventName]; + newEvent = action; + } + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === true){ + newCheck = false; + break; + } + } + + if(newCheck){ + eventStack.push({once:true, call: handler, scope: scope}); + } + + } else{ + //new event + newEvent.eventStore[eventNameIn] = []; //use an array to store functions + newEvent.eventStore[eventNameIn].push({once:true, call: handler, scope: scope}); + } + }; + + returnObject.listenOnceLocal = function(eventNameIn, handlerIn, scopeIn){ + var that = this; + + //same thing as .listen() but is only triggered once + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listenLocal(eventNameIn); + }else{ + that.listenLocal({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , local: true + }); + } + }; + + returnObject.silence = function(eventNameIn, handlerIn, onceIn, scopeIn, localFlagIn){ + //localize variables + var that = this + , i + , truthy = false + , eventName = eventNameIn + , handler = handlerIn + , once = onceIn + , scope = scopeIn + , localFlag = localFlagIn + , store; + + if(typeof eventNameIn === 'object'){ + // passed in a collection of params instead of params + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + once = eventNameIn.once; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + //local or global event store? + store = (typeof localFlag === 'undefined' || !localFlag) ? action : that; + + if(typeof store.eventStore[eventName] === 'undefined'){ + //if there is no event with a name... return nothing + return; + } + + //there is an event that matches... proceed + for(i = 0; i < store.eventStore[eventName].length; i ++){ + //reset this variable + truthy = false; + + if(typeof handler !== 'undefined'){ + //function is passed in + if(typeof scope !== 'undefined'){ + //scope is passed in... + if(typeof once === 'boolean'){ + // function + scope + once provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope && once === store.eventStore[eventName][i].once); + } else { + //function + scope provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope); + } + } else { + //function + once in for the match + if(typeof once === 'boolean'){ + truthy = (handler === store.eventStore[eventName][i].call && store.eventStore[eventName][i].once === once); + } else { + truthy = (handler === store.eventStore[eventName][i].call); + } + } + } else { + //no function unbind everything by resetting + store.eventStore[eventName] = []; + + //and exit + break; + } + + if(truthy){ + //remove this bad boy + store.eventStore[eventName].splice(i,1); + } + + } + }; + + returnObject.silenceLocal = function(eventNameIn, handlerIn, onceIn, scopeIn){ + var that = this; + + //essentially a convenience function. + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.silence(eventNameIn); + }else{ + that.silence({ + eventName: eventNameIn + , handler: handlerIn + , once: onceIn + , scope: scopeIn + , local: true + }); + } + }; + + //Event Based state machine + returnObject.requiredEvent = function(name, callback, context, fireMultipleIn){ + var that = this + , stateUpdate; + + that._fireMultiple = (typeof fireMultipleIn !== 'undefined') ? fireMultipleIn : false; + + //init some hidden storage if needed + if(typeof that.stateEvents === 'undefined'){ + that.stateEvents = {}; + } + + if(typeof that._triggeredStateReady === 'undefined'){ + that._triggeredStateReady = false; + } + + that.stateEvents[name] = false; + + stateUpdate = that.stateUpdate(name, that.stateEvents); + + that.listen(name, callback, context); + that.listen(name, stateUpdate, that); + }; + + returnObject.stateUpdate = function(nameIn, stateEventsIn){ + var name = nameIn + , stateEvents = stateEventsIn + , that = this; + + return function(){ + var truthy = true + , key; + + if(typeof stateEvents[name] !== 'undefined'){ + stateEvents[name] = true; + + for(key in stateEvents){ + truthy = truthy && stateEvents[key]; + } + + if(truthy){ + if(!that._triggeredStateReady || that._fireMultiple){ + //feels like a little bit of a hack. + // lets the data finish propogating before triggering the call + setTimeout(that.stateReady.apply(that), 100); + that._triggeredStateReady = true; + } + } + } + }; + }; + + returnObject.stateReady = function(){ + //this is a default action when all required events have been completed. + // needs to be overridden if you want to do something real + console.log('ready!'); + }; + + returnObject.listen('system:trace', function(emitterIDIn){ + var that = this; + + if(that.emitterId === emitterIDIn){ + // that.emit('system:addTraced', that); + action.traced = that; + } + }, returnObject); + + //execute the init function if it exists + if(typeof returnObject.init === 'function'){ + returnObject.init.apply(returnObject); + } + + return returnObject; + } + + , modelMe: function(objectIn){ + //this is the module for creating a data model object + var that = this + , newModel = that.eventMe({}) + , attributes = {} + , changes = []; + + newModel.super = {}; + + newModel.get = function(attributeName){ + return attributes[attributeName]; + }; + + newModel.set = function(attributeName, attributeValue){ + var that = this + , key; + + if(typeof attributeName === 'object'){ + //well... this is an object... iterate and rock on + for(key in attributeName){ + if(attributeName.hasOwnProperty(key)){ + //this attribute does not belong to the prototype. Good. + + //TODO: maybe make this do a deep copy to prevent + // pass by reference or switch to clone() + if(key !== 'destroy' && key !== 'fetch' && key !== 'save' && typeof attributeName[key] !== 'function'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeName[key])) ? [] : {}; + action.clone(attributes[attributeName], attributeName[key]); + }else{ + attributes[key] = attributeName[key]; + } + that.emitLocal('attribute:changed', key); + } else { + if(typeof that[key] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[key] = that[key].bind(that); + } + + that[key] = attributeName[key]; + } + } + } + } else{ + if(attributeName !== 'destroy' && attributeName !== 'fetch' && attributeName !== 'save'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeValue)) ? [] : {}; + action.clone(attributes[attributeName], attributeValue); + }else{ + attributes[attributeName] = attributeValue; + } + + that.emitLocal('attribute:changed', attributeName); + } else { + if(typeof that[attributeName] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[attributeName] = that[attributeName].bind(that); + } + that[attributeName] = attributeValue; + } + } + } + + newModel.flatten = function(){ + return attributes; + } + + newModel.fetch = function(setVariableName){ + var that = this + , requestUrl = that.get('url'); + + if(typeof requestUrl !== 'undefined'){ + //make the request for the model + $.ajax({ + type: 'get' + , url: requestUrl + , success: function(data, status){ + if(status !== 'success'){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + that.emit(that.get('dataEvent'), data); + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } + } + , error: function(xhr, errorType, error){ + that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); + } + }); + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.save = function(){ + //TODO make this talk to a server with the URL + //TODO make it only mark the saved changes clear + var that = this + , requestUrl = that.get('url') + , id = that.get('id') + , type = (typeof id === 'undefined') ? 'post' : 'put'; + + if(typeof requestUrl !== 'undefined'){ + $.ajax({ + type: type + , url: requestUrl + '/' + id + , data: that.flatten() + , success: function(data, status){ + //only do this on success... + that.clearChanges(); + + //update the model with stuff from the server + that.set(data); + + //emit the data event for this model to refresh everyone's values + that.emit(that.get('dataEvent'), data); + } + , error: function(){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }); + } else { + action.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.clearChanges = function(){ + changes = []; + } + + newModel.getChanges = function(){ + return changes; + } + + newModel.clear = function(){ + attributes = {}; + } + + newModel.destroy = function(){ + //TODO not really working... should get rid of this thing + // and all of its parameters + var that = this + , key; + + setTimeout(function(){ + // delete me; + },0); // not quite working... + + for(key in that){ + // delete this[key]; + } + + //TODO this still doesn't kill the attributes or changes + // private data + } + + newModel.set(objectIn); //set the inital attributes + + newModel.listenLocal('attribute:changed', function(nameIn){ + changes.push(nameIn); + }, this); //maybe eliminate this 'this' + + if(typeof newModel.init === 'function'){ + newModel.init.apply(newModel); + } + + return newModel; + } + + //TODO: figure out if this is needed since the global:error... + // , trace: function(emitterIdIn){ + // //log out the function that has the emitterId attached + + // //create the traced object/stack + // action.traced = action.modelMe({ + // stack: [] + // , emitterId: emitterIdIn + // }); + + // action.traced.listen('system:addTraced', function(objectIn){ + // var that = this; + + // that.get('stack').push(objectIn); + // }, action.traced); + + // //trigger the event that will cause the trace + // action.emit('system:trace', emitterIdIn); + // } + + , Error: function(typeIn, messageIn, objectIn, errorObjectIn){ + return { + type: typeIn + , message: messageIn + , createdBy: objectIn + , errorObject: errorObjectIn + } + } + + , clone: function(objectIn, cloneMe){ + var key; + + for(key in cloneMe){ + if(cloneMe.hasOwnProperty(key)){ + //good to copy this one... + if (typeof cloneMe[key] === 'object'){ + //set up the object for iteration later + objectIn[key] = (Array.isArray(cloneMe[key])) ? [] : {}; + + action.clone(objectIn[key], cloneMe[key]); + }else{ + objectIn[key] = cloneMe[key]; + } + } + } + } + + , eventStore: {} + }; + + //add an events hook for global dealing with events... + action = action.eventMe(action); + + action.listen('global:error', function(errorIn) { + console.group('An Error occured in an object with emitterid: ' + errorIn.createdBy.emitterId); + console.log('It was a ' + errorIn.type + 'error.'); + + if(typeof errorIn.errorObject === 'string'){ + console.log('It says: ' + errorIn.errorObject); + console.log('and: ' + errorIn.message); + } else { + console.log('It says: ' + errorIn.message); + } + + console.log('The Whole Enchilada (object that caused this mess):'); + console.dir(errorIn.createdBy); + + if(typeof errorIn.createdBy.flatten === 'function'){ + console.log('Just the Lettuce (attributes):'); + console.dir(errorIn.createdBy.flatten()); + } + + if(typeof errorIn.errorObject === 'object'){ + console.log('Oh look an Error Object!:'); + console.dir(errorIn.errorObject); + } + + console.groupEnd(); + // action.trace(errorIn.createdBy.emitterId); + // throw errorIn; + }); + + //return the tweaked function + return action; +}(this); \ No newline at end of file diff --git a/packages/0.3.2/action-v0.3.2.min.js b/packages/0.3.2/action-v0.3.2.min.js new file mode 100644 index 0000000..708235f --- /dev/null +++ b/packages/0.3.2/action-v0.3.2.min.js @@ -0,0 +1 @@ +var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,c,i=this,a="undefined"!=typeof o&&o;if(r=a?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(c=0;c Date: Fri, 23 May 2014 11:02:41 -0600 Subject: [PATCH 10/19] added a local storage abstraction for fetch, and bumped the revision accordingly" --- package.json | 2 +- public/javascripts/action.js | 48 ++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index b70bfaa..f3a12db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "action", - "version": "0.3.2", + "version": "0.3.3", "private": true, "scripts": { "start": "node app.js" diff --git a/public/javascripts/action.js b/public/javascripts/action.js index 4167b93..bed7653 100644 --- a/public/javascripts/action.js +++ b/public/javascripts/action.js @@ -420,13 +420,46 @@ var action = function(){ return attributes; } - newModel.fetch = function(setVariableName){ + newModel.fetch = function(setVariableName, successFunction, errorFunction){ var that = this - , requestUrl = that.get('url'); + , requestUrl = that.get('url') + , useLocal = action.useLocalCache; if(typeof requestUrl !== 'undefined'){ //make the request for the model - $.ajax({ + if(useLocal){ + window.localforage.getItem(window.btoa(that.get('url')), function(data){ + if(data === null){ + //this doesn't exist locally... + that.ajaxGet(setVariableName, function(dataIn){ + var localData = dataIn + , articleId = that.get('url'); + + window.localforage.setItem(window.btoa(articleId), localData, function(){ + console.log('data done'); + }); + }); + }else{ + //it does exist! + that.emit(that.get('dataEvent'), data); + } + }); + } else { + that.ajaxGet(setVariableName, successFunction); + } + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + if(typeof errorFunction === 'function'){ + errorFunction.apply(that); + } + } + }; + + newModel.ajaxGet = function(setVariableName, successFunction){ + var that = this + , requestUrl = that.get('url'); + + $.ajax({ type: 'get' , url: requestUrl , success: function(data, status){ @@ -439,15 +472,16 @@ var action = function(){ }else{ that.set(data); } + + if(typeof successFunction === 'function'){ + successFunction.apply(that, [data]); + } } , error: function(xhr, errorType, error){ that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); } }); - } else { - that.emit('global:error', new action.Error('http', 'No URL defined', that)); - } - } + }; newModel.save = function(){ //TODO make this talk to a server with the URL From 075be73b4467f04ab5099c513b0940f675a657da Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Sat, 24 May 2014 09:24:47 -0500 Subject: [PATCH 11/19] added some documentation, localforage caching for all ajax requests, created a new release based on that --- packages/0.3.3/action-v0.3.3.js | 645 ++++++++++++++++++++++++++++ packages/0.3.3/action-v0.3.3.min.js | 1 + packages/latest/action.js | 48 ++- packages/latest/action.min.js | 2 +- readme.md | 15 +- 5 files changed, 702 insertions(+), 9 deletions(-) create mode 100644 packages/0.3.3/action-v0.3.3.js create mode 100644 packages/0.3.3/action-v0.3.3.min.js diff --git a/packages/0.3.3/action-v0.3.3.js b/packages/0.3.3/action-v0.3.3.js new file mode 100644 index 0000000..bed7653 --- /dev/null +++ b/packages/0.3.3/action-v0.3.3.js @@ -0,0 +1,645 @@ +//TODO routing and pushstate +// view rendering on routing events +var action = function(){ + 'use strict'; + + var action = { + eventMe: function(objectIn){ + var returnObject = objectIn + , localEvents = {}; + + //set an emitter id for troubleshooting + returnObject.emitterId = Math.ceil(Math.random() * 10000); + + //create the lcoal event Store + returnObject.eventStore = {}; + + returnObject.emit = function(eventNameIn, eventDataIn, localFlag){ + var that = this + , eventStack + , functionToCall + , i + , isLocal = (typeof localFlag !== 'undefined' && localFlag); + + if(isLocal){ + eventStack = that.eventStore[eventNameIn]; + } else { + eventStack = action.eventStore[eventNameIn]; + } + + //emit the event + if(typeof eventStack !== 'undefined'){ + for(i = 0; i < eventStack.length; i ++){ + if(typeof eventStack[i].scope !== 'undefined'){ + eventStack[i].call.apply(eventStack[i].scope,[eventDataIn, that.emitterId]); + }else{ + eventStack[i].call(eventDataIn, that.emitterId); + } + + if(eventStack[i].once){ + that.silence(eventNameIn, eventStack[i].call, true, isLocal); + } + } + } + }; + + returnObject.emitLocal = function(eventNameIn, eventDataIn){ + var that = this; + + that.emit(eventNameIn, eventDataIn, true); + }; + + returnObject.listen = function(eventNameIn, handlerIn, scopeIn, onceIn, localFlagIn){ + var that = this + , i + , newCheck = true + + //attribute holders and such + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , local = localFlagIn + , once = onceIn + + //variables for later + , eventStack + , newEvent; + + if(typeof eventNameIn === 'object'){ + //we have an object to split up dude + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + once = eventNameIn.once; + local = eventNameIn.local; + } + + eventStack = (typeof local !== 'undefined' && local) ? that.eventStore[eventNameIn] : action.eventStore[eventNameIn]; + newEvent = (typeof local !== 'undefined' && local) ? that : action; + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === false){ + newCheck = false; + break; + } + } + + if(newCheck && typeof scopeIn !== 'undefined'){ + eventStack.push({once: false, call: handler, scope: scope}); + }else if(newCheck){ + eventStack.push({once: false, call:handler}); + } + + } else { + //new event + newEvent.eventStore[eventName] = []; //use an array to store functions + if(typeof scopeIn !== 'undefined'){ + newEvent.eventStore[eventName].push({once: false, call: handler, scope: scope}); + }else{ + newEvent.eventStore[eventName].push({once: false, call: handler}); + } + } + }; + + returnObject.listenLocal = function(eventNameIn, handlerIn, scopeIn, onceIn){ + var that = this; + + //convenience function for local listens + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listen(eventNameIn); + } else { + that.listen({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , once: onceIn + , local: true + }); + } + }; + + returnObject.listenOnce = function(eventNameIn, handlerIn, scopeIn, localFlagIn){ + //same thing as .listen() but is only triggered once + var that = this + , i + , newCheck = true + , eventStack + , newEvent + + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , localFlag = localFlagIn; + + if(typeof eventNameIn === 'object'){ + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + if(typeof localFlag !== 'undefined' && localFlag){ + //make it local! + eventStack = that.eventStore[eventName]; + newEvent = that; + }else{ + eventStack = action.eventStore[eventName]; + newEvent = action; + } + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === true){ + newCheck = false; + break; + } + } + + if(newCheck){ + eventStack.push({once:true, call: handler, scope: scope}); + } + + } else{ + //new event + newEvent.eventStore[eventNameIn] = []; //use an array to store functions + newEvent.eventStore[eventNameIn].push({once:true, call: handler, scope: scope}); + } + }; + + returnObject.listenOnceLocal = function(eventNameIn, handlerIn, scopeIn){ + var that = this; + + //same thing as .listen() but is only triggered once + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listenLocal(eventNameIn); + }else{ + that.listenLocal({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , local: true + }); + } + }; + + returnObject.silence = function(eventNameIn, handlerIn, onceIn, scopeIn, localFlagIn){ + //localize variables + var that = this + , i + , truthy = false + , eventName = eventNameIn + , handler = handlerIn + , once = onceIn + , scope = scopeIn + , localFlag = localFlagIn + , store; + + if(typeof eventNameIn === 'object'){ + // passed in a collection of params instead of params + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + once = eventNameIn.once; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + //local or global event store? + store = (typeof localFlag === 'undefined' || !localFlag) ? action : that; + + if(typeof store.eventStore[eventName] === 'undefined'){ + //if there is no event with a name... return nothing + return; + } + + //there is an event that matches... proceed + for(i = 0; i < store.eventStore[eventName].length; i ++){ + //reset this variable + truthy = false; + + if(typeof handler !== 'undefined'){ + //function is passed in + if(typeof scope !== 'undefined'){ + //scope is passed in... + if(typeof once === 'boolean'){ + // function + scope + once provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope && once === store.eventStore[eventName][i].once); + } else { + //function + scope provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope); + } + } else { + //function + once in for the match + if(typeof once === 'boolean'){ + truthy = (handler === store.eventStore[eventName][i].call && store.eventStore[eventName][i].once === once); + } else { + truthy = (handler === store.eventStore[eventName][i].call); + } + } + } else { + //no function unbind everything by resetting + store.eventStore[eventName] = []; + + //and exit + break; + } + + if(truthy){ + //remove this bad boy + store.eventStore[eventName].splice(i,1); + } + + } + }; + + returnObject.silenceLocal = function(eventNameIn, handlerIn, onceIn, scopeIn){ + var that = this; + + //essentially a convenience function. + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.silence(eventNameIn); + }else{ + that.silence({ + eventName: eventNameIn + , handler: handlerIn + , once: onceIn + , scope: scopeIn + , local: true + }); + } + }; + + //Event Based state machine + returnObject.requiredEvent = function(name, callback, context, fireMultipleIn){ + var that = this + , stateUpdate; + + that._fireMultiple = (typeof fireMultipleIn !== 'undefined') ? fireMultipleIn : false; + + //init some hidden storage if needed + if(typeof that.stateEvents === 'undefined'){ + that.stateEvents = {}; + } + + if(typeof that._triggeredStateReady === 'undefined'){ + that._triggeredStateReady = false; + } + + that.stateEvents[name] = false; + + stateUpdate = that.stateUpdate(name, that.stateEvents); + + that.listen(name, callback, context); + that.listen(name, stateUpdate, that); + }; + + returnObject.stateUpdate = function(nameIn, stateEventsIn){ + var name = nameIn + , stateEvents = stateEventsIn + , that = this; + + return function(){ + var truthy = true + , key; + + if(typeof stateEvents[name] !== 'undefined'){ + stateEvents[name] = true; + + for(key in stateEvents){ + truthy = truthy && stateEvents[key]; + } + + if(truthy){ + if(!that._triggeredStateReady || that._fireMultiple){ + //feels like a little bit of a hack. + // lets the data finish propogating before triggering the call + setTimeout(that.stateReady.apply(that), 100); + that._triggeredStateReady = true; + } + } + } + }; + }; + + returnObject.stateReady = function(){ + //this is a default action when all required events have been completed. + // needs to be overridden if you want to do something real + console.log('ready!'); + }; + + returnObject.listen('system:trace', function(emitterIDIn){ + var that = this; + + if(that.emitterId === emitterIDIn){ + // that.emit('system:addTraced', that); + action.traced = that; + } + }, returnObject); + + //execute the init function if it exists + if(typeof returnObject.init === 'function'){ + returnObject.init.apply(returnObject); + } + + return returnObject; + } + + , modelMe: function(objectIn){ + //this is the module for creating a data model object + var that = this + , newModel = that.eventMe({}) + , attributes = {} + , changes = []; + + newModel.super = {}; + + newModel.get = function(attributeName){ + return attributes[attributeName]; + }; + + newModel.set = function(attributeName, attributeValue){ + var that = this + , key; + + if(typeof attributeName === 'object'){ + //well... this is an object... iterate and rock on + for(key in attributeName){ + if(attributeName.hasOwnProperty(key)){ + //this attribute does not belong to the prototype. Good. + + //TODO: maybe make this do a deep copy to prevent + // pass by reference or switch to clone() + if(key !== 'destroy' && key !== 'fetch' && key !== 'save' && typeof attributeName[key] !== 'function'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeName[key])) ? [] : {}; + action.clone(attributes[attributeName], attributeName[key]); + }else{ + attributes[key] = attributeName[key]; + } + that.emitLocal('attribute:changed', key); + } else { + if(typeof that[key] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[key] = that[key].bind(that); + } + + that[key] = attributeName[key]; + } + } + } + } else{ + if(attributeName !== 'destroy' && attributeName !== 'fetch' && attributeName !== 'save'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeValue)) ? [] : {}; + action.clone(attributes[attributeName], attributeValue); + }else{ + attributes[attributeName] = attributeValue; + } + + that.emitLocal('attribute:changed', attributeName); + } else { + if(typeof that[attributeName] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[attributeName] = that[attributeName].bind(that); + } + that[attributeName] = attributeValue; + } + } + } + + newModel.flatten = function(){ + return attributes; + } + + newModel.fetch = function(setVariableName, successFunction, errorFunction){ + var that = this + , requestUrl = that.get('url') + , useLocal = action.useLocalCache; + + if(typeof requestUrl !== 'undefined'){ + //make the request for the model + if(useLocal){ + window.localforage.getItem(window.btoa(that.get('url')), function(data){ + if(data === null){ + //this doesn't exist locally... + that.ajaxGet(setVariableName, function(dataIn){ + var localData = dataIn + , articleId = that.get('url'); + + window.localforage.setItem(window.btoa(articleId), localData, function(){ + console.log('data done'); + }); + }); + }else{ + //it does exist! + that.emit(that.get('dataEvent'), data); + } + }); + } else { + that.ajaxGet(setVariableName, successFunction); + } + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + if(typeof errorFunction === 'function'){ + errorFunction.apply(that); + } + } + }; + + newModel.ajaxGet = function(setVariableName, successFunction){ + var that = this + , requestUrl = that.get('url'); + + $.ajax({ + type: 'get' + , url: requestUrl + , success: function(data, status){ + if(status !== 'success'){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + that.emit(that.get('dataEvent'), data); + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } + + if(typeof successFunction === 'function'){ + successFunction.apply(that, [data]); + } + } + , error: function(xhr, errorType, error){ + that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); + } + }); + }; + + newModel.save = function(){ + //TODO make this talk to a server with the URL + //TODO make it only mark the saved changes clear + var that = this + , requestUrl = that.get('url') + , id = that.get('id') + , type = (typeof id === 'undefined') ? 'post' : 'put'; + + if(typeof requestUrl !== 'undefined'){ + $.ajax({ + type: type + , url: requestUrl + '/' + id + , data: that.flatten() + , success: function(data, status){ + //only do this on success... + that.clearChanges(); + + //update the model with stuff from the server + that.set(data); + + //emit the data event for this model to refresh everyone's values + that.emit(that.get('dataEvent'), data); + } + , error: function(){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }); + } else { + action.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.clearChanges = function(){ + changes = []; + } + + newModel.getChanges = function(){ + return changes; + } + + newModel.clear = function(){ + attributes = {}; + } + + newModel.destroy = function(){ + //TODO not really working... should get rid of this thing + // and all of its parameters + var that = this + , key; + + setTimeout(function(){ + // delete me; + },0); // not quite working... + + for(key in that){ + // delete this[key]; + } + + //TODO this still doesn't kill the attributes or changes + // private data + } + + newModel.set(objectIn); //set the inital attributes + + newModel.listenLocal('attribute:changed', function(nameIn){ + changes.push(nameIn); + }, this); //maybe eliminate this 'this' + + if(typeof newModel.init === 'function'){ + newModel.init.apply(newModel); + } + + return newModel; + } + + //TODO: figure out if this is needed since the global:error... + // , trace: function(emitterIdIn){ + // //log out the function that has the emitterId attached + + // //create the traced object/stack + // action.traced = action.modelMe({ + // stack: [] + // , emitterId: emitterIdIn + // }); + + // action.traced.listen('system:addTraced', function(objectIn){ + // var that = this; + + // that.get('stack').push(objectIn); + // }, action.traced); + + // //trigger the event that will cause the trace + // action.emit('system:trace', emitterIdIn); + // } + + , Error: function(typeIn, messageIn, objectIn, errorObjectIn){ + return { + type: typeIn + , message: messageIn + , createdBy: objectIn + , errorObject: errorObjectIn + } + } + + , clone: function(objectIn, cloneMe){ + var key; + + for(key in cloneMe){ + if(cloneMe.hasOwnProperty(key)){ + //good to copy this one... + if (typeof cloneMe[key] === 'object'){ + //set up the object for iteration later + objectIn[key] = (Array.isArray(cloneMe[key])) ? [] : {}; + + action.clone(objectIn[key], cloneMe[key]); + }else{ + objectIn[key] = cloneMe[key]; + } + } + } + } + + , eventStore: {} + }; + + //add an events hook for global dealing with events... + action = action.eventMe(action); + + action.listen('global:error', function(errorIn) { + console.group('An Error occured in an object with emitterid: ' + errorIn.createdBy.emitterId); + console.log('It was a ' + errorIn.type + 'error.'); + + if(typeof errorIn.errorObject === 'string'){ + console.log('It says: ' + errorIn.errorObject); + console.log('and: ' + errorIn.message); + } else { + console.log('It says: ' + errorIn.message); + } + + console.log('The Whole Enchilada (object that caused this mess):'); + console.dir(errorIn.createdBy); + + if(typeof errorIn.createdBy.flatten === 'function'){ + console.log('Just the Lettuce (attributes):'); + console.dir(errorIn.createdBy.flatten()); + } + + if(typeof errorIn.errorObject === 'object'){ + console.log('Oh look an Error Object!:'); + console.dir(errorIn.errorObject); + } + + console.groupEnd(); + // action.trace(errorIn.createdBy.emitterId); + // throw errorIn; + }); + + //return the tweaked function + return action; +}(this); \ No newline at end of file diff --git a/packages/0.3.3/action-v0.3.3.min.js b/packages/0.3.3/action-v0.3.3.min.js new file mode 100644 index 0000000..203d4ee --- /dev/null +++ b/packages/0.3.3/action-v0.3.3.min.js @@ -0,0 +1 @@ +var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,i,a=this,c="undefined"!=typeof o&&o;if(r=c?a.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(i=0;i Date: Tue, 10 Jun 2014 23:59:03 -0600 Subject: [PATCH 12/19] removed the dependency on jquery/zepto from the framework's ajax implementation which was the only place it was currently used --- public/javascripts/action.js | 99 +++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/public/javascripts/action.js b/public/javascripts/action.js index bed7653..8fd3a92 100644 --- a/public/javascripts/action.js +++ b/public/javascripts/action.js @@ -420,10 +420,10 @@ var action = function(){ return attributes; } - newModel.fetch = function(setVariableName, successFunction, errorFunction){ + newModel.fetch = function(setVariableName, successFunction, errorFunction, flushCache){ var that = this , requestUrl = that.get('url') - , useLocal = action.useLocalCache; + , useLocal = action.useLocalCache && !flushCache; if(typeof requestUrl !== 'undefined'){ //make the request for the model @@ -436,7 +436,7 @@ var action = function(){ , articleId = that.get('url'); window.localforage.setItem(window.btoa(articleId), localData, function(){ - console.log('data done'); + // console.log('data done'); }); }); }else{ @@ -457,30 +457,39 @@ var action = function(){ newModel.ajaxGet = function(setVariableName, successFunction){ var that = this - , requestUrl = that.get('url'); + , requestUrl = that.get('url')// + '?' + Date.now() - $.ajax({ - type: 'get' - , url: requestUrl - , success: function(data, status){ - if(status !== 'success'){ + , oReq = new XMLHttpRequest(); + + oReq.onload = function(){ + var data = JSON.parse(this.responseText); + + //TODO: make the statuses more generic + if(this.status === 200 || this.status === 302){ + that.emit(that.get('dataEvent'), data); + + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } + + if(typeof successFunction === 'function'){ + successFunction.apply(that, [data]); + } + }else if(this.status === 400){ + + }else if(this.status === 500){ that.emit('global:error', new action.Error('http', 'Error in request', that)); } - that.emit(that.get('dataEvent'), data); - if(typeof setVariableName === 'string'){ - that.set(setVariableName, data); - }else{ - that.set(data); - } + }; - if(typeof successFunction === 'function'){ - successFunction.apply(that, [data]); - } - } - , error: function(xhr, errorType, error){ + oReq.onerror = function(xhr, errorType, error){ that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); - } - }); + }; + + oReq.open('get', requestUrl, true); + oReq.send(); }; newModel.save = function(){ @@ -489,27 +498,45 @@ var action = function(){ var that = this , requestUrl = that.get('url') , id = that.get('id') - , type = (typeof id === 'undefined') ? 'post' : 'put'; + , type = (typeof id === 'undefined') ? 'post' : 'put' + + , oReq = new XMLHttpRequest(); if(typeof requestUrl !== 'undefined'){ - $.ajax({ - type: type - , url: requestUrl + '/' + id - , data: that.flatten() - , success: function(data, status){ - //only do this on success... + oReq.onload = function(){ + if(this.status === 200 || this.status === 302){ that.clearChanges(); - - //update the model with stuff from the server that.set(data); - - //emit the data event for this model to refresh everyone's values that.emit(that.get('dataEvent'), data); - } - , error: function(){ + + }else if(this.status === 500 || this.status === 400){ that.emit('global:error', new action.Error('http', 'Error in request', that)); } - }); + }; + + oReq.submittedData = that.flatten(); + + oReq.open(type, requestUrl, true); + oReq.send(); + + // $.ajax({ + // type: type + // , url: requestUrl + '/' + id + // , data: that.flatten() + // , success: function(data, status){ + // //only do this on success... + // that.clearChanges(); + + // //update the model with stuff from the server + // that.set(data); + + // //emit the data event for this model to refresh everyone's values + // that.emit(that.get('dataEvent'), data); + // } + // , error: function(){ + // that.emit('global:error', new action.Error('http', 'Error in request', that)); + // } + // }); } else { action.emit('global:error', new action.Error('http', 'No URL defined', that)); } From a95f658cb10ebab919372b0030d18e7a5f46e752 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Wed, 11 Jun 2014 00:04:23 -0600 Subject: [PATCH 13/19] revved the package.json for 0.4.0 Akshay Kumar release with the new jquery/zepto-less stuff --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3a12db..5403c0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "action", - "version": "0.3.3", + "version": "0.4.0", "private": true, "scripts": { "start": "node app.js" From c5f14043a7b552b7d1e95e83ce498813bda51e16 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Wed, 11 Jun 2014 00:06:36 -0600 Subject: [PATCH 14/19] 0.4.0 release Akshay Kumar --- packages/0.4.0/action-v0.4.0.js | 672 ++++++++++++++++++++++++++++ packages/0.4.0/action-v0.4.0.min.js | 1 + 2 files changed, 673 insertions(+) create mode 100644 packages/0.4.0/action-v0.4.0.js create mode 100644 packages/0.4.0/action-v0.4.0.min.js diff --git a/packages/0.4.0/action-v0.4.0.js b/packages/0.4.0/action-v0.4.0.js new file mode 100644 index 0000000..8fd3a92 --- /dev/null +++ b/packages/0.4.0/action-v0.4.0.js @@ -0,0 +1,672 @@ +//TODO routing and pushstate +// view rendering on routing events +var action = function(){ + 'use strict'; + + var action = { + eventMe: function(objectIn){ + var returnObject = objectIn + , localEvents = {}; + + //set an emitter id for troubleshooting + returnObject.emitterId = Math.ceil(Math.random() * 10000); + + //create the lcoal event Store + returnObject.eventStore = {}; + + returnObject.emit = function(eventNameIn, eventDataIn, localFlag){ + var that = this + , eventStack + , functionToCall + , i + , isLocal = (typeof localFlag !== 'undefined' && localFlag); + + if(isLocal){ + eventStack = that.eventStore[eventNameIn]; + } else { + eventStack = action.eventStore[eventNameIn]; + } + + //emit the event + if(typeof eventStack !== 'undefined'){ + for(i = 0; i < eventStack.length; i ++){ + if(typeof eventStack[i].scope !== 'undefined'){ + eventStack[i].call.apply(eventStack[i].scope,[eventDataIn, that.emitterId]); + }else{ + eventStack[i].call(eventDataIn, that.emitterId); + } + + if(eventStack[i].once){ + that.silence(eventNameIn, eventStack[i].call, true, isLocal); + } + } + } + }; + + returnObject.emitLocal = function(eventNameIn, eventDataIn){ + var that = this; + + that.emit(eventNameIn, eventDataIn, true); + }; + + returnObject.listen = function(eventNameIn, handlerIn, scopeIn, onceIn, localFlagIn){ + var that = this + , i + , newCheck = true + + //attribute holders and such + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , local = localFlagIn + , once = onceIn + + //variables for later + , eventStack + , newEvent; + + if(typeof eventNameIn === 'object'){ + //we have an object to split up dude + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + once = eventNameIn.once; + local = eventNameIn.local; + } + + eventStack = (typeof local !== 'undefined' && local) ? that.eventStore[eventNameIn] : action.eventStore[eventNameIn]; + newEvent = (typeof local !== 'undefined' && local) ? that : action; + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === false){ + newCheck = false; + break; + } + } + + if(newCheck && typeof scopeIn !== 'undefined'){ + eventStack.push({once: false, call: handler, scope: scope}); + }else if(newCheck){ + eventStack.push({once: false, call:handler}); + } + + } else { + //new event + newEvent.eventStore[eventName] = []; //use an array to store functions + if(typeof scopeIn !== 'undefined'){ + newEvent.eventStore[eventName].push({once: false, call: handler, scope: scope}); + }else{ + newEvent.eventStore[eventName].push({once: false, call: handler}); + } + } + }; + + returnObject.listenLocal = function(eventNameIn, handlerIn, scopeIn, onceIn){ + var that = this; + + //convenience function for local listens + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listen(eventNameIn); + } else { + that.listen({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , once: onceIn + , local: true + }); + } + }; + + returnObject.listenOnce = function(eventNameIn, handlerIn, scopeIn, localFlagIn){ + //same thing as .listen() but is only triggered once + var that = this + , i + , newCheck = true + , eventStack + , newEvent + + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , localFlag = localFlagIn; + + if(typeof eventNameIn === 'object'){ + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + if(typeof localFlag !== 'undefined' && localFlag){ + //make it local! + eventStack = that.eventStore[eventName]; + newEvent = that; + }else{ + eventStack = action.eventStore[eventName]; + newEvent = action; + } + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === true){ + newCheck = false; + break; + } + } + + if(newCheck){ + eventStack.push({once:true, call: handler, scope: scope}); + } + + } else{ + //new event + newEvent.eventStore[eventNameIn] = []; //use an array to store functions + newEvent.eventStore[eventNameIn].push({once:true, call: handler, scope: scope}); + } + }; + + returnObject.listenOnceLocal = function(eventNameIn, handlerIn, scopeIn){ + var that = this; + + //same thing as .listen() but is only triggered once + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listenLocal(eventNameIn); + }else{ + that.listenLocal({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , local: true + }); + } + }; + + returnObject.silence = function(eventNameIn, handlerIn, onceIn, scopeIn, localFlagIn){ + //localize variables + var that = this + , i + , truthy = false + , eventName = eventNameIn + , handler = handlerIn + , once = onceIn + , scope = scopeIn + , localFlag = localFlagIn + , store; + + if(typeof eventNameIn === 'object'){ + // passed in a collection of params instead of params + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + once = eventNameIn.once; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + //local or global event store? + store = (typeof localFlag === 'undefined' || !localFlag) ? action : that; + + if(typeof store.eventStore[eventName] === 'undefined'){ + //if there is no event with a name... return nothing + return; + } + + //there is an event that matches... proceed + for(i = 0; i < store.eventStore[eventName].length; i ++){ + //reset this variable + truthy = false; + + if(typeof handler !== 'undefined'){ + //function is passed in + if(typeof scope !== 'undefined'){ + //scope is passed in... + if(typeof once === 'boolean'){ + // function + scope + once provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope && once === store.eventStore[eventName][i].once); + } else { + //function + scope provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope); + } + } else { + //function + once in for the match + if(typeof once === 'boolean'){ + truthy = (handler === store.eventStore[eventName][i].call && store.eventStore[eventName][i].once === once); + } else { + truthy = (handler === store.eventStore[eventName][i].call); + } + } + } else { + //no function unbind everything by resetting + store.eventStore[eventName] = []; + + //and exit + break; + } + + if(truthy){ + //remove this bad boy + store.eventStore[eventName].splice(i,1); + } + + } + }; + + returnObject.silenceLocal = function(eventNameIn, handlerIn, onceIn, scopeIn){ + var that = this; + + //essentially a convenience function. + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.silence(eventNameIn); + }else{ + that.silence({ + eventName: eventNameIn + , handler: handlerIn + , once: onceIn + , scope: scopeIn + , local: true + }); + } + }; + + //Event Based state machine + returnObject.requiredEvent = function(name, callback, context, fireMultipleIn){ + var that = this + , stateUpdate; + + that._fireMultiple = (typeof fireMultipleIn !== 'undefined') ? fireMultipleIn : false; + + //init some hidden storage if needed + if(typeof that.stateEvents === 'undefined'){ + that.stateEvents = {}; + } + + if(typeof that._triggeredStateReady === 'undefined'){ + that._triggeredStateReady = false; + } + + that.stateEvents[name] = false; + + stateUpdate = that.stateUpdate(name, that.stateEvents); + + that.listen(name, callback, context); + that.listen(name, stateUpdate, that); + }; + + returnObject.stateUpdate = function(nameIn, stateEventsIn){ + var name = nameIn + , stateEvents = stateEventsIn + , that = this; + + return function(){ + var truthy = true + , key; + + if(typeof stateEvents[name] !== 'undefined'){ + stateEvents[name] = true; + + for(key in stateEvents){ + truthy = truthy && stateEvents[key]; + } + + if(truthy){ + if(!that._triggeredStateReady || that._fireMultiple){ + //feels like a little bit of a hack. + // lets the data finish propogating before triggering the call + setTimeout(that.stateReady.apply(that), 100); + that._triggeredStateReady = true; + } + } + } + }; + }; + + returnObject.stateReady = function(){ + //this is a default action when all required events have been completed. + // needs to be overridden if you want to do something real + console.log('ready!'); + }; + + returnObject.listen('system:trace', function(emitterIDIn){ + var that = this; + + if(that.emitterId === emitterIDIn){ + // that.emit('system:addTraced', that); + action.traced = that; + } + }, returnObject); + + //execute the init function if it exists + if(typeof returnObject.init === 'function'){ + returnObject.init.apply(returnObject); + } + + return returnObject; + } + + , modelMe: function(objectIn){ + //this is the module for creating a data model object + var that = this + , newModel = that.eventMe({}) + , attributes = {} + , changes = []; + + newModel.super = {}; + + newModel.get = function(attributeName){ + return attributes[attributeName]; + }; + + newModel.set = function(attributeName, attributeValue){ + var that = this + , key; + + if(typeof attributeName === 'object'){ + //well... this is an object... iterate and rock on + for(key in attributeName){ + if(attributeName.hasOwnProperty(key)){ + //this attribute does not belong to the prototype. Good. + + //TODO: maybe make this do a deep copy to prevent + // pass by reference or switch to clone() + if(key !== 'destroy' && key !== 'fetch' && key !== 'save' && typeof attributeName[key] !== 'function'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeName[key])) ? [] : {}; + action.clone(attributes[attributeName], attributeName[key]); + }else{ + attributes[key] = attributeName[key]; + } + that.emitLocal('attribute:changed', key); + } else { + if(typeof that[key] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[key] = that[key].bind(that); + } + + that[key] = attributeName[key]; + } + } + } + } else{ + if(attributeName !== 'destroy' && attributeName !== 'fetch' && attributeName !== 'save'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeValue)) ? [] : {}; + action.clone(attributes[attributeName], attributeValue); + }else{ + attributes[attributeName] = attributeValue; + } + + that.emitLocal('attribute:changed', attributeName); + } else { + if(typeof that[attributeName] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[attributeName] = that[attributeName].bind(that); + } + that[attributeName] = attributeValue; + } + } + } + + newModel.flatten = function(){ + return attributes; + } + + newModel.fetch = function(setVariableName, successFunction, errorFunction, flushCache){ + var that = this + , requestUrl = that.get('url') + , useLocal = action.useLocalCache && !flushCache; + + if(typeof requestUrl !== 'undefined'){ + //make the request for the model + if(useLocal){ + window.localforage.getItem(window.btoa(that.get('url')), function(data){ + if(data === null){ + //this doesn't exist locally... + that.ajaxGet(setVariableName, function(dataIn){ + var localData = dataIn + , articleId = that.get('url'); + + window.localforage.setItem(window.btoa(articleId), localData, function(){ + // console.log('data done'); + }); + }); + }else{ + //it does exist! + that.emit(that.get('dataEvent'), data); + } + }); + } else { + that.ajaxGet(setVariableName, successFunction); + } + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + if(typeof errorFunction === 'function'){ + errorFunction.apply(that); + } + } + }; + + newModel.ajaxGet = function(setVariableName, successFunction){ + var that = this + , requestUrl = that.get('url')// + '?' + Date.now() + + , oReq = new XMLHttpRequest(); + + oReq.onload = function(){ + var data = JSON.parse(this.responseText); + + //TODO: make the statuses more generic + if(this.status === 200 || this.status === 302){ + that.emit(that.get('dataEvent'), data); + + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } + + if(typeof successFunction === 'function'){ + successFunction.apply(that, [data]); + } + }else if(this.status === 400){ + + }else if(this.status === 500){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }; + + oReq.onerror = function(xhr, errorType, error){ + that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); + }; + + oReq.open('get', requestUrl, true); + oReq.send(); + }; + + newModel.save = function(){ + //TODO make this talk to a server with the URL + //TODO make it only mark the saved changes clear + var that = this + , requestUrl = that.get('url') + , id = that.get('id') + , type = (typeof id === 'undefined') ? 'post' : 'put' + + , oReq = new XMLHttpRequest(); + + if(typeof requestUrl !== 'undefined'){ + oReq.onload = function(){ + if(this.status === 200 || this.status === 302){ + that.clearChanges(); + that.set(data); + that.emit(that.get('dataEvent'), data); + + }else if(this.status === 500 || this.status === 400){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }; + + oReq.submittedData = that.flatten(); + + oReq.open(type, requestUrl, true); + oReq.send(); + + // $.ajax({ + // type: type + // , url: requestUrl + '/' + id + // , data: that.flatten() + // , success: function(data, status){ + // //only do this on success... + // that.clearChanges(); + + // //update the model with stuff from the server + // that.set(data); + + // //emit the data event for this model to refresh everyone's values + // that.emit(that.get('dataEvent'), data); + // } + // , error: function(){ + // that.emit('global:error', new action.Error('http', 'Error in request', that)); + // } + // }); + } else { + action.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.clearChanges = function(){ + changes = []; + } + + newModel.getChanges = function(){ + return changes; + } + + newModel.clear = function(){ + attributes = {}; + } + + newModel.destroy = function(){ + //TODO not really working... should get rid of this thing + // and all of its parameters + var that = this + , key; + + setTimeout(function(){ + // delete me; + },0); // not quite working... + + for(key in that){ + // delete this[key]; + } + + //TODO this still doesn't kill the attributes or changes + // private data + } + + newModel.set(objectIn); //set the inital attributes + + newModel.listenLocal('attribute:changed', function(nameIn){ + changes.push(nameIn); + }, this); //maybe eliminate this 'this' + + if(typeof newModel.init === 'function'){ + newModel.init.apply(newModel); + } + + return newModel; + } + + //TODO: figure out if this is needed since the global:error... + // , trace: function(emitterIdIn){ + // //log out the function that has the emitterId attached + + // //create the traced object/stack + // action.traced = action.modelMe({ + // stack: [] + // , emitterId: emitterIdIn + // }); + + // action.traced.listen('system:addTraced', function(objectIn){ + // var that = this; + + // that.get('stack').push(objectIn); + // }, action.traced); + + // //trigger the event that will cause the trace + // action.emit('system:trace', emitterIdIn); + // } + + , Error: function(typeIn, messageIn, objectIn, errorObjectIn){ + return { + type: typeIn + , message: messageIn + , createdBy: objectIn + , errorObject: errorObjectIn + } + } + + , clone: function(objectIn, cloneMe){ + var key; + + for(key in cloneMe){ + if(cloneMe.hasOwnProperty(key)){ + //good to copy this one... + if (typeof cloneMe[key] === 'object'){ + //set up the object for iteration later + objectIn[key] = (Array.isArray(cloneMe[key])) ? [] : {}; + + action.clone(objectIn[key], cloneMe[key]); + }else{ + objectIn[key] = cloneMe[key]; + } + } + } + } + + , eventStore: {} + }; + + //add an events hook for global dealing with events... + action = action.eventMe(action); + + action.listen('global:error', function(errorIn) { + console.group('An Error occured in an object with emitterid: ' + errorIn.createdBy.emitterId); + console.log('It was a ' + errorIn.type + 'error.'); + + if(typeof errorIn.errorObject === 'string'){ + console.log('It says: ' + errorIn.errorObject); + console.log('and: ' + errorIn.message); + } else { + console.log('It says: ' + errorIn.message); + } + + console.log('The Whole Enchilada (object that caused this mess):'); + console.dir(errorIn.createdBy); + + if(typeof errorIn.createdBy.flatten === 'function'){ + console.log('Just the Lettuce (attributes):'); + console.dir(errorIn.createdBy.flatten()); + } + + if(typeof errorIn.errorObject === 'object'){ + console.log('Oh look an Error Object!:'); + console.dir(errorIn.errorObject); + } + + console.groupEnd(); + // action.trace(errorIn.createdBy.emitterId); + // throw errorIn; + }); + + //return the tweaked function + return action; +}(this); \ No newline at end of file diff --git a/packages/0.4.0/action-v0.4.0.min.js b/packages/0.4.0/action-v0.4.0.min.js new file mode 100644 index 0000000..d2942ef --- /dev/null +++ b/packages/0.4.0/action-v0.4.0.min.js @@ -0,0 +1 @@ +var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,a,i=this,c="undefined"!=typeof o&&o;if(r=c?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(a=0;a Date: Wed, 11 Jun 2014 00:07:13 -0600 Subject: [PATCH 15/19] 0.4.0 Akshay Kumar release into latest --- packages/latest/action.js | 99 ++++++++++++++++++++++------------- packages/latest/action.min.js | 2 +- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/packages/latest/action.js b/packages/latest/action.js index bed7653..8fd3a92 100644 --- a/packages/latest/action.js +++ b/packages/latest/action.js @@ -420,10 +420,10 @@ var action = function(){ return attributes; } - newModel.fetch = function(setVariableName, successFunction, errorFunction){ + newModel.fetch = function(setVariableName, successFunction, errorFunction, flushCache){ var that = this , requestUrl = that.get('url') - , useLocal = action.useLocalCache; + , useLocal = action.useLocalCache && !flushCache; if(typeof requestUrl !== 'undefined'){ //make the request for the model @@ -436,7 +436,7 @@ var action = function(){ , articleId = that.get('url'); window.localforage.setItem(window.btoa(articleId), localData, function(){ - console.log('data done'); + // console.log('data done'); }); }); }else{ @@ -457,30 +457,39 @@ var action = function(){ newModel.ajaxGet = function(setVariableName, successFunction){ var that = this - , requestUrl = that.get('url'); + , requestUrl = that.get('url')// + '?' + Date.now() - $.ajax({ - type: 'get' - , url: requestUrl - , success: function(data, status){ - if(status !== 'success'){ + , oReq = new XMLHttpRequest(); + + oReq.onload = function(){ + var data = JSON.parse(this.responseText); + + //TODO: make the statuses more generic + if(this.status === 200 || this.status === 302){ + that.emit(that.get('dataEvent'), data); + + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } + + if(typeof successFunction === 'function'){ + successFunction.apply(that, [data]); + } + }else if(this.status === 400){ + + }else if(this.status === 500){ that.emit('global:error', new action.Error('http', 'Error in request', that)); } - that.emit(that.get('dataEvent'), data); - if(typeof setVariableName === 'string'){ - that.set(setVariableName, data); - }else{ - that.set(data); - } + }; - if(typeof successFunction === 'function'){ - successFunction.apply(that, [data]); - } - } - , error: function(xhr, errorType, error){ + oReq.onerror = function(xhr, errorType, error){ that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); - } - }); + }; + + oReq.open('get', requestUrl, true); + oReq.send(); }; newModel.save = function(){ @@ -489,27 +498,45 @@ var action = function(){ var that = this , requestUrl = that.get('url') , id = that.get('id') - , type = (typeof id === 'undefined') ? 'post' : 'put'; + , type = (typeof id === 'undefined') ? 'post' : 'put' + + , oReq = new XMLHttpRequest(); if(typeof requestUrl !== 'undefined'){ - $.ajax({ - type: type - , url: requestUrl + '/' + id - , data: that.flatten() - , success: function(data, status){ - //only do this on success... + oReq.onload = function(){ + if(this.status === 200 || this.status === 302){ that.clearChanges(); - - //update the model with stuff from the server that.set(data); - - //emit the data event for this model to refresh everyone's values that.emit(that.get('dataEvent'), data); - } - , error: function(){ + + }else if(this.status === 500 || this.status === 400){ that.emit('global:error', new action.Error('http', 'Error in request', that)); } - }); + }; + + oReq.submittedData = that.flatten(); + + oReq.open(type, requestUrl, true); + oReq.send(); + + // $.ajax({ + // type: type + // , url: requestUrl + '/' + id + // , data: that.flatten() + // , success: function(data, status){ + // //only do this on success... + // that.clearChanges(); + + // //update the model with stuff from the server + // that.set(data); + + // //emit the data event for this model to refresh everyone's values + // that.emit(that.get('dataEvent'), data); + // } + // , error: function(){ + // that.emit('global:error', new action.Error('http', 'Error in request', that)); + // } + // }); } else { action.emit('global:error', new action.Error('http', 'No URL defined', that)); } diff --git a/packages/latest/action.min.js b/packages/latest/action.min.js index 203d4ee..d2942ef 100644 --- a/packages/latest/action.min.js +++ b/packages/latest/action.min.js @@ -1 +1 @@ -var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,i,a=this,c="undefined"!=typeof o&&o;if(r=c?a.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(i=0;i Date: Wed, 11 Jun 2014 00:10:24 -0600 Subject: [PATCH 16/19] updated teh dependencies --- readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.md b/readme.md index e3c88dd..926d686 100644 --- a/readme.md +++ b/readme.md @@ -41,6 +41,5 @@ in order from top left - down the column then on to the next column. If we run o ###Dependencies There are a few dependencies for Action to be a fully useful framework. They are (with the reasons for them): -0. Zepto, jQuery, or jQlite. (Action uses the jQuery syntax to wrap it's ajax and DOM manipulation) 0. localForage (If you want to use the local cache. It will automatically cache all of your ajax requests in local storage and pull from them all subsequent times they are requested) 0. Handlebars (a templating library) \ No newline at end of file From 3b601fefdf37ec435d8d90bc41eacf59b64659b9 Mon Sep 17 00:00:00 2001 From: Daniel Sellers Date: Wed, 11 Jun 2014 10:29:58 -0600 Subject: [PATCH 17/19] added a brief header to the js files in the packages directory that has the release number, name and a link to the repo. Also added a release name to the package.json so we can store that and use it in the build --- Gulpfile.js | 7 ++++--- package.json | 12 +++++++----- packages/0.4.0/action-v0.4.0.js | 4 ++++ packages/0.4.0/action-v0.4.0.min.js | 4 ++++ packages/latest/action.js | 4 ++++ packages/latest/action.min.js | 4 ++++ 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index e3a096a..e96e2bb 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -6,6 +6,7 @@ var gulp = require('gulp') , rename = require('gulp-rename') , zip = require('gulp-zip') , jshint = require('gulp-jshint') + , header = require('gulp-header') , pkg = require('./package.json'); @@ -29,12 +30,15 @@ gulp.task('localBuild', function(){ gulp.task('generateForPublish', function(){ 'use strict'; + var headerText = '/****************************************\nAction! v' + pkg.version + ' ' + pkg.releaseName + ' \nhttps://github.com/designfrontier/Action \n****************************************/\n'; gulp.src('public/javascripts/action.js') + .pipe(header(headerText)) .pipe(gulp.dest('packages/latest/')) .pipe(rename('action-v' + pkg.version + '.js')) .pipe(gulp.dest('packages/' + pkg.version + '/')) .pipe(uglify()) + .pipe(header(headerText)) .pipe(rename('action.min.js')) .pipe(gulp.dest('packages/latest/')) .pipe(rename('action-v' + pkg.version + '.min.js')) @@ -44,9 +48,6 @@ gulp.task('generateForPublish', function(){ gulp.task('publish', ['generateForPublish'], function(){ 'use strict'; - // gulp.src(['packages/' + pkg.version + '/*.js']) - // .pipe(zip('action.zip')) - // .pipe(gulp.dest('packages/' + pkg.version + '/')); }); // gulp.task('default', function () { diff --git a/package.json b/package.json index 5403c0a..5a8b717 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "action", "version": "0.4.0", + "releaseName": "Akshay Kumar", "private": true, "scripts": { "start": "node app.js" @@ -10,13 +11,14 @@ "ejs": "*" }, "devDependencies": { - "gulp-concat": "^2.2.0", "gulp": "^3.6.0", - "gulp-uglify": "^0.2.1", + "gulp-concat": "^2.2.0", + "gulp-header": "^1.0.2", + "gulp-jshint": "^1.5.5", "gulp-plumber": "^0.6.0", - "gulp-watch": "^0.5.4", "gulp-rename": "^1.2.0", - "gulp-zip": "^0.3.4", - "gulp-jshint": "^1.5.5" + "gulp-uglify": "^0.2.1", + "gulp-watch": "^0.5.4", + "gulp-zip": "^0.3.4" } } diff --git a/packages/0.4.0/action-v0.4.0.js b/packages/0.4.0/action-v0.4.0.js index 8fd3a92..231e9b3 100644 --- a/packages/0.4.0/action-v0.4.0.js +++ b/packages/0.4.0/action-v0.4.0.js @@ -1,3 +1,7 @@ +/**************************************** +Action! v0.4.0 Akshay Kumar +https://github.com/designfrontier/Action +****************************************/ //TODO routing and pushstate // view rendering on routing events var action = function(){ diff --git a/packages/0.4.0/action-v0.4.0.min.js b/packages/0.4.0/action-v0.4.0.min.js index d2942ef..e96139f 100644 --- a/packages/0.4.0/action-v0.4.0.min.js +++ b/packages/0.4.0/action-v0.4.0.min.js @@ -1 +1,5 @@ +/**************************************** +Action! v0.4.0 Akshay Kumar +https://github.com/designfrontier/Action +****************************************/ var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,a,i=this,c="undefined"!=typeof o&&o;if(r=c?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(a=0;a Date: Fri, 13 Jun 2014 16:04:46 -0600 Subject: [PATCH 18/19] super duper --- packages/0.4.0/action-v0.4.0.js | 2 +- packages/0.4.0/action-v0.4.0.min.js | 2 +- packages/latest/action.js | 2 +- packages/latest/action.min.js | 2 +- public/javascripts/action.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/0.4.0/action-v0.4.0.js b/packages/0.4.0/action-v0.4.0.js index 231e9b3..c3c214a 100644 --- a/packages/0.4.0/action-v0.4.0.js +++ b/packages/0.4.0/action-v0.4.0.js @@ -389,7 +389,7 @@ var action = function(){ } that.emitLocal('attribute:changed', key); } else { - if(typeof that[key] === 'function'){ + if(typeof that[key] === 'function' && !that.super[key]){ //wrap the super version in a closure so that we can // still execute it correctly that.super[key] = that[key].bind(that); diff --git a/packages/0.4.0/action-v0.4.0.min.js b/packages/0.4.0/action-v0.4.0.min.js index e96139f..e8c8a47 100644 --- a/packages/0.4.0/action-v0.4.0.min.js +++ b/packages/0.4.0/action-v0.4.0.min.js @@ -2,4 +2,4 @@ Action! v0.4.0 Akshay Kumar https://github.com/designfrontier/Action ****************************************/ -var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,a,i=this,c="undefined"!=typeof o&&o;if(r=c?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(a=0;a Date: Mon, 16 Jun 2014 09:28:30 -0600 Subject: [PATCH 19/19] bumped rev and rebuilt assets --- package.json | 2 +- packages/0.4.1/action-v0.4.1.js | 676 ++++++++++++++++++++++++++++ packages/0.4.1/action-v0.4.1.min.js | 5 + packages/latest/action.js | 2 +- packages/latest/action.min.js | 2 +- 5 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 packages/0.4.1/action-v0.4.1.js create mode 100644 packages/0.4.1/action-v0.4.1.min.js diff --git a/package.json b/package.json index 5a8b717..15bc73e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "action", - "version": "0.4.0", + "version": "0.4.1", "releaseName": "Akshay Kumar", "private": true, "scripts": { diff --git a/packages/0.4.1/action-v0.4.1.js b/packages/0.4.1/action-v0.4.1.js new file mode 100644 index 0000000..c489ed9 --- /dev/null +++ b/packages/0.4.1/action-v0.4.1.js @@ -0,0 +1,676 @@ +/**************************************** +Action! v0.4.1 Akshay Kumar +https://github.com/designfrontier/Action +****************************************/ +//TODO routing and pushstate +// view rendering on routing events +var action = function(){ + 'use strict'; + + var action = { + eventMe: function(objectIn){ + var returnObject = objectIn + , localEvents = {}; + + //set an emitter id for troubleshooting + returnObject.emitterId = Math.ceil(Math.random() * 10000); + + //create the lcoal event Store + returnObject.eventStore = {}; + + returnObject.emit = function(eventNameIn, eventDataIn, localFlag){ + var that = this + , eventStack + , functionToCall + , i + , isLocal = (typeof localFlag !== 'undefined' && localFlag); + + if(isLocal){ + eventStack = that.eventStore[eventNameIn]; + } else { + eventStack = action.eventStore[eventNameIn]; + } + + //emit the event + if(typeof eventStack !== 'undefined'){ + for(i = 0; i < eventStack.length; i ++){ + if(typeof eventStack[i].scope !== 'undefined'){ + eventStack[i].call.apply(eventStack[i].scope,[eventDataIn, that.emitterId]); + }else{ + eventStack[i].call(eventDataIn, that.emitterId); + } + + if(eventStack[i].once){ + that.silence(eventNameIn, eventStack[i].call, true, isLocal); + } + } + } + }; + + returnObject.emitLocal = function(eventNameIn, eventDataIn){ + var that = this; + + that.emit(eventNameIn, eventDataIn, true); + }; + + returnObject.listen = function(eventNameIn, handlerIn, scopeIn, onceIn, localFlagIn){ + var that = this + , i + , newCheck = true + + //attribute holders and such + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , local = localFlagIn + , once = onceIn + + //variables for later + , eventStack + , newEvent; + + if(typeof eventNameIn === 'object'){ + //we have an object to split up dude + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + once = eventNameIn.once; + local = eventNameIn.local; + } + + eventStack = (typeof local !== 'undefined' && local) ? that.eventStore[eventNameIn] : action.eventStore[eventNameIn]; + newEvent = (typeof local !== 'undefined' && local) ? that : action; + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === false){ + newCheck = false; + break; + } + } + + if(newCheck && typeof scopeIn !== 'undefined'){ + eventStack.push({once: false, call: handler, scope: scope}); + }else if(newCheck){ + eventStack.push({once: false, call:handler}); + } + + } else { + //new event + newEvent.eventStore[eventName] = []; //use an array to store functions + if(typeof scopeIn !== 'undefined'){ + newEvent.eventStore[eventName].push({once: false, call: handler, scope: scope}); + }else{ + newEvent.eventStore[eventName].push({once: false, call: handler}); + } + } + }; + + returnObject.listenLocal = function(eventNameIn, handlerIn, scopeIn, onceIn){ + var that = this; + + //convenience function for local listens + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listen(eventNameIn); + } else { + that.listen({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , once: onceIn + , local: true + }); + } + }; + + returnObject.listenOnce = function(eventNameIn, handlerIn, scopeIn, localFlagIn){ + //same thing as .listen() but is only triggered once + var that = this + , i + , newCheck = true + , eventStack + , newEvent + + , eventName = eventNameIn + , handler = handlerIn + , scope = scopeIn + , localFlag = localFlagIn; + + if(typeof eventNameIn === 'object'){ + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + if(typeof localFlag !== 'undefined' && localFlag){ + //make it local! + eventStack = that.eventStore[eventName]; + newEvent = that; + }else{ + eventStack = action.eventStore[eventName]; + newEvent = action; + } + + if(typeof eventStack !== 'undefined'){ + //already exists check to see if the function is already bound + + for(i = 0; i < eventStack.length; i ++){ + if(eventStack[i].call === handler && eventStack[i].once === true){ + newCheck = false; + break; + } + } + + if(newCheck){ + eventStack.push({once:true, call: handler, scope: scope}); + } + + } else{ + //new event + newEvent.eventStore[eventNameIn] = []; //use an array to store functions + newEvent.eventStore[eventNameIn].push({once:true, call: handler, scope: scope}); + } + }; + + returnObject.listenOnceLocal = function(eventNameIn, handlerIn, scopeIn){ + var that = this; + + //same thing as .listen() but is only triggered once + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.listenLocal(eventNameIn); + }else{ + that.listenLocal({ + eventName: eventNameIn + , handler: handlerIn + , scope: scopeIn + , local: true + }); + } + }; + + returnObject.silence = function(eventNameIn, handlerIn, onceIn, scopeIn, localFlagIn){ + //localize variables + var that = this + , i + , truthy = false + , eventName = eventNameIn + , handler = handlerIn + , once = onceIn + , scope = scopeIn + , localFlag = localFlagIn + , store; + + if(typeof eventNameIn === 'object'){ + // passed in a collection of params instead of params + eventName = eventNameIn.eventName; + handler = eventNameIn.handler; + once = eventNameIn.once; + scope = eventNameIn.scope; + localFlag = eventNameIn.local; + } + + //local or global event store? + store = (typeof localFlag === 'undefined' || !localFlag) ? action : that; + + if(typeof store.eventStore[eventName] === 'undefined'){ + //if there is no event with a name... return nothing + return; + } + + //there is an event that matches... proceed + for(i = 0; i < store.eventStore[eventName].length; i ++){ + //reset this variable + truthy = false; + + if(typeof handler !== 'undefined'){ + //function is passed in + if(typeof scope !== 'undefined'){ + //scope is passed in... + if(typeof once === 'boolean'){ + // function + scope + once provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope && once === store.eventStore[eventName][i].once); + } else { + //function + scope provides the match + truthy = (handler === store.eventStore[eventName][i].call && scope === store.eventStore[eventName][i].scope); + } + } else { + //function + once in for the match + if(typeof once === 'boolean'){ + truthy = (handler === store.eventStore[eventName][i].call && store.eventStore[eventName][i].once === once); + } else { + truthy = (handler === store.eventStore[eventName][i].call); + } + } + } else { + //no function unbind everything by resetting + store.eventStore[eventName] = []; + + //and exit + break; + } + + if(truthy){ + //remove this bad boy + store.eventStore[eventName].splice(i,1); + } + + } + }; + + returnObject.silenceLocal = function(eventNameIn, handlerIn, onceIn, scopeIn){ + var that = this; + + //essentially a convenience function. + if(typeof eventNameIn === 'object'){ + eventNameIn.local = true; + that.silence(eventNameIn); + }else{ + that.silence({ + eventName: eventNameIn + , handler: handlerIn + , once: onceIn + , scope: scopeIn + , local: true + }); + } + }; + + //Event Based state machine + returnObject.requiredEvent = function(name, callback, context, fireMultipleIn){ + var that = this + , stateUpdate; + + that._fireMultiple = (typeof fireMultipleIn !== 'undefined') ? fireMultipleIn : false; + + //init some hidden storage if needed + if(typeof that.stateEvents === 'undefined'){ + that.stateEvents = {}; + } + + if(typeof that._triggeredStateReady === 'undefined'){ + that._triggeredStateReady = false; + } + + that.stateEvents[name] = false; + + stateUpdate = that.stateUpdate(name, that.stateEvents); + + that.listen(name, callback, context); + that.listen(name, stateUpdate, that); + }; + + returnObject.stateUpdate = function(nameIn, stateEventsIn){ + var name = nameIn + , stateEvents = stateEventsIn + , that = this; + + return function(){ + var truthy = true + , key; + + if(typeof stateEvents[name] !== 'undefined'){ + stateEvents[name] = true; + + for(key in stateEvents){ + truthy = truthy && stateEvents[key]; + } + + if(truthy){ + if(!that._triggeredStateReady || that._fireMultiple){ + //feels like a little bit of a hack. + // lets the data finish propogating before triggering the call + setTimeout(that.stateReady.apply(that), 100); + that._triggeredStateReady = true; + } + } + } + }; + }; + + returnObject.stateReady = function(){ + //this is a default action when all required events have been completed. + // needs to be overridden if you want to do something real + console.log('ready!'); + }; + + returnObject.listen('system:trace', function(emitterIDIn){ + var that = this; + + if(that.emitterId === emitterIDIn){ + // that.emit('system:addTraced', that); + action.traced = that; + } + }, returnObject); + + //execute the init function if it exists + if(typeof returnObject.init === 'function'){ + returnObject.init.apply(returnObject); + } + + return returnObject; + } + + , modelMe: function(objectIn){ + //this is the module for creating a data model object + var that = this + , newModel = that.eventMe({}) + , attributes = {} + , changes = []; + + newModel.super = {}; + + newModel.get = function(attributeName){ + return attributes[attributeName]; + }; + + newModel.set = function(attributeName, attributeValue){ + var that = this + , key; + + if(typeof attributeName === 'object'){ + //well... this is an object... iterate and rock on + for(key in attributeName){ + if(attributeName.hasOwnProperty(key)){ + //this attribute does not belong to the prototype. Good. + + //TODO: maybe make this do a deep copy to prevent + // pass by reference or switch to clone() + if(key !== 'destroy' && key !== 'fetch' && key !== 'save' && typeof attributeName[key] !== 'function'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeName[key])) ? [] : {}; + action.clone(attributes[attributeName], attributeName[key]); + }else{ + attributes[key] = attributeName[key]; + } + that.emitLocal('attribute:changed', key); + } else { + if(typeof that[key] === 'function' && !that.super[key]){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[key] = that[key].bind(that); + } + + that[key] = attributeName[key]; + } + } + } + } else{ + if(attributeName !== 'destroy' && attributeName !== 'fetch' && attributeName !== 'save'){ + if(typeof attributeValue === 'object'){ + attributes[attributeName] = (Array.isArray(attributeValue)) ? [] : {}; + action.clone(attributes[attributeName], attributeValue); + }else{ + attributes[attributeName] = attributeValue; + } + + that.emitLocal('attribute:changed', attributeName); + } else { + if(typeof that[attributeName] === 'function'){ + //wrap the super version in a closure so that we can + // still execute it correctly + that.super[attributeName] = that[attributeName].bind(that); + } + that[attributeName] = attributeValue; + } + } + } + + newModel.flatten = function(){ + return attributes; + } + + newModel.fetch = function(setVariableName, successFunction, errorFunction, flushCache){ + var that = this + , requestUrl = that.get('url') + , useLocal = action.useLocalCache && !flushCache; + + if(typeof requestUrl !== 'undefined'){ + //make the request for the model + if(useLocal){ + window.localforage.getItem(window.btoa(that.get('url')), function(data){ + if(data === null){ + //this doesn't exist locally... + that.ajaxGet(setVariableName, function(dataIn){ + var localData = dataIn + , articleId = that.get('url'); + + window.localforage.setItem(window.btoa(articleId), localData, function(){ + // console.log('data done'); + }); + }); + }else{ + //it does exist! + that.emit(that.get('dataEvent'), data); + } + }); + } else { + that.ajaxGet(setVariableName, successFunction); + } + } else { + that.emit('global:error', new action.Error('http', 'No URL defined', that)); + if(typeof errorFunction === 'function'){ + errorFunction.apply(that); + } + } + }; + + newModel.ajaxGet = function(setVariableName, successFunction){ + var that = this + , requestUrl = that.get('url')// + '?' + Date.now() + + , oReq = new XMLHttpRequest(); + + oReq.onload = function(){ + var data = JSON.parse(this.responseText); + + //TODO: make the statuses more generic + if(this.status === 200 || this.status === 302){ + that.emit(that.get('dataEvent'), data); + + if(typeof setVariableName === 'string'){ + that.set(setVariableName, data); + }else{ + that.set(data); + } + + if(typeof successFunction === 'function'){ + successFunction.apply(that, [data]); + } + }else if(this.status === 400){ + + }else if(this.status === 500){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }; + + oReq.onerror = function(xhr, errorType, error){ + that.emit('global:error', new action.Error('http', 'Error in request type: ' + errorType, that, error)); + }; + + oReq.open('get', requestUrl, true); + oReq.send(); + }; + + newModel.save = function(){ + //TODO make this talk to a server with the URL + //TODO make it only mark the saved changes clear + var that = this + , requestUrl = that.get('url') + , id = that.get('id') + , type = (typeof id === 'undefined') ? 'post' : 'put' + + , oReq = new XMLHttpRequest(); + + if(typeof requestUrl !== 'undefined'){ + oReq.onload = function(){ + if(this.status === 200 || this.status === 302){ + that.clearChanges(); + that.set(data); + that.emit(that.get('dataEvent'), data); + + }else if(this.status === 500 || this.status === 400){ + that.emit('global:error', new action.Error('http', 'Error in request', that)); + } + }; + + oReq.submittedData = that.flatten(); + + oReq.open(type, requestUrl, true); + oReq.send(); + + // $.ajax({ + // type: type + // , url: requestUrl + '/' + id + // , data: that.flatten() + // , success: function(data, status){ + // //only do this on success... + // that.clearChanges(); + + // //update the model with stuff from the server + // that.set(data); + + // //emit the data event for this model to refresh everyone's values + // that.emit(that.get('dataEvent'), data); + // } + // , error: function(){ + // that.emit('global:error', new action.Error('http', 'Error in request', that)); + // } + // }); + } else { + action.emit('global:error', new action.Error('http', 'No URL defined', that)); + } + } + + newModel.clearChanges = function(){ + changes = []; + } + + newModel.getChanges = function(){ + return changes; + } + + newModel.clear = function(){ + attributes = {}; + } + + newModel.destroy = function(){ + //TODO not really working... should get rid of this thing + // and all of its parameters + var that = this + , key; + + setTimeout(function(){ + // delete me; + },0); // not quite working... + + for(key in that){ + // delete this[key]; + } + + //TODO this still doesn't kill the attributes or changes + // private data + } + + newModel.set(objectIn); //set the inital attributes + + newModel.listenLocal('attribute:changed', function(nameIn){ + changes.push(nameIn); + }, this); //maybe eliminate this 'this' + + if(typeof newModel.init === 'function'){ + newModel.init.apply(newModel); + } + + return newModel; + } + + //TODO: figure out if this is needed since the global:error... + // , trace: function(emitterIdIn){ + // //log out the function that has the emitterId attached + + // //create the traced object/stack + // action.traced = action.modelMe({ + // stack: [] + // , emitterId: emitterIdIn + // }); + + // action.traced.listen('system:addTraced', function(objectIn){ + // var that = this; + + // that.get('stack').push(objectIn); + // }, action.traced); + + // //trigger the event that will cause the trace + // action.emit('system:trace', emitterIdIn); + // } + + , Error: function(typeIn, messageIn, objectIn, errorObjectIn){ + return { + type: typeIn + , message: messageIn + , createdBy: objectIn + , errorObject: errorObjectIn + } + } + + , clone: function(objectIn, cloneMe){ + var key; + + for(key in cloneMe){ + if(cloneMe.hasOwnProperty(key)){ + //good to copy this one... + if (typeof cloneMe[key] === 'object'){ + //set up the object for iteration later + objectIn[key] = (Array.isArray(cloneMe[key])) ? [] : {}; + + action.clone(objectIn[key], cloneMe[key]); + }else{ + objectIn[key] = cloneMe[key]; + } + } + } + } + + , eventStore: {} + }; + + //add an events hook for global dealing with events... + action = action.eventMe(action); + + action.listen('global:error', function(errorIn) { + console.group('An Error occured in an object with emitterid: ' + errorIn.createdBy.emitterId); + console.log('It was a ' + errorIn.type + 'error.'); + + if(typeof errorIn.errorObject === 'string'){ + console.log('It says: ' + errorIn.errorObject); + console.log('and: ' + errorIn.message); + } else { + console.log('It says: ' + errorIn.message); + } + + console.log('The Whole Enchilada (object that caused this mess):'); + console.dir(errorIn.createdBy); + + if(typeof errorIn.createdBy.flatten === 'function'){ + console.log('Just the Lettuce (attributes):'); + console.dir(errorIn.createdBy.flatten()); + } + + if(typeof errorIn.errorObject === 'object'){ + console.log('Oh look an Error Object!:'); + console.dir(errorIn.errorObject); + } + + console.groupEnd(); + // action.trace(errorIn.createdBy.emitterId); + // throw errorIn; + }); + + //return the tweaked function + return action; +}(this); \ No newline at end of file diff --git a/packages/0.4.1/action-v0.4.1.min.js b/packages/0.4.1/action-v0.4.1.min.js new file mode 100644 index 0000000..b55873e --- /dev/null +++ b/packages/0.4.1/action-v0.4.1.min.js @@ -0,0 +1,5 @@ +/**************************************** +Action! v0.4.1 Akshay Kumar +https://github.com/designfrontier/Action +****************************************/ +var action=function(){"use strict";var e={eventMe:function(t){var n=t;return n.emitterId=Math.ceil(1e4*Math.random()),n.eventStore={},n.emit=function(t,n,o){var r,a,i=this,c="undefined"!=typeof o&&o;if(r=c?i.eventStore[t]:e.eventStore[t],"undefined"!=typeof r)for(a=0;a