iview/src/components/date-picker/picker.vue
2017-11-08 20:44:35 +08:00

549 lines
20 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="[prefixCls]" v-clickoutside="handleClose">
<div ref="reference" :class="[prefixCls + '-rel']">
<slot>
<i-input
:element-id="elementId"
:class="[prefixCls + '-editor']"
:readonly="!editable || readonly"
:disabled="disabled"
:size="size"
:placeholder="placeholder"
:value="visualValue"
:name="name"
@on-input-change="handleInputChange"
@on-focus="handleFocus"
@on-blur="handleBlur"
@on-click="handleIconClick"
@mouseenter.native="handleInputMouseenter"
@mouseleave.native="handleInputMouseleave"
:icon="iconType"></i-input>
</slot>
</div>
<transition :name="transition">
<Drop
@click.native="handleTransferClick"
v-show="opened"
:class="{ [prefixCls + '-transfer']: transfer }"
:placement="placement"
ref="drop"
:data-transfer="transfer"
v-transfer-dom>
<div ref="picker"></div>
</Drop>
</transition>
</div>
</template>
<script>
import iInput from '../../components/input/input.vue';
import Drop from '../../components/select/dropdown.vue';
import clickoutside from '../../directives/clickoutside';
import TransferDom from '../../directives/transfer-dom';
import { oneOf } from '../../utils/assist';
import { formatDate, parseDate } from './util';
import Emitter from '../../mixins/emitter';
const prefixCls = 'ivu-date-picker';
const DEFAULT_FORMATS = {
date: 'yyyy-MM-dd',
month: 'yyyy-MM',
year: 'yyyy',
datetime: 'yyyy-MM-dd HH:mm:ss',
time: 'HH:mm:ss',
timerange: 'HH:mm:ss',
daterange: 'yyyy-MM-dd',
datetimerange: 'yyyy-MM-dd HH:mm:ss'
};
const RANGE_SEPARATOR = ' - ';
const DATE_FORMATTER = function(value, format) {
return formatDate(value, format);
};
const DATE_PARSER = function(text, format) {
return parseDate(text, format);
};
const RANGE_FORMATTER = function(value, format) {
if (Array.isArray(value) && value.length === 2) {
const start = value[0];
const end = value[1];
if (start && end) {
return formatDate(start, format) + RANGE_SEPARATOR + formatDate(end, format);
}
}
return '';
};
const RANGE_PARSER = function(text, format) {
const array = text.split(RANGE_SEPARATOR);
if (array.length === 2) {
const range1 = array[0];
const range2 = array[1];
return [parseDate(range1, format), parseDate(range2, format)];
}
return [];
};
const TYPE_VALUE_RESOLVER_MAP = {
default: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
if (text === undefined || text === '') return null;
return text;
}
},
date: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
datetime: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
daterange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
datetimerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
timerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
time: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
month: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
year: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
number: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
let result = Number(text);
if (!isNaN(text)) {
return result;
} else {
return null;
}
}
}
};
export default {
name: 'CalendarPicker',
mixins: [ Emitter ],
components: { iInput, Drop },
directives: { clickoutside, TransferDom },
props: {
format: {
type: String
},
readonly: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
editable: {
type: Boolean,
default: true
},
clearable: {
type: Boolean,
default: true
},
confirm: {
type: Boolean,
default: false
},
open: {
type: Boolean,
default: null
},
size: {
validator (value) {
return oneOf(value, ['small', 'large', 'default']);
}
},
placeholder: {
type: String,
default: ''
},
placement: {
validator (value) {
return oneOf(value, ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end']);
},
default: 'bottom-start'
},
options: {
type: Object
},
transfer: {
type: Boolean,
default: false
},
name: {
type: String
},
elementId: {
type: String
}
},
data () {
return {
prefixCls: prefixCls,
showClose: false,
visible: false,
picker: null,
internalValue: '',
disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
disableCloseUnderTransfer: false, // transfer 模式下点击Drop也会触发关闭
currentValue: this.value
};
},
computed: {
opened () {
return this.open === null ? this.visible : this.open;
},
iconType () {
let icon = 'ios-calendar-outline';
if (this.type === 'time' || this.type === 'timerange') icon = 'ios-clock-outline';
if (this.showClose) icon = 'ios-close';
return icon;
},
transition () {
if (this.placement === 'bottom-start' || this.placement === 'bottom' || this.placement === 'bottom-end') {
return 'slide-up';
} else {
return 'slide-down';
}
},
selectionMode() {
if (this.type === 'month') {
return 'month';
} else if (this.type === 'year') {
return 'year';
}
return 'day';
},
visualValue: {
get () {
const value = this.internalValue;
if (!value) return;
const formatter = (
TYPE_VALUE_RESOLVER_MAP[this.type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).formatter;
const format = DEFAULT_FORMATS[this.type];
return formatter(value, this.format || format);
},
set (value) {
if (value) {
const type = this.type;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type]);
if (parsedValue) {
if (this.picker) this.picker.value = parsedValue;
}
return;
}
if (this.picker) this.picker.value = value;
}
}
},
methods: {
// 开启 transfer 时,点击 Drop 即会关闭,这里不让其关闭
handleTransferClick () {
if (this.transfer) this.disableCloseUnderTransfer = true;
},
handleClose () {
if (this.disableCloseUnderTransfer) {
this.disableCloseUnderTransfer = false;
return false;
}
if (this.open !== null) return;
// if (!this.disableClickOutSide) this.visible = false;
this.visible = false;
this.disableClickOutSide = false;
},
handleFocus () {
if (this.readonly) return;
this.visible = true;
},
handleBlur () {
this.visible = false;
},
handleInputChange (event) {
const oldValue = this.visualValue;
const value = event.target.value;
let correctValue = '';
let correctDate = '';
const type = this.type;
const format = this.format || DEFAULT_FORMATS[type];
if (type === 'daterange' || type === 'timerange' || type === 'datetimerange') {
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
const formatter = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).formatter;
const parsedValue = parser(value, format);
if (parsedValue[0] instanceof Date && parsedValue[1] instanceof Date) {
if (parsedValue[0].getTime() > parsedValue[1].getTime()) {
correctValue = oldValue;
} else {
correctValue = formatter(parsedValue, format);
}
// todo 判断disabledDate
} else {
correctValue = oldValue;
}
correctDate = parser(correctValue, format);
} else if (type === 'time') {
const parsedDate = parseDate(value, format);
if (parsedDate instanceof Date) {
if (this.disabledHours.length || this.disabledMinutes.length || this.disabledSeconds.length) {
const hours = parsedDate.getHours();
const minutes = parsedDate.getMinutes();
const seconds = parsedDate.getSeconds();
if ((this.disabledHours.length && this.disabledHours.indexOf(hours) > -1) ||
(this.disabledMinutes.length && this.disabledMinutes.indexOf(minutes) > -1) ||
(this.disabledSeconds.length && this.disabledSeconds.indexOf(seconds) > -1)) {
correctValue = oldValue;
} else {
correctValue = formatDate(parsedDate, format);
}
} else {
correctValue = formatDate(parsedDate, format);
}
} else {
correctValue = oldValue;
}
correctDate = parseDate(correctValue, format);
} else {
const parsedDate = parseDate(value, format);
if (parsedDate instanceof Date) {
const options = this.options || false;
if (options && options.disabledDate && typeof options.disabledDate === 'function' && options.disabledDate(new Date(parsedDate))) {
correctValue = oldValue;
} else {
correctValue = formatDate(parsedDate, format);
}
} else if (!parsedDate) {
correctValue = '';
} else {
correctValue = oldValue;
}
correctDate = parseDate(correctValue, format);
}
this.visualValue = correctValue;
event.target.value = correctValue;
this.internalValue = correctDate;
this.currentValue = correctDate;
if (correctValue !== oldValue) this.emitChange(correctDate);
},
handleInputMouseenter () {
if (this.readonly || this.disabled) return;
if (this.visualValue && this.clearable) {
this.showClose = true;
}
},
handleInputMouseleave () {
this.showClose = false;
},
handleIconClick () {
if (this.showClose) {
this.handleClear();
} else if (!this.disabled) {
this.handleFocus();
}
},
handleClear () {
this.visible = false;
this.internalValue = '';
this.currentValue = '';
this.$emit('on-clear');
this.dispatch('FormItem', 'on-form-change', '');
// #2215当初始设置了 value直接点 clear这时 this.picker 还没有加载
if (!this.picker) {
this.emitChange('');
}
},
showPicker () {
if (!this.picker) {
let isConfirm = this.confirm;
const type = this.type;
this.picker = this.Panel.$mount(this.$refs.picker);
if (type === 'datetime' || type === 'datetimerange') {
isConfirm = true;
this.picker.showTime = true;
}
this.picker.value = this.internalValue;
this.picker.confirm = isConfirm;
this.picker.selectionMode = this.selectionMode;
if (this.format) this.picker.format = this.format;
// TimePicker
if (this.disabledHours) this.picker.disabledHours = this.disabledHours;
if (this.disabledMinutes) this.picker.disabledMinutes = this.disabledMinutes;
if (this.disabledSeconds) this.picker.disabledSeconds = this.disabledSeconds;
if (this.hideDisabledOptions) this.picker.hideDisabledOptions = this.hideDisabledOptions;
const options = this.options;
for (const option in options) {
this.picker[option] = options[option];
}
this.picker.$on('on-pick', (date, visible = false) => {
if (!isConfirm) this.visible = visible;
this.currentValue = date;
this.picker.value = date;
this.picker.resetView && this.picker.resetView();
this.emitChange(date);
});
this.picker.$on('on-pick-clear', () => {
this.handleClear();
});
this.picker.$on('on-pick-success', () => {
this.visible = false;
this.$emit('on-ok');
});
this.picker.$on('on-pick-click', () => this.disableClickOutSide = true);
}
if (this.internalValue instanceof Date) {
this.picker.date = new Date(this.internalValue.getTime());
} else {
this.picker.value = this.internalValue;
}
this.picker.resetView && this.picker.resetView();
},
emitChange (date) {
const newDate = this.formattingDate(date);
this.$emit('on-change', newDate);
this.$nextTick(() => {
this.dispatch('FormItem', 'on-form-change', newDate);
});
},
formattingDate (date) {
const type = this.type;
const format = this.format || DEFAULT_FORMATS[type];
const formatter = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).formatter;
let newDate = formatter(date, format);
if (type === 'daterange' || type === 'timerange' || type === 'datetimerange') {
newDate = [newDate.split(RANGE_SEPARATOR)[0], newDate.split(RANGE_SEPARATOR)[1]];
}
return newDate;
}
},
watch: {
visible (val) {
if (val) {
this.showPicker();
this.$refs.drop.update();
if (this.open === null) this.$emit('on-open-change', true);
} else {
if (this.picker) this.picker.resetView && this.picker.resetView(true);
this.$refs.drop.destroy();
if (this.open === null) this.$emit('on-open-change', false);
// blur the input
const input = this.$el.querySelector('input');
if (input) input.blur();
}
},
internalValue(val) {
if (!val && this.picker && typeof this.picker.handleClear === 'function') {
this.picker.handleClear();
}
// this.$emit('input', val);
},
value (val) {
this.currentValue = val;
},
currentValue: {
immediate: true,
handler (val) {
const type = this.type;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
if (val && type === 'time' && !(val instanceof Date)) {
val = parser(val, this.format || DEFAULT_FORMATS[type]);
} else if (val && type.match(/range$/) && Array.isArray(val) && val.filter(Boolean).length === 2 && !(val[0] instanceof Date) && !(val[1] instanceof Date)) {
val = val.join(RANGE_SEPARATOR);
val = parser(val, this.format || DEFAULT_FORMATS[type]);
} else if (typeof val === 'string' && type.indexOf('time') !== 0 ){
val = parser(val, this.format || DEFAULT_FORMATS[type]) || val;
}
this.internalValue = val;
this.$emit('input', val);
}
},
open (val) {
if (val === true) {
this.visible = val;
this.$emit('on-open-change', true);
} else if (val === false) {
this.$emit('on-open-change', false);
}
}
},
beforeDestroy () {
if (this.picker) {
this.picker.$destroy();
}
},
mounted () {
if (this.open !== null) this.visible = this.open;
}
};
</script>