diff --git a/flow/compiler.js b/flow/compiler.js index df3bfcefd7..3a925be413 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -117,6 +117,11 @@ declare type ASTElement = { transition?: string | true; transitionOnAppear?: boolean; + model?: { + value: string; + callback: string; + }; + directives?: Array; forbidden?: true; diff --git a/flow/vnode.js b/flow/vnode.js index 84adf2f334..5d06d322e2 100644 --- a/flow/vnode.js +++ b/flow/vnode.js @@ -54,7 +54,11 @@ declare interface VNodeData { }; directives?: Array; keepAlive?: boolean; - scopedSlots?: { [key: string]: Function } + scopedSlots?: { [key: string]: Function }; + model?: { + value: any; + callback: Function; + }; } declare type VNodeDirective = { diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index c6585c02c4..e0aa9bf4bf 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -205,6 +205,10 @@ function genData (el: ASTElement): string { if (el.scopedSlots) { data += `${genScopedSlots(el.scopedSlots)},` } + // component v-model + if (el.model) { + data += `model:{value:${el.model.value},callback:${el.model.callback}},` + } // inline-template if (el.inlineTemplate) { const inlineTemplate = genInlineTemplate(el) diff --git a/src/core/vdom/create-component.js b/src/core/vdom/create-component.js index 53e99a2438..284db16c03 100644 --- a/src/core/vdom/create-component.js +++ b/src/core/vdom/create-component.js @@ -57,6 +57,11 @@ export function createComponent ( data = data || {} + // transform component v-model data into props & events + if (data.model) { + transformModel(Ctor.options, data) + } + // extract props const propsData = extractProps(data, Ctor) @@ -320,3 +325,17 @@ function mergeHook (one: Function, two: Function): Function { two(a, b, c, d) } } + +// transform component v-model info (value and callback) into +// prop and event handler respectively. +function transformModel (options, data: any) { + const prop = (options.model && options.model.prop) || 'value' + const event = (options.model && options.model.event) || 'input' + ;(data.props || (data.props = {}))[prop] = data.model.value + const on = data.on || (data.on = {}) + if (on[event]) { + on[event] = [data.model.callback].concat(on[event]) + } else { + on[event] = data.model.callback + } +} diff --git a/src/platforms/web/compiler/directives/model.js b/src/platforms/web/compiler/directives/model.js index 2e1c3ec586..0e23160c2a 100644 --- a/src/platforms/web/compiler/directives/model.js +++ b/src/platforms/web/compiler/directives/model.js @@ -1,5 +1,6 @@ /* @flow */ +import config from 'core/config' import { isIE } from 'core/util/env' import { addHandler, addProp, getBindingAttr, parseModel } from 'compiler/helpers' @@ -40,8 +41,19 @@ export default function model ( genCheckboxModel(el, value, modifiers) } else if (tag === 'input' && type === 'radio') { genRadioModel(el, value, modifiers) - } else { + } 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.' + ) } // ensure runtime directive metadata @@ -107,6 +119,41 @@ function genRadioModel ( addHandler(el, 'click', genAssignmentCode(value, valueBinding), null, true) } +function genSelect ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + if (process.env.NODE_ENV !== 'production') { + el.children.some(checkOptionWarning) + } + + 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 checkOptionWarning (option: any): boolean { + if (option.type === 1 && + option.tag === 'option' && + option.attrsMap.selected != null) { + warn( + `:\n` + - 'inline selected attributes on