[483] | 1 | define([ |
---|
| 2 | "dojo/_base/array", |
---|
| 3 | "dojo/_base/declare", |
---|
| 4 | "dojo/_base/lang", |
---|
| 5 | "dojo/Stateful", |
---|
| 6 | "./_Controller" |
---|
| 7 | ], function(array, declare, lang, Stateful, _Controller){ |
---|
| 8 | return declare("dojox.mvc.ModelRefController", _Controller, { |
---|
| 9 | // summary: |
---|
| 10 | // A controller that keeps a reference to dojo/Stateful-based data model. |
---|
| 11 | // description: |
---|
| 12 | // Does the following on behalf of such model: |
---|
| 13 | // |
---|
| 14 | // - Provides data from model via dojo/Stateful get() interface |
---|
| 15 | // - Stores data to model via dojo/Stateful set() interface |
---|
| 16 | // - Watches for change in model via dojo/Stateful watch() interface (The callback is called when there is a change in data model, as well as when the data model itself is replaced with different one) |
---|
| 17 | // |
---|
| 18 | // Can also be used to do some application-specific stuffs upon change in properties in model, by defining setter functions. |
---|
| 19 | // Doing so will help keep models and widgets free from application-specific logic, and will help keep application logic free from specifics of models and widgets. |
---|
| 20 | // Such kind of setter functions can be defined in the same manner as widgets (_setXXXAttr()). |
---|
| 21 | // |
---|
| 22 | // 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. |
---|
| 23 | // example: |
---|
| 24 | // The text box refers to "value" property in the controller (with "ctrl" ID). |
---|
| 25 | // The controller provides the "value" property on behalf of the model ("model" property in the controller). |
---|
| 26 | // Two seconds later, the text box changes from "Foo" to "Bar" as the controller changes the data model it refers to. |
---|
| 27 | // | <html> |
---|
| 28 | // | <head> |
---|
| 29 | // | <script src="/path/to/dojo-toolkit/dojo/dojo.js" type="text/javascript" data-dojo-config="parseOnLoad: 0"></script> |
---|
| 30 | // | <script type="text/javascript"> |
---|
| 31 | // | require([ |
---|
| 32 | // | "dojo/parser", "dojo/Stateful", "dijit/registry", |
---|
| 33 | // | "dijit/form/TextBox", "dojox/mvc/ModelRefController", "dojo/domReady!" |
---|
| 34 | // | ], function(parser, Stateful, registry){ |
---|
| 35 | // | modelFoo = new Stateful({value: "Foo"}); |
---|
| 36 | // | modelBar = new Stateful({value: "Bar"}); |
---|
| 37 | // | setTimeout(function(){ registry.byId("ctrl").set("model", modelBar); }, 2000); |
---|
| 38 | // | parser.parse(); |
---|
| 39 | // | }); |
---|
| 40 | // | </script> |
---|
| 41 | // | </head> |
---|
| 42 | // | <body> |
---|
| 43 | // | <script type="dojo/require">at: "dojox/mvc/at"</script> |
---|
| 44 | // | <span id="ctrl" data-dojo-type="dojox/mvc/ModelRefController" data-dojo-props="model: modelFoo"></span> |
---|
| 45 | // | <input type="text" data-dojo-type="dijit/form/TextBox" data-dojo-props="value: at('widget:ctrl', 'value')"> |
---|
| 46 | // | </body> |
---|
| 47 | // | </html> |
---|
| 48 | |
---|
| 49 | // ownProps: Object |
---|
| 50 | // List of property names owned by this controller, instead of the data model. |
---|
| 51 | ownProps: null, |
---|
| 52 | |
---|
| 53 | // _refModelProp: String |
---|
| 54 | // The property name for the data model. |
---|
| 55 | _refModelProp: "model", |
---|
| 56 | |
---|
| 57 | // _refInModelProp: String |
---|
| 58 | // The property name for the data model, used as the input. |
---|
| 59 | // Used when this controller needs data model (as input) that is different from the data model this controller provides. |
---|
| 60 | _refInModelProp: "model", |
---|
| 61 | |
---|
| 62 | // model: dojo/Stateful |
---|
| 63 | // The data model. |
---|
| 64 | model: null, |
---|
| 65 | |
---|
| 66 | postscript: function(/*Object?*/ params, /*DomNode|String?*/ srcNodeRef){ |
---|
| 67 | // summary: |
---|
| 68 | // Sets _relTargetProp so that the property specified by _refModelProp is used for relative data binding. |
---|
| 69 | |
---|
| 70 | this._relTargetProp = (params || {})._refModelProp || this._refModelProp; |
---|
| 71 | this.inherited(arguments); |
---|
| 72 | }, |
---|
| 73 | |
---|
| 74 | get: function(/*String*/ name){ |
---|
| 75 | // summary: |
---|
| 76 | // If getter function is there, use it. Otherwise, get the data from data model of this object. |
---|
| 77 | // name: String |
---|
| 78 | // The property name. |
---|
| 79 | |
---|
| 80 | if(!this.hasControllerProperty(name)){ |
---|
| 81 | var model = this[this._refModelProp]; |
---|
| 82 | return !model ? void 0 : model.get ? model.get(name) : model[name]; |
---|
| 83 | } |
---|
| 84 | return this.inherited(arguments); |
---|
| 85 | }, |
---|
| 86 | |
---|
| 87 | _set: function(/*String*/ name, /*Anything*/ value){ |
---|
| 88 | // summary: |
---|
| 89 | // Set the value to the data model or to this object. |
---|
| 90 | // name: String |
---|
| 91 | // The property name. |
---|
| 92 | // value: Anything |
---|
| 93 | // The property value. |
---|
| 94 | |
---|
| 95 | if(!this.hasControllerProperty(name)){ |
---|
| 96 | var model = this[this._refModelProp]; |
---|
| 97 | model && (model.set ? model.set(name, value) : (model[name] = value)); |
---|
| 98 | return this; |
---|
| 99 | } |
---|
| 100 | return this.inherited(arguments); |
---|
| 101 | }, |
---|
| 102 | |
---|
| 103 | watch: function(/*String?*/ name, /*Function*/ callback){ |
---|
| 104 | // summary: |
---|
| 105 | // Watch a property in the data model or in this object. |
---|
| 106 | // name: String? |
---|
| 107 | // The property name. |
---|
| 108 | // callback: Function |
---|
| 109 | // The callback function. |
---|
| 110 | |
---|
| 111 | if(this.hasControllerProperty(name)){ |
---|
| 112 | return this.inherited(arguments); |
---|
| 113 | } |
---|
| 114 | |
---|
| 115 | if(!callback){ |
---|
| 116 | callback = name; |
---|
| 117 | name = null; |
---|
| 118 | } |
---|
| 119 | |
---|
| 120 | var hm = null, hp = null, _self = this; |
---|
| 121 | |
---|
| 122 | function watchPropertiesInModel(/*dojo/Stateful*/ model){ |
---|
| 123 | // summary: |
---|
| 124 | // Watch properties in referred model. |
---|
| 125 | // model: dojo/Stateful |
---|
| 126 | // The model to watch for. |
---|
| 127 | |
---|
| 128 | // Unwatch properties of older model. |
---|
| 129 | if(hp){ hp.unwatch(); } |
---|
| 130 | // Watch properties of newer model. |
---|
| 131 | if(model && lang.isFunction(model.set) && lang.isFunction(model.watch)){ |
---|
| 132 | hp = model.watch.apply(model, (name ? [name] : []).concat([function(name, old, current){ callback.call(_self, name, old, current); }])); |
---|
| 133 | } |
---|
| 134 | } |
---|
| 135 | |
---|
| 136 | function reflectChangeInModel(/*dojo/Stateful*/ old, /*dojo/Stateful*/ current){ |
---|
| 137 | // summary: |
---|
| 138 | // Upon change in model, detect change in properties, and call watch callbacks. |
---|
| 139 | // old: dojo/Stateful |
---|
| 140 | // The older model. |
---|
| 141 | // current: dojo/Stateful |
---|
| 142 | // The newer model. |
---|
| 143 | |
---|
| 144 | // Gather list of properties to notify change in value as model changes. |
---|
| 145 | var props = {}; |
---|
| 146 | if(!name){ |
---|
| 147 | // If all properties are being watched, find out all properties from older model as well as from newer model. |
---|
| 148 | array.forEach([old, current], function(model){ |
---|
| 149 | var list = model && model.get("properties"); |
---|
| 150 | if(list){ |
---|
| 151 | // If the model explicitly specifies the list of properties, use it. |
---|
| 152 | array.forEach(list, function(item){ |
---|
| 153 | if(!_self.hasControllerProperty(item)){ props[item] = 1; } |
---|
| 154 | }); |
---|
| 155 | }else{ |
---|
| 156 | // Otherwise, iterate through own properties. |
---|
| 157 | for(var s in model){ |
---|
| 158 | if(model.hasOwnProperty(s) && !_self.hasControllerProperty(s)){ props[s] = 1; } |
---|
| 159 | } |
---|
| 160 | } |
---|
| 161 | }); |
---|
| 162 | }else{ |
---|
| 163 | props[name] = 1; |
---|
| 164 | } |
---|
| 165 | |
---|
| 166 | // Call watch callbacks for properties. |
---|
| 167 | for(var s in props){ |
---|
| 168 | callback.call(_self, s, !old ? void 0 : old.get ? old.get(s) : old[s], !current ? void 0 : current.get ? current.get(s) : current[s]); |
---|
| 169 | } |
---|
| 170 | } |
---|
| 171 | |
---|
| 172 | // Watch for change in model. |
---|
| 173 | hm = Stateful.prototype.watch.call(this, this._refModelProp, function(name, old, current){ |
---|
| 174 | if(old === current){ return; } |
---|
| 175 | reflectChangeInModel(old, current); |
---|
| 176 | watchPropertiesInModel(current); |
---|
| 177 | }); |
---|
| 178 | |
---|
| 179 | // Watch for properties in model. |
---|
| 180 | watchPropertiesInModel(this.get(this._refModelProp)); |
---|
| 181 | |
---|
| 182 | var h = {}; |
---|
| 183 | h.unwatch = h.remove = function(){ |
---|
| 184 | if(hp){ hp.unwatch(); hp = null; } if(hm){ hm.unwatch(); hm = null; } |
---|
| 185 | }; |
---|
| 186 | return h; // dojo/handle |
---|
| 187 | }, |
---|
| 188 | |
---|
| 189 | hasControllerProperty: function(/*String*/ name){ |
---|
| 190 | // summary: |
---|
| 191 | // Returns true if this controller itself owns the given property. |
---|
| 192 | // name: String |
---|
| 193 | // The property name. |
---|
| 194 | |
---|
| 195 | return name == "_watchCallbacks" || name == this._refModelProp || name == this._refInModelProp || (name in (this.ownProps || {})) || (name in this.constructor.prototype) || /^dojoAttach(Point|Event)$/i.test(name); // Let dojoAttachPoint/dojoAttachEvent be this controller's property to support <span data-dojo-type="dojox/mvc/ModelRefController" data-dojo-attach-point="controllerNode"> in widgets-in-template |
---|
| 196 | } |
---|
| 197 | }); |
---|
| 198 | }); |
---|