Skip to content

Commit 70045b8

Browse files
committed
support using literal props on root instance
1 parent 50e323c commit 70045b8

File tree

4 files changed

+79
-39
lines changed

4 files changed

+79
-39
lines changed

src/compiler/compile.js

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = compile
3232

3333
function compile (el, options, partial, transcluded) {
3434
// link function for the node itself.
35-
var nodeLinkFn = options._asComponent && !partial
35+
var nodeLinkFn = !partial
3636
? compileRoot(el, options)
3737
: compileNode(el, options)
3838
// link function for the childNodes
@@ -118,7 +118,7 @@ function teardownDirs (vm, dirs, destroying) {
118118
}
119119

120120
/**
121-
* Compile the root element of a component. There are
121+
* Compile the root element of an instance. There are
122122
* 3 types of things to process here:
123123
*
124124
* 1. props on parent container (child scope)
@@ -135,23 +135,31 @@ function teardownDirs (vm, dirs, destroying) {
135135
*/
136136

137137
function compileRoot (el, options) {
138-
var isBlock = el.nodeType === 11 // DocumentFragment
139138
var containerAttrs = options._containerAttrs
140139
var replacerAttrs = options._replacerAttrs
141140
var props = options.props
142141
var propsLinkFn, parentLinkFn, replacerLinkFn
143142
// 1. props
144-
propsLinkFn = props
143+
propsLinkFn = props && containerAttrs
145144
? compileProps(el, containerAttrs, props)
146145
: null
147-
if (!isBlock) {
148-
// 2. container attributes
149-
if (containerAttrs) {
150-
parentLinkFn = compileDirectives(containerAttrs, options)
151-
}
152-
if (replacerAttrs) {
153-
// 3. replacer attributes
154-
replacerLinkFn = compileDirectives(replacerAttrs, options)
146+
// only need to compile other attributes for
147+
// non-block instances
148+
if (el.nodeType !== 11) {
149+
// for components, container and replacer need to be
150+
// compiled separately and linked in different scopes.
151+
if (options._asComponent) {
152+
// 2. container attributes
153+
if (containerAttrs) {
154+
parentLinkFn = compileDirectives(containerAttrs, options)
155+
}
156+
if (replacerAttrs) {
157+
// 3. replacer attributes
158+
replacerLinkFn = compileDirectives(replacerAttrs, options)
159+
}
160+
} else {
161+
// non-component, just compile as a normal element.
162+
replacerLinkFn = compileDirectives(el, options)
155163
}
156164
}
157165
return function rootLinkFn (vm, el, host) {
@@ -192,17 +200,16 @@ function compileNode (node, options) {
192200
*/
193201

194202
function compileElement (el, options) {
195-
if (checkTransclusion(el)) {
203+
var hasAttrs = el.hasAttributes()
204+
if (hasAttrs && checkTransclusion(el)) {
196205
// unwrap textNode
197206
if (el.hasAttribute('__vue__wrap')) {
198207
el = el.firstChild
199208
}
200209
return compile(el, options._parent.$options, true, true)
201210
}
202-
var linkFn
203-
var hasAttrs = el.hasAttributes()
204211
// check element directives
205-
linkFn = checkElementDirectives(el, options)
212+
var linkFn = checkElementDirectives(el, options)
206213
// check terminal direcitves (repeat & if)
207214
if (!linkFn && hasAttrs) {
208215
linkFn = checkTerminalDirectives(el, options)
@@ -406,7 +413,7 @@ function compileProps (el, attrs, propNames) {
406413
if (value != null) {
407414
prop = {
408415
name: name,
409-
value: value
416+
raw: value
410417
}
411418
var tokens = textParser.parse(value)
412419
if (tokens) {
@@ -447,14 +454,22 @@ function makePropsLinkFn (props) {
447454
// so we need to wrap the path here
448455
path = _.camelize(prop.name.replace(dataAttrRE, ''))
449456
if (prop.dynamic) {
450-
vm._bindDir('prop', el, {
451-
arg: path,
452-
expression: prop.value,
453-
oneWay: prop.oneTime
454-
}, propDef)
457+
if (vm.$parent) {
458+
vm._bindDir('prop', el, {
459+
arg: path,
460+
expression: prop.value,
461+
oneWay: prop.oneTime
462+
}, propDef)
463+
} else {
464+
_.warn(
465+
'Cannot bind dynamic prop on a root instance' +
466+
' with no parent: ' + prop.name + '="' +
467+
prop.raw + '"'
468+
)
469+
}
455470
} else {
456471
// just set once
457-
vm.$set(path, prop.value)
472+
vm.$set(path, prop.raw)
458473
}
459474
}
460475
}

src/compiler/transclude.js

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ var transcludedFlagAttr = '__vue__transcluded'
1616
*/
1717

1818
module.exports = function transclude (el, options) {
19-
if (options && options._asComponent) {
20-
// extract container attributes to pass them down
21-
// to compiler, because they need to be compiled in
22-
// parent scope. we are mutating the options object here
23-
// assuming the same object will be used for compile
24-
// right after this.
19+
// extract container attributes to pass them down
20+
// to compiler, because they need to be compiled in
21+
// parent scope. we are mutating the options object here
22+
// assuming the same object will be used for compile
23+
// right after this.
24+
if (options) {
2525
options._containerAttrs = extractAttrs(el)
26-
// Mark content nodes and attrs so that the compiler
27-
// knows they should be compiled in parent scope.
26+
}
27+
// Mark content nodes and attrs so that the compiler
28+
// knows they should be compiled in parent scope.
29+
if (options && options._asComponent) {
2830
var i = el.childNodes.length
2931
while (i--) {
3032
var node = el.childNodes[i]
@@ -199,16 +201,19 @@ function insertContentAt (outlet, contents) {
199201
* determine whether an attribute is transcluded.
200202
*
201203
* @param {Element} el
204+
* @return {Object}
202205
*/
203206

204207
function extractAttrs (el) {
205-
var attrs = el.attributes
206-
var res = {}
207-
var i = attrs.length
208-
while (i--) {
209-
res[attrs[i].name] = attrs[i].value
208+
if (el.nodeType === 1 && el.hasAttributes()) {
209+
var attrs = el.attributes
210+
var res = {}
211+
var i = attrs.length
212+
while (i--) {
213+
res[attrs[i].name] = attrs[i].value
214+
}
215+
return res
210216
}
211-
return res
212217
}
213218

214219
/**

src/directives/repeat.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ module.exports = {
436436
var cache = this.cache
437437
var id
438438
if (key || idKey) {
439-
var id = idKey
439+
id = idKey
440440
? idKey === '$index'
441441
? index
442442
: data[idKey]

test/unit/specs/compiler/compile_spec.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,32 @@ if (_.inBrowser) {
199199
expect(args[2].expression).toBe('this._applyFilters(a,null,[{"name":"filter"}],false)')
200200
expect(args[3]).toBe(def)
201201
// camelCase should've warn
202-
expect(_.warn.calls.count()).toBe(1)
202+
expect(hasWarned(_, 'using camelCase')).toBe(true)
203203
// literal and one time should've called vm.$set
204204
expect(vm.$set).toHaveBeenCalledWith('a', '1')
205205
expect(vm.$set).toHaveBeenCalledWith('someOtherAttr', '2')
206206
})
207207

208+
it('props on root instance', function () {
209+
// temporarily remove vm.$parent
210+
var parent = vm.$parent
211+
vm.$parent = null
212+
var options = _.mergeOptions(Vue.options, {
213+
props: ['a', 'b']
214+
})
215+
var def = Vue.options.directives._prop
216+
el.setAttribute('a', 'hi')
217+
el.setAttribute('b', '{{hi}}')
218+
transclude(el, options)
219+
var linker = compile(el, options)
220+
linker(vm, el)
221+
expect(vm._bindDir.calls.count()).toBe(0)
222+
expect(vm.$set).toHaveBeenCalledWith('a', 'hi')
223+
expect(hasWarned(_, 'Cannot bind dynamic prop on a root')).toBe(true)
224+
// restore parent mock
225+
vm.$parent = parent
226+
})
227+
208228
it('DocumentFragment', function () {
209229
var frag = document.createDocumentFragment()
210230
frag.appendChild(el)

0 commit comments

Comments
 (0)