Evan You пре 11 година
родитељ
комит
b4aa4378ea

+ 27 - 32
changes.md

@@ -67,6 +67,18 @@ computed: {
 
 ## Directive changes
 
+### Dynamic literals
+
+Literal directives can now also be dynamic via bindings like this:
+
+``` html
+<div v-component="{{test}}"></div>
+```
+
+When `test` changes, the component used will change! This essentially replaces the old `v-view` directive.
+
+When authoring literal directives, you can now provide an `update()` function if you wish to handle it dynamically. If no `update()` is provided the directive will be treated as a static literal and only evaluated once.
+
 ### New options
 
 - `twoWay`: indicates the directive is two-way and may write back to the model. Allows the use of `this.set(value)` inside directive functions.
@@ -95,25 +107,7 @@ Vue.filter('format', {
 
 ## Block logic control
 
-``` html
-<!-- v-repeat="list" -->
-  <h2>{{title}}</h2>
-  <p>{{content}}</p>
-<!-- v-repeat-end -->
-```
-
-``` html
-<!-- v-if="showProfile" -->
-  <my-avatar></my-avatar>
-  <my-bio></my-bio>
-<!-- v-if-end -->
-```
-
-``` html
-<!-- v-partial="hello" -->
-```
-
-**Note** The old inline partial syntax `{{> partial}}` has been removed. This is to keep the semantics of interpolation tags purely for interpolation purposes; flow control and partials are now either used in the form of attribute directives or comment directives.
+Still open to suggestions. See details [here].
 
 ## Config API change
 
@@ -143,24 +137,25 @@ Vue.config.delimiters = ['(%', '%)']
 
 * Note you still cannot use `<` or `>` in delimiters because Vue uses DOM-based templating.
 
-## (Experimental) Validators
-
-This is largely write filters that accept a Boolean return value. Probably should live as a plugin.
+## One time interpolations
 
 ``` html
-  <input v-model="abc @ email">
+<span>{{* hello }}</span>
 ```
 
+## `$watch` API change
+
+`vm.$watch` can now accept an expression:
+
 ``` js
-  Vue.validator('email', function (val) {
-    return val.match(...)
-  })
-  // this.$validation.abc // false
-  // this.$valid // false
+vm.$watch('a + b', function (newVal, oldVal) {
+  // do something
+})
 ```
 
-## (Experimental) One time interpolations
+By default the callback only fires when the value changes. If you want it to be called immediately with the initial value, use the third optional `immediate` argument:
 
-``` html
-<span>{{* hello }}</span>
-```
+``` js
+vm.$watch('a', callback, true)
+// callback is fired immediately with current value of `a`
+``` 

+ 5 - 1
src/api/data.js

@@ -65,12 +65,16 @@ exports.$delete = function (key) {
  *
  * @param {String} exp
  * @param {Function} cb
+ * @param {Boolean} [immediate]
  * @return {Number}
  */
 
-exports.$watch = function (exp, cb) {
+exports.$watch = function (exp, cb, immediate) {
   var watcher = new Watcher(this, exp, cb, this)
   this._watchers[watcher.id] = watcher
+  if (immediate) {
+    cb.call(this, watcher.value)
+  }
   return watcher.id
 }
 

+ 2 - 2
src/api/dom.js

@@ -58,9 +58,9 @@ exports.$before = function (target, cb) {
 exports.$after = function (target, cb) {
   target = query(target)
   if (target.nextSibling) {
-    this.$before(target.nextSibling)
+    this.$before(target.nextSibling, cb)
   } else {
-    this.$appendTo(target.parentNode)
+    this.$appendTo(target.parentNode, cb)
   }
 }
 

+ 8 - 9
src/directive.js

@@ -63,19 +63,19 @@ p._initDef = function () {
  */
 
 p._bind = function () {
-  this.watcherExp = this.expression
-  var isDynamicLiteral = this._checkDynamicLiteral()
+  this._watcherExp = this.expression
+  this._checkDynamicLiteral()
   if (this.bind) {
     this.bind()
   }
   if (
     this.expression && this.update &&
-    (!this.isLiteral || isDynamicLiteral)
+    (!this.isLiteral || this._isDynamicLiteral)
   ) {
     if (!this._checkExpFn()) {
       this._watcher = new Watcher(
         this.vm,
-        this.watcherExp,
+        this._watcherExp,
         this._update, // callback
         this, // callback context
         this.filters,
@@ -91,8 +91,6 @@ p._bind = function () {
  * check if this is a dynamic literal binding.
  *
  * e.g. v-component="{{currentView}}"
- *
- * @return {Boolean}
  */
 
 p._checkDynamicLiteral = function () {
@@ -108,9 +106,10 @@ p._checkDynamicLiteral = function () {
           'in literal directives.'
         )
       } else {
-        this.watcherExp = tokens[0].value
-        this.expression = this.vm.$eval(expression)
-        return true
+        var exp = tokens[0].value
+        this.expression = this.vm.$get(exp)
+        this._watcherExp = exp
+        this._isDynamicLiteral = true
       }
     }
   }

+ 94 - 14
src/directives/component.js

@@ -1,4 +1,22 @@
 var _ = require('../util')
+var Watcher = require('../watcher')
+
+/**
+ * Possible permutations:
+ *
+ * - literal:
+ *   v-component="comp"
+ *
+ * - dynamic:
+ *   v-component="{{currentView}}"
+ *
+ * - conditional:
+ *   v-component="comp" v-if="abc"
+ *
+ * - dynamic + conditional:
+ *   v-component="{{currentView}}" v-if="abc"
+ *
+ */
 
 module.exports = {
 
@@ -6,25 +24,87 @@ module.exports = {
 
   bind: function () {
     if (!this.el.__vue__) {
-      var registry = this.vm.$options.components
-      var Ctor = registry[this.expression]
-      if (Ctor) {
-        this.childVM = new Ctor({
-          el: this.el,
-          parent: this.vm
-        })
-      } else {
-        _.warn(
-          'Failed to resolve component: ' +
-          this.expression
-        )
+      // create a ref anchor
+      this.ref = document.createComment('v-component')
+      _.before(this.ref, this.el)
+      _.remove(this.el)
+      // check v-if conditionals
+      this.checkIf()
+      // if static, build right now.
+      if (!this._isDynamicLiteral) {
+        this.resolveCtor(this.expression)
+        this.build()
       }
+    } else {
+      _.warn(
+        'v-component ' + this.expression + ' cannot be ' +
+        'used on an already mounted instance.'
+      )
     }
   },
 
-  unbind: function () {
+  checkIf: function () {
+    var condition = _.attr(this.el, 'if')
+    if (condition !== null) {
+      this.ifWatcher = new Watcher(
+        this.vm,
+        condition,
+        this.ifCallback,
+        this
+      )
+      this.active = this.ifWatcher.value
+    } else {
+      this.active = true
+    }
+  },
+
+  ifCallback: function (value) {
+    if (value) {
+      this.active = true
+      this.build()
+    } else {
+      this.active = false
+      this.unbuild(true)
+    }
+  },
+
+  resolveCtor: function (id) {
+    var registry = this.vm.$options.components
+    this.Ctor = registry[id]
+    if (!this.Ctor) {
+      _.warn('Failed to resolve component: ' + id)
+    }
+  },
+
+  build: function () {
+    if (this.active && this.Ctor && !this.childVM) {
+      this.childVM = new this.Ctor({
+        el: this.el.cloneNode(true),
+        parent: this.vm
+      })
+      this.childVM.$before(this.ref)
+    }
+  },
+
+  unbuild: function (remove) {
     if (this.childVM) {
-      this.childVM.$destroy()
+      this.childVM.$destroy(remove)
+      this.childVM = null
+    }
+  },
+
+  update: function (value) {
+    this.unbuild(true)
+    if (value) {
+      this.resolveCtor(value)
+      this.build()
+    }
+  },
+
+  unbind: function () {
+    this.unbuild()
+    if (this.ifWatcher) {
+      this.ifWatcher.teardown()
     }
   }
 

+ 5 - 41
src/directives/if.js

@@ -3,51 +3,15 @@ var _ = require('../util')
 module.exports = {
 
   bind: function () {
-    // resolve component
-    var registry = this.vm.$options.components
-    var el = this.el
-    this.Ctor =
-      registry[el.tagName.toLowerCase()] ||
-      registry[_.attr(el, 'component')] ||
-      _.Vue
-    this.isAnonymous = this.Ctor === _.Vue
-    // insert ref
-    this.ref = document.createComment('v-if')
-    _.before(this.ref, el)
-    _.remove(el)
-    // warn conflicts
-    if (_.attr(el, 'view')) {
-      _.warn(
-        'Conflict: v-if cannot be used together with ' +
-        'v-view. Just set v-view\'s binding value to ' +
-        'empty string to empty it.'
-      )
-    }
-    if (_.attr(el, 'repeat')) {
-      _.warn(
-        'Conflict: v-if cannot be used together with ' +
-        'v-repeat. Use `v-show` or the `filterBy` filter ' +
-        'instead.'
-      )
-    }
+    
   },
 
-  update: function (value) {
-    if (!value) {
-      this.unbind()
-    } else if (!this.childVM) {
-      this.childVM = new this.Ctor({
-        el: this.el.cloneNode(true),
-        parent: this.vm,
-        anonymous: this.isAnonymous
-      })
-      this.childVM.$before(this.ref)
-    }
+  update: function () {
+    
   },
 
   unbind: function () {
-    if (this.childVM) {
-      this.childVM.$destroy()
-    }
+    
   }
+
 }

+ 0 - 1
src/directives/index.js

@@ -17,7 +17,6 @@ directives.on         = require('./on')
 directives.model      = require('./model')
 
 // child vm directives
-directives.view       = require('./view')
 directives.component  = require('./component')
 directives.repeat     = require('./repeat')
 directives['if']      = require('./if')

+ 2 - 2
src/directives/partial.js

@@ -12,10 +12,10 @@ module.exports = {
       return
     }
     partial = templateParser.parse(partial, true)
+    var el = this.el
+    var vm = this.vm
     // comment ref node means inline partial
     if (el.nodeType === 8) {
-      var el = this.el
-      var vm = this.vm
       // keep a ref for the partial's content nodes
       var nodes = _.toArray(partial.childNodes)
       _.before(partial, el)

+ 0 - 36
src/directives/view.js

@@ -1,36 +0,0 @@
-var _ = require('../util')
-
-module.exports = {
-
-  bind: function () {
-    // track position in DOM with a ref node
-    var el = this.el
-    var ref = this.ref = document.createComment('v-view')
-    _.before(ref, el)
-    _.remove(el)
-  },
-
-  update: function(value) {
-    this.unbind()
-    if (!value) {
-      return
-    }
-    var Ctor  = this.vm.$options.components[value]
-    if (!Ctor) {
-      _.warn('Failed to resolve component: ' + value)
-      return
-    }
-    this.childVM = new Ctor({
-      el: this.el.cloneNode(true),
-      parent: this.vm
-    })
-    this.childVM.$before(this.ref)
-  },
-
-  unbind: function() {
-    if (this.childVM) {
-      this.childVM.$destroy()
-    }
-  }
-
-}

+ 1 - 2
src/filters/array-filters.js

@@ -23,7 +23,6 @@ exports._objToArray = function (obj) {
     return
   }
   var res = []
-  var val, data
   for (var key in obj) {
     res.push({
       key: key,
@@ -59,7 +58,7 @@ exports.filterBy = function (arr, searchKey, delimiter, dataKey) {
   // get the optional dataKey
   dataKey =
     dataKey &&
-    (stripQuotes(dataKey) || this.$get(dataKey))
+    (_.stripQuotes(dataKey) || this.$get(dataKey))
   return arr.filter(function (item) {
     return dataKey
       ? contains(Path.get(item, dataKey), search)

+ 1 - 1
src/filters/index.js

@@ -66,7 +66,7 @@ filters.currency = function (value, sign) {
  */
 
 filters.pluralize = function (value) {
-  var args = slice.call(arguments, 1)
+  var args = _.toArray(arguments, 1)
   return args.length > 1
     ? (args[value - 1] || args[args.length - 1])
     : (args[value - 1] || args[0] + 's')

+ 3 - 4
src/instance/compile.js

@@ -180,9 +180,8 @@ exports._compileTextNode = function (node) {
 
 var priorityDirs = [
   'repeat',
-  'if',
-  'view',
-  'component'
+  'component',
+  'if'
 ]
 
 exports._checkPriorityDirs = function (node) {
@@ -194,7 +193,7 @@ exports._checkPriorityDirs = function (node) {
   for (var i = 0, l = priorityDirs.length; i < l; i++) {
     dir = priorityDirs[i]
     if (value = _.attr(node, dir)) {
-      this._bindDirective(dir, value)
+      this._bindDirective(dir, value, node)
       return true
     }
   }