1

What is a good way to build system of nested components with a small advancing of rendering? See desired code with main question ('HOW TO...') below:

tab.vue (child component)

<template>
    <slot></slot>
</template>

<script>
    export default {
        name: 'Tab',
        props: ['title']
    }
</script>

tabs.vue (container component)

<template>
    <div class="tabs-switchers">
        <b
            v-for="(o, i) in items"
            :key="`tab-button-${i}`"
        >
            {{ o.componentInstance.props.title }}
        </b>
    </div>
    <div class="tabs-contents">
        <div class="tabs-contents-item"
            v-for="(o, i) in items"
            :key="`tab-item-${i}`"
        >
            <!-- HOW TO RENDER TAB CONTENT HERE??? -->
        </div>
    </div>
</template>
<script>
    export default {
        name: 'Tabs',
        computed () {
            items () {
                return this.$slots.default
            }
        }
    }
</script>

page.vue (component with example of using)

<template>
    <tabs>
        <tab title="tab 1"><p>Tab #1 content</p></tab>
        <tab title="tab 2"><p>Tab #2 content</p></tab>
        <tab title="tab 3"><p>Tab #3 content</p></tab>
    </tabs>
</template>
2
  • Is this what you're looking for? Commented Feb 1, 2019 at 9:07
  • Thank you but no. The diff is I need to build additional DOM elems based on children. It looks like I need to override render() method instead of using TEMPLATE tag in .vue file. Commented Feb 1, 2019 at 9:15

2 Answers 2

1

Key of solution is to use component's render function ( https://v2.vuejs.org/v2/guide/render-function.html ) to implement some customization. Example:

export default {
  name: 'Tabs',
  render: function (createElement) {
    const buttons = []
    const tabs = []
    const children = this.$slots.default // shortcut

    for (let i = 0; i < children.length; i++) {
      buttons.push(createElement(
        'button',
        children[i].componentOptions.propsData.title
      ))
      tabs.push(createElement(
        'div',
        {
          class: i === 0 ? 'active tab' : 'tab',
          attrs: {
            id: `tab-${i}`
          }
        },
        children[i].componentOptions.children
      ))
    }
    const buttonsContainer = createElement('div', { class: 'buttons' }, buttons)
    const tabsContainer = createElement('div', tabs)

    const root = createElement('div', { class: 'tabs' }, [buttonsContainer, tabsContainer])
    return root
  }
}

Im not sure about VNode API (children[i].componentOptions.propsData.title -- is it right approach?) but it works and It is right way to solve Im sure.

Cheers!

Sign up to request clarification or add additional context in comments.

Comments

0

You should not need v-for for rendering the slot contents.

Vue.component('Tabs', {
  template: `
    <div class="tab-container">
      <slot></slot>
    </div>
  `
})

Vue.component('Tab', {
  template: `
    <div class="tab">
      <strong>{{title}}</strong>
      <slot></slot>
    </div>
  `,
  
  props: ['title']
})

new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <tabs>
    <tab title="tab 1">
      <p>Tab #1 content</p>
    </tab>
    <tab title="tab 2">
      <p>Tab #2 content</p>
    </tab>
    <tab title="tab 3">
      <p>Tab #3 content</p>
    </tab>
  </tabs>
</div>

3 Comments

Nah. Look at tabs.vue to realize what kind of html I needed. <div> <div><!-- Titles --></div> <div><!-- Contents --></div> </div>
Have a look at named slots.
Maybe, but note that all my children components use the same name. What the way to render em in this case? <slot name="tab"> doesn't work in this case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.