Skip to content

lesson 17

Rati Wannapanop edited this page May 25, 2017 · 10 revisions

Passing Scoped Slot to MyVuetable

In the last lesson, we have successfully create properties in our MyVuetable component allowing passing data in from the main component/instant. But the only remaining thing we are not able to do is the scoped slots.

Scoped slots has been newly introduced in Vue 2.1 and it is a very powerful concept when working with Vue.js. It offers a more convenient way of flexible content distribution. Before the introduction of scope slots, you would have to create another component for the job.

The Problem

As of the current release of Vue 2.3, there is still no way to pass the scoped slot down into the component via template. The only way possible is to implement the render function where you'll have access to scopedSlots data object. This will allow you to pass down the scoped slot into your component. But this means you will have to give up the template and render your component using the render function only.

This seems like a daunting task. Luckily, in our case, this is not that hard but you will need some background knowledge before proceeding. Read the following topic in Vue documentation first, then come back to continue with the tutorial. You don't need to understand everything though.

Converting <template> to render function

It is possible to start making your component with render function from the beginning if your component is not so complex (e.g. does not have a complex html structure). But template is actually more natural and a lot easier to comprehend, especially if you're still new to making Vue component.

In fact, you don't really have to care or learn about render function until you really need to use it. Since we would like our users to be able to use scoped slots from our MyVuetable component and pass this down to Vuetable, we need to be able to access to scopedSlots data object which only available inside the render function.

Reviewing the <template>

So, let's begin by reviewing the current template of our MyVuetable. This will help guide us on what we need to do.

<template>
  <div class="ui container">
    <filter-bar></filter-bar>
    <vuetable ref="vuetable"
      :api-url="apiUrl"
      :fields="fields"
      pagination-path=""
      :per-page="10"
      :multi-sort="true"
      :sort-order="sortOrder"
      :append-params="appendParams"
      detail-row-component="detailRowComponent"
      @vuetable:cell-clicked="onCellClicked"
      @vuetable:pagination-data="onPaginationData"
    >
      //... this used to be where scoped slot "actions" was ...
    </vuetable>
    <div class="vuetable-pagination ui basic segment grid">
      <vuetable-pagination-info ref="paginationInfo"
      ></vuetable-pagination-info>
      <vuetable-pagination ref="pagination"
        @vuetable-pagination:change-page="onChangePage"
      ></vuetable-pagination>
    </div>
  </div>
</template>

Please note that the template above does not include the scoped slots "actions". As it should be passed down from the parent component, so it no longer needs to be there.

Looks quite intimidating, isn't it?

Fear Not! It's easier than you might think. There's a trick to that and I'll show you how.

Digesting the Template

The render function is nothing more than mimicking the browser in rendering the HTML. So, if you look at the stardard HTML page, you would see layers of layers of HTML tags inside with the outermost layer is the <html> tag.

<html>
  <head>
    <title></title>
  </head>
  <body>
    <div class="container">
      <div class="header">
        //...
      </div>
      <div class="content">
        //...
      </div>
      <div class="footer">
        //...
      </div>
    </div>
  </body>
</html>

The render function that you're going to write is exactly the same. You render from the outermost layer into the innermost one, step by step, passing the createElement argument (usually denoted with h for brevity) down to the inner layer, so that it can be used to render other stuff inside its block.

Let's digest our template down a bit and I'll explain why it is digestible to this.

<template>
  <div>
    <filter-bar></filter-bar>
    <vuetable></vuetable>
    <div>
      <vuetable-pagination-info></vuetable-pagination-info>
      <vuetable-pagination></vuetable-pagination>
    </div>
  </div>
</template>

Look much better, isn't it?

After all, our component structure wasn't so complex. What makes it complex is the functionality inside that has been exposed through its properties and events. When we strip them down, what's left is the skeleton that we can comprehend.

That, however, doesn't mean that we do not need those attributes and directives we omit. We do, but we will deal with them one by one inside its own block.

Let's discuss each block and starting writing our render function.

The "container" <div> block

Every Vue component must has exactly one root element, which in our case is the outermost <div> block. To be precise, the actual block looks like this. (We will leave out the <template> tag from now on.)

  <div class="ui container">
    <filter-bar></filter-bar>
    <vuetable></vuetable>
    <div></div> <!-- pagination block -->
  </div>
  • It has ui and container classes
  • It contains three children, which are
    • <filter-bar>
    • <vuetable>, and
    • pagination <div>

With this information, we can start writing our render function for the outermost layer, like so.

  render (h) {
    return h(
      'div',  //.. first parameter,
      {},     //.. second parameter,
      []      //.. third parameter
    )
  },

Note
The render function is not inside the methods section, it lives at the same level as the props, data, and methods section!

When you declare the render function, Vue will pass in the createElement argument as a parameter. Since we are going to use this argument very often, we should name it very short. And by convention, it usually names h.

The createElement argument (from now on will be referred to as h) is actually a function, so to use it you have to call it and supplies parameters to it and must return itself back to its parent so that the execution can be chained and handled properly by the main instance.

  • The first parameter is the "tag" that you want to render out as HTML tag, in this case, a div.
  • The second parameter (optional) is the Data Object describing the characteristics of the element to be rendered.
  • The third parameter (optional) can be either a string that will be inside the element tag (e.g. <title>Hello</title>) or array of its children.

In this tutorial, we will always write its parameters in its own line, so that it is easier to notice.

Here is the complete render function of our outermost div block.

  render (h) {
    return h(
      'div', 
      {
        class: { ui: true, container: true }
      },
      [
        h('filter-bar'),
        this.renderVuetable(h),
        this.renderPagination(h)
      ]
    )
  },

We specify that this div should have ui and container class inside the Data Object in the second parameter.

In the third parameter, we specify that this div block will contain 3 children:

  • the first one (filter-bar) does not have any attribute, so we just use h to render it out.
  • the second one (vuetable) will contain quite a lot information, so we just delegate it to another method (renderVuetable) to do the rendering of its block.
  • the third one will also contain some attributes, so we will also delegate to another method (renderPagination) to render its own block as well.

The <vuetable> block

The "pagination" <div> block

Clone this wiki locally