123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- /* @flow */
- import config from 'core/config'
- import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
- import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
- let warn
- // in some cases, the event used has to be determined at runtime
- // so we used some reserved tokens during compile.
- export const RANGE_TOKEN = '__r'
- export const CHECKBOX_RADIO_TOKEN = '__c'
- export default function model (
- el: ASTElement,
- dir: ASTDirective,
- _warn: Function
- ): ?boolean {
- warn = _warn
- const value = dir.value
- const modifiers = dir.modifiers
- const tag = el.tag
- const type = el.attrsMap.type
- if (process.env.NODE_ENV !== 'production') {
- // inputs with type="file" are read only and setting the input's
- // value will throw an error.
- if (tag === 'input' && type === 'file') {
- warn(
- `<${el.tag} v-model="${value}" type="file">:\n` +
- `File inputs are read only. Use a v-on:change listener instead.`,
- el.rawAttrsMap['v-model']
- )
- }
- }
- if (el.component) {
- genComponentModel(el, value, modifiers)
- // component v-model doesn't need extra runtime
- return false
- } else if (tag === 'select') {
- genSelect(el, value, modifiers)
- } else if (tag === 'input' && type === 'checkbox') {
- genCheckboxModel(el, value, modifiers)
- } else if (tag === 'input' && type === 'radio') {
- genRadioModel(el, value, modifiers)
- } else if (tag === 'input' || tag === 'textarea') {
- genDefaultModel(el, value, modifiers)
- } else if (!config.isReservedTag(tag)) {
- genComponentModel(el, value, modifiers)
- // component v-model doesn't need extra runtime
- return false
- } else if (process.env.NODE_ENV !== 'production') {
- warn(
- `<${el.tag} v-model="${value}">: ` +
- `v-model is not supported on this element type. ` +
- 'If you are working with contenteditable, it\'s recommended to ' +
- 'wrap a library dedicated for that purpose inside a custom component.',
- el.rawAttrsMap['v-model']
- )
- }
- // ensure runtime directive metadata
- return true
- }
- function genCheckboxModel (
- el: ASTElement,
- value: string,
- modifiers: ?ASTModifiers
- ) {
- const number = modifiers && modifiers.number
- const valueBinding = getBindingAttr(el, 'value') || 'null'
- const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
- const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
- addProp(el, 'checked',
- `Array.isArray(${value})` +
- `?_i(${value},${valueBinding})>-1` + (
- trueValueBinding === 'true'
- ? `:(${value})`
- : `:_q(${value},${trueValueBinding})`
- )
- )
- addHandler(el, 'change',
- `var $$a=${value},` +
- '$$el=$event.target,' +
- `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
- 'if(Array.isArray($$a)){' +
- `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
- '$$i=_i($$a,$$v);' +
- `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` +
- `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` +
- `}else{${genAssignmentCode(value, '$$c')}}`,
- null, true
- )
- }
- function genRadioModel (
- el: ASTElement,
- value: string,
- modifiers: ?ASTModifiers
- ) {
- const number = modifiers && modifiers.number
- let valueBinding = getBindingAttr(el, 'value') || 'null'
- valueBinding = number ? `_n(${valueBinding})` : valueBinding
- addProp(el, 'checked', `_q(${value},${valueBinding})`)
- addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
- }
- function genSelect (
- el: ASTElement,
- value: string,
- modifiers: ?ASTModifiers
- ) {
- const number = modifiers && modifiers.number
- const selectedVal = `Array.prototype.filter` +
- `.call($event.target.options,function(o){return o.selected})` +
- `.map(function(o){var val = "_value" in o ? o._value : o.value;` +
- `return ${number ? '_n(val)' : 'val'}})`
- const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
- let code = `var $$selectedVal = ${selectedVal};`
- code = `${code} ${genAssignmentCode(value, assignment)}`
- addHandler(el, 'change', code, null, true)
- }
- function genDefaultModel (
- el: ASTElement,
- value: string,
- modifiers: ?ASTModifiers
- ): ?boolean {
- const type = el.attrsMap.type
- // warn if v-bind:value conflicts with v-model
- // except for inputs with v-bind:type
- if (process.env.NODE_ENV !== 'production') {
- const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
- const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
- if (value && !typeBinding) {
- const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
- warn(
- `${binding}="${value}" conflicts with v-model on the same element ` +
- 'because the latter already expands to a value binding internally',
- el.rawAttrsMap[binding]
- )
- }
- }
- const { lazy, number, trim } = modifiers || {}
- const needCompositionGuard = !lazy && type !== 'range'
- const event = lazy
- ? 'change'
- : type === 'range'
- ? RANGE_TOKEN
- : 'input'
- let valueExpression = '$event.target.value'
- if (trim) {
- valueExpression = `$event.target.value.trim()`
- }
- if (number) {
- valueExpression = `_n(${valueExpression})`
- }
- let code = genAssignmentCode(value, valueExpression)
- if (needCompositionGuard) {
- code = `if($event.target.composing)return;${code}`
- }
- addProp(el, 'value', `(${value})`)
- addHandler(el, event, code, null, true)
- if (trim || number) {
- addHandler(el, 'blur', '$forceUpdate()')
- }
- }
|