trln-chosen-rails 1.8.7

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,523 @@
1
+ class @Chosen extends AbstractChosen
2
+
3
+ setup: ->
4
+ @current_selectedIndex = @form_field.selectedIndex
5
+
6
+ set_up_html: ->
7
+ container_classes = ["chosen-container"]
8
+ container_classes.push "chosen-container-" + (if @is_multiple then "multi" else "single")
9
+ container_classes.push @form_field.className if @inherit_select_classes && @form_field.className
10
+ container_classes.push "chosen-rtl" if @is_rtl
11
+
12
+ container_props =
13
+ 'class': container_classes.join ' '
14
+ 'title': @form_field.title
15
+
16
+ container_props.id = @form_field.id.replace(/[^\w]/g, '_') + "_chosen" if @form_field.id.length
17
+
18
+ @container = new Element('div', container_props)
19
+
20
+ # CSP without 'unsafe-inline' doesn't allow setting the style attribute directly
21
+ @container.setStyle(width: this.container_width())
22
+
23
+ if @is_multiple
24
+ @container.update this.get_multi_html()
25
+ else
26
+ @container.update this.get_single_html()
27
+
28
+ @form_field.hide().insert({ after: @container })
29
+ @dropdown = @container.down('div.chosen-drop')
30
+
31
+ @search_field = @container.down('input')
32
+ @search_results = @container.down('ul.chosen-results')
33
+ this.search_field_scale()
34
+
35
+ @search_no_results = @container.down('li.no-results')
36
+
37
+ if @is_multiple
38
+ @search_choices = @container.down('ul.chosen-choices')
39
+ @search_container = @container.down('li.search-field')
40
+ else
41
+ @search_container = @container.down('div.chosen-search')
42
+ @selected_item = @container.down('.chosen-single')
43
+
44
+ this.results_build()
45
+ this.set_tab_index()
46
+ this.set_label_behavior()
47
+
48
+ on_ready: ->
49
+ @form_field.fire("chosen:ready", {chosen: this})
50
+
51
+ register_observers: ->
52
+ @container.observe "touchstart", (evt) => this.container_mousedown(evt)
53
+ @container.observe "touchend", (evt) => this.container_mouseup(evt)
54
+
55
+ @container.observe "mousedown", (evt) => this.container_mousedown(evt)
56
+ @container.observe "mouseup", (evt) => this.container_mouseup(evt)
57
+ @container.observe "mouseenter", (evt) => this.mouse_enter(evt)
58
+ @container.observe "mouseleave", (evt) => this.mouse_leave(evt)
59
+
60
+ @search_results.observe "mouseup", (evt) => this.search_results_mouseup(evt)
61
+ @search_results.observe "mouseover", (evt) => this.search_results_mouseover(evt)
62
+ @search_results.observe "mouseout", (evt) => this.search_results_mouseout(evt)
63
+ @search_results.observe "mousewheel", (evt) => this.search_results_mousewheel(evt)
64
+ @search_results.observe "DOMMouseScroll", (evt) => this.search_results_mousewheel(evt)
65
+
66
+ @search_results.observe "touchstart", (evt) => this.search_results_touchstart(evt)
67
+ @search_results.observe "touchmove", (evt) => this.search_results_touchmove(evt)
68
+ @search_results.observe "touchend", (evt) => this.search_results_touchend(evt)
69
+
70
+ @form_field.observe "chosen:updated", (evt) => this.results_update_field(evt)
71
+ @form_field.observe "chosen:activate", (evt) => this.activate_field(evt)
72
+ @form_field.observe "chosen:open", (evt) => this.container_mousedown(evt)
73
+ @form_field.observe "chosen:close", (evt) => this.close_field(evt)
74
+
75
+ @search_field.observe "blur", (evt) => this.input_blur(evt)
76
+ @search_field.observe "keyup", (evt) => this.keyup_checker(evt)
77
+ @search_field.observe "keydown", (evt) => this.keydown_checker(evt)
78
+ @search_field.observe "focus", (evt) => this.input_focus(evt)
79
+ @search_field.observe "cut", (evt) => this.clipboard_event_checker(evt)
80
+ @search_field.observe "paste", (evt) => this.clipboard_event_checker(evt)
81
+
82
+ if @is_multiple
83
+ @search_choices.observe "click", (evt) => this.choices_click(evt)
84
+ else
85
+ @container.observe "click", (evt) => evt.preventDefault() # gobble click of anchor
86
+
87
+ destroy: ->
88
+ @container.ownerDocument.stopObserving "click", @click_test_action
89
+
90
+ for event in ['chosen:updated', 'chosen:activate', 'chosen:open', 'chosen:close']
91
+ @form_field.stopObserving(event)
92
+
93
+ @container.stopObserving()
94
+ @search_results.stopObserving()
95
+ @search_field.stopObserving()
96
+ @form_field_label.stopObserving() if @form_field_label?
97
+
98
+ if @is_multiple
99
+ @search_choices.stopObserving()
100
+ @container.select(".search-choice-close").each (choice) ->
101
+ choice.stopObserving()
102
+ else
103
+ @selected_item.stopObserving()
104
+
105
+ if @search_field.tabIndex
106
+ @form_field.tabIndex = @search_field.tabIndex
107
+
108
+ @container.remove()
109
+ @form_field.show()
110
+
111
+ search_field_disabled: ->
112
+ @is_disabled = @form_field.disabled || @form_field.up('fieldset')?.disabled || false
113
+
114
+ if @is_disabled
115
+ @container.addClassName 'chosen-disabled'
116
+ else
117
+ @container.removeClassName 'chosen-disabled'
118
+
119
+ @search_field.disabled = @is_disabled
120
+
121
+ unless @is_multiple
122
+ @selected_item.stopObserving 'focus', this.activate_field
123
+
124
+ if @is_disabled
125
+ this.close_field()
126
+ else unless @is_multiple
127
+ @selected_item.observe 'focus', this.activate_field
128
+
129
+ container_mousedown: (evt) ->
130
+ return if @is_disabled
131
+
132
+ if evt and evt.type in ['mousedown', 'touchstart'] and not @results_showing
133
+ evt.preventDefault()
134
+
135
+ if not (evt? and evt.target.hasClassName "search-choice-close")
136
+ if not @active_field
137
+ @search_field.clear() if @is_multiple
138
+ @container.ownerDocument.observe "click", @click_test_action
139
+ this.results_show()
140
+ else if not @is_multiple and evt and (evt.target is @selected_item || evt.target.up("a.chosen-single"))
141
+ this.results_toggle()
142
+
143
+ this.activate_field()
144
+
145
+ container_mouseup: (evt) ->
146
+ this.results_reset(evt) if evt.target.nodeName is "ABBR" and not @is_disabled
147
+
148
+ search_results_mousewheel: (evt) ->
149
+ delta = evt.deltaY or -evt.wheelDelta or evt.detail
150
+ if delta?
151
+ evt.preventDefault()
152
+ delta = delta * 40 if evt.type is 'DOMMouseScroll'
153
+ @search_results.scrollTop = delta + @search_results.scrollTop
154
+
155
+ blur_test: (evt) ->
156
+ this.close_field() if not @active_field and @container.hasClassName("chosen-container-active")
157
+
158
+ close_field: ->
159
+ @container.ownerDocument.stopObserving "click", @click_test_action
160
+
161
+ @active_field = false
162
+ this.results_hide()
163
+
164
+ @container.removeClassName "chosen-container-active"
165
+ this.clear_backstroke()
166
+
167
+ this.show_search_field_default()
168
+ this.search_field_scale()
169
+ @search_field.blur()
170
+
171
+ activate_field: ->
172
+ return if @is_disabled
173
+
174
+ @container.addClassName "chosen-container-active"
175
+ @active_field = true
176
+
177
+ @search_field.value = this.get_search_field_value()
178
+ @search_field.focus()
179
+
180
+ test_active_click: (evt) ->
181
+ if evt.target.up('.chosen-container') is @container
182
+ @active_field = true
183
+ else
184
+ this.close_field()
185
+
186
+ results_build: ->
187
+ @parsing = true
188
+ @selected_option_count = null
189
+
190
+ @results_data = SelectParser.select_to_array @form_field
191
+
192
+ if @is_multiple
193
+ @search_choices.select("li.search-choice").invoke("remove")
194
+ else
195
+ this.single_set_selected_text()
196
+ if @disable_search or @form_field.options.length <= @disable_search_threshold
197
+ @search_field.readOnly = true
198
+ @container.addClassName "chosen-container-single-nosearch"
199
+ else
200
+ @search_field.readOnly = false
201
+ @container.removeClassName "chosen-container-single-nosearch"
202
+
203
+ this.update_results_content this.results_option_build({first:true})
204
+
205
+ this.search_field_disabled()
206
+ this.show_search_field_default()
207
+ this.search_field_scale()
208
+
209
+ @parsing = false
210
+
211
+ result_do_highlight: (el) ->
212
+ this.result_clear_highlight()
213
+
214
+ @result_highlight = el
215
+ @result_highlight.addClassName "highlighted"
216
+
217
+ maxHeight = parseInt @search_results.getStyle('maxHeight'), 10
218
+ visible_top = @search_results.scrollTop
219
+ visible_bottom = maxHeight + visible_top
220
+
221
+ high_top = @result_highlight.positionedOffset().top
222
+ high_bottom = high_top + @result_highlight.getHeight()
223
+
224
+ if high_bottom >= visible_bottom
225
+ @search_results.scrollTop = if (high_bottom - maxHeight) > 0 then (high_bottom - maxHeight) else 0
226
+ else if high_top < visible_top
227
+ @search_results.scrollTop = high_top
228
+
229
+ result_clear_highlight: ->
230
+ @result_highlight.removeClassName('highlighted') if @result_highlight
231
+ @result_highlight = null
232
+
233
+ results_show: ->
234
+ if @is_multiple and @max_selected_options <= this.choices_count()
235
+ @form_field.fire("chosen:maxselected", {chosen: this})
236
+ return false
237
+
238
+ @container.addClassName "chosen-with-drop"
239
+ @results_showing = true
240
+
241
+ @search_field.focus()
242
+ @search_field.value = this.get_search_field_value()
243
+
244
+ this.winnow_results()
245
+ @form_field.fire("chosen:showing_dropdown", {chosen: this})
246
+
247
+ update_results_content: (content) ->
248
+ @search_results.update content
249
+
250
+ results_hide: ->
251
+ if @results_showing
252
+ this.result_clear_highlight()
253
+
254
+ @container.removeClassName "chosen-with-drop"
255
+ @form_field.fire("chosen:hiding_dropdown", {chosen: this})
256
+
257
+ @results_showing = false
258
+
259
+
260
+ set_tab_index: (el) ->
261
+ if @form_field.tabIndex
262
+ ti = @form_field.tabIndex
263
+ @form_field.tabIndex = -1
264
+ @search_field.tabIndex = ti
265
+
266
+ set_label_behavior: ->
267
+ @form_field_label = @form_field.up("label") # first check for a parent label
268
+ if not @form_field_label?
269
+ @form_field_label = $$("label[for='#{@form_field.id}']").first() #next check for a for=#{id}
270
+
271
+ if @form_field_label?
272
+ @form_field_label.observe "click", this.label_click_handler
273
+
274
+ show_search_field_default: ->
275
+ if @is_multiple and this.choices_count() < 1 and not @active_field
276
+ @search_field.value = @default_text
277
+ @search_field.addClassName "default"
278
+ else
279
+ @search_field.value = ""
280
+ @search_field.removeClassName "default"
281
+
282
+ search_results_mouseup: (evt) ->
283
+ target = if evt.target.hasClassName("active-result") then evt.target else evt.target.up(".active-result")
284
+ if target
285
+ @result_highlight = target
286
+ this.result_select(evt)
287
+ @search_field.focus()
288
+
289
+ search_results_mouseover: (evt) ->
290
+ target = if evt.target.hasClassName("active-result") then evt.target else evt.target.up(".active-result")
291
+ this.result_do_highlight( target ) if target
292
+
293
+ search_results_mouseout: (evt) ->
294
+ this.result_clear_highlight() if evt.target.hasClassName('active-result') or evt.target.up('.active-result')
295
+
296
+ choice_build: (item) ->
297
+ choice = new Element('li', { class: "search-choice" }).update("<span>#{this.choice_label(item)}</span>")
298
+
299
+ if item.disabled
300
+ choice.addClassName 'search-choice-disabled'
301
+ else
302
+ close_link = new Element('a', { href: '#', class: 'search-choice-close', rel: item.array_index })
303
+ close_link.observe "click", (evt) => this.choice_destroy_link_click(evt)
304
+ choice.insert close_link
305
+
306
+ @search_container.insert { before: choice }
307
+
308
+ choice_destroy_link_click: (evt) ->
309
+ evt.preventDefault()
310
+ evt.stopPropagation()
311
+ this.choice_destroy evt.target unless @is_disabled
312
+
313
+ choice_destroy: (link) ->
314
+ if this.result_deselect link.readAttribute("rel")
315
+ if @active_field
316
+ @search_field.focus()
317
+ else
318
+ this.show_search_field_default()
319
+
320
+ this.results_hide() if @is_multiple and this.choices_count() > 0 and this.get_search_field_value().length < 1
321
+
322
+ link.up('li').remove()
323
+
324
+ this.search_field_scale()
325
+
326
+ results_reset: ->
327
+ this.reset_single_select_options()
328
+ @form_field.options[0].selected = true
329
+ this.single_set_selected_text()
330
+ this.show_search_field_default()
331
+ this.results_reset_cleanup()
332
+ this.trigger_form_field_change()
333
+ this.results_hide() if @active_field
334
+
335
+ results_reset_cleanup: ->
336
+ @current_selectedIndex = @form_field.selectedIndex
337
+ deselect_trigger = @selected_item.down("abbr")
338
+ deselect_trigger.remove() if(deselect_trigger)
339
+
340
+ result_select: (evt) ->
341
+ if @result_highlight
342
+ high = @result_highlight
343
+ this.result_clear_highlight()
344
+
345
+ if @is_multiple and @max_selected_options <= this.choices_count()
346
+ @form_field.fire("chosen:maxselected", {chosen: this})
347
+ return false
348
+
349
+ if @is_multiple
350
+ high.removeClassName("active-result")
351
+ else
352
+ this.reset_single_select_options()
353
+
354
+ high.addClassName("result-selected")
355
+
356
+ item = @results_data[ high.getAttribute("data-option-array-index") ]
357
+ item.selected = true
358
+
359
+ @form_field.options[item.options_index].selected = true
360
+ @selected_option_count = null
361
+
362
+ if @is_multiple
363
+ this.choice_build item
364
+ else
365
+ this.single_set_selected_text(this.choice_label(item))
366
+
367
+ if @is_multiple && (!@hide_results_on_select || (evt.metaKey or evt.ctrlKey))
368
+ if evt.metaKey or evt.ctrlKey
369
+ this.winnow_results(skip_highlight: true)
370
+ else
371
+ @search_field.value = ""
372
+ this.winnow_results()
373
+ else
374
+ this.results_hide()
375
+ this.show_search_field_default()
376
+
377
+ this.trigger_form_field_change() if @is_multiple || @form_field.selectedIndex != @current_selectedIndex
378
+ @current_selectedIndex = @form_field.selectedIndex
379
+
380
+ evt.preventDefault()
381
+
382
+ this.search_field_scale()
383
+
384
+ single_set_selected_text: (text=@default_text) ->
385
+ if text is @default_text
386
+ @selected_item.addClassName("chosen-default")
387
+ else
388
+ this.single_deselect_control_build()
389
+ @selected_item.removeClassName("chosen-default")
390
+
391
+ @selected_item.down("span").update(text)
392
+
393
+ result_deselect: (pos) ->
394
+ result_data = @results_data[pos]
395
+
396
+ if not @form_field.options[result_data.options_index].disabled
397
+ result_data.selected = false
398
+
399
+ @form_field.options[result_data.options_index].selected = false
400
+ @selected_option_count = null
401
+
402
+ this.result_clear_highlight()
403
+ this.winnow_results() if @results_showing
404
+
405
+ this.trigger_form_field_change()
406
+ this.search_field_scale()
407
+ return true
408
+ else
409
+ return false
410
+
411
+ single_deselect_control_build: ->
412
+ return unless @allow_single_deselect
413
+ @selected_item.down("span").insert { after: "<abbr class=\"search-choice-close\"></abbr>" } unless @selected_item.down("abbr")
414
+ @selected_item.addClassName("chosen-single-with-deselect")
415
+
416
+ get_search_field_value: ->
417
+ @search_field.value
418
+
419
+ get_search_text: ->
420
+ this.get_search_field_value().strip()
421
+
422
+ escape_html: (text) ->
423
+ text.escapeHTML()
424
+
425
+ winnow_results_set_highlight: ->
426
+ if not @is_multiple
427
+ do_high = @search_results.down(".result-selected.active-result")
428
+
429
+ if not do_high?
430
+ do_high = @search_results.down(".active-result")
431
+
432
+ this.result_do_highlight do_high if do_high?
433
+
434
+ no_results: (terms) ->
435
+ @search_results.insert this.get_no_results_html(terms)
436
+ @form_field.fire("chosen:no_results", {chosen: this})
437
+
438
+ no_results_clear: ->
439
+ nr = null
440
+ nr.remove() while nr = @search_results.down(".no-results")
441
+
442
+
443
+ keydown_arrow: ->
444
+ if @results_showing and @result_highlight
445
+ next_sib = @result_highlight.next('.active-result')
446
+ this.result_do_highlight next_sib if next_sib
447
+ else
448
+ this.results_show()
449
+
450
+ keyup_arrow: ->
451
+ if not @results_showing and not @is_multiple
452
+ this.results_show()
453
+ else if @result_highlight
454
+ sibs = @result_highlight.previousSiblings()
455
+ actives = @search_results.select("li.active-result")
456
+ prevs = sibs.intersect(actives)
457
+
458
+ if prevs.length
459
+ this.result_do_highlight prevs.first()
460
+ else
461
+ this.results_hide() if this.choices_count() > 0
462
+ this.result_clear_highlight()
463
+
464
+ keydown_backstroke: ->
465
+ if @pending_backstroke
466
+ this.choice_destroy @pending_backstroke.down("a")
467
+ this.clear_backstroke()
468
+ else
469
+ next_available_destroy = @search_container.siblings().last()
470
+ if next_available_destroy and next_available_destroy.hasClassName("search-choice") and not next_available_destroy.hasClassName("search-choice-disabled")
471
+ @pending_backstroke = next_available_destroy
472
+ @pending_backstroke.addClassName("search-choice-focus") if @pending_backstroke
473
+ if @single_backstroke_delete
474
+ @keydown_backstroke()
475
+ else
476
+ @pending_backstroke.addClassName("search-choice-focus")
477
+
478
+ clear_backstroke: ->
479
+ @pending_backstroke.removeClassName("search-choice-focus") if @pending_backstroke
480
+ @pending_backstroke = null
481
+
482
+ search_field_scale: ->
483
+ return unless @is_multiple
484
+
485
+ style_block =
486
+ position: 'absolute'
487
+ left: '-1000px'
488
+ top: '-1000px'
489
+ display: 'none'
490
+ whiteSpace: 'pre'
491
+
492
+ styles = ['fontSize', 'fontStyle', 'fontWeight', 'fontFamily', 'lineHeight', 'textTransform', 'letterSpacing']
493
+
494
+ for style in styles
495
+ style_block[style] = @search_field.getStyle(style)
496
+
497
+ div = new Element('div').update(this.escape_html(this.get_search_field_value()))
498
+ # CSP without 'unsafe-inline' doesn't allow setting the style attribute directly
499
+ div.setStyle(style_block)
500
+ document.body.appendChild(div)
501
+
502
+ width = div.measure('width') + 25
503
+ div.remove()
504
+
505
+ if container_width = @container.getWidth()
506
+ width = Math.min(container_width - 10, width)
507
+
508
+ @search_field.setStyle(width: width + 'px')
509
+
510
+ trigger_form_field_change: ->
511
+ triggerHtmlEvent @form_field, 'input'
512
+ triggerHtmlEvent @form_field, 'change'
513
+
514
+ triggerHtmlEvent = (element, eventType) ->
515
+ if element.dispatchEvent # Modern way:
516
+ try
517
+ evt = new Event(eventType, bubbles: true, cancelable: true)
518
+ catch
519
+ evt = document.createEvent('HTMLEvents')
520
+ evt.initEvent(eventType, true, true);
521
+ element.dispatchEvent(evt)
522
+ else # Old IE:
523
+ element.fireEvent("on#{eventType}", document.createEventObject());