Nested List Components

Sitevision 6.1 introduces the ability to pass options to a component on render. This improvement simplifies creation of nested List Components. This is a quick tutorial on how to get started.

Mission

This example will demonstrate how to render a nested List Component, in the form of a timeline of entries, where each entry can hold an array of comments.

The data structure

The initial state of the app looks like this. This is the state that will be populated in the WebApp's store.

const entries = [
   {
      id: 'b712',
      author: 'John',
      message: 'Hello mates!',
      comments: [
         {
            id: '4b15',
            author: 'Jane Tigerblood',
            message: 'WINNING!'
         },
         {
            id: '4c43',
            author: 'Joe',
            message: 'Second is first loser'
         }
      ]
   },
   {
      id: '43eb',
      author: 'Jane',
      message: 'Howdy partner!',
      comments: [
         {
            id: 'b16a',
            author: 'John',
            message: 'What are you driving today?'
         },
         {
            id: '40e8',
            author: 'Joe',
            message: 'Audi, partner!'
         }
      ]
   }
];

Component setup

  • Entries.js (List Component)
  • Entry.js (Component)
  • Comments.js (List Component)
  • Comment.js (Component)

Entries.js

define(require => {
   'use strict';

   const ListComponent = require('ListComponent');

   return ListComponent.extend({

      tagName: 'ul',

      childProperty: 'entries',

      childComponentPath: 'Entry',
      
      // The component retrieves what it needs from the store state
      filterState: state => {
         return Object.assign({}, {
            entries: state.entries
         });
      }
   });
});

Entry.js

State for a child component should be retrieved from the store state using id from the new options argument.

define(require => {
   'use strict';

   const
      Component  = require('Component'),
      template   = require('/template/entry');

   return Component.extend({

      template: template,
		
      // NEWS!
      // filterState now receives a second parameter (options) that contains the id
      // of the specific item it renders. The id should be used to retrieve data from the store state.
      // Given that, make sure your children (entries in this example) have an id attribute.
      filterState: (state, options) => {
         const entry = state.entries.find(entry => entry.id === options.id);

         return Object.assign({}, entry);
      }
   });
});

entry.html

When rendering the Comments component, we pass an option to help identify correct data in the filterState method.

<h4><%- author %></h4>
<p><%- message %></p>
<!-- NEWS! Passing options to a component  -->
<!-- We pass down the id of the entry to the Comments component -->
<!-- entryId will be available as options and in the filterState method -->
<%= renderer.renderComponent('Comments', {entryId: id}) %>

Comments.js

The entryId passed on render will be available on this.options as well as on the options argument in filterState.

define(require => {
   'use strict';

   const ListComponent = require('ListComponent');

   return ListComponent.extend({

      tagName: 'ul',

      childProperty: 'comments',

      childComponentPath: 'Comment',
      
      // Pass down the entryId to the children (Comment)
      // Will be used to retrieve data from the store state
      childOptions: function() {
         return {
            entryId: this.options.entryId
         };
      },
      
      // The options argument contains the entryId which we passed on render
      // Here it is used to retrieve data from the store state
      filterState: (state, options) => {
         const entry = state.entries.find(entry => entry.id === options.entryId);

         return Object.assign({}, {
            comments: entry.comments
         });
      }
   });
});

Comment.js

To retrieve data for a specific comment we use entryId and id that are available on the options argument. The entryId is passed from childOptions of the Comments component and the id refers to the child item that is being rendered.

define(require => {
   'use strict';

   const
      Component  = require('Component'),
      template   = require('/template/comment');

   return Component.extend({

      template: template,
      	
      // options is populated with entryId (from parent component) 
      // and id (from the item being rendered)
      filterState: (state, options) => {
         const entry = state.entries.find(entry => entry.id === options.entryId);
         const comment = entry.comments.find(comment => comment.id === options.id);

         return Object.assign({}, comment);
      }
   });
});

Summary

Passing options on render will ease the creation of nested List Components as it will be easier to locate which data a child component should retrieve from the store.

Component reusability between WebApps will also be simplified since general components can be built utilizing passed options instead of relying on state coming from the store.

Do you want to subscribe to News from Sitevision Developer team? Subscribe here!