Browse Source

implement prop- syntax

Evan You 10 years ago
parent
commit
dd376974e1

+ 3 - 3
examples/grid/index.html

@@ -42,9 +42,9 @@
         Search <input name="query" v-model="searchQuery">
       </form>
       <demo-grid
-        data="{{gridData}}"
-        columns="{{gridColumns}}"
-        filter-key="{{searchQuery}}">
+        prop-data="gridData"
+        prop-columns="gridColumns"
+        prop-filter-key="searchQuery">
       </demo-grid>
     </div>
 

+ 1 - 1
examples/modal/index.html

@@ -57,7 +57,7 @@
     <div id="app">
       <button id="show-modal" on-click="showModal = true">Show Modal</button>
       <!-- use the modal component, pass in the prop -->
-      <modal show="{{@showModal}}">
+      <modal prop-show="@showModal">
         <!--
           you can use custom content here to overwrite
           default content

+ 4 - 4
examples/svg/index.html

@@ -15,9 +15,9 @@
         <circle cx="100" cy="100" r="80"></circle>
         <axis-label
           v-for="stat in stats"
-          stat="{{stat}}"
-          index="{{$index}}"
-          total="{{stats.length}}">
+          prop-stat="stat"
+          prop-index="$index"
+          prop-total="stats.length">
         </axis-label>
       </g>
     </script>
@@ -31,7 +31,7 @@
     <div id="demo">
       <!-- Use the component -->
       <svg width="200" height="200">
-        <polygraph stats="{{stats}}"></polygraph>
+        <polygraph prop-stats="stats"></polygraph>
       </svg>
       <!-- controls -->
       <div v-for="stat in stats">

+ 7 - 3
examples/tree/index.html

@@ -35,7 +35,10 @@
           <span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
         </div>
         <ul v-show="open" v-if="isFolder">
-          <item class="item" v-for="model in model.children" model="{{model}}">
+          <item
+            class="item"
+            v-for="model in model.children"
+            prop-model="model">
           </item>
           <li on-click="addChild">+</li>
         </ul>
@@ -46,8 +49,9 @@
 
     <!-- the demo root element -->
     <ul id="demo">
-      <item class="item"
-        model="{{treeData}}">
+      <item
+        class="item"
+        prop-model="treeData">
       </item>
     </ul>
 

+ 59 - 12
src/compiler/compile-props.js

@@ -10,7 +10,7 @@ var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
 var literalValueRE = /^(true|false)$|^\d.*/
 
 /**
- * Compile param attributes on a root element and return
+ * Compile props on a root element and return
  * a props link function.
  *
  * @param {Element|DocumentFragment} el
@@ -18,6 +18,9 @@ var literalValueRE = /^(true|false)$|^\d.*/
  * @return {Function} propsLinkFn
  */
 
+// TODO: 1.0.0 we can just loop through el.attributes and
+// check for prop- prefixes.
+
 module.exports = function compileProps (el, propOptions) {
   var props = []
   var i = propOptions.length
@@ -45,10 +48,21 @@ module.exports = function compileProps (el, propOptions) {
     }
     attr = _.hyphenate(name)
     value = el.getAttribute(attr)
+
+    if (value !== null && process.env.NODE_ENV !== 'production') {
+      _.deprecation.PROPS(attr, value)
+    }
+
     if (value === null) {
-      attr = 'data-' + attr
-      value = el.getAttribute(attr)
+      value = el.getAttribute('data-' + attr)
+      if (value !== null) {
+        attr = 'data-' + attr
+        if (process.env.NODE_ENV !== 'production') {
+          _.deprecation.PROPS(attr, value)
+        }
+      }
     }
+
     // create a prop descriptor
     prop = {
       name: name,
@@ -84,21 +98,54 @@ module.exports = function compileProps (el, propOptions) {
             )
           }
         }
-        if (
-          process.env.NODE_ENV !== 'production' &&
-          options.twoWay &&
-          prop.mode !== propBindingModes.TWO_WAY
-        ) {
-          _.warn(
-            'Prop "' + name + '" expects a two-way binding type.'
-          )
+      }
+    } else {
+      // new prop- syntax
+      attr = 'prop-' + attr
+      value = prop.raw = el.getAttribute(attr)
+      if (value !== null) {
+        el.removeAttribute(attr)
+        // check binding type
+        if (literalValueRE.test(value)) {
+          prop.mode = propBindingModes.ONE_TIME
+        } else if (value.charAt(0) === '*') {
+          prop.mode = propBindingModes.ONE_TIME
+          value = value.slice(1)
+        } else if (value.charAt(0) === '@') {
+          value = value.slice(1)
+          if (settablePathRE.test(value)) {
+            prop.mode = propBindingModes.TWO_WAY
+          } else {
+            process.env.NODE_ENV !== 'production' && _.warn(
+              'Cannot bind two-way prop with non-settable ' +
+              'parent path: ' + value
+            )
+          }
         }
       }
-    } else if (options && options.required) {
+      prop.dynamic = true
+      prop.parentPath = value
+    }
+
+    // warn required two-way
+    if (
+      process.env.NODE_ENV !== 'production' &&
+      options.twoWay &&
+      prop.mode !== propBindingModes.TWO_WAY
+    ) {
+      _.warn(
+        'Prop "' + name + '" expects a two-way binding type.'
+      )
+    }
+
+    // warn missing required
+    if (value === null && options && options.required) {
       process.env.NODE_ENV !== 'production' && _.warn(
         'Missing required prop: ' + name
       )
     }
+
+    // push prop
     props.push(prop)
   }
   return makePropsLinkFn(props)

+ 5 - 4
src/compiler/compile.js

@@ -136,7 +136,7 @@ function teardownDirs (vm, dirs, destroying) {
  *
  * @param {Vue} vm
  * @param {Element} el
- * @param {Object} options
+ * @param {Object} props
  * @param {Object} [scope]
  * @return {Function}
  */
@@ -554,6 +554,7 @@ function compileDirectives (attrs, options) {
       var attributeName = name.replace(bindRE, '')
       if (attributeName === 'style' || attributeName === 'class') {
         dirName = attributeName
+        arg = undefined
       } else {
         dirName = 'attr'
         arg = attributeName
@@ -588,8 +589,8 @@ function compileDirectives (attrs, options) {
     // should not see props here
     if (process.env.NODE_ENV !== 'production' && propRE.test(name)) {
       _.warn(
-        name + '="' + value + '" is not compiled. It\'s either not declared ' +
-        'on the child component, or it\'s used on a non-component element.'
+        name + '="' + value + '" is not compiled. The prop is either not declared ' +
+        'on the child component, or is used on a non-component element.'
       )
     } else
 
@@ -659,7 +660,7 @@ function collectAttrDirective (name, value, options) {
   if (tokens) {
 
     if (process.env.NODE_ENV !== 'production') {
-      _.deprecation.ATTR_INTERPOLATION()
+      _.deprecation.ATTR_INTERPOLATION(name, value)
     }
 
     var dirName = isClass ? 'class' : 'attr'

+ 11 - 2
src/deprecations.js

@@ -124,11 +124,20 @@ if (process.env.NODE_ENV !== 'production') {
       )
     },
 
-    ATTR_INTERPOLATION: function () {
+    ATTR_INTERPOLATION: function (name, value) {
       warn(
-        'Mustache interpolations inside attributes will be deprecated in 1.0.0. ' +
+        'Mustache interpolations inside attributes: ' + name + '="' + value + '". ' +
+        'This will be deprecated in 1.0.0. ' +
         'Use the "bind-" syntax instead.' + newBindingSyntaxLink
       )
+    },
+
+    PROPS: function (attr, value) {
+      warn(
+        'Prop ' + attr + '="' + value + '" should be prefixed with "prop-" and ' +
+        'bound as expression in 1.0.0. ' +
+        'For more details, see https://github.com/yyx990803/vue/issues/1173'
+      )
     }
 
   }

+ 8 - 8
test/unit/specs/compiler/compile_spec.js

@@ -60,10 +60,10 @@ if (_.inBrowser) {
       expect(typeof linker).toBe('function')
       linker(vm, el)
       expect(vm._bindDir.calls.count()).toBe(4)
-      expect(vm._bindDir).toHaveBeenCalledWith('a', el, descriptorB, defA, undefined, undefined, undefined)
-      expect(vm._bindDir).toHaveBeenCalledWith('a', el.firstChild, descriptorA, defA, undefined, undefined, undefined)
-      expect(vm._bindDir).toHaveBeenCalledWith('b', el.firstChild, descriptorB, defB, undefined, undefined, undefined)
-      expect(vm._bindDir).toHaveBeenCalledWith('b', el.lastChild, descriptorB, defB, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('a', el, descriptorB, defA, undefined, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('a', el.firstChild, descriptorA, defA, undefined, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('b', el.firstChild, descriptorB, defB, undefined, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('b', el.lastChild, descriptorB, defB, undefined, undefined, undefined, undefined)
       // check the priority sorting
       // the "b" on the firstNode should be called first!
       expect(vm._bindDir.calls.argsFor(1)[0]).toBe('b')
@@ -79,9 +79,9 @@ if (_.inBrowser) {
       var linker = compile(el, Vue.options)
       linker(vm, el)
       expect(vm._bindDir.calls.count()).toBe(3)
-      expect(vm._bindDir).toHaveBeenCalledWith('class', el, descA, Vue.options.directives.class, undefined, undefined, undefined)
-      expect(vm._bindDir).toHaveBeenCalledWith('style', el, descB, Vue.options.directives.style, undefined, undefined, undefined)
-      expect(vm._bindDir).toHaveBeenCalledWith('attr', el, descC, Vue.options.directives.attr, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('class', el, descA, Vue.options.directives.class, undefined, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('style', el, descB, Vue.options.directives.style, undefined, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('attr', el, descC, Vue.options.directives.attr, undefined, undefined, undefined, 'title')
     })
 
     it('on- syntax', function () {
@@ -90,7 +90,7 @@ if (_.inBrowser) {
       var linker = compile(el, Vue.options)
       linker(vm, el)
       expect(vm._bindDir.calls.count()).toBe(1)
-      expect(vm._bindDir).toHaveBeenCalledWith('on', el, desc, Vue.options.directives.on, undefined, undefined, undefined)
+      expect(vm._bindDir).toHaveBeenCalledWith('on', el, desc, Vue.options.directives.on, undefined, undefined, undefined, 'click')
     })
 
     it('text interpolation', function () {