iview/src/components/cascader/cascader.vue
2018-03-15 10:49:29 +08:00

400 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div :class="classes" v-clickoutside="handleClose">
<div :class="[prefixCls + '-rel']" @click="toggleOpen" ref="reference">
<input type="hidden" :name="name" :value="currentValue">
<slot>
<i-input
:element-id="elementId"
ref="input"
:readonly="!filterable"
:disabled="disabled"
:value="displayInputRender"
@on-change="handleInput"
:size="size"
:placeholder="inputPlaceholder"></i-input>
<div
:class="[prefixCls + '-label']"
v-show="filterable && query === ''"
@click="handleFocus">{{ displayRender }}</div>
<Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSelect"></Icon>
<Icon type="arrow-down-b" :class="[prefixCls + '-arrow']"></Icon>
</slot>
</div>
<transition name="slide-up">
<Drop
v-show="visible"
:class="{ [prefixCls + '-transfer']: transfer }"
ref="drop"
:data-transfer="transfer"
v-transfer-dom>
<div>
<Caspanel
v-show="!filterable || (filterable && query === '')"
ref="caspanel"
:prefix-cls="prefixCls"
:data="data"
:disabled="disabled"
:change-on-select="changeOnSelect"
:trigger="trigger"></Caspanel>
<div :class="[prefixCls + '-dropdown']" v-show="filterable && query !== '' && querySelections.length">
<ul :class="[selectPrefixCls + '-dropdown-list']">
<li
:class="[selectPrefixCls + '-item', {
[selectPrefixCls + '-item-disabled']: item.disabled
}]"
v-for="(item, index) in querySelections"
@click="handleSelectItem(index)" v-html="item.display"></li>
</ul>
</div>
<ul v-show="filterable && query !== '' && !querySelections.length" :class="[prefixCls + '-not-found-tip']"><li>{{ localeNotFoundText }}</li></ul>
</div>
</Drop>
</transition>
</div>
</template>
<script>
import iInput from '../input/input.vue';
import Drop from '../select/dropdown.vue';
import Icon from '../icon/icon.vue';
import Caspanel from './caspanel.vue';
import clickoutside from '../../directives/clickoutside';
import TransferDom from '../../directives/transfer-dom';
import { oneOf } from '../../utils/assist';
import Emitter from '../../mixins/emitter';
import Locale from '../../mixins/locale';
const prefixCls = 'ivu-cascader';
const selectPrefixCls = 'ivu-select';
export default {
name: 'Cascader',
mixins: [ Emitter, Locale ],
components: { iInput, Drop, Icon, Caspanel },
directives: { clickoutside, TransferDom },
props: {
data: {
type: Array,
default () {
return [];
}
},
value: {
type: Array,
default () {
return [];
}
},
disabled: {
type: Boolean,
default: false
},
clearable: {
type: Boolean,
default: true
},
placeholder: {
type: String
},
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
},
trigger: {
validator (value) {
return oneOf(value, ['click', 'hover']);
},
default: 'click'
},
changeOnSelect: {
type: Boolean,
default: false
},
renderFormat: {
type: Function,
default (label) {
return label.join(' / ');
}
},
loadData: {
type: Function
},
filterable: {
type: Boolean,
default: false
},
notFoundText: {
type: String
},
transfer: {
type: Boolean,
default: false
},
name: {
type: String
},
elementId: {
type: String
}
},
data () {
return {
prefixCls: prefixCls,
selectPrefixCls: selectPrefixCls,
visible: false,
selected: [],
tmpSelected: [],
updatingValue: false, // to fix set value in changeOnSelect type
currentValue: this.value,
query: '',
validDataStr: '',
isLoadedChildren: false // #950
};
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-show-clear`]: this.showCloseIcon,
[`${prefixCls}-size-${this.size}`]: !!this.size,
[`${prefixCls}-visible`]: this.visible,
[`${prefixCls}-disabled`]: this.disabled,
[`${prefixCls}-not-found`]: this.filterable && this.query !== '' && !this.querySelections.length
}
];
},
showCloseIcon () {
return this.currentValue && this.currentValue.length && this.clearable && !this.disabled;
},
displayRender () {
let label = [];
for (let i = 0; i < this.selected.length; i++) {
label.push(this.selected[i].label);
}
return this.renderFormat(label, this.selected);
},
displayInputRender () {
return this.filterable ? '' : this.displayRender;
},
localePlaceholder () {
if (this.placeholder === undefined) {
return this.t('i.select.placeholder');
} else {
return this.placeholder;
}
},
inputPlaceholder () {
return this.filterable && this.currentValue.length ? null : this.localePlaceholder;
},
localeNotFoundText () {
if (this.notFoundText === undefined) {
return this.t('i.select.noMatch');
} else {
return this.notFoundText;
}
},
querySelections () {
let selections = [];
function getSelections (arr, label, value) {
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
item.__label = label ? label + ' / ' + item.label : item.label;
item.__value = value ? value + ',' + item.value : item.value;
if (item.children && item.children.length) {
getSelections(item.children, item.__label, item.__value);
delete item.__label;
delete item.__value;
} else {
selections.push({
label: item.__label,
value: item.__value,
display: item.__label,
item: item,
disabled: !!item.disabled
});
}
}
}
getSelections(this.data);
selections = selections.filter(item => {
return item.label ? item.label.indexOf(this.query) > -1 : false;
}).map(item => {
item.display = item.display.replace(new RegExp(this.query, 'g'), `<span>${this.query}</span>`);
return item;
});
return selections;
}
},
methods: {
clearSelect () {
if (this.disabled) return false;
const oldVal = JSON.stringify(this.currentValue);
this.currentValue = this.selected = this.tmpSelected = [];
this.handleClose();
this.emitValue(this.currentValue, oldVal);
// this.$broadcast('on-clear');
this.broadcast('Caspanel', 'on-clear');
},
handleClose () {
this.visible = false;
},
toggleOpen () {
if (this.disabled) return false;
if (this.visible) {
if (!this.filterable) this.handleClose();
} else {
this.onFocus();
}
},
onFocus () {
this.visible = true;
if (!this.currentValue.length) {
this.broadcast('Caspanel', 'on-clear');
}
},
updateResult (result) {
this.tmpSelected = result;
},
updateSelected (init = false, changeOnSelectDataChange = false) {
// #2793 changeOnSelectDataChange used for changeOnSelect when data changed and set value
if (!this.changeOnSelect || init || changeOnSelectDataChange) {
this.broadcast('Caspanel', 'on-find-selected', {
value: this.currentValue
});
}
},
emitValue (val, oldVal) {
if (JSON.stringify(val) !== oldVal) {
this.$emit('on-change', this.currentValue, JSON.parse(JSON.stringify(this.selected)));
this.$nextTick(() => {
this.dispatch('FormItem', 'on-form-change', {
value: this.currentValue,
selected: JSON.parse(JSON.stringify(this.selected))
});
});
}
},
handleInput (event) {
this.query = event.target.value;
},
handleSelectItem (index) {
const item = this.querySelections[index];
if (item.item.disabled) return false;
this.query = '';
this.$refs.input.currentValue = '';
const oldVal = JSON.stringify(this.currentValue);
this.currentValue = item.value.split(',');
this.emitValue(this.currentValue, oldVal);
this.handleClose();
},
handleFocus () {
this.$refs.input.focus();
},
// 排除 loading 后的 data避免重复触发 updateSelect
getValidData (data) {
function deleteData (item) {
const new_item = Object.assign({}, item);
if ('loading' in new_item) {
delete new_item.loading;
}
if ('__value' in new_item) {
delete new_item.__value;
}
if ('__label' in new_item) {
delete new_item.__label;
}
if ('children' in new_item && new_item.children.length) {
new_item.children = new_item.children.map(i => deleteData(i));
}
return new_item;
}
return data.map(item => deleteData(item));
}
},
created () {
this.validDataStr = JSON.stringify(this.getValidData(this.data));
this.$on('on-result-change', (params) => {
// lastValue: is click the final val
// fromInit: is this emit from update value
const lastValue = params.lastValue;
const changeOnSelect = params.changeOnSelect;
const fromInit = params.fromInit;
if (lastValue || changeOnSelect) {
const oldVal = JSON.stringify(this.currentValue);
this.selected = this.tmpSelected;
let newVal = [];
this.selected.forEach((item) => {
newVal.push(item.value);
});
if (!fromInit) {
this.updatingValue = true;
this.currentValue = newVal;
this.emitValue(this.currentValue, oldVal);
}
}
if (lastValue && !fromInit) {
this.handleClose();
}
});
},
mounted () {
this.updateSelected(true);
},
watch: {
visible (val) {
if (val) {
if (this.currentValue.length) {
this.updateSelected();
}
if (this.transfer) {
this.$refs.drop.update();
}
this.broadcast('Drop', 'on-update-popper');
} else {
if (this.filterable) {
this.query = '';
this.$refs.input.currentValue = '';
}
if (this.transfer) {
this.$refs.drop.destroy();
}
this.broadcast('Drop', 'on-destroy-popper');
}
this.$emit('on-visible-change', val);
},
value (val) {
this.currentValue = val;
if (!val.length) this.selected = [];
},
currentValue () {
this.$emit('input', this.currentValue);
if (this.updatingValue) {
this.updatingValue = false;
return;
}
this.updateSelected(true);
},
data: {
deep: true,
handler () {
const validDataStr = JSON.stringify(this.getValidData(this.data));
if (validDataStr !== this.validDataStr) {
this.validDataStr = validDataStr;
if (!this.isLoadedChildren) {
this.$nextTick(() => this.updateSelected(false, this.changeOnSelect));
}
this.isLoadedChildren = false;
}
}
}
}
};
</script>