chosen-rails 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,548 @@
1
+ ###
2
+ Chosen source: generate output using 'cake build'
3
+ Copyright (c) 2011 by Harvest
4
+ ###
5
+ root = this
6
+
7
+ class Chosen extends AbstractChosen
8
+
9
+ setup: ->
10
+ @is_rtl = @form_field.hasClassName "chzn-rtl"
11
+
12
+ finish_setup: ->
13
+ @form_field.addClassName "chzn-done"
14
+
15
+ set_default_values: ->
16
+ super()
17
+
18
+ # HTML Templates
19
+ @single_temp = new Template('<a href="javascript:void(0)" class="chzn-single"><span>#{default}</span><div><b></b></div></a><div class="chzn-drop" style="left:-9000px;"><div class="chzn-search"><input type="text" autocomplete="off" /></div><ul class="chzn-results"></ul></div>')
20
+ @multi_temp = new Template('<ul class="chzn-choices"><li class="search-field"><input type="text" value="#{default}" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chzn-drop" style="left:-9000px;"><ul class="chzn-results"></ul></div>')
21
+ @choice_temp = new Template('<li class="search-choice" id="#{id}"><span>#{choice}</span><a href="javascript:void(0)" class="search-choice-close" rel="#{position}"></a></li>')
22
+ @no_results_temp = new Template('<li class="no-results">' + @results_none_found + ' "<span>#{terms}</span>"</li>')
23
+
24
+ set_up_html: ->
25
+ @container_id = @form_field.identify().replace(/(:|\.)/g, '_') + "_chzn"
26
+
27
+ @f_width = if @form_field.getStyle("width") then parseInt @form_field.getStyle("width"), 10 else @form_field.getWidth()
28
+
29
+ container_props =
30
+ 'id': @container_id
31
+ 'class': "chzn-container#{ if @is_rtl then ' chzn-rtl' else '' }"
32
+ 'style': 'width: ' + (@f_width) + 'px' #use parens around @f_width so coffeescript doesn't think + ' px' is a function parameter
33
+
34
+ @default_text = if @form_field.readAttribute 'data-placeholder' then @form_field.readAttribute 'data-placeholder' else @default_text_default
35
+
36
+ base_template = if @is_multiple then new Element('div', container_props).update( @multi_temp.evaluate({ "default": @default_text}) ) else new Element('div', container_props).update( @single_temp.evaluate({ "default":@default_text }) )
37
+
38
+ @form_field.hide().insert({ after: base_template })
39
+ @container = $(@container_id)
40
+ @container.addClassName( "chzn-container-" + (if @is_multiple then "multi" else "single") )
41
+ @dropdown = @container.down('div.chzn-drop')
42
+
43
+ dd_top = @container.getHeight()
44
+ dd_width = (@f_width - get_side_border_padding(@dropdown))
45
+
46
+ @dropdown.setStyle({"width": dd_width + "px", "top": dd_top + "px"})
47
+
48
+ @search_field = @container.down('input')
49
+ @search_results = @container.down('ul.chzn-results')
50
+ this.search_field_scale()
51
+
52
+ @search_no_results = @container.down('li.no-results')
53
+
54
+ if @is_multiple
55
+ @search_choices = @container.down('ul.chzn-choices')
56
+ @search_container = @container.down('li.search-field')
57
+ else
58
+ @search_container = @container.down('div.chzn-search')
59
+ @selected_item = @container.down('.chzn-single')
60
+ sf_width = dd_width - get_side_border_padding(@search_container) - get_side_border_padding(@search_field)
61
+ @search_field.setStyle( {"width" : sf_width + "px"} )
62
+
63
+ this.results_build()
64
+ this.set_tab_index()
65
+ @form_field.fire("liszt:ready", {chosen: this})
66
+
67
+ register_observers: ->
68
+ @container.observe "mousedown", (evt) => this.container_mousedown(evt)
69
+ @container.observe "mouseup", (evt) => this.container_mouseup(evt)
70
+ @container.observe "mouseenter", (evt) => this.mouse_enter(evt)
71
+ @container.observe "mouseleave", (evt) => this.mouse_leave(evt)
72
+
73
+ @search_results.observe "mouseup", (evt) => this.search_results_mouseup(evt)
74
+ @search_results.observe "mouseover", (evt) => this.search_results_mouseover(evt)
75
+ @search_results.observe "mouseout", (evt) => this.search_results_mouseout(evt)
76
+
77
+ @form_field.observe "liszt:updated", (evt) => this.results_update_field(evt)
78
+
79
+ @search_field.observe "blur", (evt) => this.input_blur(evt)
80
+ @search_field.observe "keyup", (evt) => this.keyup_checker(evt)
81
+ @search_field.observe "keydown", (evt) => this.keydown_checker(evt)
82
+
83
+ if @is_multiple
84
+ @search_choices.observe "click", (evt) => this.choices_click(evt)
85
+ @search_field.observe "focus", (evt) => this.input_focus(evt)
86
+
87
+ search_field_disabled: ->
88
+ @is_disabled = @form_field.disabled
89
+ if(@is_disabled)
90
+ @container.addClassName 'chzn-disabled'
91
+ @search_field.disabled = true
92
+ @selected_item.stopObserving "focus", @activate_action if !@is_multiple
93
+ this.close_field()
94
+ else
95
+ @container.removeClassName 'chzn-disabled'
96
+ @search_field.disabled = false
97
+ @selected_item.observe "focus", @activate_action if !@is_multiple
98
+
99
+ container_mousedown: (evt) ->
100
+ if !@is_disabled
101
+ target_closelink = if evt? then evt.target.hasClassName "search-choice-close" else false
102
+ if evt and evt.type is "mousedown"
103
+ evt.stop()
104
+ if not @pending_destroy_click and not target_closelink
105
+ if not @active_field
106
+ @search_field.clear() if @is_multiple
107
+ document.observe "click", @click_test_action
108
+ this.results_show()
109
+ else if not @is_multiple and evt and (evt.target is @selected_item || evt.target.up("a.chzn-single"))
110
+ this.results_toggle()
111
+
112
+ this.activate_field()
113
+ else
114
+ @pending_destroy_click = false
115
+
116
+ container_mouseup: (evt) ->
117
+ this.results_reset(evt) if evt.target.nodeName is "ABBR"
118
+
119
+ blur_test: (evt) ->
120
+ this.close_field() if not @active_field and @container.hasClassName("chzn-container-active")
121
+
122
+ close_field: ->
123
+ document.stopObserving "click", @click_test_action
124
+
125
+ if not @is_multiple
126
+ @selected_item.tabIndex = @search_field.tabIndex
127
+ @search_field.tabIndex = -1
128
+
129
+ @active_field = false
130
+ this.results_hide()
131
+
132
+ @container.removeClassName "chzn-container-active"
133
+ this.winnow_results_clear()
134
+ this.clear_backstroke()
135
+
136
+ this.show_search_field_default()
137
+ this.search_field_scale()
138
+
139
+ activate_field: ->
140
+ if not @is_multiple and not @active_field
141
+ @search_field.tabIndex = @selected_item.tabIndex
142
+ @selected_item.tabIndex = -1
143
+
144
+ @container.addClassName "chzn-container-active"
145
+ @active_field = true
146
+
147
+ @search_field.value = @search_field.value
148
+ @search_field.focus()
149
+
150
+
151
+ test_active_click: (evt) ->
152
+ if evt.target.up('#' + @container_id)
153
+ @active_field = true
154
+ else
155
+ this.close_field()
156
+
157
+ results_build: ->
158
+ @parsing = true
159
+ @results_data = root.SelectParser.select_to_array @form_field
160
+
161
+ if @is_multiple and @choices > 0
162
+ @search_choices.select("li.search-choice").invoke("remove")
163
+ @choices = 0
164
+ else if not @is_multiple
165
+ @selected_item.down("span").update(@default_text)
166
+ if @form_field.options.length <= @disable_search_threshold
167
+ @container.addClassName "chzn-container-single-nosearch"
168
+ else
169
+ @container.removeClassName "chzn-container-single-nosearch"
170
+
171
+ content = ''
172
+ for data in @results_data
173
+ if data.group
174
+ content += this.result_add_group data
175
+ else if !data.empty
176
+ content += this.result_add_option data
177
+ if data.selected and @is_multiple
178
+ this.choice_build data
179
+ else if data.selected and not @is_multiple
180
+ @selected_item.down("span").update( data.html )
181
+ this.single_deselect_control_build() if @allow_single_deselect
182
+
183
+ this.search_field_disabled()
184
+ this.show_search_field_default()
185
+ this.search_field_scale()
186
+
187
+ @search_results.update content
188
+ @parsing = false
189
+
190
+
191
+ result_add_group: (group) ->
192
+ if not group.disabled
193
+ group.dom_id = @container_id + "_g_" + group.array_index
194
+ '<li id="' + group.dom_id + '" class="group-result">' + group.label.escapeHTML() + '</li>'
195
+ else
196
+ ""
197
+
198
+ result_do_highlight: (el) ->
199
+ this.result_clear_highlight()
200
+
201
+ @result_highlight = el
202
+ @result_highlight.addClassName "highlighted"
203
+
204
+ maxHeight = parseInt @search_results.getStyle('maxHeight'), 10
205
+ visible_top = @search_results.scrollTop
206
+ visible_bottom = maxHeight + visible_top
207
+
208
+ high_top = @result_highlight.positionedOffset().top
209
+ high_bottom = high_top + @result_highlight.getHeight()
210
+
211
+ if high_bottom >= visible_bottom
212
+ @search_results.scrollTop = if (high_bottom - maxHeight) > 0 then (high_bottom - maxHeight) else 0
213
+ else if high_top < visible_top
214
+ @search_results.scrollTop = high_top
215
+
216
+ result_clear_highlight: ->
217
+ @result_highlight.removeClassName('highlighted') if @result_highlight
218
+ @result_highlight = null
219
+
220
+ results_show: ->
221
+ if not @is_multiple
222
+ @selected_item.addClassName('chzn-single-with-drop')
223
+ if @result_single_selected
224
+ this.result_do_highlight( @result_single_selected )
225
+
226
+ dd_top = if @is_multiple then @container.getHeight() else (@container.getHeight() - 1)
227
+ @dropdown.setStyle {"top": dd_top + "px", "left":0}
228
+ @results_showing = true
229
+
230
+ @search_field.focus()
231
+ @search_field.value = @search_field.value
232
+
233
+ this.winnow_results()
234
+
235
+ results_hide: ->
236
+ @selected_item.removeClassName('chzn-single-with-drop') unless @is_multiple
237
+ this.result_clear_highlight()
238
+ @dropdown.setStyle({"left":"-9000px"})
239
+ @results_showing = false
240
+
241
+
242
+ set_tab_index: (el) ->
243
+ if @form_field.tabIndex
244
+ ti = @form_field.tabIndex
245
+ @form_field.tabIndex = -1
246
+
247
+ if @is_multiple
248
+ @search_field.tabIndex = ti
249
+ else
250
+ @selected_item.tabIndex = ti
251
+ @search_field.tabIndex = -1
252
+
253
+ show_search_field_default: ->
254
+ if @is_multiple and @choices < 1 and not @active_field
255
+ @search_field.value = @default_text
256
+ @search_field.addClassName "default"
257
+ else
258
+ @search_field.value = ""
259
+ @search_field.removeClassName "default"
260
+
261
+ search_results_mouseup: (evt) ->
262
+ target = if evt.target.hasClassName("active-result") then evt.target else evt.target.up(".active-result")
263
+ if target
264
+ @result_highlight = target
265
+ this.result_select(evt)
266
+
267
+ search_results_mouseover: (evt) ->
268
+ target = if evt.target.hasClassName("active-result") then evt.target else evt.target.up(".active-result")
269
+ this.result_do_highlight( target ) if target
270
+
271
+ search_results_mouseout: (evt) ->
272
+ this.result_clear_highlight() if evt.target.hasClassName('active-result') or evt.target.up('.active-result')
273
+
274
+
275
+ choices_click: (evt) ->
276
+ evt.preventDefault()
277
+ if( @active_field and not(evt.target.hasClassName('search-choice') or evt.target.up('.search-choice')) and not @results_showing )
278
+ this.results_show()
279
+
280
+ choice_build: (item) ->
281
+ choice_id = @container_id + "_c_" + item.array_index
282
+ @choices += 1
283
+ @search_container.insert
284
+ before: @choice_temp.evaluate
285
+ id: choice_id
286
+ choice: item.html
287
+ position: item.array_index
288
+ link = $(choice_id).down('a')
289
+ link.observe "click", (evt) => this.choice_destroy_link_click(evt)
290
+
291
+ choice_destroy_link_click: (evt) ->
292
+ evt.preventDefault()
293
+ if not @is_disabled
294
+ @pending_destroy_click = true
295
+ this.choice_destroy evt.target
296
+
297
+ choice_destroy: (link) ->
298
+ @choices -= 1
299
+ this.show_search_field_default()
300
+
301
+ this.results_hide() if @is_multiple and @choices > 0 and @search_field.value.length < 1
302
+
303
+ this.result_deselect link.readAttribute("rel")
304
+ link.up('li').remove()
305
+
306
+ results_reset: (evt) ->
307
+ @form_field.options[0].selected = true
308
+ @selected_item.down("span").update(@default_text)
309
+ this.show_search_field_default()
310
+ evt.target.remove()
311
+ @form_field.simulate("change") if typeof Event.simulate is 'function'
312
+ this.results_hide() if @active_field
313
+
314
+ result_select: (evt) ->
315
+ if @result_highlight
316
+ high = @result_highlight
317
+ this.result_clear_highlight()
318
+
319
+ if @is_multiple
320
+ this.result_deactivate high
321
+ else
322
+ @search_results.descendants(".result-selected").invoke "removeClassName", "result-selected"
323
+ @result_single_selected = high
324
+
325
+ high.addClassName("result-selected")
326
+
327
+ position = high.id.substr(high.id.lastIndexOf("_") + 1 )
328
+ item = @results_data[position]
329
+ item.selected = true
330
+
331
+ @form_field.options[item.options_index].selected = true
332
+
333
+ if @is_multiple
334
+ this.choice_build item
335
+ else
336
+ @selected_item.down("span").update(item.html)
337
+ this.single_deselect_control_build() if @allow_single_deselect
338
+
339
+ this.results_hide() unless evt.metaKey and @is_multiple
340
+
341
+ @search_field.value = ""
342
+
343
+ @form_field.simulate("change") if typeof Event.simulate is 'function'
344
+ this.search_field_scale()
345
+
346
+ result_activate: (el) ->
347
+ el.addClassName("active-result")
348
+
349
+ result_deactivate: (el) ->
350
+ el.removeClassName("active-result")
351
+
352
+ result_deselect: (pos) ->
353
+ result_data = @results_data[pos]
354
+ result_data.selected = false
355
+
356
+ @form_field.options[result_data.options_index].selected = false
357
+ result = $(@container_id + "_o_" + pos)
358
+ result.removeClassName("result-selected").addClassName("active-result").show()
359
+
360
+ this.result_clear_highlight()
361
+ this.winnow_results()
362
+
363
+ @form_field.simulate("change") if typeof Event.simulate is 'function'
364
+ this.search_field_scale()
365
+
366
+ single_deselect_control_build: ->
367
+ @selected_item.down("span").insert { after: "<abbr class=\"search-choice-close\"></abbr>" } if @allow_single_deselect and not @selected_item.down("abbr")
368
+
369
+ winnow_results: ->
370
+ this.no_results_clear()
371
+
372
+ results = 0
373
+
374
+ searchText = if @search_field.value is @default_text then "" else @search_field.value.strip().escapeHTML()
375
+ regex = new RegExp('^' + searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i')
376
+ zregex = new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i')
377
+
378
+ for option in @results_data
379
+ if not option.disabled and not option.empty
380
+ if option.group
381
+ $(option.dom_id).hide()
382
+ else if not (@is_multiple and option.selected)
383
+ found = false
384
+ result_id = option.dom_id
385
+
386
+ if regex.test option.html
387
+ found = true
388
+ results += 1
389
+ else if option.html.indexOf(" ") >= 0 or option.html.indexOf("[") == 0
390
+ #TODO: replace this substitution of /\[\]/ with a list of characters to skip.
391
+ parts = option.html.replace(/\[|\]/g, "").split(" ")
392
+ if parts.length
393
+ for part in parts
394
+ if regex.test part
395
+ found = true
396
+ results += 1
397
+
398
+ if found
399
+ if searchText.length
400
+ startpos = option.html.search zregex
401
+ text = option.html.substr(0, startpos + searchText.length) + '</em>' + option.html.substr(startpos + searchText.length)
402
+ text = text.substr(0, startpos) + '<em>' + text.substr(startpos)
403
+ else
404
+ text = option.html
405
+
406
+ $(result_id).update text if $(result_id).innerHTML != text
407
+
408
+ this.result_activate $(result_id)
409
+
410
+ $(@results_data[option.group_array_index].dom_id).setStyle({display: 'list-item'}) if option.group_array_index?
411
+ else
412
+ this.result_clear_highlight() if $(result_id) is @result_highlight
413
+ this.result_deactivate $(result_id)
414
+
415
+ if results < 1 and searchText.length
416
+ this.no_results(searchText)
417
+ else
418
+ this.winnow_results_set_highlight()
419
+
420
+ winnow_results_clear: ->
421
+ @search_field.clear()
422
+ lis = @search_results.select("li")
423
+
424
+ for li in lis
425
+ if li.hasClassName("group-result")
426
+ li.show()
427
+ else if not @is_multiple or not li.hasClassName("result-selected")
428
+ this.result_activate li
429
+
430
+ winnow_results_set_highlight: ->
431
+ if not @result_highlight
432
+
433
+ if not @is_multiple
434
+ do_high = @search_results.down(".result-selected.active-result")
435
+
436
+ if not do_high?
437
+ do_high = @search_results.down(".active-result")
438
+
439
+ this.result_do_highlight do_high if do_high?
440
+
441
+ no_results: (terms) ->
442
+ @search_results.insert @no_results_temp.evaluate( terms: terms )
443
+
444
+ no_results_clear: ->
445
+ nr = null
446
+ nr.remove() while nr = @search_results.down(".no-results")
447
+
448
+
449
+ keydown_arrow: ->
450
+ actives = @search_results.select("li.active-result")
451
+ if actives.length
452
+ if not @result_highlight
453
+ this.result_do_highlight actives.first()
454
+ else if @results_showing
455
+ sibs = @result_highlight.nextSiblings()
456
+ nexts = sibs.intersect(actives)
457
+ this.result_do_highlight nexts.first() if nexts.length
458
+ this.results_show() if not @results_showing
459
+
460
+ keyup_arrow: ->
461
+ if not @results_showing and not @is_multiple
462
+ this.results_show()
463
+ else if @result_highlight
464
+ sibs = @result_highlight.previousSiblings()
465
+ actives = @search_results.select("li.active-result")
466
+ prevs = sibs.intersect(actives)
467
+
468
+ if prevs.length
469
+ this.result_do_highlight prevs.first()
470
+ else
471
+ this.results_hide() if @choices > 0
472
+ this.result_clear_highlight()
473
+
474
+ keydown_backstroke: ->
475
+ if @pending_backstroke
476
+ this.choice_destroy @pending_backstroke.down("a")
477
+ this.clear_backstroke()
478
+ else
479
+ @pending_backstroke = @search_container.siblings("li.search-choice").last()
480
+ @pending_backstroke.addClassName("search-choice-focus")
481
+
482
+ clear_backstroke: ->
483
+ @pending_backstroke.removeClassName("search-choice-focus") if @pending_backstroke
484
+ @pending_backstroke = null
485
+
486
+ keydown_checker: (evt) ->
487
+ stroke = evt.which ? evt.keyCode
488
+ this.search_field_scale()
489
+
490
+ this.clear_backstroke() if stroke != 8 and this.pending_backstroke
491
+
492
+ switch stroke
493
+ when 8
494
+ @backstroke_length = this.search_field.value.length
495
+ break
496
+ when 9
497
+ this.result_select(evt) if this.results_showing and not @is_multiple
498
+ @mouse_on_container = false
499
+ break
500
+ when 13
501
+ evt.preventDefault()
502
+ break
503
+ when 38
504
+ evt.preventDefault()
505
+ this.keyup_arrow()
506
+ break
507
+ when 40
508
+ this.keydown_arrow()
509
+ break
510
+
511
+ search_field_scale: ->
512
+ if @is_multiple
513
+ h = 0
514
+ w = 0
515
+
516
+ style_block = "position:absolute; left: -1000px; top: -1000px; display:none;"
517
+ styles = ['font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing']
518
+
519
+ for style in styles
520
+ style_block += style + ":" + @search_field.getStyle(style) + ";"
521
+
522
+ div = new Element('div', { 'style' : style_block }).update(@search_field.value.escapeHTML())
523
+ document.body.appendChild(div)
524
+
525
+ w = Element.measure(div, 'width') + 25
526
+ div.remove()
527
+
528
+ if( w > @f_width-10 )
529
+ w = @f_width - 10
530
+
531
+ @search_field.setStyle({'width': w + 'px'})
532
+
533
+ dd_top = @container.getHeight()
534
+ @dropdown.setStyle({"top": dd_top + "px"})
535
+
536
+ root.Chosen = Chosen
537
+
538
+ # Prototype does not support version numbers so we add it ourselves
539
+ if Prototype.Browser.IE
540
+ if /MSIE (\d+\.\d+);/.test(navigator.userAgent)
541
+ Prototype.BrowserFeatures['Version'] = new Number(RegExp.$1);
542
+
543
+
544
+ get_side_border_padding = (elmt) ->
545
+ layout = new Element.Layout(elmt)
546
+ side_border_padding = layout.get("border-left") + layout.get("border-right") + layout.get("padding-left") + layout.get("padding-right")
547
+
548
+ root.get_side_border_padding = get_side_border_padding