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.

219 lines (204 loc) 7.16 kB
define(["./_base/declare", "./_base/lang", "./_base/array", "./when"], function(declare, lang, array, when){ // module: // dojo/Stateful return declare("dojo.Stateful", null, { // summary: // Base class for objects that provide named properties with optional getter/setter // control and the ability to watch for property changes // // The class also provides the functionality to auto-magically manage getters // and setters for object attributes/properties. // // Getters and Setters should follow the format of _xxxGetter or _xxxSetter where // the xxx is a name of the attribute to handle. So an attribute of "foo" // would have a custom getter of _fooGetter and a custom setter of _fooSetter. // // example: // | require(["dojo/Stateful", function(Stateful) { // | var obj = new Stateful(); // | obj.watch("foo", function(){ // | console.log("foo changed to " + this.get("foo")); // | }); // | obj.set("foo","bar"); // | }); // _attrPairNames: Hash // Used across all instances a hash to cache attribute names and their getter // and setter names. _attrPairNames: {}, _getAttrNames: function(name){ // summary: // Helper function for get() and set(). // Caches attribute name values so we don't do the string ops every time. // tags: // private var apn = this._attrPairNames; if(apn[name]){ return apn[name]; } return (apn[name] = { s: "_" + name + "Setter", g: "_" + name + "Getter" }); }, postscript: function(/*Object?*/ params){ // Automatic setting of params during construction if (params){ this.set(params); } }, _get: function(name, names){ // summary: // Private function that does a get based off a hash of names // names: // Hash of names of custom attributes return typeof this[names.g] === "function" ? this[names.g]() : this[name]; }, get: function(/*String*/name){ // summary: // Get a property on a Stateful instance. // name: // The property to get. // returns: // The property value on this Stateful instance. // description: // Get a named property on a Stateful object. The property may // potentially be retrieved via a getter method in subclasses. In the base class // this just retrieves the object's property. // example: // | require(["dojo/Stateful", function(Stateful) { // | var stateful = new Stateful({foo: 3}); // | stateful.get("foo") // returns 3 // | stateful.foo // returns 3 // | }); return this._get(name, this._getAttrNames(name)); //Any }, set: function(/*String*/name, /*Object*/value){ // summary: // Set a property on a Stateful instance // name: // The property to set. // value: // The value to set in the property. // returns: // The function returns this dojo.Stateful instance. // description: // Sets named properties on a stateful object and notifies any watchers of // the property. A programmatic setter may be defined in subclasses. // example: // | require(["dojo/Stateful", function(Stateful) { // | var stateful = new Stateful(); // | stateful.watch(function(name, oldValue, value){ // | // this will be called on the set below // | } // | stateful.set(foo, 5); // set() may also be called with a hash of name/value pairs, ex: // | stateful.set({ // | foo: "Howdy", // | bar: 3 // | }); // | }); // This is equivalent to calling set(foo, "Howdy") and set(bar, 3) // If an object is used, iterate through object if(typeof name === "object"){ for(var x in name){ if(name.hasOwnProperty(x) && x !="_watchCallbacks"){ this.set(x, name[x]); } } return this; } var names = this._getAttrNames(name), oldValue = this._get(name, names), setter = this[names.s], result; if(typeof setter === "function"){ // use the explicit setter result = setter.apply(this, Array.prototype.slice.call(arguments, 1)); }else{ // no setter so set attribute directly this[name] = value; } if(this._watchCallbacks){ var self = this; // If setter returned a promise, wait for it to complete, otherwise call watches immediately when(result, function(){ self._watchCallbacks(name, oldValue, value); }); } return this; // dojo/Stateful }, _changeAttrValue: function(name, value){ // summary: // Internal helper for directly changing an attribute value. // // name: String // The property to set. // value: Mixed // The value to set in the property. // // description: // Directly change the value of an attribute on an object, bypassing any // accessor setter. Also handles the calling of watch and emitting events. // It is designed to be used by descendant class when there are two values // of attributes that are linked, but calling .set() is not appropriate. var oldValue = this.get(name); this[name] = value; if(this._watchCallbacks){ this._watchCallbacks(name, oldValue, value); } return this; // dojo/Stateful }, watch: function(/*String?*/name, /*Function*/callback){ // summary: // Watches a property for changes // name: // Indicates the property to watch. This is optional (the callback may be the // only parameter), and if omitted, all the properties will be watched // returns: // An object handle for the watch. The unwatch method of this object // can be used to discontinue watching this property: // | var watchHandle = obj.watch("foo", callback); // | watchHandle.unwatch(); // callback won't be called now // callback: // The function to execute when the property changes. This will be called after // the property has been changed. The callback will be called with the |this| // set to the instance, the first argument as the name of the property, the // second argument as the old value and the third argument as the new value. var callbacks = this._watchCallbacks; if(!callbacks){ var self = this; callbacks = this._watchCallbacks = function(name, oldValue, value, ignoreCatchall){ var notify = function(propertyCallbacks){ if(propertyCallbacks){ propertyCallbacks = propertyCallbacks.slice(); for(var i = 0, l = propertyCallbacks.length; i < l; i++){ propertyCallbacks[i].call(self, name, oldValue, value); } } }; notify(callbacks['_' + name]); if(!ignoreCatchall){ notify(callbacks["*"]); // the catch-all } }; // we use a function instead of an object so it will be ignored by JSON conversion } if(!callback && typeof name === "function"){ callback = name; name = "*"; }else{ // prepend with dash to prevent name conflicts with function (like "name" property) name = '_' + name; } var propertyCallbacks = callbacks[name]; if(typeof propertyCallbacks !== "object"){ propertyCallbacks = callbacks[name] = []; } propertyCallbacks.push(callback); // TODO: Remove unwatch in 2.0 var handle = {}; handle.unwatch = handle.remove = function(){ var index = array.indexOf(propertyCallbacks, callback); if(index > -1){ propertyCallbacks.splice(index, 1); } }; return handle; //Object } }); });