Skip to content

Instantly share code, notes, and snippets.

@Qvatra
Last active May 31, 2016 09:16
Show Gist options
  • Save Qvatra/ebc80375ab71666725b8e1bdb85c25c2 to your computer and use it in GitHub Desktop.
Save Qvatra/ebc80375ab71666725b8e1bdb85c25c2 to your computer and use it in GitHub Desktop.
<link rel='import' href="../../../bower_components/polymer/polymer.html">
<link rel='import' href="../../../bower_components/paper-menu-button/paper-menu-button.html">
<link rel='import' href="../../../bower_components/paper-item/paper-item.html">
<link rel='import' href="../../../bower_components/iron-icons/iron-icons.html">
<link rel='import' href="../../../bower_components/paper-material/paper-material.html">
<link rel='import' href="../../../bower_components/iron-selector/iron-selector.html">
<link rel='import' href="../rg-paper-input-search.html">
<link rel='import' href="rg-select-input.html">
<!--
Custom Polymer element.
Validatable dropdown selector (multi or single) element with an optional searchbar.
rg-select-input used as a trigger for the dropdown.
Options feed could also containt 'title elements' that are not selectable items.
Returns selectedKey (selectedKeys if multi===true).
-->
<dom-module id="rg-select">
<template>
<style include="shared-styles">
:host paper-menu-button {
padding: 0;
}
:host paper-menu {
padding: 0;
}
:host paper-item {
cursor: pointer;
min-height: 38px;
}
:host .search {
background: var(--sidebar-background-color-light);
}
:host .disabled {
color: var(--disabled-text-color);
}
:host .groupName {
font-weight: bold;
font-variant: small-caps;
cursor: default;
background: var(--sidebar-background-color-light);
}
:host .selected{
background: var(--light-primary-color);
}
:host paper-item {
--paper-item-focused-before: {
background: none;
}
}
</style>
<!-- ignore-select to prevent auto closing if some value was selected and users search for the second time -->
<paper-menu-button id="dropdown" horizontal-align="left" on-paper-dropdown-open="_onOpen" on-paper-dropdown-close="_onClose" ignore-select>
<rg-select-input validator="[[validator]]"
label="[[label]]"
value="[[_valuetoShow]]"
name="[[name]]"
required="[[required]]"
class="dropdown-trigger"
error-message="Invalid input!">
</rg-select-input>
<div class="dropdown-content">
<div class="search layout horizontal" hidden$="[[!enableSearch]]">
<rg-paper-input-search id="searchInput" value="{{_searchQuery}}" elevation="0"></rg-paper-input-search>
</div>
<iron-selector multi="[[multi]]"
attr-for-selected="key"
selected="{{selectedKey}}"
selected-values="{{_selectedKeys}}"
selected-class="selected"
selectable=".selectable">
<template is="dom-repeat" items="[[_options]]" filter="[[_computeSearchFilter(_searchQuery)]]">
<paper-item key="[[item.key]]" class$="[[_getItemCssClass(item.occursInContext)]] [[_getSelectableCssClass(item.isCategoryName)]]" on-tap="_select">
[[item.label]]
</paper-item>
</template>
</iron-selector>
</div>
</paper-menu-button>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'rg-select',
properties: {
// optional validator function wrapped in an Object: {validate: (val) => {}}
validator: Object,
label: {
type: String,
value: ''
},
multi: {
type: Boolean,
value: false
},
name: String,
required: Boolean,
// array of {key, label, isCategoryName, occursInContext} objects feed
options: Array,
// internal copy of options.
_options: {
type: Array,
computed: '_copyArray(options)'
},
// Set and Get key value of selected key (works only if multi=false)
selectedKey: {
type: String,
notify: true,
observer: '_selectedKeyChange'
},
// Set selected keys if multi=true. Get is implemented using 'on-selected-keys-change' event.
selectedKeys: Array,
// internal copy of selectedKeys.
_selectedKeys: {
type: Array,
computed: '_copyArray(selectedKeys)'
},
// show or hide a search bar
enableSearch: {
type: Boolean,
value: false
},
// return selected items with occursInContext === false
invalidSelectedKeys: {
type: Array,
notify: true,
value: []
},
_searchQuery: String,
// value to show in the 'rg-select-input' trigger
_valueToShow: String,
},
observers: [
'_selectedKeysChange(_selectedKeys.*)',
'_evalInvalidSelectedItems(options, selectedKey)',
'_evalInvalidSelectedItems(options, selectedKeys.*)'
],
ready() {
// Closes dropdown if clicking outsite the element
window.addEventListener('click', (ev) => {
for (var element = ev.target; element; element = element.parentNode) {
if (element === this) {
return;
}
}
this._close();
});
},
_selectedKeyChange(newVal, oldVal) {
if (Util.hasText(newVal) && !this.multi) {
this.set('_valuetoShow', newVal);
}
if (Util.exists(newVal) && Util.exists(oldVal) && newVal !== oldVal) {
this.fire('selected-key-change', newVal);
}
},
_selectedKeysChange(vals) {
if (!Util.exists(vals) || !Util.exists(vals.base))
return;
if (this.multi) {
this.set('_valuetoShow', vals.base.join(", "));
this.debounce('_selectedKeysChange', () => {
if (JSON.stringify(vals.base) !== JSON.stringify(this.selectedKeys)) {
this.fire('selected-keys-change', vals.base);
}
}, 50);
}
},
_select(ev) {
// don't close on multiselect and if item could not be selected (e.g. groupname)
if (!this.multi && ev.target.className.indexOf('selectable') > -1) {
this._close();
}
},
_getItemCssClass(occursInContext) {
return occursInContext === false ? 'disabled' : '';
},
_getSelectableCssClass(isCategoryName) {
return isCategoryName === true ? 'groupName' : 'selectable';
},
_onOpen() {
if (this.enableSearch) {
// search input needs to be visible before it can be focussed.
this.async(() => this.$.searchInput.focus(), 300);
}
},
_onClose() {
// wait for close animation to complete before resetting the filter
this.async(() => { this.set('_searchQuery', null); }, 200);
},
_close() {
// to close dropdown upon selecting an already selected item
this.$.dropdown.close();
},
_computeSearchFilter(searchText) {
return Util.hasText(searchText) ? option => Util.isStringContainingIgnoreCase(option.label, searchText) : null;
},
_evalInvalidSelectedItems(options, selected) {
if (Util.notEmptyArray(this.options) && Util.exists(selected)) {
let selectedOptions;
if (this.multi) {
selectedOptions = options.filter(opt => selected.base.indexOf(opt.key) > -1);
} else {
selectedOptions = options.filter(opt => opt.key === selected);
}
this.set('invalidSelectedKeys', selectedOptions.filter(opt => opt.occursInContext === false).map(i => i.key));
}
},
_copyArray(arr) {
return Util.exists(arr) ? arr.slice() : [];
}
});
})();
</script>
</dom-module>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment