<template>
<div class="ion-select" :class="[modeClass,{'select-disabled':isDisabled}]">
<div v-if="!text" class="select-placeholder select-text">{{placeholder}}</div>
<div v-else class="select-text">{{selectedText || text}}</div>
<div class="select-icon">
<div class="select-icon-inner"></div>
</div>
<button @click="onPointerDownHandler($event)"
ref="button"
:id="id"
class="item-cover">
</button>
<slot></slot>
</div>
</template>
<style lang="scss" src="./style.scss"></style>
<script type="text/javascript">
/**
* @name Select
* @component Select
* @description
*
* ## 表单组件 / Select选择组件
*
* ### 说明
*
* 如果在Select中使用了`v-model`指令时, Option中的`checked`属性将不起作用, 因为两者的使用逻辑是冲突的!
*
* `v-model`是在Select组件中使用数据控制, 而`checked`是在Option中使用checked属性控制.
*
* ### 数据源
*
* 使用此组件建议使用固定数据而不是异步数据, 异步数据使用Alert组件完成.
*
* ### 如何引入
* ```
* // 引入
* import { Select, SelectOption } from 'vimo'
* // 安装
* Vue.component(Select.name, Select)
* Vue.component(Option.name, SelectOption)
* // 或者
* export default{
* components: {
* Select, SelectOption
* }
* }
* ```
* @props {String} [cancelText='取消'] - cancel按钮显示文本
* @props {String} [cancelText='确认'] - OK按钮显示文本
* @props {Boolean} [disabled='false'] - OK按钮显示文本
* @props {String} [interface='alert'] - 显示界面类型, 可以是'action-sheet','alert'两个
* @props {Boolean} [multiple='false'] - 单选多选,默认为单选
* @props {String} [placeholder] - 当未选择时显示的值
* @props {Object} [selectOptions] - select组件掉用alert和action-sheet组件的, 这个是针对传入的参数 title/subTitle/message/cssClass/enableBackdropDismiss等
* @props {String} [selectedText] - 选择组件的文本提示, 代替选择的option选项
* @props {String} [mode='ios'] - 模式
* @props {Object|String|Array} [value='ios'] - 组件值
*
* @fires component:Select#onChange
* @fires component:Select#onCancel
* @fires component:Select#onSelect
*
* @demo #/select
*
* @usage
* <Item>
* <Label>Gender</Label>
* <Select item-right placeholder="Select" interface="alert"
* @ onChange="onChange"
* @ onSelect="onSelect"
* @ onCancel="onCancel">
* <Option value="f" checked>Female</Option>
* <Option value="m">Male</Option>
* </Select>
* </Item>
* */
import { setElementClass } from '../../util/util'
import { isCheckedProperty, isTrueProperty, isBlank } from '../../util/type'
import ActionSheet from '../action-sheet'
import Alert from '../alert'
let id = 0
export default {
name: 'Select',
inject: {
itemComponent: {
from: 'itemComponent',
default: null
}
},
provide () {
const _this = this
return {
selectComponent: _this
}
},
data () {
return {
isDisabled: this.disabled, // 内部 禁用
id: `rb-${id++}`, // id
optionComponents: [], // Select子组件Option的集合
texts: [], // 回显的数组
text: null, // 回显的string, 已texts为基
timer: null, // setTimeout
values: [] // options中所有选中的value数组
}
},
props: {
// cancel按钮显示文本
cancelText: {
type: String,
default: '取消'
},
// OK按钮显示文本
okText: {
type: String,
default: '确认'
},
disabled: [Boolean],
// 显示界面类型, 可以是'action-sheet','alert'两个
interface: {
type: String,
default: 'alert',
validator (val) {
return ~[
'alert',
'action-sheet'
].indexOf(val)
}
},
// 单选多选,默认为单选
multiple: Boolean,
// 当未选择时显示的值
placeholder: String,
// select组件掉用alert和action-sheet组件的, 这个是针对传入的参数
// title/subTitle/message/cssClass/enableBackdropDismiss等
selectOptions: {
type: Object,
default () { return {} }
},
// 选择组件的文本提示, 代替选择的option选项
selectedText: String,
// 模式
mode: {
type: String,
default () { return this.$config && this.$config.get('mode') || 'ios' }
},
value: [Object, String, Array]
},
watch: {
disabled (val) {
this.setDisabled(isTrueProperty(val))
}
},
computed: {
modeClass () {
return `select select-${this.mode}`
},
buttonElement () {
return this.$refs.button
}
},
methods: {
/**
* 设置当前radio的禁用状态
* */
setDisabled (isDisabled) {
this.isDisabled = isDisabled
this.itemComponent && setElementClass(this.itemComponent.$el, 'item-select-disabled', isDisabled)
},
/**
* 点击组件时触发
* */
onPointerDownHandler ($event) {
$event.preventDefault()
$event.stopPropagation()
this.open()
},
/**
* 组件开启
* */
open () {
var selectCssClass
if (this.isDisabled) {
return
}
console.debug('select, open alert')
// 避免引用产生问题
const selectOptions = JSON.parse(JSON.stringify(this.selectOptions))
// 重新给buttons赋值
selectOptions.buttons = [{
text: this.cancelText,
role: 'cancel',
handler: () => {
/**
* @event component:Select#onCancel
* @description 点击取消的时间
*/
this.$emit('onCancel', null)
}
}]
// 如果 selectOptions 没有提供title, 则使用Lable的文本
if (!selectOptions.title && this.itemComponent) {
selectOptions.title = this.itemComponent.getLabelText()
}
if (this.interface === 'action-sheet' && this.optionComponents.length > 6) {
console.warn('Interface cannot be "action-sheet" with more than 6 options. Using the "alert" interface.')
this.interface = 'alert'
}
if (this.interface === 'action-sheet' && this.multiple) {
console.warn('Interface cannot be "action-sheet" with a multi-value select. Using the "alert" interface.')
this.interface = 'alert'
}
if (this.interface === 'action-sheet') {
selectOptions.buttons = selectOptions.buttons.concat(this.optionComponents.map(input => {
return {
role: (input.isChecked ? 'selected' : ''),
text: input.label,
handler: () => {
this.onChange(input.optionValue)
/**
* @event component:Select#onChange
* @description 值发生变化时触发
* @property {any} value - 变化值
*/
/**
* @event component:Select#onSelect
* @description 点击选择时触发
* @property {any} value - 变化值
*/
this.$emit('onChange', input.optionValue)
this.$emit('input', input.optionValue)
this.$emit('onSelect', input.optionValue)
}
}
}))
selectCssClass = 'select-action-sheet'
// 允许用户自定义css样式
selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : ''
selectOptions.cssClass = selectCssClass
// 初始化并开启
ActionSheet.present(selectOptions)
} else {
this.interface = 'alert'
// 从option中获取input参数
selectOptions.inputs = this.optionComponents.map(input => {
return {
type: (this.multiple ? 'checkbox' : 'radio'),
label: input.label,
value: input.optionValue,
checked: input.isChecked,
disabled: input.disabled,
handler: (selectedOption) => {
// 点击选中时触发事件
if (selectedOption.isChecked) {
this.$emit('onSelect', input.optionValue)
}
}
}
})
selectCssClass = 'select-alert'
if (this.multiple) {
// 使用勾选框组件样式
selectCssClass += ' multiple-select-alert'
} else {
// 使用单选框组件样式
selectCssClass += ' single-select-alert'
}
// 允许用户自定义样式
selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : ''
selectOptions.cssClass = selectCssClass
selectOptions.buttons.push({
text: this.okText,
handler: (selectedValues) => {
this.onChange(selectedValues)
this.$emit('onChange', selectedValues)
this.$emit('input', selectedValues)
}
})
// 初始化并开启
Alert.present(selectOptions)
}
},
/**
* 当用户点击选择时
* @private
* */
onChange (value) {
console.debug('select, onChange value:', value)
this.values = (Array.isArray(value) ? value : isBlank(value) ? [] : [value])
this.updOpts()
},
/**
* 更新子组件option的状态
* @private
* */
updOpts () {
this.texts = []
if (this.optionComponents) {
this.optionComponents.forEach(option => {
// check this option if the option's value is in the values array
option.isChecked = this.values.some(selectValue => {
return isCheckedProperty(selectValue, option.optionValue)
})
if (option.isChecked) {
this.texts.push(option.label)
}
})
}
this.text = this.texts.join(', ')
console.debug('updOpts')
},
/**
* 由子组件调用, 将自己的this传递给父组件
* */
recordOption (optionComponent) {
this.optionComponents.push(optionComponent)
if (isBlank(this.value)) {
this.values = this.optionComponents.filter(o => o.isChecked).map(o => o.optionValue)
}
this.timer && window.clearTimeout(this.timer)
// 保证只调用一次
this.timer = window.setTimeout(() => {
this.updOpts()
}, 0)
}
},
mounted () {
// 找到外部item实例
if (this.itemComponent) {
setElementClass(this.itemComponent.$el, 'item-select', true)
} else {
// 如果不在item中的话, 则button填充满整个组件. 否则填充整个Item组件.
this.$el.style.position = 'relative'
this.buttonElement.style.cssText = 'position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: transparent;cursor: pointer;'
}
if (!isBlank(this.value)) {
this.values.push(this.value)
}
}
}
</script>