UNPKG

dojo

Version:

Dojo core is a powerful, lightweight library that makes common tasks quicker and easier. Animate elements, manipulate the DOM, and query with easy CSS syntax, all without sacrificing performance.

197 lines (186 loc) 7.21 kB
define(["../_base/kernel", "../_base/lang", "../when", "../_base/array" /*=====, "./api/Store" =====*/ ], function(kernel, lang, when, array /*=====, Store =====*/){ // module: // dojo/store/Observable var Observable = function(/*Store*/ store){ // summary: // The Observable store wrapper takes a store and sets an observe method on query() // results that can be used to monitor results for changes. // // description: // Observable wraps an existing store so that notifications can be made when a query // is performed. // // example: // Create a Memory store that returns an observable query, and then log some // information about that query. // // | var store = Observable(new Memory({ // | data: [ // | {id: 1, name: "one", prime: false}, // | {id: 2, name: "two", even: true, prime: true}, // | {id: 3, name: "three", prime: true}, // | {id: 4, name: "four", even: true, prime: false}, // | {id: 5, name: "five", prime: true} // | ] // | })); // | var changes = [], results = store.query({ prime: true }); // | var observer = results.observe(function(object, previousIndex, newIndex){ // | changes.push({previousIndex:previousIndex, newIndex:newIndex, object:object}); // | }); // // See the Observable tests for more information. var undef, queryUpdaters = [], revision = 0; // a Comet driven store could directly call notify to notify observers when data has // changed on the backend // create a new instance store = lang.delegate(store); store.notify = function(object, existingId){ revision++; var updaters = queryUpdaters.slice(); for(var i = 0, l = updaters.length; i < l; i++){ updaters[i](object, existingId); } }; var originalQuery = store.query; store.query = function(query, options){ options = options || {}; var results = originalQuery.apply(this, arguments); if(results && results.forEach){ var nonPagedOptions = lang.mixin({}, options); delete nonPagedOptions.start; delete nonPagedOptions.count; var queryExecutor = store.queryEngine && store.queryEngine(query, nonPagedOptions); var queryRevision = revision; var listeners = [], queryUpdater; results.observe = function(listener, includeObjectUpdates){ if(listeners.push(listener) == 1){ // first listener was added, create the query checker and updater queryUpdaters.push(queryUpdater = function(changed, existingId){ when(results, function(resultsArray){ var atEnd = resultsArray.length != options.count; var i, l, listener; if(++queryRevision != revision){ throw new Error("Query is out of date, you must observe() the query prior to any data modifications"); } var removedObject, removedFrom = -1, insertedInto = -1; if(existingId !== undef){ // remove the old one var filteredArray = [].concat(resultsArray); if(queryExecutor && !changed){ filteredArray = queryExecutor(resultsArray); } for(i = 0, l = resultsArray.length; i < l; i++){ var object = resultsArray[i]; if(store.getIdentity(object) == existingId){ if(filteredArray.indexOf(object)<0) continue; removedObject = object; removedFrom = i; if(queryExecutor || !changed){// if it was changed and we don't have a queryExecutor, we shouldn't remove it because updated objects would be eliminated resultsArray.splice(i, 1); } break; } } } if(queryExecutor){ // add the new one if(changed && // if a matches function exists, use that (probably more efficient) (queryExecutor.matches ? queryExecutor.matches(changed) : queryExecutor([changed]).length)){ var firstInsertedInto = removedFrom > -1 ? removedFrom : // put back in the original slot so it doesn't move unless it needs to (relying on a stable sort below) resultsArray.length; resultsArray.splice(firstInsertedInto, 0, changed); // add the new item insertedInto = array.indexOf(queryExecutor(resultsArray), changed); // sort it // we now need to push the change back into the original results array resultsArray.splice(firstInsertedInto, 1); // remove the inserted item from the previous index if((options.start && insertedInto == 0) || (!atEnd && insertedInto == resultsArray.length)){ // if it is at the end of the page, assume it goes into the prev or next page insertedInto = -1; }else{ resultsArray.splice(insertedInto, 0, changed); // and insert into the results array with the correct index } } }else if(changed){ // we don't have a queryEngine, so we can't provide any information // about where it was inserted or moved to. If it is an update, we leave it's position alone, other we at least indicate a new object if(existingId !== undef){ // an update, keep the index the same insertedInto = removedFrom; }else if(!options.start){ // a new object insertedInto = store.defaultIndex || 0; resultsArray.splice(insertedInto, 0, changed); } } if((removedFrom > -1 || insertedInto > -1) && (includeObjectUpdates || !queryExecutor || (removedFrom != insertedInto))){ var copyListeners = listeners.slice(); for(i = 0;listener = copyListeners[i]; i++){ listener(changed || removedObject, removedFrom, insertedInto); } } }); }); } var handle = {}; // TODO: Remove cancel in 2.0. handle.remove = handle.cancel = function(){ // remove this listener var index = array.indexOf(listeners, listener); if(index > -1){ // check to make sure we haven't already called cancel listeners.splice(index, 1); if(!listeners.length){ // no more listeners, remove the query updater too queryUpdaters.splice(array.indexOf(queryUpdaters, queryUpdater), 1); } } }; return handle; }; } return results; }; var inMethod; function whenFinished(method, action){ var original = store[method]; if(original){ store[method] = function(value){ var originalId; if(method === 'put'){ originalId = store.getIdentity(value); } if(inMethod){ // if one method calls another (like add() calling put()) we don't want two events return original.apply(this, arguments); } inMethod = true; try{ var results = original.apply(this, arguments); when(results, function(results){ action((typeof results == "object" && results) || value, originalId); }); return results; }finally{ inMethod = false; } }; } } // monitor for updates by listening to these methods whenFinished("put", function(object, originalId){ store.notify(object, originalId); }); whenFinished("add", function(object){ store.notify(object); }); whenFinished("remove", function(id){ store.notify(undefined, id); }); return store; }; lang.setObject("dojo.store.Observable", Observable); return Observable; });