element.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. var _ = require('../util')
  2. var templateParser = require('../parse/template')
  3. /**
  4. * Setup the instance's element before compilation.
  5. * 1. Setup $el
  6. * 2. Process the template option
  7. * 3. Resolve <content> insertion points
  8. *
  9. * @param {Node|String} el
  10. */
  11. exports._initElement = function (el) {
  12. if (typeof el === 'string') {
  13. el = document.querySelector(el)
  14. }
  15. // If the passed in `el` is a DocumentFragment, the instance is
  16. // considered a "block instance" which manages not a single element,
  17. // but multiple elements. A block instance's `$el` is an Array of
  18. // the elements it manages.
  19. if (el instanceof window.DocumentFragment) {
  20. this._isBlock = true
  21. this.$el = _.toArray(el.childNodes)
  22. } else {
  23. this.$el = el
  24. }
  25. this._initTemplate()
  26. this._initContent()
  27. }
  28. /**
  29. * Process the template option.
  30. * If the replace option is true this will also modify the $el.
  31. */
  32. exports._initTemplate = function () {
  33. var el = this.$el
  34. var options = this.$options
  35. var template = options.template
  36. if (template) {
  37. var frag = templateParser.parse(template)
  38. if (!frag) {
  39. _.warn('Invalid template option: ' + template)
  40. } else {
  41. // collect raw content. this wipes out the container el.
  42. this._collectRawContent()
  43. frag = frag.cloneNode(true)
  44. if (options.replace) {
  45. // replace
  46. if (frag.childNodes.length > 1) {
  47. // the template contains multiple nodes
  48. // in this case the original `el` is simply
  49. // a placeholder.
  50. this._isBlock = true
  51. this.$el = _.toArray(frag.childNodes)
  52. } else {
  53. // 1 to 1 replace, we need to copy all the
  54. // attributes from the original el to the replacer
  55. this.$el = frag.firstChild
  56. _.copyAttributes(el, this.$el)
  57. }
  58. if (el.parentNode) {
  59. _.before(this.$el, el)
  60. _.remove(el)
  61. }
  62. } else {
  63. // simply insert.
  64. el.appendChild(frag)
  65. }
  66. }
  67. }
  68. }
  69. /**
  70. * Collect raw content inside $el before they are
  71. * replaced by template content.
  72. */
  73. exports._collectRawContent = function () {
  74. var el = this.$el
  75. var child
  76. if (el.hasChildNodes()) {
  77. this._rawContent = document.createElement('div')
  78. /* jshint boss: true */
  79. while (child = el.firstChild) {
  80. this._rawContent.appendChild(child)
  81. }
  82. }
  83. }
  84. /**
  85. * Resolve <content> insertion points per W3C Web Components
  86. * working draft:
  87. *
  88. * http://www.w3.org/TR/2013/WD-components-intro-20130606/#insertion-points
  89. */
  90. exports._initContent = function () {
  91. var outlets = getOutlets(this.$el)
  92. var raw = this._rawContent
  93. var i = outlets.length
  94. var outlet, select, j, main
  95. if (i) {
  96. // first pass, collect corresponding content
  97. // for each outlet.
  98. while (i--) {
  99. outlet = outlets[i]
  100. if (raw) {
  101. select = outlet.getAttribute('select')
  102. if (select) { // select content
  103. outlet.content = _.toArray(raw.querySelectorAll(select))
  104. } else { // default content
  105. main = outlet
  106. }
  107. } else { // fallback content
  108. outlet.content = _.toArray(outlet.childNodes)
  109. }
  110. }
  111. // second pass, actually insert the contents
  112. for (i = 0, j = outlets.length; i < j; i++) {
  113. outlet = outlets[i]
  114. if (outlet === main) continue
  115. insertContentAt(outlet, outlet.content)
  116. }
  117. // finally insert the main content
  118. if (raw && main) {
  119. insertContentAt(main, _.toArray(raw.childNodes))
  120. }
  121. }
  122. this._rawContent = null
  123. }
  124. /**
  125. * Get <content> outlets from the element/list
  126. *
  127. * @param {Element|Array} el
  128. * @return {Array}
  129. */
  130. var concat = [].concat
  131. function getOutlets (el) {
  132. return _.isArray(el)
  133. ? concat.apply([], el.map(getOutlets))
  134. : _.toArray(el.getElementsByTagName('content'))
  135. }
  136. /**
  137. * Insert an array of nodes at outlet, then remove the outlet.
  138. *
  139. * @param {Element} outlet
  140. * @param {Array} contents
  141. */
  142. function insertContentAt (outlet, contents) {
  143. // not using util DOM methods here because
  144. // parentNode can be cached
  145. var parent = outlet.parentNode
  146. for (var i = 0, j = contents.length; i < j; i++) {
  147. parent.insertBefore(contents[i], outlet)
  148. }
  149. parent.removeChild(outlet)
  150. }