Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 1
if(!dojo._hasResource["dojo.data.ItemFileWriteStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2
dojo._hasResource["dojo.data.ItemFileWriteStore"] = true;
3
dojo.provide("dojo.data.ItemFileWriteStore");
4
dojo.require("dojo.data.ItemFileReadStore");
5
 
6
dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
7
	constructor: function(/* object */ keywordParameters){
8
		//	keywordParameters: {typeMap: object)
9
		//		The structure of the typeMap object is as follows:
10
		//		{
11
		//			type0: function || object,
12
		//			type1: function || object,
13
		//			...
14
		//			typeN: function || object
15
		//		}
16
		//		Where if it is a function, it is assumed to be an object constructor that takes the
17
		//		value of _value as the initialization parameters.  It is serialized assuming object.toString()
18
		//		serialization.  If it is an object, then it is assumed
19
		//		to be an object of general form:
20
		//		{
21
		//			type: function, //constructor.
22
		//			deserialize:	function(value) //The function that parses the value and constructs the object defined by type appropriately.
23
		//			serialize:	function(object) //The function that converts the object back into the proper file format form.
24
		//		}
25
 
26
		// ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs
27
		this._features['dojo.data.api.Write'] = true;
28
		this._features['dojo.data.api.Notification'] = true;
29
 
30
		// For keeping track of changes so that we can implement isDirty and revert
31
		this._pending = {
32
			_newItems:{},
33
			_modifiedItems:{},
34
			_deletedItems:{}
35
		};
36
 
37
		if(!this._datatypeMap['Date'].serialize){
38
			this._datatypeMap['Date'].serialize = function(obj){
39
				return dojo.date.stamp.toISOString(obj, {zulu:true});
40
			}
41
		}
42
 
43
		// this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
44
		this._saveInProgress = false;
45
	},
46
 
47
	_assert: function(/* boolean */ condition){
48
		if(!condition) {
49
			throw new Error("assertion failed in ItemFileWriteStore");
50
		}
51
	},
52
 
53
	_getIdentifierAttribute: function(){
54
		var identifierAttribute = this.getFeatures()['dojo.data.api.Identity'];
55
		// this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
56
		return identifierAttribute;
57
	},
58
 
59
 
60
/* dojo.data.api.Write */
61
 
62
	newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){
63
		// summary: See dojo.data.api.Write.newItem()
64
 
65
		this._assert(!this._saveInProgress);
66
 
67
		if (!this._loadFinished){
68
			// We need to do this here so that we'll be able to find out what
69
			// identifierAttribute was specified in the data file.
70
			this._forceLoad();
71
		}
72
 
73
		if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){
74
			throw new Error("newItem() was passed something other than an object");
75
		}
76
		var newIdentity = null;
77
		var identifierAttribute = this._getIdentifierAttribute();
78
		if(identifierAttribute === Number){
79
			newIdentity = this._arrayOfAllItems.length;
80
		}else{
81
			newIdentity = keywordArgs[identifierAttribute];
82
			if (typeof newIdentity === "undefined"){
83
				throw new Error("newItem() was not passed an identity for the new item");
84
			}
85
			if (dojo.isArray(newIdentity)){
86
				throw new Error("newItem() was not passed an single-valued identity");
87
			}
88
		}
89
 
90
		// make sure this identity is not already in use by another item, if identifiers were
91
		// defined in the file.  Otherwise it would be the item count,
92
		// which should always be unique in this case.
93
		if(this._itemsByIdentity){
94
			this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined");
95
		}
96
		this._assert(typeof this._pending._newItems[newIdentity] === "undefined");
97
		this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined");
98
 
99
		var newItem = {};
100
		newItem[this._storeRefPropName] = this;
101
		newItem[this._itemNumPropName] = this._arrayOfAllItems.length;
102
		if(this._itemsByIdentity){
103
			this._itemsByIdentity[newIdentity] = newItem;
104
		}
105
		this._arrayOfAllItems.push(newItem);
106
 
107
		//We need to construct some data for the onNew call too...
108
		var pInfo = null;
109
 
110
		// Now we need to check to see where we want to assign this thingm if any.
111
		if(parentInfo && parentInfo.parent && parentInfo.attribute){
112
			pInfo = {
113
				item: parentInfo.parent,
114
				attribute: parentInfo.attribute,
115
				oldValue: undefined
116
			};
117
 
118
			//See if it is multi-valued or not and handle appropriately
119
			//Generally, all attributes are multi-valued for this store
120
			//So, we only need to append if there are already values present.
121
			var values = this.getValues(parentInfo.parent, parentInfo.attribute);
122
			if(values && values.length > 0){
123
				var tempValues = values.slice(0, values.length);
124
				if(values.length === 1){
125
					pInfo.oldValue = values[0];
126
				}else{
127
					pInfo.oldValue = values.slice(0, values.length);
128
				}
129
				tempValues.push(newItem);
130
				this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false);
131
				pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
132
			}else{
133
				this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false);
134
				pInfo.newValue = newItem;
135
			}
136
		}else{
137
			//Toplevel item, add to both top list as well as all list.
138
			newItem[this._rootItemPropName]=true;
139
			this._arrayOfTopLevelItems.push(newItem);
140
		}
141
 
142
		this._pending._newItems[newIdentity] = newItem;
143
 
144
		//Clone over the properties to the new item
145
		for(var key in keywordArgs){
146
			if(key === this._storeRefPropName || key === this._itemNumPropName){
147
				// Bummer, the user is trying to do something like
148
				// newItem({_S:"foo"}).  Unfortunately, our superclass,
149
				// ItemFileReadStore, is already using _S in each of our items
150
				// to hold private info.  To avoid a naming collision, we
151
				// need to move all our private info to some other property
152
				// of all the items/objects.  So, we need to iterate over all
153
				// the items and do something like:
154
				//    item.__S = item._S;
155
				//    item._S = undefined;
156
				// But first we have to make sure the new "__S" variable is
157
				// not in use, which means we have to iterate over all the
158
				// items checking for that.
159
				throw new Error("encountered bug in ItemFileWriteStore.newItem");
160
			}
161
			var value = keywordArgs[key];
162
			if(!dojo.isArray(value)){
163
				value = [value];
164
			}
165
			newItem[key] = value;
166
		}
167
		this.onNew(newItem, pInfo); // dojo.data.api.Notification call
168
		return newItem; // item
169
	},
170
 
171
	_removeArrayElement: function(/* Array */ array, /* anything */ element){
172
		var index = dojo.indexOf(array, element);
173
		if (index != -1){
174
			array.splice(index, 1);
175
			return true;
176
		}
177
		return false;
178
	},
179
 
180
	deleteItem: function(/* item */ item){
181
		// summary: See dojo.data.api.Write.deleteItem()
182
		this._assert(!this._saveInProgress);
183
		this._assertIsItem(item);
184
 
185
		// remove this item from the _arrayOfAllItems, but leave a null value in place
186
		// of the item, so as not to change the length of the array, so that in newItem()
187
		// we can still safely do: newIdentity = this._arrayOfAllItems.length;
188
		var indexInArrayOfAllItems = item[this._itemNumPropName];
189
		this._arrayOfAllItems[indexInArrayOfAllItems] = null;
190
 
191
		var identity = this.getIdentity(item);
192
		item[this._storeRefPropName] = null;
193
		if(this._itemsByIdentity){
194
			delete this._itemsByIdentity[identity];
195
		}
196
		this._pending._deletedItems[identity] = item;
197
 
198
		//Remove from the toplevel items, if necessary...
199
		if(item[this._rootItemPropName]){
200
			this._removeArrayElement(this._arrayOfTopLevelItems, item);
201
		}
202
		this.onDelete(item); // dojo.data.api.Notification call
203
		return true;
204
	},
205
 
206
	setValue: function(/* item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){
207
		// summary: See dojo.data.api.Write.set()
208
		return this._setValueOrValues(item, attribute, value, true); // boolean
209
	},
210
 
211
	setValues: function(/* item */ item, /* attribute-name-string */ attribute, /* array */ values){
212
		// summary: See dojo.data.api.Write.setValues()
213
		return this._setValueOrValues(item, attribute, values, true); // boolean
214
	},
215
 
216
	unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){
217
		// summary: See dojo.data.api.Write.unsetAttribute()
218
		return this._setValueOrValues(item, attribute, [], true);
219
	},
220
 
221
	_setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){
222
		this._assert(!this._saveInProgress);
223
 
224
		// Check for valid arguments
225
		this._assertIsItem(item);
226
		this._assert(dojo.isString(attribute));
227
		this._assert(typeof newValueOrValues !== "undefined");
228
 
229
		// Make sure the user isn't trying to change the item's identity
230
		var identifierAttribute = this._getIdentifierAttribute();
231
		if(attribute == identifierAttribute){
232
			throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");
233
		}
234
 
235
		// To implement the Notification API, we need to make a note of what
236
		// the old attribute value was, so that we can pass that info when
237
		// we call the onSet method.
238
		var oldValueOrValues = this._getValueOrValues(item, attribute);
239
 
240
		var identity = this.getIdentity(item);
241
		if(!this._pending._modifiedItems[identity]){
242
			// Before we actually change the item, we make a copy of it to
243
			// record the original state, so that we'll be able to revert if
244
			// the revert method gets called.  If the item has already been
245
			// modified then there's no need to do this now, since we already
246
			// have a record of the original state.
247
			var copyOfItemState = {};
248
			for(var key in item){
249
				if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){
250
					copyOfItemState[key] = item[key];
251
				}else{
252
					var valueArray = item[key];
253
					var copyOfValueArray = [];
254
					for(var i = 0; i < valueArray.length; ++i){
255
						copyOfValueArray.push(valueArray[i]);
256
					}
257
					copyOfItemState[key] = copyOfValueArray;
258
				}
259
			}
260
			// Now mark the item as dirty, and save the copy of the original state
261
			this._pending._modifiedItems[identity] = copyOfItemState;
262
		}
263
 
264
		// Okay, now we can actually change this attribute on the item
265
		var success = false;
266
		if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){
267
			// If we were passed an empty array as the value, that counts
268
			// as "unsetting" the attribute, so we need to remove this
269
			// attribute from the item.
270
			success = delete item[attribute];
271
			newValueOrValues = undefined; // used in the onSet Notification call below
272
		}else{
273
			var newValueArray = [];
274
			if(dojo.isArray(newValueOrValues)){
275
				var newValues = newValueOrValues;
276
				// Unforunately, it's not safe to just do this:
277
				//    newValueArray = newValues;
278
				// Instead, we need to take each value in the values array and copy
279
				// it into the new array, so that our internal data structure won't
280
				// get corrupted if the user mucks with the values array *after*
281
				// calling setValues().
282
				for(var j = 0; j < newValues.length; ++j){
283
					newValueArray.push(newValues[j]);
284
				}
285
			}else{
286
				var newValue = newValueOrValues;
287
				newValueArray.push(newValue);
288
			}
289
			item[attribute] = newValueArray;
290
			success = true;
291
		}
292
 
293
		// Now we make the dojo.data.api.Notification call
294
		if(callOnSet){
295
			this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
296
		}
297
		return success; // boolean
298
	},
299
 
300
	_getValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute){
301
		var valueOrValues = undefined;
302
		if(this.hasAttribute(item, attribute)){
303
			var valueArray = this.getValues(item, attribute);
304
			if(valueArray.length == 1){
305
				valueOrValues = valueArray[0];
306
			}else{
307
				valueOrValues = valueArray;
308
			}
309
		}
310
		return valueOrValues;
311
	},
312
 
313
	_flatten: function(/* anything */ value){
314
		if(this.isItem(value)){
315
			var item = value;
316
			// Given an item, return an serializable object that provides a
317
			// reference to the item.
318
			// For example, given kermit:
319
			//    var kermit = store.newItem({id:2, name:"Kermit"});
320
			// we want to return
321
			//    {_reference:2}
322
			var identity = this.getIdentity(item);
323
			var referenceObject = {_reference: identity};
324
			return referenceObject;
325
		}else{
326
			if(typeof value === "object"){
327
				for(type in this._datatypeMap){
328
					var typeMap = this._datatypeMap[type];
329
					if (dojo.isObject(typeMap) && !dojo.isFunction(typeMap)){
330
						if(value instanceof typeMap.type){
331
							if(!typeMap.serialize){
332
								throw new Error("ItemFileWriteStore:  No serializer defined for type mapping: [" + type + "]");
333
							}
334
							return {_type: type, _value: typeMap.serialize(value)};
335
						}
336
					} else if(value instanceof typeMap){
337
						//SImple mapping, therefore, return as a toString serialization.
338
						return {_type: type, _value: value.toString()};
339
					}
340
				}
341
			}
342
			return value;
343
		}
344
	},
345
 
346
	_getNewFileContentString: function(){
347
		// summary:
348
		//		Generate a string that can be saved to a file.
349
		//		The result should look similar to:
350
		//		http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
351
		var serializableStructure = {};
352
 
353
		var identifierAttribute = this._getIdentifierAttribute();
354
		if(identifierAttribute !== Number){
355
			serializableStructure.identifier = identifierAttribute;
356
		}
357
		if(this._labelAttr){
358
			serializableStructure.label = this._labelAttr;
359
		}
360
		serializableStructure.items = [];
361
		for(var i = 0; i < this._arrayOfAllItems.length; ++i){
362
			var item = this._arrayOfAllItems[i];
363
			if(item !== null){
364
				serializableItem = {};
365
				for(var key in item){
366
					if(key !== this._storeRefPropName && key !== this._itemNumPropName){
367
						var attribute = key;
368
						var valueArray = this.getValues(item, attribute);
369
						if(valueArray.length == 1){
370
							serializableItem[attribute] = this._flatten(valueArray[0]);
371
						}else{
372
							var serializableArray = [];
373
							for(var j = 0; j < valueArray.length; ++j){
374
								serializableArray.push(this._flatten(valueArray[j]));
375
								serializableItem[attribute] = serializableArray;
376
							}
377
						}
378
					}
379
				}
380
				serializableStructure.items.push(serializableItem);
381
			}
382
		}
383
		var prettyPrint = true;
384
		return dojo.toJson(serializableStructure, prettyPrint);
385
	},
386
 
387
	save: function(/* object */ keywordArgs){
388
		// summary: See dojo.data.api.Write.save()
389
		this._assert(!this._saveInProgress);
390
 
391
		// this._saveInProgress is set to true, briefly, from when save is first called to when it completes
392
		this._saveInProgress = true;
393
 
394
		var self = this;
395
		var saveCompleteCallback = function(){
396
			self._pending = {
397
				_newItems:{},
398
				_modifiedItems:{},
399
				_deletedItems:{}
400
			};
401
			self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks
402
			if(keywordArgs && keywordArgs.onComplete){
403
				var scope = keywordArgs.scope || dojo.global;
404
				keywordArgs.onComplete.call(scope);
405
			}
406
		};
407
		var saveFailedCallback = function(){
408
			self._saveInProgress = false;
409
			if(keywordArgs && keywordArgs.onError){
410
				var scope = keywordArgs.scope || dojo.global;
411
				keywordArgs.onError.call(scope);
412
			}
413
		};
414
 
415
		if(this._saveEverything){
416
			var newFileContentString = this._getNewFileContentString();
417
			this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString);
418
		}
419
		if(this._saveCustom){
420
			this._saveCustom(saveCompleteCallback, saveFailedCallback);
421
		}
422
		if(!this._saveEverything && !this._saveCustom){
423
			// Looks like there is no user-defined save-handler function.
424
			// That's fine, it just means the datastore is acting as a "mock-write"
425
			// store -- changes get saved in memory but don't get saved to disk.
426
			saveCompleteCallback();
427
		}
428
	},
429
 
430
	revert: function(){
431
		// summary: See dojo.data.api.Write.revert()
432
		this._assert(!this._saveInProgress);
433
 
434
		var identity;
435
		for(identity in this._pending._newItems){
436
			var newItem = this._pending._newItems[identity];
437
			newItem[this._storeRefPropName] = null;
438
			// null out the new item, but don't change the array index so
439
			// so we can keep using _arrayOfAllItems.length.
440
			this._arrayOfAllItems[newItem[this._itemNumPropName]] = null;
441
			if(newItem[this._rootItemPropName]){
442
				this._removeArrayElement(this._arrayOfTopLevelItems, newItem);
443
			}
444
			if(this._itemsByIdentity){
445
				delete this._itemsByIdentity[identity];
446
			}
447
		}
448
		for(identity in this._pending._modifiedItems){
449
			// find the original item and the modified item that replaced it
450
			var originalItem = this._pending._modifiedItems[identity];
451
			var modifiedItem = null;
452
			if(this._itemsByIdentity){
453
				modifiedItem = this._itemsByIdentity[identity];
454
			}else{
455
				modifiedItem = this._arrayOfAllItems[identity];
456
			}
457
 
458
			// make the original item into a full-fledged item again
459
			originalItem[this._storeRefPropName] = this;
460
			modifiedItem[this._storeRefPropName] = null;
461
 
462
			// replace the modified item with the original one
463
			var arrayIndex = modifiedItem[this._itemNumPropName];
464
			this._arrayOfAllItems[arrayIndex] = originalItem;
465
 
466
			if(modifiedItem[this._rootItemPropName]){
467
				arrayIndex = modifiedItem[this._itemNumPropName];
468
				this._arrayOfTopLevelItems[arrayIndex] = originalItem;
469
			}
470
			if(this._itemsByIdentity){
471
				this._itemsByIdentity[identity] = originalItem;
472
			}
473
		}
474
		for(identity in this._pending._deletedItems){
475
			var deletedItem = this._pending._deletedItems[identity];
476
			deletedItem[this._storeRefPropName] = this;
477
			var index = deletedItem[this._itemNumPropName];
478
			this._arrayOfAllItems[index] = deletedItem;
479
			if (this._itemsByIdentity) {
480
				this._itemsByIdentity[identity] = deletedItem;
481
			}
482
			if(deletedItem[this._rootItemPropName]){
483
				this._arrayOfTopLevelItems.push(deletedItem);
484
			}
485
		}
486
		this._pending = {
487
			_newItems:{},
488
			_modifiedItems:{},
489
			_deletedItems:{}
490
		};
491
		return true; // boolean
492
	},
493
 
494
	isDirty: function(/* item? */ item){
495
		// summary: See dojo.data.api.Write.isDirty()
496
		if(item){
497
			// return true if the item is dirty
498
			var identity = this.getIdentity(item);
499
			return new Boolean(this._pending._newItems[identity] ||
500
				this._pending._modifiedItems[identity] ||
501
				this._pending._deletedItems[identity]); // boolean
502
		}else{
503
			// return true if the store is dirty -- which means return true
504
			// if there are any new items, dirty items, or modified items
505
			var key;
506
			for(key in this._pending._newItems){
507
				return true;
508
			}
509
			for(key in this._pending._modifiedItems){
510
				return true;
511
			}
512
			for(key in this._pending._deletedItems){
513
				return true;
514
			}
515
			return false; // boolean
516
		}
517
	},
518
 
519
/* dojo.data.api.Notification */
520
 
521
	onSet: function(/* item */ item,
522
					/*attribute-name-string*/ attribute,
523
					/*object | array*/ oldValue,
524
					/*object | array*/ newValue){
525
		// summary: See dojo.data.api.Notification.onSet()
526
 
527
		// No need to do anything. This method is here just so that the
528
		// client code can connect observers to it.
529
	},
530
 
531
	onNew: function(/* item */ newItem, /*object?*/ parentInfo){
532
		// summary: See dojo.data.api.Notification.onNew()
533
 
534
		// No need to do anything. This method is here just so that the
535
		// client code can connect observers to it.
536
	},
537
 
538
	onDelete: function(/* item */ deletedItem){
539
		// summary: See dojo.data.api.Notification.onDelete()
540
 
541
		// No need to do anything. This method is here just so that the
542
		// client code can connect observers to it.
543
	}
544
 
545
});
546
 
547
}