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