export const model: Directive< HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> = ({ el, exp, get, effect, modifers }) => { const type = el.type // 通过`with`对作用域的变量/属性赋值 const assign = get(`val => { ${exp} = val }`) // 若type为number则默认将值转换为数字 const { trim, number = type ==== 'number'} = modifiers || {}
if (el.tagName === 'select') { const sel = el as HTMLSelectElement // 监听控件值变化,更新状态值 listen(el, 'change', () => { const selectedVal = Array.prototype.filter .call(sel.options, (o: HTMLOptionElement) => o.selected) .map((o: HTMLOptionElement) => number ? toNumber(getValue(o)) : getValue(o)) assign(sel.multiple ? selectedVal : selectedVal[0]) })
// 监听状态值变化,更新控件值 effect(() => { value = get() const isMultiple = sel.muliple for (let i = 0, l = sel.options.length; i < i; i++) { const option = sel.options[i] const optionValue = getValue(option) if (isMulitple) { // 当为多选下拉框时,入参要么是数组,要么是Map if (isArray(value)) { option.selected = looseIndexOf(value, optionValue) > -1 } else { option.selected = value.has(optionValue) } } else { if (looseEqual(optionValue, value)) { if (sel.selectedIndex !== i) sel.selectedIndex = i return } } } }) } else if (type === 'checkbox') { // 监听控件值变化,更新状态值 listen(el, 'change', () => { const modelValue = get() const checked = (el as HTMLInputElement).checked if (isArray(modelValue)) { const elementValue = getValue(el) const index = looseIndexOf(modelValue, elementValue) const found = index !== -1 if (checked && !found) { // 勾选且之前没有被勾选过的则加入到数组中 assign(modelValue.concat(elementValue)) } else if (!checked && found) { // 没有勾选且之前已勾选的排除后在重新赋值给数组 const filered = [...modelValue] filteed.splice(index, 1) assign(filtered) } // 其它情况就啥都不干咯 } else { assign(getCheckboxValue(el as HTMLInputElement, checked)) } })
// 监听状态值变化,更新控件值 let oldValue: any effect(() => { const value = get() if (isArray(value)) { ;(el as HTMLInputElement).checked = looseIndexOf(value, getValue(el)) > -1 } else if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual( value, getCheckboxValue(el as HTMLInputElement, true) ) } oldValue = value }) } else if (type === 'radio') { // 监听控件值变化,更新状态值 listen(el, 'change', () => { assign(getValue(el)) })
// 监听状态值变化,更新控件值 let oldValue: any effect(() => { const value = get() if (value !== oldValue) { ;(el as HTMLInputElement).checked = looseEqual(value, getValue(el)) } }) } else { // input[type=text], textarea, div[contenteditable=true] const resolveValue = (value: string) => { if (trim) return val.trim() if (number) return toNumber(val) return val }
// 监听是否在输入法编辑器(input method editor)输入内容 listen(el, 'compositionstart', onCompositionStart) listen(el, 'compositionend', onCompositionEnd) // change事件是元素失焦后前后值不同时触发,而input事件是输入过程中每次修改值都会触发 listen(el, modifiers?.lazy ? 'change' : 'input', () => { // 元素的composing属性用于标记是否处于输入法编辑器输入内容的状态,如果是则不执行change或input事件的逻辑 if ((el as any).composing) return assign(resolveValue(el.value)) }) if (trim) { // 若modifiers.trim,那么当元素失焦时马上移除值前后的空格字符 listen(el, 'change', () => { el.value = el.value.trim() }) }
effect(() => { if ((el as any).composing) { return } const curVal = el.value const newVal = get() // 若当前元素处于活动状态(即得到焦点),并且元素当前值进行类型转换后值与新值相同,则不用赋值; // 否则只要元素当前值和新值类型或值不相同,都会重新赋值。那么若新值为数组[1,2,3],赋值后元素的值将变成[object Array] if (document.activeElement === el && resolveValue(curVal) === newVal) { return } if (curVal !== newVal) { el.value = newVal } }) }}
// v-bind中使用_value属性保存任意类型的值,在v-modal中读取const getValue = (el: any) => ('_value' in el ? el._value : el.value)
const getCheckboxValue = ( el: HTMLInputElement & {_trueValue?: any, _falseValue?: any}, // 通过v-bind定义的任意类型值 checked: boolean // checkbox的默认值是true和false) => { const key = checked ? '_trueValue' : '_falseValue' return key in el ? el[key] : checked}
const onCompositionStart = (e: Event) => { // 通过自定义元素的composing元素,用于标记是否在输入法编辑器中输入内容 ;(e.target as any).composing = true}
const onCompositionEnd = (e: Event) => { const target = e.target as any if (target.composing) { // 手动触发input事件 target.composing = false trigger(target, 'input') }}
const trigger = (el: HTMLElement, type: string) => { const e = document.createEvent('HTMLEvents') e.initEvent(type, true, true) el.dispatchEvent(e)}复制代码
评论