Created
February 17, 2017 15:02
-
-
Save tonypee/e1584ff1f8131dd5644e82ab5b60d1c5 to your computer and use it in GitHub Desktop.
Revisions
-
tonypee created this gist
Feb 17, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,274 @@ <style scoped lang="less"> @import "../../styles/variables.less"; .select-container { position: relative; width: 210px; &.invalid { button { border: 1px solid @color_error!important; } } button { width: 100%; text-align: left; padding: 0 16px; } button:active, button:hover, button:focus { border-color: #ccc; } &.active { button { border-radius: 5px 5px 0 0; } .select-menu { border-top: none; border-radius: 0 0 5px 5px; } } &.up { &.active button { border-radius: 0 0 5px 5px; } .select-menu { bottom: 37px; border-radius: 5px 5px 0 0; box-shadow: 0px -2px 3px rgba(0, 0, 0, .175); } } // force scroll bars ::-webkit-scrollbar { -webkit-appearance: none; max-width: 8px; max-height: 8px; } ::-webkit-scrollbar-thumb { border-radius: 5px; background-color: rgba(0,0,0,.35); -webkit-box-shadow: 0 0 1px rgba(255,255,255,.35); } ul.select-menu { z-index: 999; list-style-type: none; margin: 0; padding: 0; box-shadow: 0px 2px 3px rgba(0, 0, 0, .175); position: absolute; border: 1px solid #ccc; border-radius: 5px; background: white; overflow: -moz-scrollbars-vertical; overflow-y: scroll; max-height: 300px; width: 100%; a.item { padding: 7px 16px; display: block; &:hover { cursor: pointer; background-color: #CFF6F8!important; } &.selected { background-color: #E7FAFB; } } li { margin: 0; } } .caret { position: relative; top: 5px; } .caret:before { display: inline-block; float: right; margin-top: 16px; margin-right: -4px; color: #999; border-style: solid; border-width: 5px 5px 0 5px; border-color: #999999 transparent transparent transparent; content: ""; } .search { width: 100%; padding: 8px; border-width: 0 0 1px 0; } } </style> <template> <div :class="['select-container', show && 'active', !this.isValid && 'invalid', up && 'up']" v-click-outside="onClickOutside"> <slot name="before"></slot> <slot name="button"> <button type="button" @click="toggle" @keyup.esc="show = false" :disabled="disabled"> <span class="caret"></span> <span class="label" v-if="mode === 'dropdown' || options.length"> <span v-if="!multiple">{{ getLabel(value) || placeholder }}</span> <span v-if="multiple" > <span v-if="value && value.length"> <span v-for="(val, index) of value"> {{ getLabel(val) }}{{ index < (value.length -1) ? ',' : '' }} </span> {{ value.length == 0 ? placeholder : '' }} </span> <span v-else> {{ placeholder }} </span> </span> </span> <span class="label" v-else> {{ emptyMessage }} </span> </button> </slot> <slot name="select-menu"> <ul class="select-menu" v-show="show && options.length" @click="onClickMenu"> <span v-if="!multiple && showSearch"> <input v-model="search" ref="search" class="search" placeholder="Search Items..." /> </span> <a :class="['item', isSelected(option.value) && 'selected']" v-for="option of filteredObjects" @click="select(option.value)">{{ option.label }}</a> </ul> </slot> </div> </template> <script> import { Component } from 'vue-property-decorator' import ClickOutside from '../../core/directives/ClickOutside.js' import { isObject } from '../../core/utility' import validatejs from 'validate.js' import validation from '../../core/mixins/validation' @Component({ props: { name: { type: String }, value: {}, constraint: { type: Object, default: () => null }, options: { type: Array, default: () => [] }, multiple: { type: Boolean, default: false }, placeholder: { type: String, default: '- Select -' }, label: { type: String, default: null }, valueFrom: { type: String, default: 'value' }, labelFrom: { type: String, default: 'label' }, emptyMessage: { type: String, default: 'No Data' }, mode: { type: String, default: 'select' }, showSearch: { type: Boolean, default: false }, up: { type: Boolean, default: false }, }, directives: { ClickOutside }, mixins: [ validation ] }) export default class select { componentType = 'input' show = false disabled = false search = '' get filteredObjects() { return this.optionObjects.filter(o => { return !this.search.length || ~o.label.toUpperCase().indexOf(this.search.toUpperCase()) }) } get optionObjects() { return this.options.map(option => { return isObject(option) ? { label: option[this.labelFrom], value: option[this.valueFrom], } : { label: option, value: option, } }) } get labelMap() { return this.optionObjects.reduce((o, v) => { o[String(v.value)] = v.label return o }, {}) } getLabel(value) { if (this.mode === 'dropdown') { return this.placeholder } else { return this.labelMap[String(value)] } } select(value) { if (this.multiple) { const existing = this.value.concat() if (!~existing.indexOf(value)) { existing.push(value) } else { existing.splice(existing.indexOf(value), 1) } this.$emit('input', existing) } else { this.$emit('input', value) this.$emit('select', value) } } toggle() { this.show = !this.show this.search = '' setTimeout(() => { if (this.show) { this.$refs.search && this.$refs.search.focus() } }) } onClickMenu(e) { if (e.target === this.$refs.search) { return } this.hide() } onClickOutside() { this.hide() } hide() { this.show = false } validate() { const constraint = this.constraint || (this.form && this.form.constraints && this.form.constraints[this.name]) if (!constraint || this.multiple) { return } this.errors = validatejs.single(this.value, constraint) this.isValid = !this.errors this.$emit('error', this.errors) return this.errors } isSelected(option) { if (this.multiple) { return ~this.value.indexOf(option) } else { return this.value === option } } } </script>