      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