Comparing version 0.5.1 to 0.6.0
{ | ||
"name": "imvvm", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"homepage": "https://github.com/entrendipity/imvvm", | ||
@@ -5,0 +5,0 @@ "authors": [ |
@@ -21,178 +21,117 @@ !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.IMVVM=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ | ||
var ApplicationDataContext, | ||
dependsOn, | ||
appState = {}, | ||
dataContexts = {}, | ||
watchedProps, | ||
watchedPropsLen, | ||
watchList = {}, | ||
dependents = [], | ||
domain, | ||
watchListPropA, | ||
watchListPropB; | ||
links = {}, | ||
watchedDataContexts = {}, | ||
dataContext, | ||
transientState = {}, | ||
processedState = {}, | ||
watchedState, | ||
watchedItem, | ||
watchedProp, | ||
watchedDataContext, | ||
link; | ||
var depProp; | ||
var configure = function(obj, propName){ | ||
var newObj = {}; | ||
for(var k in obj){ | ||
if(obj.hasOwnProperty(k)){ | ||
if(Object.prototype.toString.call(obj[k]) === '[object Object]'){ | ||
newObj[k] = obj[k]; | ||
} else { | ||
newObj[k] = {}; | ||
newObj[k][propName] = obj[k]; | ||
} | ||
} | ||
} | ||
return newObj; | ||
}; | ||
var getDeps = function(nextState, dataContext){ | ||
var dependencies = {}, | ||
props, | ||
watchedValue; | ||
if(!dataContext){ | ||
return {}; | ||
} | ||
dataContext = ('dependsOn' in dataContext) ? dataContext : {dependsOn: dataContext}; | ||
for(var dependency in dataContext.dependsOn){ | ||
if(dataContext.dependsOn.hasOwnProperty(dependency)){ | ||
watchedValue = {}; | ||
props = dataContext.dependsOn[dependency].property.split('.'); | ||
props.forEach(function(prop, idx){ | ||
if(idx === 0){ | ||
watchedValue = nextState[prop]; | ||
} else { | ||
watchedValue = watchedValue ? watchedValue[prop] : void(0); | ||
} | ||
}); | ||
dependencies[dependency] = watchedValue; | ||
} | ||
}; | ||
return dependencies; | ||
}; | ||
var transitionState = function(caller, nextState, prevState, subscribers){ | ||
nextState = nextState || {}; | ||
prevState = prevState || {}; | ||
var processed = false, | ||
tempDeps, | ||
nextVal; | ||
if(caller !== appNamespace){ | ||
nextState[caller] = new dataContexts[caller](nextState[caller], | ||
getDeps(nextState, domain[caller]), prevState[caller]); | ||
} | ||
if(subscribers){ | ||
if(!!dependsOn){ | ||
tempDeps = getDeps(nextState, dependsOn); | ||
nextState = extend(nextState, tempDeps); | ||
for(var depKey in dependsOn){ | ||
if(dependsOn.hasOwnProperty(depKey) && ('onStateChange' in dependsOn[depKey])){ | ||
nextVal = {}; | ||
nextVal[depKey] = nextState[depKey]; | ||
nextState = extend(nextState, dependsOn[depKey].onStateChange(nextVal)); | ||
} | ||
} | ||
} | ||
nextState = new ApplicationDataContext(extend(nextState, tempDeps), prevState, enableUndo); | ||
subscribers.forEach(function(subscriber){ | ||
if(subscriber !== appNamespace){ | ||
nextState[subscriber] = new dataContexts[subscriber](nextState[subscriber], | ||
getDeps(nextState, domain[subscriber]), prevState[subscriber]); | ||
} | ||
}); | ||
} | ||
return nextState; | ||
}; | ||
var appStateChangedHandler = function(caller, newState, callback) { | ||
var appStateChangedHandler = function(caller, newState, newAppState, callback) { | ||
if((newState === void(0) || newState === null || Object.keys(newState).length === 0)){ | ||
return; | ||
} | ||
var nextState = {}, | ||
prevState = {}, | ||
subscribers = [], | ||
newStateKeys, | ||
newStateKeysLen, | ||
subscriberNames, | ||
idxKey, | ||
idxDepFld, | ||
tmpNextState = {}, | ||
changeState, | ||
dependsOnObj; | ||
keyIdx, | ||
transientStateKeysLen, | ||
dataContext, | ||
linkedDataContext, | ||
processedStateKeys = [], | ||
processedStateKeysLen, | ||
watchedField, | ||
subscribers, | ||
subscriber; | ||
if(typeof newAppState === 'function'){ | ||
callback = newAppState; | ||
newAppState = {}; | ||
} | ||
newState = newState || {}; | ||
newStateKeys = Object.keys(newState); | ||
//Check to see if appState is a ready made state object. If so | ||
//pass it straight to the stateChangedHandler. If a callback was passed in | ||
//it would be assigned to newState | ||
if(Object.getPrototypeOf(newState).constructor.classType === "DomainModel") { | ||
//This means previous state has been requested | ||
//so set nextState to the previous state | ||
if(Object.getPrototypeOf(newState).constructor.classType === "DomainViewModel") { | ||
nextState = extend(newState); | ||
//revert the current appState to the previous state of the previous state | ||
prevState = newState.previousState; | ||
} else { | ||
if(caller in watchList){ | ||
newStateKeys = Object.keys(newState); | ||
newStateKeysLen = newStateKeys.length; | ||
subscriberNames = {}; | ||
for (var i = newStateKeysLen - 1; i >= 0; i--){ | ||
if(watchList[caller][newStateKeys[i]]){ | ||
for(var j = watchList[caller][newStateKeys[i]].length - 1; j >= 0; j--){ | ||
subscriberNames[watchList[caller][newStateKeys[i]][j].dataContext] = true; | ||
//Need to reset ViewModel with instance object so that setState is associated with | ||
//the current ViewModel. This reason this ccurs is that when a ViewModel is created it | ||
//assigns a setState function to all instance fields and binds itself to it. When undo is called | ||
//the data is rolled back but viewModels are not recreated and therefore the setState functions | ||
//are associated with outdated viewModels and returns unexpected output. So to realign the ViewModels | ||
//with setState we recreate them. | ||
for(dataContext in domain){ | ||
nextState[dataContext] = new dataContexts[dataContext](nextState); | ||
} | ||
//relink | ||
for(dataContext in domain){ | ||
if(domain.hasOwnProperty(dataContext)){ | ||
for(linkedDataContext in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(linkedDataContext)){ | ||
nextState[dataContext].state[links[dataContext][linkedDataContext]] = | ||
(linkedDataContext in domain) ? extend(nextState[linkedDataContext].state) : | ||
nextState[linkedDataContext]; | ||
} | ||
} | ||
} | ||
} | ||
subscribers = Object.keys(subscriberNames); | ||
hasSubscribers = !!subscribers.length; | ||
} else { | ||
if(!!newStateKeys.length){ | ||
if(caller === appNamespace){ | ||
nextState = extend(newState); | ||
} else { | ||
nextState[caller] = extend(newState); | ||
} | ||
} | ||
transientState = extend(nextState, transientState, newAppState); | ||
transientStateKeys = Object.keys(transientState); | ||
if(transientStateKeys.length === 0){ | ||
return; | ||
} | ||
if(caller !== appNamespace){ | ||
if(typeof callback === 'function'){ | ||
callback(); | ||
return; | ||
} | ||
transientStateKeysLen = transientStateKeys.length - 1; | ||
for (keyIdx = transientStateKeysLen; keyIdx >= 0; keyIdx--) { | ||
if(transientStateKeys[keyIdx] in domain){ | ||
nextState[transientStateKeys[keyIdx]] = extend(appState[transientStateKeys[keyIdx]], transientState[transientStateKeys[keyIdx]]); | ||
nextState[transientStateKeys[keyIdx]] = new dataContexts[transientStateKeys[keyIdx]](nextState); | ||
} else { | ||
nextState[transientStateKeys[keyIdx]] = transientState[transientStateKeys[keyIdx]]; | ||
} | ||
}; | ||
nextState[caller] = newState; | ||
nextState = extend(appState.state, nextState); | ||
processedState = extend(processedState, nextState); | ||
if(hasSubscribers){ | ||
//Triggers | ||
nextState = extend(appState, processedState); | ||
subscribers.forEach(function(sub){ | ||
for(idxKey=newStateKeysLen-1; idxKey >= 0; idxKey--){ | ||
if(watchList[caller][newStateKeys[idxKey]]){ | ||
var depFldArr = watchList[caller][newStateKeys[idxKey]]; | ||
for(idxDepFld = depFldArr.length - 1; idxDepFld >= 0; idxDepFld--){ | ||
dependsOnObj = depFldArr[idxDepFld].dataContext === appNamespace ? dependsOn : | ||
domain[depFldArr[idxDepFld].dataContext].dependsOn; | ||
if(dependsOnObj[depFldArr[idxDepFld].alias].onStateChange){ | ||
tmpNextState[depFldArr[idxDepFld].alias] = nextState[caller][newStateKeys[idxKey]]; | ||
changeState = dependsOnObj[depFldArr[idxDepFld].alias]. | ||
onStateChange.call(appState.state[depFldArr[idxDepFld].dataContext], tmpNextState); | ||
if(Object.prototype.toString.call(changeState) === '[object Object]'){ | ||
if(depFldArr[idxDepFld].dataContext === appNamespace){ | ||
nextState = extend(nextState, changeState); | ||
} else { | ||
nextState[depFldArr[idxDepFld].dataContext] = | ||
extend(nextState[depFldArr[idxDepFld].dataContext], changeState); | ||
} | ||
} | ||
transientState = {}; | ||
for (keyIdx = transientStateKeysLen; keyIdx >= 0; keyIdx--) { | ||
if(transientStateKeys[keyIdx] in watchedDataContexts){ | ||
for(watchedField in watchedDataContexts[transientStateKeys[keyIdx]]){ | ||
if(watchedDataContexts[transientStateKeys[keyIdx]].hasOwnProperty(watchedField)){ | ||
if(newStateKeys.indexOf(watchedField) !== -1){ | ||
subscribers = watchedDataContexts[transientStateKeys[keyIdx]][watchedField]; | ||
for(subscriber in subscribers){ | ||
if(subscribers.hasOwnProperty(subscriber)){ | ||
transientState = extend(transientState, subscribers[subscriber].call(appState[subscriber], | ||
nextState[transientStateKeys[keyIdx]][watchedField], | ||
appState[transientStateKeys[keyIdx]][watchedField], watchedField, transientStateKeys[keyIdx])); | ||
} | ||
@@ -202,20 +141,72 @@ } | ||
} | ||
}); | ||
} | ||
} | ||
} else { | ||
nextState = extend(appState.state, newState); | ||
}; | ||
if(!!Object.keys(transientState).length){ | ||
appStateChangedHandler(void(0), {}, transientState); | ||
return; | ||
} | ||
//Link Phase | ||
processedStateKeys = Object.keys(processedState); | ||
processedStateKeysLen = processedStateKeys.length - 1; | ||
for (keyIdx = processedStateKeysLen; keyIdx >= 0; keyIdx--) { | ||
if(caller === appNamespace){ | ||
if(processedStateKeys[keyIdx] in links[appNamespace]){ | ||
for(dataContext in links[appNamespace][processedStateKeys[keyIdx]]){ | ||
if(links[appNamespace][processedStateKeys[keyIdx]].hasOwnProperty(dataContext)){ | ||
nextState[dataContext].state[links[appNamespace][processedStateKeys[keyIdx]][dataContext]] = nextState[processedStateKeys[keyIdx]]; | ||
} | ||
if(dataContext in links){ | ||
for(dataContext2 in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(dataContext2)){ | ||
nextState[dataContext].state[links[dataContext][dataContext2]] = | ||
(dataContext2 in domain) ? extend(nextState[dataContext2].state) : | ||
nextState[dataContext2]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} else { | ||
if(processedStateKeys[keyIdx] in links){ | ||
for(dataContext in links[processedStateKeys[keyIdx]]){ | ||
if(links[processedStateKeys[keyIdx]].hasOwnProperty(dataContext)){ | ||
nextState[processedStateKeys[keyIdx]].state[links[processedStateKeys[keyIdx]][dataContext]] = | ||
(dataContext in domain) ? extend(nextState[dataContext].state) : | ||
nextState[dataContext]; | ||
} | ||
if(dataContext in links){ | ||
for(dataContext2 in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(dataContext2)){ | ||
nextState[dataContext].state[links[dataContext][dataContext2]] = | ||
(dataContext2 in domain) ? extend(nextState[dataContext2].state) : | ||
nextState[dataContext2]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
prevState = appState; | ||
nextState = transitionState(caller, nextState, appState.state, hasSubscribers ? subscribers : false); | ||
} | ||
if(!!prevState){ | ||
Object.freeze(prevState); | ||
} | ||
//Create a new App state context. | ||
appState = new ApplicationDataContext(nextState, prevState, enableUndo); | ||
//All the work is done! -> Notify the View | ||
//Provided for the main app to return from init() to the View | ||
Object.freeze(appState); | ||
Object.freeze(appState.state); | ||
stateChangedHandler(appState, caller, callback); | ||
//All the work is done! -> Notify the View | ||
stateChangedHandler(appState); | ||
transientState = {}; | ||
processedState = {}; | ||
//Provided for the main app to return to the View | ||
return appState; | ||
@@ -226,55 +217,69 @@ }; | ||
ApplicationDataContext = domainModel.call(this, appStateChangedHandler.bind(this, appNamespace)); | ||
appState = new ApplicationDataContext({}, void(0), enableUndo, true); | ||
dependsOn = appState.getDependencies ? configure(appState.getDependencies(), 'property') : void(0); | ||
if(dependsOn){ | ||
dependents.push(appNamespace); | ||
for(depProp in dependsOn){ | ||
if(dependsOn.hasOwnProperty(depProp)){ | ||
watchedProps = dependsOn[depProp].property.split('.'); | ||
watchedPropsLen = watchedProps.length; | ||
watchListPropA = watchedPropsLen > 1 ? watchedProps[0] : appNamespace; | ||
watchListPropB = watchedPropsLen > 1 ? watchedProps[1] : watchedProps[0]; | ||
watchList[watchListPropA] = watchList[watchListPropA] || {}; | ||
watchList[watchListPropA][watchListPropB] = watchList[watchListPropA][watchListPropB] || []; | ||
if(watchList[watchListPropA][watchListPropB].indexOf(appNamespace) === -1){ | ||
watchList[watchListPropA][watchListPropB].push({dataContext:appNamespace, alias: depProp}); | ||
} | ||
} | ||
/*Need to look at DomainViewModel state and nextState and Domain Model and updating*/ | ||
appState = new ApplicationDataContext(void(0), void(0), enableUndo, true); | ||
appState.state = appState.state || {}; | ||
domain = appState.getDomainDataContext(); | ||
for(dataContext in domain){ | ||
if(domain.hasOwnProperty(dataContext)){ | ||
dataContexts[dataContext] = domain[dataContext].call(this, appStateChangedHandler.bind(this, dataContext)).bind(this, dataContext); | ||
appState.state[dataContext] = new dataContexts[dataContext](appState.state); | ||
if('getWatchedState' in appState[dataContext]){ | ||
watchedState = appState[dataContext].getWatchedState(); | ||
for(watchedItem in watchedState){ | ||
if(watchedState.hasOwnProperty(watchedItem)){ | ||
if(watchedItem in domain || watchedItem in appState.state){ | ||
if('alias' in watchedState[watchedItem]){ | ||
if(!(dataContext in links)){ | ||
links[dataContext] = {}; | ||
} | ||
links[dataContext][watchedItem] = watchedState[watchedItem].alias; | ||
if(!(watchedItem in domain)){ | ||
if(!(appNamespace in links)){ | ||
links[appNamespace] = {}; | ||
} | ||
if(!(dataContext in links[appNamespace])){ | ||
links[appNamespace][watchedItem] = {}; | ||
} | ||
links[appNamespace][watchedItem][dataContext] = watchedState[watchedItem].alias; | ||
} | ||
} | ||
for(watchedProp in watchedState[watchedItem].fields){ | ||
if(watchedState[watchedItem].fields.hasOwnProperty(watchedProp)){ | ||
if(watchedItem in domain){ | ||
watchedDataContext = {}; | ||
if(!(watchedItem in watchedDataContexts)){ | ||
watchedDataContexts[watchedItem] = {}; | ||
} | ||
watchedDataContext[watchedProp] = {}; | ||
watchedDataContext[watchedProp][dataContext] = watchedState[watchedItem].fields[watchedProp]; | ||
watchedDataContexts[watchedItem] = watchedDataContext; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
domain = configure(appState.getDomainDataContext(), 'viewModel'); | ||
for(var dataContext in domain){ | ||
for(dataContext in domain){ | ||
if(domain.hasOwnProperty(dataContext)){ | ||
dataContexts[dataContext] = domain[dataContext].viewModel.call(this, appStateChangedHandler.bind(this, dataContext)); | ||
appState[dataContext] = new dataContexts[dataContext]({}, {}, {}, true); | ||
if(appState[dataContext].getDependencies){ | ||
dependents.push(dataContext); | ||
domain[dataContext].dependsOn = configure(appState[dataContext].getDependencies(), 'property'); | ||
for(depProp in domain[dataContext].dependsOn){ | ||
if(domain[dataContext].dependsOn.hasOwnProperty(depProp)){ | ||
watchedProps = domain[dataContext].dependsOn[depProp].property.split('.'); | ||
watchedPropsLen = watchedProps.length; | ||
watchListPropA = watchedPropsLen > 1 ? watchedProps[0] : appNamespace; | ||
watchListPropB = watchedPropsLen > 1 ? watchedProps[1] : watchedProps[0]; | ||
watchList[watchListPropA] = watchList[watchListPropA] || {}; | ||
watchList[watchListPropA][watchListPropB] = watchList[watchListPropA][watchListPropB] || []; | ||
if(watchList[watchListPropA][watchListPropB].indexOf(dataContext) === -1){ | ||
watchList[watchListPropA][watchListPropB].push({dataContext:dataContext, alias: depProp}); | ||
} | ||
} | ||
} | ||
} | ||
for(link in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(link)){ | ||
appState[dataContext].state[links[dataContext][link]] = (link in domain) ? extend(appState[link].state): appState[link]; | ||
} | ||
} | ||
} | ||
} | ||
dependents.forEach(function(dependent){ | ||
if(dependent !== appNamespace){ | ||
appState[dependent] = new dataContexts[dependent](appState[dependent], | ||
getDeps(appState, domain[dependent]), {}); | ||
} | ||
}); | ||
appState = new ApplicationDataContext(extend(appState, getDeps(appState, dependsOn)), void(0), enableUndo); | ||
appState = new ApplicationDataContext(appState, void(0), enableUndo); | ||
Object.freeze(appState.state); | ||
return Object.freeze(appState); | ||
Object.freeze(appState); | ||
return appState; | ||
}; | ||
@@ -285,3 +290,3 @@ },{"./utils":8}],3:[function(_dereq_,module,exports){ | ||
var viewModel = _dereq_('./imvvmViewModel'); | ||
var domainModel = _dereq_('./imvvmDomainModel'); | ||
var domainModel = _dereq_('./imvvmDomainViewModel'); | ||
var mixin = _dereq_('./mixin'); | ||
@@ -293,9 +298,9 @@ | ||
var ModelBase = function() {}; | ||
var ViewModelBase = function() {}; | ||
var DomainModelBase = function() {}; | ||
var ModelBase = function(){}; | ||
var ViewModelBase = function(){}; | ||
var DomainViewModelBase = function(){}; | ||
mixInto(ModelBase, model.Mixin); | ||
mixInto(ViewModelBase, viewModel.Mixin); | ||
mixInto(DomainModelBase, domainModel.Mixin); | ||
mixInto(DomainViewModelBase, domainModel.Mixin); | ||
@@ -332,18 +337,57 @@ var IMVVMClass = { | ||
/* // Reduce time spent doing lookups by setting these on the prototype. | ||
for (var methodName in IMVVMInterface) { | ||
if (!Constructor.prototype[methodName]) { | ||
Constructor.prototype[methodName] = null; | ||
ConvenienceConstructor.getDescriptor = function(){ | ||
var descriptor = {}, | ||
proto = this.prototype, | ||
viewModels = {}, | ||
autoFreeze = [], | ||
key; | ||
if('__processedObject__' in this.originalSpec){ | ||
return this.originalSpec.__processedObject__; | ||
} | ||
} | ||
*/ | ||
/* | ||
if (__DEV__) { | ||
// In DEV the convenience constructor generates a proxy to another | ||
// instance around it to warn about access to properties on the | ||
// descriptor. | ||
DescriptorConstructor = createDescriptorProxy(Constructor); | ||
}*/ | ||
for(key in this.originalSpec){ | ||
if(this.originalSpec.hasOwnProperty(key)){ | ||
if('get' in this.originalSpec[key] || 'set' in this.originalSpec[key]){ | ||
//assume it is a descriptor | ||
this.originalSpec[key].enumerable = true; | ||
if('viewModel' in this.originalSpec[key]) { | ||
viewModels[key] = this.originalSpec[key].viewModel; | ||
delete this.originalSpec[key].viewModel; | ||
delete this.originalSpec[key].set; | ||
} else if('kind' in this.originalSpec[key]){ | ||
if(this.originalSpec[key].kind === 'pseudo'){ | ||
this.originalSpec[key].enumerable = false; | ||
} else { //'instance' || 'array' | ||
autoFreeze.push({fieldName: key, kind: this.originalSpec[key].kind}); | ||
} | ||
delete this.originalSpec[key].kind; | ||
} | ||
descriptor[key] = this.originalSpec[key]; | ||
} else { | ||
proto[key] = this.originalSpec[key]; | ||
} | ||
} | ||
} | ||
if(!('extend' in proto)){ | ||
proto.extend = utils.extend; | ||
} | ||
if(!!Object.keys(viewModels).length){ | ||
proto.getDomainDataContext = function(){ | ||
return viewModels; | ||
} | ||
} | ||
this.originalSpec.__processedObject__ = { | ||
descriptor: descriptor, | ||
proto: proto, | ||
originalSpec: this.originalSpec || {}, | ||
freezeFields: autoFreeze | ||
}; | ||
return this.originalSpec.__processedObject__; | ||
}; | ||
return ConvenienceConstructor; | ||
@@ -356,3 +400,3 @@ }, | ||
createViewModel: IMVVMClass.createClass.bind(this, ViewModelBase, 'ViewModel'), | ||
createDomainModel: IMVVMClass.createClass.bind(this, DomainModelBase, 'DomainModel'), | ||
createDomainViewModel: IMVVMClass.createClass.bind(this, DomainViewModelBase, 'DomainViewModel'), | ||
mixin: mixin | ||
@@ -363,3 +407,3 @@ }; | ||
},{"./imvvmDomainModel":4,"./imvvmModel":5,"./imvvmViewModel":6,"./mixin":7,"./utils":8}],4:[function(_dereq_,module,exports){ | ||
},{"./imvvmDomainViewModel":4,"./imvvmModel":5,"./imvvmViewModel":6,"./mixin":7,"./utils":8}],4:[function(_dereq_,module,exports){ | ||
@@ -374,3 +418,3 @@ var utils = _dereq_('./utils'); | ||
var desc = getDescriptor.call(this); | ||
var desc = this.getDescriptor(this); | ||
desc.proto.setState = stateChangedHandler; | ||
@@ -380,13 +424,6 @@ | ||
var freezeFields = desc.freezeFields; | ||
var domainModel = Object.create(desc.proto, desc.descriptor); | ||
var freezeFields = desc.freezeFields, | ||
domainModel = Object.create(desc.proto, desc.descriptor), | ||
fld; | ||
//Need to have 'state' prop in domainModel before can extend domainModel to get correct state | ||
Object.defineProperty(domainModel, 'state', { | ||
configurable: true, | ||
enumerable: false, | ||
writable: true, | ||
value: nextState | ||
}); | ||
if(!!enableUndo && !!prevState){ | ||
@@ -401,17 +438,18 @@ Object.defineProperty(domainModel, 'previousState', { | ||
prevState = prevState || {}; | ||
if(initialize && ('getInitialState' in domainModel)){ | ||
if(nextState === void(0) && ('getInitialState' in domainModel)){ | ||
//Add state prop so that it can be referenced from within getInitialState | ||
nextState = extend(nextState, domainModel.getInitialState.call(domainModel)); | ||
nextState = domainModel.getInitialState.call(domainModel); | ||
} else if('state' in nextState){ | ||
delete nextState.state; | ||
//Need to have 'state' prop in domainModel before can extend domainModel to get correct state | ||
Object.defineProperty(domainModel, 'state', { | ||
configurable: true, | ||
enumerable: false, | ||
writable: true, | ||
value: nextState | ||
}); | ||
nextState = extend(nextState, domainModel); | ||
} | ||
//attach the nextState props to domainModel if they don't exist | ||
var keys = Object.keys(nextState); | ||
for (var i = keys.length - 1; i >= 0; i--) { | ||
if(!(keys[i] in domainModel)){ | ||
domainModel[keys[i]] = nextState[keys[i]]; | ||
} | ||
}; | ||
//Need to have 'state' prop in domainModel before can extend domainModel to get correct state | ||
Object.defineProperty(domainModel, 'state', { | ||
@@ -423,5 +461,5 @@ configurable: false, | ||
}); | ||
//freeze arrays and domainModel instances | ||
for (var fld = freezeFields.length - 1; fld >= 0; fld--) { | ||
for (fld = freezeFields.length - 1; fld >= 0; fld--) { | ||
Object.freeze(domainModel[freezeFields[fld].fieldName]); | ||
@@ -450,41 +488,18 @@ }; | ||
var desc = getDescriptor.call(this); | ||
var dataContext = function(nextState, prevState, withContext) { | ||
var desc = this.getDescriptor(this); | ||
desc.stateChangedHandler = stateChangedHandler; | ||
desc.proto.__getDescriptor = function(){ | ||
return desc; | ||
} | ||
var dataContext = function(nextState, initialize) { | ||
var freezeFields = desc.freezeFields; | ||
var model = Object.create(desc.proto, desc.descriptor); | ||
var argCount = arguments.length; | ||
var lastArgIsBool = typeof Array.prototype.slice.call(arguments, -1)[0] === 'boolean'; | ||
var initialize = false; | ||
var freezeFields = desc.freezeFields, | ||
fld, | ||
model = Object.create(desc.proto, desc.descriptor); | ||
if(argCount === 0){ | ||
//defaults | ||
nextState = {}; | ||
prevState = {}; | ||
withContext = false; | ||
} else if(argCount === 1){ | ||
if(lastArgIsBool){ | ||
withContext = nextState; | ||
nextState = {}; | ||
prevState = {}; | ||
} else { | ||
//assume this is a new Object and there is no prevState | ||
prevState = {}; | ||
withContext = false; | ||
initialize = true; | ||
} | ||
} else if(argCount === 2){ | ||
if(lastArgIsBool){ | ||
//assume this is a new Object and there is no prevState | ||
withContext = prevState; | ||
prevState = {}; | ||
initialize = true; | ||
} else { | ||
withContext = false; | ||
} | ||
if(nextState === void(0)){ | ||
initialize = true; | ||
} | ||
nextState = ('state' in nextState) ? nextState.state : nextState; | ||
prevState = ('state' in prevState) ? prevState.state : prevState; | ||
nextState = nextState || {}; | ||
@@ -498,2 +513,4 @@ Object.defineProperty(model, 'state', { | ||
nextState = extend(nextState, model); | ||
if(initialize && ('getInitialState' in model)){ | ||
@@ -503,17 +520,2 @@ nextState = extend(nextState, model.getInitialState.call(model)); | ||
if(withContext){ | ||
//This will self distruct | ||
Object.defineProperty(model, 'context', { | ||
configurable: true, | ||
enumerable: false, | ||
set: function(context){ | ||
this.setState = function(nextState, callback){ //callback may be useful for DB updates | ||
return stateChangedHandler.bind(context) | ||
.call(context, extend(this.state, nextState), this.state, callback); | ||
}.bind(this); | ||
delete this.context; | ||
} | ||
}); | ||
} | ||
Object.defineProperty(model, 'state', { | ||
@@ -527,11 +529,6 @@ configurable: false, | ||
//freeze arrays and model instances | ||
for (var i = freezeFields.length - 1; i >= 0; i--) { | ||
Object.freeze(model[freezeFields[i].fieldName]); | ||
for (fld = freezeFields.length - 1; fld >= 0; fld--) { | ||
Object.freeze(model[freezeFields[fld].fieldName]); | ||
}; | ||
if(!withContext){ | ||
Object.freeze(model); | ||
} | ||
return model; | ||
return Object.freeze(model); | ||
}; | ||
@@ -555,14 +552,14 @@ return dataContext; | ||
var desc = getDescriptor.call(this); | ||
var desc = this.getDescriptor(this); | ||
desc.proto.setState = stateChangedHandler; | ||
var dataContext = function(nextState, dependencies, prevState, initialize) { | ||
var dataContext = function(VMName, appState) { | ||
//nextState has already been extended with prevState in core | ||
nextState = extend(nextState, dependencies); | ||
prevState = prevState || {}; | ||
prevState = ('state' in prevState) ? prevState.state : prevState; | ||
var freezeFields = desc.freezeFields; | ||
var viewModel = Object.create(desc.proto, desc.descriptor); | ||
var nextState = {}, | ||
freezeFields = desc.freezeFields, | ||
fld, | ||
viewModel = Object.create(desc.proto, desc.descriptor), | ||
tempDesc, | ||
tempModel; | ||
@@ -576,4 +573,8 @@ Object.defineProperty(viewModel, 'state', { | ||
if(initialize && ('getInitialState' in viewModel)){ | ||
nextState = extend(nextState, viewModel.getInitialState.call(viewModel)); | ||
if(appState[VMName] === void(0)){ | ||
if('getInitialState' in viewModel){ | ||
nextState = extend(nextState, viewModel.getInitialState.call(viewModel)); | ||
} | ||
} else { | ||
nextState = ('state' in appState[VMName] ? appState[VMName].state : appState[VMName]); | ||
} | ||
@@ -589,23 +590,28 @@ | ||
//freeze arrays and viewModel instances | ||
for (var i = freezeFields.length - 1; i >= 0; i--) { | ||
if(freezeFields[i].kind === 'instance' && | ||
('context' in viewModel[freezeFields[i].fieldName])){ | ||
viewModel[freezeFields[i].fieldName].context = viewModel; | ||
for (fld = freezeFields.length - 1; fld >= 0; fld--) { | ||
if(freezeFields[fld].kind === 'instance'){ | ||
if(viewModel[freezeFields[fld].fieldName]){ | ||
tempDesc = viewModel[freezeFields[fld].fieldName].__getDescriptor(); | ||
tempModel = Object.create(tempDesc.proto, tempDesc.descriptor); | ||
Object.defineProperty(tempModel, 'state', { | ||
configurable: false, | ||
enumerable: false, | ||
writable: false, | ||
value: viewModel[freezeFields[fld].fieldName].state | ||
}); | ||
tempModel.__proto__.setState = function(someState, callback){ //callback may be useful for DB updates | ||
return tempDesc.stateChangedHandler.call(this, | ||
extend(tempModel.state, someState), tempModel.state, callback); | ||
}.bind(viewModel); | ||
Object.freeze(viewModel[freezeFields[fld].fieldName]); | ||
} | ||
} else { | ||
Object.freeze(viewModel[freezeFields[fld].fieldName]); | ||
} | ||
Object.freeze(viewModel[freezeFields[i].fieldName]); | ||
}; | ||
//Add dependencies to viewModel | ||
for(var dep in dependencies){ | ||
if(dependencies.hasOwnProperty(dep) && dep[0] !== '_'){ | ||
Object.defineProperty(viewModel, dep, { | ||
configurable: false, | ||
enumerable: false, | ||
writable: false, | ||
value: dependencies[dep] | ||
}); | ||
} | ||
} | ||
Object.freeze(nextState); | ||
return Object.freeze(viewModel); | ||
@@ -627,26 +633,5 @@ | ||
var mixin = { | ||
stateChangedHandler: function(dataContext, caller, callback){ | ||
this.setState({domainDataContext: dataContext}, function(){ | ||
//send all state back to caller | ||
//useful if you need to know what other parts of the app | ||
//were impacted by your changes. You can also use the returned | ||
//information to display things external to your ApplicationModel | ||
//Allows you to have multiple Application ViewModels in the one app and | ||
//still share the state with other presentation models that may be interested | ||
if(typeof callback === 'function'){ | ||
if(this.state === null || !('domainDataContext' in this.state)){ | ||
callback(void(0)); | ||
} else { | ||
if(caller in this.state.domainDataContext){ | ||
callback(this.state.domainDataContext[caller]); | ||
} else if(caller === NAMESPACE) { | ||
callback(this.state.domainDataContext); | ||
} else { | ||
callback(void(0)); | ||
} | ||
} | ||
} | ||
}.bind(this)); | ||
stateChangedHandler: function(dataContext){ | ||
this.setState({domainDataContext: dataContext}) | ||
}, | ||
getInitialState: function(){ | ||
@@ -657,3 +642,2 @@ var dataContext = core.getInitialState(NAMESPACE, this.props.domainModel, | ||
} | ||
}; | ||
@@ -665,45 +649,3 @@ | ||
var utils = { | ||
getDescriptor: function(){ | ||
var descriptor = {}; | ||
var proto = this.prototype; | ||
var autoFreeze = []; | ||
if('__processedObject__' in this.originalSpec){ | ||
return this.originalSpec.__processedObject__; | ||
} | ||
for(var key in this.originalSpec){ | ||
if(this.originalSpec.hasOwnProperty(key)){ | ||
if('get' in this.originalSpec[key] || 'set' in this.originalSpec[key]){ | ||
//assume it is a descriptor | ||
this.originalSpec[key].enumerable = true; | ||
if('kind' in this.originalSpec[key]){ | ||
if(this.originalSpec[key].kind === 'pseudo'){ | ||
this.originalSpec[key].enumerable = false; | ||
} else { //'instance' || 'array' | ||
autoFreeze.push({fieldName: key, kind: this.originalSpec[key].kind}); | ||
} | ||
delete this.originalSpec[key].kind; | ||
} | ||
descriptor[key] = this.originalSpec[key]; | ||
} else { | ||
proto[key] = this.originalSpec[key]; | ||
} | ||
} | ||
} | ||
if(!('extend' in proto)){ | ||
proto.extend = utils.extend; | ||
} | ||
this.originalSpec.__processedObject__ = { | ||
descriptor: descriptor, | ||
proto: proto, | ||
originalSpec: this.originalSpec || {}, | ||
freezeFields: autoFreeze | ||
}; | ||
return this.originalSpec.__processedObject__; | ||
}, | ||
extend: function () { | ||
@@ -710,0 +652,0 @@ var newObj = {}; |
@@ -1,1 +0,1 @@ | ||
!function(b){if("object"==typeof exports){module.exports=b()}else{if("function"==typeof define&&define.amd){define(b)}else{var a;"undefined"!=typeof window?a=window:"undefined"!=typeof global?a=global:"undefined"!=typeof self&&(a=self),a.IMVVM=b()}}}(function(){var d,b,a;return(function c(f,k,h){function g(n,l){if(!k[n]){if(!f[n]){var i=typeof require=="function"&&require;if(!l&&i){return i(n,!0)}if(e){return e(n,!0)}throw new Error("Cannot find module '"+n+"'")}var m=k[n]={exports:{}};f[n][0].call(m.exports,function(o){var p=f[n][1][o];return g(p?p:o)},m,m.exports,c,f,k,h)}return k[n].exports}var e=typeof require=="function"&&require;for(var j=0;j<h.length;j++){g(h[j])}return g})({1:[function(h,g,f){var e=h("./src/imvvm.js");g.exports=e},{"./src/imvvm.js":3}],2:[function(h,g,f){var e=h("./utils");var i=e.extend;f.getInitialState=function(l,m,u,k){if(typeof u!=="function"){throw new TypeError()}k===void (0)?true:k;var B,j,t={},r={},y,n,w={},v=[],D,q,p;var z;var A=function(H,G){var F={};for(var E in H){if(H.hasOwnProperty(E)){if(Object.prototype.toString.call(H[E])==="[object Object]"){F[E]=H[E]}else{F[E]={};F[E][G]=H[E]}}}return F};var C=function(H,F){var J={},I,E;if(!F){return{}}F=("dependsOn" in F)?F:{dependsOn:F};for(var G in F.dependsOn){if(F.dependsOn.hasOwnProperty(G)){E={};I=F.dependsOn[G].property.split(".");I.forEach(function(L,K){if(K===0){E=H[L]}else{E=E?E[L]:void (0)}});J[G]=E}}return J};var x=function(E,F,J,I){F=F||{};J=J||{};var L=false,K,G;if(E!==l){F[E]=new r[E](F[E],C(F,D[E]),J[E])}if(I){if(!!j){K=C(F,j);F=i(F,K);for(var H in j){if(j.hasOwnProperty(H)&&("onStateChange" in j[H])){G={};G[H]=F[H];F=i(F,j[H].onStateChange(G))}}}F=new B(i(F,K),J,k);I.forEach(function(M){if(M!==l){F[M]=new r[M](F[M],C(F,D[M]),J[M])}})}return F};var o=function(G,I,S){if((I===void (0)||I===null||Object.keys(I).length===0)){return}var T={},R={},E=[],P,K,N,F,O,L={},Q,M;if(Object.getPrototypeOf(I).constructor.classType==="DomainModel"){T=i(I);R=I.previousState}else{if(G in w){P=Object.keys(I);K=P.length;N={};for(var J=K-1;J>=0;J--){if(w[G][P[J]]){for(var H=w[G][P[J]].length-1;H>=0;H--){N[w[G][P[J]][H].dataContext]=true}}}E=Object.keys(N);hasSubscribers=!!E.length}if(G!==l){T[G]=I;T=i(t.state,T);if(hasSubscribers){E.forEach(function(V){for(F=K-1;F>=0;F--){if(w[G][P[F]]){var U=w[G][P[F]];for(O=U.length-1;O>=0;O--){M=U[O].dataContext===l?j:D[U[O].dataContext].dependsOn;if(M[U[O].alias].onStateChange){L[U[O].alias]=T[G][P[F]];Q=M[U[O].alias].onStateChange.call(t.state[U[O].dataContext],L);if(Object.prototype.toString.call(Q)==="[object Object]"){if(U[O].dataContext===l){T=i(T,Q)}else{T[U[O].dataContext]=i(T[U[O].dataContext],Q)}}}}}}})}}else{T=i(t.state,I)}R=t;T=x(G,T,t.state,hasSubscribers?E:false)}if(!!R){Object.freeze(R)}t=new B(T,R,k);Object.freeze(t);Object.freeze(t.state);u(t,G,S);return t};B=m.call(this,o.bind(this,l));t=new B({},void (0),k,true);j=t.getDependencies?A(t.getDependencies(),"property"):void (0);if(j){v.push(l);for(z in j){if(j.hasOwnProperty(z)){y=j[z].property.split(".");n=y.length;q=n>1?y[0]:l;p=n>1?y[1]:y[0];w[q]=w[q]||{};w[q][p]=w[q][p]||[];if(w[q][p].indexOf(l)===-1){w[q][p].push({dataContext:l,alias:z})}}}}D=A(t.getDomainDataContext(),"viewModel");for(var s in D){if(D.hasOwnProperty(s)){r[s]=D[s].viewModel.call(this,o.bind(this,s));t[s]=new r[s]({},{},{},true);if(t[s].getDependencies){v.push(s);D[s].dependsOn=A(t[s].getDependencies(),"property");for(z in D[s].dependsOn){if(D[s].dependsOn.hasOwnProperty(z)){y=D[s].dependsOn[z].property.split(".");n=y.length;q=n>1?y[0]:l;p=n>1?y[1]:y[0];w[q]=w[q]||{};w[q][p]=w[q][p]||[];if(w[q][p].indexOf(s)===-1){w[q][p].push({dataContext:s,alias:z})}}}}}}v.forEach(function(E){if(E!==l){t[E]=new r[E](t[E],C(t,D[E]),{})}});t=new B(i(t,C(t,j)),void (0),k);Object.freeze(t.state);return Object.freeze(t)}},{"./utils":8}],3:[function(f,g,j){var k=f("./imvvmModel");var p=f("./imvvmViewModel");var i=f("./imvvmDomainModel");var r=f("./mixin");var q=f("./utils");var n=q.extend;var m=q.mixInto;var l=function(){};var h=function(){};var o=function(){};m(l,k.Mixin);m(h,p.Mixin);m(o,i.Mixin);var e={createClass:function(u,v,t){var y=function(){};y.prototype=new u();y.prototype.constructor=y;var x=y;var w=function(z){var A=new x();return A.construct.apply(w,arguments)};w.componentConstructor=y;y.ConvenienceConstructor=w;w.originalSpec=t;w.type=y;y.prototype.type=y;w.classType=v;y.prototype.classType=v;return w}};var s={createModel:e.createClass.bind(this,l,"Model"),createViewModel:e.createClass.bind(this,h,"ViewModel"),createDomainModel:e.createClass.bind(this,o,"DomainModel"),mixin:r};g.exports=s},{"./imvvmDomainModel":4,"./imvvmModel":5,"./imvvmViewModel":6,"./mixin":7,"./utils":8}],4:[function(j,h,f){var e=j("./utils");var k=e.extend;var g=e.getDescriptor;var i={Mixin:{construct:function(m){var n=g.call(this);n.proto.setState=m;var l=function(w,u,o,t){var q=n.freezeFields;var r=Object.create(n.proto,n.descriptor);Object.defineProperty(r,"state",{configurable:true,enumerable:false,writable:true,value:w});if(!!o&&!!u){Object.defineProperty(r,"previousState",{configurable:false,enumerable:false,writable:false,value:u})}u=u||{};if(t&&("getInitialState" in r)){w=k(w,r.getInitialState.call(r))}var v=Object.keys(w);for(var s=v.length-1;s>=0;s--){if(!(v[s] in r)){r[v[s]]=w[v[s]]}}Object.defineProperty(r,"state",{configurable:false,enumerable:false,writable:false,value:w});for(var p=q.length-1;p>=0;p--){Object.freeze(r[q[p].fieldName])}return r};return l}}};h.exports=i},{"./utils":8}],5:[function(i,h,f){var e=i("./utils");var k=e.extend;var g=e.getDescriptor;var j={Mixin:{construct:function(m){var n=g.call(this);var l=function(w,v,o){var p=n.freezeFields;var t=Object.create(n.proto,n.descriptor);var r=arguments.length;var u=typeof Array.prototype.slice.call(arguments,-1)[0]==="boolean";var s=false;if(r===0){w={};v={};o=false}else{if(r===1){if(u){o=w;w={};v={}}else{v={};o=false;s=true}}else{if(r===2){if(u){o=v;v={};s=true}else{o=false}}}}w=("state" in w)?w.state:w;v=("state" in v)?v.state:v;Object.defineProperty(t,"state",{configurable:true,enumerable:false,writable:true,value:w});if(s&&("getInitialState" in t)){w=k(w,t.getInitialState.call(t))}if(o){Object.defineProperty(t,"context",{configurable:true,enumerable:false,set:function(x){this.setState=function(y,z){return m.bind(x).call(x,k(this.state,y),this.state,z)}.bind(this);delete this.context}})}Object.defineProperty(t,"state",{configurable:false,enumerable:false,writable:false,value:w});for(var q=p.length-1;q>=0;q--){Object.freeze(t[p[q].fieldName])}if(!o){Object.freeze(t)}return t};return l}}};h.exports=j},{"./utils":8}],6:[function(j,i,g){var f=j("./utils");var k=f.extend;var h=f.getDescriptor;var e={Mixin:{construct:function(m){var n=h.call(this);n.proto.setState=m;var l=function(r,t,v,o){r=k(r,t);v=v||{};v=("state" in v)?v.state:v;var s=n.freezeFields;var p=Object.create(n.proto,n.descriptor);Object.defineProperty(p,"state",{configurable:true,enumerable:false,writable:true,value:r});if(o&&("getInitialState" in p)){r=k(r,p.getInitialState.call(p))}Object.defineProperty(p,"state",{configurable:false,enumerable:false,writable:false,value:r});for(var q=s.length-1;q>=0;q--){if(s[q].kind==="instance"&&("context" in p[s[q].fieldName])){p[s[q].fieldName].context=p}Object.freeze(p[s[q].fieldName])}for(var u in t){if(t.hasOwnProperty(u)&&u[0]!=="_"){Object.defineProperty(p,u,{configurable:false,enumerable:false,writable:false,value:t[u]})}}Object.freeze(r);return Object.freeze(p)};return l}}};i.exports=e},{"./utils":8}],7:[function(j,i,h){var f=j("./core");var e="__IMVVM__";var g={stateChangedHandler:function(k,l,m){this.setState({domainDataContext:k},function(){if(typeof m==="function"){if(this.state===null||!("domainDataContext" in this.state)){m(void (0))}else{if(l in this.state.domainDataContext){m(this.state.domainDataContext[l])}else{if(l===e){m(this.state.domainDataContext)}else{m(void (0))}}}}}.bind(this))},getInitialState:function(){var k=f.getInitialState(e,this.props.domainModel,this.stateChangedHandler,this.props.enableUndo);return{domainDataContext:k}}};i.exports=g},{"./core":2}],8:[function(h,g,f){var e={getDescriptor:function(){var l={};var k=this.prototype;var i=[];if("__processedObject__" in this.originalSpec){return this.originalSpec.__processedObject__}for(var j in this.originalSpec){if(this.originalSpec.hasOwnProperty(j)){if("get" in this.originalSpec[j]||"set" in this.originalSpec[j]){this.originalSpec[j].enumerable=true;if("kind" in this.originalSpec[j]){if(this.originalSpec[j].kind==="pseudo"){this.originalSpec[j].enumerable=false}else{i.push({fieldName:j,kind:this.originalSpec[j].kind})}delete this.originalSpec[j].kind}l[j]=this.originalSpec[j]}else{k[j]=this.originalSpec[j]}}}if(!("extend" in k)){k.extend=e.extend}this.originalSpec.__processedObject__={descriptor:l,proto:k,originalSpec:this.originalSpec||{},freezeFields:i};return this.originalSpec.__processedObject__},extend:function(){var j={};for(var l=0;l<arguments.length;l++){var m=arguments[l];for(var k in m){if(m.hasOwnProperty(k)){j[k]=m[k]}}}return j},mixInto:function(j,k){var i;for(i in k){if(!k.hasOwnProperty(i)){continue}j.prototype[i]=k[i]}}};g.exports=e},{}]},{},[1])(1)}); | ||
!function(b){if("object"==typeof exports){module.exports=b()}else{if("function"==typeof define&&define.amd){define(b)}else{var a;"undefined"!=typeof window?a=window:"undefined"!=typeof global?a=global:"undefined"!=typeof self&&(a=self),a.IMVVM=b()}}}(function(){var d,b,a;return(function c(f,k,h){function g(n,l){if(!k[n]){if(!f[n]){var i=typeof require=="function"&&require;if(!l&&i){return i(n,!0)}if(e){return e(n,!0)}throw new Error("Cannot find module '"+n+"'")}var m=k[n]={exports:{}};f[n][0].call(m.exports,function(o){var p=f[n][1][o];return g(p?p:o)},m,m.exports,c,f,k,h)}return k[n].exports}var e=typeof require=="function"&&require;for(var j=0;j<h.length;j++){g(h[j])}return g})({1:[function(h,g,f){var e=h("./src/imvvm.js");g.exports=e},{"./src/imvvm.js":3}],2:[function(h,g,f){var e=h("./utils");var i=e.extend;f.getInitialState=function(l,m,v,k){if(typeof v!=="function"){throw new TypeError()}k===void (0)?true:k;var A,u={},r={},B,j={},y={},t,q={},o={},x,w,z,s,p;var n=function(E,I,K,P){var R={},N={},L,G,F,D,H,J=[],O,M,C,Q;if(typeof K==="function"){P=K;K={}}I=I||{};L=Object.keys(I);if(Object.getPrototypeOf(I).constructor.classType==="DomainViewModel"){R=i(I);N=I.previousState;for(D in B){R[D]=new r[D](R)}for(D in B){if(B.hasOwnProperty(D)){for(H in j[D]){if(j[D].hasOwnProperty(H)){R[D].state[j[D][H]]=(H in B)?i(R[H].state):R[H]}}}}}else{if(!!L.length){if(E===l){R=i(I)}else{R[E]=i(I)}}q=i(R,q,K);transientStateKeys=Object.keys(q);if(transientStateKeys.length===0){return}if(typeof P==="function"){P();return}F=transientStateKeys.length-1;for(G=F;G>=0;G--){if(transientStateKeys[G] in B){R[transientStateKeys[G]]=i(u[transientStateKeys[G]],q[transientStateKeys[G]]);R[transientStateKeys[G]]=new r[transientStateKeys[G]](R)}else{R[transientStateKeys[G]]=q[transientStateKeys[G]]}}o=i(o,R);R=i(u,o);q={};for(G=F;G>=0;G--){if(transientStateKeys[G] in y){for(M in y[transientStateKeys[G]]){if(y[transientStateKeys[G]].hasOwnProperty(M)){if(L.indexOf(M)!==-1){C=y[transientStateKeys[G]][M];for(Q in C){if(C.hasOwnProperty(Q)){q=i(q,C[Q].call(u[Q],R[transientStateKeys[G]][M],u[transientStateKeys[G]][M],M,transientStateKeys[G]))}}}}}}}if(!!Object.keys(q).length){n(void (0),{},q);return}J=Object.keys(o);O=J.length-1;for(G=O;G>=0;G--){if(E===l){if(J[G] in j[l]){for(D in j[l][J[G]]){if(j[l][J[G]].hasOwnProperty(D)){R[D].state[j[l][J[G]][D]]=R[J[G]]}if(D in j){for(dataContext2 in j[D]){if(j[D].hasOwnProperty(dataContext2)){R[D].state[j[D][dataContext2]]=(dataContext2 in B)?i(R[dataContext2].state):R[dataContext2]}}}}}}else{if(J[G] in j){for(D in j[J[G]]){if(j[J[G]].hasOwnProperty(D)){R[J[G]].state[j[J[G]][D]]=(D in B)?i(R[D].state):R[D]}if(D in j){for(dataContext2 in j[D]){if(j[D].hasOwnProperty(dataContext2)){R[D].state[j[D][dataContext2]]=(dataContext2 in B)?i(R[dataContext2].state):R[dataContext2]}}}}}}}N=u}if(!!N){Object.freeze(N)}u=new A(R,N,k);Object.freeze(u);Object.freeze(u.state);v(u);q={};o={};return u};A=m.call(this,n.bind(this,l));u=new A(void (0),void (0),k,true);u.state=u.state||{};B=u.getDomainDataContext();for(t in B){if(B.hasOwnProperty(t)){r[t]=B[t].call(this,n.bind(this,t)).bind(this,t);u.state[t]=new r[t](u.state);if("getWatchedState" in u[t]){x=u[t].getWatchedState();for(w in x){if(x.hasOwnProperty(w)){if(w in B||w in u.state){if("alias" in x[w]){if(!(t in j)){j[t]={}}j[t][w]=x[w].alias;if(!(w in B)){if(!(l in j)){j[l]={}}if(!(t in j[l])){j[l][w]={}}j[l][w][t]=x[w].alias}}for(z in x[w].fields){if(x[w].fields.hasOwnProperty(z)){if(w in B){s={};if(!(w in y)){y[w]={}}s[z]={};s[z][t]=x[w].fields[z];y[w]=s}}}}}}}}}for(t in B){if(B.hasOwnProperty(t)){for(p in j[t]){if(j[t].hasOwnProperty(p)){u[t].state[j[t][p]]=(p in B)?i(u[p].state):u[p]}}}}u=new A(u,void (0),k);Object.freeze(u.state);Object.freeze(u);return u}},{"./utils":8}],3:[function(f,g,j){var k=f("./imvvmModel");var p=f("./imvvmViewModel");var i=f("./imvvmDomainViewModel");var r=f("./mixin");var q=f("./utils");var o=q.extend;var m=q.mixInto;var l=function(){};var h=function(){};var n=function(){};m(l,k.Mixin);m(h,p.Mixin);m(n,i.Mixin);var e={createClass:function(u,v,t){var y=function(){};y.prototype=new u();y.prototype.constructor=y;var x=y;var w=function(z){var A=new x();return A.construct.apply(w,arguments)};w.componentConstructor=y;y.ConvenienceConstructor=w;w.originalSpec=t;w.type=y;y.prototype.type=y;w.classType=v;y.prototype.classType=v;w.getDescriptor=function(){var D={},C=this.prototype,A={},z=[],B;if("__processedObject__" in this.originalSpec){return this.originalSpec.__processedObject__}for(B in this.originalSpec){if(this.originalSpec.hasOwnProperty(B)){if("get" in this.originalSpec[B]||"set" in this.originalSpec[B]){this.originalSpec[B].enumerable=true;if("viewModel" in this.originalSpec[B]){A[B]=this.originalSpec[B].viewModel;delete this.originalSpec[B].viewModel;delete this.originalSpec[B].set}else{if("kind" in this.originalSpec[B]){if(this.originalSpec[B].kind==="pseudo"){this.originalSpec[B].enumerable=false}else{z.push({fieldName:B,kind:this.originalSpec[B].kind})}delete this.originalSpec[B].kind}}D[B]=this.originalSpec[B]}else{C[B]=this.originalSpec[B]}}}if(!("extend" in C)){C.extend=q.extend}if(!!Object.keys(A).length){C.getDomainDataContext=function(){return A}}this.originalSpec.__processedObject__={descriptor:D,proto:C,originalSpec:this.originalSpec||{},freezeFields:z};return this.originalSpec.__processedObject__};return w}};var s={createModel:e.createClass.bind(this,l,"Model"),createViewModel:e.createClass.bind(this,h,"ViewModel"),createDomainViewModel:e.createClass.bind(this,n,"DomainViewModel"),mixin:r};g.exports=s},{"./imvvmDomainViewModel":4,"./imvvmModel":5,"./imvvmViewModel":6,"./mixin":7,"./utils":8}],4:[function(j,h,f){var e=j("./utils");var k=e.extend;var g=e.getDescriptor;var i={Mixin:{construct:function(m){var n=this.getDescriptor(this);n.proto.setState=m;var l=function(q,u,t,o){var s=n.freezeFields,r=Object.create(n.proto,n.descriptor),p;if(!!t&&!!u){Object.defineProperty(r,"previousState",{configurable:false,enumerable:false,writable:false,value:u})}if(q===void (0)&&("getInitialState" in r)){q=r.getInitialState.call(r)}else{if("state" in q){delete q.state;Object.defineProperty(r,"state",{configurable:true,enumerable:false,writable:true,value:q});q=k(q,r)}}Object.defineProperty(r,"state",{configurable:false,enumerable:false,writable:false,value:q});for(p=s.length-1;p>=0;p--){Object.freeze(r[s[p].fieldName])}return r};return l}}};h.exports=i},{"./utils":8}],5:[function(i,h,f){var e=i("./utils");var k=e.extend;var g=e.getDescriptor;var j={Mixin:{construct:function(m){var n=this.getDescriptor(this);n.stateChangedHandler=m;n.proto.__getDescriptor=function(){return n};var l=function(r,o){var s=n.freezeFields,q,p=Object.create(n.proto,n.descriptor);if(r===void (0)){o=true}r=r||{};Object.defineProperty(p,"state",{configurable:true,enumerable:false,writable:true,value:r});r=k(r,p);if(o&&("getInitialState" in p)){r=k(r,p.getInitialState.call(p))}Object.defineProperty(p,"state",{configurable:false,enumerable:false,writable:false,value:r});for(q=s.length-1;q>=0;q--){Object.freeze(p[s[q].fieldName])}return Object.freeze(p)};return l}}};h.exports=j},{"./utils":8}],6:[function(j,i,g){var f=j("./utils");var k=f.extend;var h=f.getDescriptor;var e={Mixin:{construct:function(m){var n=this.getDescriptor(this);n.proto.setState=m;var l=function(s,u){var r={},t=n.freezeFields,q,p=Object.create(n.proto,n.descriptor),v,o;Object.defineProperty(p,"state",{configurable:true,enumerable:false,writable:true,value:r});if(u[s]===void (0)){if("getInitialState" in p){r=k(r,p.getInitialState.call(p))}}else{r=("state" in u[s]?u[s].state:u[s])}Object.defineProperty(p,"state",{configurable:false,enumerable:false,writable:false,value:r});for(q=t.length-1;q>=0;q--){if(t[q].kind==="instance"){if(p[t[q].fieldName]){v=p[t[q].fieldName].__getDescriptor();o=Object.create(v.proto,v.descriptor);Object.defineProperty(o,"state",{configurable:false,enumerable:false,writable:false,value:p[t[q].fieldName].state});o.__proto__.setState=function(w,x){return v.stateChangedHandler.call(this,k(o.state,w),o.state,x)}.bind(p);Object.freeze(p[t[q].fieldName])}}else{Object.freeze(p[t[q].fieldName])}}return Object.freeze(p)};return l}}};i.exports=e},{"./utils":8}],7:[function(j,i,h){var f=j("./core");var e="__IMVVM__";var g={stateChangedHandler:function(k){this.setState({domainDataContext:k})},getInitialState:function(){var k=f.getInitialState(e,this.props.domainModel,this.stateChangedHandler,this.props.enableUndo);return{domainDataContext:k}}};i.exports=g},{"./core":2}],8:[function(h,g,f){var e={extend:function(){var j={};for(var l=0;l<arguments.length;l++){var m=arguments[l];for(var k in m){if(m.hasOwnProperty(k)){j[k]=m[k]}}}return j},mixInto:function(j,k){var i;for(i in k){if(!k.hasOwnProperty(i)){continue}j.prototype[i]=k[i]}}};g.exports=e},{}]},{},[1])(1)}); |
@@ -13,3 +13,5 @@ /** | ||
delete: function(e){ | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
this.props.funcDelete(e); | ||
@@ -16,0 +18,0 @@ }, |
@@ -14,3 +14,3 @@ /** | ||
React.renderComponent(<ApplicationView | ||
domainModel={DomainModel} | ||
domainModel={DomainViewModel} | ||
enableUndo={true} />, | ||
@@ -17,0 +17,0 @@ document.getElementById('container')); |
@@ -8,9 +8,23 @@ /*jshint unused: false */ | ||
getInitialState: function(/*nextState, prevState*/){ | ||
getInitialState: function(){ | ||
var id, hobbies = []; | ||
id = this.id || this.uuid(); | ||
hobbies = DataService.getHobbiesData(this.id).map(function(hobby){ | ||
return this.Hobby(hobby, true); | ||
}.bind(this)); | ||
return { | ||
age: this.calculateAge(this.dob), | ||
id: this.id ? this.id : this.uuid() | ||
id: id, | ||
hobbies: hobbies | ||
}; | ||
}, | ||
Hobby: function(hobbyState, init){ | ||
return new HobbyModel()(hobbyState, init); | ||
}, | ||
id: { | ||
@@ -111,3 +125,3 @@ get: function(){ | ||
kind: 'array', | ||
get: function(){ return this.state.hobbies ? this.state.hobbies : []; }, | ||
get: function(){ return this.state.hobbies; }, | ||
set: function(newArray){ | ||
@@ -128,4 +142,5 @@ this.setState({'hobbies': newArray}); | ||
this.hobbies = this.hobbies.filter(function(hobby){ | ||
return hobby !== value; | ||
return hobby.id !== value; | ||
}); | ||
console.log(this.hobbies); | ||
}, | ||
@@ -132,0 +147,0 @@ |
@@ -7,18 +7,29 @@ /*jshint unused: false */ | ||
var HobbiesViewModel = IMVVM.createViewModel({ | ||
getDependencies: function(){ | ||
getWatchedState: function() { | ||
return { | ||
_selectedPerson: { | ||
property: 'persons.selected', | ||
onStateChange: this.resetSelected | ||
'persons': { | ||
alias: 'personsContext', //optional - if provided then will be added to prototype | ||
fields: { //optional | ||
'selectedPerson': this.onPersonChangedHandler | ||
} | ||
}, | ||
busy: 'busy' | ||
'busy': { | ||
alias: 'busy' | ||
} | ||
} | ||
}, | ||
//Use when this needs change state triggered by others action | ||
onPersonChangedHandler: function(nextState, prevState, field, context){ | ||
if(this.current !== void(0) && context === 'persons' && | ||
nextState.id !== prevState.id){ | ||
return { hobbies: { current: void(0) }, busy: false }; | ||
} | ||
}, | ||
hobbies: { | ||
kind: 'pseudo',//kind: 'pseudo' because get is supplied from other source | ||
//if referencing a dependency this kind = 'pseudo' | ||
kind: 'pseudo', | ||
get: function(){ | ||
return this.state._selectedPerson.hobbies; | ||
return this.state.personsContext.selectedPerson.hobbies; | ||
} | ||
@@ -34,31 +45,73 @@ }, | ||
selected: { | ||
current: { | ||
kind: 'instance', | ||
get: function(){ | ||
return this.state.selected; | ||
return this.state.current; | ||
} | ||
}, | ||
select: function(value){ | ||
var nextState = {}; | ||
nextState.selected = this.hobbies.filter(function(hobby){ | ||
return hobby === value; | ||
})[0]; | ||
this.setState(nextState); | ||
Hobby: function(hobbyState, init){ | ||
return new HobbyModel(this.hobbyStateChangedHandler)(hobbyState, init); | ||
}, | ||
hobbyStateChangedHandler: function(nextState, prevState/*, callback*/){ | ||
var newState = {}; | ||
var hobbiesArr = this.hobbies.map(function(hobby){ | ||
if (hobby.id === nextState.id){ | ||
newState.current = this.Hobby(nextState); | ||
return newState.current; | ||
} | ||
return hobby; | ||
}.bind(this)); | ||
this.setState(newState, function(){ | ||
this.state.personsContext.selectedPerson.hobbies = hobbiesArr; | ||
}.bind(this)); | ||
}, | ||
selectHobby: function(id){ | ||
for (var i = this.hobbies.length - 1; i >= 0; i--) { | ||
if ((this.current === void(0) || this.current.id !== id) && this.hobbies[i].id === id){ | ||
this.setState({current: this.Hobby(this.hobbies[i])}, {busy: true}); | ||
/* | ||
//OR use a callback | ||
this.setState({current: this.Hobby(this.hobbies[i])}, function(){ | ||
this.setState(void(0), {busy: true}); | ||
}.bind(this)); | ||
*/ | ||
break; | ||
} | ||
}; | ||
}, | ||
addHobby: function(value){ | ||
this.state._selectedPerson.addHobby(value); | ||
this.state.personsContext.selectedPerson. | ||
addHobby(this.Hobby({ id:this.uuid(), name:value }, true)); | ||
}, | ||
deleteHobby: function(value){ | ||
this.state._selectedPerson.deleteHobby(value); | ||
this.state.personsContext.selectedPerson.deleteHobby(value); | ||
}, | ||
//When a dependency changes reset the selected hobby to undefined | ||
resetSelected: function(newValue) { | ||
if(this.selected !== void(0)){ | ||
return { selected: void(0) }; | ||
uuid: function () { | ||
/*jshint bitwise:false */ | ||
var i, random; | ||
var uuid = ''; | ||
for (i = 0; i < 32; i++) { | ||
random = Math.random() * 16 | 0; | ||
if (i === 8 || i === 12 || i === 16 || i === 20) { | ||
uuid += '-'; | ||
} | ||
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) | ||
.toString(16); | ||
} | ||
return uuid; | ||
}, | ||
}); |
@@ -10,40 +10,57 @@ /*jshint unused: false */ | ||
var nextState = {}; | ||
nextState.collection = DataService.getData().map(function(person, idx){ | ||
nextState.collection = DataService.getPersonData().map(function(person, idx){ | ||
if (idx === 0){ | ||
nextState.selected = this.Person(person, true); | ||
return nextState.selected; | ||
nextState.selectedPerson = this.Person(person, true); | ||
return nextState.selectedPerson; | ||
} | ||
return this.Person(person); | ||
return this.Person(person, true); | ||
}.bind(this)); | ||
return nextState; | ||
}, | ||
getDependencies: function(){ | ||
getWatchedState: function() { | ||
return { | ||
selectedHobby: 'hobbies.selected', | ||
imOnline: 'online' | ||
'hobbies': { | ||
alias: 'hobbiesContext', | ||
}, | ||
'online': { | ||
alias: 'imOnline' | ||
} | ||
} | ||
}, | ||
Person: function(){ | ||
return new PersonModel(this.personStateChangedHandler).apply(this, arguments); | ||
imOnline: { | ||
kind:'pseudo', | ||
get: function(){ | ||
return this.state.imOnline; | ||
} | ||
}, | ||
Person: function(personState, init){ | ||
return new PersonModel(this.personStateChangedHandler)(personState, init); | ||
}, | ||
personStateChangedHandler: function(nextState, prevState/*, callback*/){ | ||
var persons = {}; | ||
persons.collection = this.collection.map(function(person){ | ||
if(person.id === nextState.id){ | ||
persons.selected = this.Person(nextState, person, true); | ||
return persons.selected; | ||
persons.selectedPerson = this.Person(nextState); | ||
return persons.selectedPerson; | ||
} | ||
return person; | ||
}.bind(this)); | ||
this.setState(persons); | ||
}, | ||
selected: { | ||
selectedHobby: { | ||
kind: 'pseudo', | ||
get: function() { | ||
return this.state.hobbiesContext.current ? this.state.hobbiesContext.current.name: void(0); | ||
} | ||
}, | ||
selectedPerson: { | ||
kind: 'instance', | ||
get: function() { return this.state.selected; } | ||
get: function() { return this.state.selectedPerson; } | ||
}, | ||
@@ -56,13 +73,9 @@ | ||
select: function(id){ | ||
var nextState = {}; | ||
nextState.collection = this.collection.map(function(person){ | ||
if(person.id === id){ | ||
nextState.selected = this.Person(person, true); | ||
return nextState.selected; | ||
selectPerson: function(id){ | ||
for (var i = this.collection.length - 1; i >= 0; i--) { | ||
if(this.selectedPerson.id !== id && this.collection[i].id === id){ | ||
this.setState({ selectedPerson: this.collection[i] }); | ||
break; | ||
} | ||
return person; | ||
}.bind(this)); | ||
this.setState(nextState); | ||
}; | ||
}, | ||
@@ -76,5 +89,3 @@ | ||
name = value.split(' '); | ||
//Cannot initialize by passing in calculated prop value | ||
//i.e. fullname | ||
nextState.selected = this.Person({ | ||
nextState.selectedPerson = this.Person({ | ||
firstName: name[0], | ||
@@ -84,3 +95,3 @@ lastName: name.slice(1).join(' ') | ||
nextState.collection = this.collection.slice(0); | ||
nextState.collection = nextState.collection.concat(nextState.selected); | ||
nextState.collection = nextState.collection.concat(nextState.selectedPerson); | ||
this.setState(nextState); | ||
@@ -95,8 +106,8 @@ } | ||
}); | ||
nextState.selected = void(0); | ||
nextState.selectedPerson = void(0); | ||
if(nextState.collection.length > 0){ | ||
if (this.selected.id === uid){ | ||
nextState.selected = this.Person(nextState.collection[0], true); | ||
if (this.selectedPerson.id === uid){ | ||
nextState.selectedPerson = this.Person(nextState.collection[0]); | ||
} else { | ||
nextState.selected = this.Person(this.selected, true); | ||
nextState.selectedPerson = this.Person(this.selectedPerson); | ||
} | ||
@@ -103,0 +114,0 @@ } |
@@ -19,15 +19,2 @@ /** | ||
/*console.log(Object.isFrozen(this.state.domainDataContext.previousState || {})); | ||
console.log(Object.isFrozen(this.state.domainDataContext)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.state)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.collection)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.selected)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.selected.hobbies)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.state)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.state.collection)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.state.collection[1])); | ||
console.log(Object.isFrozen(this.state.domainDataContext.persons.state.collection[1].hobbies)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.hobbies.state._selectedPerson)); | ||
console.log(Object.isFrozen(this.state.domainDataContext.hobbies.state._selectedPerson.hobbies));*/ | ||
return ( | ||
@@ -34,0 +21,0 @@ <div> |
@@ -13,3 +13,3 @@ /** | ||
render: function() { | ||
if(!this.props.appContext.persons.selected){ | ||
if(!this.props.appContext.persons.selectedPerson){ | ||
return <div>There's nobody here!!</div>; | ||
@@ -16,0 +16,0 @@ } |
@@ -13,15 +13,15 @@ /** | ||
updateName: function(e){ | ||
this.props.appContext.persons.selected.fullName = e.target.value; | ||
this.props.appContext.persons.selectedPerson.fullName = e.target.value; | ||
}, | ||
updateOccupation: function(e){ | ||
this.props.appContext.persons.selected.occupation = e.target.value; | ||
this.props.appContext.persons.selectedPerson.occupation = e.target.value; | ||
}, | ||
updateGender: function(e){ | ||
this.props.appContext.persons.selected.gender = e.target.value; | ||
this.props.appContext.persons.selectedPerson.gender = e.target.value; | ||
}, | ||
updateDOB: function(e){ | ||
this.props.appContext.persons.selected.dob = e.target.value; | ||
this.props.appContext.persons.selectedPerson.dob = e.target.value; | ||
}, | ||
render: function() { | ||
var current = this.props.appContext.persons.selected; | ||
var current = this.props.appContext.persons.selectedPerson; | ||
@@ -28,0 +28,0 @@ return ( |
@@ -13,3 +13,5 @@ /** | ||
handleSelection: function(uid, e){ | ||
this.props.appContext.hobbies.select(uid); | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
this.props.appContext.hobbies.selectHobby(uid); | ||
}, | ||
@@ -22,17 +24,20 @@ addHobby: function(value, e){ | ||
}, | ||
updateName: function(e){ | ||
this.props.appContext.hobbies.current.name = e.target.value; | ||
}, | ||
render: function() { | ||
var app = this.props.appContext; | ||
var collection = this.props.appContext.hobbies.hobbies; | ||
var current = this.props.appContext.hobbies.selected; | ||
var current = this.props.appContext.hobbies.current; | ||
var list = collection.map(function(hobby){ | ||
if(current === hobby){ | ||
if(current && (current.id === hobby.id)){ | ||
return ( | ||
<a | ||
onClick={this.handleSelection.bind(this, hobby)} | ||
key={hobby} | ||
onClick={this.handleSelection.bind(this, hobby.id)} | ||
key={hobby.id} | ||
href="#" | ||
className="list-group-item active"> | ||
{hobby} | ||
<DeleteButton funcDelete={this.deleteHobby.bind(this, hobby)} /> | ||
{hobby.name} | ||
<DeleteButton funcDelete={this.deleteHobby.bind(this, hobby.id)} /> | ||
</a> | ||
@@ -43,8 +48,8 @@ ); | ||
<a | ||
onClick={this.handleSelection.bind(this, hobby)} | ||
key={hobby} | ||
onClick={this.handleSelection.bind(this, hobby.id)} | ||
key={hobby.id} | ||
href="#" | ||
className="list-group-item"> | ||
{hobby} | ||
<DeleteButton funcDelete={this.deleteHobby.bind(this, hobby)} /> | ||
{hobby.name} | ||
<DeleteButton funcDelete={this.deleteHobby.bind(this, hobby.id)} /> | ||
</a> | ||
@@ -59,2 +64,4 @@ ); | ||
{this.props.appContext.hobbies.busyText} | ||
<input key={current ? current.id:-1} className="form-control" type="text" value={current ? current.name : ''} | ||
onChange={this.updateName} placeholder="Select hobby to update"/> | ||
<div className="list-group"> | ||
@@ -61,0 +68,0 @@ {list} |
@@ -13,3 +13,5 @@ /** | ||
handleSelection: function(uid, e){ | ||
this.props.appContext.persons.select(uid); | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
this.props.appContext.persons.selectPerson(uid); | ||
}, | ||
@@ -22,3 +24,3 @@ deletePerson: function(uid, e){ | ||
var collection = this.props.appContext.persons.collection; | ||
var current = this.props.appContext.persons.selected; | ||
var current = this.props.appContext.persons.selectedPerson; | ||
var selectedHobby = !!this.props.appContext.persons.selectedHobby ? " is " + this.props.appContext.persons.selectedHobby : ""; | ||
@@ -25,0 +27,0 @@ |
'use strict'; | ||
var DataService = { | ||
getData: function() { | ||
getPersonData: function() { | ||
return [{id:'1', firstName:'Frank', lastName: "Smith", gender:'male', dob:'1980-03-03', occupation:'dentist', hobbies: ['reading', 'golfing', 'computer programming']}, | ||
{id:'2', firstName:'Lisa', lastName: "Jones", gender:'female', dob:'1985-02-22', occupation:'accountant', hobbies: ['reading']}, | ||
{ firstName: "John", lastName: "Citizen", gender:'male', dob:'1975-12-11', occupation:'unemployed', hobbies: ['watching TV']}]; | ||
} | ||
{id:'3', firstName: "John", lastName: "Citizen", gender:'male', dob:'1975-12-11', occupation:'unemployed', hobbies: ['watching TV']}]; | ||
}, | ||
getHobbiesData: function(uid) { | ||
var hobbies = [ | ||
{ | ||
id:'1', | ||
hobbies: [{id:'1', name: 'reading'}, {id:'2', name: 'golfing'}, {id:'3', name: 'computer programming'}] | ||
}, | ||
{ | ||
id:'2', | ||
hobbies: [{id:'1', name: 'reading'}] | ||
}, | ||
{ | ||
id:'3', | ||
hobbies: [{id:'1', name: 'watching TV'}] | ||
}]; | ||
var personHobbies = hobbies.filter(function(person){ | ||
return person.id === uid; | ||
}); | ||
return personHobbies[0] ? personHobbies[0].hobbies : []; | ||
} | ||
}; |
{ | ||
"name": "imvvm", | ||
"description": "Immutable MVVM for React", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"keywords": [ | ||
@@ -6,0 +6,0 @@ "imvvm", |
661
README.md
@@ -6,2 +6,19 @@ IMVVM | ||
## Introduction | ||
IMVVM is designed to compliment the [React](http://facebook.github.io/react/) library. A Facebook and Instagram collaboration. | ||
React is: | ||
> A Javascript Library for building User Interfaces | ||
To further quote the website, "people use React as the 'V' in MVC". Well I thought, why not use React as the 'V' in MVVM, and keeping inline with React's philosophy on immutability, lets make it use immutable data. So I created IMVVM. | ||
This is my take on MVVM and immutability using javascript. IMVVM tries to resemble a React application. So you not only feel like you developing in the same environment, but if your coming from React, you should be able to pick this up quickly. | ||
Anyway, download it, run the example application and try it out. Maybe avoid production environments for the moment. Be sure to raise issues in the Issues Register as you encounter them and please feel free to create pull requests. | ||
Thanks. | ||
___TODO:___ | ||
Write some tests. I know, they should have already been done... | ||
## Usage | ||
@@ -44,15 +61,7 @@ | ||
uuid: function () { | ||
var i, random; | ||
var uuid = ''; | ||
for (i = 0; i < 32; i++) { | ||
random = Math.random() * 16 | 0; | ||
if (i === 8 || i === 12 || i === 16 || i === 20) { | ||
uuid += '-'; | ||
} | ||
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) | ||
.toString(16); | ||
} | ||
return uuid; | ||
getInitialState: function(){ | ||
return { | ||
age: this.calculateAge(this.dob), | ||
id: this.id ? this.id : this.uuid() | ||
}; | ||
}, | ||
@@ -62,3 +71,3 @@ | ||
get: function(){ | ||
return this.state.id ? this.state.id : this.uuid(); | ||
return this.state.id; | ||
} | ||
@@ -81,6 +90,16 @@ }, | ||
set: function(newValue){ | ||
this.setState({'dob': newValue}); | ||
var age; | ||
if(newValue.length === 10){ | ||
age = this.calculateAge(newValue); | ||
} | ||
if(newValue.length === 0){ | ||
age = 'Enter your Birthday'; | ||
} | ||
this.setState({ | ||
dob: newValue, | ||
age: age | ||
}); | ||
} | ||
}, | ||
gender: { | ||
@@ -95,5 +114,40 @@ get: function(){ | ||
age: { | ||
get: function(){ | ||
return this.state.age; | ||
} | ||
}, | ||
calculateAge: function(dob){ | ||
var DOB = new Date(dob); | ||
var ageDate = new Date(Date.now() - DOB.getTime()); | ||
var age = Math.abs(ageDate.getFullYear() - 1970); | ||
return Number.isNaN(age) ? 'Enter your Birthday' : age + ' years old'; | ||
}, | ||
uuid: function () { | ||
var i, random; | ||
var uuid = ''; | ||
for (i = 0; i < 32; i++) { | ||
random = Math.random() * 16 | 0; | ||
if (i === 8 || i === 12 || i === 16 || i === 20) { | ||
uuid += '-'; | ||
} | ||
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) | ||
.toString(16); | ||
} | ||
return uuid; | ||
}, | ||
}); | ||
``` | ||
#####getInitialState() | ||
The `getInitialState` function initializes any fields that require a value on creation. Such values include, default values and calculated fields. This function has access to state that has been supplied during initializtion, and therefore `this` will be able to refer to the state. | ||
#####name | ||
`name` is a simple field descriptor. It defines a getter and setter. The getter returns state that resides in the state's property of the same name. The setter uses the passed in value to transition to the model to the next state, by passing the value, as an object to the setState function. This will then notify any ViewModels that have registered a stateChangeHandler with this model. | ||
#####age | ||
`age` is a calculated field. It is set by setting `dob`. `dob` uses the calculateAge function to set the value of age and passes both the dob and age values to setState. Please note that `age` needed to be initialized in the getInitialState() function. | ||
### Create a ViewModel | ||
@@ -103,24 +157,88 @@ | ||
var PersonsViewModel = IMVVM.createViewModel({ | ||
select: function(id){ | ||
getInitialState: function(){ | ||
var nextState = {}; | ||
nextState.collection = this.collection.map(function(person){ | ||
if(person.id === id){ | ||
nextState.selected = this.Person(person); | ||
return nextState.selected; | ||
nextState.collection = DataService.getData().map(function(person, idx){ | ||
if (idx === 0){ | ||
nextState.selectedPerson = this.Person(person, true); | ||
return nextState.selectedPerson; | ||
} | ||
return this.Person(person, true); | ||
}.bind(this)); | ||
return nextState; | ||
}, | ||
getWatchedState: function() { | ||
return { | ||
'hobbies': { | ||
alias: 'hobbiesContext', | ||
}, | ||
'online': { | ||
alias: 'imOnline' | ||
} | ||
} | ||
}, | ||
imOnline: { | ||
kind:'pseudo', | ||
get: function(){ | ||
return this.state.imOnline; | ||
} | ||
}, | ||
selectedPerson: { | ||
kind: 'instance', | ||
get: function() { return this.state.selectedPerson; } | ||
}, | ||
collection: { | ||
kind: 'array', | ||
get: function(){ return this.state.collection; }, | ||
}, | ||
personStateChangedHandler: function(nextState, prevState){ | ||
var persons = {}; | ||
persons.collection = this.collection.map(function(person){ | ||
if(person.id === nextState.id){ | ||
persons.selectedPerson = this.Person(nextState); | ||
return persons.selectedPerson; | ||
} | ||
return person; | ||
}.bind(this)); | ||
this.setState(nextState); | ||
this.setState(persons); | ||
}, | ||
Person: function(personState, init){ | ||
return new PersonModel(this.personStateChangedHandler)(personState, init); | ||
}, | ||
selectedHobby: { | ||
kind: 'pseudo', | ||
get: function() { | ||
return this.state.hobbiesContext.current ? this.state.hobbiesContext.current.name: void(0); | ||
} | ||
}, | ||
selectPerson: function(id){ | ||
for (var i = this.collection.length - 1; i >= 0; i--) { | ||
if(this.selectedPerson.id !== id && this.collection[i].id === id){ | ||
this.setState({ selectedPerson: this.collection[i] }); | ||
break; | ||
} | ||
}; | ||
}, | ||
addPerson: function(value){ | ||
var nextState = {}; | ||
var name; | ||
if(value && value.length > 0){ | ||
nextState.selected = this.Person({ | ||
name: value | ||
}); | ||
name = value.split(' '); | ||
nextState.selectedPerson = this.Person({ | ||
firstName: name[0], | ||
lastName: name.slice(1).join(' ') | ||
}, true); | ||
nextState.collection = this.collection.slice(0); | ||
nextState.collection = nextState.collection.concat(nextState.selected); | ||
nextState.collection = nextState.collection.concat(nextState.selectedPerson); | ||
this.setState(nextState); | ||
@@ -135,8 +253,8 @@ } | ||
}); | ||
nextState.selected = void(0); | ||
nextState.selectedPerson = void(0); | ||
if(nextState.collection.length > 0){ | ||
if (this.selected.id === uid){ | ||
nextState.selected = this.Person(nextState.collection[0]); | ||
if (this.selectedPerson.id === uid){ | ||
nextState.selectedPerson = this.Person(nextState.collection[0]); | ||
} else { | ||
nextState.selected = this.Person(this.selected); | ||
nextState.selectedPerson = this.Person(this.selectedPerson); | ||
} | ||
@@ -146,61 +264,139 @@ } | ||
}, | ||
getInitialState: function(){ | ||
var nextState = {}; | ||
nextState.collection = DataService.getData().map(function(person, idx){ | ||
if (idx === 0){ | ||
nextState.selected = this.Person(person); | ||
return nextState.selected; | ||
} | ||
return this.Person(person, false); | ||
}.bind(this)); | ||
return nextState; | ||
}, | ||
}); | ||
personStateChangedHandler: function(nextState, prevState){ | ||
var persons = {}; | ||
persons.collection = this.collection.map(function(person){ | ||
if(person.id === nextState.id){ | ||
persons.selected = this.Person(nextState, person); | ||
return persons.selected; | ||
``` | ||
#####getInitialState() | ||
The `getInitialState` function initializes any fields that require a value and | ||
initialize models to establish the data for the Viewmodel. Unlike Models, the ViewModel does not have access to `state`. | ||
#####getWatchedState() | ||
The `getWatchedState` function establises links between ViewModels. What is happening in this instance, is that we are watching the hobbies data context and refering to it, within this viewModel, with the alias of hobbiesContext. So we are then able to refer to is from teh state object as `this.state.hobbiesContext...`. Enabling us to display fields from other ViewModels and also update thier values. | ||
We can also reference fields from the DomainViewModel. We simply specify the field name and supply an alias. Then it can be refered to within the state object. | ||
#####imOnline | ||
The `imOnline` field is referencing the linked DomainModel field `online` with the alias of `imOnline`. It is simply forwarding the online value it recieves from the DomainViewModel to the View and has no influence over its output. It is for this reason that the field descriptor has a `kind` decorator of `pseudo`. Any field that obtains its value from elsewhere, should be flagged with the decorator `kind:'pseudo'`, unless it is an `instance` or `array`. | ||
#####selectedPerson | ||
`selectedPerson` is another field that has a `kind` decorator. The `selectedPerson` field is of kind `instance`. Which simply means that it will return a model instance. | ||
#####collection | ||
`collection` has a `kind` decorator of `array`. Yes, you guessed it, because it returns an array. | ||
_The reason that the kindd decorator is used is because IMVVM does some extra processing with these types._ | ||
#####personStateChangedHandler | ||
`personStateChangedHandler` will be invoked anytime `setState` is called within the model that it is registered with. This function processes the change in the ViewModel and notifies the DomainModel so that the DomainModel can transition to the next state. | ||
#####Person | ||
`Person` is a wrapper around the `PersonModel` constructor. It makes `PersonModel` easier to invoke and acts like a factory of sorts. The parameters passed to `Person` are passed directly to the `PersonModel` constructor, but not before registering the stateChangedHandler `personStateChangedHandler`, to ensure that all state changes in PersonModel and handled approapriately. | ||
#####selectPerson | ||
#####addPerson | ||
#####deletePerson | ||
These functions are exposed to the View and enaable tasks to be performed in the ViewModel. However, most interactions will occur via the `selectedPerson` which exposes the model instance to the View. | ||
I've added a snippet of HobbiesViewModel, from the example application to explain a little more about `getWatchedState`. | ||
__HobbiesViewModel snippet__ | ||
```javascript | ||
var HobbiesViewModel = IMVVM.createViewModel({ | ||
getWatchedState: function() { | ||
return { | ||
'persons': { | ||
alias: 'personsContext', | ||
fields: { | ||
'selectedPerson': this.onPersonChangedHandler | ||
} | ||
}, | ||
'busy': { | ||
alias: 'busy' | ||
} | ||
return person; | ||
}.bind(this)); | ||
this.setState(persons); | ||
} | ||
}, | ||
Person: function(){ | ||
return new PersonModel(this.personStateChangedHandler).apply(this, arguments); | ||
onPersonChangedHandler: function(nextState, prevState, field, context){ | ||
if(this.current !== void(0) && context === 'persons' && | ||
nextState.id !== prevState.id){ | ||
return { hobbies: { current: void(0) }, busy: false }; | ||
} | ||
}, | ||
... | ||
collection: { | ||
get: function(){ | ||
return this.state.collection; | ||
}, | ||
}, | ||
selectHobby: function(id){ | ||
for (var i = this.hobbies.length - 1; i >= 0; i--) { | ||
if ((this.current === void(0) || this.current.id !== id) && this.hobbies[i].id === id){ | ||
this.setState({current: this.Hobby(this.hobbies[i])}, {busy: true}); | ||
/* | ||
//OR use a callback | ||
this.setState({current: this.Hobby(this.hobbies[i])}, function(){ | ||
this.setState(void(0), {busy: true}); | ||
}.bind(this)); | ||
*/ | ||
selected: { | ||
get: function() { | ||
return this.state.selected; | ||
} | ||
break; | ||
} | ||
}; | ||
}, | ||
}); | ||
... | ||
}); | ||
``` | ||
### Create a DomainModel | ||
#####getWatchedState() | ||
Notice in this instance of the `getWatchedState` function the `persons` object has an extra property called fields. This specifies that we are not only linking to the `persons` data context, but we would also like to be notified when `persons.selectedPerson` changes, and if it does change trigger the `onPersonChanged` handler. | ||
#####onPersonChangedHandler | ||
`onPersonChangedHandler` will be triggered whenever any of the fields that it is assigned to changes. It is important to note that, if there is a change a state object is returned. If there is no change, return either void(0) or don't return anything. It should also be noted that the returned state object has a property with the data context, `hobbies`, and the associated object next state. This is necessary so that any data context or domain property can be updated from this ViewModel by simply specfying it in the returned object. | ||
#####selectHobby | ||
The `selectHobby` function is nothing special. What is different about it is the 'setState' function. ViewModels get an extra parameter, which enables the ViewModel to update state in other data contexts. The second parameter takes a state object, not dissimilar to the state object that is returned from `onPersonChangedHandler`. The second parameter accepts an object that specifies the data context\domain property and associated state. | ||
For instance `this.setState({current: this.Hobby(this.hobbies[i])}, {busy: true});`. The first parameter is the next state for `hobbies` data context, the second parameter specifies that `busy`, in the domain data context shold be changed to `true`. This second parameter also accepts `{person: {selectedPerson: firstName: 'Fred'}}`. | ||
Also noted in the comments is that this can be achieved with a callback, ensuring to pass `void(0)` as the first parameter to `setState`. | ||
***n.b. You may have noticed that not all fields have setters, however, the values are still able to be updated. This is what `setState` does. You only need to supply a setter if you would like the View to update the field directly.*** | ||
### Create a DomainViewModel | ||
```javascript | ||
var DomainModel = IMVVM.createDomainModel({ | ||
var DomainViewModel = IMVVM.createDomainViewModel({ | ||
getInitialState: function(){ | ||
return { | ||
online: true | ||
online: true, | ||
busy: false | ||
}; | ||
}, | ||
undo: function(){ | ||
this.setState(this.previousState); | ||
persons: { | ||
viewModel: PersonsViewModel, | ||
get: function(){ | ||
return this.state.persons; | ||
} | ||
}, | ||
hobbies: { | ||
viewModel: HobbiesViewModel, | ||
get: function(){ | ||
return this.state.hobbies; | ||
} | ||
}, | ||
personCount: { | ||
kind:'pseudo', | ||
get: function(){ | ||
return this.persons ? this.persons.collection.length : 0; | ||
} | ||
}, | ||
busy: { | ||
get: function(){ | ||
return this.state.busy; | ||
}, | ||
}, | ||
online: { | ||
@@ -215,14 +411,12 @@ get: function(){ | ||
getDomainDataContext: function(){ | ||
return { | ||
persons: { | ||
viewModel: PersonsViewModel, | ||
dependsOn: [{property: 'online', alias: 'imOnline'}] | ||
} | ||
}; | ||
} | ||
}); | ||
``` | ||
#####getInitialState() | ||
The 'getInitialState' is the same as for the ViewModel. | ||
#####persons | ||
The `persons` property is setting up a data context called 'persons'. It has a special decorator called `viewModel` which specifies which ViewModel is associated to this data context. | ||
### Hook up the View | ||
Once you have created your Models. ViewModels and DomainViewModel, you're ready to hook it up the the View. All you need to do is specify the mixin and IMVVM will attach a `domainDataContext` to the state object that will be kept in sync with you're ViewModel. | ||
```javascript | ||
@@ -236,5 +430,5 @@ var ApplicationView = React.createClass({ | ||
<div> | ||
<NavBarView appContext={this.state.applicationDataContext} /> | ||
<SideBarView appContext={this.state.applicationDataContext} /> | ||
<DetailsView appContext={this.state.applicationDataContext} /> | ||
<NavBarView appContext={this.state.domainDataContext} /> | ||
<SideBarView appContext={this.state.domainDataContext} /> | ||
<DetailsView appContext={this.state.domainDataContext} /> | ||
</div> | ||
@@ -246,7 +440,32 @@ ); | ||
The 'domainDataContext' has all the properties that were specified in the DomainViewModel. So this 'domainDataContext' will have the following properties: | ||
- busy | ||
- online | ||
- personCount | ||
- persons (dataContext) | ||
+ imOnline | ||
+ selectedPerson | ||
* [model properties and functions...] | ||
+ collection | ||
+ selectedHobby | ||
+ selectPerson | ||
+ addPerson | ||
+ deletePerson | ||
- hobbies (dataContext) | ||
+ [viewmodel properties and functions...] | ||
### Render the View | ||
Then you just render the View, specifying a `domainModel` prop, that references the 'DomainViewModel'. | ||
```javascript | ||
React.renderComponent(<ApplicationView domainModel={DomainModel}/>, document.getElementById('container')); | ||
React.renderComponent(<ApplicationView domainModel={DomainViewModel}/>, | ||
document.getElementById('container')); | ||
``` | ||
***A word on how to use the exposed Model -> selectedPerson*** | ||
As mentioned above, the `PersonsViewModel` has a `persons` data context. This data context exposes a `Person` model instance via the `selectedPerson` property. | ||
To update the `Person` instance, simply assign values to the associated properties on the `selectedPerson` property. | ||
__FormView example__ | ||
@@ -264,2 +483,5 @@ ```javascript | ||
}, | ||
updateDOB: function(e){ | ||
this.props.appContext.persons.selected.dob = e.target.value; | ||
}, | ||
render: function() { | ||
@@ -269,16 +491,17 @@ var current = this.props.appContext.persons.selected; | ||
<div key={current.id}> | ||
<form className="form-horizontal" role="form"> | ||
<div className="form-group"> | ||
<label className="col-md-2 control-label">Name</label> | ||
<div className="col-md-3"> | ||
<input className="form-control" type="text" value={current.name} | ||
<form role="form"> | ||
<div> | ||
<label>Name</label> | ||
<div> | ||
<input type="text" value={current.name} | ||
onChange={this.updateName} /> | ||
</div> | ||
</div> | ||
<div className="form-group"> | ||
<label className="col-md-2 control-label">Gender</label> | ||
<div className="col-md-3"> | ||
<div className="radio"> | ||
<div> | ||
<label>Gender</label> | ||
<div> | ||
<div> | ||
<label> | ||
<input type="radio" onChange={this.updateGender} value="male" | ||
<input type="radio" onChange={this.updateGender} | ||
value="male" | ||
checked={current.gender === 'male'} /> | ||
@@ -288,5 +511,6 @@ Male | ||
</div> | ||
<div className="radio"> | ||
<div> | ||
<label> | ||
<input type="radio" onChange={this.updateGender} value="female" | ||
<input type="radio" onChange={this.updateGender} | ||
value="female" | ||
checked={current.gender === 'female'} /> | ||
@@ -298,6 +522,6 @@ Female | ||
</div> | ||
<div className="form-group"> | ||
<label className="col-md-2 control-label">Birthday</label> | ||
<div className="col-md-3"> | ||
<input className="form-control" type="text" | ||
<div> | ||
<label>Birthday</label> | ||
<div> | ||
<input type="text" | ||
placeholder="yyyy-mm-dd" | ||
@@ -308,2 +532,10 @@ value={current.dob} | ||
</div> | ||
<div className="form-group"> | ||
<label>Age</label> | ||
<div> | ||
<div> | ||
{current.age} | ||
</div> | ||
</div> | ||
</div> | ||
</form> | ||
@@ -331,4 +563,4 @@ </div> | ||
___ | ||
#####function createDomainModel(object specification) | ||
Creates a DomainModel object. | ||
#####function createDomainViewModel(object specification) | ||
Creates a DomainViewModel object. | ||
@@ -356,3 +588,3 @@ *parameters* | ||
___ | ||
#####void setState(object nextState[, function callback]) | ||
#####void setState(object nextState[, oject nextDomainState, function callback]) | ||
Transition Data Context to the next state. | ||
@@ -363,5 +595,23 @@ | ||
__nextState__ | ||
Object containing the next state values. | ||
__nextDomainState__ ***n.b. This parameter is only available in ViewModels*** | ||
Object containing the next state values which are to be passed to the Domain Data Context. | ||
___example___ | ||
```javascript | ||
//From PersonsViewModel | ||
{ | ||
hobbies: { current: void(0) }, | ||
busy: false | ||
} | ||
``` | ||
__callback__ | ||
_Available in:_ DomainModel, ViewModel, Model | ||
Used to do sequential setState calls. | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
___ | ||
@@ -374,40 +624,68 @@ #####object extend(object currentState[, object... nextState]) | ||
__currentState__ | ||
Old state object. | ||
__nextState__ | ||
_Available in:_ DomainModel, ViewModel, Model | ||
Next state objects. | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
####Properties | ||
___ | ||
#####state | ||
Holds object state. | ||
_Available in:_ DomainModel, ViewModel, Model | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
___ | ||
#####previousState | ||
Holds Domain Data Context previous state. | ||
_Available in:_ DomainModel | ||
_Available in:_ DomainViewModel | ||
###Specification | ||
####Hooks | ||
####Functions | ||
___ | ||
#####function getDomainDataContext() | ||
_Available in:_ DomainModel | ||
_Optional:_ false | ||
___ | ||
#####object getInitialState() | ||
Called when initalized in order to initialize state. Returns a state object; | ||
_Available in:_ DomainModel, ViewModel | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
_Optional:_ true | ||
___ | ||
#####object getInitialCalculatedState(object nextState, object previousState) | ||
#####object getWatchedState() | ||
Provides watched items to domain. Returns a WatchedState object definition. | ||
*arguments* | ||
######Watching ViewModel state | ||
dataContext\: \{ | ||
[alias]\: preferredName, | ||
[fields]\: \{ | ||
dataContextField\: ViewModelStateChangedHandler | ||
\} | ||
\} | ||
__nextState__ | ||
######Watching DomainViewModel state | ||
field\: \{ | ||
alias\: preferredName | ||
\} | ||
__previousState__ | ||
___example___ | ||
```javascript | ||
getWatchedState: function() { | ||
return { | ||
'persons': { | ||
alias: 'personsContext', | ||
fields: { | ||
'selected': this.onPersonChange | ||
} | ||
}, | ||
'busy': { | ||
alias: 'busy' | ||
} | ||
} | ||
}, | ||
``` | ||
_Available in:_ DomainModel, ViewModel, Model | ||
_Available in:_ ViewModel | ||
@@ -417,3 +695,3 @@ _Optional:_ true | ||
___ | ||
#####object getValidState(object nextState, object previousState) | ||
#####object ViewModelStateChangedHandler(object nextState, object previousState, string field, string dataContext) | ||
@@ -424,30 +702,30 @@ *arguments* | ||
Next state of the watched field. | ||
__previousState__ | ||
_Available in:_ ViewModel | ||
Previous state of the watched field. | ||
_Optional:_ true | ||
__field__ | ||
####Field Descriptor | ||
___ | ||
#####\* get() | ||
#####void set(\* newValue) | ||
#####pseudo:boolean | ||
#####calculated:boolean | ||
Watched field name. | ||
_Available in:_ DomainModel, ViewModel, Model | ||
__dataContext__ | ||
####DependsOn Properties | ||
___ | ||
***Public*** | ||
This will be made available to the View from this Data Context | ||
Watched Data Context name. | ||
***Private*** | ||
Hidden from View from this Data Context | ||
___example___ | ||
```javascript | ||
onPersonChange: function(nextState, prevState, field, context){ | ||
if(this.current !== void(0) && context === 'persons' && | ||
nextState.id !== prevState.id){ | ||
return { hobbies: { current: void(0) }, busy: false }; | ||
} | ||
} | ||
``` | ||
_Available in:_ ViewModel | ||
####Model State Change Handlers | ||
___ | ||
#####void ModelStateChangeHandler(object nextState,object previousState[, function callback]) | ||
#####void ModelStateChangedHandler(object nextState,object previousState[, function callback]) | ||
@@ -458,12 +736,18 @@ *arguments* | ||
Next state of the model. | ||
__previousState__ | ||
Previous state of the model. | ||
__callback__ | ||
```javascript | ||
personStateChangeHandler: function(nextState, prevState){ | ||
personStateChangedHandler: function(nextState, prevState){ | ||
var persons = {}; | ||
persons.collection = this.collection.map(function(person){ | ||
if(person.id === nextState.id){ | ||
persons.selected = this.Person(nextState, person); | ||
persons.selected = this.Person(nextState, person, true); | ||
return persons.selected; | ||
@@ -474,3 +758,3 @@ } | ||
this.setState(persons); | ||
} | ||
}, | ||
``` | ||
@@ -480,31 +764,104 @@ | ||
####Model Factory Functions | ||
___ | ||
_Definition_ | ||
#####function ModelFactory(){ return new ModelClass(this.ModelStateChangeHandler).apply(this, arguments); } | ||
#####ModelInstance Model([function stateChangedHandler])([object nextState, boolean initialize]) | ||
Returns a new model instance. This function is usually wrapped in another function for easier usage. __n.b. This is suggested usage and not part of the API__ | ||
_Usage_ | ||
__Usage example__ | ||
#####object ModelFactory([object nextState, object previousState, boolean withContext]) | ||
#####object ModelFactory([object nextState, boolean withContext]) | ||
######ModelInstance ModelFactory(object nextState[, boolean initialize:false]) | ||
_Available in:_ ViewModel | ||
```javascript | ||
Person: function(person, init){ | ||
return new PersonModel(this.personStateChangedHandler)(person, init); | ||
}, | ||
``` | ||
*parameters* | ||
__stateChangedHandler__ | ||
State changed handler which is triggered whenever setState is called from within the model. | ||
__nextState__ | ||
Object containing the next state values for the model. | ||
__initialize__ | ||
Boolean value indicating the model instance should be initialized. (Calls getInitialState()) | ||
_Available in:_ ViewModel, Model | ||
####Field | ||
___ | ||
#####fieldName : [Descriptor](#descriptor) | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
___ | ||
####Descriptor | ||
___ | ||
#####Functions | ||
######get() | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
######void set(newValue) | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
#####Decorators | ||
######kind: ['instance' || 'array' || 'pseudo'] | ||
**instance** | ||
Specifies that the getter returns a model instance. | ||
**array** | ||
Specifies that the field holds an array. | ||
**pseudo** | ||
Specifies that the value for this field is obtained from other properties. | ||
_Available in:_ DomainViewModel, ViewModel, Model | ||
######viewModel: ViewModel Class | ||
**viewModel** | ||
Identifies the ViewModel to be used for the defined data context. | ||
_Available in:_ DomainViewModel | ||
###Mixin | ||
####mixin | ||
___ | ||
mixins: [IMVVM.mixin], | ||
#####mixins: [IMVVM.mixin] | ||
React.renderComponent(<ApplicationView | ||
domainModel={DomainModel} | ||
disableUndo={false} />, | ||
Adds domainDataContext to state object. | ||
```javascript | ||
var ApplicationView = React.createClass({ | ||
mixins: [IMVVM.mixin], | ||
render: function(){ | ||
return <DetailsView appContext={this.state.domainDataContext} />; | ||
} | ||
}); | ||
``` | ||
####props | ||
#####domainModel= DomainViewModel Class | ||
#####[enableUndo]= boolean: default = false | ||
**Point the View to the DomainViewModel.** | ||
```javascript | ||
React.renderComponent(<ApplicationView domainModel={DomainViewModel}/>, | ||
document.getElementById('container')); | ||
this.state.applicationDataContext | ||
``` | ||
## Browser Support | ||
Most ECMAScript 5 compliant browsers. | ||
__IE8 and below are not supported__ | ||
__IE8 and below are not supported.__ | ||
@@ -511,0 +868,0 @@ ## Author |
415
src/core.js
@@ -14,178 +14,117 @@ | ||
var ApplicationDataContext, | ||
dependsOn, | ||
appState = {}, | ||
dataContexts = {}, | ||
watchedProps, | ||
watchedPropsLen, | ||
watchList = {}, | ||
dependents = [], | ||
domain, | ||
watchListPropA, | ||
watchListPropB; | ||
links = {}, | ||
watchedDataContexts = {}, | ||
dataContext, | ||
transientState = {}, | ||
processedState = {}, | ||
watchedState, | ||
watchedItem, | ||
watchedProp, | ||
watchedDataContext, | ||
link; | ||
var depProp; | ||
var configure = function(obj, propName){ | ||
var newObj = {}; | ||
for(var k in obj){ | ||
if(obj.hasOwnProperty(k)){ | ||
if(Object.prototype.toString.call(obj[k]) === '[object Object]'){ | ||
newObj[k] = obj[k]; | ||
} else { | ||
newObj[k] = {}; | ||
newObj[k][propName] = obj[k]; | ||
} | ||
} | ||
} | ||
return newObj; | ||
}; | ||
var getDeps = function(nextState, dataContext){ | ||
var dependencies = {}, | ||
props, | ||
watchedValue; | ||
if(!dataContext){ | ||
return {}; | ||
} | ||
dataContext = ('dependsOn' in dataContext) ? dataContext : {dependsOn: dataContext}; | ||
for(var dependency in dataContext.dependsOn){ | ||
if(dataContext.dependsOn.hasOwnProperty(dependency)){ | ||
watchedValue = {}; | ||
props = dataContext.dependsOn[dependency].property.split('.'); | ||
props.forEach(function(prop, idx){ | ||
if(idx === 0){ | ||
watchedValue = nextState[prop]; | ||
} else { | ||
watchedValue = watchedValue ? watchedValue[prop] : void(0); | ||
} | ||
}); | ||
dependencies[dependency] = watchedValue; | ||
} | ||
}; | ||
return dependencies; | ||
}; | ||
var transitionState = function(caller, nextState, prevState, subscribers){ | ||
nextState = nextState || {}; | ||
prevState = prevState || {}; | ||
var processed = false, | ||
tempDeps, | ||
nextVal; | ||
if(caller !== appNamespace){ | ||
nextState[caller] = new dataContexts[caller](nextState[caller], | ||
getDeps(nextState, domain[caller]), prevState[caller]); | ||
} | ||
if(subscribers){ | ||
if(!!dependsOn){ | ||
tempDeps = getDeps(nextState, dependsOn); | ||
nextState = extend(nextState, tempDeps); | ||
for(var depKey in dependsOn){ | ||
if(dependsOn.hasOwnProperty(depKey) && ('onStateChange' in dependsOn[depKey])){ | ||
nextVal = {}; | ||
nextVal[depKey] = nextState[depKey]; | ||
nextState = extend(nextState, dependsOn[depKey].onStateChange(nextVal)); | ||
} | ||
} | ||
} | ||
nextState = new ApplicationDataContext(extend(nextState, tempDeps), prevState, enableUndo); | ||
subscribers.forEach(function(subscriber){ | ||
if(subscriber !== appNamespace){ | ||
nextState[subscriber] = new dataContexts[subscriber](nextState[subscriber], | ||
getDeps(nextState, domain[subscriber]), prevState[subscriber]); | ||
} | ||
}); | ||
} | ||
return nextState; | ||
}; | ||
var appStateChangedHandler = function(caller, newState, callback) { | ||
var appStateChangedHandler = function(caller, newState, newAppState, callback) { | ||
if((newState === void(0) || newState === null || Object.keys(newState).length === 0)){ | ||
return; | ||
} | ||
var nextState = {}, | ||
prevState = {}, | ||
subscribers = [], | ||
newStateKeys, | ||
newStateKeysLen, | ||
subscriberNames, | ||
idxKey, | ||
idxDepFld, | ||
tmpNextState = {}, | ||
changeState, | ||
dependsOnObj; | ||
keyIdx, | ||
transientStateKeysLen, | ||
dataContext, | ||
linkedDataContext, | ||
processedStateKeys = [], | ||
processedStateKeysLen, | ||
watchedField, | ||
subscribers, | ||
subscriber; | ||
if(typeof newAppState === 'function'){ | ||
callback = newAppState; | ||
newAppState = {}; | ||
} | ||
newState = newState || {}; | ||
newStateKeys = Object.keys(newState); | ||
//Check to see if appState is a ready made state object. If so | ||
//pass it straight to the stateChangedHandler. If a callback was passed in | ||
//it would be assigned to newState | ||
if(Object.getPrototypeOf(newState).constructor.classType === "DomainModel") { | ||
//This means previous state has been requested | ||
//so set nextState to the previous state | ||
if(Object.getPrototypeOf(newState).constructor.classType === "DomainViewModel") { | ||
nextState = extend(newState); | ||
//revert the current appState to the previous state of the previous state | ||
prevState = newState.previousState; | ||
} else { | ||
if(caller in watchList){ | ||
newStateKeys = Object.keys(newState); | ||
newStateKeysLen = newStateKeys.length; | ||
subscriberNames = {}; | ||
for (var i = newStateKeysLen - 1; i >= 0; i--){ | ||
if(watchList[caller][newStateKeys[i]]){ | ||
for(var j = watchList[caller][newStateKeys[i]].length - 1; j >= 0; j--){ | ||
subscriberNames[watchList[caller][newStateKeys[i]][j].dataContext] = true; | ||
//Need to reset ViewModel with instance object so that setState is associated with | ||
//the current ViewModel. This reason this ccurs is that when a ViewModel is created it | ||
//assigns a setState function to all instance fields and binds itself to it. When undo is called | ||
//the data is rolled back but viewModels are not recreated and therefore the setState functions | ||
//are associated with outdated viewModels and returns unexpected output. So to realign the ViewModels | ||
//with setState we recreate them. | ||
for(dataContext in domain){ | ||
nextState[dataContext] = new dataContexts[dataContext](nextState); | ||
} | ||
//relink | ||
for(dataContext in domain){ | ||
if(domain.hasOwnProperty(dataContext)){ | ||
for(linkedDataContext in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(linkedDataContext)){ | ||
nextState[dataContext].state[links[dataContext][linkedDataContext]] = | ||
(linkedDataContext in domain) ? extend(nextState[linkedDataContext].state) : | ||
nextState[linkedDataContext]; | ||
} | ||
} | ||
} | ||
} | ||
subscribers = Object.keys(subscriberNames); | ||
hasSubscribers = !!subscribers.length; | ||
} else { | ||
if(!!newStateKeys.length){ | ||
if(caller === appNamespace){ | ||
nextState = extend(newState); | ||
} else { | ||
nextState[caller] = extend(newState); | ||
} | ||
} | ||
transientState = extend(nextState, transientState, newAppState); | ||
transientStateKeys = Object.keys(transientState); | ||
if(transientStateKeys.length === 0){ | ||
return; | ||
} | ||
if(caller !== appNamespace){ | ||
if(typeof callback === 'function'){ | ||
callback(); | ||
return; | ||
} | ||
transientStateKeysLen = transientStateKeys.length - 1; | ||
for (keyIdx = transientStateKeysLen; keyIdx >= 0; keyIdx--) { | ||
if(transientStateKeys[keyIdx] in domain){ | ||
nextState[transientStateKeys[keyIdx]] = extend(appState[transientStateKeys[keyIdx]], transientState[transientStateKeys[keyIdx]]); | ||
nextState[transientStateKeys[keyIdx]] = new dataContexts[transientStateKeys[keyIdx]](nextState); | ||
} else { | ||
nextState[transientStateKeys[keyIdx]] = transientState[transientStateKeys[keyIdx]]; | ||
} | ||
}; | ||
nextState[caller] = newState; | ||
nextState = extend(appState.state, nextState); | ||
processedState = extend(processedState, nextState); | ||
if(hasSubscribers){ | ||
//Triggers | ||
nextState = extend(appState, processedState); | ||
subscribers.forEach(function(sub){ | ||
for(idxKey=newStateKeysLen-1; idxKey >= 0; idxKey--){ | ||
if(watchList[caller][newStateKeys[idxKey]]){ | ||
var depFldArr = watchList[caller][newStateKeys[idxKey]]; | ||
for(idxDepFld = depFldArr.length - 1; idxDepFld >= 0; idxDepFld--){ | ||
dependsOnObj = depFldArr[idxDepFld].dataContext === appNamespace ? dependsOn : | ||
domain[depFldArr[idxDepFld].dataContext].dependsOn; | ||
if(dependsOnObj[depFldArr[idxDepFld].alias].onStateChange){ | ||
tmpNextState[depFldArr[idxDepFld].alias] = nextState[caller][newStateKeys[idxKey]]; | ||
changeState = dependsOnObj[depFldArr[idxDepFld].alias]. | ||
onStateChange.call(appState.state[depFldArr[idxDepFld].dataContext], tmpNextState); | ||
if(Object.prototype.toString.call(changeState) === '[object Object]'){ | ||
if(depFldArr[idxDepFld].dataContext === appNamespace){ | ||
nextState = extend(nextState, changeState); | ||
} else { | ||
nextState[depFldArr[idxDepFld].dataContext] = | ||
extend(nextState[depFldArr[idxDepFld].dataContext], changeState); | ||
} | ||
} | ||
transientState = {}; | ||
for (keyIdx = transientStateKeysLen; keyIdx >= 0; keyIdx--) { | ||
if(transientStateKeys[keyIdx] in watchedDataContexts){ | ||
for(watchedField in watchedDataContexts[transientStateKeys[keyIdx]]){ | ||
if(watchedDataContexts[transientStateKeys[keyIdx]].hasOwnProperty(watchedField)){ | ||
if(newStateKeys.indexOf(watchedField) !== -1){ | ||
subscribers = watchedDataContexts[transientStateKeys[keyIdx]][watchedField]; | ||
for(subscriber in subscribers){ | ||
if(subscribers.hasOwnProperty(subscriber)){ | ||
transientState = extend(transientState, subscribers[subscriber].call(appState[subscriber], | ||
nextState[transientStateKeys[keyIdx]][watchedField], | ||
appState[transientStateKeys[keyIdx]][watchedField], watchedField, transientStateKeys[keyIdx])); | ||
} | ||
@@ -195,20 +134,72 @@ } | ||
} | ||
}); | ||
} | ||
} | ||
} else { | ||
nextState = extend(appState.state, newState); | ||
}; | ||
if(!!Object.keys(transientState).length){ | ||
appStateChangedHandler(void(0), {}, transientState); | ||
return; | ||
} | ||
//Link Phase | ||
processedStateKeys = Object.keys(processedState); | ||
processedStateKeysLen = processedStateKeys.length - 1; | ||
for (keyIdx = processedStateKeysLen; keyIdx >= 0; keyIdx--) { | ||
if(caller === appNamespace){ | ||
if(processedStateKeys[keyIdx] in links[appNamespace]){ | ||
for(dataContext in links[appNamespace][processedStateKeys[keyIdx]]){ | ||
if(links[appNamespace][processedStateKeys[keyIdx]].hasOwnProperty(dataContext)){ | ||
nextState[dataContext].state[links[appNamespace][processedStateKeys[keyIdx]][dataContext]] = nextState[processedStateKeys[keyIdx]]; | ||
} | ||
if(dataContext in links){ | ||
for(dataContext2 in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(dataContext2)){ | ||
nextState[dataContext].state[links[dataContext][dataContext2]] = | ||
(dataContext2 in domain) ? extend(nextState[dataContext2].state) : | ||
nextState[dataContext2]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} else { | ||
if(processedStateKeys[keyIdx] in links){ | ||
for(dataContext in links[processedStateKeys[keyIdx]]){ | ||
if(links[processedStateKeys[keyIdx]].hasOwnProperty(dataContext)){ | ||
nextState[processedStateKeys[keyIdx]].state[links[processedStateKeys[keyIdx]][dataContext]] = | ||
(dataContext in domain) ? extend(nextState[dataContext].state) : | ||
nextState[dataContext]; | ||
} | ||
if(dataContext in links){ | ||
for(dataContext2 in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(dataContext2)){ | ||
nextState[dataContext].state[links[dataContext][dataContext2]] = | ||
(dataContext2 in domain) ? extend(nextState[dataContext2].state) : | ||
nextState[dataContext2]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
prevState = appState; | ||
nextState = transitionState(caller, nextState, appState.state, hasSubscribers ? subscribers : false); | ||
} | ||
if(!!prevState){ | ||
Object.freeze(prevState); | ||
} | ||
//Create a new App state context. | ||
appState = new ApplicationDataContext(nextState, prevState, enableUndo); | ||
//All the work is done! -> Notify the View | ||
//Provided for the main app to return from init() to the View | ||
Object.freeze(appState); | ||
Object.freeze(appState.state); | ||
stateChangedHandler(appState, caller, callback); | ||
//All the work is done! -> Notify the View | ||
stateChangedHandler(appState); | ||
transientState = {}; | ||
processedState = {}; | ||
//Provided for the main app to return to the View | ||
return appState; | ||
@@ -219,55 +210,69 @@ }; | ||
ApplicationDataContext = domainModel.call(this, appStateChangedHandler.bind(this, appNamespace)); | ||
appState = new ApplicationDataContext({}, void(0), enableUndo, true); | ||
dependsOn = appState.getDependencies ? configure(appState.getDependencies(), 'property') : void(0); | ||
if(dependsOn){ | ||
dependents.push(appNamespace); | ||
for(depProp in dependsOn){ | ||
if(dependsOn.hasOwnProperty(depProp)){ | ||
watchedProps = dependsOn[depProp].property.split('.'); | ||
watchedPropsLen = watchedProps.length; | ||
watchListPropA = watchedPropsLen > 1 ? watchedProps[0] : appNamespace; | ||
watchListPropB = watchedPropsLen > 1 ? watchedProps[1] : watchedProps[0]; | ||
watchList[watchListPropA] = watchList[watchListPropA] || {}; | ||
watchList[watchListPropA][watchListPropB] = watchList[watchListPropA][watchListPropB] || []; | ||
if(watchList[watchListPropA][watchListPropB].indexOf(appNamespace) === -1){ | ||
watchList[watchListPropA][watchListPropB].push({dataContext:appNamespace, alias: depProp}); | ||
} | ||
} | ||
/*Need to look at DomainViewModel state and nextState and Domain Model and updating*/ | ||
appState = new ApplicationDataContext(void(0), void(0), enableUndo, true); | ||
appState.state = appState.state || {}; | ||
domain = appState.getDomainDataContext(); | ||
for(dataContext in domain){ | ||
if(domain.hasOwnProperty(dataContext)){ | ||
dataContexts[dataContext] = domain[dataContext].call(this, appStateChangedHandler.bind(this, dataContext)).bind(this, dataContext); | ||
appState.state[dataContext] = new dataContexts[dataContext](appState.state); | ||
if('getWatchedState' in appState[dataContext]){ | ||
watchedState = appState[dataContext].getWatchedState(); | ||
for(watchedItem in watchedState){ | ||
if(watchedState.hasOwnProperty(watchedItem)){ | ||
if(watchedItem in domain || watchedItem in appState.state){ | ||
if('alias' in watchedState[watchedItem]){ | ||
if(!(dataContext in links)){ | ||
links[dataContext] = {}; | ||
} | ||
links[dataContext][watchedItem] = watchedState[watchedItem].alias; | ||
if(!(watchedItem in domain)){ | ||
if(!(appNamespace in links)){ | ||
links[appNamespace] = {}; | ||
} | ||
if(!(dataContext in links[appNamespace])){ | ||
links[appNamespace][watchedItem] = {}; | ||
} | ||
links[appNamespace][watchedItem][dataContext] = watchedState[watchedItem].alias; | ||
} | ||
} | ||
for(watchedProp in watchedState[watchedItem].fields){ | ||
if(watchedState[watchedItem].fields.hasOwnProperty(watchedProp)){ | ||
if(watchedItem in domain){ | ||
watchedDataContext = {}; | ||
if(!(watchedItem in watchedDataContexts)){ | ||
watchedDataContexts[watchedItem] = {}; | ||
} | ||
watchedDataContext[watchedProp] = {}; | ||
watchedDataContext[watchedProp][dataContext] = watchedState[watchedItem].fields[watchedProp]; | ||
watchedDataContexts[watchedItem] = watchedDataContext; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
domain = configure(appState.getDomainDataContext(), 'viewModel'); | ||
for(var dataContext in domain){ | ||
for(dataContext in domain){ | ||
if(domain.hasOwnProperty(dataContext)){ | ||
dataContexts[dataContext] = domain[dataContext].viewModel.call(this, appStateChangedHandler.bind(this, dataContext)); | ||
appState[dataContext] = new dataContexts[dataContext]({}, {}, {}, true); | ||
if(appState[dataContext].getDependencies){ | ||
dependents.push(dataContext); | ||
domain[dataContext].dependsOn = configure(appState[dataContext].getDependencies(), 'property'); | ||
for(depProp in domain[dataContext].dependsOn){ | ||
if(domain[dataContext].dependsOn.hasOwnProperty(depProp)){ | ||
watchedProps = domain[dataContext].dependsOn[depProp].property.split('.'); | ||
watchedPropsLen = watchedProps.length; | ||
watchListPropA = watchedPropsLen > 1 ? watchedProps[0] : appNamespace; | ||
watchListPropB = watchedPropsLen > 1 ? watchedProps[1] : watchedProps[0]; | ||
watchList[watchListPropA] = watchList[watchListPropA] || {}; | ||
watchList[watchListPropA][watchListPropB] = watchList[watchListPropA][watchListPropB] || []; | ||
if(watchList[watchListPropA][watchListPropB].indexOf(dataContext) === -1){ | ||
watchList[watchListPropA][watchListPropB].push({dataContext:dataContext, alias: depProp}); | ||
} | ||
} | ||
} | ||
} | ||
for(link in links[dataContext]){ | ||
if(links[dataContext].hasOwnProperty(link)){ | ||
appState[dataContext].state[links[dataContext][link]] = (link in domain) ? extend(appState[link].state): appState[link]; | ||
} | ||
} | ||
} | ||
} | ||
dependents.forEach(function(dependent){ | ||
if(dependent !== appNamespace){ | ||
appState[dependent] = new dataContexts[dependent](appState[dependent], | ||
getDeps(appState, domain[dependent]), {}); | ||
} | ||
}); | ||
appState = new ApplicationDataContext(extend(appState, getDeps(appState, dependsOn)), void(0), enableUndo); | ||
appState = new ApplicationDataContext(appState, void(0), enableUndo); | ||
Object.freeze(appState.state); | ||
return Object.freeze(appState); | ||
Object.freeze(appState); | ||
return appState; | ||
}; |
var model = require('./imvvmModel'); | ||
var viewModel = require('./imvvmViewModel'); | ||
var domainModel = require('./imvvmDomainModel'); | ||
var domainModel = require('./imvvmDomainViewModel'); | ||
var mixin = require('./mixin'); | ||
@@ -11,9 +11,9 @@ | ||
var ModelBase = function() {}; | ||
var ViewModelBase = function() {}; | ||
var DomainModelBase = function() {}; | ||
var ModelBase = function(){}; | ||
var ViewModelBase = function(){}; | ||
var DomainViewModelBase = function(){}; | ||
mixInto(ModelBase, model.Mixin); | ||
mixInto(ViewModelBase, viewModel.Mixin); | ||
mixInto(DomainModelBase, domainModel.Mixin); | ||
mixInto(DomainViewModelBase, domainModel.Mixin); | ||
@@ -50,18 +50,57 @@ var IMVVMClass = { | ||
/* // Reduce time spent doing lookups by setting these on the prototype. | ||
for (var methodName in IMVVMInterface) { | ||
if (!Constructor.prototype[methodName]) { | ||
Constructor.prototype[methodName] = null; | ||
ConvenienceConstructor.getDescriptor = function(){ | ||
var descriptor = {}, | ||
proto = this.prototype, | ||
viewModels = {}, | ||
autoFreeze = [], | ||
key; | ||
if('__processedObject__' in this.originalSpec){ | ||
return this.originalSpec.__processedObject__; | ||
} | ||
} | ||
*/ | ||
/* | ||
if (__DEV__) { | ||
// In DEV the convenience constructor generates a proxy to another | ||
// instance around it to warn about access to properties on the | ||
// descriptor. | ||
DescriptorConstructor = createDescriptorProxy(Constructor); | ||
}*/ | ||
for(key in this.originalSpec){ | ||
if(this.originalSpec.hasOwnProperty(key)){ | ||
if('get' in this.originalSpec[key] || 'set' in this.originalSpec[key]){ | ||
//assume it is a descriptor | ||
this.originalSpec[key].enumerable = true; | ||
if('viewModel' in this.originalSpec[key]) { | ||
viewModels[key] = this.originalSpec[key].viewModel; | ||
delete this.originalSpec[key].viewModel; | ||
delete this.originalSpec[key].set; | ||
} else if('kind' in this.originalSpec[key]){ | ||
if(this.originalSpec[key].kind === 'pseudo'){ | ||
this.originalSpec[key].enumerable = false; | ||
} else { //'instance' || 'array' | ||
autoFreeze.push({fieldName: key, kind: this.originalSpec[key].kind}); | ||
} | ||
delete this.originalSpec[key].kind; | ||
} | ||
descriptor[key] = this.originalSpec[key]; | ||
} else { | ||
proto[key] = this.originalSpec[key]; | ||
} | ||
} | ||
} | ||
if(!('extend' in proto)){ | ||
proto.extend = utils.extend; | ||
} | ||
if(!!Object.keys(viewModels).length){ | ||
proto.getDomainDataContext = function(){ | ||
return viewModels; | ||
} | ||
} | ||
this.originalSpec.__processedObject__ = { | ||
descriptor: descriptor, | ||
proto: proto, | ||
originalSpec: this.originalSpec || {}, | ||
freezeFields: autoFreeze | ||
}; | ||
return this.originalSpec.__processedObject__; | ||
}; | ||
return ConvenienceConstructor; | ||
@@ -74,3 +113,3 @@ }, | ||
createViewModel: IMVVMClass.createClass.bind(this, ViewModelBase, 'ViewModel'), | ||
createDomainModel: IMVVMClass.createClass.bind(this, DomainModelBase, 'DomainModel'), | ||
createDomainViewModel: IMVVMClass.createClass.bind(this, DomainViewModelBase, 'DomainViewModel'), | ||
mixin: mixin | ||
@@ -77,0 +116,0 @@ }; |
@@ -10,41 +10,18 @@ | ||
var desc = getDescriptor.call(this); | ||
var dataContext = function(nextState, prevState, withContext) { | ||
var desc = this.getDescriptor(this); | ||
desc.stateChangedHandler = stateChangedHandler; | ||
desc.proto.__getDescriptor = function(){ | ||
return desc; | ||
} | ||
var dataContext = function(nextState, initialize) { | ||
var freezeFields = desc.freezeFields; | ||
var model = Object.create(desc.proto, desc.descriptor); | ||
var argCount = arguments.length; | ||
var lastArgIsBool = typeof Array.prototype.slice.call(arguments, -1)[0] === 'boolean'; | ||
var initialize = false; | ||
var freezeFields = desc.freezeFields, | ||
fld, | ||
model = Object.create(desc.proto, desc.descriptor); | ||
if(argCount === 0){ | ||
//defaults | ||
nextState = {}; | ||
prevState = {}; | ||
withContext = false; | ||
} else if(argCount === 1){ | ||
if(lastArgIsBool){ | ||
withContext = nextState; | ||
nextState = {}; | ||
prevState = {}; | ||
} else { | ||
//assume this is a new Object and there is no prevState | ||
prevState = {}; | ||
withContext = false; | ||
initialize = true; | ||
} | ||
} else if(argCount === 2){ | ||
if(lastArgIsBool){ | ||
//assume this is a new Object and there is no prevState | ||
withContext = prevState; | ||
prevState = {}; | ||
initialize = true; | ||
} else { | ||
withContext = false; | ||
} | ||
if(nextState === void(0)){ | ||
initialize = true; | ||
} | ||
nextState = ('state' in nextState) ? nextState.state : nextState; | ||
prevState = ('state' in prevState) ? prevState.state : prevState; | ||
nextState = nextState || {}; | ||
@@ -58,2 +35,4 @@ Object.defineProperty(model, 'state', { | ||
nextState = extend(nextState, model); | ||
if(initialize && ('getInitialState' in model)){ | ||
@@ -63,17 +42,2 @@ nextState = extend(nextState, model.getInitialState.call(model)); | ||
if(withContext){ | ||
//This will self distruct | ||
Object.defineProperty(model, 'context', { | ||
configurable: true, | ||
enumerable: false, | ||
set: function(context){ | ||
this.setState = function(nextState, callback){ //callback may be useful for DB updates | ||
return stateChangedHandler.bind(context) | ||
.call(context, extend(this.state, nextState), this.state, callback); | ||
}.bind(this); | ||
delete this.context; | ||
} | ||
}); | ||
} | ||
Object.defineProperty(model, 'state', { | ||
@@ -87,11 +51,6 @@ configurable: false, | ||
//freeze arrays and model instances | ||
for (var i = freezeFields.length - 1; i >= 0; i--) { | ||
Object.freeze(model[freezeFields[i].fieldName]); | ||
for (fld = freezeFields.length - 1; fld >= 0; fld--) { | ||
Object.freeze(model[freezeFields[fld].fieldName]); | ||
}; | ||
if(!withContext){ | ||
Object.freeze(model); | ||
} | ||
return model; | ||
return Object.freeze(model); | ||
}; | ||
@@ -98,0 +57,0 @@ return dataContext; |
@@ -10,14 +10,14 @@ | ||
var desc = getDescriptor.call(this); | ||
var desc = this.getDescriptor(this); | ||
desc.proto.setState = stateChangedHandler; | ||
var dataContext = function(nextState, dependencies, prevState, initialize) { | ||
var dataContext = function(VMName, appState) { | ||
//nextState has already been extended with prevState in core | ||
nextState = extend(nextState, dependencies); | ||
prevState = prevState || {}; | ||
prevState = ('state' in prevState) ? prevState.state : prevState; | ||
var freezeFields = desc.freezeFields; | ||
var viewModel = Object.create(desc.proto, desc.descriptor); | ||
var nextState = {}, | ||
freezeFields = desc.freezeFields, | ||
fld, | ||
viewModel = Object.create(desc.proto, desc.descriptor), | ||
tempDesc, | ||
tempModel; | ||
@@ -31,4 +31,8 @@ Object.defineProperty(viewModel, 'state', { | ||
if(initialize && ('getInitialState' in viewModel)){ | ||
nextState = extend(nextState, viewModel.getInitialState.call(viewModel)); | ||
if(appState[VMName] === void(0)){ | ||
if('getInitialState' in viewModel){ | ||
nextState = extend(nextState, viewModel.getInitialState.call(viewModel)); | ||
} | ||
} else { | ||
nextState = ('state' in appState[VMName] ? appState[VMName].state : appState[VMName]); | ||
} | ||
@@ -44,23 +48,28 @@ | ||
//freeze arrays and viewModel instances | ||
for (var i = freezeFields.length - 1; i >= 0; i--) { | ||
if(freezeFields[i].kind === 'instance' && | ||
('context' in viewModel[freezeFields[i].fieldName])){ | ||
viewModel[freezeFields[i].fieldName].context = viewModel; | ||
for (fld = freezeFields.length - 1; fld >= 0; fld--) { | ||
if(freezeFields[fld].kind === 'instance'){ | ||
if(viewModel[freezeFields[fld].fieldName]){ | ||
tempDesc = viewModel[freezeFields[fld].fieldName].__getDescriptor(); | ||
tempModel = Object.create(tempDesc.proto, tempDesc.descriptor); | ||
Object.defineProperty(tempModel, 'state', { | ||
configurable: false, | ||
enumerable: false, | ||
writable: false, | ||
value: viewModel[freezeFields[fld].fieldName].state | ||
}); | ||
tempModel.__proto__.setState = function(someState, callback){ //callback may be useful for DB updates | ||
return tempDesc.stateChangedHandler.call(this, | ||
extend(tempModel.state, someState), tempModel.state, callback); | ||
}.bind(viewModel); | ||
Object.freeze(viewModel[freezeFields[fld].fieldName]); | ||
} | ||
} else { | ||
Object.freeze(viewModel[freezeFields[fld].fieldName]); | ||
} | ||
Object.freeze(viewModel[freezeFields[i].fieldName]); | ||
}; | ||
//Add dependencies to viewModel | ||
for(var dep in dependencies){ | ||
if(dependencies.hasOwnProperty(dep) && dep[0] !== '_'){ | ||
Object.defineProperty(viewModel, dep, { | ||
configurable: false, | ||
enumerable: false, | ||
writable: false, | ||
value: dependencies[dep] | ||
}); | ||
} | ||
} | ||
Object.freeze(nextState); | ||
return Object.freeze(viewModel); | ||
@@ -67,0 +76,0 @@ |
@@ -6,26 +6,5 @@ | ||
var mixin = { | ||
stateChangedHandler: function(dataContext, caller, callback){ | ||
this.setState({domainDataContext: dataContext}, function(){ | ||
//send all state back to caller | ||
//useful if you need to know what other parts of the app | ||
//were impacted by your changes. You can also use the returned | ||
//information to display things external to your ApplicationModel | ||
//Allows you to have multiple Application ViewModels in the one app and | ||
//still share the state with other presentation models that may be interested | ||
if(typeof callback === 'function'){ | ||
if(this.state === null || !('domainDataContext' in this.state)){ | ||
callback(void(0)); | ||
} else { | ||
if(caller in this.state.domainDataContext){ | ||
callback(this.state.domainDataContext[caller]); | ||
} else if(caller === NAMESPACE) { | ||
callback(this.state.domainDataContext); | ||
} else { | ||
callback(void(0)); | ||
} | ||
} | ||
} | ||
}.bind(this)); | ||
stateChangedHandler: function(dataContext){ | ||
this.setState({domainDataContext: dataContext}) | ||
}, | ||
getInitialState: function(){ | ||
@@ -36,5 +15,4 @@ var dataContext = core.getInitialState(NAMESPACE, this.props.domainModel, | ||
} | ||
}; | ||
module.exports = mixin; |
var utils = { | ||
getDescriptor: function(){ | ||
var descriptor = {}; | ||
var proto = this.prototype; | ||
var autoFreeze = []; | ||
if('__processedObject__' in this.originalSpec){ | ||
return this.originalSpec.__processedObject__; | ||
} | ||
for(var key in this.originalSpec){ | ||
if(this.originalSpec.hasOwnProperty(key)){ | ||
if('get' in this.originalSpec[key] || 'set' in this.originalSpec[key]){ | ||
//assume it is a descriptor | ||
this.originalSpec[key].enumerable = true; | ||
if('kind' in this.originalSpec[key]){ | ||
if(this.originalSpec[key].kind === 'pseudo'){ | ||
this.originalSpec[key].enumerable = false; | ||
} else { //'instance' || 'array' | ||
autoFreeze.push({fieldName: key, kind: this.originalSpec[key].kind}); | ||
} | ||
delete this.originalSpec[key].kind; | ||
} | ||
descriptor[key] = this.originalSpec[key]; | ||
} else { | ||
proto[key] = this.originalSpec[key]; | ||
} | ||
} | ||
} | ||
if(!('extend' in proto)){ | ||
proto.extend = utils.extend; | ||
} | ||
this.originalSpec.__processedObject__ = { | ||
descriptor: descriptor, | ||
proto: proto, | ||
originalSpec: this.originalSpec || {}, | ||
freezeFields: autoFreeze | ||
}; | ||
return this.originalSpec.__processedObject__; | ||
}, | ||
extend: function () { | ||
@@ -47,0 +5,0 @@ var newObj = {}; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
157979
49
2332
857