Tags:
create new tag
, view all tags
-- PedroRio - 11 Oct 2012

XEO Components and Templating

Introduction

In order to improve the layout customization of existing applications, the concept of "templating" was introduced. This document explains all you need to know in order to use the new "templating" system.

In order to create a component (the old way) you need two classes:

  • The component class
  • The renderer class

The component class holds the properties of the component and the renderer is responsible for the HTML output. The rendering of a component is usually something like this:

_

w.startElement( DIV );
w.startElement( UL );
Iterator<UIComponent> it = component.getChildren().iterator();
while (it.hasNext()){
    Menu m = (Menu) it.next();
    w.startElement (LI);
      w.write (m.getText());
    w.endElement(LI);

}
w.endElement(UL);
w.endElement(DIV);
_

Apart from being cumbersome this meant that you had absolutely no chance of customizing the look and feel of a given component (apart from creating a new renderer for the said component). With templating the following scenario is possible when building a component:

  • Component class (with properties)
  • Component Renderer (can be optional)
  • Component Template (a file declaring how the component is rendered, HTML + CSS + Javascript)

A template could be something like this:

<div>
   <ul>
      <#list this.children as menu>
           <li>${menu.text}</li>
      </#list>    
   </ul>
</div>

The template now declares how the component is rendered and can be switched at runtime by defining a new template for same component. The template has access to "variables" which will be replaced by their values at runtime. Modern components require some help of javascript and css, as such it's possible to include that type of content in your templates. You can find more information in the Javascript Section.

Template Engine

_

The template system used in XEO builds upon the Freemaker Library which has its own template language. The manual for creating Freemarker templates can be found here. If you're not familiar with template systems we recommend checking the wikipedia page for a more general approach and the Freemarker page for a more specific one. The main ideia behind templating engines is rendering content using a variable-replacing system, meaning that if you want to render a "Hello" message followed by a username, you would create a template like the following:

Hello ${username}

And pass the variable "username" to the template engine to get something like "Hello John" or "Hello Bill". There are a lot more things you can do inside a template (like iterating through a list, using if/else statements and a lot more (check the Freemarker manual for a full list)).

_

Template Syntax Examples

_

The following template is a sample of the ActionButton template component. Notice the use of the <#if> directive to choose whether or not the component should render the HTML. Notice the use of the clientId, id, width and label properties to generate the HTML div and button with the correct attributes.

<#if !this.renderedOnClient>
   <div id='${this.clientId}'>
      <button id='${this.id}_btn' class='xwc-actionbutton' style='min-width;${this.width}px'>
         ${this.label}
      </button>
   </div>
</#if>

_

In the following example (extracted from the Tabs component) you can see the use of lists and a special directive( @xvw_facet - explained later in this document)

<#list this.children as tab>
   <div id='${tab.id}' class='xwc-tab-content'>
   </div>
</#list>    
<@xvw_facet />

You can also use/include Javascript and generate Javascript dinamically (this example generates a JQuery call to create a panel).

<@xvw_script position='footer'>
   $(function() {
      $( '#${this.id}' )
      <#if this.collapsible>
         .collapsiblePanel(true);
      <#else>
         .collapsiblePanel(false);   
      </#if>
   });
</@xvw_script>

_

Template Variables - What's available

_

At the heart of the template engine there are the variables. When using a template combined with a component you have access to all the properties available in the component. Each component is exported (into its own template) as the this variable and the properties of the component can be accessed like the following:

${this.propertyName}

For the Section component which has properties like visible and title you can do:

${this.visible} or ${this.title}

You also have access to any method the component has that follows Java Beans convention. If the component has getCustomValue() method, you can use ${this.customValue} (with the getCustomValue method not being related with any instance of a XUIBaseProperty).

When you're creating a new template for an existing component refer to the component's API to check which variables are available.

_

Generic Template Component

_

You may need to create a view and "use" a specific template inside that view, but not necessarily associated with a specific component. In order to support that situation, the Template component was created. Template has no properties other than the standard ones provided by XUIComponentBase, but it has an interesting feature: Any property that is declared in the XML definition, will be passed to the template as ${this.properties.PROPERTY_NAME} . The properties can be literals or expressions, meaning you could have the following in the viewer:

<xvw:template template='myCustomTemplate.ftl' customProperty='World' fruit='#{viewBean.myValue}'/>

And a template like the following:

<div>
   Hello ${this.properties.customProperty} my favorite fruit is ${this.properties.fruit}!
</div>

And a bean with:

public String getMyValue(){
   return "Orange";
}

Which would render the following:

<div>
   Hello World my favorite fruit is Orange!
</div>

_

Where to place your templates:

When you create a template for a component and reference the template by its name using the template property (now available in every component), XEO will search for the template in the following places:

  • A directory named "templates" inside your default web app (i.e. will start searching in webapps/default/templates/ + pathToTemplate).
  • The source code root directory

Imagine a component declaration like the following:

<xvw:actionButton template='vanilla/myTemplate.ftl' />

The template loader will search for the template in "webapps/default/templates/vanilla/myTemplate.ftl" and, if it does not find the template, searches in "src/vanilla/myTemplate.ftl", if the template is not found in any of the locations, an error is thrown.

_

Component Properties (regarding Templates)

The XUIComponentBase class (the class from which all components derive) was added two new properties (both binding properties whose value can be retrieved from the bean):

  • template
  • templateContent

The template property allows you to reference the template to be used in the component's rendering by it's location (i.e. the location of the file with the template content). See the previous section for more detail on how template loading is done.

The templateContent property allows you to dinamically generate the template's content in the bean and pass it to the component. You need to generate a template compliant with the Freemarker engine and return it as a String to the component: The following example depicts this situation:

(In the Viewer)

<xvw:actionButton templateContent='#{viewBean.content}' />

(In the Bean)

public String getContent(){
   return "Hello ${username}"
}

_

_

Special Directive Processing

There are special intructions that are available for use in a template. There instructions are:

  • xvw_script (Javascript inclusion)
  • xvw_css (CSS inclusion)
  • xvw_header (Page Header manipulation)
  • xvw_facet (Children rendering)

The following sections explain each directive in detail.

_

XVW_Script : Including Javascript

_

If you need to include a Javascript file for use in a template, or add an inline call to a Javascript function, you need the <@xvw_script> directive. It allows you to include files and inline calls at the header or footer of the page. The XVW_Script directive has the following properties:

  • position ( footer / header / inline ) : The position where the script will be added (relative to the page, defaults to header)
  • id : The id of the script (optional, defaults to a dinamically generated id). If two scripts share the same ID only the last one will be added to the page
  • src : The path to the script to include relative to the root of the web application (if it's an inline call to a script, use the body of the directive)

Example of how to include a script in a template:

<@xvw_script position='header' id='jquery' src='javascript/jquery/jquery.min.1.8.2.js' />

Example of how to add a call to a javascript function in a template

<@xvw_script position='footer'>
     alert(${this.id});
</@xvw_script>

_

XVW_CSS : Including CSS


If you need to include a CSS file for use in a template, or add a CSS style inline you need the _<@xvw_css>
directive. It allows you to include files and inline styles at the header or footer of the page. The XVW_CSS directive has the following properties:

  • position ( footer / header / inline ) : The position where the css will be added (relative to the page, defaults to header)
  • id : The id of the script (optional, defaults to a dinamically generated id). If two CSS share the same ID only the last one will be added to the page
  • src : The path to the CSS to include relative to the root of the web application (if it's an inline style, use the body of the directive)

Example of how to include a CSS file in a template

<@xvw_css position='header' src='path/to/myStyles.css' />

Example of how to add inline styles in a template

<@xvw_css position='header'>
    .myStyle{
        color:red;
        font-size:13px;
     }
</@xvw_css>

_

XVW_Header : Writting to the page header

If there's the need to add a special header to the page (like adding a meta tag to a page, or something like that) you can use the <@xvw_header> directive

Example of adding a meta tag

<@xvw_header>
    <meta name="description" content="Awesome Description Here">
</@xvw_header>

Or adding Google Chrome Frame

<@xvw_header>
   <meta http-equiv="X-UA-Compatible" content="chrome=1">
</@xvw_header>

_

XVW_Facet : Processing Component Children, integrally or partially

The <@xvw_facet> directive is related to the processing of child elements in templates. Several components are "childless", meaning they don't have children to be rendered. While other components can have children and may require that their children be rendered in a special place inside their own template. The <@xvw_facet> directive allows you to specify exactly where children are rendered. Lets see an example with the Section component's template:

<fieldset id='${this.id}' class='ui-widget ui-widget-content xwc-section'>
    <legend style='ui-widget-header ui-corner-all xwc-legend'>
    ${this.label}
    </legend>
    <@xvw_facet />
</fieldset>

The section component is basically a fieldset wrapping the content of its children. As such, the template draws the fieldset and legend tags, and then instructs the template (through the <@xvw_facet> directive) to render its children inside the fieldset. If a section component has as children a set of rows, they'll be rendered inside the fieldset.

But what if you know you have only a specific set of children and you want to render them individually in certain places? Well The <@xvw_facet> directive is able to help you again. If you need a component (let's assume the generic "template" component) with two children and in the template of that component you need to render those children in specific places then you need to declare each children wrapped in a f:facet component and give the facet a name. That name will be used in the template (with the <@xvw_facet> directive), see the following viewer example:

<xvw:template template='myTemplate.ftl'>
       <f:facet name='button'>
             <xvw:actionButton>
       </f:facet>
   <f:facet name='grid'>
             <xvw:gridPanel >
        </f:facet>
</xvw:template>

And a template like the following (see the use of the <@xvw_facet> directive with the name property to render the specific child in that position) :

<div id=${this.id}>
    <fieldset>
       <@xvw_facet name='actionButton' />
    </fieldset> 
    <ul>
   <li>TESTE </li>
    </ul>
<@xvw_facet name='grid' />
</div>

Each child must have a unique id when wrapped inside a f:facet component.

_

Adhoc Template Processing

_

If for some reason you need to process a template within your code you use can the class netgest.bo.xwc.components.template.util.CustomTemplateRenderer, which has two methods:

public static String processTemplateFile(String templateName, Map<String,Object> context);
public static String processTemplate(String templateContent, Map<String,Object> context);

The processTemplateFile method receives the name of a template (same rules defined earlier apply to finding templates) and a Map with the mappings between variables and their values to be applied in the template.

The processTemplate method receives the content of the template as a String and a Map with the variables, for instance you could have the following code in your application:

public String processCustomActionAndGenerateHtml(){
   // Business logic  
   String template = "Hello ${user} ";
   Map<String,Object> map = new HashMap<String,Object>();
   map.put( "user" , "John" );
   String result = CustomTemplateRenderer.renderTemplate( template , map );
   return result;
}

And the result would be:

Hello John

_

XEO Components:

Three components were created to integrate individual objects, object lists and lovs with templates. The details for each component is in the following sections:

Xeo Objects Syntax inside templates:

If you're familiar with XEO's Java API you know that to get the value of an instance attribute you need to do instance.getAttribute("name").getValueString(). Inside the templates we've made things simpler and you can do simply ${instance.name}, and iterate a bridge, you do <#list instance.bridgeName as element> smile

XeoList (xeo:xeoList)

The XeoList component allows you to display a list of objects using a template. The component has the following properties

  • boql : The boql expression to execute and retrieve the list of objects
  • name : The name for the variable to put in the context, will be available as ${this.properties.NAME}

You can use the component as follows:

<xeo:xeoList name='myVar' boql='select Ebo_Perf' template='list.ftl'>

And the template list.ftl:

<ul>
<#list this.properties.myVar as menu>
   <li><a href="none.html">${menu.username}</a></li>
</#list>
</ul>

This will generate a list with a link for each username in the application

XeoObject (xeo:xeoObject)

The XeoObject component allows you to associate a single instance with a template. It has the following properties

  • boql : The boql expression to execute and retrieve the object (if more than one is retrieved only the first one is used)
  • name : The name for the variable to put in the context, will be available as ${this.properties.NAME}

You can use the component like the following:

<xeo:xeoObject boql="select Demo where name = John" name="var" template='site/menus.ftl' ></xeo:xeoObject>

And use a template like:

<div>
    <span>${this.properties.var.name}</span>
    <span>${this.properties.var.age}</span>
    <ul>
   <#list this.properties.var.bridge as menu>
       <li>${menu.name}</li>
   </#list>
    </ul>
</div>

XeoLov (xeo:lov)

The XeoLov component allows you to have access to a specific List of Values. It has the following properties:

  • lovName : The name of the lov to load
  • name : The name for the variable to put in the context, will be available as ${this.properties.NAME}

The variable ${this.properties.NAME} contains a list with all values, which can be used in a template like this:

<#list this.properties.NAME as item>
   <li>${item.value} - ${item.description}</li>
</#list>

Each lov item has the .value and .description property representing, respectively, the getValue() and getDescription() methods available in the lovObject class

_

Error in Template Processing

Errors in template processing should be printed directly in the output stream as well as in the console (with a stack trace) for easy problem detection (it may not render very nicelly in all situations but you'll get to notice the problem)

_

Wrapper for XVWScripts

_

If you are creating a component that needs to make Ajax calls and don't want to hardcode the reference to the Javacript function you can use the provided variable name XVWScripts and call the getAjaxCommand(Component) method, like in the following example (extracted from the template of the ActionButton component):

$( '#${this.id}_btn' ).button().click( function () { ${XVWScripts.getAjaxCommand(this)}; return false; })

In this situation we're generating the Ajax call for the "this" component.

Template Examples

Here you can find some template examples:

Adding a javascript file

_

<@xvw_script src='jquery-xeo/xwc-panel.js' />

_

Adding an inline javascript function

_

<@xvw_script position='header' id='xvw-flattree' >
XVW.showHideMenu = function (elemParent){
   var elem = $(elemParent).next();
   elem.toggle();
   if (elem.hasClass('xwc-tree-panel-highlighted'))
      elem.removeClass('xwc-tree-panel-highlighted');
   else
      elem.addClass('xwc-tree-panel-highlighted');      
}
</@xvw_script>

_

Rendering HTML only if the component is not rendered. Also, telling the location of where children should be rendered

_

<#if !this.renderedOnClient>
   <div id="${this.clientId}">
      <div id="${this.id}" class='xwc-panel' title='${this.title!}'>   
         <@xvw_facet />
      </div>
   </div>   
</#if>

_

Adding a script to initialize a jQuery component (with rendering logic)

_

<@xvw_script position='footer'>
   $(function() {
      $( '#${this.id}' )
      <#if this.collapsible>
         .collapsiblePanel(true);
      <#else>
         .collapsiblePanel(false);   
      </#if>
   });
</@xvw_script>

_

Creating a list (Html select ) from a map (with a selected value on the "select" element)
<select style='width:100%' id='${this.id}' name='${this.clientId}'>
   <#list this.lovMap?keys as item> <#-- Iterate through the keys of the map -->
      <#if (this.lovMap[item] == this.value!)> <#-- Get the value from the map -->
         <option value='${item}' selected='selected'>${this.lovMap[item]}</option>
      <#else>
         <option value='${item}'>${this.lovMap[item]}</option>   
      </#if>
   </#list>   
</select>

_

Using the "switch" expression on an Enum value

_

<span style='float:left; margin:0 7px 20px 0' 
   <#switch this.messageBoxType>
      <#case "ERROR">
         class='xwc-messagebox-icon ui-icon ui-icon-alert'>
      <#break>   
      <#case "INFO">
         class='xwc-messagebox-icon ui-icon ui-icon-info'>
      <#break>
      <#case "QUESTIOn">
         class='xwc-messagebox-icon ui-icon ui-icon-help'>
      <#break>
      <#case "WARNING">
         class='xwc-messagebox-icon ui-icon ui-icon-notice'>
      <#break>
   </#switch>
</span>

_

Checking if variable has a value

_

<#if menu.actionExpression??> <#-- If menu.getActionExpression has a value then execute what's inside the if -->

_

Generating jQuery options

_

<@xvw_script position='footer'>
$(function() { $( '#${this.id}' )
   .dialog(
      { 'modal' : ${this.modal?string}
        ,'width' : ${this.width}
        ,'height' : ${this.height}
        ,'title' : '${this.title}' }); });
</@xvw_script>

_

Comments inside templates:

_

<#-- This is a comment -->

_

Edit | Attach | Print version | History: r22 | r8 < r7 < r6 < r5 | Backlinks | Raw View | Raw edit | More topic actions...
Topic revision: r6 - 2012-10-15 - PedroRio
 

No permission to view TWiki.WebTopBar

This site is powered by the TWiki collaboration platform Powered by Perl

No permission to view TWiki.WebBottomBar