1 define(['three'], function(THREE) {
  2   /**
  3    *
  4    * @class UIState
  5    *
  6    * An object that shares UI level state information across all entities under
  7    * a single emperor instance and exposes events when this information changes.
  8    * Do not access properties directly, go through the register event handlers
  9    * and use the getters/setters
 10    *
 11    */
 12   function UIState() {
 13     this.events = new THREE.EventDispatcher();
 14 
 15     //PROPERTY INITIALIZATION - (serialization and deserialization go here.)
 16     //Properties must be flattened into UIState until we determine how to handle
 17     //tiered events.
 18     this['view.usesPointCloud'] = false;
 19     this['view.viewType'] = 'scatter';
 20   }
 21 
 22   /**
 23    * Retrieve a keyed property
 24    */
 25   UIState.prototype.getProperty = function(key) {
 26     return this[key];
 27   };
 28 
 29   /**
 30    * Register for changes to a non collection backed keyed property.
 31    * The newly registered function is immediately called with the property's
 32    * current value.
 33    */
 34   UIState.prototype.registerProperty = function(key, onChange) {
 35     this.events.addEventListener(key, onChange);
 36     propertyValue = this.getProperty(key);
 37     onChange({type: key, newVal: propertyValue});
 38   };
 39 
 40   /**
 41    * Register for changes to a list backed keyed property.
 42    * onInit will be called whenever the list object is replaced
 43    * onAdd will be called whenever a new element is added to the list
 44    * onRemove will be called whenever an element is removed from the list
 45    * onUpdate will be called whenever an element at a given position is
 46    * replaced.
 47    *
 48    * onInit will be immediately called with the property's current value.
 49    */
 50   UIState.prototype.registerListProperty = function(key,
 51                                                     onInit,
 52                                                     onAdd,
 53                                                     onRemove,
 54                                                     onUpdate) {
 55     this.events.addEventListener(key + '/ADD', onAdd);
 56     this.events.addEventListener(key + '/REMOVE', onRemove);
 57     this.events.addEventListener(key + '/UPDATE', onUpdate);
 58     this.registerProperty(key, onInit);
 59   };
 60 
 61   /**
 62    * Register for changes to a dictionary backed keyed property.
 63    * onInit will be called whenever the dictionary object is replaced
 64    * onPut will be called whenever a new key value pair is put into the dict
 65    * onRemove will be called when a key is deleted from the dictionary
 66    *
 67    * onInit will be immediately called with the property's current value.
 68    */
 69   UIState.prototype.registerDictProperty = function(key,
 70                                                     onInit,
 71                                                     onPut,
 72                                                     onRemove) {
 73     this.events.addEventListener(key + '/PUT', onPut);
 74     this.events.addEventListener(key + '/REMOVE', onRemove);
 75     this.registerProperty(key, onInit);
 76   };
 77 
 78   /**
 79    * Set a keyed property and fire off its corresponding event
 80    */
 81   UIState.prototype.setProperty = function(key, value) {
 82     var oldValue = this.getProperty(key);
 83     this[key] = value;
 84     if (oldValue !== value) {
 85       this.events.dispatchEvent(
 86         {type: key, oldVal: oldValue, newVal: value}
 87       );
 88     }
 89   };
 90 
 91   /**
 92    * Set a series of keyed properties.
 93    * If a bulk event is set, this will be dispatched rather than individual
 94    * events for each property
 95    *
 96    * Bulk events must be objects of the form { type: xxx, ... } to pass
 97    * through the event dispatcher
 98    *
 99    * TODO: We could allow list and dictionary mutations to be part of
100    * bulk events, is that a use case we think is realistic?
101    */
102   UIState.prototype.setProperties = function(keyValueDict, bulkEvent) {
103 
104     if (bulkEvent === undefined) {
105       bulkEvent = null;
106     }
107 
108     var oldValueDict = {};
109     for (var key in keyValueDict) {
110       oldValueDict[key] = this.getProperty(key);
111     }
112     for (key in keyValueDict) {
113       this[key] = value;
114     }
115 
116     if (bulkEvent === null) {
117       for (key in keyValueDict) {
118         if (oldValueDict[key] !== keyValueDict[key]) {
119           this.events.dispatchEvent(
120             {type: key, oldVal: oldValueDict[key], newVal: keyValueDict[key]}
121           );
122         }
123       }
124     }
125     else {
126       this.events.dispatchEvent(bulkEvent);
127     }
128   };
129 
130   //Observable List Functionality
131   UIState.prototype.listPropertyAdd = function(propertyKey, index, value) {
132     var list = this.getProperty(propertyKey);
133     list.splice(index, 0, value);
134     this.events.dispatchEvent(
135       {type: propertyKey + '/ADD', index: index, val: value}
136     );
137   };
138 
139   UIState.prototype.listPropertyRemove = function(propertyKey, valueToRemove) {
140     var index = this.getProperty(propertyKey).indexOf(valueToRemove);
141     if (index != -1)
142       this.listPropertyRemoveAt(propertyKey, index);
143   };
144 
145   UIState.prototype.listPropertyRemoveAt = function(propertyKey, index) {
146     var list = this.getProperty(propertyKey);
147     var oldVal = list[index];
148     list.splice(index, 1);
149     this.events.dispatchEvent(
150       {type: propertyKey + '/REMOVE', index: index, oldVal: oldVal}
151     );
152   };
153 
154   UIState.prototype.listPropertyUpdate = function(propertyKey,
155                                                   index,
156                                                   newValue) {
157     var list = this.getProperty(propertyKey);
158     var oldVal = list[index];
159     list[index] = newValue;
160     this.events.dispatchEvent(
161       {type: propertyKey + '/UPDATE',
162         index: index,
163         oldVal: oldVal,
164         newVal: newVal}
165     );
166   };
167 
168   //Observable Dictionary Functionality
169   UIState.prototype.dictPropertyPut = function(propertyKey, dictKey, value) {
170     var dict = this.getProperty(propertyKey);
171     var oldVal = dict[dictKey];
172     dict[dictKey] = value;
173     this.events.dispatchEvent(
174       {type: propertyKey + '/PUT',
175         key: dictKey,
176         oldVal: oldVal,
177         newVal: value
178       }
179     );
180   };
181 
182   UIState.prototype.dictPropertyRemove = function(propertyKey, dictKey) {
183     var dict = this.getProperty(propertyKey);
184     var oldVal = dict[dictKey];
185     delete dict[dictKey];
186     this.events.dispatchEvent(
187       {type: propertyKey + '/REMOVE',
188       key: dictKey,
189       oldVal: oldVal
190       });
191   };
192 
193   //TODO FIXME HACK:  When we worry about removing linked event handlers
194   //(because an object that is listening to UIState needs to go out of scope)
195   //We must ensure that we are properly handling delinking of methods,
196   //especially when developers linking in their handler functions will often
197   //be careless about using function.bind vs wrapper functions vs anonymous
198   //functions unless we throw exceptions in their faces.
199 
200   return UIState;
201 });
202