Evan You 12 лет назад
Родитель
Сommit
4e062e9270

+ 4 - 0
changes.md

@@ -71,6 +71,10 @@ computed: {
 
 - `isFn` is no longer necessary for directives expecting function values.
 
+## Interpolation
+
+Text bindings will no longer automatically stringify objects. Use the new `json` filter which gives more flexibility in formatting. Also, `null` will now be printed as is; only `undefined` will yield empty string.
+
 ## Two Way filters
 
 If a filter is defined as a function, it is treated as a read filter by default - i.e. it is applied when data is read from the model and applied to the DOM. You can now specify write filters as well, which are applied when writing to the model, triggered by user input. Write filters are only triggered on two-way bindings like `v-model`.

+ 20 - 0
src/directives/attr.js

@@ -0,0 +1,20 @@
+var _ = require('../util')
+
+exports.bind = function () {
+  var params = this.vm.$options.paramAttributes
+  this.isParam =
+    this.el.__vue__ && // only check rootNode for params
+    params &&
+    params.indexOf(this.arg) > -1
+}
+
+exports.update = function (value) {
+  if (value || value === 0) {
+    this.el.setAttribute(this.arg, value)
+  } else {
+    this.el.removeAttribute(this.arg)
+  }
+  if (this.isParam) {
+    this.vm[this.arg] = _.guardNumber(value)
+  }
+}

+ 33 - 0
src/directives/html.js

@@ -0,0 +1,33 @@
+var _ = require('../util')
+var templateParser = require('../parse/template')
+
+exports.bind = function () {
+  // a comment node means this is a binding for
+  // {{{ inline unescaped html }}}
+  if (this.el.nodeType === 8) {
+    // hold nodes
+    this.nodes = []
+  }
+}
+
+exports.update = function (value) {
+  value = _.guard(value)
+  if (this.nodes) {
+    this.swap(value)
+  } else {
+    this.el.innerHTML = value
+  }
+}
+
+exports.swap = function (value) {
+  // remove old nodes
+  var i = this.nodes.length
+  while (i--) {
+    _.remove(this.nodes[i])
+  }
+  // convert new value to a fragment
+  var frag = templateParser.parse(value, true)
+  // save a reference to these nodes so we can remove later
+  this.nodes = _.toArray(frag.childNodes)
+  _.before(frag, this.el)
+}

+ 18 - 1
src/directives/index.js

@@ -1 +1,18 @@
-module.exports = Object.create(null)
+var directives = module.exports = Object.create(null)
+
+directives.text     = require('./text')
+directives.html     = require('./html')
+directives.attr     = require('./attr')
+// directives.show     = require('./show')
+// directives['class'] = require('./class')
+// directives.ref      = require('./ref')
+// directives.cloak    = require('./cloak')
+// directives.on       = require('./on')
+// directives.repeat   = require('./repeat')
+// directives.model    = require('./model')
+// directives['if']    = require('./if')
+// directives['with']  = require('./with')
+// directives.html     = require('./html')
+// directives.style    = require('./style')
+// directives.partial  = require('./partial')
+// directives.view     = require('./view')

+ 11 - 0
src/directives/text.js

@@ -0,0 +1,11 @@
+var _ = require('../util')
+
+exports.bind = function () {
+  this.attr = this.el.nodeType === 3
+    ? 'nodeValue'
+    : 'textContent'
+}
+
+exports.update = function (value) {
+  this.el[this.attr] = _.guard(value)
+}

+ 66 - 1
src/instance/compile.js

@@ -1,7 +1,9 @@
 var _ = require('../util')
 var config = require('../config')
 var Direcitve = require('../directive')
+var textParser = require('../parse/text')
 var dirParser = require('../parse/directive')
+var templateParser = require('../parse/template')
 
 /**
  * The main entrance to the compilation process.
@@ -109,6 +111,8 @@ exports._compileAttrs = function (node) {
       } else {
         _.warn('Unknown directive: ' + dirName)
       }
+    } else if (config.interpolate) {
+      this._bindAttr(node, attr)
     }
   }
   // sort the directives by priority, low to high
@@ -131,7 +135,38 @@ exports._compileAttrs = function (node) {
  */
 
 exports._compileTextNode = function (node) {
-  
+  var tokens = textParser.parse(node.nodeValue)
+  if (!tokens) {
+    return
+  }
+  var el, token, value
+  for (var i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i]
+    if (token.tag) {
+      if (token.oneTime) {
+        value = this.$get(token.value)
+        el = token.html
+          ? templateParser.parse(value, true)
+          : document.createTextNode(value)
+        _.before(el, node)
+      } else {
+        value = token.value
+        if (token.html) {
+          el = document.createComment('vue-html')
+          _.before(el, node)
+          this._bindDirective('html', value, el)
+        } else {
+          el = document.createTextNode('')
+          _.before(el, node)
+          this._bindDirective('text', value, el)
+        }
+      }
+    } else {
+      el = document.createTextNode(token.value)
+      _.before(el, node)
+    }
+  }
+  _.remove(node)
 }
 
 /**
@@ -144,6 +179,36 @@ exports._compileComment = function (node) {
   
 }
 
+/**
+ * Check an attribute for potential bindings
+ */
+
+exports._bindAttr = function (node, attr) {
+  var tokens = textParser.parse(attr.value)
+  if (!tokens) {
+    return
+  }
+  var expression = tokens.map(expifyToken).join('+')
+  this._bindDirective(
+    'attr',
+    attr.name + ':' + expression,
+    node
+  )
+}
+
+/**
+ * Helper to translate token value into expression parts.
+ *
+ * @param {Object} token
+ * @return {String}
+ */
+
+function expifyToken (token) {
+  return token.tag
+    ? token.value
+    : ("'" + token.value + "'")
+}
+
 /**
  * Check for priority directives that would potentially
  * skip other directives:

+ 1 - 2
src/instance/element.js

@@ -43,13 +43,12 @@ exports._initTemplate = function () {
   var options = this.$options
   var template = options.template
   if (template) {
-    var frag = templateParser.parse(template)
+    var frag = templateParser.parse(template, true)
     if (!frag) {
       _.warn('Invalid template option: ' + template)
     } else {
       // collect raw content. this wipes out $el.
       this._collectRawContent()
-      frag = frag.cloneNode(true)
       if (options.replace) {
         // replace
         if (frag.childNodes.length > 1) {

+ 5 - 2
src/parse/template.js

@@ -115,10 +115,11 @@ function nodeToFragment (node) {
  *    - Node object of type Template
  *    - id selector: '#some-template-id'
  *    - template string: '<div><span>{{msg}}</span></div>'
+ * @param {Boolean} clone
  * @return {DocumentFragment|undefined}
  */
 
-exports.parse = function (template) {
+exports.parse = function (template, clone) {
   var node, frag
 
   // if the template is already a document fragment,
@@ -149,5 +150,7 @@ exports.parse = function (template) {
     frag = nodeToFragment(template)
   }
 
-  return frag
+  return frag && clone
+    ? frag.cloneNode(true)
+    : frag
 }

+ 3 - 1
src/util/dom.js

@@ -10,7 +10,9 @@ var config = require('../config')
 exports.attr = function (node, attr) {
   attr = config.prefix + attr
   var val = node.getAttribute(attr)
-  node.removeAttribute(attr)
+  if (val !== null) {
+    node.removeAttribute(attr)
+  }
   return val
 }
 

+ 31 - 0
src/util/lang.js

@@ -1,3 +1,34 @@
+/**
+ * Guard text output, make sure undefined outputs
+ * empty string
+ *
+ * @param {*} value
+ * @return {String}
+ */
+
+exports.guard = function (value) {
+  return value === undefined
+    ? ''
+    : value
+}
+
+/**
+ * Check and convert possible numeric numbers before
+ * setting back to data
+ *
+ * @param {*} value
+ * @return {*|Number}
+ */
+
+exports.guardNumber = function (value) {
+  return (
+    isNaN(value) ||
+    value === null ||
+    typeof value === 'boolean'
+  ) ? value
+    : Number(value)
+}
+
 /**
  * Simple bind, faster than native
  *

+ 6 - 0
test/unit/specs/parse_template_spec.js

@@ -99,6 +99,12 @@ if (_.inBrowser) {
       expect(res1).toBe(res2)
     })
 
+    it('should clone', function () {
+      var res1 = parse(testString, true)
+      var res2 = parse(testString, true)
+      expect(res1).not.toBe(res2)
+    })
+
     it('should cache id selectors', function () {
       var node = document.createElement('script')
       node.setAttribute('id', 'template-test')

+ 7 - 0
test/unit/specs/util_dom_spec.js

@@ -16,6 +16,13 @@ if (_.inBrowser) {
       target = div()
       parent.appendChild(child) 
     })
+
+    it('attr', function () {
+      target.setAttribute('v-test', 'ok')
+      var val = _.attr(target, 'test')
+      expect(val).toBe('ok')
+      expect(target.hasAttribute('v-test')).toBe(false)
+    })
     
     it('before', function () {
       _.before(target, child)

+ 15 - 0
test/unit/specs/util_lang_spec.js

@@ -2,6 +2,21 @@ var _ = require('../../../src/util')
 
 describe('Util - Language Enhancement', function () {
 
+  it('guard', function () {
+    expect(_.guard(1)).toBe(1)
+    expect(_.guard(null)).toBe(null)
+    expect(_.guard(undefined)).toBe('')
+  })
+
+  it('guardNumber', function () {
+    expect(_.guardNumber('12')).toBe(12)
+    expect(_.guardNumber('1e5')).toBe(1e5)
+    expect(_.guardNumber('0x2F')).toBe(0x2F)
+    expect(_.guardNumber(null)).toBe(null)
+    expect(_.guardNumber(true)).toBe(true)
+    expect(_.guardNumber('hello')).toBe('hello')
+  })
+
   it('bind', function () {
     var original = function (a) {
       return this.a + a