| <!-- |
| This in an HTML Import-able file that contains the definition |
| of the following elements: |
| |
| <autocomplete-input-sk> |
| |
| To use this file import it: |
| |
| <link href="/res/imp/autocomplete-input-sk.html" rel="import" /> |
| |
| Usage: |
| |
| <autocomplete-input-sk></autocomplete-input-sk> |
| |
| Properties: |
| autocomplete: Array<String> values for auto-completing. |
| heading: String, the heading to display |
| value: String Any typed-in value |
| display-options-on-focus: Boolean, show all matching values during focus |
| accept-custom-value: Boolean, can choose a value not from suggestions |
| |
| Methods: |
| None. |
| |
| Events: |
| change: Fires when the value stored in the autocomplete-input-sk changes. |
| --> |
| <link rel="import" href="/res/imp/bower_components/iron-icons/iron-icons.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-listbox/paper-listbox.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html"> |
| <link rel="import" href="/res/imp/bower_components/paper-item/paper-item.html"> |
| |
| <link rel="import" href="/res/common/imp/url-params-sk.html"> |
| |
| <dom-module id="autocomplete-input-sk"> |
| <template> |
| <style> |
| #suggestions_div { |
| border: 1px solid black; |
| position: absolute; |
| z-index: 1; |
| } |
| </style> |
| <!-- |
| This element just wraps a paper-input. We trap the on-change events because |
| we only want to fire that event when the user has typed or accepted one of |
| the autocomplete suggestions. Every key press occurring when the paper-input |
| has focus results in a call to _keyup. We treat some key presses specially; |
| up and down arrow cause different autocomplete suggestions to be |
| highlighted, and the enter key accepts a suggestion when one is highlighted. |
| Otherwise, we recompute the suggestions and display them. When the user |
| accepts a suggestion by clicking it, hitting enter when it is highlighted, |
| or simply by typing it out and unfocusing the paper-input, we call _commit, |
| which fires the on-change event. |
| --> |
| <paper-input id="input" label="[[label]]" |
| on-change="_change" |
| on-keyup="_keyup" |
| on-focus="_keyup" |
| display-options-on-focus="{{displayOptionsOnFocus}}" |
| accept-custom-value="{{acceptCustomValue}}" |
| ></paper-input> |
| <div id="suggestions_div" hidden$="{{_show_suggestions(_suggestions.*)}}"> |
| <paper-listbox id="suggestions_box" on-iron-select="_suggestion_changed"> |
| <template is="dom-repeat" items="[[_suggestions]]"> |
| <paper-item value="{{item}}">{{item}}</paper-item> |
| </template> |
| </paper-listbox> |
| </div> |
| </template> |
| <script> |
| (function(){ |
| var DOWN_ARROW = 40; |
| var UP_ARROW = 38; |
| var ENTER = 13; |
| |
| Polymer({ |
| is:"autocomplete-input-sk", |
| |
| properties: { |
| autocomplete: { |
| type: Array, |
| }, |
| |
| label: { |
| type: String, |
| }, |
| |
| value: { |
| type: String, |
| value: "", |
| notify: true, |
| observer: "_value_changed", |
| }, |
| |
| displayOptionsOnFocus: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| acceptCustomValue: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| _arrow_key_pressed: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| _committing: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| _suggestions: { |
| type: Array, |
| value: function() { |
| return []; |
| }, |
| }, |
| }, |
| |
| _onFocus: function(e) { |
| if (this.displayOptionsOnFocus && ( |
| this.value === '' || this.value === undefined)) { |
| this.value = ''; |
| this._keyup(e); |
| } |
| }, |
| |
| _value_changed: function() { |
| // This event fires when we set this.value or when our parent sets it. |
| // In the latter case, we need to propagate the change down to our |
| // child paper-input. |
| if (this._committing) { |
| // The event fired because _commit() was called. Don't propagate the |
| // change or we'll get an infinite loop. |
| this._committing = false; |
| } else { |
| // Our parent set this.value. Propagate the change. |
| this.$.input.value = this.value; |
| } |
| }, |
| |
| _change: function(e) { |
| // This event fires when the user "finishes" typing in the text box. |
| // This occurs when the enter key is pressed, or when the text box |
| // loses focus. We only want our parent to receive a change event when |
| // the user has finished typing, so we need to handle the case in which |
| // this event fires because the user clicked one of the suggestions. |
| e.preventDefault(); |
| e.stopPropagation(); |
| var v = this.$.input.value; |
| if (this.autocomplete && this.autocomplete.indexOf(v) == -1) { |
| // If we're autocompleting, don't fire the change event unless we |
| // chose a suggested value. |
| } else { |
| this._commit() |
| } |
| }, |
| |
| _commit: function() { |
| // The user has "finished" typing. This occurs when the user clicks |
| // a suggestion, or, once the user has typed out an exact match to one |
| // of the suggestions, they hit the enter key or click out of the text |
| // box. Set this.value and trigger a "change" event. |
| this._committing = true; |
| this._suggestions = []; |
| this.$.suggestions_box.selected = -1; |
| this.value = this.$.input.value; |
| this.fire("change", {"value": this.value}); |
| }, |
| |
| _keyup: function(e) { |
| if (this.autocomplete) { |
| // Allow the user to scroll through suggestions using arrow keys. |
| if (e.keyCode == DOWN_ARROW && this._suggestions.length > 0) { |
| this._arrow_key_pressed = true; |
| this.$.suggestions_box.selected = this.$.suggestions_box.selected + 1; |
| } else if (e.keyCode == UP_ARROW && this._suggestions.length > 0) { |
| this._arrow_key_pressed = true; |
| this.$.suggestions_box.selected = this.$.suggestions_box.selected - 1; |
| } else if (e.keyCode == ENTER && this.$.suggestions_box.selected > -1) { |
| // If the enter key is pressed while one of the suggestions is |
| // highlighted, we accept that selection. |
| this.$.input.value = this.$.suggestions_box.selectedItem.value; |
| this._commit(); |
| } else if (e.keyCode == ENTER && this.acceptCustomValue) { |
| // If the enter key is pressed without highlighting a suggestion |
| // then accept the current input value. |
| this._commit(); |
| } else { |
| // The user has entered text. Recompute autocomplete suggestions. |
| var v = this.$.input.value; |
| this._suggestions = []; |
| // Give suggestions on empty input only if displayOptionsOnFocus |
| // is true. |
| if (v != "" || this.displayOptionsOnFocus) { |
| // If possible, treat partially-entered input as a regular |
| // expression. |
| var re; |
| try { |
| re = new RegExp(v, "i"); // case-insensitive. |
| } catch (e) { |
| // If the user enters an invalid expression, just use substring |
| // match. |
| re = new Object(); |
| re.test = function(str) { |
| return str.indexOf(v) != -1; |
| }; |
| } |
| // Include anything in the autocomplete list which matches the |
| // text in the box. |
| for (var i = 0; i < this.autocomplete.length; ++i) { |
| if (re.test(this.autocomplete[i])) { |
| this.push("_suggestions", this.autocomplete[i]); |
| } |
| } |
| } |
| // If the user types an exact match to one of the suggestions, |
| // remove the suggestions box. |
| if (this._suggestions.length == 1 && this._suggestions[0] == v) { |
| this._suggestions = []; |
| } |
| // Ensure that no suggestions are selected after recomputing. |
| this.$.suggestions_box.selected = -1; |
| } |
| } |
| }, |
| |
| _suggestion_changed: function() { |
| if (this._arrow_key_pressed) { |
| // This event fires while we're arrowing through the options. We don't |
| // want to accept a suggestion while this is happening, so ignore the |
| // event in that case. |
| this._arrow_key_pressed = false; |
| } else { |
| // The user has clicked on a suggestion. Accept it. |
| var selected = this.$.suggestions_box.selectedItem.value; |
| this.$.input.value = selected; |
| this._commit(); |
| } |
| }, |
| |
| _show_suggestions: function() { |
| return !(this._suggestions && this._suggestions.length > 0); |
| }, |
| }); |
| })() |
| </script> |
| </dom-module> |