javascriptvue.jsvuejs2vue-render-function

Wrapping dynamically added child elements in my slot


If i have a component with a single slot and instead of just rendering all of it's children I want to wrap each element, I do something like this:

Vue.component('MyElement', {
  render: function(createElement){
      for (let i = 0; i < this.$slots.default.length; i++) {
      //create a wrapper element
      let wrappedElement = createElement("div", {}, this.$slots.default[i]);
      // replace the current element in this slot, with the wrapped element
      this.$slots.default[i] = wrappedElement;
    }

    return createElement("div", this.$slots.default);
  }
}

Used like this:

<MyElement ref="myElement">
  <p>Item 1</p>
  <p>Item 2</p>
</MyElement>

Which ends up looking like this

<div>
  <div>
    <p>Item 1</p>
  </div>
  <div>
     <p>Item 2</p>
  </div>
</div>

Up to this point everything is great.

Now when I'd like to insert another <p> element into <MyElement> using

// get reference to <MyElement>
const myElement = this.$refs["myElement"];
// create a new element
var newElement = document.createElement("div");
newElement.innerText = "Hiya";
myElement .$el.appendChild(newElement);

The new element won't get wrapped, because render is not invoked again, how can I take full control of rendering for each child in my slot? or is there a better when to append children programatically into a component?

Thanks


Solution

  • If you could consider creating a component for the Items. So that when you want to add a new item, you could just create a method which will invoke the item component.

    I made a Code Snippet for this example.

    Vue.component('MyElement', {
      template: '#element-container',
    });
    
    Vue.component('MyElementItem', {
      template: '#element-item',
      props: {
        innerText: {
          type: String,
          default: '',
        }
      }
    });
    
    new Vue({
      el: '#app',
      data() {
        return {
          items: ['Item 1', 'Item 2', 'Item 3'],
        }
      },
      method: {
        // you can add a method that will add more items, then the MyElementItem component will be invoked.
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    
    <div id="app">
      <my-element>
        <my-element-item v-for="(item, index) in items" :key="index" :inner-text="item" />
      </my-element>
    </div>
    
    <script type="text/x-template" id="element-container">
      <div>
        <slot></slot>
      </div>
    </script>
    
    <script type="text/x-template" id="element-item">
      <div>
        <p>{{ innerText }}</p>
      </div>
    </script>