1 // ========================================================================== 2 // Papercube.DropdownView 3 // 4 // License: PaperCube is open source software released under 5 // the MIT License (see license.js) 6 // ========================================================================== 7 8 require('core'); 9 10 /** @class 11 12 This is a generic drop down. It is custom and not based on any SC drop down. 13 14 @extends SC.View 15 @author Peter Bergstrom 16 @version 1.0 17 @copyright 2008-2009 Peter Bergström. 18 */ 19 Papercube.DropDownView = SC.View.extend( 20 /** @scope Papercube.DropDownView.prototype */ { 21 22 /** 23 Empty element is an empty DIV. 24 25 @property {String} 26 @default "[ 27 '<div class="button">', 28 '</div>', 29 '<div class="list">', 30 '<div>' 31 ]" 32 */ 33 34 // The empty element contains a button div and list div. You click on the button to reveal the list. 35 emptyElement: [ 36 '<div class="button">', 37 '</div>', 38 '<div class="list">', 39 '<div>' 40 ].join(''), 41 42 /** 43 Outlets for author stat view. 44 45 ["button", "list"] 46 */ 47 outlets: ["button", "list"], 48 49 /** 50 This is the DIV element that forms the drop down. 51 52 @outlet {DOM Element} '.list?' 53 */ 54 list: ".list?", 55 56 /** 57 This is the DIV element that forms the button. 58 59 @outlet {DOM Element} '.button?' 60 */ 61 button: ".button?", 62 63 /** 64 Showing list boolean. 65 66 @property {Boolean} 67 @default NO 68 */ 69 showingList: NO, 70 71 /** 72 If it is a button drop down, it will act as a button unless depressed for 4/5ths of a second. 73 74 @property {Boolean} 75 @config 76 @default YES 77 @default NO 78 */ 79 isButton: NO, 80 81 /** 82 The label of the button as displayed in the HTML. 83 84 @property {String} 85 @config 86 @default 'Button' 87 */ 88 buttonLabel: 'Button', 89 90 /** 91 The action of the button. 92 93 @property {Function} 94 @config 95 */ 96 buttonAction: null, 97 98 /** 99 The action of the selection. 100 101 @property {Function} 102 @config 103 */ 104 selectionAction: null, 105 106 /** 107 This is a function that generates your list on the fly. Used instead of a binding to choices if present. 108 109 @property {Function} 110 @config 111 */ 112 lazyListGenFuncFunc: null, 113 114 /** 115 The scope of the function calls, can be set to a controller, or something else. 116 117 @property {Function} 118 @config 119 @default this 120 */ 121 funcScope: this, 122 123 /** 124 Selected value, bind to this. 125 126 @property {String} 127 @config 128 @default 'generic' 129 */ 130 value: "generic", 131 132 /** 133 Orientation can either be up or down. Default is down. 134 135 @property {Boolean} 136 @config 137 @default YES 138 @default YES 139 */ 140 orientationDown: YES, 141 142 /** 143 Sets if the button is enabled or not, if it is disabled, it will be grayed out. 144 145 @property {Boolean} 146 @default YES 147 */ 148 isEnabled: YES, 149 150 /** 151 Indication that the mousedown event has happened. Used for the isButton variation. 152 153 @property {Boolean} 154 @default NO 155 */ 156 _mouseDownHappened: NO, 157 158 /** 159 Choices, bind to this. 160 161 @property {Array} 162 @config 163 @default ["default1", "default2"] 164 */ 165 choices: ["default1", "default2"], 166 167 /** 168 Observe value and set the innerHTML of the button. 169 170 @observes isEnabled 171 */ 172 isEnabledDidChange: function() 173 { 174 this.setClassName('disabled', !this.get('isEnabled')); 175 }.observes('isEnabled'), 176 177 /** 178 Observe value and set the innerHTML of the button. 179 180 @observes value 181 */ 182 valueDidChange: function() 183 { 184 if(!this.isButton) 185 { 186 this.button.innerHTML = this.get("value"); 187 } 188 }.observes('value'), 189 190 /** 191 Handle the mouseDown event. 192 193 If the button has been clicked before show the list. If it is clicked again but it has the active class, hide the list. 194 195 @param {DOM Event} evt The mouseDown event. 196 */ 197 mouseDown: function(evt) 198 { 199 // If the button is not enabled, don't do anything. 200 if(!this.get('isEnabled')) return; 201 202 if(!this.isButton) 203 { 204 if(evt.target.hasClassName('button') && !this.button.hasClassName('active')) 205 { 206 this.button.addClassName('active'); 207 this.showList(); 208 } 209 else if(evt.target.hasClassName('button') && this.button.hasClassName('active')) 210 { 211 this.mouseExited(evt); 212 } 213 } 214 else 215 { 216 217 this.button.addClassName('active'); 218 this._mouseDownHappened = YES; 219 setTimeout(this._showList.bind(this), 800); 220 } 221 }, 222 223 /** 224 Select an item if the mouseUp event is on the list. 225 226 @param {DOM Event} evt The mouseUp event. 227 */ 228 mouseUp: function(evt) 229 { 230 // If the button is not enabled, don't do anything. 231 if(!this.get('isEnabled')) return; 232 233 if(!this.isButton) 234 { 235 // If it is a normal drop down. Select the item. 236 if(evt.target.hasClassName('list-item')) 237 { 238 this.selectItem(evt.target.innerHTML); 239 } 240 } 241 else 242 { 243 // If you click the button, do the action for the button. 244 if(evt.target.hasClassName('button') && !this._showingList) 245 { 246 this._mouseDownHappened = NO; 247 if(this.buttonAction) 248 { 249 this.buttonAction.bind(this.funcScope)(); 250 } 251 } 252 253 // If it is a list click, do the select action. 254 else if(evt.target.hasClassName('list-item')) 255 { 256 this.selectItem(evt.target.innerHTML); 257 this._mouseDownHappened = NO; 258 } 259 } 260 }, 261 262 /** 263 Hide the list and remove the active class. 264 265 @param {DOM Event} evt The mouseExited event. 266 */ 267 mouseExited: function(evt) 268 { 269 this.list.style.display = "none"; 270 this._showingList = NO; 271 this.button.removeClassName('active'); 272 this._mouseDownHappened = NO; 273 }, 274 275 /** 276 Show list if delayed enough. 277 */ 278 _showList: function() 279 { 280 if(this._mouseDownHappened) 281 { 282 this._showingList = YES; 283 this.showList(); 284 } 285 }, 286 287 /** 288 Select the value, then hide the list. 289 290 @param str {string} The selected item's string. 291 */ 292 selectItem: function(str) 293 { 294 // If there is a custom select action, execute it. 295 if(this.selectAction) 296 { 297 this.selectAction.bind(this.funcScope, str)(); 298 } 299 300 // If there is NO selectAction, set the value. 301 else 302 { 303 304 this.set("value", str); 305 } 306 this._showingList = NO; 307 this.list.style.display = "none"; 308 }, 309 310 /** 311 Show the list. 312 313 Construct the HTML based on the type of list. 314 */ 315 showList: function() 316 { 317 this.list.innerHTML = ''; 318 var items = []; 319 320 if(this.lazyListGenFunc) 321 { 322 items = this.lazyListGenFunc.bind(this.funcScope)(); 323 } 324 else 325 { 326 items = this.get('choices'); 327 } 328 329 var innerHTML = []; 330 for(var i=0; i < items.length; i++) 331 { 332 innerHTML.push('<div class="list-item">'+items[i]+'</div>'); 333 } 334 this.list.innerHTML = innerHTML.join(''); 335 if(!this.get("orientationDown")) 336 { 337 this.list.style.top = -((items.length+1)*18)+'px'; 338 } 339 this._showingList = YES; 340 if(items.length > 0) 341 { 342 this.list.style.display = "block"; 343 } 344 }, 345 346 /** 347 Initialization function. 348 349 Set the innerHTML to the emptyElement. This is done to get around 350 a SC bug that uses default HTML in the Ruby helper instead of the 351 emptyElement like it should. 352 */ 353 init: function() 354 { 355 this.set("innerHTML", this.emptyElement); 356 this.addClassName("drop-down"); 357 this.setClassName("down-orientation", this.get("orientationDown") && !this.get('isButton')); 358 this.setClassName("up-orientation", !this.get("orientationDown") && !this.get('isButton')); 359 360 361 sc_super(); 362 this.list.style.display = "none"; 363 364 // If the drop down is a button, then set the label since it won't ever change. 365 if(this.isButton) 366 { 367 this.button.innerHTML = this.buttonLabel; 368 } 369 } 370 }) ; 371