Controlling Children from within Vue's render function

Viewed 1430

I am struggling to create a seemingly easy component in Vue.js - just a bootstrap panel with optional heading, content and optional footer using JS render function. The idea is for it to go something like this:

<panel>

  <panel-header> Header </panel-header>

     [ The panel content goes here ]

  <panel-footer> Footer </panel-footer>

</panel>

My question is: How can I "include" children components (panel-header & panel-footer) with their particular classes (i.e. "panel-header" class) in the render function, keep them both optional and yet allow for full freedom in composing the panel body's, with its own classes (i.e. "panel-body" class and props.title)?

To put it differently: How can I control the children, but separate components and yet have [children] to populate the middle of the panel?

My rendering sctipt looks something like this:

import classNames from 'classnames';

export const props = {
tag: {
  type: String,
  default: "div"
},
className: {
  type: String
},
align: {
  type: String,
  default: 'left'
},
title: {
  type: String,
},
header: {
  type: String,
}
};

export default {
 functional: true,
 props,
 render(h, { props, data, children }) {
  const dataObj = {
    class: classNames(
      'card',
      props.align ? 'text-' + props.align : '',
      props.className ? props.className : ''
    ),
 };
 const title = [
  h('h4', {
    class: 'card-title'
  }, props.title)
];

 const content = [
   h('div', {
     class: 'card-body'
   })
 ];
return h(props.tag, dataObj, [title, children] );
}
};

With Best Regards, Paco Pacici

EDIT: I know that once I do it as above, I'll get the desired effect - having the header, content and footer in the panel, in correct order. But what If I wanted to add some properties to the panels' content alone, not including the footer and header? As such, they'd be considered children as well and therefore submit to my manipulation. I'd like to treat them as separately, but inside the very same function.

1 Answers

Essentially, you would examine the children, grab the header and footer as needed and lay them out as desired.

console.clear()

const PanelHeader = {
  template: `<div>Im a panel header</div>`
}

const PanelFooter = {
  template: `<div>Im a panel Footer</div>`
}

const Panel = {
  functional: true,
  render(h, context){
    // find the header if there is one. Note that if there is more than
    // one panel-header, only the first one will be used
    let header = context.children.find(c => c.componentOptions && c.componentOptions.tag === "panel-header")
    // same deal for the footer
    let footer = context.children.find(c => c.componentOptions && c.componentOptions.tag === "panel-footer")
    // filter out anything that isn't header/footer
    let body = context.children.filter(c => c !== header && c !== footer)
    
    // layout as desired.
    return h('div', [header, body, footer])
  }
}

new Vue({
  el: "#app",
  components: {Panel, PanelHeader, PanelFooter}
})
<script src="https://unpkg.com/vue@2.5.3"></script>
<div id="app">
  <panel>
    <panel-footer></panel-footer>
    some stuff
    <panel-header></panel-header>
  </panel>
  <hr>
  <panel>
    some stuff
  </panel>
  <hr>  
  <panel>
    <panel-footer></panel-footer>
    some stuff
  </panel>
</div>

This example only shows how to get the components and lay them out. It obviously doesn't follow your example exactly or include the necessary classes.

Related