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