JET | Friday, February 3, 2017

JET Composite Components IX - Advanced Slotting

By: Duncan Mills | Architect

Introduction

In the previous article, I introduced slotting and showed how it can be used to inject external content into specific places within your Composite Components. In this article we'll look at some more advanced topics that relate to the use of Composite Components with slots, specifically:

  • Nesting
  • Looping
  • Slotting and the Lifecycle

Nested Composite Components

There is no restriction as to what elements you can assign inside of a Composite Component slot and therefore it follows that it's going to be possible to nest Composite Components, and indeed this extends to nesting the same type of component with exactly the same set of slots. For example, imagine that I have two Composite Components, ccdemo-component-1 and ccdemo-component-2 where each component defines an <oj-slot> with a name of content. These would all be valid combinations:

Example 1
<!--Slot containing just a span, we've seen this before-->
<ccdemo-component-1 value={{name}}>
  <span slot="content">Component 1 Content</span>
</ccdemo-component-1>

or

Example 2
<!--Slot containing a different CCA-->
<ccdemo-component-1 value={{name}}>
  <ccdemo-component-2 value={{name}} slot="content">
    <span slot="content">Component 2 Content</span>
  </ccdemo-component-1>
</ccdemo-component-1>

or

Example 3
<!--Slot containing another Instance of the same CCA-->
<ccdemo-component-1 value={{name}}>
  <ccdemo-component-1 value={{name}}  slot="content">
    <span slot="content">Inner Component 1 Content</span>
  </ccdemo-component-1>
</ccdemo-component-1>

The key things to understand with all three variants are:

  1. How each slot is actually allocated
  2. What the binding expressions reference

Looking at the slot management issue first of all. The rule here is pretty simple, any slot attribute is scoped to the Composite Component that is the immediate parent of the element with slot defined. It is not possible for an element to "jump generations" and have itself allocated to any ancestor Composite Components slot apart from that of its immediate parent. This even extends to the case where an element is allocated a slot name which it's immediate parent does not recognize but it's grandparent does. In this case the content allocated to that slot would not be displayed because it's parent Composite Component does not recognize it.

On the issue of binding scope, nested Composite Components are all treated as siblings. In example 2 above, both ccdemo-component-1 and ccdemo-component-2 will be bound to the same source observable name in the common parent view that contains this markup. Put another way, the inner Composite Component does not gain access to the viewModel of the outer Composite Component, that remains private.

Sharing Information between Nested Composites

As stated above, a nested composite does not have magical access to its parent Composites viewModel. So should you need to share information between them then introducing a common context object in the shared top level viewModel is probably the best way. This context object can then be supplied to both the parent Composite Component and the child/children via a bound property ({{...}}).

However, If you have a use case where you do need to pass information between Composites in this way, then perhaps slotting is not the way that they should be combined. In later articles I'll be discussing how to manage deferred UI creation and also how to communicate between Composite Components in a loosely coupled fashion. These may both be better strategies in many cases.

Multiple Nodes in a Slotting

In the previous article in this series I noted how you can assign multiple child nodes to the same slot. Something like:

<ccdemo-name-badge id="cc1"  
                   badge-name="{{personName}}" 
                   badge-image="[[personImageURL]]" 
                   compact-view="false">
  <span slot="greetingArea">Hello</span>
  <div slot="greetingArea">
    <span>I &hearts;</span>
    <img src="images/oracle_jet_icon.png" 
         style="vertical-align:middle"/>
  </div>
</ccdemo-name-badge>

If you do this,  then  as I mentioned,  both DOM nodes will be relocated into the designated slot position in the order that they are defined. However, you can exercise a little more finesse here.

The <oj-slot> tag that you use within the Composite Component view template to indicate where to place slot content, actually supports an additional attribute as well as the name attribute that we saw before. The extra attribute is called index. The index attribute allows you to refer to the nodes that have been defined by the component consumer as belonging to this slot. So for example we might have a simple HTML template for the Composite component like this:

<div>
  <oj-slot name="content" index=0/>
  <hr>
  <oj-slot name="content" index=1/>
</div>

And the consumer might use this component thus:

<ccdemo-example>
  <p slot="content">Hello</p>
  <p slot="content">Goodbye</p>
  <p slot="content">This will not be rendered</p>
<ccdemo-example>

The result would be that the text Hello would end up above the <hr> line and the Goodbye would be below. As indicated the third node is not referenced by the template and so would be ignored.

Slotting and Looping

You can use slot references within a looping construct in your Composite Component view HTML template, but only in a very specific way. It is not, for example, possible to do this:

<!-- ko foreach: myItems -->
  <div class="oj-panel">
    <oj-slot name="item"/>
  </div>
<!-- /ko -->

This is because we can't place a slotting marker in more than one place.

However, we can reference the same slot multiple times if we use both name and index attributes

<!-- ko foreach: myItems -->
  <div class="oj-panel">
    <oj-slot name="item" index="{{$index()}}"/>
  </div>
<!-- /ko -->

Now rather than trying to put the same content in multiple places, we're specifically placing indexed slot children in different places which is allowed. Note that you need to be cautious here, of course, to ensure that the number of DOM nodes supplied by the consumer matches the number of loop iterations that you will go through. Otherwise, in this case, you'd be stamping out empty panels or similar.

Slotting and the Lifecycle

In the context of the Composite Component lifecycle there are two sub-topics to look at:

  1. Discovering what slots are in use
  2. What happens to the DOM during slotting

What Slots are in Use?

If you refer back to the main article on lifecycle you will see that the context object that is passed to the various lifecycle methods contains a promise called slotNodeCounts. When resolved, this gives rise to an object with a property for each slot name that the consumer has referred to, and the value of that property is the number of DOM nodes that are allocated to that slot. This then is useful information for the Composite Component to understand what the user has specified in terms of the advertised slots, for example in the looping scenario as discussed above.

Note: in JET 2.3 and earlier this promise will not resolve until after the Composite Component lifcycle is completed. This means that you cannot rely on the slot count information during the setup phases of the lifecycle. This in turn reduces the value of this information considerably, as generally you would want to actually change the UI created by the Composite Component based on what the consumer has provided for each slot. This problem has been addressed and in later versions of JET the promise should resolve in a more timely manner. As a workaround, you can inspect the DOM with JQuery to manually count the slot entries that you are interested in. (ideally you would do this in the activated() phase):

var nodesInSlot = $(context.element).children('[slot="slotName"]').length

Slotting and the DOM

As you can imagine, internally the Composite Component needs to rearrange DOM nodes in the view to weave the correct nodes into the designated slot positions defined by the view template. Looking back at the lifecycle article again, I stated that (for example) during the activated phase of the lifecycle, the child DOM within the CompositeComponent is empty and does not get populated until attached. This was not strictly true. In fact, during the activated phase (and earlier), if the Composite Component consumer has defined child elements for placement into slots, then those nodes will be present in the DOM as direct children of the Composite Component. When you get to the attached phase of the lifecycle you will find that the nodes have disappeared as direct children of the Composite Component. They have, in face, just been temporarily shunted off into a holding area. Finally, when you get to the bindingsApplied phase the nodes will be positioned in their new homes based on the <oj-slot> markers in the template.

What's Next?

We've now covered all of the core elements of CCA creation, covering properties, methods, events and slotting. The next couple of topics round out the story with some more advanced techniques, starting out with custom property parsing.


CCA Series Index

  1. Introduction
  2. Your First Composite Component - A Tutorial
  3. Composite Conventions and Standards
  4. Attributes, Properties and Data
  5. Events
  6. Methods
  7. The Lifecycle
  8. Slotting Part 1
  9. Slotting Part 2
  10. Custom Property Parsing
  11. Metadata Extensibility
  12. Advanced Loader Scripts
  13. Deferred UI Loading
  14. Using ojModule in CCAs
  15. Language Support
  16. CCAs in Form Layouts
  17. Element IDs in CCA
  18. Labelled Form Fields
  19. Component Sub-Properties

Join the discussion

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
 

Visit the Oracle Blog

 

Contact Us

Oracle

Integrated Cloud Applications & Platform Services