From 734df7b528fc8cec7755c6f76c5c5eeb2b5194aa Mon Sep 17 00:00:00 2001 From: asudoh Date: Thu, 25 Oct 2012 18:10:31 +0900 Subject: [PATCH 1/2] Making additional ModelRefController features mixins. --- EditModelRefController.js | 124 +------------ EditModelRefControllerMixin.js | 200 ++++++++++++++++++++ EditModelRefListControllerMixin.js | 172 +++++++++++++++++ EditStoreRefController.js | 104 +---------- EditStoreRefControllerMixin.js | 139 ++++++++++++++ EditStoreRefListController.js | 30 +-- EditStoreRefListControllerMixin.js | 49 +++++ ListController.js | 203 +------------------- ListControllerMixin.js | 227 +++++++++++++++++++++++ StoreRefController.js | 116 +----------- StoreRefControllerMixin.js | 150 +++++++++++++++ tests/EditModelRefListControllerMixin.js | 79 ++++++++ tests/module.js | 1 + tests/moduleFullSet.js | 1 + 14 files changed, 1046 insertions(+), 549 deletions(-) create mode 100644 EditModelRefControllerMixin.js create mode 100644 EditModelRefListControllerMixin.js create mode 100644 EditStoreRefControllerMixin.js create mode 100644 EditStoreRefListControllerMixin.js create mode 100644 ListControllerMixin.js create mode 100644 StoreRefControllerMixin.js create mode 100644 tests/EditModelRefListControllerMixin.js diff --git a/EditModelRefController.js b/EditModelRefController.js index e2a8aae..11fe6f4 100644 --- a/EditModelRefController.js +++ b/EditModelRefController.js @@ -1,30 +1,9 @@ define([ "dojo/_base/declare", - "dojo/_base/lang", - "./getPlainValue", - "./getStateful", - "./ModelRefController" -], function(declare, lang, getPlainValue, getStateful, ModelRefController){ - // module: - // dojox/mvc/EditModelRefController - - function setRefSourceModel(/*dojox/mvc/EditModelRefController*/ ctrl, /*Anything*/ old, /*Anything*/ current){ - // summary: - // A function called when this controller gets newer value as the data source. - // ctrl: dojox/mvc/EditModelRefController - // The controller. - // old: Anything - // The older value. - // current: Anything - // The newer value. - - if(old !== current){ - ctrl.set(ctrl._refOriginalModelProp, ctrl.holdModelUntilCommit ? current : ctrl.cloneModel(current)); - ctrl.set(ctrl._refEditModelProp, ctrl.holdModelUntilCommit ? ctrl.cloneModel(current) : current); - } - } - - return declare("dojox.mvc.EditModelRefController", ModelRefController, { + "./ModelRefController", + "./EditModelRefControllerMixin" +], function(declare, ModelRefController, EditModelRefControllerMixin){ + return declare("dojox.mvc.EditModelRefController", [ModelRefController, EditModelRefControllerMixin], { // summary: // A child class of dojox/mvc/ModelRefController. // Keeps a copy (originalModel) of given data model (sourceModel) so that it can manage the data model of before/after the edit. @@ -107,98 +86,5 @@ define([ // | data-dojo-props="checked: at('widget:ctrlEdit', 'value')"> // | // | - - // getStatefulOptions: dojox/mvc/getStatefulOptions - // The options to get stateful object from plain value. - getStatefulOptions: null, - - // getPlainValueOptions: dojox/mvc/getPlainValueOptions - // The options to get plain value from stateful object. - getPlainValueOptions: null, - - // holdModelUntilCommit: Boolean - // True not to send the change in model back to sourceModel until commit() is called. - holdModelUntilCommit: false, - - // originalModel: dojo/Stateful - // The data model, that serves as the original data. - originalModel: null, - - // originalModel: dojo/Stateful - // The data model, that serves as the data source. - sourceModel: null, - - // _refOriginalModelProp: String - // The property name for the data model, that serves as the original data. - _refOriginalModelProp: "originalModel", - - // _refSourceModelProp: String - // The property name for the data model, that serves as the data source. - _refSourceModelProp: "sourceModel", - - // _refEditModelProp: String - // The property name for the data model, that is being edited. - _refEditModelProp: "model", - - postscript: function(/*Object?*/ params, /*DomNode|String?*/ srcNodeRef){ - // summary: - // Sets certain properties before setting models. - - for(var s in {getStatefulOptions: 1, getPlainValueOptions: 1, holdModelUntilCommit: 1}){ - var value = (params || {})[s]; - if(typeof value != "undefined"){ - this[s] = value; - } - } - this.inherited(arguments); - }, - - set: function(/*String*/ name, /*Anything*/ value){ - // summary: - // Set a property to this. - // name: String - // The property to set. - // value: Anything - // The value to set in the property. - - if(name == this._refSourceModelProp){ - setRefSourceModel(this, this[this._refSourceModelProp], value); - } - this.inherited(arguments); - }, - - cloneModel: function(/*Anything*/ value){ - // summary: - // Create a clone object of the data source. - // Child classes of this controller can override it to achieve its specific needs. - // value: Anything - // The data serving as the data source. - - var plain = lang.isFunction((value || {}).set) && lang.isFunction((value || {}).watch) ? getPlainValue(value, this.getPlainValueOptions) : value; - return getStateful(plain, this.getStatefulOptions); - }, - - commit: function(){ - // summary: - // Send the change back to the data source. - - this.set(this.holdModelUntilCommit ? this._refSourceModelProp : this._refOriginalModelProp, this.cloneModel(this.get(this._refEditModelProp))); - }, - - reset: function(){ - // summary: - // Change the model back to its original state. - - this.set(this.holdModelUntilCommit ? this._refEditModelProp : this._refSourceModelProp, this.cloneModel(this.get(this._refOriginalModelProp))); - }, - - hasControllerProperty: function(/*String*/ name){ - // summary: - // Returns true if this controller itself owns the given property. - // name: String - // The property name. - - return this.inherited(arguments) || name == this._refOriginalModelProp || name == this._refSourceModelProp; - } }); -}); +}); \ No newline at end of file diff --git a/EditModelRefControllerMixin.js b/EditModelRefControllerMixin.js new file mode 100644 index 0000000..d12d21a --- /dev/null +++ b/EditModelRefControllerMixin.js @@ -0,0 +1,200 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "./getPlainValue", + "./getStateful" +], function(declare, lang, getPlainValue, getStateful){ + // module: + // dojox/mvc/EditModelRefControllerMixin + + function setRefSourceModel(/*dojox/mvc/EditModelRefControllerMixin*/ ctrl, /*Anything*/ old, /*Anything*/ current){ + // summary: + // A function called when this controller gets newer value as the data source. + // ctrl: dojox/mvc/EditModelRefControllerMixin + // The controller. + // old: Anything + // The older value. + // current: Anything + // The newer value. + + if(old !== current){ + ctrl.set(ctrl._refOriginalModelProp, ctrl.holdModelUntilCommit ? current : ctrl.cloneModel(current)); + ctrl.set(ctrl._refEditModelProp, ctrl.holdModelUntilCommit ? ctrl.cloneModel(current) : current); + } + } + + return declare("dojox.mvc.EditModelRefControllerMixin", null, { + // summary: + // A mixin class to dojox/mvc/ModelRefController. + // Keeps a copy (originalModel) of given data model (sourceModel) so that it can manage the data model of before/after the edit. + // description: + // Has two modes: + // + // - Directly reflect the edits to sourceModel (holdModelUntilCommit=false) + // - Don't reflect the edits to sourceModel, until commit() is called (holdModelUntilCommit=true) + // + // For the 1st case, dojo/Stateful get()/set()/watch() interfaces will work with sourceModel. + // For the 2nd case, dojo/Stateful get()/set()/watch() interfaces will work with a copy of sourceModel, and sourceModel will be replaced with such copy when commit() is called. + // example: + // The check box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property on behalf of the model ("model" property in the controller, which comes from "sourceModel" property). + // Two seconds later, the check box changes from unchecked to checked, and the controller saves the state. + // Two seconds later then, the check box changes from checked to unchecked. + // Two seconds later then, the controller goes back to the last saved state, and the check box changes from unchecked to checked as the result. + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // example: + // The controller with "ctrlSource" ID specifies holding changes until commit() is called (by setting true to holdModelUntilCommit). + // As the change in the second check box is committed two seconds later from the change, the first check box is checked at then (when the change is committed). + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | Source: + // | + // | Edit: + // | + // | + // | + + // getStatefulOptions: dojox/mvc/getStatefulOptions + // The options to get stateful object from plain value. + getStatefulOptions: null, + + // getPlainValueOptions: dojox/mvc/getPlainValueOptions + // The options to get plain value from stateful object. + getPlainValueOptions: null, + + // holdModelUntilCommit: Boolean + // True not to send the change in model back to sourceModel until commit() is called. + holdModelUntilCommit: false, + + // originalModel: dojo/Stateful + // The data model, that serves as the original data. + originalModel: null, + + // originalModel: dojo/Stateful + // The data model, that serves as the data source. + sourceModel: null, + + // _refOriginalModelProp: String + // The property name for the data model, that serves as the original data. + _refOriginalModelProp: "originalModel", + + // _refSourceModelProp: String + // The property name for the data model, that serves as the data source. + _refSourceModelProp: "sourceModel", + + // _refEditModelProp: String + // The property name for the data model, that is being edited. + _refEditModelProp: "model", + + postscript: function(/*Object?*/ params, /*DomNode|String?*/ srcNodeRef){ + // summary: + // Sets certain properties before setting models. + + for(var s in {getStatefulOptions: 1, getPlainValueOptions: 1, holdModelUntilCommit: 1}){ + var value = (params || {})[s]; + if(typeof value != "undefined"){ + this[s] = value; + } + } + this.inherited(arguments); + }, + + set: function(/*String*/ name, /*Anything*/ value){ + // summary: + // Set a property to this. + // name: String + // The property to set. + // value: Anything + // The value to set in the property. + + if(name == this._refSourceModelProp){ + setRefSourceModel(this, this[this._refSourceModelProp], value); + } + this.inherited(arguments); + }, + + cloneModel: function(/*Anything*/ value){ + // summary: + // Create a clone object of the data source. + // Child classes of this controller can override it to achieve its specific needs. + // value: Anything + // The data serving as the data source. + + var plain = lang.isFunction((value || {}).set) && lang.isFunction((value || {}).watch) ? getPlainValue(value, this.getPlainValueOptions) : value; + return getStateful(plain, this.getStatefulOptions); + }, + + commit: function(){ + // summary: + // Send the change back to the data source. + + this.set(this.holdModelUntilCommit ? this._refSourceModelProp : this._refOriginalModelProp, this.cloneModel(this.get(this._refEditModelProp))); + }, + + reset: function(){ + // summary: + // Change the model back to its original state. + + this.set(this.holdModelUntilCommit ? this._refEditModelProp : this._refSourceModelProp, this.cloneModel(this.get(this._refOriginalModelProp))); + }, + + hasControllerProperty: function(/*String*/ name){ + // summary: + // Returns true if this controller itself owns the given property. + // name: String + // The property name. + + return this.inherited(arguments) || name == this._refOriginalModelProp || name == this._refSourceModelProp; + } + }); +}); diff --git a/EditModelRefListControllerMixin.js b/EditModelRefListControllerMixin.js new file mode 100644 index 0000000..f134627 --- /dev/null +++ b/EditModelRefListControllerMixin.js @@ -0,0 +1,172 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/Destroyable" +], function(declare, lang, Destroyable){ + var undef; + + function findIndex(/*String*/ idProperty, /*Object*/ model, /*Object*/ cursor){ + if(lang.isArray(model)){ + for(var i = 0, l = model.length; i < l; ++i){ + if(model[i][idProperty] == cursor[idProperty]){ + return i; + } + } + }else{ + for(var s in model){ + if(model[s][idProperty] == cursor[idProperty]){ + return s; + } + } + } + } + + function setRefSourceModel(/*dojox/mvc/EditModelRefListControllerMixin*/ ctrl, /*Anything*/ old, /*Anything*/ current){ + // summary: + // A function called when this controller gets newer value as the data source. + // ctrl: dojox/mvc/EditModelRefControllerMixin + // The controller. + // old: Anything + // The older value. + // current: Anything + // The newer value. + + if(old !== current){ + ctrl._handleRefSourceModel && ctrl._handleRefSourceModel.remove(); + ctrl.own(ctrl._handleRefSourceModel = current.watch(function(name, old, current){ + if(old !== current){ + var index; + index = findIndex(ctrl.idProperty, ctrl[ctrl._refOriginalModelProp], current); + if(index !== undef){ + ctrl[ctrl._refOriginalModelProp].set(index, ctrl.holdModelUntilCommit ? current : ctrl.cloneModel(current)); + } + index = findIndex(ctrl.idProperty, ctrl[ctrl._refEditModelProp], current); + if(index !== undef){ + ctrl[ctrl._refEditModelProp].set(index, ctrl.holdModelUntilCommit ? ctrl.cloneModel(current) : current); + } + } + })); + } + } + + return declare("dojox.mvc.EditModelRefListControllerMixin", Destroyable, { + // summary: + // A mixin class to the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/ListControllerMixin. + // description: + // In addition to what the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/ListControllerMixin does, this class adds an ability to save data (commit) of currently selected list item. + // example: + // The text box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property, from the data model, using the third one in array. + // Two seconds later, the text box changes from "Third" to "3rd", and it's saved for reversion. + // Two seconds later then, the text box changes from "3rd" to "THIRD", and two seconds later then, the text box changes back to "3rd". + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // example: + // The controller with "ctrlSource" ID specifies holding changes until commit() is called (by setting true to holdModelUntilCommit). + // As the change in the second text box is committed two seconds later from the change, the first text box is updated at then (when the change is committed). + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | Source: + // | Edit: + // | + // | + + set: function(/*String*/ name, /*Anything*/ value){ + // summary: + // Set a property to this. + // name: String + // The property to set. + // value: Anything + // The value to set in the property. + + if(name == this._refSourceModelProp){ + setRefSourceModel(this, this[this._refSourceModelProp], value); + } + this.inherited(arguments); + }, + + commitCurrent: function(){ + // summary: + // Send the change of currently selected list item back to the data source. + + var model = this[this.holdModelUntilCommit ? this._refSourceModelProp : this._refOriginalModelProp], + index = findIndex(this.idProperty, model, this.cursor); + if(index !== undef){ + model.set(index, this.cloneModel(this.cursor)); + } + }, + + resetCurrent: function(){ + // summary: + // Change the currently selected list item back to its original state. + + var model = this[this.holdModelUntilCommit ? this._refEditModelProp : this._refSourceModelProp], + originalModel = this[this._refOriginalModelProp], + index = findIndex(this.idProperty, model, this.cursor), + originalModelIndex = findIndex(this.idProperty, originalModel, this.cursor); + if(index !== undef && originalModelIndex !== undef){ + model.set(index, this.cloneModel(originalModel[originalModelIndex])); + } + } + }); +}); \ No newline at end of file diff --git a/EditStoreRefController.js b/EditStoreRefController.js index c9e7b6a..63ca06b 100644 --- a/EditStoreRefController.js +++ b/EditStoreRefController.js @@ -1,12 +1,10 @@ define([ "dojo/_base/declare", - "dojo/_base/lang", - "dojo/when", - "./getPlainValue", - "./EditModelRefController", - "./StoreRefController" -], function(declare, lang, when, getPlainValue, EditModelRefController, StoreRefController){ - return declare("dojox.mvc.EditStoreRefController", [StoreRefController, EditModelRefController], { + "./ModelRefController", + "./EditModelRefControllerMixin", + "./StoreRefControllerMixin" +], function(declare, ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin){ + return declare("dojox.mvc.EditStoreRefController", [ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin], { // summary: // A child class of dojox/mvc/StoreRefController, managing edits. // description: @@ -46,97 +44,5 @@ define([ // | // | // | - - // getPlainValueOptions: dojox/mvc/getPlainValueOptions - // The options to get plain value from stateful object. - getPlainValueOptions: null, - - // _removals: Object[] - // The list of removed elements. - _removals: [], - - // _resultsWatchHandle: dojox/mvc/StatefulArray.watchElements.handle - // The watch handle for model array elements. - _resultsWatchHandle: null, - - // _refSourceModelProp: String - // The property name for the data model, that serves as the data source. - _refSourceModelProp: "sourceModel", - - queryStore: function(/*Object*/ query, /*dojo/store/api/Store.QueryOptions?*/ options){ - // summary: - // Queries the store for objects. - // query: Object - // The query to use for retrieving objects from the store. - // options: dojo/store/api/Store.QueryOptions? - // The optional arguments to apply to the resultset. - // returns: dojo/store/api/Store.QueryResults - // The results of the query, extended with iterative methods. - - if(!(this.store || {}).query){ return; } - if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } - this._removals = []; - var _self = this; - return when(this.inherited(arguments), function(results){ - if(_self._beingDestroyed){ return; } - if(lang.isArray(results)){ - _self._resultsWatchHandle = results.watchElements(function(idx, removals, adds){ - [].push.apply(_self._removals, removals); - }); - } - return results; - }); - }, - - getStore: function(/*Number*/ id, /*Object*/ options){ - // summary: - // Retrieves an object by its identity. - // id: Number - // The identity to use to lookup the object. - // options: Object - // The options for dojo/store/*/get(). - // returns: Object - // The object in the store that matches the given id. - - if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } - return this.inherited(arguments); - }, - - commit: function(){ - // summary: - // Send the change back to the data source. - - if(this._removals){ - for(var i = 0; i < this._removals.length; i++){ - this.store.remove(this.store.getIdentity(this._removals[i])); - } - this._removals = []; - } - var data = getPlainValue(this.get(this._refEditModelProp), this.getPlainValueOptions); - if(lang.isArray(data)){ - for(var i = 0; i < data.length; i++){ - this.store.put(data[i]); - } - }else{ - this.store.put(data); - } - this.inherited(arguments); - }, - - reset: function(){ - // summary: - // Change the model back to its original state. - - this.inherited(arguments); - this._removals = []; - }, - - destroy: function(){ - // summary: - // Clean up model watch handle as this object is destroyed. - - if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } - this.inherited(arguments); - } }); }); diff --git a/EditStoreRefControllerMixin.js b/EditStoreRefControllerMixin.js new file mode 100644 index 0000000..950cf9a --- /dev/null +++ b/EditStoreRefControllerMixin.js @@ -0,0 +1,139 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/when", + "./getPlainValue" +], function(declare, lang, when, getPlainValue){ + return declare("dojox.mvc.EditStoreRefControllerMixin", null, { + // summary: + // A mixin class to the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/StoreRefControllerMixin, managing edits. + // description: + // In addition to what the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/StoreRefControllerMixin does, the commit() method sends the data model as well as the removed entries in array to the data store. + // example: + // The check box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property, from the data coming from data store ("store" property in the controller), using the first one in array. + // Two seconds later, the check box changes from unchecked to checked. + // The change is committed to the data store, which is reflected to dojo/store/Observable callback. + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + + // getPlainValueOptions: dojox/mvc/getPlainValueOptions + // The options to get plain value from stateful object. + getPlainValueOptions: null, + + // _removals: Object[] + // The list of removed elements. + _removals: [], + + // _resultsWatchHandle: dojox/mvc/StatefulArray.watchElements.handle + // The watch handle for model array elements. + _resultsWatchHandle: null, + + // _refSourceModelProp: String + // The property name for the data model, that serves as the data source. + _refSourceModelProp: "sourceModel", + + queryStore: function(/*Object*/ query, /*dojo/store/api/Store.QueryOptions?*/ options){ + // summary: + // Queries the store for objects. + // query: Object + // The query to use for retrieving objects from the store. + // options: dojo/store/api/Store.QueryOptions? + // The optional arguments to apply to the resultset. + // returns: dojo/store/api/Store.QueryResults + // The results of the query, extended with iterative methods. + + if(!(this.store || {}).query){ return; } + if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } + this._removals = []; + var _self = this; + return when(this.inherited(arguments), function(results){ + if(_self._beingDestroyed){ return; } + if(lang.isArray(results)){ + _self._resultsWatchHandle = results.watchElements(function(idx, removals, adds){ + [].push.apply(_self._removals, removals); + }); + } + return results; + }); + }, + + getStore: function(/*Number*/ id, /*Object*/ options){ + // summary: + // Retrieves an object by its identity. + // id: Number + // The identity to use to lookup the object. + // options: Object + // The options for dojo/store/*/get(). + // returns: Object + // The object in the store that matches the given id. + + if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } + return this.inherited(arguments); + }, + + commit: function(){ + // summary: + // Send the change back to the data source. + + if(this._removals){ + for(var i = 0; i < this._removals.length; i++){ + this.store.remove(this.store.getIdentity(this._removals[i])); + } + this._removals = []; + } + var data = getPlainValue(this.get(this._refEditModelProp), this.getPlainValueOptions); + if(lang.isArray(data)){ + for(var i = 0; i < data.length; i++){ + this.store.put(data[i]); + } + }else{ + this.store.put(data); + } + this.inherited(arguments); + }, + + reset: function(){ + // summary: + // Change the model back to its original state. + + this.inherited(arguments); + this._removals = []; + }, + + destroy: function(){ + // summary: + // Clean up model watch handle as this object is destroyed. + + if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } + this.inherited(arguments); + } + }); +}); diff --git a/EditStoreRefListController.js b/EditStoreRefListController.js index ae98083..2eaa783 100644 --- a/EditStoreRefListController.js +++ b/EditStoreRefListController.js @@ -1,11 +1,14 @@ define([ "dojo/_base/declare", - "dojo/_base/lang", - "./getPlainValue", - "./EditStoreRefController", - "./ListController" -], function(declare, lang, getPlainValue, EditStoreRefController, ListController){ - return declare("dojox.mvc.EditStoreRefListController", [EditStoreRefController, ListController], { + "./ModelRefController", + "./EditModelRefControllerMixin", + "./StoreRefControllerMixin", + "./EditStoreRefControllerMixin", + "./ListControllerMixin", + "./EditModelRefListControllerMixin", + "./EditStoreRefListControllerMixin" +], function(declare, ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin, EditStoreRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin, EditStoreRefListControllerMixin){ + return declare("dojox.mvc.EditStoreRefListController", [ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin, EditStoreRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin, EditStoreRefListControllerMixin], { // summary: // A child class of dojox/mvc/EditStoreRefController, mixed with ListController. // description: @@ -45,20 +48,5 @@ define([ // | // | // | - - commitCurrent: function(){ - // summary: - // Send the change back to the data source for the current index. - - var id = this.cursor[this.idProperty]; - for(var i = 0; i < this.originalModel.length; i++){ - if(this.originalModel[i][this.idProperty] == id){ - this.originalModel.set(i, this.cloneModel(this.cursor)); - break; - } - } - this.store.put(this.cursor); - } - }); }); diff --git a/EditStoreRefListControllerMixin.js b/EditStoreRefListControllerMixin.js new file mode 100644 index 0000000..a1daf23 --- /dev/null +++ b/EditStoreRefListControllerMixin.js @@ -0,0 +1,49 @@ +define(["dojo/_base/declare"], function(declare){ + return declare("dojox.mvc.EditStoreRefListControllerMixin", null, { + // summary: + // A mixin class to the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin, dojox/mvc/StoreRefControllerMixin, dojox/mvc/EditStoreRefControllerMixin, dojox/mvc/ListControllerMixin, dojox/mvc/EditModelRefListControllerMixin and dojox/mvc/EditStoreRefControllerMixin. + // description: + // In addition to what the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin, dojox/mvc/StoreRefControllerMixin, dojox/mvc/EditStoreRefControllerMixin, dojox/mvc/ListControllerMixin, dojox/mvc/EditModelRefListControllerMixin and dojox/mvc/EditStoreRefControllerMixin does, this class adds an ability to save data (commit) of currently selected list item. + // example: + // The check box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property, from the data coming from data store ("store" property in the controller), using the first one in array. + // Two seconds later, the check box changes from unchecked to checked. + // The change is committed to the data store, which is reflected to dojo/store/Observable callback. + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + + commitCurrent: function(){ + // summary: + // Send the change of currently selected list item back to the data source for the current index. + + this.inherited(arguments); + this.store.put(this.cursor); + } + }); +}); diff --git a/ListController.js b/ListController.js index 27edae1..704e32b 100644 --- a/ListController.js +++ b/ListController.js @@ -1,92 +1,9 @@ define([ - "dojo/_base/array", - "dojo/_base/lang", "dojo/_base/declare", - "./ModelRefController" -], function(array, lang, declare, ModelRefController){ - function unwatchHandles(/*dojox/mvc/ListController*/ c){ - // summary: - // Unwatch model watch handles. - - for(var s in {"_listModelWatchHandle": 1, "_tableModelWatchHandle": 1}){ - if(c[s]){ - c[s].unwatch(); - c[s] = null; - } - } - } - - function setRefInModel(/*dojox/mvc/ListController*/ ctrl, /*dojo/Stateful*/ old, /*dojo/Stateful*/ current){ - // summary: - // A function called when this controller gets newer value as the list data. - - unwatchHandles(ctrl); - if(current && old !== current){ - if(current.watchElements){ - ctrl._listModelWatchHandle = current.watchElements(function(idx, removals, adds){ - if(removals && adds){ - var curIdx = ctrl.get("cursorIndex"); - // If selected element is removed, make "no selection" state - if(removals && curIdx >= idx && curIdx < idx + removals.length){ - ctrl.set("cursorIndex", -1); - return; - } - // If selected element is equal to or larger than the removals/adds point, update the selected index - if((removals.length || adds.length) && curIdx >= idx){ - ctrl.set(ctrl._refCursorProp, ctrl.get("cursor")); - } - }else{ - // If there is a update to the whole array, update the selected index - ctrl.set(ctrl._refCursorProp, ctrl.get(ctrl._refCursorProp)); - } - }); - }else if(current.set && current.watch){ - if(ctrl.get("cursorIndex") < 0){ ctrl._set("cursorIndex", ""); } - ctrl._tableModelWatchHandle = current.watch(function(name, old, current){ - if(old !== current && name == ctrl.get("cursorIndex")){ - ctrl.set(ctrl._refCursorProp, current); - } - }); - } - } - ctrl._setCursorIndexAttr(ctrl.cursorIndex); - } - - function setRefCursor(/*dojox/mvc/ListController*/ ctrl, /*dojo/Stateful*/ old, /*dojo/Stateful*/ current){ - // summary: - // A function called when this controller gets newer value as the data of current selection. - // description: - // Finds the index associated with the given element, and updates cursorIndex property. - - var model = ctrl[ctrl._refInModelProp]; - if(!model){ return; } - if(old !== current){ - if(lang.isArray(model)){ - var foundIdx = array.indexOf(model, current); - if(foundIdx < 0){ - var targetIdx = ctrl.get("cursorIndex"); - if(targetIdx >= 0 && targetIdx < model.length){ - model.set(targetIdx, current); - } - }else{ - ctrl.set("cursorIndex", foundIdx); - } - }else{ - for(var s in model){ - if(model[s] == current){ - ctrl.set("cursorIndex", s); - return; - } - } - var targetIdx = ctrl.get("cursorIndex"); - if(targetIdx){ - model.set(targetIdx, current); - } - } - } - } - - return declare("dojox.mvc.ListController", ModelRefController, { + "./ModelRefController", + "./ListControllerMixin" +], function(declare, ModelRefController, ListControllerMixin){ + return declare("dojox.mvc.ListController", [ModelRefController, ListControllerMixin], { // summary: // A controller working with array model, managing its cursor. // NOTE - If this class is used with a widget by data-dojo-mixins, make sure putting the widget in data-dojo-type and putting this class to data-dojo-mixins. @@ -113,117 +30,5 @@ define([ // | // | // | - - // idProperty: String - // The property name in element in the model array, that works as its identifier. - idProperty: "uniqueId", - - // cursorId: String - // The ID of the selected element in the model array. - cursorId: null, - - // cursorIndex: Number|String - // The index of the selected element in the model. - cursorIndex: -1, - - // cursor: dojo/Stateful - // The selected element in the model array. - cursor: null, - - // model: dojox/mvc/StatefulArray - // The data model working as an array. - model: null, - - // _listModelWatchHandle: Object - // The watch handle of model, watching for array elements. - _listModelWatchHandle: null, - - // _tableModelWatchHandle: Object - // The watch handle of model. - _tableModelWatchHandle: null, - - // _refCursorProp: String - // The property name for the data model of the current selection. - _refCursorProp: "cursor", - - // _refModelProp: String - // The property name for the data model. - _refModelProp: "cursor", - - destroy: function(){ - unwatchHandles(this); - this.inherited(arguments); - }, - - set: function(/*String*/ name, /*Anything*/ value){ - // summary: - // Set a property to this. - // name: String - // The property to set. - // value: Anything - // The value to set in the property. - - var oldRefInCursor = this[this._refCursorProp]; - var oldRefInModel = this[this._refInModelProp]; - this.inherited(arguments); - if(name == this._refCursorProp){ - setRefCursor(this, oldRefInCursor, value); - } - if(name == this._refInModelProp){ - setRefInModel(this, oldRefInModel, value); - } - }, - - _setCursorIdAttr: function(/*String*/ value){ - // summary: - // Handler for calls to set("cursorId", val). - // description: - // Finds the index associated with the given cursor ID, and updates cursorIndex property. - - var old = this.cursorId; - this._set("cursorId", value); - var model = this[this._refInModelProp]; - if(!model){ return; } - if(old !== value){ - if(lang.isArray(model)){ - for(var i = 0; i < model.length; i++){ - if(model[i][this.idProperty] == value){ - this.set("cursorIndex", i); - return; - } - } - this._set("cursorIndex", -1); - }else{ - for(var s in model){ - if(model[s][this.idProperty] == value){ - this.set("cursorIndex", s); - return; - } - } - this._set("cursorIndex", ""); - } - } - }, - - _setCursorIndexAttr: function(/*Number*/ value){ - // summary: - // Handler for calls to set("cursorIndex", val). - // description: - // Updates cursor, cursorId, cursorIndex properties internally and call watch callbacks for them. - - this._set("cursorIndex", value); - if(!this[this._refInModelProp]){ return; } - this.set(this._refCursorProp, this[this._refInModelProp][value]); - this.set("cursorId", this[this._refInModelProp][value] && this[this._refInModelProp][value][this.idProperty]); - }, - - hasControllerProperty: function(/*String*/ name){ - // summary: - // Returns true if this controller itself owns the given property. - // name: String - // The property name. - - return this.inherited(arguments) || name == this._refCursorProp; - } }); }); diff --git a/ListControllerMixin.js b/ListControllerMixin.js new file mode 100644 index 0000000..76d0d72 --- /dev/null +++ b/ListControllerMixin.js @@ -0,0 +1,227 @@ +define([ + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/declare" +], function(array, lang, declare){ + function unwatchHandles(/*dojox/mvc/ListControllerMixin*/ c){ + // summary: + // Unwatch model watch handles. + + for(var s in {"_listModelWatchHandle": 1, "_tableModelWatchHandle": 1}){ + if(c[s]){ + c[s].unwatch(); + c[s] = null; + } + } + } + + function setRefInModel(/*dojox/mvc/ListControllerMixin*/ ctrl, /*dojo/Stateful*/ old, /*dojo/Stateful*/ current){ + // summary: + // A function called when this controller gets newer value as the list data. + + unwatchHandles(ctrl); + if(current && old !== current){ + if(current.watchElements){ + ctrl._listModelWatchHandle = current.watchElements(function(idx, removals, adds){ + if(removals && adds){ + var curIdx = ctrl.get("cursorIndex"); + // If selected element is removed, make "no selection" state + if(removals && curIdx >= idx && curIdx < idx + removals.length){ + ctrl.set("cursorIndex", -1); + return; + } + // If selected element is equal to or larger than the removals/adds point, update the selected index + if((removals.length || adds.length) && curIdx >= idx){ + ctrl.set(ctrl._refCursorProp, ctrl.get("cursor")); + } + }else{ + // If there is a update to the whole array, update the selected index + ctrl.set(ctrl._refCursorProp, ctrl.get(ctrl._refCursorProp)); + } + }); + } + if(current.set && current.watch){ + if(ctrl.get("cursorIndex") < 0){ ctrl._set("cursorIndex", ""); } + ctrl._tableModelWatchHandle = current.watch(function(name, old, current){ + if(old !== current && name == ctrl.get("cursorIndex")){ + ctrl.set(ctrl._refCursorProp, current); + } + }); + } + } + ctrl._setCursorIndexAttr(ctrl.cursorIndex); + } + + function setRefCursor(/*dojox/mvc/ListControllerMixin*/ ctrl, /*dojo/Stateful*/ old, /*dojo/Stateful*/ current){ + // summary: + // A function called when this controller gets newer value as the data of current selection. + // description: + // Finds the index associated with the given element, and updates cursorIndex property. + + var model = ctrl[ctrl._refInModelProp]; + if(!model){ return; } + if(old !== current){ + if(lang.isArray(model)){ + var foundIdx = array.indexOf(model, current); + if(foundIdx < 0){ + var targetIdx = ctrl.get("cursorIndex"); + if(targetIdx >= 0 && targetIdx < model.length){ + model.set(targetIdx, current); + } + }else{ + ctrl.set("cursorIndex", foundIdx); + } + }else{ + for(var s in model){ + if(model[s] == current){ + ctrl.set("cursorIndex", s); + return; + } + } + var targetIdx = ctrl.get("cursorIndex"); + if(targetIdx){ + model.set(targetIdx, current); + } + } + } + } + + return declare("dojox.mvc.ListControllerMixin", null, { + // summary: + // A mixin to dojox/mvc/ModelRefController, working with array model, managing its cursor. + // example: + // The text box changes its value every two seconds. + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + + // idProperty: String + // The property name in element in the model array, that works as its identifier. + idProperty: "uniqueId", + + // cursorId: String + // The ID of the selected element in the model array. + cursorId: null, + + // cursorIndex: Number|String + // The index of the selected element in the model. + cursorIndex: -1, + + // cursor: dojo/Stateful + // The selected element in the model array. + cursor: null, + + // model: dojox/mvc/StatefulArray + // The data model working as an array. + model: null, + + // _listModelWatchHandle: Object + // The watch handle of model, watching for array elements. + _listModelWatchHandle: null, + + // _tableModelWatchHandle: Object + // The watch handle of model. + _tableModelWatchHandle: null, + + // _refCursorProp: String + // The property name for the data model of the current selection. + _refCursorProp: "cursor", + + // _refModelProp: String + // The property name for the data model. + _refModelProp: "cursor", + + destroy: function(){ + unwatchHandles(this); + this.inherited(arguments); + }, + + set: function(/*String*/ name, /*Anything*/ value){ + // summary: + // Set a property to this. + // name: String + // The property to set. + // value: Anything + // The value to set in the property. + + var oldRefInCursor = this[this._refCursorProp]; + var oldRefInModel = this[this._refInModelProp]; + this.inherited(arguments); + if(name == this._refCursorProp){ + setRefCursor(this, oldRefInCursor, value); + } + if(name == this._refInModelProp){ + setRefInModel(this, oldRefInModel, value); + } + }, + + _setCursorIdAttr: function(/*String*/ value){ + // summary: + // Handler for calls to set("cursorId", val). + // description: + // Finds the index associated with the given cursor ID, and updates cursorIndex property. + + var old = this.cursorId; + this._set("cursorId", value); + var model = this[this._refInModelProp]; + if(!model){ return; } + if(old !== value){ + if(lang.isArray(model)){ + for(var i = 0; i < model.length; i++){ + if(model[i][this.idProperty] == value){ + this.set("cursorIndex", i); + return; + } + } + this._set("cursorIndex", -1); + }else{ + for(var s in model){ + if(model[s][this.idProperty] == value){ + this.set("cursorIndex", s); + return; + } + } + this._set("cursorIndex", ""); + } + } + }, + + _setCursorIndexAttr: function(/*Number*/ value){ + // summary: + // Handler for calls to set("cursorIndex", val). + // description: + // Updates cursor, cursorId, cursorIndex properties internally and call watch callbacks for them. + + this._set("cursorIndex", value); + if(!this[this._refInModelProp]){ return; } + this.set(this._refCursorProp, this[this._refInModelProp][value]); + this.set("cursorId", this[this._refInModelProp][value] && this[this._refInModelProp][value][this.idProperty]); + }, + + hasControllerProperty: function(/*String*/ name){ + // summary: + // Returns true if this controller itself owns the given property. + // name: String + // The property name. + + return this.inherited(arguments) || name == this._refCursorProp; + } + }); +}); diff --git a/StoreRefController.js b/StoreRefController.js index ae4dc54..ad6a09d 100644 --- a/StoreRefController.js +++ b/StoreRefController.js @@ -1,11 +1,9 @@ define([ "dojo/_base/declare", - "dojo/_base/lang", - "dojo/when", - "./getStateful", - "./ModelRefController" -], function(declare, lang, when, getStateful, ModelRefController){ - return declare("dojox.mvc.StoreRefController", ModelRefController, { + "./ModelRefController", + "./StoreRefControllerMixin" +], function(declare, ModelRefController, StoreRefControllerMixin){ + return declare("dojox.mvc.StoreRefController", [ModelRefController, StoreRefControllerMixin], { // summary: // A child class of dojox.mvc.ModelRefController, which keeps a reference to Dojo Object Store (in store property). // description: @@ -45,109 +43,5 @@ define([ // | // | // | - - // store: dojo/store/* - // The Dojo Object Store in use. - store: null, - - // getStatefulOptions: dojox.mvc.getStatefulOptions - // The options to get stateful object from plain value. - getStatefulOptions: null, - - // _refSourceModelProp: String - // The property name for the data model, that serves as the data source. - _refSourceModelProp: "model", - - queryStore: function(/*Object*/ query, /*dojo/store/api/Store.QueryOptions?*/ options){ - // summary: - // Queries the store for objects. - // query: Object - // The query to use for retrieving objects from the store. - // options: dojo/store/api/Store.QueryOptions? - // The optional arguments to apply to the resultset. - // returns: dojo/store/api/Store.QueryResults - // The results of the query, extended with iterative methods. - - if(!(this.store || {}).query){ return; } - if(this._queryObserveHandle){ this._queryObserveHandle.cancel(); } - - var _self = this, - queryResult = this.store.query(query, options), - result = when(queryResult, function(results){ - if(_self._beingDestroyed){ return; } - results = getStateful(results, _self.getStatefulOptions); - _self.set(_self._refSourceModelProp, results); - return results; - }); - // For dojo/store/Observable, which adds a function to query result - for(var s in queryResult){ - if(isNaN(s) && queryResult.hasOwnProperty(s) && lang.isFunction(queryResult[s])){ - result[s] = queryResult[s]; - } - } - return result; - }, - - getStore: function(/*Number*/ id, /*Object*/ options){ - // summary: - // Retrieves an object by its identity. - // id: Number - // The identity to use to lookup the object. - // options: Object - // The options for dojo/store.*.get(). - // returns: Object - // The object in the store that matches the given id. - - if(!(this.store || {}).get){ return; } - if(this._queryObserveHandle){ this._queryObserveHandle.cancel(); } - var _self = this; - result = when(this.store.get(id, options), function(result){ - if(_self._beingDestroyed){ return; } - result = getStateful(result, _self.getStatefulOptions); - _self.set(_self._refSourceModelProp, result); - return result; - }); - return result; - }, - - putStore: function(/*Object*/ object, /*dojo/store/api/Store.PutDirectives?*/ options){ - // summary: - // Stores an object. - // object: Object - // The object to store. - // options: dojo/store/api/Store.PutDirectives? - // Additional metadata for storing the data. Includes an "id" property if a specific id is to be used. - // returns: Number - - if(!(this.store || {}).put){ return; } - return this.store.put(object, options); - }, - - addStore: function(object, options){ - // summary: - // Creates an object, throws an error if the object already exists. - // object: Object - // The object to store. - // options: dojo/store/api/Store.PutDirectives? - // Additional metadata for storing the data. Includes an "id" property if a specific id is to be used. - // returns: Number - - if(!(this.store || {}).add){ return; } - return this.store.add(object, options); - }, - - removeStore: function(/*Number*/ id, /*Object*/ options){ - // summary: - // Deletes an object by its identity - // id: Number - // The identity to use to delete the object - // options: Object - // The options for dojo/store/*.remove(). - // returns: Boolean - // Returns true if an object was removed, falsy (undefined) if no object matched the id. - - if(!(this.store || {}).remove){ return; } - return this.store.remove(id, options); - } }); -}); +}); \ No newline at end of file diff --git a/StoreRefControllerMixin.js b/StoreRefControllerMixin.js new file mode 100644 index 0000000..a1e794d --- /dev/null +++ b/StoreRefControllerMixin.js @@ -0,0 +1,150 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/when", + "./getStateful" +], function(declare, lang, when, getStateful){ + return declare("dojox.mvc.StoreRefControllerMixin", null, { + // summary: + // A mixin class to dojox.mvc.ModelRefController, which keeps a reference to Dojo Object Store (in store property). + // description: + // Has several methods to work with the store: + // + // - queryStore(): Runs query() against the store, and creates a data model from retrieved data + // - getStore(): Runs get() against the store, and creates a data model from retrieved data + // - putStore(): Runs put() against the store + // - addStore(): Runs add() against the store + // - removeStore(): Runs remove() against the store + // + // dojo.Stateful get()/set()/watch() interfaces in dojox.mvc.StoreRefControllerMixin will work with the data model from queryStore() or getStore(). + // example: + // The text box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property, from the data coming from data store ("store" property in the controller). + // Two seconds later, the text box changes from "Foo" to "Bar" as the controller gets new data from data store. + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + + // store: dojo/store/* + // The Dojo Object Store in use. + store: null, + + // getStatefulOptions: dojox.mvc.getStatefulOptions + // The options to get stateful object from plain value. + getStatefulOptions: null, + + // _refSourceModelProp: String + // The property name for the data model, that serves as the data source. + _refSourceModelProp: "model", + + queryStore: function(/*Object*/ query, /*dojo/store/api/Store.QueryOptions?*/ options){ + // summary: + // Queries the store for objects. + // query: Object + // The query to use for retrieving objects from the store. + // options: dojo/store/api/Store.QueryOptions? + // The optional arguments to apply to the resultset. + // returns: dojo/store/api/Store.QueryResults + // The results of the query, extended with iterative methods. + + if(!(this.store || {}).query){ return; } + if(this._queryObserveHandle){ this._queryObserveHandle.cancel(); } + + var _self = this, + queryResult = this.store.query(query, options), + result = when(queryResult, function(results){ + if(_self._beingDestroyed){ return; } + results = getStateful(results, _self.getStatefulOptions); + _self.set(_self._refSourceModelProp, results); + return results; + }); + // For dojo/store/Observable, which adds a function to query result + for(var s in queryResult){ + if(isNaN(s) && queryResult.hasOwnProperty(s) && lang.isFunction(queryResult[s])){ + result[s] = queryResult[s]; + } + } + return result; + }, + + getStore: function(/*Number*/ id, /*Object*/ options){ + // summary: + // Retrieves an object by its identity. + // id: Number + // The identity to use to lookup the object. + // options: Object + // The options for dojo/store.*.get(). + // returns: Object + // The object in the store that matches the given id. + + if(!(this.store || {}).get){ return; } + if(this._queryObserveHandle){ this._queryObserveHandle.cancel(); } + var _self = this; + result = when(this.store.get(id, options), function(result){ + if(_self._beingDestroyed){ return; } + result = getStateful(result, _self.getStatefulOptions); + _self.set(_self._refSourceModelProp, result); + return result; + }); + return result; + }, + + putStore: function(/*Object*/ object, /*dojo/store/api/Store.PutDirectives?*/ options){ + // summary: + // Stores an object. + // object: Object + // The object to store. + // options: dojo/store/api/Store.PutDirectives? + // Additional metadata for storing the data. Includes an "id" property if a specific id is to be used. + // returns: Number + + if(!(this.store || {}).put){ return; } + return this.store.put(object, options); + }, + + addStore: function(object, options){ + // summary: + // Creates an object, throws an error if the object already exists. + // object: Object + // The object to store. + // options: dojo/store/api/Store.PutDirectives? + // Additional metadata for storing the data. Includes an "id" property if a specific id is to be used. + // returns: Number + + if(!(this.store || {}).add){ return; } + return this.store.add(object, options); + }, + + removeStore: function(/*Number*/ id, /*Object*/ options){ + // summary: + // Deletes an object by its identity + // id: Number + // The identity to use to delete the object + // options: Object + // The options for dojo/store/*.remove(). + // returns: Boolean + // Returns true if an object was removed, falsy (undefined) if no object matched the id. + + if(!(this.store || {}).remove){ return; } + return this.store.remove(id, options); + } + }); +}); diff --git a/tests/EditModelRefListControllerMixin.js b/tests/EditModelRefListControllerMixin.js new file mode 100644 index 0000000..619e0ad --- /dev/null +++ b/tests/EditModelRefListControllerMixin.js @@ -0,0 +1,79 @@ +define([ + "doh", + "dojo/_base/declare", + "dojo/Stateful", + "../ModelRefController", + "../EditModelRefControllerMixin", + "../ListControllerMixin", + "../EditModelRefListControllerMixin", + "../at", + "../sync", + "../getStateful" +], function(doh, declare, Stateful, ModelRefController, EditModelRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin, at, sync, getStateful){ + var data = [ + {uniqueId: 0, value: "Index 0"}, + {uniqueId: 1, value: "Index 1"}, + {uniqueId: 2, value: "Index 2"}, + {uniqueId: 3, value: "Index 3"}, + {uniqueId: 4, value: "Index 4"} + ], clz = declare([ModelRefController, EditModelRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin], {}); + + doh.register("dojox.mvc.tests.EditModelRefListControllerMixin", [ + function commit(){ + var ctrl = new clz({sourceModel: getStateful(data), cursorIndex: 2}); + ctrl.set("value", "3rd"); + doh.is("3rd", ctrl.sourceModel[2].value, "sourceModel should be updated as soon as there is a change in value"); + doh.is("Index 2", ctrl.originalModel[2].value, "originalModel shouldn't be updated until commit() is called"); + ctrl.commitCurrent(); + doh.is("3rd", ctrl.originalModel[2].value, "originalModel should be updated as commit() is called"); + }, + function commitHoldModel(){ + var ctrl = new clz({sourceModel: getStateful(data), cursorIndex: 2, holdModelUntilCommit: true}); + ctrl.set("value", "3rd"); + doh.is("Index 2", ctrl.sourceModel[2].value, "sourceModel shouldn't be updated until commit() is called"); + doh.is("Index 2", ctrl.originalModel[2].value, "originalModel shouldn't be updated until commit() is called"); + ctrl.commitCurrent(); + doh.is("3rd", ctrl.sourceModel[2].value, "sourceModel should be updated as commit() is called"); + doh.is("3rd", ctrl.originalModel[2].value, "originalModel should be updated as commit() is called"); + }, + function reset(){ + var ctrl = new clz({sourceModel: getStateful(data), cursorIndex: 2}); + ctrl.set("value", "3rd"); + ctrl.commitCurrent(); + ctrl.set("value", "Third"); + ctrl.resetCurrent(); + doh.is("3rd", ctrl.get("value"), "Model should be updated as reset() is called"); + doh.is("3rd", ctrl.model[2].value, "Model should be updated as reset() is called"); + doh.is("3rd", ctrl.sourceModel[2].value, "sourceModel should be updated as reset() is called"); + doh.is("3rd", ctrl.originalModel[2].value, "originalModel should be updated as reset() is called"); + }, + function resetHoldModel(){ + var ctrl = new clz({sourceModel: getStateful(data), cursorIndex: 2, holdModelUntilCommit: true}); + ctrl.set("value", "3rd"); + ctrl.commitCurrent(); + ctrl.set("value", "Third"); + ctrl.resetCurrent(); + doh.is("3rd", ctrl.get("value"), "Model should be updated as reset() is called"); + doh.is("3rd", ctrl.model[2].value, "Model should be updated as reset() is called"); + doh.is("3rd", ctrl.sourceModel[2].value, "sourceModel should be updated as reset() is called"); + doh.is("3rd", ctrl.originalModel[2].value, "originalModel should be updated as reset() is called"); + }, + { + name: "commitHoldModelToBindTarget", + runTest: function(){ + var ctrlSource = new (declare([ModelRefController, ListControllerMixin], {}))({model: getStateful(data), cursorIndex: 2}), + ctrlEdit = this.ctrlEdit = new clz({sourceModel: at(ctrlSource, "model"), cursorIndex: 2, holdModelUntilCommit: true}), + target = new Stateful(); + this.h = sync(ctrlSource, "value", target, "value"); + ctrlEdit.set("value", "3rd"); + doh.is("Index 2", target.value, "sourceModel shouldn't be updated until commit() is called"); + ctrlEdit.commitCurrent(); + doh.is("3rd", target.value, "sourceModel should be updated as commit() is called"); + }, + tearDown: function(){ + this.ctrlEdit && this.ctrlEdit.destroy(); + this.h && this.h.remove(); + } + } + ]); +}); \ No newline at end of file diff --git a/tests/module.js b/tests/module.js index 7d4bcca..e5988ca 100644 --- a/tests/module.js +++ b/tests/module.js @@ -6,6 +6,7 @@ define([ "./_Controller", "./ModelRefController", "./StoreRefControllerTest", + "./EditModelRefListControllerMixin", "./WidgetList", "./StatefulArray", "./StatefulModelOptions" diff --git a/tests/moduleFullSet.js b/tests/moduleFullSet.js index 0597dd9..bc6737c 100644 --- a/tests/moduleFullSet.js +++ b/tests/moduleFullSet.js @@ -6,6 +6,7 @@ define([ "./_Controller", "./ModelRefController", "./StoreRefControllerTest", + "./EditModelRefListControllerMixin", "./WidgetList", "./StatefulArray", "./StatefulModelOptions" From 0a456011db2861018afeaf56007913d4b0accb2f Mon Sep 17 00:00:00 2001 From: Akira Sudoh Date: Sat, 27 Oct 2012 23:37:42 +0900 Subject: [PATCH 2/2] Reduced mixins for ModelRefController. --- EditModelRefControllerMixin.js | 2 + EditModelRefListControllerMixin.js | 172 ------------------ EditStoreRefControllerMixin.js | 139 -------------- EditStoreRefListController.js | 13 +- EditStoreRefListControllerMixin.js | 49 ----- ListControllerMixin.js | 156 +++++++++++++++- StoreRefControllerMixin.js | 114 +++++++++++- _Controller.js | 5 +- ...ixin.js => EditModelRefControllerMixin.js} | 7 +- tests/module.js | 2 +- tests/moduleFullSet.js | 2 +- 11 files changed, 276 insertions(+), 385 deletions(-) delete mode 100644 EditModelRefListControllerMixin.js delete mode 100644 EditStoreRefControllerMixin.js delete mode 100644 EditStoreRefListControllerMixin.js rename tests/{EditModelRefListControllerMixin.js => EditModelRefControllerMixin.js} (93%) diff --git a/EditModelRefControllerMixin.js b/EditModelRefControllerMixin.js index d12d21a..554061b 100644 --- a/EditModelRefControllerMixin.js +++ b/EditModelRefControllerMixin.js @@ -179,6 +179,7 @@ define([ // Send the change back to the data source. this.set(this.holdModelUntilCommit ? this._refSourceModelProp : this._refOriginalModelProp, this.cloneModel(this.get(this._refEditModelProp))); + this.inherited(arguments); }, reset: function(){ @@ -186,6 +187,7 @@ define([ // Change the model back to its original state. this.set(this.holdModelUntilCommit ? this._refEditModelProp : this._refSourceModelProp, this.cloneModel(this.get(this._refOriginalModelProp))); + this.inherited(arguments); }, hasControllerProperty: function(/*String*/ name){ diff --git a/EditModelRefListControllerMixin.js b/EditModelRefListControllerMixin.js deleted file mode 100644 index f134627..0000000 --- a/EditModelRefListControllerMixin.js +++ /dev/null @@ -1,172 +0,0 @@ -define([ - "dojo/_base/declare", - "dojo/_base/lang", - "dijit/Destroyable" -], function(declare, lang, Destroyable){ - var undef; - - function findIndex(/*String*/ idProperty, /*Object*/ model, /*Object*/ cursor){ - if(lang.isArray(model)){ - for(var i = 0, l = model.length; i < l; ++i){ - if(model[i][idProperty] == cursor[idProperty]){ - return i; - } - } - }else{ - for(var s in model){ - if(model[s][idProperty] == cursor[idProperty]){ - return s; - } - } - } - } - - function setRefSourceModel(/*dojox/mvc/EditModelRefListControllerMixin*/ ctrl, /*Anything*/ old, /*Anything*/ current){ - // summary: - // A function called when this controller gets newer value as the data source. - // ctrl: dojox/mvc/EditModelRefControllerMixin - // The controller. - // old: Anything - // The older value. - // current: Anything - // The newer value. - - if(old !== current){ - ctrl._handleRefSourceModel && ctrl._handleRefSourceModel.remove(); - ctrl.own(ctrl._handleRefSourceModel = current.watch(function(name, old, current){ - if(old !== current){ - var index; - index = findIndex(ctrl.idProperty, ctrl[ctrl._refOriginalModelProp], current); - if(index !== undef){ - ctrl[ctrl._refOriginalModelProp].set(index, ctrl.holdModelUntilCommit ? current : ctrl.cloneModel(current)); - } - index = findIndex(ctrl.idProperty, ctrl[ctrl._refEditModelProp], current); - if(index !== undef){ - ctrl[ctrl._refEditModelProp].set(index, ctrl.holdModelUntilCommit ? ctrl.cloneModel(current) : current); - } - } - })); - } - } - - return declare("dojox.mvc.EditModelRefListControllerMixin", Destroyable, { - // summary: - // A mixin class to the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/ListControllerMixin. - // description: - // In addition to what the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/ListControllerMixin does, this class adds an ability to save data (commit) of currently selected list item. - // example: - // The text box refers to "value" property in the controller (with "ctrl" ID). - // The controller provides the "value" property, from the data model, using the third one in array. - // Two seconds later, the text box changes from "Third" to "3rd", and it's saved for reversion. - // Two seconds later then, the text box changes from "3rd" to "THIRD", and two seconds later then, the text box changes back to "3rd". - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | - // example: - // The controller with "ctrlSource" ID specifies holding changes until commit() is called (by setting true to holdModelUntilCommit). - // As the change in the second text box is committed two seconds later from the change, the first text box is updated at then (when the change is committed). - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | Source: - // | Edit: - // | - // | - - set: function(/*String*/ name, /*Anything*/ value){ - // summary: - // Set a property to this. - // name: String - // The property to set. - // value: Anything - // The value to set in the property. - - if(name == this._refSourceModelProp){ - setRefSourceModel(this, this[this._refSourceModelProp], value); - } - this.inherited(arguments); - }, - - commitCurrent: function(){ - // summary: - // Send the change of currently selected list item back to the data source. - - var model = this[this.holdModelUntilCommit ? this._refSourceModelProp : this._refOriginalModelProp], - index = findIndex(this.idProperty, model, this.cursor); - if(index !== undef){ - model.set(index, this.cloneModel(this.cursor)); - } - }, - - resetCurrent: function(){ - // summary: - // Change the currently selected list item back to its original state. - - var model = this[this.holdModelUntilCommit ? this._refEditModelProp : this._refSourceModelProp], - originalModel = this[this._refOriginalModelProp], - index = findIndex(this.idProperty, model, this.cursor), - originalModelIndex = findIndex(this.idProperty, originalModel, this.cursor); - if(index !== undef && originalModelIndex !== undef){ - model.set(index, this.cloneModel(originalModel[originalModelIndex])); - } - } - }); -}); \ No newline at end of file diff --git a/EditStoreRefControllerMixin.js b/EditStoreRefControllerMixin.js deleted file mode 100644 index 950cf9a..0000000 --- a/EditStoreRefControllerMixin.js +++ /dev/null @@ -1,139 +0,0 @@ -define([ - "dojo/_base/declare", - "dojo/_base/lang", - "dojo/when", - "./getPlainValue" -], function(declare, lang, when, getPlainValue){ - return declare("dojox.mvc.EditStoreRefControllerMixin", null, { - // summary: - // A mixin class to the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/StoreRefControllerMixin, managing edits. - // description: - // In addition to what the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin and dojox/mvc/StoreRefControllerMixin does, the commit() method sends the data model as well as the removed entries in array to the data store. - // example: - // The check box refers to "value" property in the controller (with "ctrl" ID). - // The controller provides the "value" property, from the data coming from data store ("store" property in the controller), using the first one in array. - // Two seconds later, the check box changes from unchecked to checked. - // The change is committed to the data store, which is reflected to dojo/store/Observable callback. - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | - - // getPlainValueOptions: dojox/mvc/getPlainValueOptions - // The options to get plain value from stateful object. - getPlainValueOptions: null, - - // _removals: Object[] - // The list of removed elements. - _removals: [], - - // _resultsWatchHandle: dojox/mvc/StatefulArray.watchElements.handle - // The watch handle for model array elements. - _resultsWatchHandle: null, - - // _refSourceModelProp: String - // The property name for the data model, that serves as the data source. - _refSourceModelProp: "sourceModel", - - queryStore: function(/*Object*/ query, /*dojo/store/api/Store.QueryOptions?*/ options){ - // summary: - // Queries the store for objects. - // query: Object - // The query to use for retrieving objects from the store. - // options: dojo/store/api/Store.QueryOptions? - // The optional arguments to apply to the resultset. - // returns: dojo/store/api/Store.QueryResults - // The results of the query, extended with iterative methods. - - if(!(this.store || {}).query){ return; } - if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } - this._removals = []; - var _self = this; - return when(this.inherited(arguments), function(results){ - if(_self._beingDestroyed){ return; } - if(lang.isArray(results)){ - _self._resultsWatchHandle = results.watchElements(function(idx, removals, adds){ - [].push.apply(_self._removals, removals); - }); - } - return results; - }); - }, - - getStore: function(/*Number*/ id, /*Object*/ options){ - // summary: - // Retrieves an object by its identity. - // id: Number - // The identity to use to lookup the object. - // options: Object - // The options for dojo/store/*/get(). - // returns: Object - // The object in the store that matches the given id. - - if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } - return this.inherited(arguments); - }, - - commit: function(){ - // summary: - // Send the change back to the data source. - - if(this._removals){ - for(var i = 0; i < this._removals.length; i++){ - this.store.remove(this.store.getIdentity(this._removals[i])); - } - this._removals = []; - } - var data = getPlainValue(this.get(this._refEditModelProp), this.getPlainValueOptions); - if(lang.isArray(data)){ - for(var i = 0; i < data.length; i++){ - this.store.put(data[i]); - } - }else{ - this.store.put(data); - } - this.inherited(arguments); - }, - - reset: function(){ - // summary: - // Change the model back to its original state. - - this.inherited(arguments); - this._removals = []; - }, - - destroy: function(){ - // summary: - // Clean up model watch handle as this object is destroyed. - - if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } - this.inherited(arguments); - } - }); -}); diff --git a/EditStoreRefListController.js b/EditStoreRefListController.js index 2eaa783..1f6867f 100644 --- a/EditStoreRefListController.js +++ b/EditStoreRefListController.js @@ -3,12 +3,9 @@ define([ "./ModelRefController", "./EditModelRefControllerMixin", "./StoreRefControllerMixin", - "./EditStoreRefControllerMixin", - "./ListControllerMixin", - "./EditModelRefListControllerMixin", - "./EditStoreRefListControllerMixin" -], function(declare, ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin, EditStoreRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin, EditStoreRefListControllerMixin){ - return declare("dojox.mvc.EditStoreRefListController", [ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin, EditStoreRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin, EditStoreRefListControllerMixin], { + "./ListControllerMixin" +], function(declare, ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin, ListControllerMixin){ + return declare("dojox.mvc.EditStoreRefListController", [ModelRefController, EditModelRefControllerMixin, StoreRefControllerMixin, ListControllerMixin], { // summary: // A child class of dojox/mvc/EditStoreRefController, mixed with ListController. // description: @@ -18,7 +15,7 @@ define([ // The check box refers to "value" property in the controller (with "ctrl" ID). // The controller provides the "value" property, from the data coming from data store ("store" property in the controller), using the first one in array. // Two seconds later, the check box changes from unchecked to checked. - // The change is committed to the data store, which is reflected to dojo/store/Observable callback. + // The change is committed to the data store, which is reflected to dojo/store/Observable callback. // | // | // | @@ -43,7 +40,7 @@ define([ // | // | // | - // | // | // | diff --git a/EditStoreRefListControllerMixin.js b/EditStoreRefListControllerMixin.js deleted file mode 100644 index a1daf23..0000000 --- a/EditStoreRefListControllerMixin.js +++ /dev/null @@ -1,49 +0,0 @@ -define(["dojo/_base/declare"], function(declare){ - return declare("dojox.mvc.EditStoreRefListControllerMixin", null, { - // summary: - // A mixin class to the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin, dojox/mvc/StoreRefControllerMixin, dojox/mvc/EditStoreRefControllerMixin, dojox/mvc/ListControllerMixin, dojox/mvc/EditModelRefListControllerMixin and dojox/mvc/EditStoreRefControllerMixin. - // description: - // In addition to what the combination of dojox/mvc/ModelRefController, dojox/mvc/EditModelRefControllerMixin, dojox/mvc/StoreRefControllerMixin, dojox/mvc/EditStoreRefControllerMixin, dojox/mvc/ListControllerMixin, dojox/mvc/EditModelRefListControllerMixin and dojox/mvc/EditStoreRefControllerMixin does, this class adds an ability to save data (commit) of currently selected list item. - // example: - // The check box refers to "value" property in the controller (with "ctrl" ID). - // The controller provides the "value" property, from the data coming from data store ("store" property in the controller), using the first one in array. - // Two seconds later, the check box changes from unchecked to checked. - // The change is committed to the data store, which is reflected to dojo/store/Observable callback. - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | - // | - - commitCurrent: function(){ - // summary: - // Send the change of currently selected list item back to the data source for the current index. - - this.inherited(arguments); - this.store.put(this.cursor); - } - }); -}); diff --git a/ListControllerMixin.js b/ListControllerMixin.js index 76d0d72..30d104f 100644 --- a/ListControllerMixin.js +++ b/ListControllerMixin.js @@ -3,6 +3,24 @@ define([ "dojo/_base/lang", "dojo/_base/declare" ], function(array, lang, declare){ + var undef; + + function findIndex(/*String*/ idProperty, /*Object*/ model, /*Object*/ cursor){ + if(lang.isArray(model)){ + for(var i = 0, l = model.length; i < l; ++i){ + if(model[i][idProperty] == cursor[idProperty]){ + return i; + } + } + }else{ + for(var s in model){ + if(model[s][idProperty] == cursor[idProperty]){ + return s; + } + } + } + } + function unwatchHandles(/*dojox/mvc/ListControllerMixin*/ c){ // summary: // Unwatch model watch handles. @@ -52,6 +70,34 @@ define([ ctrl._setCursorIndexAttr(ctrl.cursorIndex); } + function setRefSourceModel(/*dojox/mvc/EditModelRefListControllerMixin*/ ctrl, /*Anything*/ old, /*Anything*/ current){ + // summary: + // A function called when this controller gets newer value as the data source. + // ctrl: dojox/mvc/EditModelRefControllerMixin + // The controller. + // old: Anything + // The older value. + // current: Anything + // The newer value. + + if(old !== current){ + ctrl._handleRefSourceModel && ctrl._handleRefSourceModel.remove(); + current && ctrl.own(ctrl._handleRefSourceModel = current.watch(function(name, old, current){ + if(old !== current){ + var index; + index = findIndex(ctrl.idProperty, ctrl[ctrl._refOriginalModelProp], current); + if(index !== undef){ + ctrl[ctrl._refOriginalModelProp].set(index, ctrl.holdModelUntilCommit ? current : ctrl.cloneModel(current)); + } + index = findIndex(ctrl.idProperty, ctrl[ctrl._refEditModelProp], current); + if(index !== undef){ + ctrl[ctrl._refEditModelProp].set(index, ctrl.holdModelUntilCommit ? ctrl.cloneModel(current) : current); + } + } + })); + } + } + function setRefCursor(/*dojox/mvc/ListControllerMixin*/ ctrl, /*dojo/Stateful*/ old, /*dojo/Stateful*/ current){ // summary: // A function called when this controller gets newer value as the data of current selection. @@ -111,6 +157,82 @@ define([ // | // | // | + // example: + // The text box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property, from the data model, using the third one in array. + // Two seconds later, the text box changes from "Third" to "3rd", and it's saved for reversion. + // Two seconds later then, the text box changes from "3rd" to "THIRD", and two seconds later then, the text box changes back to "3rd". + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // example: + // The controller with "ctrlSource" ID specifies holding changes until commit() is called (by setting true to holdModelUntilCommit). + // As the change in the second text box is committed two seconds later from the change, the first text box is updated at then (when the change is committed). + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | Source: + // | Edit: + // | + // | // idProperty: String // The property name in element in the model array, that works as its identifier. @@ -161,8 +283,9 @@ define([ // value: Anything // The value to set in the property. - var oldRefInCursor = this[this._refCursorProp]; - var oldRefInModel = this[this._refInModelProp]; + var oldRefInCursor = this[this._refCursorProp], + oldRefInModel = this[this._refInModelProp], + oldRefSourceModel = this[this._refSourceModelProp]; this.inherited(arguments); if(name == this._refCursorProp){ setRefCursor(this, oldRefInCursor, value); @@ -170,6 +293,9 @@ define([ if(name == this._refInModelProp){ setRefInModel(this, oldRefInModel, value); } + if(name == this._refSourceModelProp){ + setRefSourceModel(this, oldRefSourceModel, value); + } }, _setCursorIdAttr: function(/*String*/ value){ @@ -215,6 +341,32 @@ define([ this.set("cursorId", this[this._refInModelProp][value] && this[this._refInModelProp][value][this.idProperty]); }, + commitCurrent: function(){ + // summary: + // Send the change of currently selected list item back to the data source. + + var model = this[this.holdModelUntilCommit ? this._refSourceModelProp : this._refOriginalModelProp], + index = findIndex(this.idProperty, model, this.cursor); + if(index !== undef){ + model.set(index, this.cloneModel(this.cursor)); + } + this.inherited(arguments); + }, + + resetCurrent: function(){ + // summary: + // Change the currently selected list item back to its original state. + + var model = this[this.holdModelUntilCommit ? this._refEditModelProp : this._refSourceModelProp], + originalModel = this[this._refOriginalModelProp], + index = findIndex(this.idProperty, model, this.cursor), + originalModelIndex = findIndex(this.idProperty, originalModel, this.cursor); + if(index !== undef && originalModelIndex !== undef){ + model.set(index, this.cloneModel(originalModel[originalModelIndex])); + } + this.inherited(arguments); + }, + hasControllerProperty: function(/*String*/ name){ // summary: // Returns true if this controller itself owns the given property. diff --git a/StoreRefControllerMixin.js b/StoreRefControllerMixin.js index a1e794d..f93ef51 100644 --- a/StoreRefControllerMixin.js +++ b/StoreRefControllerMixin.js @@ -2,8 +2,9 @@ define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/when", - "./getStateful" -], function(declare, lang, when, getStateful){ + "./getStateful", + "./getPlainValue" +], function(declare, lang, when, getStateful, getPlainValue){ return declare("dojox.mvc.StoreRefControllerMixin", null, { // summary: // A mixin class to dojox.mvc.ModelRefController, which keeps a reference to Dojo Object Store (in store property). @@ -42,6 +43,39 @@ define([ // | // | // | + // example: + // The check box refers to "value" property in the controller (with "ctrl" ID). + // The controller provides the "value" property, from the data coming from data store ("store" property in the controller), using the first one in array. + // Two seconds later, the check box changes from unchecked to checked. + // The change is committed to the data store, which is reflected to dojo/store/Observable callback. + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | + // | // store: dojo/store/* // The Dojo Object Store in use. @@ -51,9 +85,23 @@ define([ // The options to get stateful object from plain value. getStatefulOptions: null, - // _refSourceModelProp: String - // The property name for the data model, that serves as the data source. - _refSourceModelProp: "model", + // getPlainValueOptions: dojox/mvc/getPlainValueOptions + // The options to get plain value from stateful object. + getPlainValueOptions: null, + + // _removals: Object[] + // The list of removed elements. + _removals: [], + + // _resultsWatchHandle: dojox/mvc/StatefulArray.watchElements.handle + // The watch handle for model array elements. + _resultsWatchHandle: null, + + constructor: function(){ + if(!this[this._refSourceModelProp]){ + this[this._refSourceModelProp] = "model"; + } + }, queryStore: function(/*Object*/ query, /*dojo/store/api/Store.QueryOptions?*/ options){ // summary: @@ -67,6 +115,8 @@ define([ if(!(this.store || {}).query){ return; } if(this._queryObserveHandle){ this._queryObserveHandle.cancel(); } + if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } + this._removals = []; var _self = this, queryResult = this.store.query(query, options), @@ -74,6 +124,11 @@ define([ if(_self._beingDestroyed){ return; } results = getStateful(results, _self.getStatefulOptions); _self.set(_self._refSourceModelProp, results); + if(lang.isArray(results)){ + _self._resultsWatchHandle = results.watchElements(function(idx, removals, adds){ + [].push.apply(_self._removals, removals); + }); + } return results; }); // For dojo/store/Observable, which adds a function to query result @@ -97,14 +152,14 @@ define([ if(!(this.store || {}).get){ return; } if(this._queryObserveHandle){ this._queryObserveHandle.cancel(); } + if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } var _self = this; - result = when(this.store.get(id, options), function(result){ + return when(this.store.get(id, options), function(result){ if(_self._beingDestroyed){ return; } result = getStateful(result, _self.getStatefulOptions); _self.set(_self._refSourceModelProp, result); return result; }); - return result; }, putStore: function(/*Object*/ object, /*dojo/store/api/Store.PutDirectives?*/ options){ @@ -145,6 +200,51 @@ define([ if(!(this.store || {}).remove){ return; } return this.store.remove(id, options); + }, + + commit: function(){ + // summary: + // Send the change back to the data source. + + if(this._removals){ + for(var i = 0; i < this._removals.length; i++){ + this.store.remove(this.store.getIdentity(this._removals[i])); + } + this._removals = []; + } + var data = getPlainValue(this.get(this._refEditModelProp), this.getPlainValueOptions); + if(lang.isArray(data)){ + for(var i = 0; i < data.length; i++){ + this.store.put(data[i]); + } + }else{ + this.store.put(data); + } + this.inherited(arguments); + }, + + commitCurrent: function(){ + // summary: + // Send the change of currently selected list item back to the data source for the current index. + + this.inherited(arguments); + this.store.put(this.cursor); + }, + + reset: function(){ + // summary: + // Change the model back to its original state. + + this.inherited(arguments); + this._removals = []; + }, + + destroy: function(){ + // summary: + // Clean up model watch handle as this object is destroyed. + + if(this._resultsWatchHandle){ this._resultsWatchHandle.unwatch(); } + this.inherited(arguments); } }); }); diff --git a/_Controller.js b/_Controller.js index 77693da..5693637 100644 --- a/_Controller.js +++ b/_Controller.js @@ -2,9 +2,10 @@ define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/Stateful", + "dijit/Destroyable", "./_atBindingMixin" -], function(declare, lang, Stateful, _atBindingMixin){ - return declare("dojox.mvc._Controller", [Stateful, _atBindingMixin], { +], function(declare, lang, Stateful, Destroyable, _atBindingMixin){ + return declare("dojox.mvc._Controller", [Stateful, Destroyable, _atBindingMixin], { postscript: function(/*Object?*/ params, /*DomNode|String?*/ srcNodeRef){ // summary: // If this object is not called from Dojo parser, starts this up right away. diff --git a/tests/EditModelRefListControllerMixin.js b/tests/EditModelRefControllerMixin.js similarity index 93% rename from tests/EditModelRefListControllerMixin.js rename to tests/EditModelRefControllerMixin.js index 619e0ad..0699bc5 100644 --- a/tests/EditModelRefListControllerMixin.js +++ b/tests/EditModelRefControllerMixin.js @@ -5,20 +5,19 @@ define([ "../ModelRefController", "../EditModelRefControllerMixin", "../ListControllerMixin", - "../EditModelRefListControllerMixin", "../at", "../sync", "../getStateful" -], function(doh, declare, Stateful, ModelRefController, EditModelRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin, at, sync, getStateful){ +], function(doh, declare, Stateful, ModelRefController, EditModelRefControllerMixin, ListControllerMixin, at, sync, getStateful){ var data = [ {uniqueId: 0, value: "Index 0"}, {uniqueId: 1, value: "Index 1"}, {uniqueId: 2, value: "Index 2"}, {uniqueId: 3, value: "Index 3"}, {uniqueId: 4, value: "Index 4"} - ], clz = declare([ModelRefController, EditModelRefControllerMixin, ListControllerMixin, EditModelRefListControllerMixin], {}); + ], clz = declare([ModelRefController, EditModelRefControllerMixin, ListControllerMixin], {}); - doh.register("dojox.mvc.tests.EditModelRefListControllerMixin", [ + doh.register("dojox.mvc.tests.EditModelRefControllerMixin", [ function commit(){ var ctrl = new clz({sourceModel: getStateful(data), cursorIndex: 2}); ctrl.set("value", "3rd"); diff --git a/tests/module.js b/tests/module.js index e5988ca..462530e 100644 --- a/tests/module.js +++ b/tests/module.js @@ -6,7 +6,7 @@ define([ "./_Controller", "./ModelRefController", "./StoreRefControllerTest", - "./EditModelRefListControllerMixin", + "./EditModelRefControllerMixin", "./WidgetList", "./StatefulArray", "./StatefulModelOptions" diff --git a/tests/moduleFullSet.js b/tests/moduleFullSet.js index bc6737c..4e0830a 100644 --- a/tests/moduleFullSet.js +++ b/tests/moduleFullSet.js @@ -6,7 +6,7 @@ define([ "./_Controller", "./ModelRefController", "./StoreRefControllerTest", - "./EditModelRefListControllerMixin", + "./EditModelRefControllerMixin", "./WidgetList", "./StatefulArray", "./StatefulModelOptions"