chosen-1.8.7/ 0000775 0000000 0000000 00000000000 13306546612 0013014 5 ustar 00root root 0000000 0000000 chosen-1.8.7/.github/ 0000775 0000000 0000000 00000000000 13306546612 0014354 5 ustar 00root root 0000000 0000000 chosen-1.8.7/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000002612 13306546612 0017062 0 ustar 00root root 0000000 0000000
Summarize your issue here.
### Steps to reproduce
Tell us how to reproduce this issue.
1. This is the first step
2. This is the second step
3. Further steps, etc.
Additionally, please link to a working demo that shows the issue so we can attempt to reproduce. You can use [this template](https://jsfiddle.net/j7k727cp/) as a base. Alternatively, confirm that the [Chosen demo page](http://harvesthq.github.io/chosen/) shows the issue.
### Expected behavior
Tell us what should happen.
### Actual behavior
Tell us what happens instead.
### Environment
- **Chosen Version**:
- **jQuery or Prototype Version**:
- **Browser and Version**:
- **OS and Version**:
### Additional information
Any other information you want to share that is relevant to the issue being reported. This might include the lines of code that you have identified as causing the bug, or potential solutions and workarounds.
chosen-1.8.7/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000002400 13306546612 0020151 0 ustar 00root root 0000000 0000000
### Summary
Provide a general description of the code changes in your pull request.
Please double-check that:
- [ ] All changes were made in CoffeeScript files, **not** JavaScript files.
- [ ] You used [Grunt](https://github.com/harvesthq/chosen/blob/master/contributing.md#grunt) to build the JavaScript files and tested them locally.
- [ ] You've updated both the jQuery *and* Prototype versions.
- [ ] You haven't manually updated the version number in `package.json`.
- [ ] If necessary, you've updated [the documentation](https://github.com/harvesthq/chosen/blob/master/public/options.html).
See the [Pull Requests section of our Contributing Guidelines](https://github.com/harvesthq/chosen/blob/master/contributing.md#pull-requests) for more details.
### References
If your pull request is in reference to one or more open GitHub issues, please mention them here to keep the conversations linked together.
chosen-1.8.7/.gitignore 0000664 0000000 0000000 00000000262 13306546612 0015004 0 ustar 00root root 0000000 0000000 .DS_Store
node_modules
.project
public/*.js
public/*.css
public/*.json
public/LICENSE.md
chosen*.zip
.sass-cache
.ruby-version
.rbenv-gemsets
.grunt
_SpecRunner.html
spec/public
chosen-1.8.7/.travis.yml 0000664 0000000 0000000 00000001022 13306546612 0015120 0 ustar 00root root 0000000 0000000 sudo: false
language: node_js
node_js:
- 6
- node
addons:
apt:
sources:
- git-core
packages:
- git
matrix:
fast_finish: true
allow_failures:
- node_js: node
before_install: npm install -g grunt-cli
before_script: grunt build package-npm package-bower
after_success: ./publish-package.sh
env:
global:
secure: "SOYNh0YO4eLAM38FQxrg7iqytXgdjJHRkmj/1lFzGrGeuuXP6Owe/2TaMyTJXWb9nHAAtRRwQyhAUE07eKhxI6b3YNyozeRulMK4B0K8P3P1B2MslpROyvQYtZupno3dWc0tyvsQ3ucnZE25mtetH6KYcwiI+vHv6hT8HnzBnp0="
chosen-1.8.7/Gruntfile.coffee 0000664 0000000 0000000 00000010006 13306546612 0016121 0 ustar 00root root 0000000 0000000 module.exports = (grunt) ->
require('load-grunt-tasks')(grunt)
grunt.loadNpmTasks('grunt1.0-dom-munger') # the naming convention of the package does not allow auto-discovery.
grunt.initConfig
pkg: grunt.file.readJSON('package.json')
version_tag: 'v<%= pkg.version %>'
comments: """
/*!
Chosen, a Select Box Enhancer for jQuery and Prototype
by Patrick Filler for Harvest, http://getharvest.com
Version <%= pkg.version %>
Full source at https://github.com/harvesthq/chosen
Copyright (c) 2011-<%= grunt.template.today('yyyy') %> Harvest http://getharvest.com
MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
This file is generated by `grunt build`, do not edit it by hand.
*/
\n
"""
minified_comments: "/* Chosen <%= version_tag %> | (c) 2011-<%= grunt.template.today('yyyy') %> by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */\n"
concat:
options:
banner: '<%= comments %>'
jquery:
src: ['public/chosen.jquery.js']
dest: 'public/chosen.jquery.js'
proto:
src: ['public/chosen.proto.js']
dest: 'public/chosen.proto.js'
css:
src: ['public/chosen.css']
dest: 'public/chosen.css'
copy:
main:
src: 'LICENSE.md'
dest: 'public/'
php:
src: 'composer.json'
dest: 'public/'
coffee:
options:
join: true
jquery:
files:
'public/chosen.jquery.js': ['coffee/lib/select-parser.coffee', 'coffee/lib/abstract-chosen.coffee', 'coffee/chosen.jquery.coffee']
proto:
files:
'public/chosen.proto.js': ['coffee/lib/select-parser.coffee', 'coffee/lib/abstract-chosen.coffee', 'coffee/chosen.proto.coffee']
test:
files:
'spec/public/jquery_specs.js': 'spec/jquery/*.spec.coffee'
'spec/public/proto_specs.js': 'spec/proto/*.spec.coffee'
uglify:
options:
banner: '<%= minified_comments %>'
jquery:
options:
ie8: true
mangle:
reserved: ['jQuery']
files:
'public/chosen.jquery.min.js': ['public/chosen.jquery.js']
proto:
files:
'public/chosen.proto.min.js': ['public/chosen.proto.js']
sass:
options:
outputStyle: 'expanded'
chosen_css:
files:
'public/chosen.css': 'sass/chosen.scss'
postcss:
options:
processors: [
require('autoprefixer')(browsers: 'last 2 versions, IE 8')
]
main:
src: 'public/chosen.css'
cssmin:
options:
banner: '<%= minified_comments %>'
keepSpecialComments: 0
main:
src: 'public/chosen.css'
dest: 'public/chosen.min.css'
watch:
default:
files: ['coffee/**/*.coffee', 'sass/*.scss']
tasks: ['build', 'jasmine']
test:
files: ['spec/**/*.coffee']
tasks: ['jasmine']
jasmine:
jquery:
options:
vendor: [
'public/docsupport/jquery-3.2.1.min.js'
]
specs: 'spec/public/jquery_specs.js'
src: [ 'public/chosen.jquery.js' ]
jquery_old:
options:
vendor: [
'public/docsupport/jquery-1.12.4.min.js'
]
specs: 'spec/public/jquery_specs.js'
src: [ 'public/chosen.jquery.js' ]
proto:
options:
vendor: [
'public/docsupport/prototype-1.7.0.0.js'
'node_modules/simulant/dist/simulant.umd.js'
]
specs: 'spec/public/proto_specs.js'
src: [ 'public/chosen.proto.js' ]
grunt.loadTasks 'tasks'
grunt.registerTask 'default', ['build']
grunt.registerTask 'build', ['coffee:jquery', 'coffee:proto', 'sass', 'concat', 'uglify', 'postcss', 'cssmin', 'copy']
grunt.registerTask 'test', ['coffee', 'jasmine']
grunt.registerTask 'test:jquery', ['coffee:test', 'coffee:jquery', 'jasmine:jquery', 'jasmine:jquery_old']
grunt.registerTask 'test:proto', ['coffee:test', 'coffee:proto', 'jasmine:proto']
chosen-1.8.7/LICENSE.md 0000664 0000000 0000000 00000002277 13306546612 0014430 0 ustar 00root root 0000000 0000000 #### Chosen
- by Patrick Filler for [Harvest](http://getharvest.com)
- Copyright (c) 2011-2016 by Harvest
Available for use under the [MIT License](http://en.wikipedia.org/wiki/MIT_License)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
chosen-1.8.7/README.md 0000664 0000000 0000000 00000003735 13306546612 0014303 0 ustar 00root root 0000000 0000000 # Chosen
Chosen is a library for making long, unwieldy select boxes more user friendly.
- jQuery support: 1.7+
- Prototype support: 1.7+
For **documentation**, usage, and examples, see:
http://harvesthq.github.io/chosen/
For **downloads**, see:
https://github.com/harvesthq/chosen/releases/
### Package managers
Chosen is available through [Bower](https://bower.io/), [npm](https://www.npmjs.com), and [Composer](https://getcomposer.org/), _however, the package names are not the same_.
To install with Bower:
```
bower install chosen
```
To install with npm:
```
npm install chosen-js
```
To install with Composer:
```
composer require harvesthq/chosen
```
The compiled files for these packages are automatically generated and stored in a [2nd Chosen repository](https://github.com/harvesthq/chosen-package). No pull requests will be accepted to that repository.
### Contributing to this project
We welcome all to participate in making Chosen the best software it can be. The repository is maintained by only a few people, but has accepted contributions from over 50 authors after reviewing hundreds of pull requests related to thousands of issues. You can help reduce the maintainers' workload (and increase your chance of having an accepted contribution to Chosen) by following the
[guidelines for contributing](contributing.md).
* [Bug reports](contributing.md#bugs)
* [Feature requests](contributing.md#features)
* [Pull requests](contributing.md#pull-requests)
### Chosen Credits
- Concept and development by [Patrick Filler](http://patrickfiller.com) for [Harvest](http://getharvest.com/).
- Design and CSS by [Matthew Lettini](http://matthewlettini.com/)
- Repository maintained by [@pfiller](http://github.com/pfiller), [@kenearley](http://github.com/kenearley), [@stof](http://github.com/stof), [@koenpunt](http://github.com/koenpunt), and [@tjschuck](http://github.com/tjschuck).
- Chosen includes [contributions by many fine folks](https://github.com/harvesthq/chosen/contributors).
chosen-1.8.7/coffee/ 0000775 0000000 0000000 00000000000 13306546612 0014243 5 ustar 00root root 0000000 0000000 chosen-1.8.7/coffee/chosen.jquery.coffee 0000664 0000000 0000000 00000042603 13306546612 0020216 0 ustar 00root root 0000000 0000000 $ = jQuery
$.fn.extend({
chosen: (options) ->
# Do no harm and return as soon as possible for unsupported browsers, namely IE6 and IE7
# Continue on if running IE document type but in compatibility mode
return this unless AbstractChosen.browser_is_supported()
this.each (input_field) ->
$this = $ this
chosen = $this.data('chosen')
if options is 'destroy'
if chosen instanceof Chosen
chosen.destroy()
return
unless chosen instanceof Chosen
$this.data('chosen', new Chosen(this, options))
return
})
class Chosen extends AbstractChosen
setup: ->
@form_field_jq = $ @form_field
@current_selectedIndex = @form_field.selectedIndex
set_up_html: ->
container_classes = ["chosen-container"]
container_classes.push "chosen-container-" + (if @is_multiple then "multi" else "single")
container_classes.push @form_field.className if @inherit_select_classes && @form_field.className
container_classes.push "chosen-rtl" if @is_rtl
container_props =
'class': container_classes.join ' '
'title': @form_field.title
container_props.id = @form_field.id.replace(/[^\w]/g, '_') + "_chosen" if @form_field.id.length
@container = ($ "
", container_props)
# CSP without 'unsafe-inline' doesn't allow setting the style attribute directly
@container.width this.container_width()
if @is_multiple
@container.html this.get_multi_html()
else
@container.html this.get_single_html()
@form_field_jq.hide().after @container
@dropdown = @container.find('div.chosen-drop').first()
@search_field = @container.find('input').first()
@search_results = @container.find('ul.chosen-results').first()
this.search_field_scale()
@search_no_results = @container.find('li.no-results').first()
if @is_multiple
@search_choices = @container.find('ul.chosen-choices').first()
@search_container = @container.find('li.search-field').first()
else
@search_container = @container.find('div.chosen-search').first()
@selected_item = @container.find('.chosen-single').first()
this.results_build()
this.set_tab_index()
this.set_label_behavior()
on_ready: ->
@form_field_jq.trigger("chosen:ready", {chosen: this})
register_observers: ->
@container.on 'touchstart.chosen', (evt) => this.container_mousedown(evt); return
@container.on 'touchend.chosen', (evt) => this.container_mouseup(evt); return
@container.on 'mousedown.chosen', (evt) => this.container_mousedown(evt); return
@container.on 'mouseup.chosen', (evt) => this.container_mouseup(evt); return
@container.on 'mouseenter.chosen', (evt) => this.mouse_enter(evt); return
@container.on 'mouseleave.chosen', (evt) => this.mouse_leave(evt); return
@search_results.on 'mouseup.chosen', (evt) => this.search_results_mouseup(evt); return
@search_results.on 'mouseover.chosen', (evt) => this.search_results_mouseover(evt); return
@search_results.on 'mouseout.chosen', (evt) => this.search_results_mouseout(evt); return
@search_results.on 'mousewheel.chosen DOMMouseScroll.chosen', (evt) => this.search_results_mousewheel(evt); return
@search_results.on 'touchstart.chosen', (evt) => this.search_results_touchstart(evt); return
@search_results.on 'touchmove.chosen', (evt) => this.search_results_touchmove(evt); return
@search_results.on 'touchend.chosen', (evt) => this.search_results_touchend(evt); return
@form_field_jq.on "chosen:updated.chosen", (evt) => this.results_update_field(evt); return
@form_field_jq.on "chosen:activate.chosen", (evt) => this.activate_field(evt); return
@form_field_jq.on "chosen:open.chosen", (evt) => this.container_mousedown(evt); return
@form_field_jq.on "chosen:close.chosen", (evt) => this.close_field(evt); return
@search_field.on 'blur.chosen', (evt) => this.input_blur(evt); return
@search_field.on 'keyup.chosen', (evt) => this.keyup_checker(evt); return
@search_field.on 'keydown.chosen', (evt) => this.keydown_checker(evt); return
@search_field.on 'focus.chosen', (evt) => this.input_focus(evt); return
@search_field.on 'cut.chosen', (evt) => this.clipboard_event_checker(evt); return
@search_field.on 'paste.chosen', (evt) => this.clipboard_event_checker(evt); return
if @is_multiple
@search_choices.on 'click.chosen', (evt) => this.choices_click(evt); return
else
@container.on 'click.chosen', (evt) -> evt.preventDefault(); return # gobble click of anchor
destroy: ->
$(@container[0].ownerDocument).off 'click.chosen', @click_test_action
@form_field_label.off 'click.chosen' if @form_field_label.length > 0
if @search_field[0].tabIndex
@form_field_jq[0].tabIndex = @search_field[0].tabIndex
@container.remove()
@form_field_jq.removeData('chosen')
@form_field_jq.show()
search_field_disabled: ->
@is_disabled = @form_field.disabled || @form_field_jq.parents('fieldset').is(':disabled')
@container.toggleClass 'chosen-disabled', @is_disabled
@search_field[0].disabled = @is_disabled
unless @is_multiple
@selected_item.off 'focus.chosen', this.activate_field
if @is_disabled
this.close_field()
else unless @is_multiple
@selected_item.on 'focus.chosen', this.activate_field
container_mousedown: (evt) ->
return if @is_disabled
if evt and evt.type in ['mousedown', 'touchstart'] and not @results_showing
evt.preventDefault()
if not (evt? and ($ evt.target).hasClass "search-choice-close")
if not @active_field
@search_field.val "" if @is_multiple
$(@container[0].ownerDocument).on 'click.chosen', @click_test_action
this.results_show()
else if not @is_multiple and evt and (($(evt.target)[0] == @selected_item[0]) || $(evt.target).parents("a.chosen-single").length)
evt.preventDefault()
this.results_toggle()
this.activate_field()
container_mouseup: (evt) ->
this.results_reset(evt) if evt.target.nodeName is "ABBR" and not @is_disabled
search_results_mousewheel: (evt) ->
delta = evt.originalEvent.deltaY or -evt.originalEvent.wheelDelta or evt.originalEvent.detail if evt.originalEvent
if delta?
evt.preventDefault()
delta = delta * 40 if evt.type is 'DOMMouseScroll'
@search_results.scrollTop(delta + @search_results.scrollTop())
blur_test: (evt) ->
this.close_field() if not @active_field and @container.hasClass "chosen-container-active"
close_field: ->
$(@container[0].ownerDocument).off "click.chosen", @click_test_action
@active_field = false
this.results_hide()
@container.removeClass "chosen-container-active"
this.clear_backstroke()
this.show_search_field_default()
this.search_field_scale()
@search_field.blur()
activate_field: ->
return if @is_disabled
@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]
@active_field = true
else
this.close_field()
results_build: ->
@parsing = true
@selected_option_count = null
@results_data = SelectParser.select_to_array @form_field
if @is_multiple
@search_choices.find("li.search-choice").remove()
else
this.single_set_selected_text()
if @disable_search or @form_field.options.length <= @disable_search_threshold
@search_field[0].readOnly = true
@container.addClass "chosen-container-single-nosearch"
else
@search_field[0].readOnly = false
@container.removeClass "chosen-container-single-nosearch"
this.update_results_content this.results_option_build({first:true})
this.search_field_disabled()
this.show_search_field_default()
this.search_field_scale()
@parsing = false
result_do_highlight: (el) ->
if el.length
this.result_clear_highlight()
@result_highlight = el
@result_highlight.addClass "highlighted"
maxHeight = parseInt @search_results.css("maxHeight"), 10
visible_top = @search_results.scrollTop()
visible_bottom = maxHeight + visible_top
high_top = @result_highlight.position().top + @search_results.scrollTop()
high_bottom = high_top + @result_highlight.outerHeight()
if high_bottom >= visible_bottom
@search_results.scrollTop if (high_bottom - maxHeight) > 0 then (high_bottom - maxHeight) else 0
else if high_top < visible_top
@search_results.scrollTop high_top
result_clear_highlight: ->
@result_highlight.removeClass "highlighted" if @result_highlight
@result_highlight = null
results_show: ->
if @is_multiple and @max_selected_options <= this.choices_count()
@form_field_jq.trigger("chosen:maxselected", {chosen: this})
return false
@container.addClass "chosen-with-drop"
@results_showing = true
@search_field.focus()
@search_field.val this.get_search_field_value()
this.winnow_results()
@form_field_jq.trigger("chosen:showing_dropdown", {chosen: this})
update_results_content: (content) ->
@search_results.html content
results_hide: ->
if @results_showing
this.result_clear_highlight()
@container.removeClass "chosen-with-drop"
@form_field_jq.trigger("chosen:hiding_dropdown", {chosen: this})
@results_showing = false
set_tab_index: (el) ->
if @form_field.tabIndex
ti = @form_field.tabIndex
@form_field.tabIndex = -1
@search_field[0].tabIndex = ti
set_label_behavior: ->
@form_field_label = @form_field_jq.parents("label") # first check for a parent label
if not @form_field_label.length and @form_field.id.length
@form_field_label = $("label[for='#{@form_field.id}']") #next check for a for=#{id}
if @form_field_label.length > 0
@form_field_label.on 'click.chosen', this.label_click_handler
show_search_field_default: ->
if @is_multiple and this.choices_count() < 1 and not @active_field
@search_field.val(@default_text)
@search_field.addClass "default"
else
@search_field.val("")
@search_field.removeClass "default"
search_results_mouseup: (evt) ->
target = if $(evt.target).hasClass "active-result" then $(evt.target) else $(evt.target).parents(".active-result").first()
if target.length
@result_highlight = target
this.result_select(evt)
@search_field.focus()
search_results_mouseover: (evt) ->
target = if $(evt.target).hasClass "active-result" then $(evt.target) else $(evt.target).parents(".active-result").first()
this.result_do_highlight( target ) if target
search_results_mouseout: (evt) ->
this.result_clear_highlight() if $(evt.target).hasClass("active-result") or $(evt.target).parents('.active-result').first()
choice_build: (item) ->
choice = $('', { class: "search-choice" }).html("#{this.choice_label(item)}")
if item.disabled
choice.addClass 'search-choice-disabled'
else
close_link = $('', { class: 'search-choice-close', 'data-option-array-index': item.array_index })
close_link.on 'click.chosen', (evt) => this.choice_destroy_link_click(evt)
choice.append close_link
@search_container.before choice
choice_destroy_link_click: (evt) ->
evt.preventDefault()
evt.stopPropagation()
this.choice_destroy $(evt.target) unless @is_disabled
choice_destroy: (link) ->
if this.result_deselect( link[0].getAttribute("data-option-array-index") )
if @active_field
@search_field.focus()
else
this.show_search_field_default()
this.results_hide() if @is_multiple and this.choices_count() > 0 and this.get_search_field_value().length < 1
link.parents('li').first().remove()
this.search_field_scale()
results_reset: ->
this.reset_single_select_options()
@form_field.options[0].selected = true
this.single_set_selected_text()
this.show_search_field_default()
this.results_reset_cleanup()
this.trigger_form_field_change()
this.results_hide() if @active_field
results_reset_cleanup: ->
@current_selectedIndex = @form_field.selectedIndex
@selected_item.find("abbr").remove()
result_select: (evt) ->
if @result_highlight
high = @result_highlight
this.result_clear_highlight()
if @is_multiple and @max_selected_options <= this.choices_count()
@form_field_jq.trigger("chosen:maxselected", {chosen: this})
return false
if @is_multiple
high.removeClass("active-result")
else
this.reset_single_select_options()
high.addClass("result-selected")
item = @results_data[ high[0].getAttribute("data-option-array-index") ]
item.selected = true
@form_field.options[item.options_index].selected = true
@selected_option_count = null
if @is_multiple
this.choice_build item
else
this.single_set_selected_text(this.choice_label(item))
if @is_multiple && (!@hide_results_on_select || (evt.metaKey or evt.ctrlKey))
if evt.metaKey or evt.ctrlKey
this.winnow_results(skip_highlight: true)
else
@search_field.val("")
this.winnow_results()
else
this.results_hide()
this.show_search_field_default()
this.trigger_form_field_change selected: @form_field.options[item.options_index].value if @is_multiple || @form_field.selectedIndex != @current_selectedIndex
@current_selectedIndex = @form_field.selectedIndex
evt.preventDefault()
this.search_field_scale()
single_set_selected_text: (text=@default_text) ->
if text is @default_text
@selected_item.addClass("chosen-default")
else
this.single_deselect_control_build()
@selected_item.removeClass("chosen-default")
@selected_item.find("span").html(text)
result_deselect: (pos) ->
result_data = @results_data[pos]
if not @form_field.options[result_data.options_index].disabled
result_data.selected = false
@form_field.options[result_data.options_index].selected = false
@selected_option_count = null
this.result_clear_highlight()
this.winnow_results() if @results_showing
this.trigger_form_field_change deselected: @form_field.options[result_data.options_index].value
this.search_field_scale()
return true
else
return false
single_deselect_control_build: ->
return unless @allow_single_deselect
@selected_item.find("span").first().after "" unless @selected_item.find("abbr").length
@selected_item.addClass("chosen-single-with-deselect")
get_search_field_value: ->
@search_field.val()
get_search_text: ->
$.trim this.get_search_field_value()
escape_html: (text) ->
$('').text(text).html()
winnow_results_set_highlight: ->
selected_results = if not @is_multiple then @search_results.find(".result-selected.active-result") else []
do_high = if selected_results.length then selected_results.first() else @search_results.find(".active-result").first()
this.result_do_highlight do_high if do_high?
no_results: (terms) ->
no_results_html = this.get_no_results_html(terms)
@search_results.append no_results_html
@form_field_jq.trigger("chosen:no_results", {chosen:this})
no_results_clear: ->
@search_results.find(".no-results").remove()
keydown_arrow: ->
if @results_showing and @result_highlight
next_sib = @result_highlight.nextAll("li.active-result").first()
this.result_do_highlight next_sib if next_sib
else
this.results_show()
keyup_arrow: ->
if not @results_showing and not @is_multiple
this.results_show()
else if @result_highlight
prev_sibs = @result_highlight.prevAll("li.active-result")
if prev_sibs.length
this.result_do_highlight prev_sibs.first()
else
this.results_hide() if this.choices_count() > 0
this.result_clear_highlight()
keydown_backstroke: ->
if @pending_backstroke
this.choice_destroy @pending_backstroke.find("a").first()
this.clear_backstroke()
else
next_available_destroy = @search_container.siblings("li.search-choice").last()
if next_available_destroy.length and not next_available_destroy.hasClass("search-choice-disabled")
@pending_backstroke = next_available_destroy
if @single_backstroke_delete
@keydown_backstroke()
else
@pending_backstroke.addClass "search-choice-focus"
clear_backstroke: ->
@pending_backstroke.removeClass "search-choice-focus" if @pending_backstroke
@pending_backstroke = null
search_field_scale: ->
return unless @is_multiple
style_block =
position: 'absolute'
left: '-1000px'
top: '-1000px'
display: 'none'
whiteSpace: 'pre'
styles = ['fontSize', 'fontStyle', 'fontWeight', 'fontFamily', 'lineHeight', 'textTransform', 'letterSpacing']
for style in styles
style_block[style] = @search_field.css(style)
div = $('').css(style_block)
div.text this.get_search_field_value()
$('body').append div
width = div.width() + 25
div.remove()
if @container.is(':visible')
width = Math.min(@container.outerWidth() - 10, width)
@search_field.width(width)
trigger_form_field_change: (extra) ->
@form_field_jq.trigger "input", extra
@form_field_jq.trigger "change", extra
chosen-1.8.7/coffee/chosen.proto.coffee 0000664 0000000 0000000 00000042373 13306546612 0020046 0 ustar 00root root 0000000 0000000 class @Chosen extends AbstractChosen
setup: ->
@current_selectedIndex = @form_field.selectedIndex
set_up_html: ->
container_classes = ["chosen-container"]
container_classes.push "chosen-container-" + (if @is_multiple then "multi" else "single")
container_classes.push @form_field.className if @inherit_select_classes && @form_field.className
container_classes.push "chosen-rtl" if @is_rtl
container_props =
'class': container_classes.join ' '
'title': @form_field.title
container_props.id = @form_field.id.replace(/[^\w]/g, '_') + "_chosen" if @form_field.id.length
@container = new Element('div', container_props)
# CSP without 'unsafe-inline' doesn't allow setting the style attribute directly
@container.setStyle(width: this.container_width())
if @is_multiple
@container.update this.get_multi_html()
else
@container.update this.get_single_html()
@form_field.hide().insert({ after: @container })
@dropdown = @container.down('div.chosen-drop')
@search_field = @container.down('input')
@search_results = @container.down('ul.chosen-results')
this.search_field_scale()
@search_no_results = @container.down('li.no-results')
if @is_multiple
@search_choices = @container.down('ul.chosen-choices')
@search_container = @container.down('li.search-field')
else
@search_container = @container.down('div.chosen-search')
@selected_item = @container.down('.chosen-single')
this.results_build()
this.set_tab_index()
this.set_label_behavior()
on_ready: ->
@form_field.fire("chosen:ready", {chosen: this})
register_observers: ->
@container.observe "touchstart", (evt) => this.container_mousedown(evt)
@container.observe "touchend", (evt) => this.container_mouseup(evt)
@container.observe "mousedown", (evt) => this.container_mousedown(evt)
@container.observe "mouseup", (evt) => this.container_mouseup(evt)
@container.observe "mouseenter", (evt) => this.mouse_enter(evt)
@container.observe "mouseleave", (evt) => this.mouse_leave(evt)
@search_results.observe "mouseup", (evt) => this.search_results_mouseup(evt)
@search_results.observe "mouseover", (evt) => this.search_results_mouseover(evt)
@search_results.observe "mouseout", (evt) => this.search_results_mouseout(evt)
@search_results.observe "mousewheel", (evt) => this.search_results_mousewheel(evt)
@search_results.observe "DOMMouseScroll", (evt) => this.search_results_mousewheel(evt)
@search_results.observe "touchstart", (evt) => this.search_results_touchstart(evt)
@search_results.observe "touchmove", (evt) => this.search_results_touchmove(evt)
@search_results.observe "touchend", (evt) => this.search_results_touchend(evt)
@form_field.observe "chosen:updated", (evt) => this.results_update_field(evt)
@form_field.observe "chosen:activate", (evt) => this.activate_field(evt)
@form_field.observe "chosen:open", (evt) => this.container_mousedown(evt)
@form_field.observe "chosen:close", (evt) => this.close_field(evt)
@search_field.observe "blur", (evt) => this.input_blur(evt)
@search_field.observe "keyup", (evt) => this.keyup_checker(evt)
@search_field.observe "keydown", (evt) => this.keydown_checker(evt)
@search_field.observe "focus", (evt) => this.input_focus(evt)
@search_field.observe "cut", (evt) => this.clipboard_event_checker(evt)
@search_field.observe "paste", (evt) => this.clipboard_event_checker(evt)
if @is_multiple
@search_choices.observe "click", (evt) => this.choices_click(evt)
else
@container.observe "click", (evt) => evt.preventDefault() # gobble click of anchor
destroy: ->
@container.ownerDocument.stopObserving "click", @click_test_action
for event in ['chosen:updated', 'chosen:activate', 'chosen:open', 'chosen:close']
@form_field.stopObserving(event)
@container.stopObserving()
@search_results.stopObserving()
@search_field.stopObserving()
@form_field_label.stopObserving() if @form_field_label?
if @is_multiple
@search_choices.stopObserving()
@container.select(".search-choice-close").each (choice) ->
choice.stopObserving()
else
@selected_item.stopObserving()
if @search_field.tabIndex
@form_field.tabIndex = @search_field.tabIndex
@container.remove()
@form_field.show()
search_field_disabled: ->
@is_disabled = @form_field.disabled || @form_field.up('fieldset')?.disabled || false
if @is_disabled
@container.addClassName 'chosen-disabled'
else
@container.removeClassName 'chosen-disabled'
@search_field.disabled = @is_disabled
unless @is_multiple
@selected_item.stopObserving 'focus', this.activate_field
if @is_disabled
this.close_field()
else unless @is_multiple
@selected_item.observe 'focus', this.activate_field
container_mousedown: (evt) ->
return if @is_disabled
if evt and evt.type in ['mousedown', 'touchstart'] and not @results_showing
evt.preventDefault()
if not (evt? and evt.target.hasClassName "search-choice-close")
if not @active_field
@search_field.clear() if @is_multiple
@container.ownerDocument.observe "click", @click_test_action
this.results_show()
else if not @is_multiple and evt and (evt.target is @selected_item || evt.target.up("a.chosen-single"))
this.results_toggle()
this.activate_field()
container_mouseup: (evt) ->
this.results_reset(evt) if evt.target.nodeName is "ABBR" and not @is_disabled
search_results_mousewheel: (evt) ->
delta = evt.deltaY or -evt.wheelDelta or evt.detail
if delta?
evt.preventDefault()
delta = delta * 40 if evt.type is 'DOMMouseScroll'
@search_results.scrollTop = delta + @search_results.scrollTop
blur_test: (evt) ->
this.close_field() if not @active_field and @container.hasClassName("chosen-container-active")
close_field: ->
@container.ownerDocument.stopObserving "click", @click_test_action
@active_field = false
this.results_hide()
@container.removeClassName "chosen-container-active"
this.clear_backstroke()
this.show_search_field_default()
this.search_field_scale()
@search_field.blur()
activate_field: ->
return if @is_disabled
@container.addClassName "chosen-container-active"
@active_field = true
@search_field.value = this.get_search_field_value()
@search_field.focus()
test_active_click: (evt) ->
if evt.target.up('.chosen-container') is @container
@active_field = true
else
this.close_field()
results_build: ->
@parsing = true
@selected_option_count = null
@results_data = SelectParser.select_to_array @form_field
if @is_multiple
@search_choices.select("li.search-choice").invoke("remove")
else
this.single_set_selected_text()
if @disable_search or @form_field.options.length <= @disable_search_threshold
@search_field.readOnly = true
@container.addClassName "chosen-container-single-nosearch"
else
@search_field.readOnly = false
@container.removeClassName "chosen-container-single-nosearch"
this.update_results_content this.results_option_build({first:true})
this.search_field_disabled()
this.show_search_field_default()
this.search_field_scale()
@parsing = false
result_do_highlight: (el) ->
this.result_clear_highlight()
@result_highlight = el
@result_highlight.addClassName "highlighted"
maxHeight = parseInt @search_results.getStyle('maxHeight'), 10
visible_top = @search_results.scrollTop
visible_bottom = maxHeight + visible_top
high_top = @result_highlight.positionedOffset().top
high_bottom = high_top + @result_highlight.getHeight()
if high_bottom >= visible_bottom
@search_results.scrollTop = if (high_bottom - maxHeight) > 0 then (high_bottom - maxHeight) else 0
else if high_top < visible_top
@search_results.scrollTop = high_top
result_clear_highlight: ->
@result_highlight.removeClassName('highlighted') if @result_highlight
@result_highlight = null
results_show: ->
if @is_multiple and @max_selected_options <= this.choices_count()
@form_field.fire("chosen:maxselected", {chosen: this})
return false
@container.addClassName "chosen-with-drop"
@results_showing = true
@search_field.focus()
@search_field.value = this.get_search_field_value()
this.winnow_results()
@form_field.fire("chosen:showing_dropdown", {chosen: this})
update_results_content: (content) ->
@search_results.update content
results_hide: ->
if @results_showing
this.result_clear_highlight()
@container.removeClassName "chosen-with-drop"
@form_field.fire("chosen:hiding_dropdown", {chosen: this})
@results_showing = false
set_tab_index: (el) ->
if @form_field.tabIndex
ti = @form_field.tabIndex
@form_field.tabIndex = -1
@search_field.tabIndex = ti
set_label_behavior: ->
@form_field_label = @form_field.up("label") # first check for a parent label
if not @form_field_label?
@form_field_label = $$("label[for='#{@form_field.id}']").first() #next check for a for=#{id}
if @form_field_label?
@form_field_label.observe "click", this.label_click_handler
show_search_field_default: ->
if @is_multiple and this.choices_count() < 1 and not @active_field
@search_field.value = @default_text
@search_field.addClassName "default"
else
@search_field.value = ""
@search_field.removeClassName "default"
search_results_mouseup: (evt) ->
target = if evt.target.hasClassName("active-result") then evt.target else evt.target.up(".active-result")
if target
@result_highlight = target
this.result_select(evt)
@search_field.focus()
search_results_mouseover: (evt) ->
target = if evt.target.hasClassName("active-result") then evt.target else evt.target.up(".active-result")
this.result_do_highlight( target ) if target
search_results_mouseout: (evt) ->
this.result_clear_highlight() if evt.target.hasClassName('active-result') or evt.target.up('.active-result')
choice_build: (item) ->
choice = new Element('li', { class: "search-choice" }).update("#{this.choice_label(item)}")
if item.disabled
choice.addClassName 'search-choice-disabled'
else
close_link = new Element('a', { href: '#', class: 'search-choice-close', rel: item.array_index })
close_link.observe "click", (evt) => this.choice_destroy_link_click(evt)
choice.insert close_link
@search_container.insert { before: choice }
choice_destroy_link_click: (evt) ->
evt.preventDefault()
evt.stopPropagation()
this.choice_destroy evt.target unless @is_disabled
choice_destroy: (link) ->
if this.result_deselect link.readAttribute("rel")
if @active_field
@search_field.focus()
else
this.show_search_field_default()
this.results_hide() if @is_multiple and this.choices_count() > 0 and this.get_search_field_value().length < 1
link.up('li').remove()
this.search_field_scale()
results_reset: ->
this.reset_single_select_options()
@form_field.options[0].selected = true
this.single_set_selected_text()
this.show_search_field_default()
this.results_reset_cleanup()
this.trigger_form_field_change()
this.results_hide() if @active_field
results_reset_cleanup: ->
@current_selectedIndex = @form_field.selectedIndex
deselect_trigger = @selected_item.down("abbr")
deselect_trigger.remove() if(deselect_trigger)
result_select: (evt) ->
if @result_highlight
high = @result_highlight
this.result_clear_highlight()
if @is_multiple and @max_selected_options <= this.choices_count()
@form_field.fire("chosen:maxselected", {chosen: this})
return false
if @is_multiple
high.removeClassName("active-result")
else
this.reset_single_select_options()
high.addClassName("result-selected")
item = @results_data[ high.getAttribute("data-option-array-index") ]
item.selected = true
@form_field.options[item.options_index].selected = true
@selected_option_count = null
if @is_multiple
this.choice_build item
else
this.single_set_selected_text(this.choice_label(item))
if @is_multiple && (!@hide_results_on_select || (evt.metaKey or evt.ctrlKey))
if evt.metaKey or evt.ctrlKey
this.winnow_results(skip_highlight: true)
else
@search_field.value = ""
this.winnow_results()
else
this.results_hide()
this.show_search_field_default()
this.trigger_form_field_change() if @is_multiple || @form_field.selectedIndex != @current_selectedIndex
@current_selectedIndex = @form_field.selectedIndex
evt.preventDefault()
this.search_field_scale()
single_set_selected_text: (text=@default_text) ->
if text is @default_text
@selected_item.addClassName("chosen-default")
else
this.single_deselect_control_build()
@selected_item.removeClassName("chosen-default")
@selected_item.down("span").update(text)
result_deselect: (pos) ->
result_data = @results_data[pos]
if not @form_field.options[result_data.options_index].disabled
result_data.selected = false
@form_field.options[result_data.options_index].selected = false
@selected_option_count = null
this.result_clear_highlight()
this.winnow_results() if @results_showing
this.trigger_form_field_change()
this.search_field_scale()
return true
else
return false
single_deselect_control_build: ->
return unless @allow_single_deselect
@selected_item.down("span").insert { after: "" } unless @selected_item.down("abbr")
@selected_item.addClassName("chosen-single-with-deselect")
get_search_field_value: ->
@search_field.value
get_search_text: ->
this.get_search_field_value().strip()
escape_html: (text) ->
text.escapeHTML()
winnow_results_set_highlight: ->
if not @is_multiple
do_high = @search_results.down(".result-selected.active-result")
if not do_high?
do_high = @search_results.down(".active-result")
this.result_do_highlight do_high if do_high?
no_results: (terms) ->
@search_results.insert this.get_no_results_html(terms)
@form_field.fire("chosen:no_results", {chosen: this})
no_results_clear: ->
nr = null
nr.remove() while nr = @search_results.down(".no-results")
keydown_arrow: ->
if @results_showing and @result_highlight
next_sib = @result_highlight.next('.active-result')
this.result_do_highlight next_sib if next_sib
else
this.results_show()
keyup_arrow: ->
if not @results_showing and not @is_multiple
this.results_show()
else if @result_highlight
sibs = @result_highlight.previousSiblings()
actives = @search_results.select("li.active-result")
prevs = sibs.intersect(actives)
if prevs.length
this.result_do_highlight prevs.first()
else
this.results_hide() if this.choices_count() > 0
this.result_clear_highlight()
keydown_backstroke: ->
if @pending_backstroke
this.choice_destroy @pending_backstroke.down("a")
this.clear_backstroke()
else
next_available_destroy = @search_container.siblings().last()
if next_available_destroy and next_available_destroy.hasClassName("search-choice") and not next_available_destroy.hasClassName("search-choice-disabled")
@pending_backstroke = next_available_destroy
@pending_backstroke.addClassName("search-choice-focus") if @pending_backstroke
if @single_backstroke_delete
@keydown_backstroke()
else
@pending_backstroke.addClassName("search-choice-focus")
clear_backstroke: ->
@pending_backstroke.removeClassName("search-choice-focus") if @pending_backstroke
@pending_backstroke = null
search_field_scale: ->
return unless @is_multiple
style_block =
position: 'absolute'
left: '-1000px'
top: '-1000px'
display: 'none'
whiteSpace: 'pre'
styles = ['fontSize', 'fontStyle', 'fontWeight', 'fontFamily', 'lineHeight', 'textTransform', 'letterSpacing']
for style in styles
style_block[style] = @search_field.getStyle(style)
div = new Element('div').update(this.escape_html(this.get_search_field_value()))
# CSP without 'unsafe-inline' doesn't allow setting the style attribute directly
div.setStyle(style_block)
document.body.appendChild(div)
width = div.measure('width') + 25
div.remove()
if container_width = @container.getWidth()
width = Math.min(container_width - 10, width)
@search_field.setStyle(width: width + 'px')
trigger_form_field_change: ->
triggerHtmlEvent @form_field, 'input'
triggerHtmlEvent @form_field, 'change'
triggerHtmlEvent = (element, eventType) ->
if element.dispatchEvent # Modern way:
try
evt = new Event(eventType, bubbles: true, cancelable: true)
catch
evt = document.createEvent('HTMLEvents')
evt.initEvent(eventType, true, true);
element.dispatchEvent(evt)
else # Old IE:
element.fireEvent("on#{eventType}", document.createEventObject());
chosen-1.8.7/coffee/lib/ 0000775 0000000 0000000 00000000000 13306546612 0015011 5 ustar 00root root 0000000 0000000 chosen-1.8.7/coffee/lib/abstract-chosen.coffee 0000664 0000000 0000000 00000031552 13306546612 0021250 0 ustar 00root root 0000000 0000000 class AbstractChosen
constructor: (@form_field, @options={}) ->
return unless AbstractChosen.browser_is_supported()
@is_multiple = @form_field.multiple
this.set_default_text()
this.set_default_values()
this.setup()
this.set_up_html()
this.register_observers()
# instantiation done, fire ready
this.on_ready()
set_default_values: ->
@click_test_action = (evt) => this.test_active_click(evt)
@activate_action = (evt) => this.activate_field(evt)
@active_field = false
@mouse_on_container = false
@results_showing = false
@result_highlighted = null
@is_rtl = @options.rtl || /\bchosen-rtl\b/.test(@form_field.className)
@allow_single_deselect = if @options.allow_single_deselect? and @form_field.options[0]? and @form_field.options[0].text is "" then @options.allow_single_deselect else false
@disable_search_threshold = @options.disable_search_threshold || 0
@disable_search = @options.disable_search || false
@enable_split_word_search = if @options.enable_split_word_search? then @options.enable_split_word_search else true
@group_search = if @options.group_search? then @options.group_search else true
@search_contains = @options.search_contains || false
@single_backstroke_delete = if @options.single_backstroke_delete? then @options.single_backstroke_delete else true
@max_selected_options = @options.max_selected_options || Infinity
@inherit_select_classes = @options.inherit_select_classes || false
@display_selected_options = if @options.display_selected_options? then @options.display_selected_options else true
@display_disabled_options = if @options.display_disabled_options? then @options.display_disabled_options else true
@include_group_label_in_selected = @options.include_group_label_in_selected || false
@max_shown_results = @options.max_shown_results || Number.POSITIVE_INFINITY
@case_sensitive_search = @options.case_sensitive_search || false
@hide_results_on_select = if @options.hide_results_on_select? then @options.hide_results_on_select else true
set_default_text: ->
if @form_field.getAttribute("data-placeholder")
@default_text = @form_field.getAttribute("data-placeholder")
else if @is_multiple
@default_text = @options.placeholder_text_multiple || @options.placeholder_text || AbstractChosen.default_multiple_text
else
@default_text = @options.placeholder_text_single || @options.placeholder_text || AbstractChosen.default_single_text
@default_text = this.escape_html(@default_text)
@results_none_found = @form_field.getAttribute("data-no_results_text") || @options.no_results_text || AbstractChosen.default_no_result_text
choice_label: (item) ->
if @include_group_label_in_selected and item.group_label?
"#{this.escape_html(item.group_label)}#{item.html}"
else
item.html
mouse_enter: -> @mouse_on_container = true
mouse_leave: -> @mouse_on_container = false
input_focus: (evt) ->
if @is_multiple
setTimeout (=> this.container_mousedown()), 50 unless @active_field
else
@activate_field() unless @active_field
input_blur: (evt) ->
if not @mouse_on_container
@active_field = false
setTimeout (=> this.blur_test()), 100
label_click_handler: (evt) =>
if @is_multiple
this.container_mousedown(evt)
else
this.activate_field()
results_option_build: (options) ->
content = ''
shown_results = 0
for data in @results_data
data_content = ''
if data.group
data_content = this.result_add_group data
else
data_content = this.result_add_option data
if data_content != ''
shown_results++
content += data_content
# this select logic pins on an awkward flag
# we can make it better
if options?.first
if data.selected and @is_multiple
this.choice_build data
else if data.selected and not @is_multiple
this.single_set_selected_text(this.choice_label(data))
if shown_results >= @max_shown_results
break
content
result_add_option: (option) ->
return '' unless option.search_match
return '' unless this.include_option_in_results(option)
classes = []
classes.push "active-result" if !option.disabled and !(option.selected and @is_multiple)
classes.push "disabled-result" if option.disabled and !(option.selected and @is_multiple)
classes.push "result-selected" if option.selected
classes.push "group-option" if option.group_array_index?
classes.push option.classes if option.classes != ""
option_el = document.createElement("li")
option_el.className = classes.join(" ")
option_el.style.cssText = option.style if option.style
option_el.setAttribute("data-option-array-index", option.array_index)
option_el.innerHTML = option.highlighted_html or option.html
option_el.title = option.title if option.title
this.outerHTML(option_el)
result_add_group: (group) ->
return '' unless group.search_match || group.group_match
return '' unless group.active_options > 0
classes = []
classes.push "group-result"
classes.push group.classes if group.classes
group_el = document.createElement("li")
group_el.className = classes.join(" ")
group_el.innerHTML = group.highlighted_html or this.escape_html(group.label)
group_el.title = group.title if group.title
this.outerHTML(group_el)
results_update_field: ->
this.set_default_text()
this.results_reset_cleanup() if not @is_multiple
this.result_clear_highlight()
this.results_build()
this.winnow_results() if @results_showing
reset_single_select_options: () ->
for result in @results_data
result.selected = false if result.selected
results_toggle: ->
if @results_showing
this.results_hide()
else
this.results_show()
results_search: (evt) ->
if @results_showing
this.winnow_results()
else
this.results_show()
winnow_results: (options) ->
this.no_results_clear()
results = 0
query = this.get_search_text()
escapedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
regex = this.get_search_regex(escapedQuery)
for option in @results_data
option.search_match = false
results_group = null
search_match = null
option.highlighted_html = ''
if this.include_option_in_results(option)
if option.group
option.group_match = false
option.active_options = 0
if option.group_array_index? and @results_data[option.group_array_index]
results_group = @results_data[option.group_array_index]
results += 1 if results_group.active_options is 0 and results_group.search_match
results_group.active_options += 1
text = if option.group then option.label else option.text
unless option.group and not @group_search
search_match = this.search_string_match(text, regex)
option.search_match = search_match?
results += 1 if option.search_match and not option.group
if option.search_match
if query.length
startpos = search_match.index
prefix = text.slice(0, startpos)
fix = text.slice(startpos, startpos + query.length)
suffix = text.slice(startpos + query.length)
option.highlighted_html = "#{this.escape_html(prefix)}#{this.escape_html(fix)}#{this.escape_html(suffix)}"
results_group.group_match = true if results_group?
else if option.group_array_index? and @results_data[option.group_array_index].search_match
option.search_match = true
this.result_clear_highlight()
if results < 1 and query.length
this.update_results_content ""
this.no_results query
else
this.update_results_content this.results_option_build()
this.winnow_results_set_highlight() unless options?.skip_highlight
get_search_regex: (escaped_search_string) ->
regex_string = if @search_contains then escaped_search_string else "(^|\\s|\\b)#{escaped_search_string}[^\\s]*"
regex_string = "^#{regex_string}" unless @enable_split_word_search or @search_contains
regex_flag = if @case_sensitive_search then "" else "i"
new RegExp(regex_string, regex_flag)
search_string_match: (search_string, regex) ->
match = regex.exec(search_string)
match.index += 1 if !@search_contains && match?[1] # make up for lack of lookbehind operator in regex
match
choices_count: ->
return @selected_option_count if @selected_option_count?
@selected_option_count = 0
for option in @form_field.options
@selected_option_count += 1 if option.selected
return @selected_option_count
choices_click: (evt) ->
evt.preventDefault()
this.activate_field()
this.results_show() unless @results_showing or @is_disabled
keydown_checker: (evt) ->
stroke = evt.which ? evt.keyCode
this.search_field_scale()
this.clear_backstroke() if stroke != 8 and @pending_backstroke
switch stroke
when 8 # backspace
@backstroke_length = this.get_search_field_value().length
break
when 9 # tab
this.result_select(evt) if @results_showing and not @is_multiple
@mouse_on_container = false
break
when 13 # enter
evt.preventDefault() if @results_showing
break
when 27 # escape
evt.preventDefault() if @results_showing
break
when 32 # space
evt.preventDefault() if @disable_search
break
when 38 # up arrow
evt.preventDefault()
this.keyup_arrow()
break
when 40 # down arrow
evt.preventDefault()
this.keydown_arrow()
break
keyup_checker: (evt) ->
stroke = evt.which ? evt.keyCode
this.search_field_scale()
switch stroke
when 8 # backspace
if @is_multiple and @backstroke_length < 1 and this.choices_count() > 0
this.keydown_backstroke()
else if not @pending_backstroke
this.result_clear_highlight()
this.results_search()
break
when 13 # enter
evt.preventDefault()
this.result_select(evt) if this.results_showing
break
when 27 # escape
this.results_hide() if @results_showing
break
when 9, 16, 17, 18, 38, 40, 91
# don't do anything on these keys
else
this.results_search()
break
clipboard_event_checker: (evt) ->
return if @is_disabled
setTimeout (=> this.results_search()), 50
container_width: ->
return if @options.width? then @options.width else "#{@form_field.offsetWidth}px"
include_option_in_results: (option) ->
return false if @is_multiple and (not @display_selected_options and option.selected)
return false if not @display_disabled_options and option.disabled
return false if option.empty
return true
search_results_touchstart: (evt) ->
@touch_started = true
this.search_results_mouseover(evt)
search_results_touchmove: (evt) ->
@touch_started = false
this.search_results_mouseout(evt)
search_results_touchend: (evt) ->
this.search_results_mouseup(evt) if @touch_started
outerHTML: (element) ->
return element.outerHTML if element.outerHTML
tmp = document.createElement("div")
tmp.appendChild(element)
tmp.innerHTML
get_single_html: ->
"""
#{@default_text}
Chosen automatically sets the default field text ("Choose a country...") by reading the select element's data-placeholder value. If no data-placeholder value is present, it will default to "Select an Option" or "Select Some Options" depending on whether the select is single or multiple. You can change these elements in the plugin js file as you see fit.
<select data-placeholder="Choose a country..." multiple class="chosen-select">
Note: on single selects, the first element is assumed to be selected by the browser. To take advantage of the default text support, you will need to include a blank option as the first element of your select list.
When a single select box isn't a required field, you can set allow_single_deselect: true and Chosen will add a UI element for option deselection. This will only work if the first option has blank text.
When working with form fields, you often want to perform some behavior after a value has been selected or deselected. Whenever a user selects a field in Chosen, it triggers a "change" event on the original form field. That lets you do something like this:
$("#form_field").chosen().change( … );
Note: Prototype doesn't offer support for triggering standard browser events. Event.simulate is required to trigger the change event when using the Prototype version.
Updating Chosen Dynamically
If you need to update the options in your select field and want Chosen to pick up the changes, you'll need to trigger the "chosen:updated" event on the field. Chosen will re-build itself based on the updated content.
Event.fire($("form_field"), "chosen:updated");
Destroying Chosen
To destroy Chosen and revert back to the native select, call destroy on the Chosen instance:
chosen = new Chosen($("form_field"));
// ...later
chosen.destroy();
Yes! Please report all issues using the GitHub issue tracking tool. Please include the plugin version (jQuery or Prototype), browser and OS. The more information provided, the easier it is to fix a problem.
What browsers are supported?
All modern desktop browsers are supported (Firefox, Chrome, Safari and IE9). Legacy support for IE8 is also enabled. Chosen is disabled on iPhone, iPod Touch, and Android mobile devices (more information).
When set to true on a single select, Chosen adds a UI element which selects the first element (if it is blank).
disable_search
false
When set to true, Chosen will not display the search field (single selects only).
disable_search_threshold
0
Hide the search input on single selects if there are n or fewer options.
enable_split_word_search
true
By default, searching will match on any word within an option tag. Set this option to false if you want to only match on the entire text of an option tag.
inherit_select_classes
false
When set to true, Chosen will grab any classes on the original select field and add them to Chosen’s container div.
max_selected_options
Infinity
Limits how many options the user can select. When the limit is reached, the chosen:maxselected event is triggered.
no_results_text
"No results match"
The text to be displayed when no matching results are found. The current search is shown at the end of the text (e.g.,
No results match "Bad Search").
placeholder_text_multiple
"Select Some Options"
The text to be displayed as a placeholder when no options are selected for a multiple select.
placeholder_text_single
"Select an Option"
The text to be displayed as a placeholder when no options are selected for a single select.
search_contains
false
By default, Chosen’s search matches starting at the beginning of a word. Setting this option to true allows matches starting from anywhere within a word. This is especially useful for options that include a lot of special characters or phrases in ()s and []s.
group_search
true
By default, Chosen will search group labels as well as options, and filter to show all options below matching groups. Set this to false to search only in the options.
single_backstroke_delete
true
By default, pressing delete/backspace on multiple selects will remove a selected choice. When false, pressing delete/backspace will highlight the last choice, and a second press deselects it.
width
Original select width.
The width of the Chosen select box. By default, Chosen attempts to match the width of the select box you are replacing. If your select is hidden when Chosen is instantiated, you must specify a width or the select will show up with a width of 0.
display_disabled_options
true
By default, Chosen includes disabled options in search results with a special styling. Setting this option to false will hide disabled results and exclude them from searches.
display_selected_options
true
By default, Chosen includes selected options in search results with a special styling. Setting this option to false will hide selected results and exclude them from searches.
Note: this is for multiple selects only. In single selects, the selected result will always be displayed.
include_group_label_in_selected
false
By default, Chosen only shows the text of a selected option. Setting this option to true will show the text and group (if any) of the selected option.
max_shown_results
Infinity
Only show the first (n) matching options in the results. This can be used to increase performance for selects with very many options.
case_sensitive_search
false
By default, Chosen's search is case-insensitive. Setting this option to true makes the search case-sensitive.
hide_results_on_select
true
By default, Chosen's results are hidden after a option is selected. Setting this option to false will keep the results open after selection. This only applies to multiple selects.
rtl
false
Chosen supports right-to-left text in select boxes. Set this option to true to support right-to-left text options.
Note:the chosen-rtl class on the select has precedence over this option. However, the classname approach is deprecated and will be removed in future versions of Chosen.
The text to be displayed as a placeholder when no options are selected for a select. Defaults to "Select an Option" for single selects or "Select Some Options" for multiple selects.
Note:This attribute overrides anything set in the placeholder_text_multiple or placeholder_text_single options.
multiple
The attribute multiple on your select box dictates whether Chosen will render a multiple or single select.
selected, disabled
Chosen automatically highlights selected options and disables disabled options.
Chosen triggers the standard DOM event whenever a selection is made (it also sends a selected or deselected parameter that tells you which option was changed).
Note: The selected and deselected parameters are not available for Prototype.
chosen:ready
Triggered after Chosen has been fully instantiated.
chosen:maxselected
Triggered if max_selected_options is set and that total is broken.
chosen:showing_dropdown
Triggered when Chosen’s dropdown is opened.
chosen:hiding_dropdown
Triggered when Chosen’s dropdown is closed.
chosen:no_results
Triggered when a search returns no matching results.
Note: all custom Chosen events (those that begin with chosen:) also include the chosen object as a parameter.
You can trigger several events on the original select field to invoke a behavior in Chosen.
Example:
// tell Chosen that a select has changed
$('.my_select_box').trigger('chosen:updated');
Event
Description
chosen:updated
This event should be triggered whenever Chosen’s underlying select element changes (such as a change in selected options).
chosen:activate
This is the equivalant of focusing a standard HTML select field. When activated, Chosen will capture keypress events as if you had clicked the field directly.
chosen:open
This event activates Chosen and also displays the search results.
chosen:close
This event deactivates Chosen and hides the search results.
").html(tmpl)
select = div.find("select")
expect(select.length).toBe(1)
select.chosen()
# very simple check that the necessary elements have been created
["container", "container-single", "single", "default"].forEach (clazz)->
el = div.find(".chosen-#{clazz}")
expect(el.length).toBe(1)
# test a few interactions
expect(select.val()).toBe ""
container = div.find(".chosen-container")
container.trigger("mousedown") # open the drop
expect(container.hasClass("chosen-container-active")).toBe true
#select an item
container.find(".active-result").last().trigger("mouseup")
expect(select.val()).toBe "Afghanistan"
describe "data-placeholder", ->
it "should render", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
expect(select.length).toBe(1)
select.chosen()
placeholder = div.find(".chosen-single > span")
expect(placeholder.text()).toBe("Choose a Country...")
it "should render with special characters", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
expect(select.length).toBe(1)
select.chosen()
placeholder = div.find(".chosen-single > span")
expect(placeholder.text()).toBe("")
describe "disabled fieldset", ->
it "should render as disabled", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
select.chosen
include_group_label_in_selected: true
# open the drop
container = div.find(".chosen-container")
container.trigger("mousedown")
xss_option = container.find(".active-result").last()
expect(xss_option.html()).toBe "an xss option"
# trigger the selection of the xss option
xss_option.trigger("mouseup")
# make sure the script tags are escaped correctly
label_html = container.find("a.chosen-single").html()
expect(label_html).toContain('</script><script>console.log(1)</script>')
chosen-1.8.7/spec/jquery/events.spec.coffee 0000664 0000000 0000000 00000002160 13306546612 0020672 0 ustar 00root root 0000000 0000000 describe "Events", ->
it "chosen should fire the right events", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
expect(select.length).toBe(1)
select.chosen()
# very simple check that the necessary elements have been created
["container", "container-single", "single", "default"].forEach (clazz)->
el = div.find(".chosen-#{clazz}")
expect(el.length).toBe(1)
# test a few interactions
event_sequence = []
div.on 'input change', (evt) -> event_sequence.push evt.type
container = div.find(".chosen-container")
container.trigger("mousedown") # open the drop
expect(container.hasClass("chosen-container-active")).toBe true
#select an item
container.find(".active-result").last().trigger("mouseup")
expect(event_sequence).toEqual ['input', 'change']
chosen-1.8.7/spec/jquery/max_shown_results.spec.coffee 0000664 0000000 0000000 00000004507 13306546612 0023161 0 ustar 00root root 0000000 0000000 describe "search", ->
it "should display only matching items when entering a search term", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
select.chosen()
container = div.find(".chosen-container")
container.trigger("mousedown") # open the drop
# Expect all results to be shown
results = div.find(".active-result")
expect(results.length).toBe(3)
# Enter some text in the search field.
search_field = div.find(".chosen-search input").first()
search_field.val("Afgh")
search_field.trigger('keyup')
# Expect to only have one result: 'Afghanistan'.
results = div.find(".active-result")
expect(results.length).toBe(1)
expect(results.first().text()).toBe "Afghanistan"
it "should only show max_shown_results items in results", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
select.chosen({max_shown_results: 1 })
container = div.find(".chosen-container")
container.trigger("mousedown") # open the drop
results = div.find(".active-result")
expect(results.length).toBe(1)
# Enter some text in the search field.
search_field = div.find(".chosen-search input").first()
search_field.val("United")
search_field.trigger("keyup")
# Showing only one result: the one that occurs first.
results = div.find(".active-result")
expect(results.length).toBe(1)
expect(results.first().text()).toBe "United States"
# Showing still only one result, but not the first one.
search_field.val("United Ki")
search_field.trigger("keyup")
results = div.find(".active-result")
expect(results.length).toBe(1)
expect(results.first().text()).toBe "United Kingdom"
chosen-1.8.7/spec/jquery/searching.spec.coffee 0000664 0000000 0000000 00000032413 13306546612 0021335 0 ustar 00root root 0000000 0000000 describe "Searching", ->
it "should not match the actual text of HTML entities", ->
tmpl = "
"
div = $("
").html(tmpl)
select = div.find("select")
select.chosen({search_contains: true})
container = div.find(".chosen-container")
container.trigger("mousedown") # open the drop
# Both options should be active
results = div.find(".active-result")
expect(results.length).toBe(2)
# Search for the html entity by name
search_field = div.find(".chosen-search input").first()
search_field.val("mp")
search_field.trigger("keyup")
results = div.find(".active-result")
expect(results.length).toBe(0)
it "renders options correctly when they contain characters that require HTML encoding", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".active-result").length).toBe(1)
expect(div.find(".active-result").first().html()).toBe("A & B")
search_field = div.find(".chosen-search-input").first()
search_field.val("A")
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(1)
expect(div.find(".active-result").first().html()).toBe("A & B")
it "renders optgroups correctly when they contain html encoded tags", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".group-result").length).toBe(1)
expect(div.find(".group-result").first().html()).toBe("A <b>hi</b> B")
it "renders optgroups correctly when they contain characters that require HTML encoding when searching", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".group-result").length).toBe(1)
expect(div.find(".group-result").first().html()).toBe("A & B")
search_field = div.find(".chosen-search-input").first()
search_field.val("A")
search_field.trigger("keyup")
expect(div.find(".group-result").length).toBe(1)
expect(div.find(".group-result").first().html()).toBe("A & B")
it "renders no results message correctly when it contains characters that require HTML encoding", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
search_field = div.find(".chosen-search-input").first()
search_field.val("&")
search_field.trigger("keyup")
expect(div.find(".no-results").length).toBe(1)
expect(div.find(".no-results").first().html().trim()).toBe("No results match &")
search_field.val("&")
search_field.trigger("keyup")
expect(div.find(".no-results").length).toBe(1)
expect(div.find(".no-results").first().html().trim()).toBe("No results match &")
it "matches in non-ascii languages like Chinese when selecting a single item", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".active-result").length).toBe(12)
search_field = div.find(".chosen-search-input").first()
search_field.val("一")
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(1)
expect(div.find(".active-result")[0].innerHTML).toBe("一")
it "matches in non-ascii languages like Chinese when selecting a single item with search_contains", ->
div = $("
").html("""
""")
div.find("select").chosen({search_contains: true})
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".active-result").length).toBe(12)
search_field = div.find(".chosen-search-input").first()
search_field.val("一")
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(2)
expect(div.find(".active-result")[0].innerHTML).toBe("一")
expect(div.find(".active-result")[1].innerHTML).toBe("十一")
it "matches in non-ascii languages like Chinese when selecting multiple items", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".active-result").length).toBe(12)
search_field = div.find(".chosen-search-input")
search_field.val("一")
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(1)
expect(div.find(".active-result")[0].innerHTML).toBe("一")
it "matches in non-ascii languages like Chinese when selecting multiple items with search_contains", ->
div = $("
").html("""
""")
div.find("select").chosen({search_contains: true})
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".active-result").length).toBe(12)
search_field = div.find(".chosen-search-input")
search_field.val("一")
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(2)
expect(div.find(".active-result")[0].innerHTML).toBe("一")
expect(div.find(".active-result")[1].innerHTML).toBe("十一")
it "highlights results correctly when multiple words are present", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
expect(div.find(".active-result").length).toBe(1)
search_field = div.find(".chosen-search-input")
search_field.val("h")
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(1)
expect(div.find(".active-result")[0].innerHTML).toBe("oh hello")
describe "respects word boundaries when not using search_contains", ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
search_field = div.find(".chosen-search-input")
div.find("option").each () ->
boundary_thing = this.value.slice(1)
it "correctly finds words that start after a(n) #{boundary_thing}", ->
search_field.val(boundary_thing)
search_field.trigger("keyup")
expect(div.find(".active-result").length).toBe(1)
expect(div.find(".active-result")[0].innerText.slice(1)).toBe(boundary_thing)
it "does not clear search text or highlight first suggestion when holding ctrl/cmd and selecting multiple items", () ->
div = $("
").html("""
""")
div.find("select").chosen()
div.find(".chosen-container").trigger("mousedown") # open the drop
search_field = div.find(".chosen-search-input")
search_field.val("s").trigger("keyup")
results = div.find(".chosen-results")
first_result = results.find("li:nth-of-type(1)")
second_result = results.find("li:nth-of-type(2)")
expect(first_result.prop("class")).toContain("highlighted")
mouseup_with_meta = $.Event("mouseup")
mouseup_with_meta.metaKey = true
second_result.mouseover().trigger(mouseup_with_meta)
expect(div.find(".search-choice").length).toBe(1)
expect(search_field.val()).toBe("s")
expect(first_result.prop("class")).not.toContain("highlighted")
third_result = results.find("li:nth-of-type(3)")
mouseup_with_ctrl = $.Event("mouseup")
mouseup_with_ctrl.ctrlKey = true
third_result.mouseover().trigger(mouseup_with_ctrl)
expect(div.find(".search-choice").length).toBe(2)
expect(search_field.val()).toBe("s")
expect(first_result.prop("class")).not.toContain("highlighted")
it "clears search text and highlights first suggestion without ctrl/cmd and selecting multiple items without hide_results_on_select", () ->
div = $("
").html("""
""")
div.find("select").chosen(hide_results_on_select: false)
div.find(".chosen-container").trigger("mousedown") # open the drop
search_field = div.find(".chosen-search-input")
search_field.val("s").trigger("keyup")
results = div.find(".chosen-results")
first_result = results.find("li:nth-of-type(1)")
second_result = results.find("li:nth-of-type(2)")
expect(first_result.prop("class")).toContain("highlighted")
second_result.mouseover().mouseup()
expect(div.find(".search-choice").length).toBe(1)
expect(search_field.val()).toBe("")
expect(results.find("li:nth-of-type(1)").prop("class")).toContain("highlighted")
chosen-1.8.7/spec/proto/ 0000775 0000000 0000000 00000000000 13306546612 0015111 5 ustar 00root root 0000000 0000000 chosen-1.8.7/spec/proto/basic.spec.coffee 0000664 0000000 0000000 00000006414 13306546612 0020301 0 ustar 00root root 0000000 0000000 describe "Basic setup", ->
it "should add expose a Chosen global", ->
expect(Chosen).toBeDefined()
it "should create very basic chosen", ->
tmpl = "
"
div = new Element("div")
document.body.insert(div)
div.update(tmpl)
select = div.down("select")
expect(select).toBeDefined()
new Chosen(select)
# very simple check that the necessary elements have been created
["container", "container-single", "single", "default"].forEach (clazz)->
el = div.down(".chosen-#{clazz}")
expect(el).toBeDefined()
# test a few interactions
expect($F(select)).toBe ""
container = div.down(".chosen-container")
simulant.fire(container, "mousedown") # open the drop
expect(container.hasClassName("chosen-container-active")).toBe true
#select an item
result = container.select(".active-result").last()
simulant.fire(result, "mouseup")
expect($F(select)).toBe "Afghanistan"
div.remove()
describe "data-placeholder", ->
it "should render", ->
tmpl = "
"
div = new Element("div")
document.body.insert(div)
div.update(tmpl)
select = div.down("select")
expect(select).toBeDefined()
new Chosen(select)
placeholder = div.down(".chosen-single > span")
expect(placeholder.innerText).toBe("Choose a Country...")
it "should render with special characters", ->
tmpl = "
"
div = new Element("div")
document.body.insert(div)
div.update(tmpl)
select = div.down("select")
expect(select).toBeDefined()
new Chosen(select)
placeholder = div.down(".chosen-single > span")
expect(placeholder.innerText).toBe("")
describe "disabled fieldset", ->
it "should render as disabled", ->
tmpl = "
"
div = new Element("div")
document.body.insert(div)
div.update(tmpl)
select = div.down("select")
expect(select).toBeDefined()
new Chosen(select)
container = div.down(".chosen-container")
expect(container.hasClassName("chosen-disabled")).toBe true
chosen-1.8.7/spec/proto/bugfixes.spec.coffee 0000664 0000000 0000000 00000002106 13306546612 0021026 0 ustar 00root root 0000000 0000000 describe "Bugfixes", ->
it "https://github.com/harvesthq/chosen/issues/2996 - XSS Vulnerability with `include_group_label_in_selected: true`", ->
tmpl = "
"
div = new Element("div")
document.body.insert(div)
div.innerHTML = tmpl
select = div.down("select")
new Chosen select,
include_group_label_in_selected: true
# open the drop
container = div.down(".chosen-container")
simulant.fire(container, "mousedown") # open the drop
xss_option = container.select(".active-result").last()
expect(xss_option.innerHTML).toBe "an xss option"
# trigger the selection of the xss option
simulant.fire(xss_option, "mouseup")
# make sure the script tags are escaped correctly
label_html = container.down("a.chosen-single").innerHTML
expect(label_html).toContain('</script><script>console.log(1)</script>')
chosen-1.8.7/spec/proto/events.spec.coffee 0000664 0000000 0000000 00000002062 13306546612 0020517 0 ustar 00root root 0000000 0000000 describe "Events", ->
it "chosen should fire the right events", ->
tmpl = "
"
div = new Element("div")
document.body.insert(div)
div.update(tmpl)
select = div.down("select")
expect(select).toBeDefined()
new Chosen(select)
event_sequence = []
document.addEventListener 'input', -> event_sequence.push 'input'
document.addEventListener 'change', -> event_sequence.push 'change'
container = div.down(".chosen-container")
simulant.fire(container, "mousedown") # open the drop
expect(container.hasClassName("chosen-container-active")).toBe true
#select an item
result = container.select(".active-result").last()
simulant.fire(result, "mouseup")
expect(event_sequence).toEqual ['input', 'change']
div.remove()
chosen-1.8.7/spec/proto/max_shown_results.spec.coffee 0000664 0000000 0000000 00000004640 13306546612 0023003 0 ustar 00root root 0000000 0000000 describe 'search', ->
it 'should display only matching items when entering a search term', ->
tmpl = '''
'''
div = new Element('div')
document.body.insert(div)
div.update(tmpl)
select = div.down('select')
new Chosen(select)
container = div.down('.chosen-container')
simulant.fire(container, 'mousedown') # open the drop
# Expect all results to be shown
results = div.select('.active-result')
expect(results.length).toBe(3)
# Enter some text in the search field.
search_field = div.down('.chosen-search input')
search_field.value = 'Afgh'
simulant.fire(search_field, 'keyup')
# Expect to only have one result: 'Afghanistan'.
results = div.select('.active-result')
expect(results.length).toBe(1)
expect(results[0].innerText).toBe 'Afghanistan'
it 'should only show max_shown_results items in results', ->
tmpl = '''
'''
div = new Element('div')
document.body.insert(div)
div.update(tmpl)
select = div.down('select')
new Chosen(select, { max_shown_results: 1 })
container = div.down('.chosen-container')
simulant.fire(container, 'mousedown') # open the drop
results = div.select('.active-result')
expect(results.length).toBe(1)
# Enter some text in the search field.
search_field = div.down('.chosen-search input')
search_field.value = 'United'
simulant.fire(search_field, 'keyup')
# Showing only one result: the one that occurs first.
results = div.select('.active-result')
expect(results.length).toBe(1)
expect(results[0].innerText).toBe 'United States'
# Showing still only one result, but not the first one.
search_field.value = 'United Ki'
simulant.fire(search_field, 'keyup')
results = div.select('.active-result')
expect(results.length).toBe(1)
expect(results[0].innerText).toBe 'United Kingdom'
chosen-1.8.7/spec/proto/searching.spec.coffee 0000664 0000000 0000000 00000024742 13306546612 0021167 0 ustar 00root root 0000000 0000000 describe "Searching", ->
it "should not match the actual text of HTML entities", ->
tmpl = "
"
div = new Element('div')
document.body.insert(div)
div.update(tmpl)
select = div.down('select')
new Chosen(select, {search_contains: true})
container = div.down('.chosen-container')
simulant.fire(container, 'mousedown') # open the drop
# Both options should be active
results = div.select('.active-result')
expect(results.length).toBe(2)
# Search for the html entity by name
search_field = div.down(".chosen-search input")
search_field.value = "mp"
simulant.fire(search_field, 'keyup')
results = div.select(".active-result")
expect(results.length).toBe(0)
it "renders options correctly when they contain characters that require HTML encoding", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".active-result").length).toBe(1)
expect(div.down(".active-result").innerHTML).toBe("A & B")
search_field = div.down(".chosen-search-input")
search_field.value = "A"
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(1)
expect(div.down(".active-result").innerHTML).toBe("A & B")
it "renders optgroups correctly when they contain characters that require HTML encoding", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".group-result").length).toBe(1)
expect(div.down(".group-result").innerHTML).toBe("A <b>hi</b> B")
it "renders optgroups correctly when they contain characters that require HTML encoding when searching", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".group-result").length).toBe(1)
expect(div.down(".group-result").innerHTML).toBe("A & B")
search_field = div.down(".chosen-search-input")
search_field.value = "A"
simulant.fire(search_field, "keyup")
expect(div.select(".group-result").length).toBe(1)
expect(div.down(".group-result").innerHTML).toBe("A & B")
it "renders no results message correctly when it contains characters that require HTML encoding", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
search_field = div.down(".chosen-search-input")
search_field.value = "&"
simulant.fire(search_field, "keyup")
expect(div.select(".no-results").length).toBe(1)
expect(div.down(".no-results").innerHTML.trim()).toBe("No results match &")
search_field.value = "&"
simulant.fire(search_field, "keyup")
expect(div.select(".no-results").length).toBe(1)
expect(div.down(".no-results").innerHTML.trim()).toBe("No results match &")
it "matches in non-ascii languages like Chinese when selecting a single item", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".active-result").length).toBe(12)
search_field = div.down(".chosen-search-input")
search_field.value = "一"
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(1)
expect(div.select(".active-result")[0].innerHTML).toBe("一")
it "matches in non-ascii languages like Chinese when selecting a single item with search_contains", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"), {search_contains: true})
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".active-result").length).toBe(12)
search_field = div.down(".chosen-search-input")
search_field.value = "一"
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(2)
expect(div.select(".active-result")[0].innerHTML).toBe("一")
expect(div.select(".active-result")[1].innerHTML).toBe("十一")
it "matches in non-ascii languages like Chinese when selecting multiple items", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".active-result").length).toBe(12)
search_field = div.down(".chosen-search-input")
search_field.value = "一"
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(1)
expect(div.select(".active-result")[0].innerHTML).toBe("一")
it "matches in non-ascii languages like Chinese when selecting multiple items with search_contains", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"), {search_contains: true})
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".active-result").length).toBe(12)
search_field = div.down(".chosen-search-input")
search_field.value = "一"
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(2)
expect(div.select(".active-result")[0].innerHTML).toBe("一")
expect(div.select(".active-result")[1].innerHTML).toBe("十一")
it "highlights results correctly when multiple words are present", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
expect(div.select(".active-result").length).toBe(1)
search_field = div.down(".chosen-search-input")
search_field.value = "h"
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(1)
expect(div.select(".active-result")[0].innerHTML).toBe("oh hello")
describe "respects word boundaries when not using search_contains", ->
div = new Element("div")
div.update("""
""")
new Chosen(div.down("select"))
simulant.fire(div.down(".chosen-container"), "mousedown") # open the drop
search_field = div.down(".chosen-search-input")
div.select("option").forEach (option) ->
boundary_thing = option.value.slice(1)
it "correctly finds words that start after a(n) #{boundary_thing}", ->
search_field.value = boundary_thing
simulant.fire(search_field, "keyup")
expect(div.select(".active-result").length).toBe(1)
expect(div.select(".active-result")[0].innerText.slice(1)).toBe(boundary_thing)
chosen-1.8.7/tasks/ 0000775 0000000 0000000 00000000000 13306546612 0014141 5 ustar 00root root 0000000 0000000 chosen-1.8.7/tasks/package.coffee 0000664 0000000 0000000 00000003540 13306546612 0016707 0 ustar 00root root 0000000 0000000 ###
This file contains tasks only necessary for packaging and publishing Chosen
###
module.exports = (grunt) ->
grunt.config 'dom_munger',
latest_version:
src: ['public/index.html', 'public/index.proto.html', 'public/options.html']
options:
callback: ($) ->
$('#latest-version').text(grunt.config.get('version_tag'))
grunt.config 'zip',
chosen:
cwd: 'public/'
src: ['public/**/*']
dest: 'chosen_<%= version_tag %>.zip'
grunt.config 'gh-pages',
options:
base: 'public',
message: 'Updated to new Chosen version <%= pkg.version %>'
src: ['**']
grunt.registerTask 'package-npm', 'Generate npm manifest', () ->
pkg = grunt.config.get('pkg')
extra = pkg._extra
json =
name: "#{pkg.name}-js"
version: pkg.version
description: pkg.description
keywords: pkg.keywords
homepage: pkg.homepage
bugs: pkg.bugs
license: pkg.license
contributors: pkg.contributors
dependencies: pkg.dependencies
files: extra.files
main: extra.main[0]
repository: pkg.repository
grunt.file.write('public/package.json', JSON.stringify(json, null, 2) + "\n")
grunt.registerTask 'package-bower', 'Generate bower manifest', () ->
pkg = grunt.config.get('pkg')
extra = pkg._extra
json =
name: pkg.name
description: pkg.description
keywords: pkg.keywords
homepage: pkg.homepage
license: extra.license.url
authors: pkg.contributors
dependencies: pkg.dependencies
main: extra.main
ignore: []
repository: pkg.repository
grunt.file.write('public/bower.json', JSON.stringify(json, null, 2) + "\n")
grunt.registerTask 'prep-release', ['build', 'dom_munger:latest_version', 'zip:chosen', 'package-npm', 'package-bower']
grunt.registerTask 'publish-release', ['gh-pages']