Skip to content

dynamic contents in v-if (in component's template) aren't persistent #804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
TerenceZ opened this issue Apr 22, 2015 · 5 comments
Closed

Comments

@TerenceZ
Copy link

If We have a markup [v-if] > content and the content is dynamic, as long as the content changes and make the [v-if] hides->shows, the content in [v-if] will back to the previous [demo]. To find the actual problem, I check the source of vuejs and find that the [v-if] only snapshots the content when binding, and I also find that the implements for instance/compile (snapshot for _transCpnts) and compile in [v-if] (snapshot for children) will occur some problems (e.g., not fire the detach event) in some situations.

@yyx990803
Copy link
Member

Thanks for isolating the issue. Indeed this is due to transcluded content being snapshotted across v-if re-compiles. I'm looking into a better strategy so that transcluded content are properly re-compiled as part of the block.

@TerenceZ
Copy link
Author

I find that the directive placeholder comments are static when as content in the [v-if]. for example,

<my-comp>
  <div v-repeat='somewhat'>Do Something</div>
  <div v-component='some-comp'>Some Content</div>
  <div v-partial='some-partial'>Some Partial</div>
  <div v-if='some-condition'>Do Something</div>
</my-comp>
<script type='text/template' id='my-comp'>
  <div v-if='some-condition'>
    <content></content>
  </div>
</script>

When compiling the transcluded template for my-comp, it will be:

<my-comp>
  <div v-if='some-condition'>
    <div>Do Something</div>
    ...
    <!--v-repeat-->
    <div>Some Content...</div>
    <!--v-component-->
    <div>Some Partial</div>
    <!--v-partial-->
    <!--v-if-start-->
    <div>Do Something</div>
    <!--v-if-end-->
  </div>
</my-comp>

So if the directive comments are all like [v-if] that contains start comment and end comment, we can figure out which parts in [v-if] are dynamic.

Here are some experimental code for [v-if]:

  checkContent: function (el) {
    el = el || this.el
    var stack = 0
    for (var i = 0; i < el.childNodes.length; i++) {
      var node = el.childNodes[i]
      // _isContent is a flag set in instance/compile
      // after the raw content has been compiled by parent
      if (node._isContent) {
        if (node.nodeType === 8) {
          if (node._isEnd && !(--stack)) {
            this.placeholderNodes[this.placeholderNodes.length - 1][1] = node
            this.placeholderPositions[this.placeholderPositions.length - 1][1] = i;
          } else if (node._isStart) {
            if (!stack) {
              ;(this.placeholderNodes = this.placeholderNodes || []).push([node])
              ;(this.placeholderPositions = this.placeholderPositions || []).push([i])
            }
            ++stack
          }
        } else if (!stack) {
          ;(this.contentNodes = this.contentNodes || []).push(node)
          ;(this.contentPositions = this.contentPositions || []).push(i)
        }
      }
    }
    // keep track of any transcluded components contained within
    // the conditional block. we need to call attach/detach hooks
    // for them.
    this.transCpnts =
      this.vm._transCpnts &&
      this.vm._transCpnts.filter(function (c) {
        return el.contains(c.$el)
      })
  },

  update: function (value) {
    if (this.invalid) return
    if (value) {
      // avoid duplicate compiles, since update() can be
      // called with different truthy values
      if (!this.unlink) {
        var frag = templateParser.clone(this.template)
        // persist content nodes from parent.
        this.transclude(frag)
        this.compile(frag)
      }
    } else {
      this.teardown()
    }
  },

  transclude: function (frag) {
    var el = frag.childNodes[0]
    if (this.contentNodes) {
      for (var i = 0, l = this.contentNodes.length; i < l; ++i) {
        var node = this.contentNodes[i]
        var j = this.contentPositions[i]
        el.replaceChild(node, el.childNodes[j])
      }
    }
    if (this.placeholderNodes) {
      var offset = 0
      for (var i = 0, l = this.placeholderNodes.length; i < l; ++i) {
        var startNode = this.placeholderNodes[i][0]
        var endNode = this.placeholderNodes[i][1]
        var start = this.placeholderPositions[i][0] + offset
        var end = this.placeholderPositions[i][1] + offset
        var len = end - start
        var node = startNode.nextSibling
        var j = 1
        while (node !== endNode) {
          if (j < len) {
            el.replaceChild(node, el.childNodes[start + j])
          } else {
            el.insertBefore(node, el.childNodes[end++])
          }
          ++j
          // it is safe to skip compiling the node and its children
          node.setAttribute('v-pre', '')
          node = startNode.nextSibling
        }
        if (j < len) {
          offset += len - j
          while (j < len) {
            el.removeChild(el.childNodes[j++])
          }
        } else {
          offset += j - len
        }
        el.replaceChild(startNode, el.childNodes[start])
        el.replaceChild(endNode, el.childNodes[end])
      }

      /* If offset is not zero, it means the structure changed, 
       * we have to re-compile to adjust the linkFns.
       * I think there is a better way to re-structure the linkFns.
       */
      if (offset) {
        this.template = templateParser.clone(frag)
        if (this.contentNodes) {
          this.contentNodes = []
          this.contentPositions = []
        }
        this.placeholderNodes = []
        this.placeholderPositions = []
        this.checkContent(el)
        this.linker = compile(
          this.template,
          this.vm.$options,
          true
        )
      }
    }
  }

Using this strategy, if there is no dynamic contents in [v-if], it is the same as before.

@TerenceZ
Copy link
Author

I find that this strategy only works for [v-if] > content, but not [v-if] > ... > content :(

@yyx990803
Copy link
Member

I've created a new branch with some major refactoring of transclusion logic: https://github.com/yyx990803/vue/tree/transclude-refactor

It works correctly with your jsfiddle demo, and includes a test case for [v-if] > content ... > [v-repeat]: https://github.com/yyx990803/vue/blob/transclude-refactor/test/unit/specs/directives/if_spec.js#L243-L318

@TerenceZ
Copy link
Author

Thank you, the strategy used in branch has also solved some another problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants