├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .nvmrc ├── .travis.yml ├── .watchmanconfig ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep └── mixins │ ├── item-cursor.js │ └── select-picker.js ├── app ├── .gitkeep ├── components │ ├── keyboard-select-picker.js │ ├── list-picker.js │ └── select-picker.js └── templates │ └── components │ ├── -native-select.hbs │ ├── list-picker.hbs │ └── select-picker.hbs ├── blueprints └── ember-cli-select-picker │ └── index.js ├── bower.json ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── common │ └── select-picker.js ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── highlight-code.js │ │ │ └── modal-dialog.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── application.js │ │ │ ├── index.js │ │ │ ├── keyboard.js │ │ │ ├── options.js │ │ │ ├── searching.js │ │ │ ├── test-list-picker.js │ │ │ └── test-select-picker.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── mixins │ │ │ └── choices.js │ │ ├── models │ │ │ └── .gitkeep │ │ ├── modules │ │ │ └── choices-props.js │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ ├── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ │ ├── .gitkeep │ │ │ │ ├── highlight-code.hbs │ │ │ │ └── modal-dialog.hbs │ │ │ ├── i18n.hbs │ │ │ ├── index.hbs │ │ │ ├── install.hbs │ │ │ ├── keyboard.hbs │ │ │ ├── options.hbs │ │ │ ├── searching.hbs │ │ │ ├── test-list-picker.hbs │ │ │ └── test-select-picker.hbs │ │ └── views │ │ │ └── .gitkeep │ ├── config │ │ └── environment.js │ └── public │ │ ├── crossdomain.xml │ │ ├── robots.txt │ │ └── screen-shot.png ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html ├── integration │ ├── list-picker-test.js │ └── select-picker-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ └── components │ ├── list-picker-test.js │ └── select-picker-test.js └── vendor ├── .gitkeep └── select-picker.css /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | bower.json.ember-try 7 | 8 | # dependencies 9 | /node_modules 10 | /bower_components 11 | 12 | # misc 13 | /.sass-cache 14 | /connect.lock 15 | /coverage/* 16 | /libpeerconnection.log 17 | npm-debug.log 18 | testem.log 19 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "jquery": true, 9 | "boss": true, 10 | "curly": true, 11 | "debug": false, 12 | "devel": true, 13 | "eqeqeq": true, 14 | "evil": true, 15 | "forin": false, 16 | "immed": false, 17 | "laxbreak": false, 18 | "newcap": true, 19 | "noarg": true, 20 | "noempty": false, 21 | "nonew": false, 22 | "nomen": false, 23 | "onevar": false, 24 | "plusplus": false, 25 | "regexp": false, 26 | "undef": true, 27 | "sub": true, 28 | "strict": false, 29 | "white": false, 30 | "eqnull": true, 31 | "esnext": true, 32 | "unused": true 33 | } 34 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | dist/ 5 | 6 | .bowerrc 7 | .editorconfig 8 | .ember-cli 9 | .travis.yml 10 | .npmignore 11 | **/.gitkeep 12 | bower.json 13 | ember-cli-build.js 14 | Brocfile.js 15 | testem.json 16 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.12 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | env: 13 | - EMBER_TRY_SCENARIO=default 14 | - EMBER_TRY_SCENARIO=ember-release 15 | # - EMBER_TRY_SCENARIO=ember-beta 16 | # - EMBER_TRY_SCENARIO=ember-canary 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | - env: EMBER_TRY_SCENARIO=ember-canary 22 | 23 | before_install: 24 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 25 | - "npm config set spin false" 26 | - "npm install -g npm@^2" 27 | 28 | install: 29 | - npm install -g bower 30 | - npm install 31 | - bower install 32 | 33 | script: 34 | - ember try $EMBER_TRY_SCENARIO test 35 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # ember-cli-select-picker is an OPEN Open Source Project 2 | 3 | ## What? 4 | 5 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 6 | 7 | ## Rules 8 | 9 | There are a few basic ground-rules for contributors: 10 | 11 | 1. **No `--force` pushes** or modifying the Git history in any way. 12 | 1. **Non-master branches** ought to be used for ongoing work. 13 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 14 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 15 | 1. Contributors should attempt to adhere to the prevailing code-style. 16 | 17 | ## Releases 18 | 19 | Declaring formal releases remains the prerogative of the project maintainer. 20 | 21 | ## Changes to this arrangement 22 | 23 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember-cli-select-picker [![npm version](http://img.shields.io/npm/v/ember-cli-select-picker.svg)](https://npmjs.org/package/ember-cli-select-picker) [![build status](http://img.shields.io/travis/sukima/ember-cli-select-picker.svg)](https://travis-ci.org/sukima/ember-cli-select-picker) 2 | 3 | 4 | 5 | This is a reinvention of the select view. It is designed to offer a [Bootstrap][1] style and function. It is highly inspired from the jQuery plugin [bootstrap-select][2] but designed for Ember-CLI apps specifically. It supports single and multiple select. It adds select all/none, and search filtering for multiple selections. 6 | 7 | See the [demo][] for examples, usage, and code snippits. 8 | 9 | [1]: http://getbootstrap.com/ 10 | [2]: http://silviomoreto.github.io/bootstrap-select/ 11 | 12 | ## Dependencies 13 | 14 | Version 2.0 is designed for Ember CLI 1.13 or greater. If you want to use an older unsupported version of ember take a look at the last 1.x release. 15 | 16 | ## Installation 17 | 18 | * `ember install ember-cli-select-picker` 19 | 20 | ## Using 21 | 22 | In your templates simply replace the usual `{{select …}}` with `{{select-picker …}}`. This addon is implemented as a component since the core Ember team is deprecating views. It is down-grades (read: backwards compatible) to mobile by keeping a select view in sync under the hood. 23 | 24 | More options and examples are available on the [demo][] site. 25 | 26 | ```handlebars 27 | {{select-picker value=myModel.myAttr 28 | content=mySelectContents 29 | optionGroupPath="group" 30 | optionLabelPath="content.label" 31 | optionValuePath="content.value"}} 32 | ``` 33 | 34 | ## Running Tests 35 | 36 | * `npm test` - Test with Ember release, beta, and canary 37 | * `ember test` 38 | * `ember test --server` 39 | 40 | ## Building 41 | 42 | * `ember build` 43 | 44 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/). 45 | 46 | [demo]: https://sukima.github.io/ember-cli-select-picker/ 47 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/addon/.gitkeep -------------------------------------------------------------------------------- /addon/mixins/item-cursor.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Mixin.create({ 4 | activeCursor: null, 5 | previousActiveIndex: 0, 6 | 7 | updateActiveItem: Ember.observer( 8 | 'activeCursor', 'contentList.length', 9 | function() { 10 | const previousActiveIndex = this.get('previousActiveIndex'); 11 | const activeIndex = this.get('activeIndex'); 12 | if (Ember.typeOf(activeIndex) !== 'number') { return; } 13 | this.set(`contentList.${previousActiveIndex}.active`, false); 14 | this.set(`contentList.${activeIndex}.active`, true); 15 | this.set('previousActiveIndex', activeIndex); 16 | } 17 | ), 18 | 19 | activeIndex: Ember.computed( 20 | 'activeCursor', 'contentList.length', 21 | function() { 22 | var cursor = this.get('activeCursor'); 23 | if (Ember.isNone(cursor)) { 24 | return null; 25 | } 26 | var len = this.get('contentList.length'); 27 | return (cursor % len + len) % len; 28 | } 29 | ), 30 | 31 | activeItem: Ember.computed( 32 | 'activeIndex', 'contentList.[]', 33 | function() { 34 | return this.get('contentList').objectAt(this.get('activeIndex')); 35 | } 36 | ), 37 | 38 | actions: { 39 | activeNext() { 40 | if (Ember.isNone(this.get('activeCursor'))) { 41 | this.set('activeCursor', 0); 42 | } else { 43 | this.incrementProperty('activeCursor'); 44 | } 45 | }, 46 | 47 | activePrev() { 48 | if (Ember.isNone(this.get('activeCursor'))) { 49 | this.set('activeCursor', -1); 50 | } else { 51 | this.decrementProperty('activeCursor'); 52 | } 53 | }, 54 | 55 | selectActiveItem() { 56 | var item = this.get('activeItem'); 57 | if (Ember.isPresent(item)) { 58 | this.send('selectItem', item); 59 | } 60 | }, 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /addon/mixins/select-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | // Ember Addons need to be coded as if Ember.EXTEND_PROTOTYPES = false 4 | // Because of this we need to make our own proxy functions to apply as one offs 5 | // to native arrays. 6 | const emberArrayFunc = function(method) { 7 | return function(ctx, ...args) { 8 | let enumeral = Ember.A(ctx); 9 | Ember.assert( 10 | `Ember.Enumerable has no method ${method}`, 11 | Ember.typeOf(enumeral[method]) === 'function' 12 | ); 13 | let result = enumeral[method](...args); 14 | if (Ember.typeOf(result) === 'array') { 15 | return Ember.A(result); 16 | } else { 17 | return result; 18 | } 19 | }; 20 | }; 21 | const _contains = emberArrayFunc( 22 | // Backwards compatability for Ember < 2.x 23 | Ember.Enumerable.keys().indexOf('includes') !== -1 ? 24 | 'includes' : 25 | 'contains' 26 | ); 27 | const _mapBy = emberArrayFunc('mapBy'); 28 | const _filterBy = emberArrayFunc('filterBy'); 29 | const _findBy = emberArrayFunc('findBy'); 30 | const _uniq = emberArrayFunc('uniq'); 31 | const _compact = emberArrayFunc('compact'); 32 | 33 | const selectOneOf = function(someSelected, 34 | allSelected, 35 | noneSelected) { 36 | return Ember.computed( 37 | 'hasSelectedItems', 'allItemsSelected', 38 | function() { 39 | if (this.get('allItemsSelected')) { 40 | return allSelected.call(this); 41 | } else if (this.get('hasSelectedItems')) { 42 | return someSelected.call(this); 43 | } else { 44 | return noneSelected.call(this); 45 | } 46 | } 47 | ); 48 | }; 49 | 50 | const selectOneOfValue = function(someSelectedValue, 51 | allSelectedValue, 52 | noneSelectedValue) { 53 | return selectOneOf( 54 | function() { return someSelectedValue; }, 55 | function() { return allSelectedValue; }, 56 | function() { return noneSelectedValue; } 57 | ); 58 | }; 59 | 60 | const selectOneOfProperty = function(someSelectedKey, 61 | allSelectedKey, 62 | noneSelectedKey) { 63 | return selectOneOf( 64 | function() { return this.get(someSelectedKey); }, 65 | function() { return this.get(allSelectedKey); }, 66 | function() { return this.get(noneSelectedKey); } 67 | ); 68 | }; 69 | 70 | const isAdvancedSearch = function(liveSearch) { 71 | return ( 72 | Ember.typeOf(liveSearch) === 'string' && 73 | liveSearch.toLowerCase() === 'advanced' 74 | ); 75 | }; 76 | 77 | export default Ember.Mixin.create({ 78 | liveSearch: false, 79 | showDropdown: false, 80 | promptMessage: 'Please select an option', 81 | prompt: Ember.computed.bool('promptMessage'), 82 | 83 | showNativePrompt: Ember.computed( 84 | 'multiple', 'prompt', 85 | function() { 86 | return !this.get('multiple') && Ember.isPresent(this.get('prompt')); 87 | } 88 | ), 89 | 90 | menuButtonId: Ember.computed( 91 | 'elementId', 92 | function() { 93 | return this.get('elementId') + '-dropdown-menu'; 94 | } 95 | ), 96 | 97 | selectionAsArray: function() { 98 | return Ember.makeArray(this.get('selection')); 99 | }, 100 | 101 | contentList: Ember.computed( 102 | 'selection.[]', 'content.[]', 'optionGroupPath', 103 | 'optionLabelPath', 'optionValuePath', 'searchFilter', 104 | function() { 105 | // Ember.Select does not include the content prefix for optionGroupPath 106 | var groupPath = this.get('optionGroupPath'); 107 | // Ember.Select expects optionLabelPath and optionValuePath to have a 108 | // `content.` prefix 109 | var labelPath = this.contentPathName('optionLabelPath'); 110 | var valuePath = this.contentPathName('optionValuePath'); 111 | // selection is either an object or an array of object depending on the 112 | // value of the multiple property. Ember.Select maintains the value 113 | // property. 114 | var selection = this.selectionAsArray().map(function(item) { 115 | return valuePath ? Ember.get(item, valuePath) : item; 116 | }); 117 | var searchMatcher = this.makeSearchMatcher(); 118 | 119 | var result = _compact(Ember.makeArray(this.get('content')) 120 | .map(function(item, index) { 121 | const label = labelPath ? Ember.get(item, labelPath) : item; 122 | const value = valuePath ? Ember.get(item, valuePath) : item; 123 | const group = groupPath ? Ember.get(item, groupPath) : null; 124 | if (searchMatcher(group) || searchMatcher(label)) { 125 | return Ember.Object.create({ 126 | item: item, 127 | itemId: index, 128 | group: group, 129 | label: label, 130 | value: value, 131 | selected: _contains(selection, value) 132 | }); 133 | } else { 134 | return null; 135 | } 136 | })); 137 | 138 | if (Ember.isPresent(result)) { 139 | result.set('firstObject.first', true); 140 | } 141 | 142 | return result; 143 | } 144 | ), 145 | 146 | nestedGroupContentList: Ember.computed( 147 | 'contentList.[].group', 148 | function() { 149 | const contentList = this.get('contentList'); 150 | const groups = _uniq(_mapBy(contentList, 'group')); 151 | const results = Ember.A(); 152 | groups.forEach(function(group) { 153 | results.pushObject(Ember.Object.create({ 154 | name: group, 155 | items: _filterBy(contentList, 'group', group) 156 | })); 157 | }); 158 | return results; 159 | } 160 | ), 161 | 162 | contentPathName: function(pathName) { 163 | return this.getWithDefault(pathName, '').substr(8); 164 | }, 165 | 166 | getByContentPath: function(obj, pathName) { 167 | return Ember.get(obj, this.contentPathName(pathName)); 168 | }, 169 | 170 | selectedContentList: Ember.computed.filterBy('contentList', 'selected'), 171 | unselectedContentList: Ember.computed.setDiff('contentList', 'selectedContentList'), 172 | hasSelectedItems: Ember.computed.gt('selection.length', 0), 173 | allItemsSelected: Ember.computed( 174 | 'selection.length', 'content.length', 175 | function() { 176 | return Ember.isEqual(this.get('selection.length'), this.get('content.length')); 177 | } 178 | ), 179 | 180 | glyphiconClass: selectOneOfValue('glyphicon-minus', 'glyphicon-ok', ''), 181 | selectAllNoneLabel: selectOneOfProperty('selectNoneLabel', 'selectNoneLabel', 'selectAllLabel'), 182 | 183 | makeSearchMatcher: function () { 184 | var searchFilter = this.get('searchFilter'); 185 | // item can be null, string, or SafeString. 186 | // SafeString does not have toLowerCase() so use toString() to 187 | // normalize it. 188 | if (Ember.isEmpty(searchFilter)) { 189 | return function () { 190 | return true; // Show all 191 | }; 192 | } else if (isAdvancedSearch(this.get('liveSearch'))) { 193 | searchFilter = new RegExp(searchFilter.split('').join('.*'), 'i'); 194 | return function (item) { 195 | if (Ember.isNone(item)) { 196 | return false; 197 | } else { 198 | return searchFilter.test(item.toString()); 199 | } 200 | }; 201 | } else { 202 | searchFilter = searchFilter.toLowerCase(); 203 | return function (item) { 204 | if (Ember.isNone(item)) { 205 | return false; 206 | } else { 207 | return item.toString().toLowerCase().indexOf(searchFilter) >= 0; 208 | } 209 | }; 210 | } 211 | }, 212 | 213 | selectionLabels: Ember.computed.mapBy('selectedContentList', 'label'), 214 | 215 | selectionSummary: Ember.computed( 216 | 'selectionLabels.[]', 'nothingSelectedMessage', 'multipleSelectedMessage', 217 | 'summaryMessage', 'summaryMessageKey', 218 | function() { 219 | var selection = this.get('selectionLabels'); 220 | var count = selection.get('length'); 221 | var messageKey = this.get('summaryMessageKey'); 222 | var message = this.get('summaryMessage'); 223 | if (Ember.I18n && Ember.isPresent(messageKey)) { 224 | // TODO: Allow an enablePrompt="false" feature 225 | if (count === 0) { 226 | return this.get('nothingSelectedMessage'); 227 | } 228 | var item = selection.get('firstObject'); 229 | var translation = Ember.I18n.t(messageKey, { 230 | count: count, 231 | item: item, 232 | list: selection.join(', ') 233 | }); 234 | // I18n is returning a string that's been escaped, we don't want the 235 | // string to get escaped again. 236 | return Ember.String.htmlSafe(translation); 237 | } else if (Ember.isPresent(message)) { 238 | return message; 239 | } else { 240 | switch (count) { 241 | case 0: 242 | return this.get('nothingSelectedMessage'); 243 | case 1: 244 | return selection.get('firstObject'); 245 | default: 246 | return Ember.String.fmt( 247 | this.get('multipleSelectedMessage'), 248 | count, 249 | selection.get('firstObject'), 250 | selection.join(', ') 251 | ); 252 | } 253 | } 254 | } 255 | ), 256 | 257 | clearSearchDisabled: Ember.computed.empty('searchFilter'), 258 | 259 | toggleSelection: function(value) { 260 | var selection = Ember.A(this.get('selection')); 261 | if (_contains(selection, value)) { 262 | selection.removeObject(value); 263 | } else { 264 | selection.pushObject(value); 265 | } 266 | this.set('selection', selection); 267 | }, 268 | 269 | selectAnItem: function(selected) { 270 | if (!this.get('disabled')) { 271 | if (this.get('multiple')) { 272 | this.set('keepDropdownOpen', true); 273 | this.toggleSelection(selected.get('item')); 274 | } else { 275 | this.setProperties({ 276 | // TODO: value will be removed in the future 277 | value: selected.get('value'), 278 | selection: selected.get('item') 279 | }); 280 | } 281 | } 282 | }, 283 | 284 | sendChangeAction: function() { 285 | const changeAction = Ember.get(this, 'attrs.action'); 286 | if (changeAction) { 287 | changeAction(this.get('selection')); 288 | } 289 | }, 290 | 291 | actions: { 292 | selectItem(selected) { 293 | if (this.get('disabled')) { return true; } 294 | this.selectAnItem(selected); 295 | this.sendChangeAction(); 296 | return false; 297 | }, 298 | 299 | selectAllNone(listName) { 300 | if (this.get('disabled')) { return true; } 301 | this.get(listName).forEach(Ember.run.bind(this, this.selectAnItem)); 302 | this.sendChangeAction(); 303 | return false; 304 | }, 305 | 306 | selectByValue() { 307 | if (this.get('disabled')) { return true; } 308 | const hasPrompt = Ember.isPresent(this.get('prompt')); 309 | const contentList = this.get('contentList'); 310 | const selectedValues = Ember.makeArray(this.$('select').val()); 311 | if (this.get('multiple')) { 312 | this.set('selection', contentList.filter(function(item) { 313 | return selectedValues.indexOf(item.get('value')) !== -1; 314 | })); 315 | } else if (hasPrompt && Ember.isEmpty(selectedValues[0])) { 316 | this.setProperties({value: null, selection: null}); 317 | } else { 318 | this.send('selectItem', _findBy(contentList, 'value', selectedValues[0])); 319 | } 320 | this.sendChangeAction(); 321 | }, 322 | 323 | toggleSelectAllNone() { 324 | var listName; 325 | if (this.get('hasSelectedItems')) { 326 | listName = 'selectedContentList'; 327 | } else { 328 | listName = 'unselectedContentList'; 329 | } 330 | this.send('selectAllNone', listName); 331 | return false; 332 | }, 333 | 334 | clearFilter() { 335 | this.set('searchFilter', null); 336 | return false; 337 | } 338 | } 339 | }); 340 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/app/.gitkeep -------------------------------------------------------------------------------- /app/components/keyboard-select-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import SelectPicker from './select-picker'; 3 | import ItemCursorMixin from 'ember-cli-select-picker/mixins/item-cursor'; 4 | 5 | const KEY_ENTER = 13; 6 | const KEY_ESC = 27; 7 | const KEY_UP = 38; 8 | const KEY_DOWN = 40; 9 | 10 | export default SelectPicker.extend(ItemCursorMixin, { 11 | layoutName: 'components/select-picker', 12 | classNames: ['select-picker', 'keyboard-select-picker'], 13 | 14 | didInsertElement() { 15 | this.$().on(`keydown.${this.get('elementId')}`, 16 | Ember.run.bind(this, 'handleKeyPress')); 17 | }, 18 | 19 | willDestroyElement() { 20 | this.$().off(`keydown.${this.get('elementId')}`); 21 | }, 22 | 23 | focusActiveItem() { 24 | this.$(`[data-itemid=${this.get('activeItem.itemId')}]`).focus(); 25 | }, 26 | 27 | handleKeyPress(e) { 28 | var actionName = (() => { 29 | switch (e.which) { 30 | case KEY_DOWN: return 'activeNext'; 31 | case KEY_UP: return 'activePrev'; 32 | case KEY_ESC: return 'closeDropdown'; 33 | case KEY_ENTER: 34 | return this.get('showDropdown') ? 35 | 'selectActiveItem' : 36 | 'openDropdown'; 37 | default: return null; 38 | } 39 | })(); 40 | 41 | if (actionName) { 42 | e.preventDefault(); 43 | Ember.run(() => { this.send(actionName); }); 44 | this.focusActiveItem(); 45 | return false; 46 | } 47 | 48 | return true; 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /app/components/list-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import SelectPickerMixin from 'ember-cli-select-picker/mixins/select-picker'; 3 | 4 | var I18nProps = (Ember.I18n && Ember.I18n.TranslateableProperties) || {}; 5 | 6 | export default Ember.Component.extend( 7 | SelectPickerMixin, I18nProps, 8 | { 9 | classNames: ['select-picker', 'list-picker'], 10 | selectAllLabel: 'Select All', 11 | selectNoneLabel: 'Select None', 12 | nativeMobile: false 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /app/components/select-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import SelectPickerMixin from 'ember-cli-select-picker/mixins/select-picker'; 3 | 4 | var I18nProps = (Ember.I18n && Ember.I18n.TranslateableProperties) || {}; 5 | 6 | export default Ember.Component.extend( 7 | SelectPickerMixin, I18nProps, { 8 | 9 | nothingSelectedMessage: 'Nothing Selected', 10 | multipleSelectedMessage: '%@ items selected', 11 | selectAllLabel: 'All', 12 | selectNoneLabel: 'None', 13 | 14 | nativeMobile: true, 15 | 16 | classNames: ['select-picker', 'btn-group'], 17 | buttonClass: 'btn-default', 18 | 19 | badgeEnabled: Ember.computed.and('showBadge', 'multiple'), 20 | 21 | selectionBadge: Ember.computed( 22 | 'selection.length', 'badgeEnabled', 23 | function() { 24 | const enabled = this.get('badgeEnabled'); 25 | const selected = this.get('selection.length'); 26 | return (enabled && selected && selected !== 0) ? selected : ''; 27 | } 28 | ), 29 | 30 | setupDom: Ember.on('didInsertElement', function() { 31 | const id = this.get('elementId'); 32 | Ember.run.scheduleOnce('afterRender', this, this.updateDropUp); 33 | $(document) 34 | .on(`click.${id}`, Ember.run.bind(this, this.hideDropdownMenu)) 35 | .on(`touchstart.${id}`, Ember.run.bind(this, this.hideDropdownMenu)) 36 | .on(`scroll.${id}`, Ember.run.bind(this, this.updateDropUp)) 37 | .on(`resize.${id}`, Ember.run.bind(this, this.updateDropUp)); 38 | }), 39 | 40 | hideDropdownMenu: function(evt) { 41 | if (this.get('keepDropdownOpen')) { 42 | this.set('keepDropdownOpen', false); 43 | return; 44 | } 45 | if (this.element && !$.contains(this.element, evt.target)) { 46 | this.send('closeDropdown'); 47 | } 48 | }, 49 | 50 | updateDropUp() { 51 | const windowHeight = $(window).height(); 52 | const scrollTop = $(window).scrollTop(); 53 | const buttonOffset = this.$().offset().top; 54 | const buttonHeight = this.$().height(); 55 | const menuHeight = this.$('.dropdown-menu').height(); 56 | const viewportOffset = buttonOffset - scrollTop; 57 | const menuBottom = viewportOffset + buttonHeight + menuHeight; 58 | this.set('isDropUp', menuBottom > windowHeight); 59 | }, 60 | 61 | teardownDom: Ember.on('willDestroyElement', function() { 62 | $(document).off(`.${this.get('elementId')}`); 63 | }), 64 | 65 | actions: { 66 | showHide() { 67 | this.toggleProperty('showDropdown'); 68 | }, 69 | 70 | openDropdown() { 71 | this.set('showDropdown', true); 72 | }, 73 | 74 | closeDropdown() { 75 | this.set('showDropdown', false); 76 | } 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /app/templates/components/-native-select.hbs: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /app/templates/components/list-picker.hbs: -------------------------------------------------------------------------------- 1 | {{#if nativeMobile}} 2 |
3 | {{yield}} 4 | {{partial "components/native-select"}} 5 |
6 | {{/if}} 7 | 8 |
9 | {{yield}} 10 | 11 | {{#if liveSearch}} 12 |
13 | {{input type="text" class="search-filter form-control" value=searchFilter focus="preventClosing"}} 14 | 15 | 21 | 22 |
23 | {{/if}} 24 | {{#if multiple}} 25 | {{#if splitAllNoneButtons}} 26 |
27 | 28 | 29 |
30 | {{else}} 31 |
32 | 36 |
37 | {{/if}} 38 | {{/if}} 39 | {{#each nestedGroupContentList as |group|}} 40 | {{#if group.name}}

{{group.name}}

{{/if}} 41 |
42 | {{#each group.items as |item|}} 43 | 49 | {{/each}} 50 |
51 | {{/each}} 52 |
53 | -------------------------------------------------------------------------------- /app/templates/components/select-picker.hbs: -------------------------------------------------------------------------------- 1 | {{#if nativeMobile}} 2 |
3 | {{yield}} 4 | {{partial "components/native-select"}} 5 |
6 | {{/if}} 7 | 8 | 77 | -------------------------------------------------------------------------------- /blueprints/ember-cli-select-picker/index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | module.exports = { 3 | // no-op since we're just adding dependencies 4 | normalizeEntityName: function() {}, 5 | 6 | afterInstall: function() { 7 | return this.addBowerPackagesToProject([ 8 | {name: 'bootstrap', target: '~3.3.4'} 9 | ]); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-select-picker", 3 | "dependencies": { 4 | "ember": "1.13.8", 5 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 6 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 7 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", 8 | "ember-qunit": "0.4.9", 9 | "ember-qunit-notifications": "0.0.7", 10 | "ember-resolver": "~0.1.18", 11 | "jquery": "~1.11.3", 12 | "loader.js": "ember-cli/loader.js#3.2.1", 13 | "qunit": "~1.18.0", 14 | "chance": "~0.7.3", 15 | "highlightjs": "~8.4.0", 16 | "bootstrap": "~3.3.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | scenarios: [ 3 | { 4 | name: 'default', 5 | dependencies: { } 6 | }, 7 | { 8 | name: 'ember-release', 9 | dependencies: { 10 | 'ember': 'components/ember#release' 11 | }, 12 | resolutions: { 13 | 'ember': 'release' 14 | } 15 | } 16 | /* TODO: Skipping, Current code does not work on beta/canary builds yet. 17 | { 18 | name: 'ember-beta', 19 | dependencies: { 20 | 'ember': 'components/ember#beta' 21 | }, 22 | resolutions: { 23 | 'ember': 'beta' 24 | } 25 | }, 26 | { 27 | name: 'ember-canary', 28 | dependencies: { 29 | 'ember': 'components/ember#canary' 30 | }, 31 | resolutions: { 32 | 'ember': 'canary' 33 | } 34 | } 35 | */ 36 | ] 37 | }; 38 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | var EmberApp = require('ember-cli/lib/broccoli/ember-addon'); 3 | var Funnel = require('broccoli-funnel'); 4 | 5 | module.exports = function(defaults) { 6 | var app = new EmberApp(defaults, { 7 | sourcemaps: { 8 | enabled: false 9 | }, 10 | fingerprint: { 11 | customHash: 'dist', 12 | exclude: ['screen-shot.png'] 13 | }, 14 | minifyCSS: { 15 | enabled: false 16 | }, 17 | minifyJS: { 18 | enabled: false 19 | } 20 | }); 21 | 22 | /* 23 | This build file specifes the options for the dummy test app of this 24 | addon, located in `/tests/dummy` 25 | This build file does *not* influence how the addon or the app using it 26 | behave. You most likely want to be modifying `./index.js` or app's build file 27 | */ 28 | app.import('bower_components/bootstrap/dist/css/bootstrap.css'); 29 | app.import('bower_components/bootstrap/dist/js/bootstrap.js'); 30 | 31 | app.import('bower_components/chance/chance.js'); 32 | 33 | app.import('bower_components/highlightjs/styles/github.css'); 34 | app.import('bower_components/highlightjs/highlight.pack.js'); 35 | 36 | var fontTree = new Funnel( 37 | 'bower_components/bootstrap/dist/fonts', 38 | {destDir: '/fonts'} 39 | ); 40 | 41 | return app.toTree([fontTree]); 42 | }; 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-cli-select-picker', 6 | 7 | included: function(app, parentAddon) { 8 | var target = (parentAddon || app); 9 | 10 | target.import('vendor/select-picker.css'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-select-picker", 3 | "version": "2.3.8", 4 | "description": "A bootstrap 3 compatible select component", 5 | "author": "Devin Weaver (@sukima) ", 6 | "homepage": "https://sukima.github.io/ember-cli-select-picker/", 7 | "directories": { 8 | "doc": "doc", 9 | "test": "tests" 10 | }, 11 | "scripts": { 12 | "start": "ember server", 13 | "build": "ember build", 14 | "test": "ember try:testall", 15 | "deploy": "ember github-pages:commit --message \"Deploy gh-pages from commit $(git rev-parse HEAD)\"" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/sukima/ember-cli-select-picker" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/sukima/ember-cli-select-picker/issues" 23 | }, 24 | "engines": { 25 | "node": ">= 0.10.0" 26 | }, 27 | "license": "MIT", 28 | "devDependencies": { 29 | "broccoli-asset-rev": "^2.1.2", 30 | "broccoli-funnel": "^1.0.1", 31 | "ember-cli": "1.13.8", 32 | "ember-cli-app-version": "0.5.0", 33 | "ember-cli-content-security-policy": "0.4.0", 34 | "ember-cli-dependency-checker": "^1.0.1", 35 | "ember-cli-github-pages": "0.0.6", 36 | "ember-cli-htmlbars": "0.7.9", 37 | "ember-cli-htmlbars-inline-precompile": "^0.2.0", 38 | "ember-cli-ic-ajax": "0.2.1", 39 | "ember-cli-inject-live-reload": "^1.3.1", 40 | "ember-cli-qunit": "^1.0.0", 41 | "ember-cli-release": "0.2.3", 42 | "ember-cli-sri": "^1.0.3", 43 | "ember-cli-uglify": "^1.2.0", 44 | "ember-disable-prototype-extensions": "^1.0.0", 45 | "ember-disable-proxy-controllers": "^1.0.0", 46 | "ember-export-application-global": "^1.0.3", 47 | "ember-try": "0.0.6" 48 | }, 49 | "keywords": [ 50 | "ember-addon", 51 | "bootstrap", 52 | "ember", 53 | "select", 54 | "component" 55 | ], 56 | "dependencies": { 57 | "ember-cli-babel": "^5.1.3" 58 | }, 59 | "ember-addon": { 60 | "configPath": "tests/dummy/config", 61 | "demoURL": "http://sukima.github.io/ember-cli-select-picker/" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "disable_watching": true, 5 | "launch_in_ci": [ 6 | "PhantomJS" 7 | ], 8 | "launch_in_dev": [ 9 | "PhantomJS", 10 | "Chrome" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName", 25 | "Chance", 26 | "chance", 27 | "hljs" 28 | ], 29 | "node": false, 30 | "browser": false, 31 | "boss": true, 32 | "curly": true, 33 | "debug": false, 34 | "devel": false, 35 | "eqeqeq": true, 36 | "evil": true, 37 | "forin": false, 38 | "immed": false, 39 | "laxbreak": false, 40 | "newcap": true, 41 | "noarg": true, 42 | "noempty": false, 43 | "nonew": false, 44 | "nomen": false, 45 | "onevar": false, 46 | "plusplus": false, 47 | "regexp": false, 48 | "undef": true, 49 | "sub": true, 50 | "strict": false, 51 | "white": false, 52 | "eqnull": true, 53 | "esnext": true, 54 | "unused": true 55 | } 56 | -------------------------------------------------------------------------------- /tests/common/select-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test } from 'ember-qunit'; 3 | 4 | const contentArray = [ 5 | {name: 'First item', id: 1}, 6 | {name: 'Second item', id: 2}, 7 | {name: 'Third item', id: 3}, 8 | {name: 'Another', id: 4} 9 | ]; 10 | 11 | export default function() { 12 | test('sends action when there are updates to the selection', function(assert) { 13 | assert.expect(1); 14 | 15 | var results; 16 | var component = this.subject({ 17 | content: contentArray, 18 | selection: Ember.A(), 19 | multiple: true, 20 | optionValuePath: 'content.id', 21 | optionLabelPath: 'content.name', 22 | attrs: { 23 | action: function(values) { results = values; } 24 | } 25 | }); 26 | 27 | Ember.run(function() { 28 | component.send('selectItem', component.get('contentList.firstObject')); 29 | }); 30 | 31 | assert.ok( 32 | Ember.isPresent(results), 33 | 'action should be triggered on change' 34 | ); 35 | }); 36 | 37 | test('updates selection when selectItem is triggered (single)', function(assert) { 38 | assert.expect(2); 39 | 40 | var component = this.subject({ 41 | content: contentArray, 42 | selection: Ember.A(), 43 | optionValuePath: 'content.id', 44 | optionLabelPath: 'content.name' 45 | }); 46 | 47 | assert.ok( 48 | Ember.isEmpty(component.get('selection')), 49 | 'no items should be selected' 50 | ); 51 | 52 | Ember.run(function() { 53 | component.send('selectItem', component.get('contentList.firstObject')); 54 | }); 55 | 56 | assert.ok( 57 | Ember.isPresent(component.get('selection')), 58 | 'one item should be selected' 59 | ); 60 | }); 61 | 62 | test('updates selection when selectItem is triggered (multiple)', function(assert) { 63 | assert.expect(3); 64 | 65 | var component = this.subject({ 66 | multiple: true, 67 | content: contentArray, 68 | selection: Ember.A(), 69 | optionValuePath: 'content.id', 70 | optionLabelPath: 'content.name' 71 | }); 72 | 73 | Ember.run(function() { 74 | component.send('selectAllNone', 'unselectedContentList'); 75 | }); 76 | 77 | assert.equal( 78 | component.get('selection.length'), contentArray.length, 79 | 'all items should be selected' 80 | ); 81 | 82 | Ember.run(function() { 83 | component.send('selectAllNone', 'selectedContentList'); 84 | }); 85 | 86 | assert.equal( 87 | component.get('selection.length'), 0, 88 | 'no items should be selected' 89 | ); 90 | 91 | Ember.run(function() { 92 | component.send('selectItem', component.get('contentList.firstObject')); 93 | }); 94 | 95 | assert.equal( 96 | component.get('selection.length'), 1, 97 | 'one item should be selected' 98 | ); 99 | }); 100 | 101 | test('Normal Search (multiple)', function(assert) { 102 | assert.expect(5); 103 | 104 | var component = this.subject({ 105 | multiple: true, 106 | liveSearch: true, 107 | content: contentArray, 108 | selection: Ember.A(), 109 | optionValuePath: 'content.id', 110 | optionLabelPath: 'content.name' 111 | }); 112 | 113 | assert.equal( 114 | component.get('contentList.length'), contentArray.length, 115 | 'contentList should not be filtered initially' 116 | ); 117 | 118 | Ember.run(function() { 119 | component.set('searchFilter', 'F'); 120 | }); 121 | 122 | // Only one Items has 'F' litter in it (First Item), So, Filtering should 123 | // result in one item being listed. 124 | assert.equal( 125 | component.get('contentList.length'), 1, 126 | 'contentList should be filtered to one result for "F"' 127 | ); 128 | 129 | Ember.run(function() { 130 | component.set('searchFilter', 'S'); 131 | }); 132 | 133 | // The result should be 2 (First, Second) 134 | assert.equal( 135 | component.get('contentList.length'), 2, 136 | 'contentList should be filtered to two results for "S"' 137 | ); 138 | 139 | Ember.run(function() { 140 | component.send('clearFilter'); 141 | }); 142 | 143 | assert.equal( 144 | component.get('contentList.length'), contentArray.length, 145 | 'contentList should not be filtered when searchFilter is cleared' 146 | ); 147 | 148 | Ember.run(function() { 149 | component.set('searchFilter', 'itm'); 150 | }); 151 | 152 | // When search is set to normal (not advanced), searching for 'itm' should 153 | // give 0 results 154 | assert.equal( 155 | component.get('contentList.length'), 0, 156 | 'contentList should be filtered to no results for "itm"' 157 | ); 158 | }); 159 | 160 | test('Advanced Search (multiple)', function(assert) { 161 | assert.expect(2); 162 | 163 | var component = this.subject({ 164 | multiple: true, 165 | liveSearch: 'advanced', 166 | content: contentArray, 167 | selection: Ember.A(), 168 | optionValuePath: 'content.id', 169 | optionLabelPath: 'content.name' 170 | }); 171 | 172 | assert.equal( 173 | component.get('contentList.length'), contentArray.length, 174 | 'contentList should not be filtered initially' 175 | ); 176 | 177 | Ember.run(function() { 178 | component.set('searchFilter', 'itm'); 179 | }); 180 | 181 | assert.equal( 182 | component.get('contentList.length'), 3, 183 | 'contentList should be filtered to three results for "itm"' 184 | ); 185 | }); 186 | } 187 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | var App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver: Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/components/highlight-code.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | lang: 'nohighlight', 5 | highlightCodeBlocks: Ember.on('didInsertElement', function() { 6 | hljs.highlightBlock(this.$('code').get(0)); 7 | }) 8 | }); 9 | -------------------------------------------------------------------------------- /tests/dummy/app/components/modal-dialog.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | isVisible: false, 5 | showHide: Ember.observer('isVisible', function() { 6 | this.$('.modal').css({display: (this.get('isVisible') ? 'block' : 'none')}); 7 | }) 8 | }); 9 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from '../config/environment'; 3 | 4 | var ApplicationController = Ember.Controller.extend({ 5 | addonVersion: config.APP.addonVersion 6 | }); 7 | 8 | export default ApplicationController; 9 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ChoicesMixin from '../mixins/choices'; 3 | 4 | function neighborhood() { 5 | return chance.pick(['East side', 'West side']); 6 | } 7 | 8 | export default Ember.Controller.extend(ChoicesMixin, { 9 | singleContent: Ember.computed(function() { 10 | return chance.unique(chance.street, 10) 11 | .map(function(street) { 12 | return {label: street, value: street}; 13 | }); 14 | }), 15 | 16 | multipleContent: Ember.computed(function() { 17 | return Ember.A(chance.unique(chance.street, 10) 18 | .map(function(street) { 19 | return {label: street, value: street, group: neighborhood()}; 20 | })) 21 | .sortBy('group'); 22 | }), 23 | 24 | listContent: Ember.computed(function() { 25 | return Ember.A(chance.unique(chance.street, 10) 26 | .map(function(street) { 27 | return {label: street, value: street, group: neighborhood()}; 28 | })) 29 | .sortBy('group'); 30 | }) 31 | }); 32 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/keyboard.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ChoicesMixin from '../mixins/choices'; 3 | 4 | function neighborhood() { 5 | return chance.pick(['East side', 'West side']); 6 | } 7 | 8 | var KeyboardController = Ember.Controller.extend(ChoicesMixin, { 9 | singleContent: Ember.computed(function() { 10 | return chance.unique(chance.street, 10) 11 | .map(function(street) { 12 | return {label: street, value: street}; 13 | }); 14 | }), 15 | 16 | multipleContent: Ember.computed(function() { 17 | return Ember.A(chance.unique(chance.street, 10) 18 | .map(function(street) { 19 | return {label: street, value: street, group: neighborhood()}; 20 | })) 21 | .sortBy('group'); 22 | }) 23 | }); 24 | 25 | export default KeyboardController; 26 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/options.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ChoicesMixin from '../mixins/choices'; 3 | 4 | function neighborhood() { 5 | return chance.pick(['East side', 'West side']); 6 | } 7 | 8 | export default Ember.Controller.extend(ChoicesMixin, { 9 | showHelloDialog: false, 10 | 11 | prepMultipleValue: Ember.on('init', function() { 12 | var sample = this.get('multipleContent').slice(0, 4); 13 | this.set('multipleValue', sample); 14 | }), 15 | 16 | singleContent: Ember.computed(function() { 17 | return chance.unique(chance.street, 10) 18 | .map(function(street) { 19 | return {label: street, value: street}; 20 | }); 21 | }), 22 | 23 | multipleContent: Ember.computed(function() { 24 | return Ember.A(chance.unique(chance.street, 10) 25 | .map(function(street) { 26 | return {label: street, value: street, group: neighborhood()}; 27 | })) 28 | .sortBy('group'); 29 | }), 30 | 31 | listContent: Ember.computed(function() { 32 | return Ember.A(chance.unique(chance.street, 10) 33 | .map(function(street) { 34 | return {label: street, value: street, group: neighborhood()}; 35 | })) 36 | .sortBy('group'); 37 | }), 38 | 39 | actions: { 40 | showHelloDialog() { 41 | this.set('showHelloDialog', true); 42 | }, 43 | 44 | hideHelloDialog() { 45 | this.set('showHelloDialog', false); 46 | } 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/searching.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { choicesToString, setChoicesAction } from '../modules/choices-props'; 3 | 4 | function popularity() { 5 | return chance.pick(['Great states', 'Awesome states']); 6 | } 7 | 8 | function stateList() { 9 | return Ember.computed(function() { 10 | return Ember.A(chance.states() 11 | .map(function(state) { 12 | return {label: state.name, value: state.name, group: popularity()}; 13 | })) 14 | .sortBy('group', 'label'); 15 | }); 16 | } 17 | 18 | var SearchingController = Ember.Controller.extend({ 19 | simpleSearchContent: stateList(), 20 | simpleSearchChoices: [], 21 | simpleSearchChoicesStr: choicesToString('simpleSearchChoices'), 22 | 23 | advancedSearchContent: stateList(), 24 | advancedSearchChoices: [], 25 | advancedSearchChoicesStr: choicesToString('advancedSearchChoices'), 26 | 27 | listSearchContent: stateList(), 28 | listSearchChoices: [], 29 | listSearchChoicesStr: choicesToString('listSearchChoices'), 30 | 31 | actions: { 32 | setSimpleSearchChoices: setChoicesAction('simpleSearchValue'), 33 | setAdvancedSearchChoices: setChoicesAction('advancedSearchValue'), 34 | setListSearchChoices: setChoicesAction('listSearchValue') 35 | } 36 | }); 37 | 38 | export default SearchingController; 39 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/test-list-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | theContent: [ 5 | {name: 'First item', id: 1}, 6 | {name: 'Second item', id: 2}, 7 | {name: 'Third item', id: 3}, 8 | {name: 'Another', id: 4} 9 | ] 10 | }); 11 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/test-select-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | theContent: [ 5 | {name: 'First item', id: 1}, 6 | {name: 'Second item', id: 2}, 7 | {name: 'Third item', id: 3}, 8 | {name: 'Another', id: 4} 9 | ] 10 | }); 11 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TestSelectPicker 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/mixins/choices.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { setChoicesAction, choicesToString } from '../modules/choices-props'; 3 | 4 | export default Ember.Mixin.create({ 5 | singleChoice: [], 6 | singleChoiceStr: Ember.computed.readOnly('singleChoice.firstObject.value'), 7 | 8 | multipleChoices: [], 9 | multipleChoicesStr: choicesToString('multipleChoices'), 10 | 11 | listChoices: [], 12 | listChoicesStr: choicesToString('listChoices'), 13 | 14 | actions: { 15 | setSingleChoice: setChoicesAction('singleChoice'), 16 | setMultipleChoices: setChoicesAction('multipleChoices'), 17 | setListChoices: setChoicesAction('listChoices') 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/modules/choices-props.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export function setChoicesAction(prop) { 4 | return function(selection) { 5 | this.set(prop, Ember.makeArray(selection)); 6 | }; 7 | } 8 | 9 | export function choicesToString(dependentProp) { 10 | return Ember.computed(`${dependentProp}.[].value`, function() { 11 | return Ember.A(this.get(dependentProp)).mapBy('value').join(', '); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType, 6 | rootURL: config.baseURL 7 | }); 8 | 9 | Router.map(function() { 10 | this.route('test-select-picker'); 11 | this.route('test-list-picker'); 12 | this.route('install'); 13 | this.route('searching'); 14 | this.route('options'); 15 | this.route('i18n'); 16 | this.route('keyboard'); 17 | }); 18 | 19 | export default Router; 20 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | .search-examples code u { 2 | background-color: #F9F200; 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Select Picker

4 |

An Ember based reinvention of the select picker for Bootstrap 3.

5 |

Version: {{addonVersion}}

6 | 7 | 27 | 28 |
29 | 30 | {{outlet}} 31 | 32 |
33 | 34 | {{!-- 35 | live search 36 | simple search 37 | advanced search 38 | options 39 | all or none 40 | class changes primary, info, success, warning, danger, inverse 41 | prompt 42 | I18n support 43 | Keyboard support 44 | --}} 45 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/highlight-code.hbs: -------------------------------------------------------------------------------- 1 |
{{yield}}
2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/modal-dialog.hbs: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/i18n.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

I18n Support

4 |
5 |
6 |

To use i18n support you first need to install the ember-i18n addon to your applicaiton project.

7 |
$ ember install ember-i18n
8 |
9 |
10 | 11 |
12 |
13 |

Template Example

14 |
15 |
16 | {{#highlight-code lang="handlebars"}} 17 | \{{select-picker content=multipleContent 18 | selection=multipleValue 19 | multiple=true 20 | 21 | titleTranslation="tranlation.key.title" 22 | selectAllTranslation="tranlation.key.select_all" 23 | selectNoneTranslation="tranlation.key.select_none" 24 | nothingSelectedMessageTranslation="tranlation.key.prompt" 25 | 26 | summaryMessageKey="i18n.tranlation.key.summary" 27 | 28 | optionLabelPath="content.label" 29 | optionValuePath="content.value"}} 30 | {{/highlight-code}} 31 |
32 |
33 | 34 |
35 |
36 |

JSON Strings Example

37 |
38 |
39 | {{#highlight-code lang="json"}} 40 | { 41 | "translation": { 42 | "key: { 43 | "title": "translated title used for accessibility", 44 | "prompt": "translated prompt", 45 | "select_all": "Select All", 46 | "select_none": "Select None", 47 | "summary": { 48 | "one": "You selected \{{item}}", 49 | "other": "You selected \{{count}} items: \{{list}}" 50 | } 51 | } 52 | } 53 | } 54 | {{/highlight-code}} 55 |
56 |
57 | 58 |
59 |
60 |

Caveats

61 |
62 |
63 |

Because translated properties can not have interpolated values the summaryMessage uses summaryMessageKey instead of summaryMessageTranslation. It has the following values:

64 |
65 |
count
66 |
Number of selected items. If zero then nothingSelectedMessage is used instead.
67 |
item
68 |
The item selected (first item in the list if multiple items are selected).
69 |
list
70 |
The list of selected items comma separated.
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Single Selection

4 |
5 |
6 |
7 |
8 |

Results: {{singleChoiceStr}}

9 |
10 |
11 |
12 |
13 |

{{select-picker content=singleContent 14 | selection=singleChoice 15 | action=(action "setSingleChoice") 16 | optionLabelPath="content.label" 17 | optionValuePath="content.value"}}

18 |
19 |
20 | {{#highlight-code lang="handlebars"}} 21 | \{{select-picker content=singleContent 22 | selection=singleChoice 23 | action=(action "setSingleChoice") 24 | optionLabelPath="content.label" 25 | optionValuePath="content.value"}} 26 | {{/highlight-code}} 27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |

Multiple Selections

35 |
36 |
37 |
38 |
39 |

Results: {{multipleChoicesStr}}

40 |
41 |
42 |
43 |
44 |

{{select-picker content=multipleContent 45 | selection=multipleChoices 46 | action=(action "setMultipleChoices") 47 | multiple=true 48 | optionGroupPath="group" 49 | optionLabelPath="content.label" 50 | optionValuePath="content.value"}}

51 |
52 |
53 | {{#highlight-code lang="handlebars"}} 54 | \{{select-picker content=multipleContent 55 | selection=multipleChoices 56 | action=(action "setMultipleChoices") 57 | multiple=true 58 | optionGroupPath="group" 59 | optionLabelPath="content.label" 60 | optionValuePath="content.value"}} 61 | {{/highlight-code}} 62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 |
70 |

List Picker

71 |
72 |
73 |
74 |
75 |

Results: {{listChoicesStr}}

76 |
77 |
78 |
79 |
80 |

{{list-picker content=listContent 81 | selection=listChoices 82 | action=(action "setListChoices") 83 | multiple="true" 84 | optionGroupPath="group" 85 | optionLabelPath="content.label" 86 | optionValuePath="content.value"}}

87 |
88 |
89 | {{#highlight-code lang="handlebars"}} 90 | \{{list-picker content=listContent 91 | selection=listChoices 92 | action=(action "setListChoices") 93 | multiple="true" 94 | optionGroupPath="group" 95 | optionLabelPath="content.label" 96 | optionValuePath="content.value"}} 97 | {{/highlight-code}} 98 |
99 |
100 |
101 |
102 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/install.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Installing

4 |
5 |
6 |

This is an Ember CLI Addon and can be installed through the ember command.

7 |
$ ember install ember-cli-select-picker
8 |
9 |
10 | 11 |
12 |
13 |

Contributing

14 |
15 |
16 |

Contributions are welcome. All source code is managed through GitHub.

17 |

18 | 19 | View source on GitHub 20 | 21 |

22 |
23 |
24 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/keyboard.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Keyboard Support

4 |
5 |
6 |

To use keyboard support you first need to install the ember-keyboard-shortcuts addon to your applicaiton project.

7 |
$ ember install ember-keyboard-shortcuts
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
KeyAction
EscClose the dropdown list
EnterOpen the dropdown list or Select/Unselect item the cursor is on
DownMove cursor to next item
UpMove cursor to previous item
35 |
36 | 37 |
38 |
39 |

Single Selection

40 |
41 |
42 |
43 |
44 |

Results: {{singleChoiceStr}}

45 |
46 |
47 |
48 |
49 | {{keyboard-select-picker content=singleContent 50 | selection=singleChoice 51 | action=(action "setSingleChoice") 52 | optionLabelPath="content.label" 53 | optionValuePath="content.value"}} 54 |
55 |
56 | {{#highlight-code lang="handlebars"}} 57 | \{{keyboard-select-picker content=singleContent 58 | selection=singleChoice 59 | action=(action "setSingleChoice") 60 | optionLabelPath="content.label" 61 | optionValuePath="content.value"}} 62 | {{/highlight-code}} 63 |
64 |
65 |
66 |
67 | 68 |
69 |
70 |

Multiple Selections

71 |
72 |
73 |
74 |
75 |

Results: {{multipleChoicesStr}}

76 |
77 |
78 |
79 |
80 | {{keyboard-select-picker content=multipleContent 81 | selection=multipleChoices 82 | action=(action "setMultipleChoices") 83 | multiple=true 84 | optionGroupPath="group" 85 | optionLabelPath="content.label" 86 | optionValuePath="content.value"}} 87 |
88 |
89 | {{#highlight-code lang="handlebars"}} 90 | \{{keyboard-select-picker content=multipleContent 91 | selection=multipleChoices 92 | action=(action "setMultipleChoices") 93 | multiple=true 94 | optionGroupPath="group" 95 | optionLabelPath="content.label" 96 | optionValuePath="content.value"}} 97 | {{/highlight-code}} 98 |
99 |
100 |
101 |
102 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/options.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Badges

4 |
5 |
6 |
7 |
8 |

Results: {{multipleChoicesStr}}

9 |
10 |
11 |
12 |
13 |

{{select-picker content=multipleContent 14 | selection=multipleChoices 15 | action=(action "setMultipleChoices") 16 | multiple=true 17 | showBadge=true 18 | summaryMessage="Multiple items selected" 19 | optionLabelPath="content.label" 20 | optionValuePath="content.value"}}

21 |
22 |
23 | {{#highlight-code lang="handlebars"}} 24 | \{{select-picker content=multipleContent 25 | selection=multipleChoices 26 | action=(action "setMultipleChoices") 27 | multiple=true 28 | showBadge=true 29 | summaryMessage="Multiple items selected" 30 | optionLabelPath="content.label" 31 | optionValuePath="content.value"}} 32 | {{/highlight-code}} 33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 |

Mobile Support

41 |
42 |
43 |
44 |
45 |

By default the native select widget is used for small screens (mobile) If you want the Bootstrap styled picker on mobile devices then you need to set nativeMobile to false.

46 |

Results: {{multipleChoicesStr}}

47 |
48 |
49 |
50 |
51 |

{{select-picker content=multipleContent 52 | selection=multipleChoices 53 | action=(action "setMultipleChoices") 54 | multiple=true 55 | nativeMobile=false 56 | optionLabelPath="content.label" 57 | optionValuePath="content.value"}}

58 |
59 |
60 | {{#highlight-code lang="handlebars"}} 61 | \{{select-picker content=multipleContent 62 | selection=multipleChoices 63 | action=(action "setMultipleChoices") 64 | multiple=true 65 | nativeMobile=false 66 | optionLabelPath="content.label" 67 | optionValuePath="content.value"}} 68 | {{/highlight-code}} 69 |
70 |
71 |
72 |
73 | 74 |
75 |
76 |

Custom content

77 |
78 |
79 |
80 |
81 |

You can add your own custom content to the top of the drop down by using Ember's block syntax.

82 |

Results: {{multipleChoicesStr}}

83 |
84 |
85 |
86 |
87 |

{{#select-picker content=multipleContent 88 | selection=multipleChoices 89 | action=(action "setMultipleChoices") 90 | multiple=true 91 | summaryMessage="Example content inside" 92 | optionLabelPath="content.label" 93 | optionValuePath="content.value"}} 94 |

95 | Hello! Try this: 96 |
97 | {{/select-picker}}

98 |
99 |
100 | {{#highlight-code lang="handlebars"}} 101 | \{{#select-picker content=multipleContent 102 | selection=multipleChoices 103 | action=(action "setMultipleChoices") 104 | multiple=true 105 | summaryMessage="Example content inside" 106 | optionLabelPath="content.label" 107 | optionValuePath="content.value"}} 108 | <div class="alert alert-info"> 109 | Hello! Try this <button \{{action "showHelloDialog"}}>button</button> 110 | </div> 111 | \{{/select-picker}} 112 | {{/highlight-code}} 113 |
114 |
115 |
116 |
117 | 118 | {{#modal-dialog title="Hello User" isVisible=showHelloDialog onDismiss=(action "hideHelloDialog")}} 119 |

Hello random user!

120 | {{/modal-dialog}} 121 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/searching.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Single Search

4 |
5 |
6 |
7 |
8 |

Simple search attempts to match terms as full strings.

9 |

For example the term def would match the words abcdef, defghi, or indefinitely.

10 |

Result: {{simpleSearchChoicesStr}}

11 |
12 |
13 |
14 |
15 | {{select-picker content=simpleSearchContent 16 | selection=simpleSearchChoices 17 | action=(action "setSimpleSearchChoices") 18 | multiple=true 19 | liveSearch=true 20 | optionGroupPath="group" 21 | optionLabelPath="content.label" 22 | optionValuePath="content.value"}} 23 |
24 |
25 | {{#highlight-code lang="handlebars"}} 26 | \{{select-picker content=simpleSearchContent 27 | selection=simpleSearchChoices 28 | action=(action "setSimpleSearchChoices") 29 | multiple=true 30 | liveSearch=true 31 | optionGroupPath="group" 32 | optionLabelPath="content.label" 33 | optionValuePath="content.value"}} 34 | {{/highlight-code}} 35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 |

Advanced Search

43 |
44 |
45 |
46 |
47 |

Advanced search finds terms through a fuzzy match. It looks for characters in the string that appear in order from left to right with any number of other character in between.

48 |

For example the search term def would match abcdef or diversify.

49 |

Result: {{advancedSearchChoicesStr}}

50 |
51 |
52 |
53 |
54 | {{select-picker content=advancedSearchContent 55 | selection=advancedSearchChoices 56 | action=(action "setAdvancedSearchChoices") 57 | multiple=true 58 | liveSearch="advanced" 59 | optionGroupPath="group" 60 | optionLabelPath="content.label" 61 | optionValuePath="content.value"}} 62 |
63 |
64 | {{#highlight-code lang="handlebars"}} 65 | \{{select-picker content=advancedSearchContent 66 | selection=advancedSearchChoices 67 | action=(action "setAdvancedSearchChoices") 68 | multiple=true 69 | liveSearch="advanced" 70 | optionGroupPath="group" 71 | optionLabelPath="content.label" 72 | optionValuePath="content.value"}} 73 | {{/highlight-code}} 74 |
75 |
76 |
77 |
78 | 79 |
80 |
81 |

List Picker

82 |
83 |
84 |
85 |
86 |

Result: {{listSearchChoicesStr}}

87 |
88 |
89 |
90 |
91 | {{list-picker content=listSearchContent 92 | selection=listSearchChoices 93 | action=(action "setListSearchChoices") 94 | multiple=true 95 | liveSearch="advanced" 96 | optionGroupPath="group" 97 | optionLabelPath="content.label" 98 | optionValuePath="content.value"}} 99 |
100 |
101 | {{#highlight-code lang="handlebars"}} 102 | \{{list-picker content=advancedSearchContent 103 | selection=listSearchChoices 104 | action=(action "setListSearchChoices") 105 | multiple=true 106 | liveSearch="advanced" 107 | optionGroupPath="group" 108 | optionLabelPath="content.label" 109 | optionValuePath="content.value"}} 110 | {{/highlight-code}} 111 |
112 |
113 |
114 |
115 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/test-list-picker.hbs: -------------------------------------------------------------------------------- 1 |

list-picker

2 | 3 |
4 | {{list-picker content=theContent 5 | value=singleValue 6 | nativeMobile=false 7 | optionLabelPath="content.name" 8 | optionValuePath="content.id"}} 9 |
10 | 11 |
12 | {{list-picker content=theContent 13 | selection=multipleValue 14 | multiple=true 15 | nativeMobile=false 16 | optionLabelPath="content.name" 17 | optionValuePath="content.id"}} 18 |
19 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/test-select-picker.hbs: -------------------------------------------------------------------------------- 1 |

select-picker

2 | 3 |
4 | {{select-picker content=theContent 5 | value=singleValue 6 | nativeMobile=false 7 | optionLabelPath="content.name" 8 | optionValuePath="content.id"}} 9 |
10 | 11 |
12 | {{select-picker content=theContent 13 | selection=multipleValue 14 | multiple=true 15 | showBadge=true 16 | nativeMobile=false 17 | optionLabelPath="content.name" 18 | optionValuePath="content.id"}} 19 |
20 | 21 |
22 | {{!select-picker content=theContent 23 | multiple=true 24 | selection=multipleValue 25 | liveSearch=true 26 | nativeMobile=false 27 | optionLabelPath="content.name" 28 | optionValuePath="content.id"}} 29 |
30 | 31 |
32 | {{!select-picker content=theContent 33 | multiple=true 34 | selection=multipleValue 35 | liveSearch="advanced" 36 | nativeMobile=false 37 | optionLabelPath="content.name" 38 | optionValuePath="content.id"}} 39 |
40 | -------------------------------------------------------------------------------- /tests/dummy/app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/app/views/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | var pkg = require('../../../package.json'); 3 | 4 | module.exports = function(environment) { 5 | var ENV = { 6 | modulePrefix: 'dummy', 7 | environment: environment, 8 | baseURL: '/', 9 | locationType: 'auto', 10 | EmberENV: { 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. 'with-controller': true 14 | } 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | addonVersion: pkg.version 21 | } 22 | }; 23 | 24 | if (environment === 'development') { 25 | // ENV.APP.LOG_RESOLVER = true; 26 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 27 | // ENV.APP.LOG_TRANSITIONS = true; 28 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 29 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 30 | } 31 | 32 | if (environment === 'test') { 33 | // Testem prefers this... 34 | ENV.baseURL = '/'; 35 | ENV.locationType = 'none'; 36 | 37 | // keep test console output quieter 38 | ENV.APP.LOG_ACTIVE_GENERATION = false; 39 | ENV.APP.LOG_VIEW_LOOKUPS = false; 40 | 41 | ENV.APP.rootElement = '#ember-testing'; 42 | } 43 | 44 | if (environment === 'production') { 45 | ENV.baseURL = '/ember-cli-select-picker'; 46 | } 47 | 48 | return ENV; 49 | }; 50 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/dummy/public/screen-shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/dummy/public/screen-shot.png -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | var application; 7 | 8 | var attributes = Ember.merge({}, config.APP); 9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | Ember.run(function() { 12 | application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | }); 16 | 17 | return application; 18 | } 19 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/list-picker-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test, module } from 'qunit'; 3 | import startApp from '../helpers/start-app'; 4 | 5 | var App; 6 | 7 | function checkMarkVisibilityTest(selectPickerID) { 8 | var selector = 9 | `#${selectPickerID} .list-picker-items-container button:eq(%@) span.check-mark`; 10 | return function(itemIndex) { 11 | return !find(Ember.String.fmt(selector, itemIndex)).hasClass('invisible'); 12 | }; 13 | } 14 | 15 | module('List Picker Integration', { 16 | beforeEach: function() { 17 | App = startApp(); 18 | }, 19 | afterEach: function() { 20 | Ember.run(App, App.destroy); 21 | } 22 | }); 23 | 24 | test('Select multiple', function(assert) { 25 | var isChecked = checkMarkVisibilityTest('multiple-picker'); 26 | 27 | assert.expect(3); 28 | 29 | visit('/test-list-picker') 30 | 31 | .click('#multiple-picker .list-picker-items-container button:eq(0)') 32 | .click('#multiple-picker .list-picker-items-container button:eq(1)') 33 | 34 | .then(function() { 35 | // Dropdowns might not be visible causing the check mark to not be visible. 36 | // .is(':visible') and .is(':hidden') are not reliable tests for check 37 | // marks. use hasClass('hidden') instead. 38 | assert.ok(isChecked(0), 'first item should show a check mark'); 39 | assert.ok(isChecked(1), 'second item should show a check mark'); 40 | assert.ok(!isChecked(2), 'third item should not show a check mark'); 41 | }); 42 | }); 43 | 44 | test('Select single', function(assert) { 45 | var isChecked = checkMarkVisibilityTest('single-picker'); 46 | 47 | assert.expect(4); 48 | 49 | visit('/test-list-picker') 50 | 51 | .click('#single-picker .list-picker-items-container button:eq(0)') 52 | 53 | .then(function() { 54 | assert.ok(isChecked(0), 'first item should show a check mark'); 55 | assert.ok(!isChecked(1), 'second item should not show a check mark'); 56 | }) 57 | 58 | .click('#single-picker .list-picker-items-container button:eq(1)') 59 | 60 | .then(function() { 61 | assert.ok(!isChecked(0), 'first item should not show a check mark'); 62 | assert.ok(isChecked(1), 'second item should show a check mark'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/integration/select-picker-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test, module } from 'qunit'; 3 | import startApp from '../helpers/start-app'; 4 | 5 | var App; 6 | 7 | function checkMarkVisibilityTest(selectPickerID) { 8 | var selector = 9 | `#${selectPickerID} .dropdown-menu li>a:eq(%@) span.check-mark`; 10 | return function(itemIndex) { 11 | return !find(Ember.String.fmt(selector, itemIndex)).hasClass('hidden'); 12 | }; 13 | } 14 | 15 | module('Select Picker Integration', { 16 | beforeEach: function() { 17 | App = startApp(); 18 | }, 19 | afterEach: function() { 20 | Ember.run(App, App.destroy); 21 | } 22 | }); 23 | 24 | test('Show and Hide', function(assert) { 25 | assert.expect(3); 26 | 27 | visit('/test-select-picker') 28 | 29 | .then(function() { 30 | assert.ok( 31 | find('#single-picker .dropdown-menu').is(':hidden'), 32 | 'dropdown menu should be hidden' 33 | ); 34 | }) 35 | 36 | .click('#single-picker button.dropdown-toggle') 37 | 38 | .then(function() { 39 | assert.ok( 40 | find('#single-picker .dropdown-menu').is(':visible'), 41 | 'dropdown menu should be visible' 42 | ); 43 | }) 44 | 45 | .click('#single-picker') 46 | 47 | .then(function() { 48 | assert.ok( 49 | find('#single-picker .dropdown-menu').is(':hidden'), 50 | 'dropdown menu should be hidden' 51 | ); 52 | }); 53 | }); 54 | 55 | test('Select multiple', function(assert) { 56 | var isChecked = checkMarkVisibilityTest('multiple-picker'); 57 | 58 | assert.expect(4); 59 | 60 | visit('/test-select-picker') 61 | 62 | .click('#multiple-picker button.dropdown-toggle') 63 | .click('#multiple-picker .dropdown-menu li>a:eq(0)') 64 | .click('#multiple-picker .dropdown-menu li>a:eq(1)') 65 | 66 | .then(function() { 67 | // Dropdowns might not be visible causing the check mark to not be visible. 68 | // .is(':visible') and .is(':hidden') are not reliable tests for check 69 | // marks. use hasClass('hidden') instead. 70 | assert.ok(isChecked(0), 'first item should show a check mark'); 71 | assert.ok(isChecked(1), 'second item should show a check mark'); 72 | assert.ok(!isChecked(2), 'third item should not show a check mark'); 73 | 74 | assert.equal( 75 | find('#multiple-picker .badge').text(), 2, 76 | 'badge should show number of selected items' 77 | ); 78 | }); 79 | }); 80 | 81 | test('Select single', function(assert) { 82 | var isChecked = checkMarkVisibilityTest('single-picker'); 83 | 84 | assert.expect(4); 85 | 86 | visit('/test-select-picker') 87 | 88 | .click('#single-picker button.dropdown-toggle') 89 | .click('#single-picker .dropdown-menu li>a:eq(0)') 90 | 91 | .then(function() { 92 | assert.ok(isChecked(0), 'first item should show a check mark'); 93 | assert.ok(!isChecked(1), 'second item should not show a check mark'); 94 | }) 95 | 96 | .click('#single-picker .dropdown-menu li>a:eq(1)') 97 | 98 | .then(function() { 99 | assert.ok(!isChecked(0), 'first item should not show a check mark'); 100 | assert.ok(isChecked(1), 'second item should show a check mark'); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/components/list-picker-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent } from 'ember-qunit'; 2 | import commonPickerTests from '../../common/select-picker'; 3 | 4 | moduleForComponent('list-picker', { 5 | // specify the other units that are required for this test 6 | // needs: ['component:foo', 'helper:bar'] 7 | }); 8 | 9 | commonPickerTests(); 10 | -------------------------------------------------------------------------------- /tests/unit/components/select-picker-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent } from 'ember-qunit'; 2 | import commonPickerTests from '../../common/select-picker'; 3 | 4 | moduleForComponent('select-picker', { 5 | // specify the other units that are required for this test 6 | // needs: ['component:foo', 'helper:bar'] 7 | }); 8 | 9 | commonPickerTests(); 10 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sukima/ember-cli-select-picker/ea014668ae740d2f72375e91aa3de3e9e2e48b80/vendor/.gitkeep -------------------------------------------------------------------------------- /vendor/select-picker.css: -------------------------------------------------------------------------------- 1 | /* Fix to enable Ember actions 2 | * http://stackoverflow.com/a/17490775/227176 3 | */ 4 | .select-picker .dropdown-menu li a { 5 | cursor: pointer; 6 | } 7 | 8 | .select-picker .bs-select span.check-mark { 9 | position: absolute; 10 | display: inline-block; 11 | top: inherit; 12 | right: 10px; 13 | margin-top: 5px; 14 | } 15 | 16 | .select-picker .dropdown-menu { 17 | padding-right: 5px; 18 | padding-left: 5px; 19 | overflow-y: auto; 20 | max-height: 400px; 21 | } 22 | 23 | .select-picker .dropdown-menu .dropdown-header { 24 | padding-left: 10px; 25 | font-size: 14px; 26 | } 27 | 28 | .select-picker .select-all-none { 29 | margin-top: 5px; 30 | } 31 | 32 | .select-picker .select-all-none button { 33 | width: 50%; 34 | text-align: center; 35 | } 36 | --------------------------------------------------------------------------------