]>
git.wh0rd.org - tt-rss.git/blob - lib/dojo/data/ItemFileWriteStore.js.uncompressed.js
1 define ( "dojo/data/ItemFileWriteStore" , [ "../_base/lang" , "../_base/declare" , "../_base/array" , "../_base/json" , "../_base/window" ,
2 "./ItemFileReadStore" , "../date/stamp"
3 ], function ( lang
, declare
, arrayUtil
, jsonUtil
, window
, ItemFileReadStore
, dateStamp
) {
5 // dojo/data/ItemFileWriteStore
9 /*===== var ItemFileReadStore = dojo.data.ItemFileReadStore; =====*/
10 return declare ( "dojo.data.ItemFileWriteStore" , ItemFileReadStore
, {
11 constructor : function ( /* object */ keywordParameters
){
12 // keywordParameters: {typeMap: object)
13 // The structure of the typeMap object is as follows:
15 // type0: function || object,
16 // type1: function || object,
18 // typeN: function || object
20 // Where if it is a function, it is assumed to be an object constructor that takes the
21 // value of _value as the initialization parameters. It is serialized assuming object.toString()
22 // serialization. If it is an object, then it is assumed
23 // to be an object of general form:
25 // type: function, //constructor.
26 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
27 // serialize: function(object) //The function that converts the object back into the proper file format form.
30 // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs
31 this . _features
[ 'dojo.data.api.Write' ] = true ;
32 this . _features
[ 'dojo.data.api.Notification' ] = true ;
34 // For keeping track of changes so that we can implement isDirty and revert
41 if (! this . _datatypeMap
[ 'Date' ]. serialize
){
42 this . _datatypeMap
[ 'Date' ]. serialize = function ( obj
){
43 return dateStamp
. toISOString ( obj
, { zulu
: true });
46 //Disable only if explicitly set to false.
47 if ( keywordParameters
&& ( keywordParameters
. referenceIntegrity
=== false )){
48 this . referenceIntegrity
= false ;
51 // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
52 this . _saveInProgress
= false ;
55 referenceIntegrity
: true , //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively.
57 _assert : function ( /* boolean */ condition
){
59 throw new Error ( "assertion failed in ItemFileWriteStore" );
63 _getIdentifierAttribute : function (){
64 // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
65 return this . getFeatures ()[ 'dojo.data.api.Identity' ];
69 /* dojo.data.api.Write */
71 newItem : function ( /* Object? */ keywordArgs
, /* Object? */ parentInfo
){
72 // summary: See dojo.data.api.Write.newItem()
74 this . _assert (! this . _saveInProgress
);
76 if (! this . _loadFinished
){
77 // We need to do this here so that we'll be able to find out what
78 // identifierAttribute was specified in the data file.
82 if ( typeof keywordArgs
!= "object" && typeof keywordArgs
!= "undefined" ){
83 throw new Error ( "newItem() was passed something other than an object" );
85 var newIdentity
= null ;
86 var identifierAttribute
= this . _getIdentifierAttribute ();
87 if ( identifierAttribute
=== Number
){
88 newIdentity
= this . _arrayOfAllItems
. length
;
90 newIdentity
= keywordArgs
[ identifierAttribute
];
91 if ( typeof newIdentity
=== "undefined" ){
92 throw new Error ( "newItem() was not passed an identity for the new item" );
94 if ( lang
. isArray ( newIdentity
)){
95 throw new Error ( "newItem() was not passed an single-valued identity" );
99 // make sure this identity is not already in use by another item, if identifiers were
100 // defined in the file. Otherwise it would be the item count,
101 // which should always be unique in this case.
102 if ( this . _itemsByIdentity
){
103 this . _assert ( typeof this . _itemsByIdentity
[ newIdentity
] === "undefined" );
105 this . _assert ( typeof this . _pending
. _newItems
[ newIdentity
] === "undefined" );
106 this . _assert ( typeof this . _pending
. _deletedItems
[ newIdentity
] === "undefined" );
109 newItem
[ this . _storeRefPropName
] = this ;
110 newItem
[ this . _itemNumPropName
] = this . _arrayOfAllItems
. length
;
111 if ( this . _itemsByIdentity
){
112 this . _itemsByIdentity
[ newIdentity
] = newItem
;
113 //We have to set the identifier now, otherwise we can't look it
114 //up at calls to setValueorValues in parentInfo handling.
115 newItem
[ identifierAttribute
] = [ newIdentity
];
117 this . _arrayOfAllItems
. push ( newItem
);
119 //We need to construct some data for the onNew call too...
122 // Now we need to check to see where we want to assign this thingm if any.
123 if ( parentInfo
&& parentInfo
. parent
&& parentInfo
. attribute
){
125 item
: parentInfo
. parent
,
126 attribute
: parentInfo
. attribute
,
130 //See if it is multi-valued or not and handle appropriately
131 //Generally, all attributes are multi-valued for this store
132 //So, we only need to append if there are already values present.
133 var values
= this . getValues ( parentInfo
. parent
, parentInfo
. attribute
);
134 if ( values
&& values
. length
> 0 ){
135 var tempValues
= values
. slice ( 0 , values
. length
);
136 if ( values
. length
=== 1 ){
137 pInfo
. oldValue
= values
[ 0 ];
139 pInfo
. oldValue
= values
. slice ( 0 , values
. length
);
141 tempValues
. push ( newItem
);
142 this . _setValueOrValues ( parentInfo
. parent
, parentInfo
. attribute
, tempValues
, false );
143 pInfo
. newValue
= this . getValues ( parentInfo
. parent
, parentInfo
. attribute
);
145 this . _setValueOrValues ( parentInfo
. parent
, parentInfo
. attribute
, newItem
, false );
146 pInfo
. newValue
= newItem
;
149 //Toplevel item, add to both top list as well as all list.
150 newItem
[ this . _rootItemPropName
]= true ;
151 this . _arrayOfTopLevelItems
. push ( newItem
);
154 this . _pending
. _newItems
[ newIdentity
] = newItem
;
156 //Clone over the properties to the new item
157 for ( var key
in keywordArgs
){
158 if ( key
=== this . _storeRefPropName
|| key
=== this . _itemNumPropName
){
159 // Bummer, the user is trying to do something like
160 // newItem({_S:"foo"}). Unfortunately, our superclass,
161 // ItemFileReadStore, is already using _S in each of our items
162 // to hold private info. To avoid a naming collision, we
163 // need to move all our private info to some other property
164 // of all the items/objects. So, we need to iterate over all
165 // the items and do something like:
166 // item.__S = item._S;
167 // item._S = undefined;
168 // But first we have to make sure the new "__S" variable is
169 // not in use, which means we have to iterate over all the
170 // items checking for that.
171 throw new Error ( "encountered bug in ItemFileWriteStore.newItem" );
173 var value
= keywordArgs
[ key
];
174 if (! lang
. isArray ( value
)){
177 newItem
[ key
] = value
;
178 if ( this . referenceIntegrity
){
179 for ( var i
= 0 ; i
< value
. length
; i
++){
181 if ( this . isItem ( val
)){
182 this . _addReferenceToMap ( val
, newItem
, key
);
187 this . onNew ( newItem
, pInfo
); // dojo.data.api.Notification call
188 return newItem
; // item
191 _removeArrayElement : function ( /* Array */ array
, /* anything */ element
){
192 var index
= arrayUtil
. indexOf ( array
, element
);
194 array
. splice ( index
, 1 );
200 deleteItem : function ( /* item */ item
){
201 // summary: See dojo.data.api.Write.deleteItem()
202 this . _assert (! this . _saveInProgress
);
203 this . _assertIsItem ( item
);
205 // Remove this item from the _arrayOfAllItems, but leave a null value in place
206 // of the item, so as not to change the length of the array, so that in newItem()
207 // we can still safely do: newIdentity = this._arrayOfAllItems.length;
208 var indexInArrayOfAllItems
= item
[ this . _itemNumPropName
];
209 var identity
= this . getIdentity ( item
);
211 //If we have reference integrity on, we need to do reference cleanup for the deleted item
212 if ( this . referenceIntegrity
){
213 //First scan all the attributes of this items for references and clean them up in the map
214 //As this item is going away, no need to track its references anymore.
216 //Get the attributes list before we generate the backup so it
217 //doesn't pollute the attributes list.
218 var attributes
= this . getAttributes ( item
);
220 //Backup the map, we'll have to restore it potentially, in a revert.
221 if ( item
[ this . _reverseRefMap
]){
222 item
[ "backup_" + this . _reverseRefMap
] = lang
. clone ( item
[ this . _reverseRefMap
]);
225 //TODO: This causes a reversion problem. This list won't be restored on revert since it is
226 //attached to the 'value'. item, not ours. Need to back tese up somehow too.
227 //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored
228 //later. Or just record them and call _addReferenceToMap on them in revert.
229 arrayUtil
. forEach ( attributes
, function ( attribute
){
230 arrayUtil
. forEach ( this . getValues ( item
, attribute
), function ( value
){
231 if ( this . isItem ( value
)){
232 //We have to back up all the references we had to others so they can be restored on a revert.
233 if (! item
[ "backupRefs_" + this . _reverseRefMap
]){
234 item
[ "backupRefs_" + this . _reverseRefMap
] = [];
236 item
[ "backupRefs_" + this . _reverseRefMap
]. push ({ id
: this . getIdentity ( value
), attr
: attribute
});
237 this . _removeReferenceFromMap ( value
, item
, attribute
);
242 //Next, see if we have references to this item, if we do, we have to clean them up too.
243 var references
= item
[ this . _reverseRefMap
];
245 //Look through all the items noted as references to clean them up.
246 for ( var itemId
in references
){
247 var containingItem
= null ;
248 if ( this . _itemsByIdentity
){
249 containingItem
= this . _itemsByIdentity
[ itemId
];
251 containingItem
= this . _arrayOfAllItems
[ itemId
];
253 //We have a reference to a containing item, now we have to process the
254 //attributes and clear all references to the item being deleted.
256 for ( var attribute
in references
[ itemId
]){
257 var oldValues
= this . getValues ( containingItem
, attribute
) || [];
258 var newValues
= arrayUtil
. filter ( oldValues
, function ( possibleItem
){
259 return !( this . isItem ( possibleItem
) && this . getIdentity ( possibleItem
) == identity
);
261 //Remove the note of the reference to the item and set the values on the modified attribute.
262 this . _removeReferenceFromMap ( item
, containingItem
, attribute
);
263 if ( newValues
. length
< oldValues
. length
){
264 this . _setValueOrValues ( containingItem
, attribute
, newValues
, true );
272 this . _arrayOfAllItems
[ indexInArrayOfAllItems
] = null ;
274 item
[ this . _storeRefPropName
] = null ;
275 if ( this . _itemsByIdentity
){
276 delete this . _itemsByIdentity
[ identity
];
278 this . _pending
. _deletedItems
[ identity
] = item
;
280 //Remove from the toplevel items, if necessary...
281 if ( item
[ this . _rootItemPropName
]){
282 this . _removeArrayElement ( this . _arrayOfTopLevelItems
, item
);
284 this . onDelete ( item
); // dojo.data.api.Notification call
288 setValue : function ( /* item */ item
, /* attribute-name-string */ attribute
, /* almost anything */ value
){
289 // summary: See dojo.data.api.Write.set()
290 return this . _setValueOrValues ( item
, attribute
, value
, true ); // boolean
293 setValues : function ( /* item */ item
, /* attribute-name-string */ attribute
, /* array */ values
){
294 // summary: See dojo.data.api.Write.setValues()
295 return this . _setValueOrValues ( item
, attribute
, values
, true ); // boolean
298 unsetAttribute : function ( /* item */ item
, /* attribute-name-string */ attribute
){
299 // summary: See dojo.data.api.Write.unsetAttribute()
300 return this . _setValueOrValues ( item
, attribute
, [], true );
303 _setValueOrValues : function ( /* item */ item
, /* attribute-name-string */ attribute
, /* anything */ newValueOrValues
, /*boolean?*/ callOnSet
){
304 this . _assert (! this . _saveInProgress
);
306 // Check for valid arguments
307 this . _assertIsItem ( item
);
308 this . _assert ( lang
. isString ( attribute
));
309 this . _assert ( typeof newValueOrValues
!== "undefined" );
311 // Make sure the user isn't trying to change the item's identity
312 var identifierAttribute
= this . _getIdentifierAttribute ();
313 if ( attribute
== identifierAttribute
){
314 throw new Error ( "ItemFileWriteStore does not have support for changing the value of an item's identifier." );
317 // To implement the Notification API, we need to make a note of what
318 // the old attribute value was, so that we can pass that info when
319 // we call the onSet method.
320 var oldValueOrValues
= this . _getValueOrValues ( item
, attribute
);
322 var identity
= this . getIdentity ( item
);
323 if (! this . _pending
. _modifiedItems
[ identity
]){
324 // Before we actually change the item, we make a copy of it to
325 // record the original state, so that we'll be able to revert if
326 // the revert method gets called. If the item has already been
327 // modified then there's no need to do this now, since we already
328 // have a record of the original state.
329 var copyOfItemState
= {};
330 for ( var key
in item
){
331 if (( key
=== this . _storeRefPropName
) || ( key
=== this . _itemNumPropName
) || ( key
=== this . _rootItemPropName
)){
332 copyOfItemState
[ key
] = item
[ key
];
333 } else if ( key
=== this . _reverseRefMap
){
334 copyOfItemState
[ key
] = lang
. clone ( item
[ key
]);
336 copyOfItemState
[ key
] = item
[ key
]. slice ( 0 , item
[ key
]. length
);
339 // Now mark the item as dirty, and save the copy of the original state
340 this . _pending
. _modifiedItems
[ identity
] = copyOfItemState
;
343 // Okay, now we can actually change this attribute on the item
346 if ( lang
. isArray ( newValueOrValues
) && newValueOrValues
. length
=== 0 ){
348 // If we were passed an empty array as the value, that counts
349 // as "unsetting" the attribute, so we need to remove this
350 // attribute from the item.
351 success
= delete item
[ attribute
];
352 newValueOrValues
= undefined ; // used in the onSet Notification call below
354 if ( this . referenceIntegrity
&& oldValueOrValues
){
355 var oldValues
= oldValueOrValues
;
356 if (! lang
. isArray ( oldValues
)){
357 oldValues
= [ oldValues
];
359 for ( var i
= 0 ; i
< oldValues
. length
; i
++){
360 var value
= oldValues
[ i
];
361 if ( this . isItem ( value
)){
362 this . _removeReferenceFromMap ( value
, item
, attribute
);
368 if ( lang
. isArray ( newValueOrValues
)){
369 // Unfortunately, it's not safe to just do this:
370 // newValueArray = newValueOrValues;
371 // Instead, we need to copy the array, which slice() does very nicely.
372 // This is so that our internal data structure won't
373 // get corrupted if the user mucks with the values array *after*
374 // calling setValues().
375 newValueArray
= newValueOrValues
. slice ( 0 , newValueOrValues
. length
);
377 newValueArray
= [ newValueOrValues
];
380 //We need to handle reference integrity if this is on.
381 //In the case of set, we need to see if references were added or removed
382 //and update the reference tracking map accordingly.
383 if ( this . referenceIntegrity
){
384 if ( oldValueOrValues
){
385 var oldValues
= oldValueOrValues
;
386 if (! lang
. isArray ( oldValues
)){
387 oldValues
= [ oldValues
];
389 //Use an associative map to determine what was added/removed from the list.
390 //Should be O(n) performant. First look at all the old values and make a list of them
391 //Then for any item not in the old list, we add it. If it was already present, we remove it.
392 //Then we pass over the map and any references left it it need to be removed (IE, no match in
393 //the new values list).
395 arrayUtil
. forEach ( oldValues
, function ( possibleItem
){
396 if ( this . isItem ( possibleItem
)){
397 var id
= this . getIdentity ( possibleItem
);
398 map
[ id
. toString ()] = true ;
401 arrayUtil
. forEach ( newValueArray
, function ( possibleItem
){
402 if ( this . isItem ( possibleItem
)){
403 var id
= this . getIdentity ( possibleItem
);
404 if ( map
[ id
. toString ()]){
405 delete map
[ id
. toString ()];
407 this . _addReferenceToMap ( possibleItem
, item
, attribute
);
413 if ( this . _itemsByIdentity
){
414 removedItem
= this . _itemsByIdentity
[ rId
];
416 removedItem
= this . _arrayOfAllItems
[ rId
];
418 this . _removeReferenceFromMap ( removedItem
, item
, attribute
);
421 //Everything is new (no old values) so we have to just
422 //insert all the references, if any.
423 for ( var i
= 0 ; i
< newValueArray
. length
; i
++){
424 var value
= newValueArray
[ i
];
425 if ( this . isItem ( value
)){
426 this . _addReferenceToMap ( value
, item
, attribute
);
431 item
[ attribute
] = newValueArray
;
435 // Now we make the dojo.data.api.Notification call
437 this . onSet ( item
, attribute
, oldValueOrValues
, newValueOrValues
);
439 return success
; // boolean
442 _addReferenceToMap : function ( /* item */ refItem
, /* item */ parentItem
, /* string */ attribute
){
444 // Method to add an reference map entry for an item and attribute.
446 // Method to add an reference map entry for an item and attribute. //
448 // The item that is referenced.
450 // The item that holds the new reference to refItem.
452 // The attribute on parentItem that contains the new reference.
454 var parentId
= this . getIdentity ( parentItem
);
455 var references
= refItem
[ this . _reverseRefMap
];
458 references
= refItem
[ this . _reverseRefMap
] = {};
460 var itemRef
= references
[ parentId
];
462 itemRef
= references
[ parentId
] = {};
464 itemRef
[ attribute
] = true ;
467 _removeReferenceFromMap : function ( /* item */ refItem
, /* item */ parentItem
, /* string */ attribute
){
469 // Method to remove an reference map entry for an item and attribute.
471 // Method to remove an reference map entry for an item and attribute. This will
472 // also perform cleanup on the map such that if there are no more references at all to
473 // the item, its reference object and entry are removed.
476 // The item that is referenced.
478 // The item holding a reference to refItem.
480 // The attribute on parentItem that contains the reference.
481 var identity
= this . getIdentity ( parentItem
);
482 var references
= refItem
[ this . _reverseRefMap
];
485 for ( itemId
in references
){
486 if ( itemId
== identity
){
487 delete references
[ itemId
][ attribute
];
488 if ( this . _isEmpty ( references
[ itemId
])){
489 delete references
[ itemId
];
493 if ( this . _isEmpty ( references
)){
494 delete refItem
[ this . _reverseRefMap
];
499 _dumpReferenceMap : function (){
501 // Function to dump the reverse reference map of all items in the store for debug purposes.
503 // Function to dump the reverse reference map of all items in the store for debug purposes.
505 for ( i
= 0 ; i
< this . _arrayOfAllItems
. length
; i
++){
506 var item
= this . _arrayOfAllItems
[ i
];
507 if ( item
&& item
[ this . _reverseRefMap
]){
508 console
. log ( "Item: [" + this . getIdentity ( item
) + "] is referenced by: " + jsonUtil
. toJson ( item
[ this . _reverseRefMap
]));
513 _getValueOrValues : function ( /* item */ item
, /* attribute-name-string */ attribute
){
514 var valueOrValues
= undefined ;
515 if ( this . hasAttribute ( item
, attribute
)){
516 var valueArray
= this . getValues ( item
, attribute
);
517 if ( valueArray
. length
== 1 ){
518 valueOrValues
= valueArray
[ 0 ];
520 valueOrValues
= valueArray
;
523 return valueOrValues
;
526 _flatten : function ( /* anything */ value
){
527 if ( this . isItem ( value
)){
528 // Given an item, return an serializable object that provides a
529 // reference to the item.
530 // For example, given kermit:
531 // var kermit = store.newItem({id:2, name:"Kermit"});
534 return { _reference
: this . getIdentity ( value
)};
536 if ( typeof value
=== "object" ){
537 for ( var type
in this . _datatypeMap
){
538 var typeMap
= this . _datatypeMap
[ type
];
539 if ( lang
. isObject ( typeMap
) && ! lang
. isFunction ( typeMap
)){
540 if ( value
instanceof typeMap
. type
){
541 if (! typeMap
. serialize
){
542 throw new Error ( "ItemFileWriteStore: No serializer defined for type mapping: [" + type
+ "]" );
544 return { _type
: type
, _value
: typeMap
. serialize ( value
)};
546 } else if ( value
instanceof typeMap
){
547 //SImple mapping, therefore, return as a toString serialization.
548 return { _type
: type
, _value
: value
. toString ()};
556 _getNewFileContentString : function (){
558 // Generate a string that can be saved to a file.
559 // The result should look similar to:
560 // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
561 var serializableStructure
= {};
563 var identifierAttribute
= this . _getIdentifierAttribute ();
564 if ( identifierAttribute
!== Number
){
565 serializableStructure
. identifier
= identifierAttribute
;
568 serializableStructure
. label
= this . _labelAttr
;
570 serializableStructure
. items
= [];
571 for ( var i
= 0 ; i
< this . _arrayOfAllItems
. length
; ++ i
){
572 var item
= this . _arrayOfAllItems
[ i
];
574 var serializableItem
= {};
575 for ( var key
in item
){
576 if ( key
!== this . _storeRefPropName
&& key
!== this . _itemNumPropName
&& key
!== this . _reverseRefMap
&& key
!== this . _rootItemPropName
){
577 var valueArray
= this . getValues ( item
, key
);
578 if ( valueArray
. length
== 1 ){
579 serializableItem
[ key
] = this . _flatten ( valueArray
[ 0 ]);
581 var serializableArray
= [];
582 for ( var j
= 0 ; j
< valueArray
. length
; ++ j
){
583 serializableArray
. push ( this . _flatten ( valueArray
[ j
]));
584 serializableItem
[ key
] = serializableArray
;
589 serializableStructure
. items
. push ( serializableItem
);
592 var prettyPrint
= true ;
593 return jsonUtil
. toJson ( serializableStructure
, prettyPrint
);
596 _isEmpty : function ( something
){
598 // Function to determine if an array or object has no properties or values.
600 // The array or object to examine.
602 if ( lang
. isObject ( something
)){
608 } else if ( lang
. isArray ( something
)){
609 if ( something
. length
> 0 ){
613 return empty
; //boolean
616 save : function ( /* object */ keywordArgs
){
617 // summary: See dojo.data.api.Write.save()
618 this . _assert (! this . _saveInProgress
);
620 // this._saveInProgress is set to true, briefly, from when save is first called to when it completes
621 this . _saveInProgress
= true ;
624 var saveCompleteCallback = function (){
631 self
. _saveInProgress
= false ; // must come after this._pending is cleared, but before any callbacks
632 if ( keywordArgs
&& keywordArgs
. onComplete
){
633 var scope
= keywordArgs
. scope
|| window
. global
;
634 keywordArgs
. onComplete
. call ( scope
);
637 var saveFailedCallback = function ( err
){
638 self
. _saveInProgress
= false ;
639 if ( keywordArgs
&& keywordArgs
. onError
){
640 var scope
= keywordArgs
. scope
|| window
. global
;
641 keywordArgs
. onError
. call ( scope
, err
);
645 if ( this . _saveEverything
){
646 var newFileContentString
= this . _getNewFileContentString ();
647 this . _saveEverything ( saveCompleteCallback
, saveFailedCallback
, newFileContentString
);
649 if ( this . _saveCustom
){
650 this . _saveCustom ( saveCompleteCallback
, saveFailedCallback
);
652 if (! this . _saveEverything
&& ! this . _saveCustom
){
653 // Looks like there is no user-defined save-handler function.
654 // That's fine, it just means the datastore is acting as a "mock-write"
655 // store -- changes get saved in memory but don't get saved to disk.
656 saveCompleteCallback ();
661 // summary: See dojo.data.api.Write.revert()
662 this . _assert (! this . _saveInProgress
);
665 for ( identity
in this . _pending
. _modifiedItems
){
666 // find the original item and the modified item that replaced it
667 var copyOfItemState
= this . _pending
. _modifiedItems
[ identity
];
668 var modifiedItem
= null ;
669 if ( this . _itemsByIdentity
){
670 modifiedItem
= this . _itemsByIdentity
[ identity
];
672 modifiedItem
= this . _arrayOfAllItems
[ identity
];
675 // Restore the original item into a full-fledged item again, we want to try to
676 // keep the same object instance as if we don't it, causes bugs like #9022.
677 copyOfItemState
[ this . _storeRefPropName
] = this ;
678 for ( var key
in modifiedItem
){
679 delete modifiedItem
[ key
];
681 lang
. mixin ( modifiedItem
, copyOfItemState
);
684 for ( identity
in this . _pending
. _deletedItems
){
685 deletedItem
= this . _pending
. _deletedItems
[ identity
];
686 deletedItem
[ this . _storeRefPropName
] = this ;
687 var index
= deletedItem
[ this . _itemNumPropName
];
689 //Restore the reverse refererence map, if any.
690 if ( deletedItem
[ "backup_" + this . _reverseRefMap
]){
691 deletedItem
[ this . _reverseRefMap
] = deletedItem
[ "backup_" + this . _reverseRefMap
];
692 delete deletedItem
[ "backup_" + this . _reverseRefMap
];
694 this . _arrayOfAllItems
[ index
] = deletedItem
;
695 if ( this . _itemsByIdentity
){
696 this . _itemsByIdentity
[ identity
] = deletedItem
;
698 if ( deletedItem
[ this . _rootItemPropName
]){
699 this . _arrayOfTopLevelItems
. push ( deletedItem
);
702 //We have to pass through it again and restore the reference maps after all the
703 //undeletes have occurred.
704 for ( identity
in this . _pending
. _deletedItems
){
705 deletedItem
= this . _pending
. _deletedItems
[ identity
];
706 if ( deletedItem
[ "backupRefs_" + this . _reverseRefMap
]){
707 arrayUtil
. forEach ( deletedItem
[ "backupRefs_" + this . _reverseRefMap
], function ( reference
){
709 if ( this . _itemsByIdentity
){
710 refItem
= this . _itemsByIdentity
[ reference
. id
];
712 refItem
= this . _arrayOfAllItems
[ reference
. id
];
714 this . _addReferenceToMap ( refItem
, deletedItem
, reference
. attr
);
716 delete deletedItem
[ "backupRefs_" + this . _reverseRefMap
];
720 for ( identity
in this . _pending
. _newItems
){
721 var newItem
= this . _pending
. _newItems
[ identity
];
722 newItem
[ this . _storeRefPropName
] = null ;
723 // null out the new item, but don't change the array index so
724 // so we can keep using _arrayOfAllItems.length.
725 this . _arrayOfAllItems
[ newItem
[ this . _itemNumPropName
]] = null ;
726 if ( newItem
[ this . _rootItemPropName
]){
727 this . _removeArrayElement ( this . _arrayOfTopLevelItems
, newItem
);
729 if ( this . _itemsByIdentity
){
730 delete this . _itemsByIdentity
[ identity
];
739 return true ; // boolean
742 isDirty : function ( /* item? */ item
){
743 // summary: See dojo.data.api.Write.isDirty()
745 // return true if the item is dirty
746 var identity
= this . getIdentity ( item
);
747 return new Boolean ( this . _pending
. _newItems
[ identity
] ||
748 this . _pending
. _modifiedItems
[ identity
] ||
749 this . _pending
. _deletedItems
[ identity
]). valueOf (); // boolean
751 // return true if the store is dirty -- which means return true
752 // if there are any new items, dirty items, or modified items
753 return ! this . _isEmpty ( this . _pending
. _newItems
) ||
754 ! this . _isEmpty ( this . _pending
. _modifiedItems
) ||
755 ! this . _isEmpty ( this . _pending
. _deletedItems
); // boolean
759 /* dojo.data.api.Notification */
761 onSet : function ( /* item */ item
,
762 /*attribute-name-string*/ attribute
,
763 /*object|array*/ oldValue
,
764 /*object|array*/ newValue
){
765 // summary: See dojo.data.api.Notification.onSet()
767 // No need to do anything. This method is here just so that the
768 // client code can connect observers to it.
771 onNew : function ( /* item */ newItem
, /*object?*/ parentInfo
){
772 // summary: See dojo.data.api.Notification.onNew()
774 // No need to do anything. This method is here just so that the
775 // client code can connect observers to it.
778 onDelete : function ( /* item */ deletedItem
){
779 // summary: See dojo.data.api.Notification.onDelete()
781 // No need to do anything. This method is here just so that the
782 // client code can connect observers to it.
785 close : function ( /* object? */ request
){
787 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
789 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
790 // If the store is still dirty (unsaved changes), then an error will be thrown instead of
791 // clearing the internal state for reload from the url.
793 //Clear if not dirty ... or throw an error
794 if ( this . clearOnClose
){
796 this . inherited ( arguments
);
798 //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved).
799 throw new Error ( "dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close." );