mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-05 13:38:18 +08:00
refactor: extract universal v-model codegen code and update weex v-model codegen
This commit is contained in:
parent
90a455c95c
commit
6cbee6b286
127
src/compiler/directives/model.js
Normal file
127
src/compiler/directives/model.js
Normal file
@ -0,0 +1,127 @@
|
||||
/* @flow */
|
||||
|
||||
/**
|
||||
* Cross-platform code generation for component v-model
|
||||
*/
|
||||
export function genComponentModel (
|
||||
el: ASTElement,
|
||||
value: string,
|
||||
modifiers: ?ASTModifiers
|
||||
): ?boolean {
|
||||
const { number, trim } = modifiers || {}
|
||||
|
||||
let valueExpression = 'value'
|
||||
if (trim) {
|
||||
valueExpression = `(typeof value === 'string' ? value.trim() : value)`
|
||||
}
|
||||
if (number) {
|
||||
valueExpression = `_n(${valueExpression})`
|
||||
}
|
||||
|
||||
el.model = {
|
||||
value: `(${value})`,
|
||||
callback: `function (value) {${genAssignmentCode(value, valueExpression)}}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-platform codegen helper for generating v-model value assignment code.
|
||||
*/
|
||||
export function genAssignmentCode (
|
||||
value: string,
|
||||
assignment: string
|
||||
): string {
|
||||
const modelRs = parseModel(value)
|
||||
if (modelRs.idx === null) {
|
||||
return `${value}=${assignment}`
|
||||
} else {
|
||||
return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
|
||||
`if (!Array.isArray($$exp)){` +
|
||||
`${value}=${assignment}}` +
|
||||
`else{$$exp.splice($$idx, 1, ${assignment})}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
|
||||
*
|
||||
* for loop possible cases:
|
||||
*
|
||||
* - test
|
||||
* - test[idx]
|
||||
* - test[test1[idx]]
|
||||
* - test["a"][idx]
|
||||
* - xxx.test[a[a].test1[idx]]
|
||||
* - test.xxx.a["asa"][test1[idx]]
|
||||
*
|
||||
*/
|
||||
|
||||
let len, str, chr, index, expressionPos, expressionEndPos
|
||||
|
||||
export function parseModel (val: string): Object {
|
||||
str = val
|
||||
len = str.length
|
||||
index = expressionPos = expressionEndPos = 0
|
||||
|
||||
if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
|
||||
return {
|
||||
exp: val,
|
||||
idx: null
|
||||
}
|
||||
}
|
||||
|
||||
while (!eof()) {
|
||||
chr = next()
|
||||
/* istanbul ignore if */
|
||||
if (isStringStart(chr)) {
|
||||
parseString(chr)
|
||||
} else if (chr === 0x5B) {
|
||||
parseBracket(chr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exp: val.substring(0, expressionPos),
|
||||
idx: val.substring(expressionPos + 1, expressionEndPos)
|
||||
}
|
||||
}
|
||||
|
||||
function next (): number {
|
||||
return str.charCodeAt(++index)
|
||||
}
|
||||
|
||||
function eof (): boolean {
|
||||
return index >= len
|
||||
}
|
||||
|
||||
function isStringStart (chr: number): boolean {
|
||||
return chr === 0x22 || chr === 0x27
|
||||
}
|
||||
|
||||
function parseBracket (chr: number): void {
|
||||
let inBracket = 1
|
||||
expressionPos = index
|
||||
while (!eof()) {
|
||||
chr = next()
|
||||
if (isStringStart(chr)) {
|
||||
parseString(chr)
|
||||
continue
|
||||
}
|
||||
if (chr === 0x5B) inBracket++
|
||||
if (chr === 0x5D) inBracket--
|
||||
if (inBracket === 0) {
|
||||
expressionEndPos = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseString (chr: number): void {
|
||||
const stringQuote = chr
|
||||
while (!eof()) {
|
||||
chr = next()
|
||||
if (chr === stringQuote) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -100,87 +100,3 @@ export function getAndRemoveAttr (el: ASTElement, name: string): ?string {
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
let len, str, chr, index, expressionPos, expressionEndPos
|
||||
|
||||
/**
|
||||
* parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
|
||||
*
|
||||
* for loop possible cases:
|
||||
*
|
||||
* - test
|
||||
* - test[idx]
|
||||
* - test[test1[idx]]
|
||||
* - test["a"][idx]
|
||||
* - xxx.test[a[a].test1[idx]]
|
||||
* - test.xxx.a["asa"][test1[idx]]
|
||||
*
|
||||
*/
|
||||
|
||||
export function parseModel (val: string): Object {
|
||||
str = val
|
||||
len = str.length
|
||||
index = expressionPos = expressionEndPos = 0
|
||||
|
||||
if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
|
||||
return {
|
||||
exp: val,
|
||||
idx: null
|
||||
}
|
||||
}
|
||||
|
||||
while (!eof()) {
|
||||
chr = next()
|
||||
/* istanbul ignore if */
|
||||
if (isStringStart(chr)) {
|
||||
parseString(chr)
|
||||
} else if (chr === 0x5B) {
|
||||
parseBracket(chr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exp: val.substring(0, expressionPos),
|
||||
idx: val.substring(expressionPos + 1, expressionEndPos)
|
||||
}
|
||||
}
|
||||
|
||||
function next (): number {
|
||||
return str.charCodeAt(++index)
|
||||
}
|
||||
|
||||
function eof (): boolean {
|
||||
return index >= len
|
||||
}
|
||||
|
||||
function isStringStart (chr: number): boolean {
|
||||
return chr === 0x22 || chr === 0x27
|
||||
}
|
||||
|
||||
function parseBracket (chr: number): void {
|
||||
let inBracket = 1
|
||||
expressionPos = index
|
||||
while (!eof()) {
|
||||
chr = next()
|
||||
if (isStringStart(chr)) {
|
||||
parseString(chr)
|
||||
continue
|
||||
}
|
||||
if (chr === 0x5B) inBracket++
|
||||
if (chr === 0x5D) inBracket--
|
||||
if (inBracket === 0) {
|
||||
expressionEndPos = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseString (chr: number): void {
|
||||
const stringQuote = chr
|
||||
while (!eof()) {
|
||||
chr = next()
|
||||
if (chr === stringQuote) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
import config from 'core/config'
|
||||
import { isIE } from 'core/util/env'
|
||||
import { addHandler, addProp, getBindingAttr, parseModel } from 'compiler/helpers'
|
||||
import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
|
||||
import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
|
||||
|
||||
let warn
|
||||
|
||||
@ -183,36 +184,3 @@ function genDefaultModel (
|
||||
addHandler(el, 'blur', '$forceUpdate()')
|
||||
}
|
||||
}
|
||||
|
||||
function genComponentModel (
|
||||
el: ASTElement,
|
||||
value: string,
|
||||
modifiers: ?ASTModifiers
|
||||
): ?boolean {
|
||||
const { number, trim } = modifiers || {}
|
||||
|
||||
let valueExpression = 'value'
|
||||
if (trim) {
|
||||
valueExpression = `(typeof value === 'string' ? value.trim() : value)`
|
||||
}
|
||||
if (number) {
|
||||
valueExpression = `_n(${valueExpression})`
|
||||
}
|
||||
|
||||
el.model = {
|
||||
value,
|
||||
callback: `function (value) {${genAssignmentCode(value, valueExpression)}}`
|
||||
}
|
||||
}
|
||||
|
||||
function genAssignmentCode (value: string, assignment: string): string {
|
||||
const modelRs = parseModel(value)
|
||||
if (modelRs.idx === null) {
|
||||
return `${value}=${assignment}`
|
||||
} else {
|
||||
return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
|
||||
`if (!Array.isArray($$exp)){` +
|
||||
`${value}=${assignment}}` +
|
||||
`else{$$exp.splice($$idx, 1, ${assignment})}`
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
/* @flow */
|
||||
|
||||
import { addHandler, addAttr, parseModel } from 'compiler/helpers'
|
||||
import { addHandler, addAttr } from 'compiler/helpers'
|
||||
import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
|
||||
|
||||
export default function model (
|
||||
el: ASTElement,
|
||||
dir: ASTDirective,
|
||||
_warn: Function
|
||||
): ?boolean {
|
||||
genDefaultModel(el, dir.value, dir.modifiers)
|
||||
if (el.tag === 'input' || el.tag === 'textarea') {
|
||||
genDefaultModel(el, dir.value, dir.modifiers)
|
||||
} else {
|
||||
genComponentModel(el, dir.value, dir.modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
function genDefaultModel (
|
||||
@ -15,25 +20,15 @@ function genDefaultModel (
|
||||
value: string,
|
||||
modifiers: ?ASTModifiers
|
||||
): ?boolean {
|
||||
const { lazy, trim } = modifiers || {}
|
||||
const { lazy, trim, number } = modifiers || {}
|
||||
const event = lazy ? 'change' : 'input'
|
||||
const isNative = el.tag === 'input' || el.tag === 'textarea'
|
||||
const valueExpression = isNative
|
||||
? `$event.target.attr.value${trim ? '.trim()' : ''}`
|
||||
: `$event`
|
||||
|
||||
let valueExpression = `$event.target.attr.value${trim ? '.trim()' : ''}`
|
||||
if (number) {
|
||||
valueExpression = `_n(${valueExpression})`
|
||||
}
|
||||
|
||||
const code = genAssignmentCode(value, valueExpression)
|
||||
addAttr(el, 'value', `(${value})`)
|
||||
addHandler(el, event, code, null, true)
|
||||
}
|
||||
|
||||
function genAssignmentCode (value: string, assignment: string): string {
|
||||
const modelRs = parseModel(value)
|
||||
if (modelRs.idx === null) {
|
||||
return `${value}=${assignment}`
|
||||
} else {
|
||||
return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
|
||||
`if (!Array.isArray($$exp)){` +
|
||||
`${value}=${assignment}}` +
|
||||
`else{$$exp.splice($$idx, 1, ${assignment})}`
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { parseModel } from 'compiler/helpers'
|
||||
import { parseModel } from 'compiler/directives/model'
|
||||
|
||||
describe('model expression parser', () => {
|
||||
it('parse object dot notation', () => {
|
||||
|
@ -14,28 +14,16 @@ describe('compile v-model', () => {
|
||||
it('should compile other component with whole $event as the value', () => {
|
||||
const { render, staticRenderFns, errors } = compile(`<div><foo v-model="x" /></div>`)
|
||||
expect(render).not.toBeUndefined()
|
||||
expect(render).toMatch(strToRegExp(`attrs:{"value":(x)}`))
|
||||
expect(render).toMatch(strToRegExp(`on:{"input":function($event){x=$event}}`))
|
||||
expect(staticRenderFns).toEqual([])
|
||||
expect(errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should compile with lazy modifier', () => {
|
||||
const { render, staticRenderFns, errors } = compile(`<div><foo v-model.lazy="x" /></div>`)
|
||||
expect(render).not.toBeUndefined()
|
||||
expect(render).toMatch(strToRegExp(`attrs:{"value":(x)}`))
|
||||
expect(render).toMatch(strToRegExp(`on:{"change":function($event){x=$event}}`))
|
||||
expect(render).toMatch(strToRegExp(`model:{value:(x),callback:function (value) {x=value}}`))
|
||||
expect(staticRenderFns).toEqual([])
|
||||
expect(errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should compile with trim modifier for modelable native component', () => {
|
||||
const { render, staticRenderFns, errors } = compile(`<div><input v-model.trim="x" /><foo v-model.trim="y" /></div>`)
|
||||
const { render, staticRenderFns, errors } = compile(`<div><input v-model.trim="x" /></div>`)
|
||||
expect(render).not.toBeUndefined()
|
||||
expect(render).toMatch(strToRegExp(`attrs:{"value":(x)}`))
|
||||
expect(render).toMatch(strToRegExp(`attrs:{"value":(y)}`))
|
||||
expect(render).toMatch(strToRegExp(`on:{"input":function($event){x=$event.target.attr.value.trim()}}`))
|
||||
expect(render).toMatch(strToRegExp(`on:{"input":function($event){y=$event}}`))
|
||||
expect(staticRenderFns).toEqual([])
|
||||
expect(errors).toEqual([])
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user