Skip to content

Commit a25b9f3

Browse files
author
Damian Dulisz
committed
Groups
1 parent c488534 commit a25b9f3

File tree

12 files changed

+186
-52
lines changed

12 files changed

+186
-52
lines changed

docs/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ new Vue({
4343
values: ['2', '2a', '2b', 'bcc', 'aa', 'ee2', 'ee33']
4444
}
4545
],
46-
groups2: [
46+
groupsWithObjects: [
4747
{
4848
groupLabel: 'grupa 1',
4949
values: [

docs/partials/_nav.jade

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ul.list(
1515
+nav-element('Multiselect with search', 'multiselect-search')
1616
+nav-element('Asynchronous select', 'ajax')
1717
+nav-element('Tagging', 'tagging')
18+
+nav-element('Option groups', 'groups')
1819
+nav-element('Action select', 'action')
1920
+nav-element('Custom configuration', 'custom')
2021

docs/partials/examples/_groups.jade

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
h2.typo__h2#tagging Groups
1+
h2.typo__h2#groups Groups
22
p.typo__p
33
| To enable groups...
44
.grid__row
55
.grid__column.grid__unit--md-5
66
label.typo__label Groups
77
multiselect(
88
v-model="groupsSelected",
9-
:options="groups2",
9+
:options="groupsWithObjects",
1010
:multiple="true",
1111
:searchable="true",
1212
:close-on-select="false",

lib/Multiselect.vue

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
@blur="searchable ? false : deactivate()"
77
@keydown.self.down.prevent="pointerForward()"
88
@keydown.self.up.prevent="pointerBackward()"
9-
@keydown.enter.stop.prevent.self="addPointerElement()"
10-
@keydown.tab.stop.prevent.self="addPointerElement()"
9+
@keydown.enter.tab.stop.prevent.self="addPointerElement($event)"
1110
@keyup.esc="deactivate()"
1211
class="multiselect">
1312
<div @mousedown.prevent="toggle()" class="multiselect__select"></div>
@@ -44,17 +43,16 @@
4443
@focus.prevent="activate()"
4544
@blur.prevent="deactivate()"
4645
@keyup.esc="deactivate()"
47-
@keyup.down="pointerForward()"
48-
@keyup.up="pointerBackward()"
49-
@keydown.enter.stop.prevent.self="addPointerElement()"
50-
@keydown.tab.stop.prevent.self="addPointerElement()"
46+
@keydown.down.prevent="pointerForward()"
47+
@keydown.up.prevent="pointerBackward()"
48+
@keydown.enter.tab.stop.prevent.self="addPointerElement($event)"
5149
@keydown.delete="removeLastElement()"
5250
class="multiselect__input"/>
53-
<span
54-
v-if="!searchable && !multiple"
55-
class="multiselect__single"
56-
v-text="currentOptionLabel || placeholder">
57-
</span>
51+
<span
52+
v-if="!searchable"
53+
class="multiselect__single"
54+
v-text="currentOptionLabel || placeholder">
55+
</span>
5856
</div>
5957
<transition name="multiselect">
6058
<ul
@@ -72,6 +70,7 @@
7270
<li v-for="(option, index) of filteredOptions" :key="index">
7371
<span
7472
tabindex="0"
73+
v-if="!option.$isLabel"
7574
:class="optionHighlight(index, option)"
7675
@mousedown.prevent="select(option)"
7776
@mouseenter="pointerSet(index)"
@@ -85,6 +84,12 @@
8584
:option="option">
8685
</multiselect-option>
8786
</span>
87+
<span
88+
v-if="option.$isLabel"
89+
:class="optionHighlight(index, option)"
90+
class="multiselect__option multiselect__option--disabled">
91+
{{ option.$groupLabel }}
92+
</span>
8893
</li>
8994
</template>
9095
<li v-show="filteredOptions.length === 0 && search">
@@ -329,7 +334,8 @@ fieldset[disabled] .multiselect {
329334
margin-bottom: 8px;
330335
}
331336
332-
.multiselect__tag ~ .multiselect__input {
337+
.multiselect__tag ~ .multiselect__input,
338+
.multiselect__tag ~ .multiselect__single {
333339
width: auto;
334340
}
335341
@@ -551,16 +557,11 @@ fieldset[disabled] .multiselect {
551557
background: #ededed;
552558
color: #a6a6a6;
553559
cursor: text;
554-
pointer-events: none;
555-
}
556-
557-
.multiselect__option--disabled:visited {
558-
color: #a6a6a6;
560+
/*pointer-events: none;*/
559561
}
560562
561-
.multiselect__option--disabled:hover,
562-
.multiselect__option--disabled:focus {
563-
background: #3dad7b;
563+
.multiselect__option--disabled.multiselect__option--highlight {
564+
background: #dedede !important;
564565
}
565566
566567
.multiselect-enter-active,

lib/multiselectMixin.js

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,46 @@ function includes (str, query) {
66
return text.indexOf(query) !== -1
77
}
88

9+
function filterOptions (options, search, label) {
10+
return label
11+
? options.filter(option => includes(option[label], search))
12+
: options.filter(option => includes(option, search))
13+
}
14+
15+
function stripGroups (options) {
16+
return options.filter(option => !option.$isLabel)
17+
}
18+
19+
function flattenOptions (values, label) {
20+
return (options) =>
21+
options.reduce((prev, curr) => {
22+
if (curr[values] && curr[values].length) {
23+
prev.push({
24+
$groupLabel: curr[label],
25+
$isLabel: true
26+
})
27+
return prev.concat(curr[values])
28+
}
29+
return prev.concat(curr)
30+
}, [])
31+
}
32+
33+
function filterGroups (search, label, values, groupLabel) {
34+
return (groups) =>
35+
groups.map(group => {
36+
const groupOptions = filterOptions(group[values], search, label)
37+
38+
return groupOptions.length
39+
? {
40+
[groupLabel]: group[groupLabel],
41+
[values]: groupOptions
42+
}
43+
: []
44+
})
45+
}
46+
47+
const flow = (...fns) => x => fns.reduce((v, f) => f(v), x)
48+
949
module.exports = {
1050
data () {
1151
return {
@@ -197,6 +237,12 @@ module.exports = {
197237
optionsLimit: {
198238
type: Number,
199239
default: 1000
240+
},
241+
groupKey: {
242+
type: String
243+
},
244+
groupLabel: {
245+
type: String
200246
}
201247
},
202248
created () {
@@ -205,17 +251,23 @@ module.exports = {
205251
computed: {
206252
filteredOptions () {
207253
let search = this.search || ''
208-
let options = this.hideSelected
209-
? this.options.filter(this.isNotSelected)
210-
: this.options
254+
255+
let options = this.options
256+
211257
if (this.localSearch) {
212-
options = this.label
213-
? options.filter(option => includes(option[this.label], this.search))
214-
: options.filter(option => includes(option, this.search))
258+
options = this.groupKey
259+
? this.filterAndFlat(options, search, this.label)
260+
: filterOptions(options, search, this.label)
261+
262+
options = this.hideSelected
263+
? options.filter(this.isNotSelected)
264+
: options
215265
}
266+
216267
if (this.taggable && search.length && !this.isExistingOption(search)) {
217268
options.unshift({ isTag: true, label: search })
218269
}
270+
219271
return options.slice(0, this.optionsLimit)
220272
},
221273
valueKeys () {
@@ -228,12 +280,13 @@ module.exports = {
228280
}
229281
},
230282
optionKeys () {
283+
const options = this.groupKey ? this.flatAndStrip(this.options) : this.options
231284
return this.label
232-
? this.options.map(element => element[this.label].toString().toLowerCase())
233-
: this.options.map(element => element.toString().toLowerCase())
285+
? options.map(element => element[this.label].toString().toLowerCase())
286+
: options.map(element => element.toString().toLowerCase())
234287
},
235288
currentOptionLabel () {
236-
return this.getOptionLabel(this.internalValue)
289+
return this.getOptionLabel(this.internalValue) + ''
237290
}
238291
},
239292
watch: {
@@ -255,8 +308,20 @@ module.exports = {
255308
}
256309
},
257310
methods: {
311+
filterAndFlat (options) {
312+
return flow(
313+
filterGroups(this.search, this.label, this.groupKey, this.groupLabel),
314+
flattenOptions(this.groupKey, this.groupLabel)
315+
)(options)
316+
},
317+
flatAndStrip (options) {
318+
return flow(
319+
flattenOptions(this.groupKey, this.groupLabel),
320+
stripGroups
321+
)(options)
322+
},
258323
updateSearch (query) {
259-
this.search = query.trim().toLowerCase()
324+
this.search = query.trim().toLowerCase().toString()
260325
},
261326
/**
262327
* Finds out if the given query is already present
@@ -308,24 +373,25 @@ module.exports = {
308373
getOptionLabel (option) {
309374
if (!option && option !== 0) return ''
310375
if (option.isTag) return option.label
311-
return this.customLabel(option, this.label) + ''
376+
return this.customLabel(option, this.label) || ''
312377
},
313378
/**
314379
* Add the given option to the list of selected options
315380
* or sets the option as the selected option.
316381
* If option is already selected -> remove it from the results.
317382
*
318383
* @param {Object||String||Integer} option to select/deselect
384+
* @param {Boolean} block removing
319385
*/
320-
select (option) {
386+
select (option, cantRemove) {
321387
if (this.max && this.multiple && this.internalValue.length === this.max) return
322388
if (option.isTag) {
323389
this.$emit('tag', option.label, this.id)
324390
this.search = ''
325391
} else {
326392
if (this.multiple) {
327393
if (this.isSelected(option)) {
328-
this.removeElement(option)
394+
if (!cantRemove) this.removeElement(option)
329395
return
330396
} else {
331397
this.internalValue.push(option)
@@ -334,7 +400,7 @@ module.exports = {
334400
const isSelected = this.isSelected(option)
335401

336402
/* istanbul ignore else */
337-
if (isSelected && !this.allowEmpty) return
403+
if (isSelected && (!this.allowEmpty || cantRemove)) return
338404

339405
this.internalValue = isSelected ? null : option
340406
}

lib/pointerMixin.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ module.exports = {
3333
'multiselect__option--selected': this.isSelected(option)
3434
}
3535
},
36-
addPointerElement () {
36+
addPointerElement (e = 'Enter') {
37+
if (this.filteredOptions[this.pointer].isLabel) return
3738
if (this.filteredOptions.length > 0) {
38-
this.select(this.filteredOptions[this.pointer])
39+
this.select(this.filteredOptions[this.pointer], e.key === 'Tab')
3940
}
4041
this.pointerReset()
4142
},

lib/vue-multiselect.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"sinon": "^1.17.3",
7979
"sinon-chai": "^2.8.0",
8080
"url-loader": "^0.5.7",
81-
"vue": "^2.0.0",
81+
"vue": "^2.0.7",
8282
"vue-hot-reload-api": "^1.2.0",
8383
"vue-html-loader": "^1.0.0",
8484
"vue-loader": "^9.7.0",

src/Multiselect.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
@blur="searchable ? false : deactivate()"
77
@keydown.self.down.prevent="pointerForward()"
88
@keydown.self.up.prevent="pointerBackward()"
9-
@keydown.enter.stop.prevent.self="addPointerElement()"
10-
@keydown.tab.stop.prevent.self="addPointerElement()"
9+
@keydown.enter.tab.stop.prevent.self="addPointerElement($event)"
1110
@keyup.esc="deactivate()"
1211
class="multiselect">
1312
<div @mousedown.prevent="toggle()" class="multiselect__select"></div>
@@ -32,7 +31,6 @@
3231
<div v-show="loading" class="multiselect__spinner"></div>
3332
</transition>
3433
<input
35-
name="search"
3634
ref="search"
3735
type="text"
3836
autocomplete="off"
@@ -46,8 +44,7 @@
4644
@keyup.esc="deactivate()"
4745
@keydown.down.prevent="pointerForward()"
4846
@keydown.up.prevent="pointerBackward()"
49-
@keydown.enter.stop.prevent.self="addPointerElement()"
50-
@keydown.tab.stop.prevent.self="addPointerElement()"
47+
@keydown.enter.tab.stop.prevent.self="addPointerElement($event)"
5148
@keydown.delete="removeLastElement()"
5249
class="multiselect__input"/>
5350
<span

0 commit comments

Comments
 (0)