From 88b5b641ef178ef2a7c7987cf9e4c71297d99025 Mon Sep 17 00:00:00 2001 From: Petter Kjelkenes Date: Mon, 13 Jul 2015 22:13:55 +0200 Subject: [PATCH 01/39] First commit ( not done ). --- addon/components/paper-autocomplete-input.js | 65 +++++ addon/components/paper-autocomplete-item.js | 5 + addon/components/paper-autocomplete-wrap.js | 29 ++ addon/components/paper-autocomplete.js | 93 +++++++ app/components/paper-autocomplete-input.js | 1 + app/components/paper-autocomplete-item.js | 1 + app/components/paper-autocomplete-wrap.js | 1 + app/components/paper-autocomplete.js | 1 + app/styles/ember-paper.scss | 1 + app/styles/paper-autocomplete.scss | 253 ++++++++++++++++++ .../components/paper-autocomplete-item.hbs | 1 + .../components/paper-autocomplete-wrap.hbs | 78 ++++++ .../components/paper-autocomplete.hbs | 9 + tests/dummy/app/controllers/autocomplete.js | 249 +++++++++++++++++ tests/dummy/app/router.js | 1 + tests/dummy/app/templates/application.hbs | 1 + tests/dummy/app/templates/autocomplete.hbs | 16 ++ .../paper-autocomplete-input-test.js | 19 ++ .../components/paper-autocomplete-test.js | 19 ++ .../paper-autocomplete-wrap-test.js | 19 ++ 20 files changed, 862 insertions(+) create mode 100644 addon/components/paper-autocomplete-input.js create mode 100644 addon/components/paper-autocomplete-item.js create mode 100644 addon/components/paper-autocomplete-wrap.js create mode 100644 addon/components/paper-autocomplete.js create mode 100644 app/components/paper-autocomplete-input.js create mode 100644 app/components/paper-autocomplete-item.js create mode 100644 app/components/paper-autocomplete-wrap.js create mode 100644 app/components/paper-autocomplete.js create mode 100644 app/styles/paper-autocomplete.scss create mode 100644 app/templates/components/paper-autocomplete-item.hbs create mode 100644 app/templates/components/paper-autocomplete-wrap.hbs create mode 100644 app/templates/components/paper-autocomplete.hbs create mode 100644 tests/dummy/app/controllers/autocomplete.js create mode 100644 tests/dummy/app/templates/autocomplete.hbs create mode 100644 tests/unit/components/paper-autocomplete-input-test.js create mode 100644 tests/unit/components/paper-autocomplete-test.js create mode 100644 tests/unit/components/paper-autocomplete-wrap-test.js diff --git a/addon/components/paper-autocomplete-input.js b/addon/components/paper-autocomplete-input.js new file mode 100644 index 000000000..9e6f215a5 --- /dev/null +++ b/addon/components/paper-autocomplete-input.js @@ -0,0 +1,65 @@ +import Ember from 'ember'; +import BaseFocusable from './base-focusable'; +import constants from '../utils/constants'; + +export default Ember.TextField.extend({ + type: 'search', + + handleSearchText () { + var parent = this.get("parent"); + var text = parent.get('searchText').toLowerCase(); + + parent.set('index', parent.getDefaultIndex()); + + var items = parent.get('items'); + var lookupKey = parent.get('lookupKey'); + var suggestions = items.filter(function (item) { + var search = item[lookupKey].toLowerCase(); + return search.indexOf(text) === 0; + }); + + parent.set('suggestions', suggestions); + + }, + + searchTextObserver: Ember.observer('parent.searchText',function() { + var text = this.get('parent').get('searchText'); + if (typeof text === 'undefined') return; + + var wait = parseInt(this.get("parent").get('delay'), 10) || 0; + Ember.run.debounce(this, this.handleSearchText, wait); + }), + + keyDown (event) { + var autocomplete = this.get("parent"); + switch (event.keyCode) { + case constants.KEYCODE.DOWN_ARROW: + if (autocomplete.get('loading')) return; + event.preventDefault(); + autocomplete.set('index', Math.min(autocomplete.get('index') + 1, autocomplete.get('matches').length - 1)); + autocomplete.updateScroll(); + autocomplete.updateMessages(); + break; + case constants.KEYCODE.UP_ARROW: + if (autocomplete.get('loading')) return; + event.preventDefault(); + autocomplete.set('index', autocomplete.get('index') < 0 ? autocomplete.get('matches').length - 1 : Math.max(0, autocomplete.get('index') - 1)); + autocomplete.updateScroll(); + autocomplete.updateMessages(); + break; + case constants.KEYCODE.TAB: + case constants.KEYCODE.ENTER: + if (autocomplete.get('hidden') || autocomplete.get('loading') || autocomplete.get('index') < 0 || autocomplete.get('matches').length < 1) return; + event.preventDefault(); + select(autocomplete.get('index')); + break; + case constants.KEYCODE.ESCAPE: + autocomplete.set('matches', Ember.A([])); + autocomplete.set('hidden', true); + autocomplete.set('index', autocomplete.getDefaultIndex()); + break; + default: + } + } + +}); diff --git a/addon/components/paper-autocomplete-item.js b/addon/components/paper-autocomplete-item.js new file mode 100644 index 000000000..c24ebc4ec --- /dev/null +++ b/addon/components/paper-autocomplete-item.js @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'li' +}); diff --git a/addon/components/paper-autocomplete-wrap.js b/addon/components/paper-autocomplete-wrap.js new file mode 100644 index 000000000..2d8b609cf --- /dev/null +++ b/addon/components/paper-autocomplete-wrap.js @@ -0,0 +1,29 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'md-autocomplete-wrap', + classBindings: ['notFloating:md-whiteframe-z1', 'notHidden:md-menu-showing'], + attributeBindings: ['role', 'layoutAttr:layout'], + role: 'listbox', + layoutAttr: 'row', + + + notFloating: Ember.computed.not('parent.floatingLabel'), + notHidden: Ember.computed.not('parent.hidden'), + + actions: { + clear: function () { + this.get("parent").set('searchText', ''); + } + }, + + didInsertElement () { + var ul = this.$().find('ul'); + ul.appendTo('body'); + this.get('parent').set('ulContainer', ul); + }, + didDestroyElement () { + this.get('parent').get('ulContainer').remove(); + } + +}); diff --git a/addon/components/paper-autocomplete.js b/addon/components/paper-autocomplete.js new file mode 100644 index 000000000..90243fa2b --- /dev/null +++ b/addon/components/paper-autocomplete.js @@ -0,0 +1,93 @@ +import Ember from 'ember'; + +var ITEM_HEIGHT = 41, + MAX_HEIGHT = 5.5 * ITEM_HEIGHT, + MENU_PADDING = 8; + + +export default Ember.Component.extend({ + tagName: 'md-autocomplete', + itemTemplate: {isItemTemplate: true}, + noItemsTemplate: {isNotFoundTemplate: true}, + classNames: ['md-default-theme'], + + suggestions: Ember.A([]), + loading: false, + hidden: true, + index: null, + messages: [], + isDisabled: null, + isRequired: null, + lookupKey: null, + + + + + getDefaultIndex () { + return this.get("autoselect") ? 0 : -1; + }, + + updateScroll () {/* + if (!elements.li[ctrl.index]) return; + var li = elements.li[ctrl.index], + top = li.offsetTop, + bot = top + li.offsetHeight, + hgt = elements.ul.clientHeight; + if (top < elements.ul.scrollTop) { + elements.ul.scrollTop = top; + } else if (bot > elements.ul.scrollTop + hgt) { + elements.ul.scrollTop = bot - hgt; + }*/ + }, + + updateMessages () { + /*getCurrentDisplayValue().then(function(msg) { + ctrl.messages = [ getCountMessage(), msg ]; + });*/ + }, + + positionDropdown () { + var hrect = this.$().find('md-autocomplete-wrap:first')[0].getBoundingClientRect(), + vrect = hrect, + root = document.body.getBoundingClientRect(), + top = vrect.bottom - root.top, + bot = root.bottom - vrect.top, + left = hrect.left - root.left, + width = hrect.width, + styles = { + left: left + 'px', + minWidth: width + 'px', + maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px' + }, + ul = this.get('ulContainer'); + if (top > bot && root.height - hrect.bottom - MENU_PADDING < MAX_HEIGHT) { + styles.top = 'auto'; + styles.bottom = bot + 'px'; + styles.maxHeight = Math.min(MAX_HEIGHT, hrect.top - root.top - MENU_PADDING) + 'px'; + } else { + styles.top = top + 'px'; + styles.bottom = 'auto'; + styles.maxHeight = Math.min(MAX_HEIGHT, root.bottom - hrect.bottom - MENU_PADDING) + 'px'; + } + ul.css(styles); + correctHorizontalAlignment(); + + /** + * Makes sure that the menu doesn't go off of the screen on either side. + */ + function correctHorizontalAlignment () { + var dropdown = ul[0].getBoundingClientRect(), + styles = {}; + if (dropdown.right > root.right - MENU_PADDING) { + styles.left = (hrect.right - dropdown.width) + 'px'; + } + ul.css(styles); + } + }, + + + observeSuggestions: Ember.observer('suggestions', function () { + this.positionDropdown(); + }) + +}); diff --git a/app/components/paper-autocomplete-input.js b/app/components/paper-autocomplete-input.js new file mode 100644 index 000000000..a74fed5a0 --- /dev/null +++ b/app/components/paper-autocomplete-input.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-autocomplete-input'; \ No newline at end of file diff --git a/app/components/paper-autocomplete-item.js b/app/components/paper-autocomplete-item.js new file mode 100644 index 000000000..7641a4be4 --- /dev/null +++ b/app/components/paper-autocomplete-item.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-autocomplete-item'; diff --git a/app/components/paper-autocomplete-wrap.js b/app/components/paper-autocomplete-wrap.js new file mode 100644 index 000000000..5af82bf94 --- /dev/null +++ b/app/components/paper-autocomplete-wrap.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-autocomplete-wrap'; \ No newline at end of file diff --git a/app/components/paper-autocomplete.js b/app/components/paper-autocomplete.js new file mode 100644 index 000000000..b88975292 --- /dev/null +++ b/app/components/paper-autocomplete.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-autocomplete'; \ No newline at end of file diff --git a/app/styles/ember-paper.scss b/app/styles/ember-paper.scss index 893dd81ac..c60a10d28 100644 --- a/app/styles/ember-paper.scss +++ b/app/styles/ember-paper.scss @@ -75,6 +75,7 @@ @import 'paper-icon'; @import 'paper-slider'; @import 'paper-subheader'; +@import 'paper-autocomplete'; @import 'paper-sidenav'; @import 'paper-backdrop'; diff --git a/app/styles/paper-autocomplete.scss b/app/styles/paper-autocomplete.scss new file mode 100644 index 000000000..334590c8b --- /dev/null +++ b/app/styles/paper-autocomplete.scss @@ -0,0 +1,253 @@ +$autocomplete-option-height: 48px; +$input-container-padding: 2px !default; +$input-error-height: 24px !default; + +@keyframes md-autocomplete-list-out { + 0% { + animation-timing-function: linear; + } + 50% { + opacity: 0; + height: 40px; + animation-timing-function: ease-in; + } + 100% { + height: 0; + opacity: 0; + } +} +@keyframes md-autocomplete-list-in { + 0% { + opacity: 0; + height: 0; + animation-timing-function: ease-out; + } + 50% { + opacity: 0; + height: 40px; + } + 100% { + opacity: 1; + height: 40px; + } +} +md-autocomplete { + border-radius: 2px; + display: block; + height: 40px; + position: relative; + overflow: visible; + min-width: 190px; + &[disabled] { + input { + cursor: not-allowed; + } + } + &[md-floating-label] { + padding-bottom: $input-container-padding + $input-error-height; + border-radius: 0; + background: transparent; + height: auto; + md-input-container { + padding-bottom: 0; + } + md-autocomplete-wrap { + height: auto; + } + button { + position: absolute; + top: auto; + bottom: 0; + right: 0; + width: 30px; + height: 30px; + } + } + md-autocomplete-wrap { + display: block; + position: relative; + overflow: visible; + height: 40px; + &.md-menu-showing { + z-index: $z-index-backdrop + 1; + } + md-progress-linear[md-mode=indeterminate] { + position: absolute; + bottom: 0; left: 0; width: 100%; + height: 3px; + transition: none; + + .md-container { + transition: none; + top: auto; + height: 3px; + } + &.ng-enter { + transition: opacity 0.15s linear; + &.ng-enter-active { + opacity: 1; + } + } + &.ng-leave { + transition: opacity 0.15s linear; + &.ng-leave-active { + opacity: 0; + } + } + } + } + input:not(.md-input) { + width: 100%; + box-sizing: border-box; + border: none; + box-shadow: none; + padding: 0 15px; + font-size: 14px; + line-height: 40px; + height: 40px; + outline: none; + background: transparent; + &::-ms-clear { + display: none; + } + } + button { + position: relative; + line-height: 20px; + text-align: center; + width: 30px; + height: 30px; + cursor: pointer; + border: none; + border-radius: 50%; + padding: 0; + font-size: 12px; + background: transparent; + margin: auto 5px; + &:after { + content: ''; + position: absolute; + top: -6px; + right: -6px; + bottom: -6px; + left: -6px; + border-radius: 50%; + transform: scale(0); + opacity: 0; + transition: $swift-ease-out; + } + &:focus { + outline: none; + + &:after { + transform: scale(1); + opacity: 1; + } + } + md-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate3d(-50%, -50%, 0) scale(0.9); + path { + stroke-width: 0; + } + } + &.ng-enter { + transform: scale(0); + transition: transform 0.15s ease-out; + &.ng-enter-active { + transform: scale(1); + } + } + &.ng-leave { + transition: transform 0.15s ease-out; + &.ng-leave-active { + transform: scale(0); + } + } + } + @media screen and (-ms-high-contrast: active) { + $border-color: #fff; + + input { + border: 1px solid $border-color; + } + li:focus { + color: #fff; + } + } +} +.md-autocomplete-suggestions { + position: absolute; + margin: 0; + list-style: none; + padding: 0; + overflow: auto; + max-height: 41px * 5.5; + z-index: $z-index-tooltip; + li { + cursor: pointer; + font-size: 14px; + overflow: hidden; + padding: 0 15px; + line-height: $autocomplete-option-height; + height: $autocomplete-option-height; + transition: background 0.15s linear; + margin: 0; + white-space: nowrap; + text-overflow: ellipsis; + &.ng-enter, + &.ng-hide-remove { + transition: none; + animation: md-autocomplete-list-in 0.2s; + } + &.ng-leave, + &.ng-hide-add { + transition: none; + animation: md-autocomplete-list-out 0.2s; + } + + &:focus { + outline: none; + } + + } +} +@media screen and (-ms-high-contrast: active) { + md-autocomplete, + .md-autocomplete-suggestions { + border: 1px solid #fff; + } +} + +//THEME +md-autocomplete.md-#{$theme-name}-theme { + background: color($background, '50'); + &[disabled] { + background: color($background, '100'); + } + button { + md-icon { + path { + fill: color($background, '600'); + } + } + &:after { + background: color($background, '600'); + } + } +} +.md-autocomplete-suggestions.md-#{$theme-name}-theme { + background: color($background, '50'); + li { + color: color($background, '900'); + .highlight { + color: color($background, '600'); + } + &:hover, + &.selected { + background: color($background, '200'); + } + } +} diff --git a/app/templates/components/paper-autocomplete-item.hbs b/app/templates/components/paper-autocomplete-item.hbs new file mode 100644 index 000000000..fb5c4b157 --- /dev/null +++ b/app/templates/components/paper-autocomplete-item.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/app/templates/components/paper-autocomplete-wrap.hbs b/app/templates/components/paper-autocomplete-wrap.hbs new file mode 100644 index 000000000..d59acec6e --- /dev/null +++ b/app/templates/components/paper-autocomplete-wrap.hbs @@ -0,0 +1,78 @@ +{{#if mdFloatingLabel}} + + + +
' + leftover + '
+
+{{else}} + + {{paper-autocomplete-input flex=true + parent=parent + type="search" + autocomplete="off" + placeholder=placeholder + required=parent.isRequired + disabled=parent.isDisabled + value=parent.searchText + aria-haspopup=true + aria-autocomplete="list" + aria-activedescendant=""}} + + + + + {{#if parent.searchText}} + {{#paper-button classNames="md-icon-button" action="clear"}}{{paper-icon icon="close"}}{{/paper-button}} + {{/if}} + +{{/if}} + + + \ No newline at end of file diff --git a/app/templates/components/paper-autocomplete.hbs b/app/templates/components/paper-autocomplete.hbs new file mode 100644 index 000000000..1618c964b --- /dev/null +++ b/app/templates/components/paper-autocomplete.hbs @@ -0,0 +1,9 @@ +{{paper-autocomplete-wrap parent=this}} + + + {{#each messages as |message index|}} + {{#if message}} +

{{message}}

+ {{/if}} + {{/each}} +
\ No newline at end of file diff --git a/tests/dummy/app/controllers/autocomplete.js b/tests/dummy/app/controllers/autocomplete.js new file mode 100644 index 000000000..ff93b72b7 --- /dev/null +++ b/tests/dummy/app/controllers/autocomplete.js @@ -0,0 +1,249 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + items: Ember.A([ + {name: 'Afghanistan', code: 'AF'}, + {name: 'Åland Islands', code: 'AX'}, + {name: 'Albania', code: 'AL'}, + {name: 'Algeria', code: 'DZ'}, + {name: 'American Samoa', code: 'AS'}, + {name: 'AndorrA', code: 'AD'}, + {name: 'Angola', code: 'AO'}, + {name: 'Anguilla', code: 'AI'}, + {name: 'Antarctica', code: 'AQ'}, + {name: 'Antigua and Barbuda', code: 'AG'}, + {name: 'Argentina', code: 'AR'}, + {name: 'Armenia', code: 'AM'}, + {name: 'Aruba', code: 'AW'}, + {name: 'Australia', code: 'AU'}, + {name: 'Austria', code: 'AT'}, + {name: 'Azerbaijan', code: 'AZ'}, + {name: 'Bahamas', code: 'BS'}, + {name: 'Bahrain', code: 'BH'}, + {name: 'Bangladesh', code: 'BD'}, + {name: 'Barbados', code: 'BB'}, + {name: 'Belarus', code: 'BY'}, + {name: 'Belgium', code: 'BE'}, + {name: 'Belize', code: 'BZ'}, + {name: 'Benin', code: 'BJ'}, + {name: 'Bermuda', code: 'BM'}, + {name: 'Bhutan', code: 'BT'}, + {name: 'Bolivia', code: 'BO'}, + {name: 'Bosnia and Herzegovina', code: 'BA'}, + {name: 'Botswana', code: 'BW'}, + {name: 'Bouvet Island', code: 'BV'}, + {name: 'Brazil', code: 'BR'}, + {name: 'British Indian Ocean Territory', code: 'IO'}, + {name: 'Brunei Darussalam', code: 'BN'}, + {name: 'Bulgaria', code: 'BG'}, + {name: 'Burkina Faso', code: 'BF'}, + {name: 'Burundi', code: 'BI'}, + {name: 'Cambodia', code: 'KH'}, + {name: 'Cameroon', code: 'CM'}, + {name: 'Canada', code: 'CA'}, + {name: 'Cape Verde', code: 'CV'}, + {name: 'Cayman Islands', code: 'KY'}, + {name: 'Central African Republic', code: 'CF'}, + {name: 'Chad', code: 'TD'}, + {name: 'Chile', code: 'CL'}, + {name: 'China', code: 'CN'}, + {name: 'Christmas Island', code: 'CX'}, + {name: 'Cocos (Keeling) Islands', code: 'CC'}, + {name: 'Colombia', code: 'CO'}, + {name: 'Comoros', code: 'KM'}, + {name: 'Congo', code: 'CG'}, + {name: 'Congo, The Democratic Republic of the', code: 'CD'}, + {name: 'Cook Islands', code: 'CK'}, + {name: 'Costa Rica', code: 'CR'}, + {name: 'Cote D\'Ivoire', code: 'CI'}, + {name: 'Croatia', code: 'HR'}, + {name: 'Cuba', code: 'CU'}, + {name: 'Cyprus', code: 'CY'}, + {name: 'Czech Republic', code: 'CZ'}, + {name: 'Denmark', code: 'DK'}, + {name: 'Djibouti', code: 'DJ'}, + {name: 'Dominica', code: 'DM'}, + {name: 'Dominican Republic', code: 'DO'}, + {name: 'Ecuador', code: 'EC'}, + {name: 'Egypt', code: 'EG'}, + {name: 'El Salvador', code: 'SV'}, + {name: 'Equatorial Guinea', code: 'GQ'}, + {name: 'Eritrea', code: 'ER'}, + {name: 'Estonia', code: 'EE'}, + {name: 'Ethiopia', code: 'ET'}, + {name: 'Falkland Islands (Malvinas)', code: 'FK'}, + {name: 'Faroe Islands', code: 'FO'}, + {name: 'Fiji', code: 'FJ'}, + {name: 'Finland', code: 'FI'}, + {name: 'France', code: 'FR'}, + {name: 'French Guiana', code: 'GF'}, + {name: 'French Polynesia', code: 'PF'}, + {name: 'French Southern Territories', code: 'TF'}, + {name: 'Gabon', code: 'GA'}, + {name: 'Gambia', code: 'GM'}, + {name: 'Georgia', code: 'GE'}, + {name: 'Germany', code: 'DE'}, + {name: 'Ghana', code: 'GH'}, + {name: 'Gibraltar', code: 'GI'}, + {name: 'Greece', code: 'GR'}, + {name: 'Greenland', code: 'GL'}, + {name: 'Grenada', code: 'GD'}, + {name: 'Guadeloupe', code: 'GP'}, + {name: 'Guam', code: 'GU'}, + {name: 'Guatemala', code: 'GT'}, + {name: 'Guernsey', code: 'GG'}, + {name: 'Guinea', code: 'GN'}, + {name: 'Guinea-Bissau', code: 'GW'}, + {name: 'Guyana', code: 'GY'}, + {name: 'Haiti', code: 'HT'}, + {name: 'Heard Island and Mcdonald Islands', code: 'HM'}, + {name: 'Holy See (Vatican City State)', code: 'VA'}, + {name: 'Honduras', code: 'HN'}, + {name: 'Hong Kong', code: 'HK'}, + {name: 'Hungary', code: 'HU'}, + {name: 'Iceland', code: 'IS'}, + {name: 'India', code: 'IN'}, + {name: 'Indonesia', code: 'ID'}, + {name: 'Iran, Islamic Republic Of', code: 'IR'}, + {name: 'Iraq', code: 'IQ'}, + {name: 'Ireland', code: 'IE'}, + {name: 'Isle of Man', code: 'IM'}, + {name: 'Israel', code: 'IL'}, + {name: 'Italy', code: 'IT'}, + {name: 'Jamaica', code: 'JM'}, + {name: 'Japan', code: 'JP'}, + {name: 'Jersey', code: 'JE'}, + {name: 'Jordan', code: 'JO'}, + {name: 'Kazakhstan', code: 'KZ'}, + {name: 'Kenya', code: 'KE'}, + {name: 'Kiribati', code: 'KI'}, + {name: 'Korea, Democratic People\'S Republic of', code: 'KP'}, + {name: 'Korea, Republic of', code: 'KR'}, + {name: 'Kuwait', code: 'KW'}, + {name: 'Kyrgyzstan', code: 'KG'}, + {name: 'Lao People\'S Democratic Republic', code: 'LA'}, + {name: 'Latvia', code: 'LV'}, + {name: 'Lebanon', code: 'LB'}, + {name: 'Lesotho', code: 'LS'}, + {name: 'Liberia', code: 'LR'}, + {name: 'Libyan Arab Jamahiriya', code: 'LY'}, + {name: 'Liechtenstein', code: 'LI'}, + {name: 'Lithuania', code: 'LT'}, + {name: 'Luxembourg', code: 'LU'}, + {name: 'Macao', code: 'MO'}, + {name: 'Macedonia, The Former Yugoslav Republic of', code: 'MK'}, + {name: 'Madagascar', code: 'MG'}, + {name: 'Malawi', code: 'MW'}, + {name: 'Malaysia', code: 'MY'}, + {name: 'Maldives', code: 'MV'}, + {name: 'Mali', code: 'ML'}, + {name: 'Malta', code: 'MT'}, + {name: 'Marshall Islands', code: 'MH'}, + {name: 'Martinique', code: 'MQ'}, + {name: 'Mauritania', code: 'MR'}, + {name: 'Mauritius', code: 'MU'}, + {name: 'Mayotte', code: 'YT'}, + {name: 'Mexico', code: 'MX'}, + {name: 'Micronesia, Federated States of', code: 'FM'}, + {name: 'Moldova, Republic of', code: 'MD'}, + {name: 'Monaco', code: 'MC'}, + {name: 'Mongolia', code: 'MN'}, + {name: 'Montserrat', code: 'MS'}, + {name: 'Morocco', code: 'MA'}, + {name: 'Mozambique', code: 'MZ'}, + {name: 'Myanmar', code: 'MM'}, + {name: 'Namibia', code: 'NA'}, + {name: 'Nauru', code: 'NR'}, + {name: 'Nepal', code: 'NP'}, + {name: 'Netherlands', code: 'NL'}, + {name: 'Netherlands Antilles', code: 'AN'}, + {name: 'New Caledonia', code: 'NC'}, + {name: 'New Zealand', code: 'NZ'}, + {name: 'Nicaragua', code: 'NI'}, + {name: 'Niger', code: 'NE'}, + {name: 'Nigeria', code: 'NG'}, + {name: 'Niue', code: 'NU'}, + {name: 'Norfolk Island', code: 'NF'}, + {name: 'Northern Mariana Islands', code: 'MP'}, + {name: 'Norway', code: 'NO'}, + {name: 'Oman', code: 'OM'}, + {name: 'Pakistan', code: 'PK'}, + {name: 'Palau', code: 'PW'}, + {name: 'Palestinian Territory, Occupied', code: 'PS'}, + {name: 'Panama', code: 'PA'}, + {name: 'Papua New Guinea', code: 'PG'}, + {name: 'Paraguay', code: 'PY'}, + {name: 'Peru', code: 'PE'}, + {name: 'Philippines', code: 'PH'}, + {name: 'Pitcairn', code: 'PN'}, + {name: 'Poland', code: 'PL'}, + {name: 'Portugal', code: 'PT'}, + {name: 'Puerto Rico', code: 'PR'}, + {name: 'Qatar', code: 'QA'}, + {name: 'Reunion', code: 'RE'}, + {name: 'Romania', code: 'RO'}, + {name: 'Russian Federation', code: 'RU'}, + {name: 'RWANDA', code: 'RW'}, + {name: 'Saint Helena', code: 'SH'}, + {name: 'Saint Kitts and Nevis', code: 'KN'}, + {name: 'Saint Lucia', code: 'LC'}, + {name: 'Saint Pierre and Miquelon', code: 'PM'}, + {name: 'Saint Vincent and the Grenadines', code: 'VC'}, + {name: 'Samoa', code: 'WS'}, + {name: 'San Marino', code: 'SM'}, + {name: 'Sao Tome and Principe', code: 'ST'}, + {name: 'Saudi Arabia', code: 'SA'}, + {name: 'Senegal', code: 'SN'}, + {name: 'Serbia and Montenegro', code: 'CS'}, + {name: 'Seychelles', code: 'SC'}, + {name: 'Sierra Leone', code: 'SL'}, + {name: 'Singapore', code: 'SG'}, + {name: 'Slovakia', code: 'SK'}, + {name: 'Slovenia', code: 'SI'}, + {name: 'Solomon Islands', code: 'SB'}, + {name: 'Somalia', code: 'SO'}, + {name: 'South Africa', code: 'ZA'}, + {name: 'South Georgia and the South Sandwich Islands', code: 'GS'}, + {name: 'Spain', code: 'ES'}, + {name: 'Sri Lanka', code: 'LK'}, + {name: 'Sudan', code: 'SD'}, + {name: 'Suriname', code: 'SR'}, + {name: 'Svalbard and Jan Mayen', code: 'SJ'}, + {name: 'Swaziland', code: 'SZ'}, + {name: 'Sweden', code: 'SE'}, + {name: 'Switzerland', code: 'CH'}, + {name: 'Syrian Arab Republic', code: 'SY'}, + {name: 'Taiwan, Province of China', code: 'TW'}, + {name: 'Tajikistan', code: 'TJ'}, + {name: 'Tanzania, United Republic of', code: 'TZ'}, + {name: 'Thailand', code: 'TH'}, + {name: 'Timor-Leste', code: 'TL'}, + {name: 'Togo', code: 'TG'}, + {name: 'Tokelau', code: 'TK'}, + {name: 'Tonga', code: 'TO'}, + {name: 'Trinidad and Tobago', code: 'TT'}, + {name: 'Tunisia', code: 'TN'}, + {name: 'Turkey', code: 'TR'}, + {name: 'Turkmenistan', code: 'TM'}, + {name: 'Turks and Caicos Islands', code: 'TC'}, + {name: 'Tuvalu', code: 'TV'}, + {name: 'Uganda', code: 'UG'}, + {name: 'Ukraine', code: 'UA'}, + {name: 'United Arab Emirates', code: 'AE'}, + {name: 'United Kingdom', code: 'GB'}, + {name: 'United States', code: 'US'}, + {name: 'United States Minor Outlying Islands', code: 'UM'}, + {name: 'Uruguay', code: 'UY'}, + {name: 'Uzbekistan', code: 'UZ'}, + {name: 'Vanuatu', code: 'VU'}, + {name: 'Venezuela', code: 'VE'}, + {name: 'Viet Nam', code: 'VN'}, + {name: 'Virgin Islands, British', code: 'VG'}, + {name: 'Virgin Islands, U.S.', code: 'VI'}, + {name: 'Wallis and Futuna', code: 'WF'}, + {name: 'Western Sahara', code: 'EH'}, + {name: 'Yemen', code: 'YE'}, + {name: 'Zambia', code: 'ZM'}, + {name: 'Zimbabwe', code: 'ZW'} + ]) +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index 9b84d6c81..67b320cbe 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -7,6 +7,7 @@ var Router = Ember.Router.extend({ Router.map(function() { this.route('introduction'); + this.route('autocomplete'); this.route('button'); this.route('card'); this.route('checkbox'); diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index d81703cd2..30c31b877 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -14,6 +14,7 @@ {{#paper-list}} {{#paper-item action="transitionTo" param="index"}}Introduction{{/paper-item}} + {{#paper-item action="transitionTo" param="autocomplete"}}Autocomplete{{/paper-item}} {{#paper-item action="transitionTo" param="sidenav"}}Sidenav{{/paper-item}} {{#paper-item action="transitionTo" param="typography"}}Typography{{/paper-item}} {{#paper-item action="transitionTo" param="list"}}List{{/paper-item}} diff --git a/tests/dummy/app/templates/autocomplete.hbs b/tests/dummy/app/templates/autocomplete.hbs new file mode 100644 index 000000000..07383c615 --- /dev/null +++ b/tests/dummy/app/templates/autocomplete.hbs @@ -0,0 +1,16 @@ +{{#paper-toolbar}} +

+ {{#paper-sidenav-toggle class="menu-sidenav-toggle"}} + {{paper-icon icon="menu"}} + {{/paper-sidenav-toggle}} + Autocomplete +

+{{/paper-toolbar}} + +{{#paper-content classNames="md-padding"}} +
+ {{#paper-autocomplete items=items lookupKey="name" identifier="code" as |autocomplete|}} + {{/paper-autocomplete}} +

Template

+
+{{/paper-content}} diff --git a/tests/unit/components/paper-autocomplete-input-test.js b/tests/unit/components/paper-autocomplete-input-test.js new file mode 100644 index 000000000..73d9c74ba --- /dev/null +++ b/tests/unit/components/paper-autocomplete-input-test.js @@ -0,0 +1,19 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete-input', 'Unit | Component | paper autocomplete input', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'], + unit: true +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/components/paper-autocomplete-test.js b/tests/unit/components/paper-autocomplete-test.js new file mode 100644 index 000000000..02c2684cf --- /dev/null +++ b/tests/unit/components/paper-autocomplete-test.js @@ -0,0 +1,19 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete', 'Unit | Component | paper autocomplete', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'], + unit: true +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); diff --git a/tests/unit/components/paper-autocomplete-wrap-test.js b/tests/unit/components/paper-autocomplete-wrap-test.js new file mode 100644 index 000000000..37b875143 --- /dev/null +++ b/tests/unit/components/paper-autocomplete-wrap-test.js @@ -0,0 +1,19 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete-wrap', 'Unit | Component | paper autocomplete wrap', { + // Specify the other units that are required for this test + // needs: ['component:foo', 'helper:bar'], + unit: true +}); + +test('it renders', function(assert) { + assert.expect(2); + + // Creates the component instance + var component = this.subject(); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); From daa6125209529936a50ad444fe98df430762b0d8 Mon Sep 17 00:00:00 2001 From: Petter Kjelkenes Date: Tue, 14 Jul 2015 20:23:00 +0200 Subject: [PATCH 02/39] Fix keyDown actions on autocomplete input. --- addon/components/paper-autocomplete-input.js | 38 ++++++++++----- addon/components/paper-autocomplete-item.js | 14 +++++- addon/components/paper-autocomplete-wrap.js | 5 ++ addon/components/paper-autocomplete.js | 47 +++++++++---------- .../components/paper-autocomplete-wrap.hbs | 8 ++-- tests/dummy/app/controllers/autocomplete.js | 7 ++- tests/dummy/app/templates/autocomplete.hbs | 3 +- 7 files changed, 78 insertions(+), 44 deletions(-) diff --git a/addon/components/paper-autocomplete-input.js b/addon/components/paper-autocomplete-input.js index 9e6f215a5..d586bd5f4 100644 --- a/addon/components/paper-autocomplete-input.js +++ b/addon/components/paper-autocomplete-input.js @@ -5,11 +5,12 @@ import constants from '../utils/constants'; export default Ember.TextField.extend({ type: 'search', + hadKeyDown: false, + handleSearchText () { var parent = this.get("parent"); var text = parent.get('searchText').toLowerCase(); - parent.set('index', parent.getDefaultIndex()); var items = parent.get('items'); var lookupKey = parent.get('lookupKey'); @@ -24,10 +25,11 @@ export default Ember.TextField.extend({ searchTextObserver: Ember.observer('parent.searchText',function() { var text = this.get('parent').get('searchText'); - if (typeof text === 'undefined') return; + if (typeof text === 'undefined' || !this.get('hadKeyDown')) return; var wait = parseInt(this.get("parent").get('delay'), 10) || 0; Ember.run.debounce(this, this.handleSearchText, wait); + }), keyDown (event) { @@ -36,30 +38,44 @@ export default Ember.TextField.extend({ case constants.KEYCODE.DOWN_ARROW: if (autocomplete.get('loading')) return; event.preventDefault(); - autocomplete.set('index', Math.min(autocomplete.get('index') + 1, autocomplete.get('matches').length - 1)); - autocomplete.updateScroll(); - autocomplete.updateMessages(); + autocomplete.set('index', Math.min(autocomplete.get('index') + 1, autocomplete.get('suggestions').length - 1)); + this.updateScroll(); break; case constants.KEYCODE.UP_ARROW: if (autocomplete.get('loading')) return; event.preventDefault(); - autocomplete.set('index', autocomplete.get('index') < 0 ? autocomplete.get('matches').length - 1 : Math.max(0, autocomplete.get('index') - 1)); - autocomplete.updateScroll(); - autocomplete.updateMessages(); + autocomplete.set('index', autocomplete.get('index') < 0 ? autocomplete.get('suggestions').length - 1 : Math.max(0, autocomplete.get('index') - 1)); + this.updateScroll(); break; case constants.KEYCODE.TAB: case constants.KEYCODE.ENTER: - if (autocomplete.get('hidden') || autocomplete.get('loading') || autocomplete.get('index') < 0 || autocomplete.get('matches').length < 1) return; + if (autocomplete.get('index') < 0 || autocomplete.get('suggestions').length < 1) return; event.preventDefault(); - select(autocomplete.get('index')); + autocomplete.set('model', autocomplete.get('suggestions')[autocomplete.get('index')]); break; case constants.KEYCODE.ESCAPE: autocomplete.set('matches', Ember.A([])); autocomplete.set('hidden', true); - autocomplete.set('index', autocomplete.getDefaultIndex()); break; default: } + this.set('hadKeyDown', true); + }, + + updateScroll () { + var autocomplete = this.get("parent"); + var suggestions = autocomplete.get('suggestions'); + if (!suggestions[autocomplete.get('index')]) return; + var ul = autocomplete.get('ulContainer'), + li = ul.find('li:eq('+autocomplete.get('index')+')')[0], + top = li.offsetTop, + bot = top + li.offsetHeight, + hgt = ul[0].clientHeight; + if (top < ul[0].scrollTop) { + ul[0].scrollTop = top; + } else if (bot > ul[0].scrollTop + hgt) { + ul[0].scrollTop = bot - hgt; + } } }); diff --git a/addon/components/paper-autocomplete-item.js b/addon/components/paper-autocomplete-item.js index c24ebc4ec..336433b09 100644 --- a/addon/components/paper-autocomplete-item.js +++ b/addon/components/paper-autocomplete-item.js @@ -1,5 +1,17 @@ import Ember from 'ember'; export default Ember.Component.extend({ - tagName: 'li' + tagName: 'li', + attributeBindings: ['tabindex', 'role'], + classNameBindings: ['isSelected:selected'], + tabindex: 0, + role: 'option', + + isSelected: Ember.computed('parent.index', function () { + return this.get('parent').get('index') === this.get('index'); + }), + + click () { + this.get('parent').set('model', this.get('item')); + } }); diff --git a/addon/components/paper-autocomplete-wrap.js b/addon/components/paper-autocomplete-wrap.js index 2d8b609cf..adbf511e1 100644 --- a/addon/components/paper-autocomplete-wrap.js +++ b/addon/components/paper-autocomplete-wrap.js @@ -13,7 +13,12 @@ export default Ember.Component.extend({ actions: { clear: function () { + this.get('parent').set('model', null); this.get("parent").set('searchText', ''); + }, + + pickItem: function (item) { + this.set('model', item); } }, diff --git a/addon/components/paper-autocomplete.js b/addon/components/paper-autocomplete.js index 90243fa2b..4cd845a90 100644 --- a/addon/components/paper-autocomplete.js +++ b/addon/components/paper-autocomplete.js @@ -19,33 +19,20 @@ export default Ember.Component.extend({ isDisabled: null, isRequired: null, lookupKey: null, + searchText: '', + valueObserver: Ember.observer('model',function () { + var value; + if (this.get('model')) { + value = this.get('model')[this.get('lookupKey')]; + } else { + value = ''; + } + this.set('searchText', value); + }), - getDefaultIndex () { - return this.get("autoselect") ? 0 : -1; - }, - - updateScroll () {/* - if (!elements.li[ctrl.index]) return; - var li = elements.li[ctrl.index], - top = li.offsetTop, - bot = top + li.offsetHeight, - hgt = elements.ul.clientHeight; - if (top < elements.ul.scrollTop) { - elements.ul.scrollTop = top; - } else if (bot > elements.ul.scrollTop + hgt) { - elements.ul.scrollTop = bot - hgt; - }*/ - }, - - updateMessages () { - /*getCurrentDisplayValue().then(function(msg) { - ctrl.messages = [ getCountMessage(), msg ]; - });*/ - }, - positionDropdown () { var hrect = this.$().find('md-autocomplete-wrap:first')[0].getBoundingClientRect(), vrect = hrect, @@ -87,7 +74,17 @@ export default Ember.Component.extend({ observeSuggestions: Ember.observer('suggestions', function () { - this.positionDropdown(); - }) + if (this.get('suggestions').length) { + this.positionDropdown(); + } + }), + + + didInsertElement: function () { + var _self = this; + jQuery(window).resize(function () { + _self.positionDropdown(); + }); + } }); diff --git a/app/templates/components/paper-autocomplete-wrap.hbs b/app/templates/components/paper-autocomplete-wrap.hbs index d59acec6e..955ead28c 100644 --- a/app/templates/components/paper-autocomplete-wrap.hbs +++ b/app/templates/components/paper-autocomplete-wrap.hbs @@ -65,14 +65,14 @@ -