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 | }); |
---|