Sys.Observer = function() { #if DEBUG throw Error.invalidOperation(); #endif } Sys.Observer.registerClass("Sys.Observer"); Sys.Observer.makeObservable = function(target) { /// Makes an object directly observable by adding observable methods to it. /// The object, array, or DOM element to make observable. /// The observable object. var isArray = target instanceof Array, o = Sys.Observer; ##DEBUG Sys.Observer._ensureObservable(target); if (target.setValue === o._observeMethods.setValue) return target; o._addMethods(target, o._observeMethods); if (isArray) { o._addMethods(target, o._arrayMethods); } return target; } #if DEBUG Sys.Observer._ensureObservable = function(target) { var type = typeof target; if ((type === "string") || (type === "number") || (type === "boolean") || (type === "date")) { throw Error.invalidOperation(String.format(Sys.Res.notObservable, type)); } } #endif Sys.Observer._addMethods = function(target, methods) { for (var m in methods) { #if DEBUG if (target[m] && (target[m] !== methods[m])) { throw Error.invalidOperation(String.format(Sys.Res.observableConflict, m)); } #endif target[m] = methods[m]; } } // Make use of private version for significant perf improvement in Binding. // See references of these functions in binding.js for more details. Sys.Observer._addEventHandler = function(target, eventName, handler) { Sys.Observer._getContext(target, true).events._addHandler(eventName, handler); } Sys.Observer.addEventHandler = function(target, eventName, handler) { /// Adds an observable event handler to the target. /// /// /// ##DEBUG Sys.Observer._ensureObservable(target); Sys.Observer._addEventHandler(target, eventName, handler); } // Make use of private version for significant perf improvement in Binding. // See references of these functions in binding.js for more details. Sys.Observer._removeEventHandler = function(target, eventName, handler) { Sys.Observer._getContext(target, true).events._removeHandler(eventName, handler); } Sys.Observer.removeEventHandler = function(target, eventName, handler) { /// Adds an observable event handler to the target. /// /// /// ##DEBUG Sys.Observer._ensureObservable(target); Sys.Observer._removeEventHandler(target, eventName, handler); } Sys.Observer.raiseEvent = function(target, eventName, eventArgs) { /// Raises an observable event on the target. /// /// /// ##DEBUG Sys.Observer._ensureObservable(target); var ctx = Sys.Observer._getContext(target); if (!ctx) return; var handler = ctx.events.getHandler(eventName); if (handler) { handler(target, eventArgs); } } Sys.Observer.addPropertyChanged = function(target, handler) { /// Adds a propertyChanged event handler to the target. /// The object to observe. /// The event handler. ##DEBUG Sys.Observer._ensureObservable(target); Sys.Observer._addEventHandler(target, "propertyChanged", handler); } Sys.Observer.removePropertyChanged = function(target, handler) { /// Removes a propertyChanged event handler from the target. /// The object to observe. /// The event handler. ##DEBUG Sys.Observer._ensureObservable(target); Sys.Observer._removeEventHandler(target, "propertyChanged", handler); } Sys.Observer.beginUpdate = function(target) { /// /// ##DEBUG Sys.Observer._ensureObservable(target); Sys.Observer._getContext(target, true).updating = true; } Sys.Observer.endUpdate = function(target) { /// /// ##DEBUG Sys.Observer._ensureObservable(target); var ctx = Sys.Observer._getContext(target); if (!ctx || !ctx.updating) return; ctx.updating = false; var dirty = ctx.dirty; ctx.dirty = false; if (dirty) { if (target instanceof Array) { var changes = ctx.changes; ctx.changes = null; Sys.Observer.raiseCollectionChanged(target, changes); } Sys.Observer.raisePropertyChanged(target, ""); } } Sys.Observer.isUpdating = function(target) { /// /// /// ##DEBUG Sys.Observer._ensureObservable(target); var ctx = Sys.Observer._getContext(target); return ctx ? ctx.updating : false; } // Make use of private version for significant perf improvement in Binding. // See references of these functions in binding.js for more details. Sys.Observer._setValue = function(target, propertyName, value) { var getter, setter, mainTarget = target, path = propertyName.split('.'); for (var i = 0, l = (path.length - 1); i < l ; i++) { var name = path[i]; getter = target["get_" + name]; if (typeof (getter) === "function") { target = getter.call(target); } else { target = target[name]; } var type = typeof (target); if ((target === null) || (type === "undefined")) { throw Error.invalidOperation(String.format(Sys.Res.nullReferenceInPath, propertyName)); } } var currentValue, lastPath = path[l]; getter = target["get_" + lastPath]; setter = target["set_" + lastPath]; if (typeof(getter) === 'function') { currentValue = getter.call(target); } else { currentValue = target[lastPath]; } if (typeof(setter) === 'function') { setter.call(target, value); } else { target[lastPath] = value; } if (currentValue !== value) { var ctx = Sys.Observer._getContext(mainTarget); if (ctx && ctx.updating) { ctx.dirty = true; return; }; Sys.Observer.raisePropertyChanged(mainTarget, path[0]); } } Sys.Observer.setValue = function(target, propertyName, value) { /// Sets a property or field on the target in an observable manner. /// The object to set a property on. /// The name of the property to field to set. /// The value to set. ##DEBUG Sys.Observer._ensureObservable(target); Sys.Observer._setValue(target, propertyName, value); } Sys.Observer.raisePropertyChanged = function(target, propertyName) { /// Raises a change notification event. /// /// The object to raise the event on. /// The name of the property that changed. Sys.Observer.raiseEvent(target, "propertyChanged", new Sys.PropertyChangedEventArgs(propertyName)); } Sys.Observer.addCollectionChanged = function(target, handler) { /// /// /// Sys.Observer._addEventHandler(target, "collectionChanged", handler); } Sys.Observer.removeCollectionChanged = function(target, handler) { /// /// /// Sys.Observer._removeEventHandler(target, "collectionChanged", handler); } Sys.Observer._collectionChange = function(target, change) { var ctx = Sys.Observer._getContext(target); if (ctx && ctx.updating) { ctx.dirty = true; var changes = ctx.changes; if (!changes) { ctx.changes = changes = [change]; } else { changes.push(change); } } else { Sys.Observer.raiseCollectionChanged(target, [change]); Sys.Observer.raisePropertyChanged(target, 'length'); } } Sys.Observer.add = function(target, item) { /// Adds an item to the collection in an observable manner. /// The array to add to. /// The item to add. var change = new Sys.CollectionChange(Sys.NotifyCollectionChangedAction.add, [item], target.length); Array.add(target, item); Sys.Observer._collectionChange(target, change); } Sys.Observer.addRange = function(target, items) { /// Adds items to the collection in an observable manner. /// The array to add to. /// The array of items to add. var change = new Sys.CollectionChange(Sys.NotifyCollectionChangedAction.add, items, target.length); Array.addRange(target, items); Sys.Observer._collectionChange(target, change); } Sys.Observer.clear = function(target) { /// Clears the array of its elements in an observable manner. /// The array to clear. var oldItems = Array.clone(target); Array.clear(target); Sys.Observer._collectionChange(target, new Sys.CollectionChange(Sys.NotifyCollectionChangedAction.reset, null, -1, oldItems, 0)); } Sys.Observer.insert = function(target, index, item) { /// Inserts an item at the specified index in an observable manner. /// The array to insert into. /// The index where the item will be inserted. /// The item to insert. Array.insert(target, index, item); Sys.Observer._collectionChange(target, new Sys.CollectionChange(Sys.NotifyCollectionChangedAction.add, [item], index)); } Sys.Observer.remove = function(target, item) { /// Removes the first occurence of an item from the array in an observable manner. /// The array to remove from. /// The item to remove. /// True if the item was found. var index = Array.indexOf(target, item); if (index !== -1) { Array.remove(target, item); Sys.Observer._collectionChange(target, new Sys.CollectionChange(Sys.NotifyCollectionChangedAction.remove, null, -1, [item], index)); return true; } return false; } Sys.Observer.removeAt = function(target, index) { /// Removes the item at the specified index from the array in an observable manner. /// The array to remove from. /// The index of the item to remove. if ((index > -1) && (index < target.length)) { var item = target[index]; Array.removeAt(target, index); Sys.Observer._collectionChange(target, new Sys.CollectionChange(Sys.NotifyCollectionChangedAction.remove, null, -1, [item], index)); } } Sys.Observer.raiseCollectionChanged = function(target, changes) { /// Raises the collectionChanged event. /// /// The collection to raise the event on. /// A list of changes that were performed on the collection since the last event. Sys.Observer.raiseEvent(target, "collectionChanged", new Sys.NotifyCollectionChangedEventArgs(changes)); } // note: triple-slash comments in these methods works with the preprocessor despite them being defined in this unique way Sys.Observer._observeMethods = { add_propertyChanged: function(handler) { Sys.Observer._addEventHandler(this, "propertyChanged", handler); }, remove_propertyChanged: function(handler) { Sys.Observer._removeEventHandler(this, "propertyChanged", handler); }, addEventHandler: function(eventName, handler) { /// Adds an observable event handler. /// /// Sys.Observer._addEventHandler(this, eventName, handler); }, removeEventHandler: function(eventName, handler) { /// Removes an observable event handler. /// /// Sys.Observer._removeEventHandler(this, eventName, handler); }, get_isUpdating: function() { /// /// /// return Sys.Observer.isUpdating(this); }, beginUpdate: function() { /// /// Sys.Observer.beginUpdate(this); }, endUpdate: function() { /// /// Sys.Observer.endUpdate(this); }, setValue: function(name, value) { /// Sets a property or field on the target in an observable manner. /// The name of the property to field to set. /// The value to set. Sys.Observer._setValue(this, name, value); }, raiseEvent: function(eventName, eventArgs) { /// Raises an observable event. /// /// /// Sys.Observer.raiseEvent(this, eventName, eventArgs); }, raisePropertyChanged: function(name) { /// Raises a change notification event. /// /// The name of the property that changed. Sys.Observer.raiseEvent(this, "propertyChanged", new Sys.PropertyChangedEventArgs(name)); } } Sys.Observer._arrayMethods = { add_collectionChanged: function(handler) { Sys.Observer._addEventHandler(this, "collectionChanged", handler); }, remove_collectionChanged: function(handler) { Sys.Observer._removeEventHandler(this, "collectionChanged", handler); }, add: function(item) { /// Adds an item to the collection in an observable manner. /// /// The item to add. Sys.Observer.add(this, item); }, addRange: function(items) { /// Adds items to the collection in an observable manner. /// /// The array of items to add. Sys.Observer.addRange(this, items); }, clear: function() { /// Clears the array of its elements in an observable manner. /// Sys.Observer.clear(this); }, insert: function(index, item) { /// Inserts an item at the specified index in an observable manner. /// /// The index where the item will be inserted. /// The item to insert. Sys.Observer.insert(this, index, item); }, remove: function(item) { /// Removes the first occurence of an item from the array in an observable manner. /// /// The item to remove. /// True if the item was found. return Sys.Observer.remove(this, item); }, removeAt: function(index) { /// Removes the item at the specified index from the array in an observable manner. /// /// The index of the item to remove. Sys.Observer.removeAt(this, index); }, raiseCollectionChanged: function(changes) { /// Raises the collectionChanged event. /// /// A list of changes that were performed on the collection since the last event. Sys.Observer.raiseEvent(this, "collectionChanged", new Sys.NotifyCollectionChangedEventArgs(changes)); } } Sys.Observer._getContext = function(obj, create) { var ctx = obj._observerContext; if (ctx) return ctx(); if (create) { return (obj._observerContext = Sys.Observer._createContext())(); } return null; } Sys.Observer._createContext = function() { // instead of attaching an EventHandlerList, etc directly onto the observed object, we attach a function // which returns it as a closure. This prevents the need to attach a field to the object which is not a // function, which could have a negative impact on serializers, whereas they typically always skip functions. // It's also better for sparse arrays, where a for-var-in loop need only ignore functions. // Return an object instead of the handler list directly so we have a place to put other fields without having // to add separate getX() methods for each. var ctx = { events: new Sys.EventHandlerList() }; return function() { return ctx; } }