Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

imvvm

Package Overview
Dependencies
Maintainers
1
Versions
52
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

imvvm - npm Package Compare versions

Comparing version 0.5.1 to 0.6.0

example/app/scripts/models/hobbyModel.js

2

bower.json
{
"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",

@@ -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

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc