Kiyaa XHTML TemplatesKiyaa templates are based on the facelets template system, and due credit goes to the inventor of facelets for the main ideas behind this. I just take their great ideas and replaced the JSF components with GWT widgets and a new concept called a View which is somewhat similar to the JSF components. Start With the View Class and Data ModelEach template is associated with a view class that defines the actions and variables available to that template. The view class will have fields for the controller of the application and any model objects which are displayed or edited by the view. package com.example;
import com.habitsoft.kiyaa.views.GeneratedHTMLView;
import com.google.gwt.core.client.GWT;
/**
* Sample XHTML view.
*
* The filename of the template, by default, is MyTemplatedView.xhtml in the same
* package folder as this class.
*/
public abstract class MyTemplatedView implements GeneratedHTMLView {
/** An imaginary Constants subclass for localization */
protected final MyLabels labels = GWT.create(MyLabels.class);
protected MyLabels getLabels() { return labels; }
/** An imaginary controller class that implements our business logic */
protected final MyController controller = MyController.getInstance();
protected MyController getController() { return controller; }
/** Some field we'll let the UI mess with */
int height;
protected int getHeight() { return height; }
protected void setHeight(int height) { this.height = height; }
protected void checkHeight() { controller.checkHeight(height); }
/** Example static factory method to illustrate how these are created */
public static final MyTemplatedView createInstance() {
return GWT.create(MyTemplatedView.class);
}
}Notes: - The class is abstract; Kiyaa adds a generator which generates a subclass of this class for you based on the contents of the xhtml template.
- The template file is, by default, at the same location as the java file but with the xhtml extension
- The class must implement GeneratedHTMLView in order to signal to GWT to use the generator
- Fields need getters and setters in order to be used in the templates
Basic XHTML Example<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:k="http://habitsoft.com/kiyaa/core"
xmlns:ui="http://habitsoft.com/kiyaa/ui"
xmlns:gwt="http://habitsoft.com/kiyaa/gwt">
<body>
This text is removed
<k:view class="account-editor">
<h1>${labels.exampleTitle}</h1>
<h2>Non-localized Text Using a Title Tag</h2>
<ui:label class="my-label">
${labels.thisIsAnExampleOfALabel}
</ui:label>
<label>Enter your height in cm:
<ui:textbox class="my-textbox" text="#{height}"/>
</label>
<!-- Button which calls methods directly on the controller -->
<ui:button onclick="controller.checkHeight(height)" text="Button 1"/>
<!-- Button which calls methods on the view class
(probably a better convention, but more work) -->
<ui:button onclick="checkHeight" text="${labels.button2}"/>
Localization can be supported inside text using
${labels.interpolations}. Note however, that these
are often wrapped in a span element.
</k:view>
This text is removed
</body>
</html>Notes: - The XML namespaces define the meaning of the tags used inside the template
- The k:view element defines the "significant" part of the template; everything around it is removed in the resulting view. This allows you to take an existing XHTML page and select just a part of it to be the part you are converting into a view class.
- You can mix text, XHTML tags, text, widgets, and views together in the template
- You can use localized strings or just inline text, or mix them
- Using ${ ... } allows you to access values on properties of your view
- Using #{ ... } creates a read-write binding; when the view "loads" the value is set in the same manner as with ${ ... } but before running an action the value is copied out of the widget and back to the mentioned property.
The EL SyntaxAs you've seen by now the expressions have 3 formats: - ${ ... } contains an expression which is read when the view loads and copied into the target property on the widget
- #{ ... } extends the behavior of ${ ... } by additionally copying the value back out of the widget to the given expression when the view saves.
- %{ ... } is a variation of ${ ... } that only runs once, when the view is created. Use this for constant values to avoid re-setting them on every load
- @{ ... } is a variation of ${ ... } that loads the value "early" - that is, values with @{ ... } are set before values with ${ ... }. Use this for the condition of k:when to get the view to show up without waiting for other parts of the view to load.
But wait ... there's more! Here are some more of the features supported: - All property accesses and method calls can be asynchronous, even intermediate objects and properties. Any method, getter, or setter whose last parameter is an AsyncCallback will be treated as asynchronous and handled automagically
- You can use some basic arithmetic and boolean expressions, including: + - * / and or (and is used in place of && and or is there for symmetry).
- Values can automatically be converted between some core types and other types. Especially java.lang.String can be converted to/from most primitive types and their boxed versions.
Some pitfalls: - If you use the same #{ ... } expression more than once, only one of them counts - when the view saves, it'll keep the last value that it gets from the widgets. This can be confusing because there is currently no warning that you have done this. To get around it, add an onchange or onclick handler that assigns the value when only when it changes instead of binding the value directly.
ActionsSpecific attributes, as well as some widget or view properties are considered to be "actions" and get special treatment. Actions are identified as follows: - onclick is an action if the widget implements SourcesClickEvents
- onchange is an action if the widget implements SourcesChangeEvents
- onfocus and onblur are actions if the widget implements SourcesFocusEvents
- onPressEnter', 'onPressEscape, onPressSpace, and onKeyPress are actions when the widget implements SourcesKeyboardEvents`.
- Any property whose setter takes an com.habitsoft.kiyaa.metamodel.Action parameter is an action
You can usually get more information about the event (like onKeyPress) from Event.getCurrentEvent(), as long as the save() implementation for your view completes without returning to the main event loop. Action SyntaxAn action can be the name of a method to call, for example: onclick='sayHello()' The method can take parameters, and the parameters may be expressions or even asynchronous properties: onclick='sayHello(labels.helloWorld)' You can also chain multiple actions together using semicolons, they will be run in order until the end, or one of them fails: onclick="sayHello() ; sayGoodbye()" You can use assignments as part of an action to set a property: onclick='name = "Bob" ; sayHello(name)' You can also define an empty action to simply refresh the view: onclick='' Or use the word 'null' as a no-op: onclick='null' Putting a semicolon at the beginning of an action tells kiyaa NOT to save the view before running the action, and a semicolon at the end indicates that load should NOT be run after the action: onclick='; doSomethingWithoutRefreshingTheView() ;' To refresh a different view than the one currently being defined, use the "on view: ..." prefix: onclick='on parentView: doSomethingThatAffectsDataInTheParentView'
onclick='on childView: doSomethingThatOnlyAffectsDataInTheChildView' Defining Views inside PanelsAny View or Widget which implements a method setView, addView, setWidget, or setViewFactory taking a View, 'Widget', or ViewFactory as a parameter (matching the method name) can have an inner subview which is also defined in the same xhtml file. The generator will construct another View subclass and pass either an instance of it, the result of its getViewWidget() method, or an instance of ViewFactory which constructs and returns it. This is very useful for defining panels which can contain other views. When creating a View or a ViewFactory, the special XML attribute 'with-model' indicates that the view is a ModelView. This property contains a fully qualified class name followed by a variable name which is bound to the "model" of the view; that is, when someone calls setModel on the ModelView the value is stored into a variable with the given name. This variable can only be accessed inside the element where it is defined. See the list and table sections below for examples. Conditional DisplayIt's generally quite useful to be able to show and hide parts of the view based on the state of the application. The template system provides two ways of handling this; first, you can call the setVisible method of any widget supporting it by using the visible attribute: <gwt:checkbox visible="${shouldShowCheckbox}"/>In addition, a special WhenView class is provided, which is typically accessed using the tag k:when. This view takes a test property and a viewFactory, which can be created automatically for the elements inside it; for example: <k:when test="${myEntity != null}">
<ui:label class="entity-name-label" text="${myEntity.name}"/>
</k:when>
<k:when test="${myEntity == null}">
<h1>Entity is NULL! NNnnnooOOoooOOoooo!!!!</h1>
</k:when>The condition passed to test must be enclosed in ${ ... } and may use boolean operators like and, or, <, >, <=, >=, != along with method calls (foo.bar(baz)), bean property paths (foo.bar.baz), arithmetic, and various combinations thereof. Note: these operators are not specific to k:when and can be used in any EL expression; I'm mentioning them here for convenience. WhenView is given a ViewFactory and it only constructs the inner view if the condition is true as a kind of Lazy Panel. This means that the code inside WhenView can be somewhat expensive to run, and also that it can assume that the condition is true in its code since it won't be invoked otherwise. Lists and TablesLists are implemented using ListView, whose tag is ui:list. The list of objects is set using the models property, which should be given an array of objects. For each object in the array, a View is constructed using - you guessed it - a ViewFactory. The ViewFactory must return a ModelView subclass which provides the setModel method used by the list to tell each view which object it is displaying. For example: <h2>Here are some strings:</h2>
<ui:list models="${someStringArray}" with-model="java.lang.String someString">
<ui:label>${"The string is: "+someString}</label>
</ui:list>
<h2>Here's some more information about those strings in a table:</h2>
<ui:table models="${someStringArray}" with-model="java.lang.String someString">
<ui:column heading="Length">
<ui:label>${someString.length}</ui:label>
</ui:column>
<ui:column heading="Content">
${someString}
</ui:column>
<ui:column heading="HashCode">
# ${someString.hashCode}
</ui:column>
</ui:table>In addition to the heading, the columns can also have a class to set the css class for cells in the column and/or a test to make the entire column conditionally visible (using setVisible()). The HTML structure of the table is designed to be styled using CSS, and the DOM tree ends up looking like this: <div class="ui-table"> <!-- change this CSS class using `class` or `styleName` -->
<!-- Optional ui:navigation if specified, shows up here in a DIV -->
<div class="table-border-top"/>
<div class="table-border-middle">
<table class="ui-table"> <!-- change this CSS class using `tableClass` -->
<thead><!-- headings ---></thead>
<tbody>
<!-- each row has ui-table-row and either odd or even -->
<tr class="ui-table-row odd">
<td> <!-- td will have the class given in the ui:column element --> </td>
</tr>
</tbody>
</table>
<!-- Optional ui:emptyContent if specified and the table is empty,
shows up here in a DIV if the table is empty -->
<div class="empty-table-content" style="display: none;"> Empty table content </div>
</div>
<div class="table-border-bottom"/>
</div>The list can also be styled using css <!-- The css class of the list is ui-list by default, the -selectable and
-clickable suffixes are added if you set selectable=true or
clickable=true on the list. Change the base "ui-list" by setting
class or styleName. -->
<div class="ui-list ui-list-selectable ui-list-clickable">
<!-- Each list item gets a div with the class ui-list-item, and an odd or
even depending on which row it is in. -->
<div class="ui-list-item odd">
</div>
</div>Lists and tables can also be "clickable" and/or "selectable" in which case they'll start to listen for hover and click events. - When an item is clicked it becomes the new value of "selectedModel"
- The selectedModel, if selectable is true, gains a new CSS class; "ui-list-item-selected" or "ui-table-row-selected" - you can use this to highlight the selected row.
- Selectable and clickable lists and tables also detect hovers and you can use the "ui-table-row-hover" for table rollovers and "ui-list-item-hover" for list rollovers.
- When a row in a clickable or selectable list or table is clicked it sends a click event; use onclick to set an action for this
- When clicking on the table changes current selection, a change event is fired; use onchange to set an action for this
Example: <ui:list id="employeeList"
models="${employees}"
clickable="true"
onclick="showEmployee(employeeList.selectedModel)">
${employee.name}
</ui:list>Tables also have some other features, like context menus, which I haven't even gotten into yet and are still at a somewhat experimental stage. Look at the source to "discover" some more handy features. Widget BindingsWhen a widget or view in the template has the special attribute binding it will be set into the view when it is created. For example: /** TextBox field in our view class we want to bind to */
private TextBox myTextBox;
protected TextBox getMyTextBox() { return myTextBox; }
protected void setMyTextBox(TextBox tb) { this.myTextBox = tb; }
/** Some example action that makes use of the binding for something */
protected void tbChanged() { tb.setStyleName("dirty", true); }<k:view>
<!-- By specifying the binding property here, we can get
direct access to this textbox in the code -->
<ui:textbox binding="myTextBox" text="#{height}"/>
<k:view>When you want to refer to a widget from an action, but don't want to bother with a binding, you can use the id attribute instead. Any widget or view with an id attribute is declared in such a way that it is accessible to its sibling or child views and widgets. Example: <h1>Click the button to change the textbox</h1>
<ui:textbox id="tb1" text="Initial Value"/><br/>
<ui:button onclick='tb1.text = "new value"'/><br/> Loading and SavingThe interface for View defines operations load() and save(). - When the view is first created you must call load() to copy all the values from the model into the view's widgets.
- Before performing any action, the view calls save(), which copies values from the widgets back into the model.
- If the action completes successfully, it calls load() to reload any values changed by the action so the widgets show that value
- Although the method is named save(), it is not necessarily the right time to save to the database; you may want to wait until the user clicks a button labelled "Save". I usually call this action method "commit".
You can define your own methods load() and save() and the subclass will call them before their own implementation, and only if they return success to the callback. This allows you to pre-load some data from the server which is used in the view and which you don't want to be re-loading several times while the view is loaded - the view is not very smart about sharing the results of previous Debugging Template IssuesGWT stores the generated files on disk in the build.gwt/.gen folder (or something like that, depending on your GWT setup) so if you get compile errors or stack traces passing through those files, you'll often have to look at the generated output to figure out what's going wrong. Often you'll have something that is null, but you can't tell what until you look at the right line number in the generated code. The generator is implemented in com.habitsoft.kiyaa.rebind.GeneratedHTMLViewGenerator, if you run into problems where the generated code is invalid, buggy, or doesn't compile you'll have to look there. Adding Tag LibrariesYou can of course define your own tag libraries as namespaces - the libraries shown in the example are the ones included. The files mus be placed in the classpath in the META-INF folder, and named with the extension .kiyaa-taglib.xml. Here's an example: <kiyaa-taglib>
<!--
This tag library contains GWT widgets that can be instantiated
directly using the already available attributes.
-->
<namespace>http://habitsoft.com/kiyaa/gwt</namespace>
<package>com.google.gwt.user.client.ui</package>
<tag>
<tag-name>textbox</tag-name>
<tag-class>TextBox</tag-class>
<content-attr>text</content-attr>
</tag>
<tag>
<tag-name>label</tag-name>
<tag-class>Label</tag-class>
<content-attribute>text</content-attribute>
</tag>
<tag>
<tag-name>a</tag-name>
<tag-class>Anchor</tag-class>
<default for="href">javascript:void(0)</default>
<content-attribute>text</content-attribute>
</tag>
<tag>
<tag-name>form</tag-name>
<tag-class>FormPanel</tag-class>
<default for="encoding">multipart/form-data</default>
<default for="method">post</default>
</tag>
<tag>
<tag-name>file-upload</tag-name>
<tag-class>FileUpload</tag-class>
<default for="name">data</default>
</tag>
<tag>
<tag-name>hidden</tag-name>
<tag-class>Hidden</tag-class>
<content-attribute>value</content-attribute>
</tag>
</kiyaa-taglib>Notes: - The namespace defined here is matched against the namespaces in your template file
- The optional package is used as a package prefix for the classes that follow, to save typing
- Providing a <content-attribute>text</content-attribute> means that you can use a format like <gwt:label>Text</gwt:label> instead of <gwt:label text="Text"/> - just a notation nicety and may also be helpful when converting templates written by others
- Default values are treated as if you'd entered that exact attribute name and value into the XHTML template, if that attribute is missing
More informationThere are some javadocs at the top of the GeneratedHTMLViewGenerator class with more information (sorry I didn't copy it all in here yet ...). Furthermore, that class itself represents althmost the entire implementation of this templating system, and thus stands as the ultimate reference of what can be done, as long as you can grok the code (it's not heavily commented).
|
Cool! any more examples something like the showcase built using kiyaa would be gr8.
I have implemented custom components in kiyaa now for Widgets button, tree etc. i am moving on to containers. Idea is to allow templated ui developments using kiyaa and gwt ext libraries. progress is slow due to other commitments. but will keep you posted.
Note the addition of %{ ... } for costants to improve performance, and the "on parentView: ..." action syntax to allow your action to refresh another view instead of the current one when it runs.
Could you provide a downloadable example project of the above?
Thanks