Crafter Studio - Advanced Web Forms Development

Revision History

Author Date Change
Russ Danner (Rivet Logic) 03/2011 First revision

Contents

1. What is a content type?

A content type in Crafter Studio consists of the following:

  • Content type configuration
  • Model prototype
  • Form
  • Alfresco Content Model
  • Metadata Extraction

1.1. Content type configuration

Content type configuration provides the content controller with the basic information about a content type:

  • what is the name/location of the form
  • what is the name/location of the model prototype
  • what deletes on references can be cascaded during a delete
  • what reverts on references can be cascaded during a reversion
  • what thumbnail image can be used as a example of the content

In the future you may find the following configurations

  • rendition configuration
  • pub/sub configuration
  • security configuration

1.2. The Model protoype

The model Prototype is essentially a prototype of the content you are creating. It's the stating point of any new content item – the default values. Each content type has a model prototype which is essentially copied to create a new item. Additionally the model prototype is examined on edit to bring over new model changes to an existing content item

1.3. Advanced Web Form Definition

The form definition is an advanced webform that is used to capture content and persist it in the repository. Persisted content is sometimes called "the model." A form defition is composed of a controller and a XForm Definition. The controller is a simple server side javascript file which optionally contains methods for basic lifecycle events for the form (onLoad, onBeforeSave, onSave, onValidate)

1.4. Alfresco Content Model

The Alfresco content model is a representation of the content stored in the Alfresco repository as properties. These proprties are used to support the application or in order to make the content searchable by a specific field.

1.5. Metadata Extraction

Metadata Extraction is the process of pulling values out of the XML Model instance and inserting it in the Alfresco Content Model in order to make the values searchable. Extraction is performed by a framework. Each content type has a simple server side javascript file called an extract.js which lives with the content type configuration which encapsulates the extraction code

2. What are Crafter Studio Advanced Forms?

  • Support multiple data inputs (a form may require 1 or more data sets to run)
    • Data streams may come from different sources on a single form
      • main model (alfresco)
        • Support read/write to both AVM and DM Alfresco stores
          • May read from both and write to one or the other on the same form
      • taxonomy (alfresco)
      • roles (LDAP)
      • images (DAM system)
    • Form visibility, constraints etc may require datasets to relate to one another
      • constrain if value in set A is x and value in set B is q
  • Support form construction based on widgets
    • widget code must be encapsulated / plugin
  • Fields must be able to be related to one another
    • constraints
    • relevant
  • Support complex structures
    • nesting
    • Repeats
  • Form System must allow more than one form open at a time
    • User can open child forms from parent, go back to parent on close of child form

3. Design

3.1. Data / configuration flow


3.2. Active Ingredients


4. Building a simple form

4.1. Defining a model prototype

A model prototype is default configuration of a type of content object in the system. In the WCM system our models are captured and stored as XML. For WCM our model prototypes are simply a default copy of the XML for the given type.

Model protoypes are store in the repository at the following location:

/company home/ecr/model-prototypes/wcm

Because WCM model prototypes are XML documents they can be stored in any hierarchy inside the WCM folder that best enables you to manage them for your use-case. For our example we'll create a prototype for a page and store it in the following location:

...model-prototypes/wcm
   |
   +--page
      |
      +---generic-page.xml

In this XML we want to put any initial structure and default values we expect to see when creating a new item. Let's consider the following basic page model:

page
  headline
  body

This is a pretty basic model, and our XML for this model would look something like the following:

<page>
  <headline></headline>
  <body></body>
</page>

What this means is that when an author creates a new page, they will see a headline and a body field, however neither would have a default value.
Now suppose wanted to also include a byline for the page. For our purposes let's assume that the byline is the same value 9 times out of 10 and so for this reason we want to provide a default value for the author so they don't have to key in this information over and over. Let's add a new byline field to our model with a default value:

<page>
  <headline></headline>
  <body></body>
  <byline>My-News corp.</byline>
</page>

In addition to the fields which are directly related to content we want to capture for our model, we also need to include metadata about the page to support the delivery tier in processing the XML and rendering the page.

4.1.1. Crafter Controller metadata

The default controller for Crafter requires two pieces of metadata in order to render a page.

field description
descriptor-mapper Crafter uses the descriptor mapper field to understand how to merge XML documents.
page-template Crafter uses the page-template field to understand what template is associated with the page.

In most cases the authors will not need control of these values so our model prototype will simply default them to the proper value.

<page>
  <headline></headline>
  <body></body>
  <byline>My-News corp.</byline>

  <page-template>/templates/web/news/newsItem.xhtml</page-template>
  <descriptor-mapper>hierarchical-mapper</descriptor-mapper>
</page>

Note: for this example we are assuming a JBOSS Seam presentation layer on top of Crafter. Crafter supports may presentation frameworks. If you were using the JRuby layer for example the value for page-template might look something like

  <page-template>/templates/web/news/newsItem.erb</page-template>

4.1.2. Crafter Studio support fields

Crafter Studio also requires a number of fields to be associated with each model. For simpliciy sake we'll cover the madatory fields here and provide a more complete list later in the documentation.

content-type content type maps to the form path for the content. This enables crafter studio to automatically associate a form with the content immediately after a content import.
file-name file-name store the file name of the xml.

4.1.3. Our complete example page model prototype

<page>
  <headline></headline>
  <body></body>
  <byline>My-News corp.</byline>

  <page-template>/templates/web/news/newsItem.xhtml</page-template>
  <descriptor-mapper>hierarchical-mapper</descriptor-mapper>

  <content-type>/my-site/page/news/my-page</content-type>
  <file-name>index.xml</file-name>
</page>

4.2. Setting up the form configuration

Forms are stored in the Alfresco repository at the following location

/company home/ecr/config/forms

Once inside the forms folder the structure of the folders can be complete adapted to best organize the forms for your implementation. A typical structure might look something like the following:

.../forms
    |
    +--mysite-dot-com
    |  |
    |  +--page
    |  |  |
    |  |  +---entry
    |  |  |
    |  |  +---news
    |  |      |
    |  |      +---section
    |  |      |
    |  |      +---press-release
    |  |
    |  +---component
    |      |
    |      +---section-defaults
    |      |
    |      +---sidebar
    |      |
    |      +---splash-box
    |
    +---acme-dot-com
        |
        +---page
        |
        +---component

leaf-folders, those folders which have no children contain the actual form components (controller, definition etc.) Forms are identified by their path inside the forms directory. For example, to reference the press release form in the news section of the mysite web property we would use the following path:

/mysite-dot-com/page/news/press-release

4.3. Defining a controller

Recall from our design discussion that a controllers job is to respond to events in the Form lifecycle and manage the data for that point in the lifecycle. During LOAD, a controller can acquire data for the form, During SAVE the controller can mutate the values coming back from the form.

Controllers are written in server-side javascript. All the basic standard java APIs you are accustomed to on the browser are available to you in your controllers. The key difference is that you are not working with a page or in the context of a web browser so access to browser and DOM objects is not available. In addition to standard javascript APIs we inject a number of other Crafter Studio specific APIs to support form development.

By convention we always put the controller code in a file called controller.js. If we're building the press release form our path to the controller inside the forms folder would be:

/mysite-dot-com/page/news/press-release/controller.js

Because our form is very simple, our controller will be very simple as well. Let's look at this simple controller:

var config = {	contentType: "/mysite-dot-com/page/news/press-release" };

controller = CStudioFormsEngine.ControllerFactory.newWcmController(config);

You can see in the example above we simple declare a configuration and instanciate our controller. The configuration points to our content type.

4.4. Defining the Form Definition:

4.4.1. XForms Primer (Skip familiar with XForms)

From our design discussion we know that form definitions are coded in XFORMS, a W3C standard for declaratively defining a form. We won't go in to depth here with XFORMS but let's do a quick primer to establish some terminology and basic concepts.

  • XForms Is The Successors Of HTML Forms
  • XForms Separate Data From Presentation
  • XForms Uses XML To Define Form Data
  • XForms Uses XML To Store And Transport Data
  • XForms Is Device Independent
  • XForms Is A W3C Recommendation

Let's consider the following XForm definition, this is completely self contained and should run in any XForm container.

<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms"
            xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xforms:model>
      <xforms:instance id="instance">
        <person>
          <fname/>
          <lname/>
        </person>
      </xforms:instance>
      <xforms:submission id='sample-submission'    
                   ref='instance("instance")'  
                   validate='true'
                   action='/submit.jsp'
                   method='post'
                   replace='replace'>
      </xforms:submission>
    </xforms:model>
  </xhtml:head>

  <xhtml:body>
    <xforms:input ref="fname"><xforms:label>First Name</xforms:label></xforms:input><xhtml:br />
    <xforms:input ref="lname"><xforms:label>Last Name</xforms:label></xforms:input><xhtml:br />
    <xforms:submit submission="sample-submission"><xforms:label>Submit</xforms:label></xforms:submit>
  </xhtml:body>
</xhtml:html>

4.4.1.1. The Container

You will note that the outer-most element is a xhtml tag. This tag is called the container and basically informs the engine that we will be producing and XHTML compliant rendering of the form definition.

  • note in this form the namespace is xforms, xhtml
<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms"
            xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
  ....
  </xhtml:head>

  <xhtml:body>
    ...
  </xhtml:body>
</xhtml:html>

4.4.1.2. The Model

The model consists of

  • One or more named instances.
  • One or more submission handlers.
    <xforms:model>
      <xforms:instance id="instance">
        <person>
          <fname/>
          <lname/>
        </person>
      </xforms:instance>
      <xforms:submission id='sample-submission'    
                   ref='instance("instance")'  
                   validate='true'
                   action='/submit.jsp'
                   method='post'
                   replace='replace'>
      </xforms:submission>
    </xforms:model>
4.4.1.2.1. Instances

An instance is an XML DOM that represents the whole, or a portion of the data model behind the form.  Simple forms may require only a single instance.  Most forms require multiple instances. In general there are three types of instances (in concept, structurally all instances are the same.)

  • A Submittable Instance.  Generally there will be one instance you will modify for submission while other instances will be used in support of the editing process.
  • A Data instance. This is an instance that provides related data to the editing process e.g. roles from the security system, taxonomy items, etc.
  • A Prototype Instance.  Based on the way XForms works, when we want to insert a structure in to one of the instances we must reference a example or prototype structure for the engine to copy.  These prototypes are generally kept as their own instance.
4.4.1.2.2. Submission Handlers

A submission handler is the machinery in an XForm that determines what to do with a model instance.  XForms will communicate with any back end and can handle the response in a variety of ways including replacing the existing model with the response.  In the example above the submission handler we're submitting the DOM for instance named "instance" via post to submit.jsp  and we'll replace the current instance with whatever is returned by the server.

Additionally the submission handler may contain actions which will fire on certain events like validation failure, successful submission, failed submission.

4.4.1.3. Controls/Form Widgets

In this example the controls are all stock XForms controls.  As you will see later, Crafter Studio uses Orbeon's component model to extend the capabilities of the XForm engine to provide new control types.
Note that the form widgets are placed inside of the body element of the XHTML container

    <xforms:input ref="fname"><xforms:label>First Name</xforms:label></xforms:input><xhtml:br />
    <xforms:input ref="lname"><xforms:label>Last Name</xforms:label></xforms:input><xhtml:br />
    <xforms:submit submission="sample-submission"><xforms:label>Submit</xforms:label></xforms:submit>

Note that the input controls bind to specific XPath locations inside the instance.  This could also be written as instance('instance')/fname

Note the submit control will fire our submission handler

4.4.1.4. How do I add rules to my form?

There are two mechanisms in XForms for enforcing rules

  • pre submission
  • post submission

Pre submission rules are handled by the XForms Engine based on a model element called a binding

Post submission rules are handled by the server when the submission handler has validate='true' set.

Pre-submission validation is the primary means of defining rules for the form. Remember that these are a declarative cross platform rules that live with the form whereas post submission validation lives outside the form.

<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms"
            xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <xhtml:head>
    <xforms:model>
      <xforms:instance id="instance">
        <person>
          <fname/>
          <lname/>
        </person>


       <xforms:bind nodeset='instance("instance")'>
        <xforms:bind nodeset='fname' required="true()"/>
      </xforms:bind>

      </xforms:instance>
      <xforms:submission id='sample-submission'
                   ref='instance("instance")'
                   validate='true'
                   action='/submit.jsp'
                   method='post'
                   replace='replace'>
      </xforms:submission>
    </xforms:model>
  </xhtml:head>

  <xhtml:body>
    <xforms:input ref="fname"><xforms:label>First Name</xforms:label></xforms:input><xhtml:br />
    <xforms:input ref="lname"><xforms:label>Last Name</xforms:label></xforms:input><xhtml:br />
    <xforms:submit submission="sample-submission"><xforms:label>Submit</xforms:label></xforms:submit>
  </xhtml:body>
</xhtml:html>

Our binding example is very simple: it simply marks a specific XPath as required in order for a submission to succeed. However bindings are very powerful:

  • Enforce constraints using any xpath functions including regex and if/then logic.
  • Reference more than one model instance or more than one xpath to bind models and elements in the same model together
  • Automatically calculate values

4.4.2. A Simple Crafter Web Form

<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms"
            xmlns:f="http://orbeon.org/oxf/xml/formatting"        xmlns:xhtml="http://www.w3.org/1999/xhtml"
            xmlns:xxforms="http://orbeon.org/oxf/xml/xforms"      xmlns:xi="http://www.w3.org/2001/XInclude"
            xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"        xmlns:xs="http://www.w3.org/2001/XMLSchema"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:widget="http://orbeon.org/oxf/xml/widget"
            xmlns:ev="http://www.w3.org/2001/xml-events"          xmlns:xdt="http://www.w3.org/2005/xpath-datatypes"
            xmlns:xbl="http://www.w3.org/ns/xbl"                  xmlns:fr="http://orbeon.org/oxf/xml/form-runner">

    <xhtml:head>
      <xforms:model id="main"
	            xxforms:session-heartbeat="true"
	            xxforms:show-error-dialog="false"
	            xxforms:external-events="submit-save submit-cancel submit-preview">

         <xforms:instance id="instance">
           <dynamic></dynamic>
         </xforms:instance>

         {standardRepoSubmissionHanders}

      </xforms:model>
    </xhtml:head>

    <xhtml:body class="body">
      <fr:entity-avm id="entityId" ref="instance('instance')/file-name" />

      <fr:accordionEx id="accordion">
        <xforms:label>My Simple Web Form</xforms:label>

        <fr:accordionEx-section id="demoSection">
	  <xforms:label>Demo Section</xforms:label>

          <div class="cstudio-xforms-widget-wrapper">
	    <fr:input-counted id="fname" ref="instance('instance')/fname" max="80">
	      <xforms:label>First Name</xforms:label>
	      <xforms:hint>This is a field note.</xforms:hint>
	      <xforms:help>info</xforms:help>
	      <xforms:alert></xforms:alert>
	    </fr:input-counted>
          </div>

        </fr:accordionEx-section>
      </fr:accordionEx>

     {xml-inspector-widget}

     <fr:formctrl-wcm enablePreview="false"/>
  </xhtml:body>
</xhtml:html>

Let's break this form down so we can see what is going on here...

4.4.2.1. Importing Namespaces

<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms"
            xmlns:f="http://orbeon.org/oxf/xml/formatting"        xmlns:xhtml="http://www.w3.org/1999/xhtml"
            xmlns:xxforms="http://orbeon.org/oxf/xml/xforms"      xmlns:xi="http://www.w3.org/2001/XInclude"
            xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"        xmlns:xs="http://www.w3.org/2001/XMLSchema"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:widget="http://orbeon.org/oxf/xml/widget"
            xmlns:ev="http://www.w3.org/2001/xml-events"          xmlns:xdt="http://www.w3.org/2005/xpath-datatypes"
            xmlns:xbl="http://www.w3.org/ns/xbl"                  xmlns:fr="http://orbeon.org/oxf/xml/form-runner">

You will note we import quite a number of namespaces. These give us access to

  • orbeon extensions
  • xml events
  • xslt functions
  • crafter studio extensions

4.4.2.2. Model Declaration

      <xforms:model id="main"
	            xxforms:session-heartbeat="true"
	            xxforms:show-error-dialog="false"
	            xxforms:external-events="submit-save submit-cancel submit-preview">

The model declaration includes several Orbeon extensions

  • heart beat with the server to keep the form alive
  • don't show the error dialog
  • declare a number of events the form can respond to. We would like to encapsulate these in to the controls or submission handlers at some point but this is not currently possible with the platform.

4.4.2.3. Instance data

     <xforms:instance id="instance">
       <dynamic></dynamic>
     </xforms:instance>

Our form controller (the JS file) populates the model. XForms requires that instances atleast have a root element. The value you put in an instance doesn't really matter however we have put the "dynamic" tag to document the form definition. As you will see with complex forms you will also have prototype models with actual DOM structures so this approach makes it a little easier to tell what's what.

4.4.2.4. Submission handlers

   {standardRepoSubmissionHanders}

You may write your own submission handlers but for many if not all forms in Crafter Studio these are boiler plate. Since it's not possible to create an XML element to encapsulate this boiler-plate code, we've created a simple macro instead.

4.4.2.5. Entity

<fr:entity-avm id="entityId" ref="instance('instance')/file-name" />

Entity is a component makes it possible for the form to be loaded by other forms as a child form. Entity ID manages a proper path/id to the object so that parent forms can obtain it and store it as a reference.

4.4.2.6. Accordion and Sections

      <fr:accordionEx id="accordion">
        <xforms:label>My Simple Web Form</xforms:label>

        <fr:accordionEx-section id="demoSection">
	  <xforms:label>Demo Section</xforms:label>
          ....
       </fr:accordionEx-section>

        <fr:accordionEx-section id="demoSection2">
	  <xforms:label>Demo Section 2</xforms:label>
          ....
       </fr:accordionEx-section>
    </accordionEx>

The accordion is not actually required but we put this all forms to keep look and feel the same. You can see we have an outer accordion element and then you can create as many sections as you need.

4.4.2.7. Example Custom Control

          <div class="cstudio-xforms-widget-wrapper">
	    <fr:input-counted id="fname" ref="instance('instance')/fname" max="80">
	      <xforms:label>First Name</xforms:label>
	      <xforms:hint>This is a field note.</xforms:hint>
	      <xforms:help>info</xforms:help>
	      <xforms:alert></xforms:alert>
	    </fr:input-counted>
          </div>
4.4.2.7.1. How do I know what controls are available?
 ./WEB-INF/resources/xbl/orbeon
4.4.2.7.1.1. How do I "read" an XBL?

Let's look at example control

<xbl:xbl xmlns:xhtml="http://www.w3.org/1999/xhtml"
         xmlns:xforms="http://www.w3.org/2002/xforms"
         xmlns:xs="http://www.w3.org/2001/XMLSchema"
         xmlns:ev="http://www.w3.org/2001/xml-events"
         xmlns:xi="http://www.w3.org/2001/XInclude"
         xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
         xmlns:xxforms="http://orbeon.org/oxf/xml/xforms"
         xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
         xmlns:saxon="http://saxon.sf.net/"
         xmlns:oxf="http://www.orbeon.com/oxf/processors"
         xmlns:xbl="http://www.w3.org/ns/xbl"
         xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xbl:script src="/components/cstudio-form/image-manager.js"/>

  <xbl:binding id="fr-image-manager" element="fr|image-manager">

    <xbl:template xxbl:transform="oxf:unsafe-xslt">
      <xsl:transform version="2.0">
        <xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl"/>

        <xsl:template match="/*">
          <xforms:group xbl:attr="model context ref bind" xxbl:scope="outer">

           <xsl:copy-of select="xxbl:parameter(., 'name')"/>
            <xsl:copy-of select="xxbl:parameter(., 'getImagesUri')"/>
            <xsl:copy-of select="xxbl:parameter(., 'uploadImageUri')"/>

            <xxforms:script ev:target="#observer" ev:event="xforms-enabled">
              YAHOO.xbl.fr.ImageManager.instance(this).init();
            </xxforms:script>
          </xforms:group>

        </xsl:template>
      </xsl:transform>
    </xbl:template>
  </xbl:binding>
</xbl:xbl>

The binding tells you that the component tag in the XForm will be

  <xbl:binding id="fr-image-manager" element="fr|image-manager">

will look like the following in the XForm:

  <fr:image-manager>
  </fr:image-manager>

The following code in the XBL indicates that there is a parameter on the control with the called "name"

  <xsl:copy-of select="xxbl:parameter(., 'name')"/>

looks like this in the XForm:

  <fr:image-manager name="myImageManager">
  </fr:image-manager>

4.4.2.8. XML Inspector Macro

{xml-inspector-widget}

We also added a macro that we recommend you put in the bottom of your form. This macro does nothing by default. However, if you add &debugXml=true to the form URL this macro will render an additional control that will allow you to inspect all of the model documents behind the forms.

4.4.2.9. Submission Controls

<fr:formctrl-wcm enablePreview="false"/>

This control is essentially an XForms Trigger for Submit, Cancel or Preview. If enablePreview option is false, no preview button is rendered. Only pages support Preview from the form.

4.5. Wiring up a content type

4.5.1. Content Descriptor

Content Descriptors are defined at the site level in the following location

/company home/ecr/config/sites/SITE-NAME/content-types/SAME-FOLDERS-AS-FORM-PATH/config.xml

Example:

/company home/ecr/config/sites/SITE-NAME/content-types/acme-com/page/news/config.xml

The contents of this descriptor are as follows

<content-type name="/acme-com/page/news" is-wcm-type="true">
  <label>News Content</label>
  <form>/acme-com/page/news</form>
  <form-path>/page/site/cstudio/cstudio-webform</form-path> <!-- this will go away at some point -->
  <model-instance-path>
     /app:company_home/cm:ecr/cm:model-prototypes/cm:wcm/cm:acme-com/cm:page/cm:news/news-item.xml
  </model-instance-path>
  <file-extension>xml</file-extension>
  <content-as-folder>true</content-as-folder>
  <previewable>true</previewable>
</content-type>

4.5.2. Metadata Extractor

Each content type has a metadata extractor which pulls content out of the XML and places it in the Alfresco Model where it can be indexed and searched.  A extractor is coded in Server Side Javascript within a file located at the following path:

 /company home/ecr/config/sites/SITE-NAME/content-types/SAME-FOLDERS-AS-FORM-PATH/extract.js

Example:

/company home/ecr/config/sites/SITE-NAME/content-types/acme-com/page/news/extract.js


An example of the contents of extract.js can be found in the example below:

<import resource="classpath:alfresco/templates/webscripts/com/cstudio/common/lib/common-extraction-api.js">

// extract metadata by xpath query
var root = contentXml.getRootElement();

extractCommonProperties(contentNode, root);

contentNode.properties["ctx-core:title"] = root.valueOf("title");

contentNode.save();

Common properties such as internal name are extracted for you via the call  extractCommonProperties(...) which is supplied by the import of common-extraction-api.js

4.5.3. Choose Template Dialog Configuration

4.5.3.1. How do I add my content type to the choose template menu?

to configure the choose template menu to must add configuration to the following files:

/company home/ecr/config/sites/SITENAME/content-type-path-config.xml

add a new path or a form to an existing path:

<content-type-path-mapping>
  <path>
    <!--  no path-include means match everything -->
    <path-includes/>

    <path-excludes>
      <path-exclude>/site/website/downloads/.*</path-exclude>
      <path-exclude>/site/website/news/.*</path-exclude>
    </path-excludes>

    <content-types>
      <content-type name="/acme-com/page/generic" is-wcm-type="true"/>
    </content-types>
  </path>
  ..
  ..
  ..
</content-type-path-mapping>

4.5.3.2. How do I set up a template thumbnail on the Choose template dialog?

Add the following tag to your content descriptor

 <image-thumbnail>page-template-thumbs/news-template.jpg</image-thumbnail>

The put your image inside the war file at the following path:

/cstudioTheme/images/page-template-thumbs/news-template.jpg

5. Developing a Form Component

Orbeon supports a open standards approach to developing additional widgets for use in it's XForms engine. The following technologies are relevant:

   
XML extensible markup language
XSL xml stylesheet
XPath xml path
XBL xml binding language
Javascript client side / browser javascript, specifically YUI library
CSS browser cascading stylesheet

5.1. Architecture

5.2. Example Custom Component Tag in XForm

<fr:input-counted id="internalName" ref="instance('instance')/internal-name" max="80">
  <xforms:label>Internal Name</xforms:label>
  <xforms:hint>This is a field note.</xforms:hint>
  <xforms:help>info</xforms:help>
  <xforms:alert></xforms:alert>
</fr:input-counted>

5.3. XBL

The XBL (XML Binding Language) file is the base of any widget.  You may not need any client side code or even xsl on the server but in order to create a widget you must have an XBL.  The XBL files must go in a specific directory structure within the war.

Let's take a look at one of our basic controls, the counted input control. First the location of the XBL File:

/WEB-INF/resources/xbl/orbeon/COMPONENT-TAG_NAME/COMPONENT-TAG_NAME.xbl

example:
/WEB-INF/resources/xbl/orbeon/input-counted/input-counted.xbl

As you can see there is a convention of sorts that requires us to make the name of the tag we want to create the name of the folder and file that house the component.

Now lets look at the content of the XBL file:

<xbl:xbl xmlns:xhtml="http://www.w3.org/1999/xhtml"
		 xmlns:xforms="http://www.w3.org/2002/xforms"
		 xmlns:xs="http://www.w3.org/2001/XMLSchema"
		 xmlns:ev="http://www.w3.org/2001/xml-events"
		 xmlns:xi="http://www.w3.org/2001/XInclude"
		 xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
		 xmlns:xxforms="http://orbeon.org/oxf/xml/xforms"
		 xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
		 xmlns:saxon="http://saxon.sf.net/"
		 xmlns:oxf="http://www.orbeon.com/oxf/processors"
		 xmlns:xbl="http://www.w3.org/ns/xbl"
		 xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
		 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xbl:script src="/components/cstudio-form/input-counted.js" />

  <xbl:binding id="fr-input-counted" element="fr|input-counted">
    <xbl:template xxbl:transform="oxf:unsafe-xslt">
      <xsl:transform version="2.0">
        <xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl" />

        <xsl:template match="/*">
          <xforms:group xbl:attr="model context ref bind" xxbl:scope="outer">
            <xsl:variable name="label">
              <xsl:choose>
                <xsl:when test="./xforms:label">
                  <xsl:value-of select="./xforms:label"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:choose>
                    <xsl:when test="./xforms:altTextLabel">
                      <xsl:value-of select="./xforms:altTextLabel"/>
                    </xsl:when>
                    <xsl:otherwise>Alt Text:</xsl:otherwise>
                  </xsl:choose>
                </xsl:otherwise> <!-- default value -->
              </xsl:choose>
	    </xsl:variable>

            <xsl:variable name="note">
              <xsl:choose>
                <xsl:when test="./xforms:hint">
                  <xsl:value-of select="./xforms:hint"/>
                </xsl:when>
                <xsl:otherwise></xsl:otherwise> <!-- default value -->
              </xsl:choose>
            </xsl:variable>

           <xsl:variable name="info">
             <xsl:choose>
               <xsl:when test="./xforms:help">
                 <xsl:value-of select="./xforms:help"/>
	       </xsl:when>
               <xsl:otherwise></xsl:otherwise> <!-- default value -->
             </xsl:choose>
           </xsl:variable>

           <xsl:variable name="alert">
             <xsl:choose>
               <xsl:when test="./xforms:alert">
                 <xsl:value-of select="./xforms:alert"/>
               </xsl:when>
               <xsl:otherwise></xsl:otherwise> <!-- default value -->
             </xsl:choose>
           </xsl:variable>

           <xsl:copy-of select="xxbl:parameter(., 'max')" />

           <xxforms:script ev:target="#observer" ev:event="xforms-enabled">
             YAHOO.xbl.fr.InputCounted.instance(this).init();
           </xxforms:script>

           <xhtml:label class="cstudio-xforms-label">
             <xsl:copy-of select="$label" />
           </xhtml:label>

           <xforms:group xxbl:scope="inner">
             <xxforms:variable name="binding" as="node()?">
               <xxforms:sequence select="." xxbl:scope="outer"/>
             </xxforms:variable>

             <xforms:input id="input-counted" ref="$binding" incremental="true" class="cstudio-xforms-input">
               <xforms:action ev:event="#all" ev:propagate="stop"/>
                 <xsl:if test="$alert != ''">
                 <xforms:alert>
                   <xsl:copy-of select="$alert" />
                 </xforms:alert>
               </xsl:if>
             </xforms:input>

           </xforms:group>

           <div class="cstudio-clear"></div>

           <xsl:if test="$note != ''">
             <div class="cstudio-xforms-fieldnote-control">
               <xhtml:label class="cstudio-xforms-note-label">
                 <xsl:copy-of select="$note" />
               </xhtml:label>

               <xsl:if test="$info != ''">
                 <xforms:trigger appearance="minimal">
                   <xforms:label></xforms:label>
                   <xforms:help class="cstudio-xforms-fieldnote-image">
                     <xsl:copy-of select="$info" />
                   </xforms:help>

                   <xforms:message level="modal" ev:event="DOMActivate"></xforms:message>
                 </xforms:trigger>
               </xsl:if>
             </div>
           </xsl:if>
         </xforms:group>
       </xsl:template>
     </xsl:transform>
   </xbl:template>
  </xbl:binding>
</xbl:xbl>

5.3.1. Declaring the Javascript your component needs

  <xbl:script src="/components/cstudio-form/input-counted.js" />

If you have client side code for your component (most components do) you will call your library in with the code above. This does not execute anything (unless the library executes something on load.), it simply pulls the script in to the page.

You may specify any resource you need here.  It must be relative to the root of the web app.

5.3.2. Declaring the component (Binding)

  <xbl:binding id="fr-input-counted" element="fr|input-counted">

5.3.3. Basic Machinery

<xbl:template xxbl:transform="oxf:unsafe-xslt">
      <xsl:transform version="2.0">
        <xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl" />

        <xsl:template match="/*">
          <xforms:group xbl:attr="model context ref bind" xxbl:scope="outer">
             ...
             ...
             meat goes here
             ...
             ...
          </xforms:group>
        <xsl:template>
      </xsl:transform>
</xbl:template>

5.3.3.1. Why the transform?

The XSL transform is a powerful capability that allows the widget developer to access the widget in the XForm Definition and it's data then transform those values in to markup.

5.3.3.2. How do scopes work?

There are two scopes inner and outerInner scope gives you access to the model inside the widget.  Outer scope gives you access to the model the widget is attached to.

Internal models are important for complex controls because it allows controls to manage their state.

5.3.4. Getting Values from the XFrom Definition and using them later

<xsl:variable name="label">
  <xsl:choose>
    <xsl:when test="./xforms:label">
      <xsl:value-of select="./xforms:label"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:choose>
        <xsl:when test="./xforms:altTextLabel">
          <xsl:value-of select="./xforms:altTextLabel"/>
        </xsl:when>
        <xsl:otherwise>Alt Text:</xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise> <!-- default value -->
  </xsl:choose>
</xsl:variable>

In the code above you can see XSL being used to acquire the value from the Xform and create a variable which we can use later. Notice how powerful the ability to use XSL is, in that you can do far more than simply pluck a specific value out of the form, you have logic and XPath and custom XSL functions like this one select="xxbl:parameter(., 'max')" which allows you to acquire an attribute.

Now let's use a variable. In the example below we'll take the value we got from the XForm and Render HTML with it.

<xhtml:label class="cstudio-xforms-label">
  <xsl:copy-of select="$label" />
</xhtml:label>

5.3.5. Initializing Client side code when component is rendered:

   <xxforms:script ev:target="#observer" ev:event="xforms-enabled">
     YAHOO.xbl.fr.InputCounted.instance(this).init();
   </xxforms:script>

The xxforms:script tag allows a developer to register for an even and then to execute Javascript when that event is fired.

5.4. Client Side Code, Javascript

Javascript is used to create a rich experience on the client side. You can think of the XBL as the artifact that creates the XHTML structures. defines rules for the internal and handlers events. The Javascript peer can be used to do much of the UI heavy lifting.

/cstudio-components/form/input-counted.js
YAHOO.namespace("xbl.fr");
YAHOO.xbl.fr.InputCounted = function() {};
ORBEON.xforms.XBL.declareClass(YAHOO.xbl.fr.InputCounted, "xbl-fr-input-counted");

YAHOO.xbl.fr.InputCounted.prototype = {

  _instances: {},

  _getInstance: function(target) {
    var container = YDOM.getElementsByClassName("xbl-fr-input-counted", null, this.container)[0];
    return this._instances[container.id];
  },

  /**
   * initialize a new image selector
   */
  init: function() {

    var container = this.container;
	var htmlElement =  container.getElementsByTagName('input')[0];

    if(htmlElement.controlInitialized !== true) {
      var instance = {
        id: "",
	currentCount: 0,
	maxCount: 0,
	disabled: false
      };

      var maxCountEl = YDOM.getElementsByClassName("xbl-fr-input-counted-max", null, this.container)[0];
      var maxCount = (maxCountEl && maxCountEl.innerHTML != "") ? maxCountEl.innerHTML : 100;

      // populate instance
      instance.container = container;
      instance.HTMLElement = htmlElement;
      instance.id = this.container.id;
      instance.minCount = 0;
      instance.maxCount = maxCount;

      this.initInstance(this.container, instance);

      htmlElement.controlInitialized = true;
      this._instances[this.container.id] = instance;
    }
  },

  /**
   * method takes the ordinary text input box pointed to by target and
   * adds controls via dom manipulation to create an image picker.
   */
  initInstance: function(target, instance) {
    if(instance.maxCount != -1) {
      // span tag actually gets the id given in the xform definition
      var field = new YAHOO.util.Element(instance.id);

      var counter = document.createElement("label");
      counter.id = instance.id + "-counter";
      counter.className="cstudio-xforms-counter";

      if(field.getElementsByTagName('input')[0] !== null) {
        field = field.getElementsByTagName('input')[0];
        counter.className="cstudio-xforms-counter";
      }

      Dom.insertAfter(counter, field);
      var currentCount = field.textLength;
      field.maxLength = instance.maxCount;
      field.ctr = counter;

      if (currentCount > 0) {
        field.ctr.innerHTML =  currentCount + ' / ' + instance.maxCount;
      }
      else {
        field.ctr.innerHTML =  instance.minCount + ' / ' + instance.maxCount;
      }

      Event.on(field, 'keyup', this.count, field);
      Event.on(field, 'keypress', this.count, field);
      Event.on(field, 'mouseup', this.count, field);
    }
  },

  /**
   * perform count calculation on keypress
   * @param evt event
   * @param el element
   */
  count : function(evt, el) {
    var text = el.value;
    var charCount = ((text.length) ? text.length : ((el.textLength) ? el.textLength : 0));

    if (charCount > el.maxLength) {
      if (charCount > el.maxLength) {
        el.value = text.substr (0,el.maxLength);
        charCount = el.maxLength;
      }

      if (evt.keyCode!=8 && evt.keyCode!=46 && evt.keyCode!=37
      && evt.keyCode!=38 && evt.keyCode!=39 && evt.keyCode!=40 // arrow keys
      && evt.keyCode!=88 && evt.keyCode !=86) {	               // allow backspace and
	  				                       // delete key and arrow keys (37-40)
                                                               // 86 -ctrl-v, 90-ctrl-z,
        Event.stopEvent(evt);
      }
    }

    Dom.get(el.ctr).innerHTML = charCount + ' / ' + el.maxLength;
  }
};
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.