Creating a reusable component

This articles outlines the steps necessary to create and use reusable components, as well as provides a complete example.

This articles outlines the steps necessary to create and use reusable components, as well as provides a complete example.

  1. First, the reusable component author must identify the content of the component, i.e. information that is going to be provided by page authors who will be using the component.

  2. Next, the component author must define the markup representation for page authors to use. It needs a custom namespace that should be based on a domain that the author of the component owns. Note that multiple bindings can use the same namespace.

    The component then needs a root element which will trigger the binding to be applied, and an element wrapper for each separate piece of content.

    The component author can also specify common styles, JavaScript, etc. that should be shared between all usages of that binding.

  3. The next step is to create a binding implementation variant for processing the binding definition. Typically, it will consists of a common part that defines common information (i.e. styles and markup), and an XSL transformation containing templates that will process the input elements.

  1. First, the page author who wants to use a reusable component must add the corresponding binding policy to the mcs-project.xml file. The policy must provide links to the binding definition and implementation(s).

  2. Then, the page author can use the custom markup, defined by the binding definition, just as if it was a part of XDIME and create its own version of the component.

  3. At runtime, MCS will use the component definition to map from the custom markup to built-in markup that it can process directly.

Example

The following XDIME 2 page uses a reusable component that defines a folding item component similar to the one described in the Client Framework 2 tutorial. Please refer to the inline comments for more information.

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/2002/06/xhtml2"
  xmlns:mcs="http://www.volantis.com/xmlns/2006/01/xdime/mcs"
  xmlns:template="http://www.volantis.com/xmlns/marlin-template"
  xmlns:cool="http://www.cool-components.com/xmlns/library">
  <head>
    <title>Reusable Components - Folding Item</title>
    <style type="text/css">
      @namespace cool "http://www.cool-components.com/xmlns/library";
      .folding-item::cool|button {
        border: 1px solid orange;
      }
      cool|details {
        display: block;
        border: 1px solid blue;
      }
    </style>
  </head>
  <body>

    <!--
     ! The root element of the folding item reusable component
     !-->
    <cool:folding-item class="folding-item" open="false">

      <!--
       ! Defines the visible title, label or thumbnail for content that is initially hidden. 
       !-->
      <cool:summary style="color: green">Summary</cool:summary>

      <!-- 
       ! Specifies the content of the component that is initially hidden 
       !-->
      <cool:details>
        <div>Item 1</div>
        <div>Item 2</div>
        <div>Item 3</div>
      </cool:details>

    </cool:folding-item>

  </body>
</html>

The mcs-project.xml file must link to a binding policy that contains the binding definition and implementation.

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://www.volantis.com/xmlns/mcs/project"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  name="Bindings">
  <xml-policies directory="policies/"/>
  <assets base-url="assets/"/>

  <bindings>
    <policy>/bindings/folding-item.mbnd</policy>
  </bindings>

</project>

The /bindings/folding-item.mbnd policy must reference the binding definition and following implementation. The folding-item-definition.xml file should contain the following code:

<bd:definition xmlns:bd="http://www.volantis.com/xmlns/2011/07/binding"
  xmlns:rng="http://relaxng.org/ns/structure/1.0"
  xmlns:cool="http://www.cool-components.com/xmlns/library"
  xmlns="http://www.w3.org/2002/06/xhtml2"
  xmlns:cx="http://www.volantis.com/xmlns/2010/07/cf2/cx"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.volantis.com/xmlns/2011/07/binding
    http://www.volantis.com/schema/2011/07/binding/binding.xsd
    http://relaxng.org/ns/structure/1.0
    http://www.volantis.com/schema/2011/07/binding/relax-ng.xsd">
  <bd:common>

    <!--
     ! Define the component class for folding item.
     !-->
    <cx:component-class name="cool:FoldingItem"
      defines="reference-types">
      <cx:client-class name="V$L['http://www.cool-components.com/xmlns/library'].FoldingItem"/>
      <cx:reference name="group" component-types="cool:FoldingItemGroup"
                    structural-relationship="parent" min-occurs="0">
        <cx:client-reference setter="setGroup"/>
      </cx:reference>
      <cx:reference name="details">
        <cx:client-reference setter="setDetails"/>
      </cx:reference>
    </cx:component-class>

    <style type="text/css">
      @namespace cool "http://www.cool-components.com/xmlns/library";
      @ui|dynamic-property {
        name: cx-open;
        type: boolean;
        cool|closed: false;
        cool|open: true;
      }
      cool|folding-item {
        display: block;
      }
      cool|summary {
        display: block;
      }
      cool|details {
        display: block;
        border: 1px solid red;
      }
    </style>
  </bd:common>

  <!--
   ! Define the grammar for folding item.
   !-->
  <rng:grammar>
    <rng:start>
      
      <!--
       ! The root bound element.
       !-->
      <rng:element name="cool:folding-item" bd:component="true"/>

    </rng:start>
    <rng:define name="elements">

      <!--
       ! All the other (non-root) bound elements.
       !-->
      <rng:element name="cool:summary"/>
      <rng:element name="cool:details"/>

    </rng:define>
  </rng:grammar>

</bd:definition>

The folding-item-implementation.xml file should contain the following code:

<bd:implementation xmlns:bd="http://www.volantis.com/xmlns/2011/07/binding"
  xmlns:cool="http://www.cool-components.com/xmlns/library"
  xmlns:cf2="http://www.volantis.com/xmlns/2009/07/cf2"
  xmlns:cx="http://www.volantis.com/xmlns/2010/07/cf2/cx"
  xmlns="http://www.w3.org/2002/06/xhtml2"
  xmlns:mcs="http://www.volantis.com/xmlns/2006/01/xdime/mcs"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:ui="http://www.volantis.com/xmlns/2009/07/cf2/ui"
  xsi:schemaLocation="http://www.volantis.com/xmlns/2011/07/binding
    http://www.volantis.com/schema/2011/07/binding/binding.xsd">
  <bd:common>

    <!--
     ! The script defining and using the FoldingItem component, It should really be in an mscr
     ! file to allow it to be easily reused across pages and also to allow it to import
     ! required features but it is here to make it easier to read.
     !-->
    <mcs:script type="text/javascript">
    
    // ====================================================================================
    // Folding Item
    // ====================================================================================
    
    // Create an anonymous function to encapsulate all the variables used during
    // initialization of the library to prevent them polluting the global namespace.
    // Call it immediately to initialize the library, passing in the methods which are
    // created outside the function
    (function(methods) {
    
    // Create an object to contain the library, store it under the library namespace
    // so that it is accessible to code that uses it.
    var lib = V$L['http://www.cool-components.com/xmlns/library'];
    if (lib === undefined) {
      lib = {};
      V$L['http://www.cool-components.com/xmlns/library'] = lib;
    }
    
    lib.FoldingItem = V$.Class.create();
    lib.FoldingItem.methods(methods);
    })(
    // The methods for the folding item, created outside  so that the anonymous
    // function is not part of their closure which would prevent the anonymous
    // function from being garbage collected.
    {
    initialize: function() {
      // Define the properties.
    
      // References the optional containing group.
      this.group = null;
    
      // This references the displayed property of the box that contains the
      // details and which is only displayed when the folding item is open.
      this.detailsDisplayed = null;
    },
    
    // Used to set the reference to folding item group component (if available)
    setGroup: function(c) {
      this.group = c;
    },
    
    // Used to set the reference to details component
    setDetails: function(c) {
      this.detailsDisplayed = c.opGet("displayed");
    },
    
    toggle: function() {
      // Use the displayed state of the details to determine whether the folding item
      // is open or not. That ensures that the folding item behaves even when the
      // details are initially displayed.
      var currentlyOpen = this.detailsDisplayed.get();
    
      if (this.group) {
        if (currentlyOpen) {
          this.group.closing(this);
        } else {
          this.group.opening(this);
        }
      }
    
      // If it is currently open then it needs to be closed if it is currently
      // closed then it needs to be opened. Also, need to set the property that
      // controls the dynamic property state of the buttons.
      this.detailsDisplayed.set(!currentlyOpen);
      this.opGet("cx-open").set(!currentlyOpen);
    }
    });
  </mcs:script>
  </bd:common>
  <xsl:transform version="2.0">

    <!--
     ! Construct a representation of a pseudo element for use when copying styles.
     !-->
    <xsl:variable name="pseudo-element-button" select="bd:pseudo-element(xs:QName('cool:button'))"/>

    <!--
     ! Check to see whether necessary features are supported.
     !-->
    <xsl:variable name="supported" select="mcs:feature('cf2:ui.DynamicProperties#Displayed',
      'cf2:ui.Box')"/>

    <xsl:template match="cool:folding-item">

      <!--
       ! The component id of the folding item.
       !-->
      <xsl:variable name="fi-cid" select="bd:get-component-id(.)"/>

      <!--
       ! Generate a unique id for the component that will represent the details. It does
       ! not get a component id from its bound element as that does not represent a
       ! separate component as far as the author is concerned and so cannot have a
       ! component-id attribute; it does not set bd:component attribute to true in the
       ! binding definition.
       !-->
      <xsl:variable name="details-cid" select="bd:get-component-id()"/>

      <!--
       ! Get the initial open state of the folding item, if not specified then defaults to
       ! false.
       !-->
      <xsl:variable name="initial-open-state">
        <xsl:choose>
          <xsl:when test="@open = 'true'">
            <xsl:value-of select="true()"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:if test="@open != 'false'">
              <xsl:message>
                <xsl:text>ERROR: The value of the open attribute must be either 'true', or 'false', not '</xsl:text>
                <xsl:value-of select="@open"/>
                <xsl:text>', ignoring and defaulting to 'false'.</xsl:text>
              </xsl:message>
            </xsl:if>
            <xsl:value-of select="false()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>

      <!--
       ! Folding Item - Component
       !-->
      <xsl:if test="$supported">

        <!--
         ! Generate a unique id to use to target the validation mode at the custom
         ! component.
         !-->
        <xsl:variable name="meta-id" select="bd:get-id()"/>

        <!--
         ! The custom component that underpins the folding item.
         !-->
        <cx:component type="cool:FoldingItem">

          <!--
           ! Copy component out-of-band data from the cool:folding-item bound element
           ! onto this one.
           !-->
          <xsl:call-template name="bd:component">
            <xsl:with-param name="component-id" select="$fi-cid"/>

            <!--
             ! Give it a unique id in order to allow the validation mode meta property
             ! to be targeted at this.
             !-->
            <xsl:with-param name="id" select="$meta-id"/>
          </xsl:call-template>

          <!--
           ! Set the reference to point to the details component.
           !-->
          <cx:ref name="details" component="{$details-cid}"/>

          <!--
           ! Add a property to this called cx-open.
           !-->
          <cf2:property name="cx-open" owner="{$fi-cid}" boolean="{@open}"/>
        </cx:component>
      </xsl:if>

      <!--
       ! Folding Item - Presentation
       !-->
      <div class="folding-item">

        <!--
         ! Copy presentational out-of-band data from the cool:folding-item bound element
         ! onto this one.
         !-->
        <xsl:call-template name="bd:presentational"/>
        <xsl:apply-templates select="cool:summary[1]">
          <xsl:with-param name="fi-cid" select="$fi-cid"/>
          <xsl:with-param name="open" select="@open"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="cool:details[1]">
          <xsl:with-param name="fi-cid" select="$fi-cid"/>
          <xsl:with-param name="details-cid" select="$details-cid"/>
          <xsl:with-param name="open" select="@open"/>
        </xsl:apply-templates>
      </div>
    </xsl:template>

    <xsl:template match="cool:summary">
      <xsl:param name="fi-cid"/>
      <xsl:param name="open"/>

      <!--
       ! The summary line of the folding item.
       !-->
      <div class="summary">

        <!--
         ! Copy default out-of-band data from the cool:summary bound element onto this one.
         !-->
        <xsl:call-template name="bd:bound"/>
        <xsl:choose>
          <xsl:when test="$supported">

            <!--
             ! The button does not have a corresponding bound element, it is an
             ! internal structural part of the folding item. Page authors can style
             ! this part of the folding item by associating styles with the
             ! ::cool|button pseudo element child of the cool|folding-item element.
             ! Note, that although as far as this implementation goes the button is
             ! treated as part of the summary externally it is part of the folding
             ! item itself.
             !-->
            <ui:button initial-state="{if ($open) then 'cool:open' else 'cool:closed'}">

              <!--
               ! Copy the styles associated with the cool|button pseudo element on
               ! the cool|folding-item element.
               !-->
              <xsl:call-template name="bd:presentational">

                <!--
                 ! The XPath to the cool:folding-item element, which is the parent
                 ! of the element to which the current template is being applied.
                 !-->
                <xsl:with-param name="element" select=".."/>

                <!--
                 ! Use the styles associated with the ::cool|button pseudo element
                 ! child of the element, i.e. cool:folding-item.
                 !-->
                <xsl:with-param name="pseudo" select="$pseudo-element-button"/>
              </xsl:call-template>

              <!--
               ! When the button is activated toggle the state of the folding item
               ! component.
               !-->
              <cf2:on event="cf2:activate" do="{$fi-cid}#toggle"/>

              <!--
               ! Add a property to this that is an alias for the open state on the
               ! corresponding folding item, this will be used as the observable
               ! property underlying the custom dynamic property defined in the
               ! binding definition.
               !-->
              <span> +<cf2:property name="cx-open" alias="{$fi-cid}#cx-open"/>
              </span>
            </ui:button>

            <!--
             ! Textual summary outside the button so not active.
             !-->
            <span class="summary-text">

              <!--
               ! Make the folding item the parent component of any components in the
               ! summary section.
               !-->
              <cx:component widen="{$fi-cid}">
                <xsl:apply-templates/>
              </cx:component>
            </span>
          </xsl:when>

          <!--
           ! Fallback if components not supported.
           ! -->
          <xsl:otherwise>

            <!--
             ! Display the summary without any button.
             !-->
            <span class="summary-text">
              <xsl:apply-templates/>
            </span>

          </xsl:otherwise>
        </xsl:choose>
      </div>
    </xsl:template>

    <xsl:template match="cool:details">
      <xsl:param name="fi-cid"/>
      <xsl:param name="details-cid"/>
      <xsl:param name="open"/>
      <xsl:choose>
        <xsl:when test="$supported">

          <!--
           ! The details of the folding item.
           ! This is a ui:box as it is manipulated on the client.
           !-->
          <ui:box class="details" initial-displayed-state="{$open}">

            <!--
             ! Copy the default out-of-band information from cool:details bound element
             ! to this one adding a component id.
             !-->
            <xsl:call-template name="bd:bound">

              <!--
               ! Although the cool:details bound element is not a component this is
               ! so specify its component id.
               !-->
              <xsl:with-param name="component-id" select="$details-cid"/>
            </xsl:call-template>

            <!--
             ! Make the folding item the parent component of any components in the
             ! details section.
             !-->
            <cx:component widen="{$fi-cid}">
              <xsl:apply-templates/>
            </cx:component>
          </ui:box>
        </xsl:when>
        <xsl:otherwise>
          <div class="details">

            <!--
             ! Copy the default out-of-band information from cool:details bound element
             ! to this one.
             !-->
            <xsl:call-template name="bd:bound"/>
            <xsl:apply-templates/>
          </div>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
  </xsl:transform>
</bd:implementation>

Related topics