]> git.proxmox.com Git - sencha-touch.git/blob - src/src/ux/parse/Model.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / ux / parse / Model.js
1 Ext.define('Ext.ux.parse.Model', {
2 extend: 'Ext.data.Model',
3 requires: [
4 'Ext.ux.parse.Helper',
5 'Ext.ux.parse.Proxy',
6 'Ext.Promise',
7 'Ext.ux.parse.association.Pointer',
8 'Ext.ux.parse.association.Relation'
9 ],
10
11 config: {
12 proxy: "parse"
13 },
14
15 inheritableStatics: {
16 getParseClass: function() {
17 if (!this.$parseClass) {
18 this.$parseClass = this.getName().split(".").pop();
19 }
20 return this.$parseClass;
21 }
22 },
23
24 isParseModel: true,
25
26 $parseObject: null,
27 getParseObject: function() {
28 return this.$parseObject;
29 },
30 setParseObject: function(value) {
31 this.$parseObject = value;
32 },
33
34 $parseClass: null,
35 getParseClass: function() {
36 if (!this.$parseClass) {
37 var modelClass = Ext.ModelManager.getModel(this.$className);
38 this.$parseClass = modelClass.getParseClass();
39 }
40
41 return this.$parseClass;
42 },
43
44 constructor: function(data, id, raw, convertedData) {
45 if (data instanceof Parse.Object) {
46 this.setParseObject(data);
47 id = id || data.id || null;
48 data = data.attributes;
49 } else {
50 var _data = raw || data || {};
51 id = _data.id || id || null;
52 this.setParseObject(ParseHelper.getObject(this.getParseClass(), _data));
53 }
54
55 return this.callParent([data, id, raw, convertedData]);
56 },
57
58 monitorRelations: function(config) {
59 config = config || {};
60 var me = this,
61 promise = new Ext.Promise,
62 callback = config.callback || Ext.emptyFn,
63 scope = config.scope || this;
64
65 (function run() {
66 var status = me.getRelationsStatus();
67 if(status.loading){
68 var association = status.loading.shift(),
69 store = association.getStore(me);
70
71 store.on("load", run, me, {single:true})
72 } else {
73 promise.fulfill();
74 callback.apply(scope);
75 }
76 })();
77
78 return promise;
79 },
80
81 getRelationsStatus: function() {
82 var me = this,
83 status = {relations: {}},
84 associations = this.getAssociations(),
85 relationStatus, relationStore;
86
87 associations.each(function(association) {
88 if (association.getType() === "relation" && association.getStatus) {
89 relationStatus = association.getStatus(me);
90 relationStore = association.getStore(me);
91
92 if(!Ext.isArray(status[relationStatus])){
93 status[relationStatus] = []
94 }
95 status[relationStatus].push(association);
96 status.relations[association.getName()] = {store:relationStore, status: relationStatus};
97 }
98 });
99
100 return status;
101 },
102
103 relationsLoaded: function() {
104 var associations = this.getAssociations(),
105 loaded = true,
106 relationStatus;
107
108 associations.each(function(association) {
109 if (association.getType() === "relation" && association.getStatus) {
110 relationStatus = association.getStatus(this);
111 loaded = status === "loading" || status === "unloaded";
112 }
113 });
114
115 return loaded;
116 },
117
118 getDataFlat: function() {
119 var me = this,
120 data = Ext.merge({}, this.data),
121 associations = this.getAssociations();
122
123 associations.each(function(association) {
124 if (association.getData) {
125 data[association.getName()] = association.getData(me);
126 }
127 });
128
129 return data;
130 },
131
132 load: function(options) {
133 options = options || {};
134 var me = this,
135 id = me.get("id") || options.id || null;
136
137 if (id && id.indexOf("ext-record") === -1) {
138 var modelClass = Ext.ModelManager.getModel(me.$className);
139 modelClass.load(id, {
140 success: function(record, operation) {
141 me.syncParse(me.getFields().all);
142 if (options.success) options.success.apply(options.scope || me, [me, operation]);
143 },
144 failure: function(record, operation) {
145 if (options.failure) options.failure.apply(options.scope || me, [me, operation]);
146 }
147 });
148 } else {
149 // <debug>
150 Ext.Logger.warn('You cannot load Parse models without a Parse ID');
151 // </debug>
152 }
153 },
154
155 syncParse: function(fields) {
156 var me = this, value;
157
158 Ext.Array.forEach(fields, function(field) {
159 if (field.isField) {
160 value = me.get(field.getName());
161 field = field.getName();
162 } else {
163 value = me.get(field);
164 }
165
166 if (value && value.isParseModel) {
167 value = value.getParseObject();
168 }
169
170 me.$parseObject.set(field, value);
171 });
172 },
173
174 afterEdit: function(modifiedFieldNames, modified) {
175 this.callParent(arguments);
176 this.syncParse(modifiedFieldNames);
177 },
178
179 /**
180 * Sets the given field to the given value, marks the instance as dirty.
181 * @param {String/Object} fieldName The field to set, or an object containing key/value pairs.
182 * @param {Object} value The value to set.
183 */
184 set: function(fieldName, value) {
185 var me = this,
186 // We are using the fields map since it saves lots of function calls
187 fieldMap = me.fields.map,
188 modified = me.modified,
189 notEditing = !me.editing,
190 modifiedCount = 0,
191 modifiedFieldNames = [],
192 field, key, i, ln, currentValue, convert;
193
194 /*
195 * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
196 * set those last so that all other possible data is set before the convert function is called
197 */
198 if (arguments.length == 1) {
199 for (key in fieldName) {
200 if (fieldName.hasOwnProperty(key)) {
201 //here we check for the custom convert function. Note that if a field doesn't have a convert function,
202 //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
203 field = fieldMap[key];
204 if (field && field.hasCustomConvert()) {
205 modifiedFieldNames.push(key);
206 continue;
207 }
208
209 if (!modifiedCount && notEditing) {
210 me.beginEdit();
211 }
212 ++modifiedCount;
213
214 if (!field) field = this.get(key);
215 if (field && field.isModel) {
216 field.set(fieldName[key]);
217 } else {
218 me.set(key, fieldName[key]);
219 }
220 }
221 }
222
223 ln = modifiedFieldNames.length;
224 if (ln) {
225 if (!modifiedCount && notEditing) {
226 me.beginEdit();
227 }
228 modifiedCount += ln;
229 for (i = 0; i < ln; i++) {
230 field = modifiedFieldNames[i];
231 me.set(field, fieldName[field]);
232 }
233 me.dirty = true;
234 }
235
236 if (notEditing && modifiedCount) {
237 me.endEdit(false, modifiedFieldNames);
238 }
239 } else if (modified) {
240 field = fieldMap[fieldName];
241 convert = field && field.getConvert();
242 if (convert) {
243 value = convert.call(field, value, me);
244 }
245
246 currentValue = me.data[fieldName];
247 if (currentValue && currentValue != value) {
248 me.fireEvent("fieldupdate", this, value, fieldName);
249 }
250 me.data[fieldName] = value;
251
252 if (field && !me.isEqual(currentValue, value)) {
253 if (modified.hasOwnProperty(fieldName)) {
254 if (me.isEqual(modified[fieldName], value)) {
255 // the original value in me.modified equals the new value, so the
256 // field is no longer modified
257 delete modified[fieldName];
258 // we might have removed the last modified field, so check to see if
259 // there are any modified fields remaining and correct me.dirty:
260 me.dirty = false;
261 for (key in modified) {
262 if (modified.hasOwnProperty(key)) {
263 me.dirty = true;
264 break;
265 }
266 }
267 }
268 } else {
269 me.dirty = true;
270 // We only go one level back?
271 modified[fieldName] = currentValue;
272 }
273 }
274
275 if (notEditing) {
276 me.afterEdit([fieldName], modified);
277 }
278 }
279
280 if (this.dirty) this.fireEvent("dirty", this);
281 }
282 });