diff --git a/CHANGELOG.md b/CHANGELOG.md index 421291e25..54042cee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Ember Paper Changelog +### 0.2.7 (Aug 11, 2015) +- [#132](https://github.com/miguelcobain/ember-paper/pull/132) Added autocomplete component. +- [#144](https://github.com/miguelcobain/ember-paper/pull/144) Fixed paper-icon sizes and added new size md-sm (size="sm"). +- [#146](https://github.com/miguelcobain/ember-paper/pull/146) Upgrade to ember 1.13.7 and ember-cli 1.13.8. + ### 0.2.6 (Jul 20, 2015) - [#135](https://github.com/miguelcobain/ember-paper/pull/135) Fix deprecation bug in linear progress indicator. diff --git a/addon/components/paper-autocomplete-highlight.js b/addon/components/paper-autocomplete-highlight.js new file mode 100644 index 000000000..aedba4da6 --- /dev/null +++ b/addon/components/paper-autocomplete-highlight.js @@ -0,0 +1,35 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'span', + flags: '', + + highlight: Ember.computed('searchText', 'label', 'flags', function() { + var unsafeText = Ember.Handlebars.Utils.escapeExpression(this.get('label')); + var text = unsafeText; + var flags = this.get('flags'); + var regex = this.getRegExp(this.get('searchText'), flags); + var html = text.replace(regex, '$&'); + return new Ember.Handlebars.SafeString(html); + }), + + sanitize(term) { + if (!term) { + return term; + } + return term.replace(/[\\\^\$\*\+\?\.\(\)\|\{}\[\]]/g, '\\$&'); + }, + + getRegExp(text, flags) { + var str = ''; + if (flags.indexOf('^') >= 1) { + str += '^'; + } + str += text; + if (flags.indexOf('$') >= 1) { + str += '$'; + } + return new RegExp(this.sanitize(str), flags.replace(/[\$\^]/g, '')); + } + +}); diff --git a/addon/components/paper-autocomplete-item.js b/addon/components/paper-autocomplete-item.js new file mode 100644 index 000000000..4443c7ec8 --- /dev/null +++ b/addon/components/paper-autocomplete-item.js @@ -0,0 +1,25 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'li', + attributeBindings: ['tabindex', 'role'], + classNameBindings: ['isSelected:selected'], + tabindex: 0, + role: 'option', + + label: Ember.computed('item', function() { + return this.lookupLabelOfItem(this.get('item')); + }), + + isSelected: Ember.computed('selectedIndex', function() { + return this.get('selectedIndex') === this.get('index'); + }), + + lookupLabelOfItem(model) { + return this.get('lookupKey') ? Ember.get(model, this.get('lookupKey')) : model; + }, + + click() { + this.sendAction('pick', this.get('item')); + } +}); diff --git a/addon/components/paper-autocomplete-list.js b/addon/components/paper-autocomplete-list.js new file mode 100644 index 000000000..ea9a29e77 --- /dev/null +++ b/addon/components/paper-autocomplete-list.js @@ -0,0 +1,108 @@ +import Ember from 'ember'; + +//TODO Move to constants? +var ITEM_HEIGHT = 41, + MAX_HEIGHT = 5.5 * ITEM_HEIGHT, + MENU_PADDING = 8; + +export default Ember.Component.extend({ + util: Ember.inject.service(), + + tagName: 'ul', + classNames: ['md-default-theme', 'md-autocomplete-suggestions', 'md-whiteframe-z1'], + attributeNameBindings: ['role'], + role: 'presentation', + stickToElement: null, + + mouseEnter() { + this.sendAction('mouse-enter'); + }, + + mouseLeave() { + this.sendAction('mouse-leave'); + }, + + mouseUp() { + this.sendAction('mouse-up'); + }, + + //TODO reafactor into a computed property that binds directly to dropdown's `style` + positionDropdown() { + var hrect = Ember.$('#' + this.get('wrapToElementId'))[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.$(); + + 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); + } + }, + + observeIndex: Ember.observer('selectedIndex', function() { + var suggestions = this.get('suggestions'); + if (!suggestions || !suggestions.objectAt(this.get('selectedIndex'))) { + return; + } + + var ul = this.$(), + li = ul.find('li:eq('+this.get('selectedIndex')+')')[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; + } + }), + + resizeWindowEvent() { + this.positionDropdown(); + }, + + didInsertElement() { + this._super(...arguments); + + //TODO refactor using ember-wormhole? + var ul = this.$().detach(); + Ember.$('body').append(ul); + Ember.$(window).on('resize', Ember.run.bind(this, this.resizeWindowEvent)); + this.get('util').disableScrollAround(this.$()); + this.positionDropdown(); + }, + + willDestroyElement() { + Ember.$(window).off('resize'); + this.get('util').enableScrolling(); + } + +}); diff --git a/addon/components/paper-autocomplete.js b/addon/components/paper-autocomplete.js new file mode 100644 index 000000000..f7282db09 --- /dev/null +++ b/addon/components/paper-autocomplete.js @@ -0,0 +1,306 @@ +import Ember from 'ember'; +import HasBlockMixin from '../mixins/hasblock-mixin'; +import { promiseArray } from 'ember-paper/utils/promise-proxies'; + +function isString(item) { + return typeof item === 'string' || item instanceof String; +} + +/** + * @name paper-autocomplete + * + * @description + * Provides material design autocomplete. + * + * + * ## Dependencies + * - paper-autocomplete-item + * - paper-autocomplete-list + * - paper-input + * - paper-button + * - input + */ +export default Ember.Component.extend(HasBlockMixin, { + util: Ember.inject.service(), + constants: Ember.inject.service(), + + tagName: 'md-autocomplete', + classNameBindings: ['notFloating:md-default-theme'], + attributeBindings: ['floating:md-floating-label', 'showDisabled:disabled'], + + + // Internal + hidden: true, + selectedIndex: 0, + messages: [], + noBlur: false, + hasFocus: false, + searchText: '', + // wrap in a computed property so that cache + // isn't shared among autocomplete instances + itemCache: Ember.computed(function() { + return {}; + }), + + // Public + disabled: null, + required: null, + lookupKey: null, + placeholder: '', + delay: 0, + minLength: 1, + allowNonExisting: false, + noCache: false, + notFoundMessage: 'No matches found for \'%@\'.', + + init() { + this._super(...arguments); + + if (this.get('model')) { + this.set('searchText', this.lookupLabelOfItem(this.get('model'))); + this.searchTextDidChange(); + } + }, + + notFloating: Ember.computed.not('floating'), + notHidden: Ember.computed.not('hidden'), + + autocompleteWrapperId: Ember.computed('elementId', function() { + return 'autocomplete-wrapper-' + this.get('elementId'); + }), + + sections: { + itemTemplate: {isItemTemplate: true}, + notFoundTemplate: {isNotFoundTemplate: true} + }, + + notFoundMsg: Ember.computed('searchText', 'notFoundMessage', function() { + return Ember.String.fmt(this.get('notFoundMessage'), [this.get('searchText')]); + }), + + /** + * Needed because of false = disabled='false'. + */ + showDisabled: Ember.computed('disabled', function() { + if (this.get('disabled')) { + return true; + } + }), + + showLoadingBar: Ember.computed('loading', 'allowNonExisting', 'debouncingState', function() { + return !this.get('loading') && !this.get('allowNonExisting') && !this.get('debouncingState'); + }), + + enableClearButton: Ember.computed('searchText', 'disabled', function() { + return this.get('searchText') && !this.get('disabled'); + }), + + /** + * Source filtering logic + */ + + searchTextDidChange: Ember.observer('searchText', function() { + var searchText = this.get('searchText'); + if (searchText !== this.get('previousSearchText')) { + if (!this.get('allowNonExisting')) { + this.set('model', null); + } else { + this.set('model', searchText); + } + + this.sendAction('update-filter', searchText); + + this.set('debouncingState', true); + Ember.run.debounce(this, this.setDebouncedSearchText, this.get('delay')); + this.set('previousSearchText', searchText); + } + }), + + setDebouncedSearchText() { + var searchText = this.get('searchText'); + if (this.get('isMinLengthMet')) { + this.sendAction('debounced-update-filter', searchText); + if (!this.cacheGet(searchText)) { + this.sendAction('cache-miss', searchText); + } else { + this.sendAction('cache-hit', searchText); + } + this.set('debouncedSearchText', searchText); + + // If the autocomplete is being triggered by a human / not on initial render. + if (this.get('hasFocus') || this.get('noBlur')) { + this.set('hidden', false); + } + } else { + this.set('hidden', true); + } + this.set('debouncingState', false); + }, + + loading: Ember.computed.bool('sourcePromiseArray.isPending').readOnly(), + + //coalesces all promises into PromiseArrays or Arrays + sourcePromiseArray: Ember.computed('source', function() { + var source = this.get('source'); + if (source && source.then) { + //coalesce into promise array + return promiseArray(source); + } else if (Ember.isArray(source)) { + //return array + return Ember.A(source); + } else { + //Unknown source type + Ember.assert('The provided \'source\' for paper-autocomplete must be an Array or a Promise.', !Ember.isPresent(source)); + return Ember.A(); + } + }).readOnly(), + + suggestions: Ember.computed('debouncedSearchText', 'sourcePromiseArray.[]', function() { + var source = this.get('sourcePromiseArray'); + var lookupKey = this.get('lookupKey'); + var searchText = (this.get('debouncedSearchText') || '').toLowerCase(); + var cachedItems = this.cacheGet(searchText); + var suggestions; + + if (cachedItems) { + //We have cached results + suggestions = cachedItems; + } else { + //no cache + + var data = this.filterArray(source, searchText, lookupKey); + if (source.then && source.get('isFulfilled')) { + //cache when we have a PromiseArray + this.cacheSet(searchText, data); + } + suggestions = Ember.A(data); + } + // If we have no item suggestions, and allowNonExisting is enabled + // We need to close the paper-autocomplete-list so all mouse events get activated again. + if (suggestions.length === 0 && this.get('allowNonExisting')){ + this.set('hidden', true); + } + return suggestions; + }).readOnly(), + + + filterArray(array, searchText, lookupKey) { + return array.filter(function(item) { + Ember.assert('You have not defined \'lookupKey\' on paper-autocomplete, when source contained ' + + 'items that are not of type String. To fix this error provide a ' + + 'lookupKey=\'key to lookup from source item\'.', isString(item) || Ember.isPresent(lookupKey)); + + Ember.assert('You specified \'' + lookupKey + '\' as a lookupKey on paper-autocomplete, ' + + 'but at least one of its values is not of type String. To fix this error make sure that every \'' + lookupKey + + '\' value is a string.', isString(item) || (Ember.isPresent(lookupKey) && isString(Ember.get(item, lookupKey))) ); + + var search = isString(item) ? item.toLowerCase() : Ember.get(item, lookupKey).toLowerCase(); + return search.indexOf(searchText) === 0; + }); + }, + + //TODO move cache to service? Components are not singletons. + cacheGet(text) { + return !this.get('noCache') && this.get('itemCache')[text]; + }, + + cacheSet(text, data) { + this.get('itemCache')[text] = data; + }, + + shouldHide: Ember.computed.not('isMinLengthMet'), + + isMinLengthMet: Ember.computed('searchText', 'minLength', function() { + return this.get('searchText').length >= this.get('minLength'); + }), + + /** + * Returns the default index based on whether or not autoselect is enabled. + * @returns {number} + */ + defaultIndex: Ember.computed('autoselect', function() { + return this.get('autoselect') ? 0 : -1; + }), + + lookupLabelOfItem(model) { + return this.get('lookupKey') ? Ember.get(model, this.get('lookupKey')) : model; + }, + + actions: { + clear() { + this.set('searchText', ''); + this.set('selectedIndex', -1); + this.set('model', null); + this.set('hidden', this.get('shouldHide')); + }, + + pickModel(model) { + this.set('model', model); + var value = this.lookupLabelOfItem(model); + // First set previousSearchText then searchText ( do not trigger observer only update value! ). + this.set('previousSearchText', value); + this.set('searchText', value); + this.set('hidden', true); + }, + + inputFocusOut() { + this.set('hasFocus', false); + if (this.get('noBlur') === false) { + this.set('hidden', true); + } + }, + + inputFocusIn() { + this.set('hasFocus', true); + this.set('hidden', this.get('shouldHide')); + }, + + inputKeyDown(value, event) { + switch (event.keyCode) { + case this.get('constants').KEYCODE.DOWN_ARROW: + if (this.get('loading')) { + return; + } + this.set('selectedIndex', Math.min(this.get('selectedIndex') + 1, this.get('suggestions').length - 1)); + break; + case this.get('constants').KEYCODE.UP_ARROW: + if (this.get('loading')) { + return; + } + this.set('selectedIndex', this.get('selectedIndex') < 0 ? this.get('suggestions').length - 1 : Math.max(0, this.get('selectedIndex') - 1)); + break; + case this.get('constants').KEYCODE.TAB: + case this.get('constants').KEYCODE.ENTER: + if (this.get('hidden') || this.get('loading') || this.get('selectedIndex') < 0 || this.get('suggestions').length < 1) { + return; + } + this.send('pickModel', this.get('suggestions').objectAt(this.get('selectedIndex'))); + break; + case this.get('constants').KEYCODE.ESCAPE: + this.set('searchText', ''); + this.set('selectedIndex', this.get('defaultIndex')); + this.set('model', null); + this.set('hidden', this.get('shouldHide')); + break; + default: + break; + } + }, + + listMouseEnter() { + this.set('noBlur', true); + }, + + listMouseLeave() { + this.set('noBlur', false); + if (this.get('hasFocus') === false) { + this.set('hidden', true); + } + }, + + listMouseUp() { + this.$().find('input').focus(); + } + } + +}); diff --git a/addon/components/paper-button.js b/addon/components/paper-button.js index 92e558b40..b07b8fadd 100644 --- a/addon/components/paper-button.js +++ b/addon/components/paper-button.js @@ -7,8 +7,8 @@ import ColorMixin from 'ember-paper/mixins/color-mixin'; export default BaseFocusable.extend(RippleMixin, ProxiableMixin, ColorMixin, { attributeBindings: ['target', 'action'], tagName: 'button', - classNames: ['md-button','md-default-theme'], - classNameBindings: ['raised:md-raised', 'icon-button:md-icon-button'], + themed: true, + classNameBindings: ['raised:md-raised', 'icon-button:md-icon-button', 'themed:md-default-theme', 'themed:md-button'], /* RippleMixin overrides */ isIconButton: Ember.computed(function() { diff --git a/addon/components/paper-icon.js b/addon/components/paper-icon.js index 1ade74475..1d3a9300f 100644 --- a/addon/components/paper-icon.js +++ b/addon/components/paper-icon.js @@ -25,6 +25,8 @@ export default Ember.Component.extend(ColorMixin, { switch(this.get('size')) { case 'lg': return ' md-lg'; + case 'sm': + return ' md-sm'; case 2: return ' md-2x'; case 3: diff --git a/addon/components/paper-input.js b/addon/components/paper-input.js index 8ab3280f5..7dd317e2a 100644 --- a/addon/components/paper-input.js +++ b/addon/components/paper-input.js @@ -1,12 +1,14 @@ import Ember from 'ember'; import BaseFocusable from './base-focusable'; import ColorMixin from 'ember-paper/mixins/color-mixin'; +import FlexMixin from 'ember-paper/mixins/flex-mixin'; -export default BaseFocusable.extend(ColorMixin, { +export default BaseFocusable.extend(ColorMixin, FlexMixin, { tagName: 'md-input-container', classNames: ['md-default-theme'], classNameBindings: ['hasValue:md-input-has-value', 'focus:md-input-focused', 'isInvalid:md-input-invalid'], type: 'text', + autofocus: false, tabindex: -1, hasValue: Ember.computed.notEmpty('value'), inputElementId: Ember.computed('elementId', function() { @@ -63,11 +65,18 @@ export default BaseFocusable.extend(ColorMixin, { }, actions: { - focusIn() { + focusIn(value) { + // We resend action so other components can take use of the actions also ( if they want ). + // Actions must be sent before focusing. + this.sendAction('focus-in', value); this.set('focus',true); }, - focusOut() { + focusOut(value) { + this.sendAction('focus-out', value); this.set('focus',false); + }, + keyDown(value, event) { + this.sendAction('key-down', value, event); } } }); diff --git a/addon/components/paper-item.js b/addon/components/paper-item.js index 61d1e9bf3..1c3f4b349 100644 --- a/addon/components/paper-item.js +++ b/addon/components/paper-item.js @@ -27,7 +27,7 @@ export default Ember.Component.extend(RippleMixin, ProxyMixin, { return secondaryItem && (secondaryItem.action || (this.get('action') && this.isProxiedComponent(secondaryItem))); }), - secondaryItem: Ember.computed('proxiedComponents.@each', function() { + secondaryItem: Ember.computed('proxiedComponents.[]', function() { var proxiedComponents = this.get('proxiedComponents'); return proxiedComponents.find(function(component) { return component.classNames.indexOf('md-secondary') !== -1; diff --git a/addon/mixins/proxiable-mixin.js b/addon/mixins/proxiable-mixin.js index 997c258b7..422ccae2d 100644 --- a/addon/mixins/proxiable-mixin.js +++ b/addon/mixins/proxiable-mixin.js @@ -2,7 +2,7 @@ import Ember from 'ember'; import ProxyMixin from './proxy-mixin'; export default Ember.Mixin.create({ - didInsertElement: function() { + didInsertElement() { this._super(...arguments); var proxy = this.nearestOfType(ProxyMixin); diff --git a/addon/utils/promise-proxies.js b/addon/utils/promise-proxies.js new file mode 100644 index 000000000..37603224d --- /dev/null +++ b/addon/utils/promise-proxies.js @@ -0,0 +1,27 @@ +import Ember from 'ember'; + +var Promise = Ember.RSVP.Promise; + +// See http://emberjs.com/api/data/classes/DS.PromiseArray.html +var PromiseArray = Ember.ArrayProxy.extend(Ember.PromiseProxyMixin); +// See http://emberjs.com/api/data/classes/DS.PromiseObject.html +var PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin); + +var promiseObject = function(promise, label) { + return PromiseObject.create({ + promise: Promise.resolve(promise, label) + }); +}; + +var promiseArray = function(promise, label) { + return PromiseArray.create({ + promise: Promise.resolve(promise, label) + }); +}; + +export { + PromiseArray, + PromiseObject, + promiseArray, + promiseObject +}; diff --git a/app/components/paper-autocomplete-highlight.js b/app/components/paper-autocomplete-highlight.js new file mode 100644 index 000000000..0de8d8183 --- /dev/null +++ b/app/components/paper-autocomplete-highlight.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-autocomplete-highlight'; \ 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-list.js b/app/components/paper-autocomplete-list.js new file mode 100644 index 000000000..5ff0cfea6 --- /dev/null +++ b/app/components/paper-autocomplete-list.js @@ -0,0 +1 @@ +export { default } from 'ember-paper/components/paper-autocomplete-list'; \ 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/services/util.js b/app/services/util.js new file mode 100644 index 000000000..4206687fb --- /dev/null +++ b/app/services/util.js @@ -0,0 +1,108 @@ +import Ember from 'ember'; + +/* global jQuery */ + +var Util = Ember.Service.extend({ + + // Disables scroll around the passed element. + disableScrollAround: function (element) { + var $mdUtil = this, + $document = jQuery(window.document); + + $mdUtil.disableScrollAround._count = $mdUtil.disableScrollAround._count || 0; + ++$mdUtil.disableScrollAround._count; + if ($mdUtil.disableScrollAround._enableScrolling) return $mdUtil.disableScrollAround._enableScrolling; + var body = $document[0].body, + restoreBody = disableBodyScroll(), + restoreElement = disableElementScroll(); + + return $mdUtil.disableScrollAround._enableScrolling = function () { + if (!--$mdUtil.disableScrollAround._count) { + restoreBody(); + restoreElement(); + delete $mdUtil.disableScrollAround._enableScrolling; + } + }; + + // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events + function disableElementScroll() { + var zIndex = 50; + var scrollMask = jQuery( + '
' + + '
' + + '
'); + body.appendChild(scrollMask[0]); + + scrollMask.on('wheel', preventDefault); + scrollMask.on('touchmove', preventDefault); + $document.on('keydown', disableKeyNav); + + return function restoreScroll() { + scrollMask.off('wheel'); + scrollMask.off('touchmove'); + scrollMask[0].parentNode.removeChild(scrollMask[0]); + $document.off('keydown', disableKeyNav); + delete $mdUtil.disableScrollAround._enableScrolling; + }; + + // Prevent keypresses from elements inside the body + // used to stop the keypresses that could cause the page to scroll + // (arrow keys, spacebar, tab, etc). + function disableKeyNav(e) { + //-- temporarily removed this logic, will possibly re-add at a later date + return; + if (!element[0].contains(e.target)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + + function preventDefault(e) { + e.preventDefault(); + } + } + + // Converts the body to a position fixed block and translate it to the proper scroll + // position + function disableBodyScroll() { + var htmlNode = body.parentNode; + var restoreHtmlStyle = htmlNode.getAttribute('style') || ''; + var restoreBodyStyle = body.getAttribute('style') || ''; + var scrollOffset = body.scrollTop + body.parentElement.scrollTop; + var clientWidth = body.clientWidth; + + if (body.scrollHeight > body.clientHeight) { + applyStyles(body, { + position: 'fixed', + width: '100%', + top: -scrollOffset + 'px' + }); + + applyStyles(htmlNode, { + overflowY: 'scroll' + }); + } + + + if (body.clientWidth < clientWidth) applyStyles(body, {overflow: 'hidden'}); + + return function restoreScroll() { + body.setAttribute('style', restoreBodyStyle); + htmlNode.setAttribute('style', restoreHtmlStyle); + body.scrollTop = scrollOffset; + }; + } + + function applyStyles(el, styles) { + for (var key in styles) { + el.style[key] = styles[key]; + } + } + }, + enableScrolling: function () { + var method = this.disableScrollAround._enableScrolling; + method && method(); + } +}); + +export default Util; diff --git a/app/styles/ember-paper.scss b/app/styles/ember-paper.scss index f961c6801..9ab0e556c 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-progress-linear'; @import 'paper-progress-circular'; 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/styles/paper-icon.scss b/app/styles/paper-icon.scss index c8ac9eefb..424773212 100644 --- a/app/styles/paper-icon.scss +++ b/app/styles/paper-icon.scss @@ -11,17 +11,6 @@ md-icon { width: 3 * $baseline-grid; line-height: 3 * $baseline-grid; } - -// Icon sizes -.md-lg { - font-size: 1.5em; - line-height: .5em; - vertical-align: -35%; -} -.md-2x { font-size: 2em; } -.md-3x { font-size: 3em; } -.md-4x { font-size: 4em; } -.md-5x { font-size: 5em; } */ // Icon spinners @@ -2505,3 +2494,29 @@ md-icon.md-#{$theme-name}-theme { content: "\e900" } } + +// Icon sizes +.md-lg { + font-size: 1.5em; +} + +.md-sm { + font-size: 1em; +} + +.md-2x { + font-size: 2em; +} + +.md-3x { + font-size: 3em; +} + +.md-4x { + font-size: 4em; +} + +.md-5x { + font-size: 5em; +} + diff --git a/app/templates/components/paper-autocomplete-highlight.hbs b/app/templates/components/paper-autocomplete-highlight.hbs new file mode 100644 index 000000000..da402e7ac --- /dev/null +++ b/app/templates/components/paper-autocomplete-highlight.hbs @@ -0,0 +1 @@ +{{highlight}} \ No newline at end of file diff --git a/app/templates/components/paper-autocomplete-item.hbs b/app/templates/components/paper-autocomplete-item.hbs new file mode 100644 index 000000000..cb11eb126 --- /dev/null +++ b/app/templates/components/paper-autocomplete-item.hbs @@ -0,0 +1 @@ +{{yield label}} \ 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..2edb014a1 --- /dev/null +++ b/app/templates/components/paper-autocomplete.hbs @@ -0,0 +1,83 @@ + + {{#if floating}} + {{paper-input + type="search" + label=placeholder + focus-in="inputFocusIn" + focus-out="inputFocusOut" + key-down="inputKeyDown" + value=searchText + disabled=disabled + required=required + flex=true}} + {{else}} + {{input type="search" + flex=true + placeholder=placeholder + value=searchText + focus-in="inputFocusIn" + focus-out="inputFocusOut" + key-down="inputKeyDown" + autocomplete="off" + disabled=disabled + required=required + aria-haspopup=true + aria-autocomplete="list" + aria-activedescendant="" + aria-expanded=notHidden}} + + {{#if enableClearButton}} + {{#paper-button icon-button=true themed=false action="clear"}} + {{paper-icon icon="close"}} + {{/paper-button}} + {{/if}} + + {{/if}} + + {{#if loading}} + {{paper-progress-linear}} + {{/if}} + + {{#if notHidden}} + {{#paper-autocomplete-list suggestions=suggestions selectedIndex=selectedIndex wrapToElementId=autocompleteWrapperId mouse-up="listMouseUp" mouse-leave="listMouseLeave" mouse-enter="listMouseEnter"}} + + {{#each suggestions as |item index|}} + + {{#paper-autocomplete-item lookupKey=lookupKey item=item selectedIndex=selectedIndex index=index pick="pickModel" as |label|}} + {{!-- Render block template, then named component then default --}} + {{#if hasBlock}} + {{yield searchText item index}} + {{else}} + {{#if itemComponent}} + {{component itemComponent searchText=searchText label=label index=index}} + {{else}} + {{paper-autocomplete-highlight searchText=searchText label=label}} + {{/if}} + {{/if}} + {{/paper-autocomplete-item}} + + {{else}} + {{#if showLoadingBar}} + {{!-- Render block template, then named component then default --}} + {{#if hasBlock}} +
  • {{yield searchText to="inverse"}}
  • + {{else}} + {{#if notFoundComponent}} +
  • {{component notFoundComponent searchText=searchText}}
  • + {{else}} +
  • {{notFoundMsg}}
  • + {{/if}} + {{/if}} + {{/if}} + {{/each}} + {{/paper-autocomplete-list}} + {{/if}} +
    + + + {{#each messages as |message index|}} + {{#if message}} +

    {{message}}

    + {{/if}} + {{/each}} +
    diff --git a/app/templates/components/paper-input.hbs b/app/templates/components/paper-input.hbs index 26dea537c..e1dc4e047 100644 --- a/app/templates/components/paper-input.hbs +++ b/app/templates/components/paper-input.hbs @@ -1,9 +1,9 @@ {{#if textarea}} - {{textarea class="md-input" id=inputElementId value=value focus-in="focusIn" focus-out="focusOut" disabled=disabled required=required}} + {{textarea class="md-input" id=inputElementId value=value focus-in="focusIn" key-down="keyDown" focus-out="focusOut" disabled=disabled required=required autofocus=autofocus}} {{else}} - {{input class="md-input" id=inputElementId type=type value=value focus-in="focusIn" focus-out="focusOut" disabled=disabled required=required}} + {{input class="md-input" id=inputElementId type=type value=value focus-in="focusIn" key-down="keyDown" focus-out="focusOut" disabled=disabled required=required autofocus=autofocus}} {{/if}}
    diff --git a/app/templates/components/paper-item.hbs b/app/templates/components/paper-item.hbs index 8cb2d45a3..328bad11b 100644 --- a/app/templates/components/paper-item.hbs +++ b/app/templates/components/paper-item.hbs @@ -1,5 +1,5 @@ {{#if action}} - {{#paper-button classNames="md-no-style" noink=true action="buttonAction" skipProxy=true}} + {{#paper-button class="md-no-style" noink=true action="buttonAction" skipProxy=true}}
    {{yield}}
    diff --git a/bower.json b/bower.json index 23294c329..d29c0cc68 100644 --- a/bower.json +++ b/bower.json @@ -1,17 +1,17 @@ { "name": "ember-paper", "dependencies": { - "ember": "1.13.5", + "ember": "1.13.7", "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", - "ember-qunit": "0.4.2", + "ember-qunit": "0.4.9", "ember-qunit-notifications": "0.0.7", "ember-resolver": "~0.1.18", "hammerjs": "~2.0.4", - "jquery": "^1.11.1", - "loader.js": "ember-cli/loader.js#3.2.0", - "qunit": "~1.17.1", + "jquery": "^1.11.3", + "loader.js": "ember-cli/loader.js#3.2.1", + "qunit": "~1.18.0", "prism": "*" } } diff --git a/package.json b/package.json index aad5f472b..bdf8918b3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ember-paper", "description": "The Ember approach to Material Design.", - "version": "0.2.6", + "version": "0.2.7", "directories": { "doc": "doc", "test": "tests" @@ -21,31 +21,32 @@ "url": "https://github.com/miguelcobain/ember-paper/issues" }, "devDependencies": { - "broccoli-asset-rev": "^2.0.2", + "broccoli-asset-rev": "^2.1.2", "broccoli-merge-trees": "~0.2.1", "broccoli-static-compiler": "~0.2.1", - "ember-cli": "1.13.1", - "ember-cli-app-version": "0.4.0", + "ember-cli": "1.13.8", + "ember-cli-app-version": "0.5.0", "ember-cli-content-security-policy": "0.4.0", - "ember-cli-dependency-checker": "^1.0.0", + "ember-cli-dependency-checker": "^1.0.1", "ember-cli-github-pages": "0.0.4", "ember-cli-htmlbars": "0.7.9", - "ember-cli-htmlbars-inline-precompile": "^0.1.1", + "ember-cli-htmlbars-inline-precompile": "^0.2.0", "ember-cli-ic-ajax": "0.2.1", - "ember-cli-inject-live-reload": "^1.3.0", - "ember-cli-qunit": "0.3.15", + "ember-cli-inject-live-reload": "^1.3.1", + "ember-cli-qunit": "^1.0.0", "ember-cli-release": "0.2.3", "ember-cli-sass": "4.0.0-beta.5", - "ember-cli-uglify": "^1.0.1", + "ember-cli-sri": "^1.0.3", + "ember-cli-uglify": "^1.2.0", "ember-disable-prototype-extensions": "^1.0.0", "ember-disable-proxy-controllers": "^1.0.0", - "ember-export-application-global": "^1.0.2", + "ember-export-application-global": "^1.0.3", "ember-prism": "0.0.5", "ember-try": "0.0.6" }, "dependencies": { "broccoli-autoprefixer": "^3.0.0", - "ember-cli-babel": "^5.0.0" + "ember-cli-babel": "^5.1.3" }, "keywords": [ "ember-addon", diff --git a/tests/dummy/app/controllers/autocomplete.js b/tests/dummy/app/controllers/autocomplete.js new file mode 100644 index 000000000..19e3afa7f --- /dev/null +++ b/tests/dummy/app/controllers/autocomplete.js @@ -0,0 +1,299 @@ +import Ember from 'ember'; + +var SOME_DATA_FROM_API = Ember.A([ + {name: 'Computer', id: 1}, + {name: 'Ham', id: 2}, + {name: 'Unfair', id: 3}, + {name: 'Ram', id: 4}, + {name: 'Test', id: 5}, +]); + +export default Ember.Controller.extend({ + myModel: {name: 'United States', code: 'US'}, + + searchText: '', + + /** + * This is a sample of data loaded dynamically. + * Here we use a fake promise, but this can come directly from the ember-data filter API or e.g. jQuery $.getJSON + * + * @param searchText Search text from the autocomplete API. Lower cased version. + * @returns {Promise} + */ + dataFromPromise: Ember.computed('searchText', function() { + var searchText = this.get('searchText'); + + // Can also come from e.g. this.store.query('country', {text: searchText}).then( ... ); + return new Ember.RSVP.Promise(resolve => { + // Just wait for 800ms to 2 seconds for a fake progress, so it feels like a query. + var waitMS = Math.floor(Math.random() * 1000) + 800; + + Ember.run.later(this, function() { + var result = SOME_DATA_FROM_API.filter(function (item) { + return item.name.toLowerCase().indexOf(searchText.toLowerCase()) === 0; + }); + resolve(Ember.A(result)); + }, waitMS); + + }); + }), + + actions: { + updateFilter(str) { + this.set('searchText', str); + } + }, + + arrayOfItems: ['Ember', 'Paper', 'One', 'Two', 'Three','Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve'], + + /** + * Array of static Objects. + * When having objects, use lookupKey="name" on the paper-autocomplete component so it knows to use "name" to search in. + */ + 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 92a5bf8dc..bdec9313b 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/styles/app.scss b/tests/dummy/app/styles/app.scss index 0a57ac5ae..693730a85 100644 --- a/tests/dummy/app/styles/app.scss +++ b/tests/dummy/app/styles/app.scss @@ -124,7 +124,7 @@ ul li:first-child { body { - &> .ember-view { + &> div.ember-view { width: 100%; height: 100%; } diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index 6e8aa4e1b..7c6089084 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -10,9 +10,9 @@ {{/if}} -{{#paper-nav-container open=drawerOpen classNames="ember-app"}} +{{#paper-nav-container open=drawerOpen class="ember-app"}} - {{#paper-sidenav classNames="md-sidenav-left md-whiteframe-z2" flex-layout="column" flex=true}} + {{#paper-sidenav class="md-sidenav-left md-whiteframe-z2" flex-layout="column" flex=true}} {{#paper-toolbar}}
    @@ -26,6 +26,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..97de84ac9 --- /dev/null +++ b/tests/dummy/app/templates/autocomplete.hbs @@ -0,0 +1,323 @@ +{{#paper-toolbar}} +

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

    +{{/paper-toolbar}} + +{{#paper-content class="md-padding"}} +
    + +{{#paper-card}} +{{#paper-card-content}} +

    Basic Usage

    +
    Use \{{paper-autocomplete}} to search for matches from local or remote data sources.
    + + {{paper-autocomplete + disabled=firstDisabled + placeholder="Select a Country ..." + notFoundMessage="Oops country: \"%@\" doesn't exist here." + source=items lookupKey="name" + model=myModel}} +

    + {{#if myModel}} + Selected country is {{myModel.name}} + ({{myModel.code}}) + {{else}} + Nothing selected... + {{/if}} +

    + {{#paper-checkbox checked=firstDisabled}}Disable input{{/paper-checkbox}} + + + +

    Template

    +{{#code-block language='handlebars'}} +\{{paper-autocomplete + disabled=firstDisabled + placeholder="Select a Country ..." + notFoundMessage="Oops country: \"%@\" doesn't exist here." + source=items lookupKey="name" + model=myModel}}{{/code-block}} + +

    Allow non existing items

    +
    Use attribute allowNonExisting=true for the autocomplete to allow setting model to non existing items in the autocomplete. This is useful for search boxes.
    + + {{paper-autocomplete minLength=0 allowNonExisting=true placeholder="Type e.g. ember, paper, one, two etc." source=arrayOfItems model=sixthModel}} +

    + {{#if sixthModel}} + Selected thing was: {{sixthModel}} + {{else}} + Nothing is selected... + {{/if}} +

    + +{{/paper-card-content}} +{{/paper-card}} + + +{{#paper-card}} +{{#paper-card-content}} +

    Promise support

    +
    You may pass a promise to the source attribute. This means you can use Ember Data async relationships directly, for example. + You can even use a computed property that returns a promise. + When dealing with Promises that possibly hit the server, the delay attribute is recommended to set a delay for the search to start. + This way we avoid unecessary requests while the user types. + Promise values are cached unless noCache is true. +
    + + {{paper-autocomplete minLength=0 delay=300 placeholder="Type e.g. Ram, Test, etc." source=dataFromPromise lookupKey="name" model=otherModel cache-miss="updateFilter"}} +

    + {{#if otherModel}} + You have selected: {{otherModel.name}} ({{otherModel.id}}) + {{else}} + No Item Selected. + {{/if}} +

    + +

    Template

    +{{#code-block language="handlebars"}} +\{{paper-autocomplete minLength=0 delay=300 placeholder="Type e.g. Ram, Test, etc." source=dataFromPromise lookupKey="name" model=otherModel cache-miss="updateFilter"}}{{/code-block}} + +

    Javascript (Ember-data)

    +

    + In the above template we use the variable dataFromPromise which is a computed property that returns a promise. + We are also using the cache-miss action to update our computed property. + Here is a sample configuration: +

    +{{#code-block language="javascript"}} + ... + // searchText is received from the autocomplete component, this is what the user typed in the input field. + dataFromPromise: Ember.computed('filterText', function() { + let filterText = this.get('filterText'); + return this.store.query('country', { + name: filterText + }); + }), + actions: { + updateFilter(str) { + this.set('filterText', str); + } + } + ...{{/code-block}} + + + +{{/paper-card-content}} +{{/paper-card}} + +{{#paper-card}} +{{#paper-card-content}} +

    Block Custom template

    +
    Use \{{paper-autocomplete}} with custom templates to show styled autocomplete results. In this example we also use minLength=0 which allow to see all results if input is empty.
    + + {{#paper-autocomplete minLength=0 placeholder="Type e.g. ember, paper, one, two etc." source=arrayOfItems model=fourthModel as |searchText item index|}} + + {{paper-icon icon="star"}} + {{paper-autocomplete-highlight searchText=searchText label=item}} (index {{index}} ) + + {{else}} + Whoops! Could not find "{{searchText}}". + {{/paper-autocomplete}} + +

    + {{#if fourthModel}} + Selected thing was: {{fourthModel}} + {{else}} + Nothing selected... + {{/if}} +

    + +

    Template

    +{{#code-block language="handlebars"}} +\{{#paper-autocomplete minLength=0 placeholder="Type e.g. ember, paper, one, two etc." source=arrayOfItems model=fourthModel as |item index searchText|}}} + <span class="item-title"> + \{{paper-icon icon="star"}} + <span> \{{paper-autocomplete-highlight searchText=searchText label=item}} </span> + </span> +\{{else}} + Whoops! Could not find "\{{searchText}}". +\{{/paper-autocomplete}}{{/code-block}} + +

    The custom template receives 3 block parameters (searchText, item and index). +

    + +
      +
    • searchText This is the original searchText from the user.
    • +
    • item This is the item directly from the source array. If it is an object you would forexample + need to reference it with \{{item.name}}. Not defined in the inverse template.
    • +
    • index This is the index of the suggestions that are currently in the list. Not defined in the inverse template.
    • +
    + +

    Blockless Custom template

    +

    Alternatively you can use the blockless/named componend approach which produces the same result:

    + +{{paper-autocomplete minLength=0 placeholder="Type e.g. ember, paper, one, two etc." source=arrayOfItems model=fourthModelB + itemComponent="example-item" notFoundComponent="not-found"}} + +

    + {{#if fourthModelB}} + Selected thing was: {{fourthModelB}} + {{else}} + Nothing selected... + {{/if}} +

    + +

    Template

    +{{#code-block language="handlebars"}} +\{{paper-autocomplete minLength=0 placeholder="Type e.g. ember, paper, one, two etc." source=arrayOfItems model=fourthModelB + itemComponent="example-item" notFoundComponent="not-found"}}{{/code-block}} + +{{#code-block language="handlebars"}} +\{{!-- app/templates/components/not-found.hbs --}} +Whoops! Could not find "\{{searchText}}".{{/code-block}} + +{{#code-block language="handlebars"}} +\{{!-- app/templates/components/example-item.hbs --}} +<span class="item-title"> + \{{paper-icon icon="star"}} + <span>\{{paper-autocomplete-highlight searchText=searchText label=item}} (index \{{index}} )</span> +</span>{{/code-block}} + +{{/paper-card-content}} +{{/paper-card}} + + + + +{{#paper-card}} +{{#paper-card-content}} +

    Floating Label

    +
    The following example demonstrates floating labels being used as a normal form element.
    + {{paper-autocomplete floating=true placeholder="Select a Country ..." source=items lookupKey="name" model=thirdModel}} +

    + {{#if thirdModel}} + Selected country is {{thirdModel.name}} ({{thirdModel.code}}) + {{else}} + Nothing selected... + {{/if}} +

    +

    Template

    +{{#code-block language="handlebars"}} +\{{paper-autocomplete floating=true placeholder="Select a Country ..." source=items lookupKey="name" model=thirdModel}}{{/code-block}} + +{{/paper-card-content}} +{{/paper-card}} + + +

    Attributes for paper-autocomplete

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    sourcemixedThe source attribute is used to look up possible suggestions for the autocomplete. + The source attribute accepts: +
      +
    • Array of strings: Simple array of strings. Example: ['One', 'Two']
    • +
    • Promise: This is useful if you want to fetch suggestions from the server with e.g. Ember-data or jQuery's $.ajax method. The action cacheMiss may be useful to update the promise. See promise example above.
    • +
    • Array of objects: If you pass array of objects, paper-autocomplete needs to know what key in each object to search in. Use in combination with lookupKey. Example if you have provided [{id: 1, name="Car"}] as source, you will also need to configure the lookupKey to lookupKey="name".
    • +
    +
    modelmixedWhen a user selects item from the suggestions, model will be set and updated. Provide a model so you can do something about the value when user clicks the item.
    placeholderstringSets a placeholder for the autocomplete input field.
    minLengthintegerSets how many characters the user must type before the autocomplete gives suggestions. Default is 1.
    delayintegerThe delay attribute lets you configure how many milliseconds to wait before we trigger a search, this is + useful to avoid mass sending HTTP requests to your backend if you are using Function based source with + AJAX calls. Somewhere around 300 ms is good.
    noCachebooleanOnly effective if you use promise as source. This disables the cache of promise loaded suggestions. By default they are cached when loaded the first time.
    floatingbooleanMakes the autocomplete field a normal input field with floating labels.
    autoselectbooleanWhen suggestions is being displayed, by default when autoselect is true it will select the first element as selected. Default is false.
    disabledbooleanDisables the autocomplete.
    requiredbooleanMakes the autocomplete a required field.
    allowNonExistingbooleanallowNonExisting is useful for search boxes. It allows to use items that are not in the autocomplete selection. If you type e.g. "Chees" the model will also be set to "Chees".
    notFoundMessagestringThe message to display if no items was found. Default is: No matches found for "%@".. The %@ part will be replaced by the users input.
    + +

    Actions for paper-autocomplete

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ActionDescription
    update-filterTriggered as soon as the filter changes. Has searchText as a parameter.
    debounced-update-filterTriggered delayms after the filter changes. Has searchText as a parameter.
    cache-missTriggered delayms after the filter changes and there arent't any cached results for this text. Has searchText as a parameter.
    cache-hitTriggered delayms after the filter changes and there are cached results for this text. Has searchText as a parameter.
    + +
    +{{/paper-content}} diff --git a/tests/dummy/app/templates/button.hbs b/tests/dummy/app/templates/button.hbs index f8055f739..0f4736404 100644 --- a/tests/dummy/app/templates/button.hbs +++ b/tests/dummy/app/templates/button.hbs @@ -6,7 +6,7 @@ Buttons {{/paper-toolbar}} -{{#paper-content classNames="md-padding demo-buttons"}} +{{#paper-content class="md-padding demo-buttons"}}

    {{#paper-button action="flatButton"}}Button with action{{/paper-button}} diff --git a/tests/dummy/app/templates/card.hbs b/tests/dummy/app/templates/card.hbs index 8739aef6f..792929cf9 100644 --- a/tests/dummy/app/templates/card.hbs +++ b/tests/dummy/app/templates/card.hbs @@ -7,9 +7,9 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

    - {{#paper-content classNames="md-whiteframe-z1 list-demo"}} + {{#paper-content class="md-whiteframe-z1 list-demo"}} {{#paper-card}} Washed Out {{#paper-card-content}} diff --git a/tests/dummy/app/templates/checkbox.hbs b/tests/dummy/app/templates/checkbox.hbs index 8ae944a0d..4a9a0f460 100644 --- a/tests/dummy/app/templates/checkbox.hbs +++ b/tests/dummy/app/templates/checkbox.hbs @@ -6,7 +6,7 @@ Checkboxes {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}
    {{#paper-checkbox checked=value1}}A checkbox: {{value1}}{{/paper-checkbox}} {{#paper-checkbox checked=value2}}A checkbox: {{#if value2}}yep{{else}}nope{{/if}}{{/paper-checkbox}} diff --git a/tests/dummy/app/templates/components/example-item.hbs b/tests/dummy/app/templates/components/example-item.hbs new file mode 100644 index 000000000..4c7edf671 --- /dev/null +++ b/tests/dummy/app/templates/components/example-item.hbs @@ -0,0 +1,4 @@ + +{{paper-icon icon="star"}} +{{paper-autocomplete-highlight searchText=searchText label=item}} (index {{index}} ) + diff --git a/tests/dummy/app/templates/components/not-found.hbs b/tests/dummy/app/templates/components/not-found.hbs new file mode 100644 index 000000000..55ef0a18d --- /dev/null +++ b/tests/dummy/app/templates/components/not-found.hbs @@ -0,0 +1 @@ +Whoops! Could not find "{{searchText}}". diff --git a/tests/dummy/app/templates/divider.hbs b/tests/dummy/app/templates/divider.hbs index f8f86f459..c078ee35a 100644 --- a/tests/dummy/app/templates/divider.hbs +++ b/tests/dummy/app/templates/divider.hbs @@ -7,10 +7,10 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

    Full width dividers

    - {{#paper-content classNames="md-whiteframe-z1 list-demo"}} + {{#paper-content class="md-whiteframe-z1 list-demo"}} {{#paper-list}} {{#each listData as |item|}} {{#paper-item class="md-3-line"}} @@ -29,7 +29,7 @@ {{/paper-content}}

    Inset dividers

    - {{#paper-content classNames="md-whiteframe-z1 list-demo"}} + {{#paper-content class="md-whiteframe-z1 list-demo"}} {{#paper-list}} {{#each listData as |item|}} {{#paper-item class="md-3-line"}} @@ -49,7 +49,7 @@

    Template

    {{#code-block language='handlebars'}} -\{{#paper-content classNames="md-whiteframe-z1 list-demo"}} +\{{#paper-content class="md-whiteframe-z1 list-demo"}} \{{#paper-list}} \{{#each listData as |item|}} \{{#paper-item}} diff --git a/tests/dummy/app/templates/icons.hbs b/tests/dummy/app/templates/icons.hbs index bc0dad314..39184f9a8 100644 --- a/tests/dummy/app/templates/icons.hbs +++ b/tests/dummy/app/templates/icons.hbs @@ -7,19 +7,22 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding icon-demo"}} +{{#paper-content class="md-padding icon-demo"}}

    Template

    Basic Icons

    {{#code-block language='handlebars'}} -\{{paper-icon icon="check"}}{{/code-block}} -

    Larger Icons

    + \{{paper-icon icon="check"}} + {{/code-block}} +

    Changing Sizes

    {{#code-block language='handlebars'}} -\{{paper-icon icon="check" size="lg"}} -\{{paper-icon icon="check" size=2}} -\{{paper-icon icon="check" size=3}} -\{{paper-icon icon="check" size=4}} -\{{paper-icon icon="check" size=5}}{{/code-block}} + \{{paper-icon icon="check" size="sm"}} + \{{paper-icon icon="check" size="lg"}} + \{{paper-icon icon="check" size=2}} + \{{paper-icon icon="check" size=3}} + \{{paper-icon icon="check" size=4}} + \{{paper-icon icon="check" size=5}} + {{/code-block}}

    Spinners

    {{#code-block language='handlebars'}} \{{paper-icon icon="rotate-right" spin=true}} diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index c8e5a39ef..8c6c1f8bc 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

    Welcome to Ember Paper.

    This project aims to bring Google's new Material Design to Ember. The goal is to encapsulate everything possible in Ember components. This project is packaged as an Ember-cli addon.

    diff --git a/tests/dummy/app/templates/input.hbs b/tests/dummy/app/templates/input.hbs index 6aeb4b75a..f4743447c 100644 --- a/tests/dummy/app/templates/input.hbs +++ b/tests/dummy/app/templates/input.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}
    • {{paper-input label="Name" value=name}}
    • diff --git a/tests/dummy/app/templates/list-controls.hbs b/tests/dummy/app/templates/list-controls.hbs index 9f5d0f2d6..c2d94af47 100644 --- a/tests/dummy/app/templates/list-controls.hbs +++ b/tests/dummy/app/templates/list-controls.hbs @@ -7,37 +7,37 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

      Basic Usage

      - {{#paper-content classNames="md-whiteframe-z1 list-demo list-controls-demo"}} + {{#paper-content class="md-whiteframe-z1 list-demo list-controls-demo"}} {{#paper-list}} - {{#paper-subheader classNames="md-no-sticky"}}Single Action Checkboxes{{/paper-subheader}} + {{#paper-subheader class="md-no-sticky"}}Single Action Checkboxes{{/paper-subheader}} {{#paper-item}}

      Checkbox 1

      - {{paper-checkbox classNames="md-secondary" checked=checkboxEnabled1}} + {{paper-checkbox class="md-secondary" checked=checkboxEnabled1}} {{/paper-item}} {{#paper-item}}

      Checkbox 2

      - {{paper-checkbox classNames="md-secondary" checked=checkboxEnabled2}} + {{paper-checkbox class="md-secondary" checked=checkboxEnabled2}} {{/paper-item}} {{paper-divider}} - {{#paper-subheader classNames="md-no-sticky"}}Clickable Items with Secondary Controls{{/paper-subheader}} + {{#paper-subheader class="md-no-sticky"}}Clickable Items with Secondary Controls{{/paper-subheader}} {{#paper-item action="transitionToWifiMenu"}} {{paper-icon icon="network-wifi"}}

      Wi-Fi

      - {{paper-switch classNames="md-secondary" checked=wifiEnabled}} + {{paper-switch class="md-secondary" checked=wifiEnabled}} {{/paper-item}} {{#paper-item action="transitionToBluetoothMenu"}} {{paper-icon icon="bluetooth"}}

      Bluetooth

      - {{paper-switch classNames="md-secondary" checked=bluetoothEnabled}} + {{paper-switch class="md-secondary" checked=bluetoothEnabled}} {{/paper-item}} {{#paper-item action="transitionToDataUsage"}} @@ -47,7 +47,7 @@ {{paper-divider}} - {{#paper-subheader classNames="md-no-sticky"}}Checkbox with Secondary Action{{/paper-subheader}} + {{#paper-subheader class="md-no-sticky"}}Checkbox with Secondary Action{{/paper-subheader}} {{#each messageData as |item|}} {{#paper-item}} @@ -61,7 +61,7 @@ {{#paper-divider}}{{/paper-divider}} - {{#paper-subheader classNames="md-no-sticky"}}Clickable Avatars{{/paper-subheader}} + {{#paper-subheader class="md-no-sticky"}}Clickable Avatars{{/paper-subheader}} {{#each listData as |item|}} {{#paper-item class="md-3-line" param=item action="goToPerson"}} @@ -76,7 +76,7 @@ {{paper-divider}} - {{#paper-subheader classNames="md-no-sticky"}}Avatar with Secondary Action Icon{{/paper-subheader}} + {{#paper-subheader class="md-no-sticky"}}Avatar with Secondary Action Icon{{/paper-subheader}} {{#each listData as |item|}} {{#paper-item class="md-3-line" param=item action="goToPerson"}} @@ -100,32 +100,32 @@ {{#code-block language='handlebars'}} \{{#paper-list}} - \{{#paper-subheader classNames="md-no-sticky"}}Single Action Checkboxes\{{/paper-subheader}} + \{{#paper-subheader class="md-no-sticky"}}Single Action Checkboxes\{{/paper-subheader}} \{{#paper-item}} <p>Checkbox 1</p> - \{{paper-checkbox classNames="md-secondary" checked=checkboxEnabled1}} + \{{paper-checkbox class="md-secondary" checked=checkboxEnabled1}} \{{/paper-item}} \{{#paper-item}} <p>Checkbox 2</p> - \{{paper-checkbox classNames="md-secondary" checked=checkboxEnabled2}} + \{{paper-checkbox class="md-secondary" checked=checkboxEnabled2}} \{{/paper-item}} \{{paper-divider}} - \{{#paper-subheader classNames="md-no-sticky"}}Clickable Items with Secondary Controls\{{/paper-subheader}} + \{{#paper-subheader class="md-no-sticky"}}Clickable Items with Secondary Controls\{{/paper-subheader}} \{{#paper-item action="transitionToWifiMenu"}} \{{paper-icon icon="network-wifi"}} <p>Wi-Fi</p> - \{{paper-switch classNames="md-secondary" checked=wifiEnabled}} + \{{paper-switch class="md-secondary" checked=wifiEnabled}} \{{/paper-item}} \{{#paper-item action="transitionToBluetoothMenu"}} \{{paper-icon icon="bluetooth"}} <p>Bluetooth</p> - \{{paper-switch classNames="md-secondary" checked=bluetoothEnabled}} + \{{paper-switch class="md-secondary" checked=bluetoothEnabled}} \{{/paper-item}} \{{#paper-item action="transitionToDataUsage"}} @@ -135,7 +135,7 @@ \{{paper-divider}} - \{{#paper-subheader classNames="md-no-sticky"}}Checkbox with Secondary Action\{{/paper-subheader}} + \{{#paper-subheader class="md-no-sticky"}}Checkbox with Secondary Action\{{/paper-subheader}} \{{#each messageData as |item|}} \{{#paper-item}} @@ -149,7 +149,7 @@ \{{#paper-divider}}\{{/paper-divider}} - \{{#paper-subheader classNames="md-no-sticky"}}Clickable Avatars\{{/paper-subheader}} + \{{#paper-subheader class="md-no-sticky"}}Clickable Avatars\{{/paper-subheader}} \{{#each listData as |item|}} \{{#paper-item class="md-3-line" param=item action="goToPerson"}} @@ -164,7 +164,7 @@ \{{paper-divider}} - \{{#paper-subheader classNames="md-no-sticky"}}Avatar with Secondary Action Icon\{{/paper-subheader}} + \{{#paper-subheader class="md-no-sticky"}}Avatar with Secondary Action Icon\{{/paper-subheader}} \{{#each listData as |item|}} \{{#paper-item class="md-3-line" param=item action="goToPerson"}} diff --git a/tests/dummy/app/templates/list.hbs b/tests/dummy/app/templates/list.hbs index c600bbba8..3214763b1 100644 --- a/tests/dummy/app/templates/list.hbs +++ b/tests/dummy/app/templates/list.hbs @@ -7,10 +7,10 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

      Basic Usage

      - {{#paper-content classNames="md-whiteframe-z1 list-demo"}} + {{#paper-content class="md-whiteframe-z1 list-demo"}} {{#paper-list}} {{#each listData as |item|}} {{#paper-item class="md-3-line"}} @@ -30,7 +30,7 @@

      Template

      {{#code-block language='handlebars'}} -\{{#paper-content classNames="md-whiteframe-z1 list-demo"}} +\{{#paper-content class="md-whiteframe-z1 list-demo"}} \{{#paper-list}} \{{#each listData as |item|}} \{{#paper-item class="md-3-line"}} diff --git a/tests/dummy/app/templates/progress-circular.hbs b/tests/dummy/app/templates/progress-circular.hbs index bfcb40f67..0fee825b4 100644 --- a/tests/dummy/app/templates/progress-circular.hbs +++ b/tests/dummy/app/templates/progress-circular.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

      Basic Usage

      Determinate

      @@ -25,10 +25,10 @@

      Theming

      - {{paper-progress-circular classNames="md-hue-2"}} + {{paper-progress-circular class="md-hue-2"}} {{paper-progress-circular accent=true}} - {{paper-progress-circular accent=true classNames="md-hue-1"}} - {{paper-progress-circular warn=true classNames="md-hue-3"}} + {{paper-progress-circular accent=true class="md-hue-1"}} + {{paper-progress-circular warn=true class="md-hue-3"}} {{paper-progress-circular warn=true}}
      diff --git a/tests/dummy/app/templates/progress-linear.hbs b/tests/dummy/app/templates/progress-linear.hbs index 4b390f5be..1c984ab13 100644 --- a/tests/dummy/app/templates/progress-linear.hbs +++ b/tests/dummy/app/templates/progress-linear.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

      Basic Usage

      diff --git a/tests/dummy/app/templates/radio.hbs b/tests/dummy/app/templates/radio.hbs index abaf553a6..9fd5b1ace 100644 --- a/tests/dummy/app/templates/radio.hbs +++ b/tests/dummy/app/templates/radio.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}
      • {{#paper-radio}}A radio button{{/paper-radio}}
      • diff --git a/tests/dummy/app/templates/sidenav.hbs b/tests/dummy/app/templates/sidenav.hbs index a7551c599..a3a4e605c 100644 --- a/tests/dummy/app/templates/sidenav.hbs +++ b/tests/dummy/app/templates/sidenav.hbs @@ -7,13 +7,13 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

        Try to resize this webpage.

        Template

        -{{#code-block language='handlebars'}}\{{#paper-nav-container open=drawerOpen classNames="ember-app"}} +{{#code-block language='handlebars'}}\{{#paper-nav-container open=drawerOpen class="ember-app"}} - \{{#paper-sidenav classNames="md-sidenav-left md-whiteframe-z2" flex-layout="column" flex=true}} + \{{#paper-sidenav class="md-sidenav-left md-whiteframe-z2" flex-layout="column" flex=true}} \{{#paper-toolbar}} <div class="md-toolbar-tools"> diff --git a/tests/dummy/app/templates/slider.hbs b/tests/dummy/app/templates/slider.hbs index 1505425dc..c7a61a530 100644 --- a/tests/dummy/app/templates/slider.hbs +++ b/tests/dummy/app/templates/slider.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

        RGB       @@ -27,7 +27,7 @@
        G
        - {{paper-slider flex=true min='0' max='255' value=color.green classNames='md-accent'}} + {{paper-slider flex=true min='0' max='255' value=color.green class='md-accent'}}
        {{input type="number" value=color.green}}
        @@ -37,7 +37,7 @@
        B
        - {{paper-slider flex=true min='0' max='255' value=color.blue classNames='md-primary'}} + {{paper-slider flex=true min='0' max='255' value=color.blue class='md-primary'}}
        {{input type="number" value=color.blue}}
        @@ -91,7 +91,7 @@ <div flex="10" layout layout-align="center center"> <span>G</span> </div> - \{{paper-slider flex=true min='0' max='255' value=color.green classNames='md-accent'}} + \{{paper-slider flex=true min='0' max='255' value=color.green class='md-accent'}} <div flex="20" layout layout-align="center center"> \{{input type="number" value=color.green}} </div> @@ -101,7 +101,7 @@ <div flex="10" layout layout-align="center center"> <span>B</span> </div> - \{{paper-slider flex=true min='0' max='255' value=color.blue classNames='md-primary'}} + \{{paper-slider flex=true min='0' max='255' value=color.blue class='md-primary'}} <div flex="20" layout layout-align="center center"> \{{input type="number" value=color.blue}} </div> diff --git a/tests/dummy/app/templates/switch.hbs b/tests/dummy/app/templates/switch.hbs index 2131ee103..3b9d5f999 100644 --- a/tests/dummy/app/templates/switch.hbs +++ b/tests/dummy/app/templates/switch.hbs @@ -7,7 +7,7 @@

        {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

        {{#paper-switch checked=booleanProp1}} {{booleanProp1}} {{/paper-switch}} diff --git a/tests/dummy/app/templates/toolbar.hbs b/tests/dummy/app/templates/toolbar.hbs index 03fd2968d..36625b898 100644 --- a/tests/dummy/app/templates/toolbar.hbs +++ b/tests/dummy/app/templates/toolbar.hbs @@ -6,7 +6,7 @@ Toolbars {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

        {{#paper-toolbar}}
        diff --git a/tests/dummy/app/templates/typography.hbs b/tests/dummy/app/templates/typography.hbs index 5e7a52ee4..72f4a6a0e 100644 --- a/tests/dummy/app/templates/typography.hbs +++ b/tests/dummy/app/templates/typography.hbs @@ -7,7 +7,7 @@ {{/paper-toolbar}} -{{#paper-content classNames="md-padding"}} +{{#paper-content class="md-padding"}}

        Headings

        diff --git a/tests/unit/components/paper-autocomplete-highlight-test.js b/tests/unit/components/paper-autocomplete-highlight-test.js new file mode 100644 index 000000000..d6365eee9 --- /dev/null +++ b/tests/unit/components/paper-autocomplete-highlight-test.js @@ -0,0 +1,41 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete-highlight', 'Unit | Component | paper autocomplete highlight', { + // 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'); +}); + + + +test('should highlight correct text', function(assert) { + assert.expect(2); + + var component = this.subject({ + searchText: 'ed S', + label: 'United States' + }); + + this.render(); + + var el = this.$(); + + + assert.equal(el.html(), 'United States', 'Sets correct html with highlight based on searchText'); + + assert.equal(component.get('highlight'), 'United States', 'Sets highlight based on searchText'); + + +}); diff --git a/tests/unit/components/paper-autocomplete-item-test.js b/tests/unit/components/paper-autocomplete-item-test.js new file mode 100644 index 000000000..1409a9190 --- /dev/null +++ b/tests/unit/components/paper-autocomplete-item-test.js @@ -0,0 +1,81 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete-item', 'Unit | Component | paper autocomplete item', { + // 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'); +}); + +test('it sets correct label when lookupKey is defined', function(assert) { + assert.expect(1); + + // Creates the component instance + var component = this.subject({ + lookupKey: 'name', + item: {id: 2, name: 'Blah Test'}, + }); + + assert.equal(component.get('label'), 'Blah Test'); + +}); + +test('it sets correct label when lookupKey is NOT defined', function(assert) { + assert.expect(1); + + // Creates the component instance + var component = this.subject({ + item: 'Blah Test' + }); + + assert.equal(component.get('label'), 'Blah Test'); + +}); + +test('it sets isSelected when index is equal to selectedIndex', function(assert) { + assert.expect(1); + + // Creates the component instance + var component = this.subject({ + index: 23, + selectedIndex: 23 + }); + + assert.equal(component.get('isSelected'), true); + +}); + + +test('trigger external action when item is clicked', function(assert) { + assert.expect(1); + + var item = {name: "test"}; + + var component = this.subject({ + item: item, + lookupKey: 'name' + }); + this.$(); + + var targetObject = { + externalAction: function(item2) { + // we have the assertion here which will be + // called when the action is triggered + assert.equal(item, item2, 'external Action was called and item received was correct.'); + } + }; + component.set('pick', 'externalAction'); + component.set('targetObject', targetObject); + this.$().click(); +}); diff --git a/tests/unit/components/paper-autocomplete-list-test.js b/tests/unit/components/paper-autocomplete-list-test.js new file mode 100644 index 000000000..f956d3a7d --- /dev/null +++ b/tests/unit/components/paper-autocomplete-list-test.js @@ -0,0 +1,46 @@ +import Ember from 'ember'; +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete-list', 'Unit | Component | paper autocomplete list', { + // Specify the other units that are required for this test + needs: ['service:util'], + unit: true +}); + +test('it renders', function(assert) { + assert.expect(2); + + Ember.$('#qunit-fixture').append('
        '); + + // Creates the component instance + var component = this.subject({ + wrapToElementId: 'elId' + }); + assert.equal(component._state, 'preRender'); + + // Renders the component to the page + this.render(); + assert.equal(component._state, 'inDOM'); +}); + + + + +test('it sets positional styles on component when toggling hidden attribute', function(assert) { + + // Creates the component instance + var component = this.subject({ + wrapToElementId: 'elId' + }); + Ember.$('#qunit-fixture').append('
        '); + + this.$(); + Ember.run(function() { + component.set('hidden', false); + }); + + assert.ok(component.$().attr('style'), 'Has styles set by hideSuggestionObserver'); + +}); + + diff --git a/tests/unit/components/paper-autocomplete-test.js b/tests/unit/components/paper-autocomplete-test.js new file mode 100644 index 000000000..1875f692e --- /dev/null +++ b/tests/unit/components/paper-autocomplete-test.js @@ -0,0 +1,44 @@ +import { moduleForComponent, test } from 'ember-qunit'; + +moduleForComponent('paper-autocomplete', 'Unit | Component | paper autocomplete', { + // Specify the other units that are required for this test + needs: [ + 'service:util', + 'service:sniffer', + 'service:constants', + 'component:paper-autocomplete-list', + 'component:paper-autocomplete-item', + 'component:paper-autocomplete-highlight', + 'component:paper-progress-linear', + 'component:paper-button', + 'component:paper-icon' + ], + 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'); + +}); + +test('it propagates placeholder to input box', function(assert) { + assert.expect(1); + + // Creates the component instance + var component = this.subject({ + placeholder: "Testing" + }); + + this.render(); + assert.equal(component.$().find('input').attr('placeholder'), 'Testing', 'Sets correct placeholder on input box.'); +}); + +