Created
February 17, 2017 15:02
-
-
Save tonypee/e1584ff1f8131dd5644e82ab5b60d1c5 to your computer and use it in GitHub Desktop.
This file contains 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 characters
<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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment