Skip to content

Commit

Permalink
Use two inputs for Chosen’s focus and search.
Browse files Browse the repository at this point in the history
Moving a single input to catch keyboard events isn’t reliable in IE11, so we must use two for adequate cross-browser support.

Note: the `setTimeout`s are needed for the cut/paste events to make sure the
value being read from the `focus_field` happens after the events finish
changing the input's value.
  • Loading branch information
adunkman committed Apr 9, 2018
1 parent 0ab2f69 commit ee36be0
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 18 deletions.
27 changes: 18 additions & 9 deletions coffee/chosen.jquery.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ class Chosen extends AbstractChosen
@form_field_jq.hide().after @container
@dropdown = @container.find('div.chosen-drop').first()

@search_field = @container.find('input').first()
@search_field = @container.find('input.chosen-search-input')
@focus_field = @container.find('input.chosen-focus-input')
@search_results = @container.find('ul.chosen-results').first()
this.search_field_scale()

Expand Down Expand Up @@ -105,6 +106,18 @@ class Chosen extends AbstractChosen
else
@container.on 'click.chosen', (evt) -> evt.preventDefault(); return # gobble click of anchor

@focus_field.on 'blur.chosen', (evt) => this.input_blur(evt); return
@focus_field.on 'focus.chosen', (evt) => this.input_focus(evt); return

transfer_value = () =>
@search_field.val(@focus_field.val())
@focus_field.val('')

@focus_field.on 'keyup.chosen', (evt) => transfer_value(); this.keyup_checker(evt); return
@focus_field.on 'keydown.chosen', (evt) => transfer_value(); this.keydown_checker(evt); return
@focus_field.on 'cut.chosen', (evt) => setTimeout(transfer_value, 0); this.clipboard_event_checker(evt); return
@focus_field.on 'paste.chosen', (evt) => setTimeout(transfer_value, 0); this.clipboard_event_checker(evt); return

destroy: ->
$(@container[0].ownerDocument).off 'click.chosen', @click_test_action
@form_field_label.off 'click.chosen' if @form_field_label.length > 0
Expand Down Expand Up @@ -179,10 +192,8 @@ class Chosen extends AbstractChosen
@container.addClass "chosen-container-active"
@active_field = true

@search_field.val(@search_field.val())
@search_field.focus()


test_active_click: (evt) ->
active_container = $(evt.target).closest('.chosen-container')
if active_container.length and @container[0] == active_container[0]
Expand All @@ -202,9 +213,11 @@ class Chosen extends AbstractChosen
this.single_set_selected_text()
if @disable_search or @form_field.options.length <= @disable_search_threshold
@search_field[0].readOnly = true
@focus_field[0].readOnly = true
@container.addClass "chosen-container-single-nosearch"
else
@search_field[0].readOnly = false
@focus_field[0].readOnly = false
@container.removeClass "chosen-container-single-nosearch"

this.update_results_content this.results_option_build({first:true})
Expand Down Expand Up @@ -243,9 +256,6 @@ class Chosen extends AbstractChosen
@form_field_jq.trigger("chosen:maxselected", {chosen: this})
return false

unless @is_multiple
@search_container.append @search_field

@container.addClass "chosen-with-drop"
@results_showing = true

Expand All @@ -262,9 +272,7 @@ class Chosen extends AbstractChosen
if @results_showing
this.result_clear_highlight()

unless @is_multiple
@selected_item.prepend @search_field
@search_field.focus()
setTimeout((() => @focus_field.focus()), 0)

@container.removeClass "chosen-with-drop"
@form_field_jq.trigger("chosen:hiding_dropdown", {chosen: this})
Expand All @@ -277,6 +285,7 @@ class Chosen extends AbstractChosen
ti = @form_field.tabIndex
@form_field.tabIndex = -1
@search_field[0].tabIndex = ti
@focus_field[0]?.tabIndex = ti

set_label_behavior: ->
@form_field_label = @form_field_jq.parents("label") # first check for a parent label
Expand Down
26 changes: 18 additions & 8 deletions coffee/chosen.proto.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class @Chosen extends AbstractChosen
@form_field.hide().insert({ after: @container })
@dropdown = @container.down('div.chosen-drop')

@search_field = @container.down('input')
@search_field = @container.down('input.chosen-search-input')
@focus_field = @container.down('input.chosen-focus-input')
@search_results = @container.down('ul.chosen-results')
this.search_field_scale()

Expand Down Expand Up @@ -84,6 +85,18 @@ class @Chosen extends AbstractChosen
else
@container.observe "click", (evt) => evt.preventDefault() # gobble click of anchor

@focus_field.observe "blur", (evt) => this.input_blur(evt)
@focus_field.observe "focus", (evt) => this.input_focus(evt)

transfer_value = () =>
@search_field.value = @focus_field.value
@focus_field.value = ""

@focus_field.observe "keyup", (evt) => transfer_value(); this.keyup_checker(evt)
@focus_field.observe "keydown", (evt) => transfer_value(); this.keydown_checker(evt)
@focus_field.observe "cut", (evt) => setTimeout(transfer_value, 0); this.clipboard_event_checker(evt)
@focus_field.observe "paste", (evt) => setTimeout(transfer_value, 0); this.clipboard_event_checker(evt)

destroy: ->
@container.ownerDocument.stopObserving "click", @click_test_action

Expand Down Expand Up @@ -174,7 +187,6 @@ class @Chosen extends AbstractChosen
@container.addClassName "chosen-container-active"
@active_field = true

@search_field.value = this.get_search_field_value()
@search_field.focus()

test_active_click: (evt) ->
Expand All @@ -195,9 +207,11 @@ class @Chosen extends AbstractChosen
this.single_set_selected_text()
if @disable_search or @form_field.options.length <= @disable_search_threshold
@search_field.readOnly = true
@focus_field?.readOnly = true
@container.addClassName "chosen-container-single-nosearch"
else
@search_field.readOnly = false
@focus_field?.readOnly = false
@container.removeClassName "chosen-container-single-nosearch"

this.update_results_content this.results_option_build({first:true})
Expand Down Expand Up @@ -235,9 +249,6 @@ class @Chosen extends AbstractChosen
@form_field.fire("chosen:maxselected", {chosen: this})
return false

unless @is_multiple
@search_container.insert @search_field

@container.addClassName "chosen-with-drop"
@results_showing = true

Expand All @@ -254,9 +265,7 @@ class @Chosen extends AbstractChosen
if @results_showing
this.result_clear_highlight()

unless @is_multiple
@selected_item.insert top: @search_field
@search_field.focus()
setTimeout((() => @focus_field?.focus()), 0)

@container.removeClassName "chosen-with-drop"
@form_field.fire("chosen:hiding_dropdown", {chosen: this})
Expand All @@ -269,6 +278,7 @@ class @Chosen extends AbstractChosen
ti = @form_field.tabIndex
@form_field.tabIndex = -1
@search_field.tabIndex = ti
@focus_field?.tabIndex = ti

set_label_behavior: ->
@form_field_label = @form_field.up("label") # first check for a parent label
Expand Down
3 changes: 2 additions & 1 deletion coffee/lib/abstract-chosen.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,13 @@ class AbstractChosen
get_single_html: ->
"""
<a class="chosen-single chosen-default">
<input class="chosen-search-input" type="text" autocomplete="off" />
<input class="chosen-focus-input" type="text" autocomplete="off" />
<span>#{@default_text}</span>
<div><b></b></div>
</a>
<div class="chosen-drop">
<div class="chosen-search">
<input class="chosen-search-input" type="text" autocomplete="off" />
</div>
<ul class="chosen-results"></ul>
</div>
Expand Down
1 change: 1 addition & 0 deletions sass/chosen.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ $chosen-sprite-retina: url('[email protected]') !default;
cursor: pointer;
opacity: 0;
position: absolute;
width: 0;
}
}
.chosen-default {
Expand Down

0 comments on commit ee36be0

Please sign in to comment.