Forráskód Böngészése

working on template and element processing

Evan You 11 éve
szülő
commit
c7fba5ffc4
6 módosított fájl, 337 hozzáadás és 12 törlés
  1. 2 1
      package.json
  2. 1 11
      src/api/lifecycle.js
  3. 98 0
      src/instance/element.js
  4. 11 0
      src/instance/init.js
  5. 157 0
      src/parse/template.js
  6. 68 0
      src/util.js

+ 2 - 1
package.json

@@ -17,7 +17,8 @@
   "bugs": "https://github.com/yyx990803/vue/issues",
   "homepage": "http://vuejs.org",
   "scripts": {
-    "test": "grunt ci"
+    "test": "grunt ci",
+    "jasmine": "jasmine-node test/unit/ --verbose"
   },
   "devDependencies": {
     "browserify": "^4.2.0",

+ 1 - 11
src/api/lifecycle.js

@@ -10,17 +10,7 @@ var _ = require('../util')
  */
 
 exports.$mount = function (el) {
-  if (typeof el === 'string') {
-    el = document.querySelector(el)
-  }
-  // If the passed in `el` is a DocumentFragment, the instance is
-  // considered a "block instance" which manages not a single element,
-  // but multiple elements. A block instance's `$el` is an Array of
-  // the elements it manages.
-  this._isBlock = el instanceof DocumentFragment
-  this.$el = this._isBlock
-    ? _.toArray(el.childNodes)
-    : el
+  this._initElement(el)
   this._compile()
   this._isMounted = true
 }

+ 98 - 0
src/instance/element.js

@@ -0,0 +1,98 @@
+var _ = require('../util')
+var templateParser = require('../parse/template')
+
+/**
+ * Setup the instance's element before compilation.
+ * 1. Setup $el
+ * 2. Process the template option
+ * 3. Resolve <content> insertion points
+ *
+ * @param {Node|String} el
+ */
+
+exports._initElement = function (el) {
+  if (typeof el === 'string') {
+    el = document.querySelector(el)
+  }
+  // If the passed in `el` is a DocumentFragment, the instance is
+  // considered a "block instance" which manages not a single element,
+  // but multiple elements. A block instance's `$el` is an Array of
+  // the elements it manages.
+  if (el instanceof DocumentFragment) {
+    this._isBlock = true
+    this.$el = _.toArray(el.childNodes)
+  } else {
+    this.$el = el
+  }
+  this._initTemplate()
+  this._initContent()
+}
+
+/**
+ * Process the template option.
+ * If the replace option is true this will also modify the $el.
+ */
+
+exports._initTemplate = function () {
+  var el = this.$el
+  var options = this.$options
+  var template = options.template
+  if (template) {
+    var frag = templateParser.parse(template)
+    if (!frag) {
+      _.warn('Invalid template option: ' + template)
+    } else {
+      // collect raw content. this wipes out the container el.
+      this._collectRawContent()
+      frag = frag.cloneNode(true)
+      if (options.replace) {
+        // replace
+        if (frag.childNodes.length > 1) {
+          // the template contains multiple nodes
+          // in this case the original `el` is simply
+          // a placeholder.
+          this._isBlock = true
+          this.$el = _.toArray(frag.childNodes)
+        } else {
+          // 1 to 1 replace, we need to copy all the
+          // attributes from the original el to the replacer
+          this.$el = frag.firstChild
+          _.copyAttributes(el, this.$el)
+        }
+        if (el.parentNode) {
+          _.before(this.$el, el)
+          _.remove(el)
+        }
+      } else {
+        // simply insert.
+        el.appendChild(frag)
+      }
+    }
+  }
+}
+
+/**
+ * Collect raw content inside $el before they are
+ * replaced by template content.
+ */
+
+exports._collectRawContent = function () {
+  if (el.hasChildNodes()) {
+    this.rawContent = document.createElement('div')
+    /* jshint boss: true */
+    while (child = el.firstChild) {
+      this.rawContent.appendChild(child)
+    }
+  }
+}
+
+/**
+ * Resolve <content> insertion points per W3C Web Components
+ * working draft:
+ *
+ *  http://www.w3.org/TR/2013/WD-components-intro-20130606/#insertion-points
+ */
+
+exports._initContent = function () {
+  // TODO
+}

+ 11 - 0
src/instance/init.js

@@ -47,6 +47,17 @@ exports._init = function (options) {
 
   this._isDestroyed = false
 
+  /**
+   * If the instance has a template option, the raw content it holds
+   * before compilation will be preserved so they can be queried against
+   * during content insertion.
+   *
+   * @type {DocumentFragment}
+   * @private
+   */
+
+  this._rawContent = null
+
   // create scope.
   this._initScope()
 

+ 157 - 0
src/parse/template.js

@@ -0,0 +1,157 @@
+var Cache = require('../cache')
+var templateCache = new Cache(100)
+
+var map = {
+  legend   : [1, '<fieldset>', '</fieldset>'],
+  tr       : [2, '<table><tbody>', '</tbody></table>'],
+  col      : [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
+  _default : [0, '', '']
+}
+
+map.td =
+map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>']
+
+map.option =
+map.optgroup = [1, '<select multiple="multiple">', '</select>']
+
+map.thead =
+map.tbody =
+map.colgroup =
+map.caption =
+map.tfoot = [1, '<table>', '</table>']
+
+map.text =
+map.circle =
+map.ellipse =
+map.line =
+map.path =
+map.polygon =
+map.polyline =
+map.rect = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>']
+
+var TAG_RE = /<([\w:]+)/
+
+/**
+ * Convert a string template to a DocumentFragment.
+ * Determines correct wrapping by tag types. Wrapping strategy
+ * originally from jQuery, scooped from component/domify.
+ *
+ * @param {String} templateString
+ * @return {DocumentFragment}
+ */
+
+function stringToFragment (templateString) {
+  // try a cache hit first
+  var hit = templateCache.get(templateString)
+  if (hit) {
+    return hit
+  }
+
+  var frag = document.createDocumentFragment()
+  var tagMatch = TAG_RE.exec(templateString)
+
+  if (!tagMatch) {
+    // text only, return a single text node.
+    frag.appendChild(document.createTextNode(templateString))
+  } else {
+
+    var tag    = tagMatch[1]
+    var wrap   = map[tag] || map._default
+    var depth  = wrap[0]
+    var prefix = wrap[1]
+    var suffix = wrap[2]
+    var node   = document.createElement('div')
+
+    node.innerHTML = prefix + templateString.trim() + suffix
+    while (depth--) {
+      node = node.lastChild
+    }
+
+    if (node.firstChild === node.lastChild) {
+      // one element
+      frag.appendChild(node.firstChild)
+      templateCache.put(templateString, frag)
+      return frag
+    } else {
+      // multiple nodes, return a fragment
+      /* jshint boss: true */
+      var child
+      while (child = node.firstChild) {
+        if (node.nodeType === 1) {
+          frag.appendChild(child)
+        }
+      }
+    }
+  }
+  templateCache.put(templateString, frag)
+  return frag
+}
+
+/**
+ * Convert a template node to a DocumentFragment.
+ *
+ * @param {Node} node
+ * @return {DocumentFragment}
+ */
+
+function nodeToFragment (node) {
+  var tag = node.tagName
+  // if its a template tag and the browser supports it,
+  // its content is already a document fragment.
+  if (tag === 'TEMPLATE' && node.content) {
+    return node.content
+  }
+  // script tag
+  if (tag === 'SCRIPT') {
+    return stringToFragment(node.textContent)
+  }
+  // non-script node. not recommended...
+  return toFragment(node.outerHTML)
+}
+
+/**
+ * Process the template option and normalizes it into a
+ * a DocumentFragment that can be used as a partial or a
+ * instance template.
+ *
+ * @param {*} template
+ *    Possible values include:
+ *    - DocumentFragment object
+ *    - Node object of type Template
+ *    - id selector: '#some-template-id'
+ *    - template string: '<div><span>my template</span></div>'
+ * @return {DocumentFragment|undefined}
+ */
+
+exports.parse = function (template) {
+  var node, frag
+
+  // if the template is already a document fragment -- do nothing
+  if (template instanceof DocumentFragment) {
+    return template
+  }
+
+  if (typeof template === 'string') {
+    // id selector
+    if (template.charAt(0) === '#') {
+      // id selector can be cached too
+      frag = templateCache.get(template)
+      if (!frag) {
+        node = document.getElementById(template.slice(1))
+        if (node) {
+          frag = nodeToFragment(node)
+          // save selector to cache
+          templateCache.put(template, frag)
+        }
+      }
+    } else {
+      // normal string template
+      frag = stringToFragment(template)
+    }
+  } else if (template.nodeType) {
+    // a direct node
+    frag = nodeToFragment(template)
+  }
+
+  return frag
+}

+ 68 - 0
src/util.js

@@ -137,6 +137,74 @@ exports.mergeOptions = function (parent, child, noRecurse) {
   // - use prototypal inheritance where appropriate
 }
 
+/**
+ * Insert el before target
+ *
+ * @param {Element} el
+ * @param {Element} target 
+ */
+
+exports.before = function (el, target) {
+  target.parentNode.insertBefore(el, target)
+}
+
+/**
+ * Insert el after target
+ *
+ * @param {Element} el
+ * @param {Element} target 
+ */
+
+exports.after = function (el, target) {
+  if (target.nextSibling) {
+    exports.before(el, target.nextSibling)
+  } else {
+    target.parentNode.appendChild(el)
+  }
+}
+
+/**
+ * Remove el from DOM
+ *
+ * @param {Element} el
+ */
+
+exports.remove = function (el) {
+  el.parentNode.removeChild(el)
+}
+
+/**
+ * Prepend el to target
+ *
+ * @param {Element} el
+ * @param {Element} target 
+ */
+
+exports.prepend = function (el, target) {
+  if (target.firstChild) {
+    exports.before(el, target.firstChild)
+  } else {
+    target.appendChild(el)
+  }
+}
+
+/**
+ * Copy attributes from one element to another.
+ *
+ * @param {Element} from
+ * @param {Element} to
+ */
+
+exports.copyAttributes = function (from, to) {
+  if (from.hasAttributes()) {
+    var attrs = from.attributes
+    for (var i = 0, l = attrs.length; i < l; i++) {
+      var attr = attrs[i]
+      to.setAttribute(attr.name, attr.value)
+    }
+  }
+}
+
 /**
  * Enable debug utilities. The enableDebug() function and all
  * _.log() & _.warn() calls will be dropped in the minified