user2950426
user2950426

Reputation: 21

Vue <template> markup not behaving as expected while iterating through a list

I have my app.js to generate tabs as below:

Input:

<tabs>
    <div>
        <a slot="header">Tab 1</a>
        <article slot="content">
            <h2>Tab 1 Content</h2>
            <p>Lorem ipsum dolor sit amet</p>
        </article>
    </div>
    <div>
        <a slot="header">Tab 2</a>
        <article slot="content">
            <h2>Tab 2 Content</h2>
            <p>Sed ut perspiciatis unde</p>
        </article>
    </div>
    <div>
        <a slot="header">Tab 3</a>
        <article slot="content">
            <h2>Tab 3 Content</h2>
            <p>Neque porro quisquam est</p>
        </article>
    </div>
</tabs>

Each div above specifies one tab. header slot being title of the tab nav and the content slot is tab content for respective tab.

With the above input to my tabs component I have to generate below output:

Output:

<section class="tabs">
    <nav class="tabs-nav">
        <ul class="tabs-list">
            <li class="tabs-listitem">
                <a class="tabs-trigger">Tab 1</a>
            </li>
            <li class="tabs-listitem">
                <a class="tabs-trigger">Tab 2</a>
            </li>
            <li class="tabs-listitem">
                <a class="tabs-trigger">Tab 3</a>
            </li>
        </ul>
    </nav>
    <div class="tabs-body">
        <article>
            <h2>Tab 1 Content</h2>
            <p>Lorem ipsum dolor sit amet</p>
        </article>
        <article>
            <h2>Tab 2 Content</h2>
            <p>Sed ut perspiciatis unde</p>
        </article>
        <article>
            <h2>Tab 3 Content</h2>
            <p>Lorem ipsum dolor sit amet</p>
        </article>
    </div>
</section>

With the above input to Tabs how can I get the output as specified above in my render function or Vue template.

If I try to iterate through slots by this.$slots.header, I am getting undefined

Could someone please help me to achieve this

Upvotes: 0

Views: 75

Answers (2)

tony19
tony19

Reputation: 138216

The $slots property contains slots that are immediate children, so nested named slots (as you have in your example) would be excluded. In your case, you would only have the default slot, containing the wrapper divs (i.e., the tabs). In the render function, you'd have to manually process the tab's children to get the inner slots in order to generate the HTML you described:

render(h) {
  const tabs = this.$slots.default;
  const headers = tabs && tabs.map(tab => tab.children.filter(x => x.data.attrs['slot'] === 'header'));
  const bodies = tabs && tabs.map(tab => tab.children.filter(x => x.data.attrs['slot'] === 'content'));

  if (!tabs || !bodies) {
    return h('div', 'No tabs');
  }

  return h('section', [
    h('nav', { class: 'tabs-nav' }, [
      h('ul', { class: 'tabs-list' },
        headers.map(header => h('li', { class: 'tabs-listitem' }, header)))
    ]),
    h('div', { class: 'tabs-body' }, bodies)
  ]);
}

demo

Upvotes: 1

Duong Dang
Duong Dang

Reputation: 349

This is basic loop in Vue. Hope this helps

Vue.component('tabs', {
  props: ['tabs'],
  template: `
  <section class="tabs">
      <nav class="tabs-nav">
          <ul class="tabs-list">
              <li v-for="item in tabs" :key="item" class="tabs-listitem">
                  <a class="tabs-trigger">{{item.title}}</a>
              </li>
          </ul>
      </nav>
      <div class="tabs-body">
          <article v-for="item in tabs" v-html="item.content" :key="item"></article>
      </div>
  </section>`
})

var app = new Vue({
  el: '#app',
  data () {
    return {
      tabs: [
        {
          title: 'Tab 1',
          content: '<h1>Tab 1 Content</h1>'
        },
        {
          title: 'Tab 2',
          content: '<h1>Tab 2 Content</h1>'
        },
        {
          title: 'Tab 3',
          content: '<h1>Tab 3 Content</h1>'
        }
      ]
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<div id="app">
  <tabs :tabs="tabs"></tabs>
</div>

Upvotes: 1

Related Questions