2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-22 10:30:34 +03:00

enforce value param to be of type array

This commit is contained in:
Jeff Sagal
2016-03-07 16:42:49 -08:00
parent a3d2a739ab
commit 50d1991641
5 changed files with 219 additions and 108 deletions
+48 -38
View File
File diff suppressed because one or more lines are too long
+76 -14
View File
@@ -4,22 +4,12 @@
<h1>Vue Select</h1> <h1>Vue Select</h1>
<p class="lead">A simple component that provides similar functionality to Select2 without the overhead of jQuery.</p> <p class="lead">A simple component that provides similar functionality to Select2 without the overhead of jQuery.</p>
<div class="row"> <hr>
<div class="col-md-6">
<h3>Options</h3>
<div class="checkbox">
<label>
<input v-model="multiple" type="checkbox"> Allow Multiple
</label>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<v-select <v-select
placeholder="choose something" :placeholder="placeholder"
:value.sync="select" :value.sync="select"
:options="options" :options="options"
:multiple="multiple"> :multiple="multiple">
@@ -30,20 +20,92 @@
<pre><strong>Selected:</strong>{{ select | json }}</pre> <pre><strong>Selected:</strong>{{ select | json }}</pre>
</div> </div>
</div> </div>
<hr>
<div class="row">
<div class="col-md-6">
<h4>Todos:</h4>
<ul>
<li>Fix layout issue where selected tags & text input overflow outside <code>.dropdown</code>.</li>
<li>Clicking the 'caret' icon should toggle the dropdown.</li>
<li>Add boolean prop to disable search.</li>
<li>Add a 'simple' prop that disables search, and the selected 'tags'. Uses an active class on the selected item(s) while keeping the placeholder constant.</li>
</ul>
</div>
<div class="col-md-6">
<h4>Component Props</h4>
<div class="form-group">
<label class="control-label">Max Height</label>
<input type="text" v-model="maxHeight" class="form-control">
<span class="help-block">Limit the height of the dropdown menu.</span>
</div>
<div class="form-group">
<label class="control-label">Placeholder</label>
<input type="text" v-model="placeholder" class="form-control">
<span class="help-block">Equivalent to the <code>placeholder</code> attribute.</span>
</div>
<div class="checkbox">
<label class="control-label">
<input v-model="multiple" type="checkbox"> Multiple
</label>
<span class="help-block">Equivalent to the <code>multiple</code> attribute to a <code>&#x3C;select&#x3E;</code></span>
</div>
<div class="form-group">
</div>
</div>
</div>
<hr>
<h3>Build Setup</h3>
<h5>install dependencies</h5>
<p><code>npm install</code></p>
<h5>serve with hot reload at localhost:8080</h5>
<p><code>npm run dev</code></p>
<h5>build for production with minification</h5>
<p><code>npm run build</code></p>
<h5>lint all *.js and *.vue files</h5>
<p><code>npm run lint</code></p>
<h5>run unit tests</h5>
<p><code>npm test</code></p>
</div> </div>
</template> </template>
<script> <script>
import Vue from 'vue'
import vSelect from './components/Select.vue' import vSelect from './components/Select.vue'
Vue.config.debug = true
export default { export default {
components: {vSelect}, components: {vSelect},
data() { data() {
return { return {
select: [], // select: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}],
select: ['one'],
placeholder: 'Choose a Country',
multiple: true, multiple: true,
options: require('./countries.js') maxHeight: '400px',
// options: require('./countries.js')
options: ['one','two','three']
// options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}, {label: 'This is Baz', value: 'baz'}]
} }
} }
} }
+55 -39
View File
@@ -1,10 +1,13 @@
<style scoped> <style scoped>
.dropdown {
position: relative;
}
.dropdown-toggle { .dropdown-toggle {
display: block; display: block;
padding: 0; padding: 0;
max-width: 100%;
background: none; background: none;
text-align: left; border: 1px solid rgba(60,60,60,.26);
} }
.dropdown-toggle:hover, .dropdown-toggle:hover,
@@ -27,7 +30,7 @@
.dropdown-menu { .dropdown-menu {
margin: 0; margin: 0;
max-height: 400px; width: 100%;
overflow-y: scroll; overflow-y: scroll;
border-top: none; border-top: none;
border-color: #337ab7; border-color: #337ab7;
@@ -37,7 +40,7 @@
.alert { .alert {
margin: 0; margin: 0;
margin-left: .25em; margin-left: 6px;
padding: .25em; padding: .25em;
} }
@@ -55,6 +58,7 @@
width: 100%; width: 100%;
background: none; background: none;
position: relative; position: relative;
box-shadow: none;
} }
input[type=search]:focus { input[type=search]:focus {
@@ -67,8 +71,6 @@
.dropdown-toggle:after { .dropdown-toggle:after {
display: block; display: block;
/*width: 10px;
height: 10px;*/
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
@@ -79,7 +81,9 @@
line-height: 1; line-height: 1;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
content: "\e114"; content: "\e114";
transition: all .25s;
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
} }
.open .dropdown-toggle:after { .open .dropdown-toggle:after {
@@ -94,17 +98,22 @@
cursor: pointer; cursor: pointer;
} }
.highlight a, .active a {
.active.highlight a {
background: rgba(50,50,50,.1); background: rgba(50,50,50,.1);
color: #333; color: #333;
} }
.highlight a,
li:hover a {
background: #337ab7;
color: #fff;
}
</style> </style>
<template> <template>
<div class="dropdown" :class="{open: open, }"> <div class="dropdown" :class="{open: open, }">
<div v-el:toggle @click="toggleDropdown" class="btn btn-default dropdown-toggle clearfix" type="button"> <div v-el:toggle @click="toggleDropdown" class="btn dropdown-toggle clearfix" type="button">
<span class="form-control" v-if="! searchable && placeholder && ! selected.length"> <span class="form-control" v-if="! searchable && placeholder && ! value.length">
{{ placeholder }} {{ placeholder }}
</span> </span>
@@ -116,22 +125,25 @@
</span> </span>
<input <input
v-el:search
v-show="searchable"
v-model="search"
@keydown.delete="maybeDeleteValue" @keydown.delete="maybeDeleteValue"
@keydown.esc="onEscape" @keydown.esc="onEscape"
@keydown.up.prevent="typeAheadUp" @keydown.up.prevent="typeAheadUp"
@keydown.down.prevent="typeAheadDown" @keydown.down.prevent="typeAheadDown"
@keydown.enter.prevent="typeAheadSelect" @keydown.enter.prevent="typeAheadSelect"
v-bind:placeholder="searchPlaceholder" @focus="open = true"
@blur="open = false"
type="search"
class="form-control" class="form-control"
:class="{inline: selected.length}"
v-el:search type="search" :placeholder="searchPlaceholder"
v-show="searchable"
v-model="search"
> >
</div> </div>
<ul v-if="open" :style="{width: dropdownWidth + 'px'}" :transition="transition" class="dropdown-menu animated"> <ul v-show="open" :transition="transition" :style="{ 'max-height': maxHeight }" class="dropdown-menu animated">
<li v-for="option in filteredOptions" :class="{ active: value.indexOf(option.value) !== -1, highlight: $index === typeAheadPointer }"> <li v-for="option in filteredOptions" :class="{ active: value.indexOf(option) !== -1, highlight: $index === typeAheadPointer }">
<a @mousedown.prevent="select(option)"> <a @mousedown.prevent="select(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
</a> </a>
@@ -148,9 +160,14 @@
props: { props: {
value: { value: {
type: Array,
twoway: true, twoway: true,
required: true required: true
}, },
maxHeight: {
type: String,
default: '400px'
},
searchable: { searchable: {
type: Boolean, type: Boolean,
default: true default: true
@@ -161,7 +178,7 @@
}, },
placeholder: { placeholder: {
type: String, type: String,
default: null default: ''
}, },
transition: { transition: {
type: String, type: String,
@@ -216,9 +233,9 @@
}, },
toggleDropdown( e ) { toggleDropdown( e ) {
if( e.target == this.$els.toggle || e.target == this.$els.search ) { // if( e.target == this.$els.toggle || e.target == this.$els.search ) {
this.open = !this.open // this.open = !this.open
} // }
}, },
getOptionValue( option ) { getOptionValue( option ) {
@@ -247,7 +264,7 @@
typeAheadSelect() { typeAheadSelect() {
if( this.filteredOptions[ this.typeAheadPointer ] ) { if( this.filteredOptions[ this.typeAheadPointer ] ) {
this.select( this.filteredOptions[ this.typeAheadPointer ] ); this.select( this.filteredOptions[ this.typeAheadPointer ] );
} }
this.search = ""; this.search = "";
}, },
@@ -268,18 +285,21 @@
}, },
computed: { computed: {
selected() { // selected() {
let foundItems = [] // let foundItems = []
if (this.value.length) { // if (this.value && this.value.length) {
for (let item in this.value) { // for (let item in this.value) {
if (typeof this.value[item] === "string") { // if (typeof this.value[item] === "string") {
foundItems.push(this.value[item]) // foundItems.push(this.value[item])
} // }
} //
} // console.log(this.value[item])
// // this.$log('value')
return foundItems // }
}, // }
//
// return foundItems
// },
searchPlaceholder() { searchPlaceholder() {
if( ! this.value.length && this.placeholder ) { if( ! this.value.length && this.placeholder ) {
@@ -287,10 +307,6 @@
} }
}, },
dropdownWidth() {
return this.$els.toggle.offsetWidth
},
filteredOptions() { filteredOptions() {
return this.$options.filters.filterBy(this.options, this.search) return this.$options.filters.filterBy(this.options, this.search)
} }
-17
View File
@@ -1,17 +0,0 @@
/* global describe, it, expect */
import Vue from 'vue'
import Hello from '../../src/components/Hello.vue'
describe('Hello.vue', () => {
it('should render correct contents', () => {
const vm = new Vue({
template: '<div><hello></hello></div>',
components: { Hello }
}).$mount()
expect(vm.$el.querySelector('.hello h1').textContent).toBe('Hello World!')
})
})
// also see example testing a component with mocks at
// https://github.com/vuejs/vueify-example/blob/master/test/unit/a.spec.js#L22-L43
+40
View File
@@ -0,0 +1,40 @@
/* global describe, it, expect */
import Vue from 'vue'
import vSelect from '../../src/components/Select.vue'
describe('Select.vue', () => {
it('can accept an array with pre-selected values', () => {
const vm = new Vue({
template: '<div><v-select :value.sync="value"></v-select></div>',
components: { vSelect },
data: {
value: ['one'],
options: ['one','two','three']
}
}).$mount()
expect(vm.$children[0].value).toEqual(['one'])
})
/**
* TODO: Right now this only works for arrays of strings.. But the same method
* should apply to arrays of objects.
*/
it('can accept an array of objects and pre-selected values', () => {
const vm = new Vue({
template: '<div><v-select :value.sync="value"></v-select></div>',
components: { vSelect },
data: {
value: [{label: 'This is Foo', value: 'foo'}],
options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}]
}
}).$mount()
expect(vm.$children[0].$get('value')).toEqual({label: 'This is Foo', value: 'foo'})
})
})
// also see example testing a component with mocks at
// https://github.com/vuejs/vueify-example/blob/master/test/unit/a.spec.js#L22-L43