Last active
August 24, 2016 21:57
-
-
Save lennyburdette/3a65587a3029ad2526a7236141f00a84 to your computer and use it in GitHub Desktop.
filtered select
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| /* | |
| This component manages: | |
| * Converting an arbitrary array of objects in options (objects with a value and label) | |
| for inner select component. | |
| * The filter text and the filtered list of options. | |
| * The proposed option, held temporarily until the selection is committed or discarded. | |
| * Converting the option back into the original value in the action up to the calling comment. | |
| */ | |
| export default Ember.Component.extend({ | |
| classNames: [ | |
| 'filtered-select-container' | |
| ], | |
| label: Ember.computed('filter', 'proposedOption', 'selectedOption', function() { | |
| /*const proposedOption = this.get('proposedOption'); | |
| if (proposedOption) { | |
| return proposedOption.label; | |
| }*/ | |
| const filter = this.get('filter'); | |
| if (filter && filter.length) { | |
| return filter; | |
| } | |
| const selectedOption = this.get('selectedOption'); | |
| if (selectedOption) { | |
| return selectedOption.label; | |
| } | |
| }), | |
| optionsWithLabels: Ember.computed('options', 'labelPath', function() { | |
| const labelPath = this.get('labelPath') | |
| return this.get('options').map(value => ({ | |
| label: Ember.get(value, labelPath), | |
| value | |
| })); | |
| }), | |
| selectedOption: Ember.computed('optionsWithLabels', 'value', function() { | |
| return this.get('optionsWithLabels').findBy('value', this.get('value')); | |
| }), | |
| filteredOptions: Ember.computed('optionsWithLabels', 'filter', function() { | |
| let filtered = this.get('optionsWithLabels'); | |
| const filter = this.get('filter'); | |
| if (filter && filter.length) { | |
| const pattern = new RegExp(`^${filter.toLowerCase()}`); | |
| filtered = filtered.filter(value => pattern.test(value.label.toLowerCase())); | |
| if (this.get('allowNew')) { | |
| const newOption = { additive: true, label: filter, value: filter }; | |
| filtered.unshift(newOption); | |
| this.set('proposedOption', newOption); | |
| } | |
| } | |
| return filtered; | |
| }), | |
| actions: { | |
| filterChanged(event) { | |
| const lowerCase = event.target.value.trim().toLowerCase(); | |
| const exactMatch = this.get('optionsWithLabels').find(value => | |
| value.label.toLowerCase() === lowerCase | |
| ); | |
| if (exactMatch && !this.get('allowNew')) { | |
| this.set('proposedOption', exactMatch); | |
| return; | |
| } | |
| this.set('filter', event.target.value); | |
| }, | |
| filterCanceled() { | |
| this.set('filter', null); | |
| this.set('proposedOption', null); | |
| }, | |
| optionProposed(option) { | |
| this.set('proposedOption', option); | |
| }, | |
| optionSelected(option) { | |
| this.set('filter', null); | |
| this.set('proposedOption', null); | |
| this.sendAction('action', option.value); | |
| } | |
| } | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| export default Ember.Controller.extend({ | |
| states: Ember.inject.service(), | |
| log: Ember.computed(() => Ember.ArrayProxy.create({ content: [] })), | |
| init(...args) { | |
| this._super(...args); | |
| this.set('selectedState', this.get('states.all')[20]); | |
| }, | |
| actions: { | |
| change(value) { | |
| this.set('selectedState', value); | |
| } | |
| } | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| import truthConvert from '../utils/truth-convert'; | |
| export function and(params) { | |
| for (var i=0, len=params.length; i<len; i++) { | |
| if (truthConvert(params[i]) === false) { | |
| return params[i]; | |
| } | |
| } | |
| return params[params.length-1]; | |
| } | |
| export default Ember.Helper.helper(and); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| export function eq(params) { | |
| return params[0] === params[1]; | |
| } | |
| export default Ember.Helper.helper(eq); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| import truthConvert from '../utils/truth-convert'; | |
| export function not(params) { | |
| for (var i=0, len=params.length; i<len; i++) { | |
| if (truthConvert(params[i]) === true) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| export default Ember.Helper.helper(not); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| import truthConvert from '../utils/truth-convert'; | |
| export function or(params) { | |
| for (var i=0, len=params.length; i<len; i++) { | |
| if (truthConvert(params[i]) === true) { | |
| return params[i]; | |
| } | |
| } | |
| return params[params.length-1]; | |
| } | |
| export default Ember.Helper.helper(or); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| export default Ember.Service.extend({ | |
| all: [ | |
| { | |
| "name": "Alabama", | |
| "abbreviation": "AL" | |
| }, | |
| { | |
| "name": "Alaska", | |
| "abbreviation": "AK" | |
| }, | |
| { | |
| "name": "American Samoa", | |
| "abbreviation": "AS" | |
| }, | |
| { | |
| "name": "Arizona", | |
| "abbreviation": "AZ" | |
| }, | |
| { | |
| "name": "Arkansas", | |
| "abbreviation": "AR" | |
| }, | |
| { | |
| "name": "California", | |
| "abbreviation": "CA" | |
| }, | |
| { | |
| "name": "Colorado", | |
| "abbreviation": "CO" | |
| }, | |
| { | |
| "name": "Connecticut", | |
| "abbreviation": "CT" | |
| }, | |
| { | |
| "name": "Delaware", | |
| "abbreviation": "DE" | |
| }, | |
| { | |
| "name": "District Of Columbia", | |
| "abbreviation": "DC" | |
| }, | |
| { | |
| "name": "Federated States Of Micronesia", | |
| "abbreviation": "FM" | |
| }, | |
| { | |
| "name": "Florida", | |
| "abbreviation": "FL" | |
| }, | |
| { | |
| "name": "Georgia", | |
| "abbreviation": "GA" | |
| }, | |
| { | |
| "name": "Guam", | |
| "abbreviation": "GU" | |
| }, | |
| { | |
| "name": "Hawaii", | |
| "abbreviation": "HI" | |
| }, | |
| { | |
| "name": "Idaho", | |
| "abbreviation": "ID" | |
| }, | |
| { | |
| "name": "Illinois", | |
| "abbreviation": "IL" | |
| }, | |
| { | |
| "name": "Indiana", | |
| "abbreviation": "IN" | |
| }, | |
| { | |
| "name": "Iowa", | |
| "abbreviation": "IA" | |
| }, | |
| { | |
| "name": "Kansas", | |
| "abbreviation": "KS" | |
| }, | |
| { | |
| "name": "Kentucky", | |
| "abbreviation": "KY" | |
| }, | |
| { | |
| "name": "Louisiana", | |
| "abbreviation": "LA" | |
| }, | |
| { | |
| "name": "Maine", | |
| "abbreviation": "ME" | |
| }, | |
| { | |
| "name": "Marshall Islands", | |
| "abbreviation": "MH" | |
| }, | |
| { | |
| "name": "Maryland", | |
| "abbreviation": "MD" | |
| }, | |
| { | |
| "name": "Massachusetts", | |
| "abbreviation": "MA" | |
| }, | |
| { | |
| "name": "Michigan", | |
| "abbreviation": "MI" | |
| }, | |
| { | |
| "name": "Minnesota", | |
| "abbreviation": "MN" | |
| }, | |
| { | |
| "name": "Mississippi", | |
| "abbreviation": "MS" | |
| }, | |
| { | |
| "name": "Missouri", | |
| "abbreviation": "MO" | |
| }, | |
| { | |
| "name": "Montana", | |
| "abbreviation": "MT" | |
| }, | |
| { | |
| "name": "Nebraska", | |
| "abbreviation": "NE" | |
| }, | |
| { | |
| "name": "Nevada", | |
| "abbreviation": "NV" | |
| }, | |
| { | |
| "name": "New Hampshire", | |
| "abbreviation": "NH" | |
| }, | |
| { | |
| "name": "New Jersey", | |
| "abbreviation": "NJ" | |
| }, | |
| { | |
| "name": "New Mexico", | |
| "abbreviation": "NM" | |
| }, | |
| { | |
| "name": "New York", | |
| "abbreviation": "NY" | |
| }, | |
| { | |
| "name": "North Carolina", | |
| "abbreviation": "NC" | |
| }, | |
| { | |
| "name": "North Dakota", | |
| "abbreviation": "ND" | |
| }, | |
| { | |
| "name": "Northern Mariana Islands", | |
| "abbreviation": "MP" | |
| }, | |
| { | |
| "name": "Ohio", | |
| "abbreviation": "OH" | |
| }, | |
| { | |
| "name": "Oklahoma", | |
| "abbreviation": "OK" | |
| }, | |
| { | |
| "name": "Oregon", | |
| "abbreviation": "OR" | |
| }, | |
| { | |
| "name": "Palau", | |
| "abbreviation": "PW" | |
| }, | |
| { | |
| "name": "Pennsylvania", | |
| "abbreviation": "PA" | |
| }, | |
| { | |
| "name": "Puerto Rico", | |
| "abbreviation": "PR" | |
| }, | |
| { | |
| "name": "Rhode Island", | |
| "abbreviation": "RI" | |
| }, | |
| { | |
| "name": "South Carolina", | |
| "abbreviation": "SC" | |
| }, | |
| { | |
| "name": "South Dakota", | |
| "abbreviation": "SD" | |
| }, | |
| { | |
| "name": "Tennessee", | |
| "abbreviation": "TN" | |
| }, | |
| { | |
| "name": "Texas", | |
| "abbreviation": "TX" | |
| }, | |
| { | |
| "name": "Utah", | |
| "abbreviation": "UT" | |
| }, | |
| { | |
| "name": "Vermont", | |
| "abbreviation": "VT" | |
| }, | |
| { | |
| "name": "Virgin Islands", | |
| "abbreviation": "VI" | |
| }, | |
| { | |
| "name": "Virginia", | |
| "abbreviation": "VA" | |
| }, | |
| { | |
| "name": "Washington", | |
| "abbreviation": "WA" | |
| }, | |
| { | |
| "name": "West Virginia", | |
| "abbreviation": "WV" | |
| }, | |
| { | |
| "name": "Wisconsin", | |
| "abbreviation": "WI" | |
| }, | |
| { | |
| "name": "Wyoming", | |
| "abbreviation": "WY" | |
| } | |
| ] | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| const { computed } = Ember; | |
| const KEY_CODE_TAB = 9; | |
| const KEY_CODE_ENTER = 13; | |
| const KEY_CODE_ESC = 27; | |
| const KEY_CODE_UP = 38; | |
| const KEY_CODE_DOWN = 40; | |
| /* | |
| This component manages: | |
| * The dropdown state: open or closed. | |
| * Focus, click, and keyboard events. | |
| Fun note! This component never calls this.set() except for isOpen. | |
| */ | |
| export default Ember.Component.extend({ | |
| classNames: ['filtered-select'], | |
| classNameBindings: [ | |
| 'isOpen:filtered-select--is-active', | |
| 'dropdownMode:filtered-select--dropdown' | |
| ], | |
| // passed in | |
| possibleOptions: null, | |
| proposedOption: null, | |
| selectedOption: null, | |
| autoFocus: false, | |
| dropdownMode: true, | |
| label: null, | |
| // internal state | |
| isOpen: false, | |
| // lifecycle methods | |
| didInsertElement(...args) { | |
| this._super(...args); | |
| if (this.get('autoFocus')) { | |
| this.$('input:text').focus(); | |
| } | |
| }, | |
| click() { | |
| this.openDropdown(); | |
| }, | |
| // TODO should we do this? | |
| mouseLeave() { | |
| this.highlight(null); | |
| }, | |
| // event handlers | |
| keyDown(e) { | |
| switch (e.keyCode) { | |
| case KEY_CODE_DOWN: | |
| this.highlightNext(e); | |
| Ember.run.scheduleOnce('afterRender', this, this.trackHighlighted); | |
| return; | |
| case KEY_CODE_UP: | |
| this.highlightPrevious(e); | |
| Ember.run.scheduleOnce('afterRender', this, this.trackHighlighted); | |
| return; | |
| case KEY_CODE_ENTER: | |
| return this.commitHightlightedSelection(); | |
| case KEY_CODE_TAB: | |
| return this.commitHightlightedSelection(); | |
| case KEY_CODE_ESC: | |
| return this.revert(); | |
| default: | |
| this.openDropdown(); | |
| } | |
| }, | |
| // dropdown management | |
| openDropdown() { | |
| if (this.get('isOpen')) { | |
| return; | |
| } | |
| this.set('isOpen', true); | |
| this.$(document).on(`click.filteredSelect${this.get('elementId')}`, event => { | |
| const target = this.$(event.target); | |
| if (target && !target.closest(this.$()).length) { | |
| return this.commitHightlightedSelection(); | |
| } | |
| }); | |
| Ember.run.scheduleOnce('afterRender', this, this.trackHighlighted); | |
| }, | |
| closeDropdown() { | |
| this.set('isOpen', false); | |
| this.$(document).off(`click.filteredSelect${this.get('elementId')}`); | |
| }, | |
| // selection management | |
| commitSelection(option) { | |
| if (option && option !== this.get('selectedOption')) { | |
| this.select(option); | |
| } | |
| this.closeDropdown(); | |
| this.$('input:text').blur(); | |
| }, | |
| commitHightlightedSelection() { | |
| this.commitSelection(this.get('proposedOption')); | |
| }, | |
| // proposed selection management | |
| highlightedIndex: Ember.computed('possibleOptions', 'proposedOption', 'selectedOption', function() { | |
| const proposed = this.get('proposedOption'); | |
| if (proposed) { | |
| return this.get('possibleOptions').indexOf(proposed); | |
| } | |
| const selected = this.get('selectedOption'); | |
| if (selected) { | |
| return this.get('possibleOptions').indexOf(selected); | |
| } | |
| return -1; | |
| }), | |
| highlight(value) { | |
| this.propose(value); | |
| if (value) { | |
| Ember.run.scheduleOnce('afterRender', () => this.$('input:text').get(0).select()); | |
| } | |
| }, | |
| highlightIndex(index) { | |
| this.highlight(this.get('possibleOptions')[index]); | |
| }, | |
| highlightNext(e) { | |
| this.openDropdown(); | |
| if (this.get('highlightedIndex') < 0) { | |
| this.highlightIndex(0); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| return | |
| } | |
| const nextIndex = this.get('highlightedIndex') + 1; | |
| if (nextIndex < this.get('possibleOptions.length')) { | |
| this.highlightIndex(nextIndex); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| Ember.run.scheduleOnce('afterRender', this, this.trackHighlighted); | |
| } | |
| }, | |
| highlightPrevious(e) { | |
| const previousIndex = this.get('highlightedIndex') - 1; | |
| if (previousIndex > -1) { | |
| this.highlightIndex(previousIndex); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| Ember.run.scheduleOnce('afterRender', this, this.trackHighlighted); | |
| } | |
| }, | |
| trackHighlighted() { | |
| let index = this.get('highlightedIndex'); | |
| if (!this.get('isOpen') || index < 0) { | |
| return; | |
| } | |
| const $highlighted = this.$(`.filtered-select__option:eq(${index})`); | |
| if ($highlighted.length) { | |
| $highlighted.get(0).scrollIntoView(false); | |
| } | |
| }, | |
| revert() { | |
| this.closeDropdown(); | |
| this.cancel(); | |
| }, | |
| actions: { | |
| highlightOption(option) { | |
| if (option && option !== this.get('proposedOption')) { | |
| this.highlight(option); | |
| } | |
| }, | |
| optionClicked(option) { | |
| this.commitSelection(option); | |
| }, | |
| open() { | |
| this.openDropdown(); | |
| this.$('input:text').get(0).select(); | |
| } | |
| } | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: helvetica; | |
| } | |
| .filtered-select-container { | |
| position: relative; | |
| } | |
| .filtered-select__input, | |
| .filtered-select__prompt { | |
| position: relative; | |
| min-height: 47px; | |
| /*width: 100%;*/ | |
| padding: 13px 24px; | |
| border: 1px solid #333; | |
| border-radius: 0; | |
| background: transparent; | |
| font-size: 14px; | |
| } | |
| .filtered-select__input:focus { | |
| outline: 0 | |
| } | |
| .filtered-select__prompt { | |
| display: none; | |
| position: absolute; | |
| top: 0; | |
| cursor: pointer; | |
| color: #333; | |
| line-height: 1.5; | |
| } | |
| .filtered-select--has-prompt > .filtered-select__prompt { | |
| display: block; | |
| } | |
| .filtered-select--is-active .form-field__caret, | |
| .filtered-select--is-active .filtered-select__prompt { | |
| display: none; | |
| } | |
| .filtered-select__input { | |
| z-index: 5; | |
| } | |
| .filtered-select--dropdown > .filtered-select__input { | |
| cursor: pointer; | |
| } | |
| .filtered-select--has-prompt > .filtered-select__input { | |
| opacity: 0; | |
| } | |
| .filtered-select--is-active > .filtered-select__input { | |
| cursor: inherit; | |
| } | |
| .filtered-select--dropdown__input { | |
| /*border-color: transparent;*/ | |
| color: #333; | |
| } | |
| .filtered-select--is-active > .filtered-select--dropdown__input { | |
| position: absolute; | |
| /*width: 345px;*/ | |
| border: 1px solid lightblue; | |
| border-width: 1px 1px 0 1px; | |
| color: gray; | |
| cursor: inherit; | |
| min-height: 48px; | |
| margin-top: -1px; | |
| opacity: 1; | |
| transition: border-color 0.2s ease-in; | |
| } | |
| .filtered-select--is-fluid-width > .filtered-select--dropdown__input { | |
| /*width: 100%;*/ | |
| } | |
| .filtered-select__popover { | |
| position: absolute; | |
| z-index: 600; | |
| max-height: 376px; | |
| /*width: 345px;*/ | |
| margin-top: -5px; | |
| margin-left: 5px; | |
| border: 1px solid gray; | |
| border-width: 0 1px; | |
| background-color: white; | |
| opacity: 0; | |
| overflow: auto; | |
| visibility: hidden; | |
| } | |
| .filtered-select--is-fluid-width > .filtered-select__popover { | |
| width: 100%; | |
| } | |
| .filtered-select--dropdown__popover { | |
| top: 48px; | |
| margin: 0; | |
| border: 1px solid lightblue; | |
| border-width: 0 1px 1px 1px; | |
| } | |
| .filtered-select__popover--is-active { | |
| opacity: 1; | |
| visibility: visible; | |
| transition: visibility 0s linear, opacity 0.2s ease-in; | |
| } | |
| .filtered-select__null-state, | |
| .filtered-select__option { | |
| min-height: 47px; | |
| padding: 13px 24px; | |
| line-height: 1.5; | |
| } | |
| .filtered-select__null-state:hover, | |
| .filtered-select__option:hover { | |
| cursor: pointer; | |
| } | |
| .filtered-select__option { | |
| color: #ddd; | |
| } | |
| .filtered-select__option:first-of-type { | |
| border-top: 1px solid gray; | |
| } | |
| .filtered-select__option:last-of-type { | |
| border-bottom: 1px solid gray; | |
| } | |
| .filtered-select--dropdown__popover > .filtered-select__option { | |
| color: gray; | |
| border: 0; | |
| } | |
| .filtered-select__option--is-highlighted { | |
| background-color: lightblue; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "version": "0.10.1", | |
| "EmberENV": { | |
| "FEATURES": {} | |
| }, | |
| "options": { | |
| "use_pods": false, | |
| "enable-testing": false | |
| }, | |
| "dependencies": { | |
| "jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js", | |
| "ember": "1.13.13", | |
| "ember-data": "1.13.15", | |
| "ember-template-compiler": "1.13.13" | |
| }, | |
| "addons": {} | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Ember from 'ember'; | |
| export default function truthConvert(result) { | |
| var truthy = result && Ember.get(result, 'isTruthy'); | |
| if (typeof truthy === 'boolean') { return truthy; } | |
| if (Ember.isArray(result)) { | |
| return Ember.get(result, 'length') !== 0; | |
| } else { | |
| return !!result; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment