Skip to content

Commit

Permalink
feat(v-model): support dynamic input type binding
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Oct 8, 2017
1 parent 2876ed8 commit f3fe012
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 43 deletions.
11 changes: 8 additions & 3 deletions flow/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ declare type CompiledResult = {
};

declare type ModuleOptions = {
preTransformNode: (el: ASTElement) => void;
transformNode: (el: ASTElement) => void; // transform an element's AST node
// returning an ASTElement from pre/transforms replaces the element
preTransformNode: (el: ASTElement) => ?ASTElement;
transformNode: (el: ASTElement) => ?ASTElement;
// cannot return replacement in postTransform because tree is already finalized
postTransformNode: (el: ASTElement) => void;
genData: (el: ASTElement) => string; // generate extra data string for an element
transformCode?: (el: ASTElement, code: string) => string; // further transform generated code for an element
staticKeys?: Array<string>; // AST properties to be considered static
};

declare type ASTModifiers = { [key: string]: boolean };
declare type ASTIfConditions = Array<{ exp: ?string; block: ASTElement }>;
declare type ASTIfCondition = { exp: ?string; block: ASTElement };
declare type ASTIfConditions = Array<ASTIfCondition>;

declare type ASTElementHandler = {
value: string;
Expand Down Expand Up @@ -74,6 +77,8 @@ declare type ASTElement = {
parent: ASTElement | void;
children: Array<ASTNode>;

processed?: true;

static?: boolean;
staticRoot?: boolean;
staticInFor?: boolean;
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ export function getBindingAttr (
}
}

export function getAndRemoveAttr (el: ASTElement, name: string): ?string {
// note: this only removes the attr from the Array (attrsList) so that it
// doesn't get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
export function getAndRemoveAttr (
el: ASTElement,
name: string,
removeFromMap?: boolean
): ?string {
let val
if ((val = el.attrsMap[name]) != null) {
const list = el.attrsList
Expand All @@ -115,5 +123,8 @@ export function getAndRemoveAttr (el: ASTElement, name: string): ?string {
}
}
}
if (removeFromMap) {
delete el.attrsMap[name]
}
return val
}
65 changes: 40 additions & 25 deletions src/compiler/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ let platformIsPreTag
let platformMustUseProp
let platformGetTagNamespace

type Attr = { name: string; value: string }
export function createASTElement (
tag: string,
attrs: Array<Attr>,
parent: ASTElement | void
): ASTElement {
return {
type: 1,
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent,
children: []
}
}

/**
* Convert HTML string to AST.
*/
Expand Down Expand Up @@ -102,14 +118,7 @@ export function parse (
attrs = guardIESVGBug(attrs)
}

const element: ASTElement = {
type: 1,
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent: currentParent,
children: []
}
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
Expand All @@ -125,7 +134,7 @@ export function parse (

// apply pre-transforms
for (let i = 0; i < preTransforms.length; i++) {
preTransforms[i](element, options)
element = preTransforms[i](element, options) || element
}

if (!inVPre) {
Expand All @@ -139,23 +148,13 @@ export function parse (
}
if (inVPre) {
processRawAttrs(element)
} else {
} else if (!element.processed) {
// structural directives
processFor(element)
processIf(element)
processOnce(element)
processKey(element)

// determine whether this is a plain element after
// removing structural attributes
element.plain = !element.key && !attrs.length

processRef(element)
processSlot(element)
processComponent(element)
for (let i = 0; i < transforms.length; i++) {
transforms[i](element, options)
}
processAttrs(element)
// element-scope stuff
processElement(element, options)
}

function checkRootConstraints (el) {
Expand Down Expand Up @@ -309,6 +308,22 @@ function processRawAttrs (el) {
}
}

export function processElement (element: ASTElement, options: CompilerOptions) {
processKey(element)

// determine whether this is a plain element after
// removing structural attributes
element.plain = !element.key && !element.attrsList.length

processRef(element)
processSlot(element)
processComponent(element)
for (let i = 0; i < transforms.length; i++) {
element = transforms[i](element, options) || element
}
processAttrs(element)
}

function processKey (el) {
const exp = getBindingAttr(el, 'key')
if (exp) {
Expand All @@ -327,7 +342,7 @@ function processRef (el) {
}
}

function processFor (el) {
export function processFor (el: ASTElement) {
let exp
if ((exp = getAndRemoveAttr(el, 'v-for'))) {
const inMatch = exp.match(forAliasRE)
Expand Down Expand Up @@ -403,7 +418,7 @@ function findPrevElement (children: Array<any>): ASTElement | void {
}
}

function addIfCondition (el, condition) {
export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
if (!el.ifConditions) {
el.ifConditions = []
}
Expand Down
7 changes: 0 additions & 7 deletions src/platforms/web/compiler/directives/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@ export default function model (
const type = el.attrsMap.type

if (process.env.NODE_ENV !== 'production') {
const dynamicType = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
if (tag === 'input' && dynamicType) {
warn(
`<input :type="${dynamicType}" v-model="${value}">:\n` +
`v-model does not support dynamic input types. Use v-if branches instead.`
)
}
// inputs with type="file" are read only and setting the input's
// value will throw an error.
if (tag === 'input' && type === 'file') {
Expand Down
4 changes: 3 additions & 1 deletion src/platforms/web/compiler/modules/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import klass from './class'
import style from './style'
import model from './model'

export default [
klass,
style
style,
model
]
77 changes: 77 additions & 0 deletions src/platforms/web/compiler/modules/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* @flow */

/**
* Expand input[v-model] with dyanmic type bindings into v-if-else chains
* Turn this:
* <input v-model="data[type]" :type="type">
* into this:
* <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]">
* <input v-else-if="type === 'radio'" type="radio" v-model="data[type]">
* <input v-else :type="type" v-model="data[type]">
*/

import {
getBindingAttr,
getAndRemoveAttr
} from 'compiler/helpers'

import {
processFor,
processElement,
addIfCondition,
createASTElement
} from 'compiler/parser/index'

function preTransformNode (el: ASTElement, options: CompilerOptions) {
if (el.tag === 'input') {
const map = el.attrsMap
if (map['v-model'] && (map['v-bind:type'] || map[':type'])) {
const typeBinding: any = getBindingAttr(el, 'type')
const ifCondition = getAndRemoveAttr(el, 'v-if', true)
// 1. checkbox
const branch0 = cloneASTElement(el)
// process for on the main node
processFor(branch0)
addRawAttr(branch0, 'type', 'checkbox')
processElement(branch0, options)
branch0.processed = true // prevent it from double-processed
branch0.if = `type==='checkbox'` + (ifCondition ? `&&(${ifCondition})` : ``)
addIfCondition(branch0, {
exp: branch0.if,
block: branch0
})
// 2. add radio else-if condition
const branch1 = cloneASTElement(el)
getAndRemoveAttr(branch1, 'v-for', true)
addRawAttr(branch1, 'type', 'radio')
processElement(branch1, options)
addIfCondition(branch0, {
exp: `type==='radio'` + (ifCondition ? `&&(${ifCondition})` : ``),
block: branch1
})
// 3. other
const branch2 = cloneASTElement(el)
getAndRemoveAttr(branch2, 'v-for', true)
addRawAttr(branch2, ':type', typeBinding)
processElement(branch2, options)
addIfCondition(branch0, {
exp: ifCondition,
block: branch2
})
return branch0
}
}
}

function cloneASTElement (el) {
return createASTElement(el.tag, el.attrsList.slice(), el.parent)
}

function addRawAttr (el, name, value) {
el.attrsMap[name] = value
el.attrsList.push({ name, value })
}

export default {
preTransformNode
}
Loading

0 comments on commit f3fe012

Please sign in to comment.