Beruflich Dokumente
Kultur Dokumente
This document describes and presents examples on how to create GUI classes in
TIBCO General Interface.
Topics
Although TIBCO General Interface has dozens of pre-built GUI classes ranging
from sliders to tree-grids, developers may need to build their own controls. In the
past this has been difficult, due to the complex APIs that General Interface uses to
manage browser layout differences. With the inclusion of the new class,
j s x 3 . g u i . T e m p l a t e , General Interface now provides a base class for you to use
when creating your own user controls. A new schema also allows you to
declaratively create layouts using familiar HTML syntax.
Background
TIBCO General Interface employs a model-view-controller (MVC) architecture.
For each on-screen object (the “view”) there is a corresponding state model that
describes it (the “model”). Logical functions synchronize the two (the
“controller”). For example, consider a slider widget with the model shown in
Table 1.
value 25
orientation horizontal
name myslider
When it is time to paint the model, the corresponding view is generated as HTML.
Note: Some content in the following code has been removed for readability.
The HTML is then sent to the browser (the view delegate—basically a helper that
can render the HTML) and it renders as follows:
If the user interacts with the view by dragging the slider handle, the controller is
notified of the relevant events, including mousedown, mousemove, and
mouseup. The controller then updates the model based upon its own processing
rules. In this case, assume the slider has been dragged to position 50 (Table 2).
The system then synchronizes the model to the view (which is sent to the browser
to reflect the updated state), and the cycle continues.
left
top
width 60 (pixels)
height 20 (pixels)
vertical-align middle
font-name
font-size 20
value 1
value 1
while defining the class. Also note the name for the new class:
my.Spinner1.
jsx3.lang.Class.defineClass("my.Spinner1" //b)
,jsx3.gui.Template.Block
,[jsx3.gui.Form]
,function(SPINNER,spinner) {
});
2. Define any model defaults for the GUI control. The spinner will implement a
default value of 1 if no value is specified. Because the control implements the
j s x 3 . g u i . F o r m interface, its value is stored using the j s x v a l u e field. This
means that any call to the g e t V a l u e method (a mix-in method provided by
j s x 3 . g u i . F o r m ) will return 1 if the given instance does not define a value.
jsx3.require("jsx3.gui.Template","jsx3.gui.Form");
jsx3.lang.Class.defineClass("my.Spinner1"
,jsx3.gui.Template.Block
,[jsx3.gui.Form]
,function(SPINNER,spinner) {
spinner.init = function(strName) {
this.jsxsuper(strName);
};
spinner.getTemplateXML = function() {
return ['<transform ',
'xmlns="http://gi.tibco.com/transform/" ',
'xmlns:u="http://gi.tibco.com/transform/user"
version="1.0">',
' <model/>' ,
' <template/>' ,
'</transform>'].join("");
};
});
The system serializes all simple model properties. This means that serializing
an instance by calling its t o X M L method will result in the property value, 1,
being serialized, regardless of whether or not it was explicitly declared on the
instance.
3. Author the HTML prototype to reflect the requirements in the functional
specification:
<input type="text" value="1"
onchange=";" onkeydown=";" onkeyup=";" onmousewheel=";"
style="position:relative;left:;top:;width:60px;height:20px;
margin:0px;border:solid 1px gray;font-name:;
font-size:10px;vertical-align:middle;" />
Next, replace every HTML attribute, event, or style property value that should
be made dynamic with a replacement variable (denoted by curly braces). For
example, to allow the font-size property to be dynamically replaced with the
model property, j s x f o n t s i z e , change the explicit style declaration from this:
font-size:10px;
to this:
font-size:{jsxfontsize};
width:{jsxwidth|60};height:{jsxheight|20};margin:{jsxmargin}
;
border:{border|solid 1px gray};font-name:{jsxfontname};
font-size:{jsxfontsize};vertical-align:middle;" />
jsx3.lang.Class.defineClass("my.Spinner1"
,jsx3.gui.Template.Block
,[jsx3.gui.Form]
,function(SPINNER,spinner) {
spinner.init = function(strName) {
this.jsxsuper(strName);
};
spinner.jsxvalue = 1;
spinner.getTemplateXML = function() {
return ['<transform
xmlns="http://gi.tibco.com/transform/" ' ,
' xmlns:u="http://gi.tibco.com/transform/user"
version="1.0">' ,
' <model/>' ,
' <template>' ,
' <input type="text" value="{jsxvalue}" ' ,
'onchange="{onchange}" onkeydown="{onkeydown}" ' ,
'onkeyup="{onkeyup}" onmousewheel="{onmousewheel}" ' ,
});
4. The last step is to define the controller functions. As previously explained, any
event declared in the template using the standard variable syntax (such as
o n k e y d o w n = " { o n k e y d o w n } " ) will automatically call the prototype class
function of the same name. This means if you declare an o n k e y d o w n event in
your template, you should also declare the JavaScript controller o n k e y d o w n .
By naming the controllers appropriately, the on-screen view will be able to
locate the corresponding in-memory controller. When found, an instance of
j s x 3 . g u i . E v e n t and a reference to the target GUI object will be passed to the
controller. The controller can then update the model and the view by calling
the s e t P r o p e r t y method. This single call updates the named property on the
model and projects the update to the on-screen view. Note how the s e t V a l u e
method utilizes this function call. This is a final critical step that all controller
methods should use to ensure a complete feedback loop from model to view.
The final class is as follows (the i n i t and c o m p i l e methods have been
removed for readability):
jsx3.require("jsx3.gui.Template","jsx3.gui.Form");
jsx3.lang.Class.defineClass("my.Spinner1"
,jsx3.gui.Template.Block
,[jsx3.gui.Form]
,function(SPINNER,spinner) {
spinner.onkeyup = function(objEvent,objGUI) {
this.jsxvalue = objGUI.value;
};
spinner.onchange = function(objEvent,objGUI) {
this.jsxvalue = objGUI.value;
};
spinner.onmousewheel = function(objEvent,objGUI) {
var wd = objEvent.getWheelDelta();
if (wd > 0) this.up();
else if(wd < 0) this.down();
};
spinner.up = function() {
this.setValue(parseInt(this.getValue())+1);
};
spinner.down = function() {
this.setValue(parseInt(this.getValue())-1);
};
spinner.setValue = function(strValue) {
//tell the engine to update the model and synchronize the
view
this.setProperty("jsxvalue",strValue);
};
});
This example describes how to create a spinner control that includes up and down
buttons.
jsx3.lang.Class.defineClass("my.Spinner1"
,jsx3.gui.Template.Block
,[jsx3.gui.Form]
,function(SPINNER,spinner) {
spinner.init = function(strName) {
this.jsxsuper(strName);
};
spinner.jsxvalue = 1;
this.arrow = {};
this.arrow.up = "url(" +
jsx3.resolveURI("jsx:///images/jsxtimepicker/" +
"spin_up.gif") + ")";
spinner.getTemplateXML = function() {
return ['<transform
xmlns="http://gi.tibco.com/transform/" ' ,
' xmlns:u="http://gi.tibco.com/transform/user"
version="1.0">' ,
' <model/>' ,
' <template/>' ,
'</transform>'].join("");
};
});
2. Expand the definition for the HTML template. Note that the input box is
nested inside a special box, i n l i n e b o x . This is an adapted SPAN object
provided by the template engine that allows the GUI control to have layout
(width and height) even if it is relatively positioned or flowed inline with text.
This enables you to position the control either relatively or absolutely without
having cross browser differences corrupt the layout.
<inlinebox style="position:{$position};left:{jsxleft};
top:{jsxtop};width:{jsxwidth|60};height:{jsxheight|20};
margin:{jsxmargin};border:{jsxborder|solid 1px gray};
vertical-align:middle;">
<input type="text" value="{jsxvalue}" onchange="{onchange}"
onkeydown="{onkeydown}" onkeyup="{onkeyup}"
onmousewheel="{onmousewheel}"
style="position:absolute;left:0px;top:0px;
width:100%-12px;height:100%;
border:{iborder|0px;solid 1px gray;0px;0px;};
font-name:{jsxfontname};font-size:{jsxfontsize};" />
<span onclick="{up}"
style="position:absolute;right:11px;top:0px;
width:11px;height:8px;overflow:hidden;cursor:pointer;
background-image:{arrow.up};"></span>
<span onclick="{down}"
style="position:absolute;right:11px;top:8px;
width:11px;height:8px;overflow:hidden;cursor:pointer;
background-image:{arrow.down};"></span>
</inlinebox>
This example describes how to create the same spinner control as in Exercise 2 on
page 11 using the a t t a c h tag.
Because all fields are calculated, any valid JavaScript can be used. For example,
the top value is calculated based upon the child index of the target child.
top="$$target.getChildIndex() * 8"
To paint exactly four rows of data, you can implement the following selector
query. Note how the select statement returns an instance of j s x 3 . u t i l . I t e r a t o r.
<table>
<tbody>
<for-each select="jsx3.util.List.wrap([1,2,3,4]).iterator()">
<tr><td><text>hello</text></td></tr>
</for-each>
</tbody>
</table>
Although the above selector hard codes the contents of the iterator (such as
[1,2,3,4]), it is common to use a more dynamic approach for your class. For
example, if your GUI class implements the j s x 3 . x m l . C a c h e a b l e and
j s x 3 . x m l . C D F interfaces (as in this example), the class has access to common
methods such as g e t X M L . This allows you to paint as many rows as you have CDF
records in the XML document used by the control. For example:
<table>
<tbody>
<for-each
select="this.getXML().selectNodeIterator('//record')">
<tr><td><text>hello</text></td></tr>
</for-each>
</tbody>
</table>
When it is time to paint the component, a corresponding method wraps the array
and returns an iterator.
grid.getIterableColumns = function() {
return jsx3.util.List.wrap(this.columns.items).iterator();
};
example, the sample class used for this exercise uses a single HTML input in
order to make the grid editable. This reduces the amount of HTML needed to
create a large, editable grid, since the single input can be overlaid on top of a grid
cell when it is time to edit.
The Template engine is capable of positioning the single text input when a given
cell receives focus. The handler for the class locates the position of the cell in
relation to the HTML element that contains both it and the input mask, using the
method g e t R e l a t i v e P o s i t i o n :
var objPos =
jsx3.html.getRelativePosition(objDataContainer,objCell);
objPos.target = objCell;
When the true position for the cell is determined, the position is cached (along
with a few additional values) and the Template engine is notified of the change:
this._setTargetProfile(objPos);
When the model variables are updated, the Template class next attempts to apply
these updates to every HTML element that uses one. In this case, the variables
m a s k _ v a l u e , m a s k _ l e f t , m a s k _ t o p , m a s k _ w i d t h , m a s k _ h e i g h t , and
m a s k _ d i s p l a y are used to update the value, display, and position of the single
input box.
The following input definition appears in the template for the class:
<input u:id="txt_editmask" type="text" class=""
value="{mask_value}"
onkeydown="{onkeydown}" onblur="{onblur}"
style="position:absolute;padding:3 0 0 1;background-color:#ffffff;
z-index:2;color:blue;left:{mask_left};top:{mask_top};
width:{mask_width};height:{mask_height};font-family:{$fontname};
font-size:{$fontsize};display:{mask_display};border:solid 1px
gray;" />
Although the template engine simplifies the act of updating the user interface, it
can also be inefficient, because the engine does not know in advance which tags
implement which variables. When the input mask has been positioned, the engine
continues to scan the remainder of the template, in case other HTML elements
also use the newly-updated variables. To avoid this, you can tell the engine to exit
early when you know that there is no need for it to continue scanning the
remaining HTML elements. For example, note how the i f tag tells the engine to
exit early when there is a flag on the cached profile:
<input u:id="txt_editmask" type="text" class=""
value="{mask_value}"
onkeydown="{onkeydown}" onblur="{onblur}"
style="position:absolute;padding:3 0 0
1;background-color:#ffffff;
z-index:2;color:blue;left:{mask_left};top:{mask_top};
width:{mask_width};height:{mask_height};font-family:{$fontname}
;
font-size:{$fontsize};display:{mask_display};border:solid 1px
gray;" />
<if test="this._getTargetProfile().target"><return/></if>
jsx3.lang.Class.defineClass("my.Grid",jsx3.gui.Template.Block,[jsx
3.xml.Cacheable,jsx3.xml.CDF],function(GRID,grid) {
//init
grid.init = function(strName) {
this.jsxsuper(strName);
};
// defaults
grid.columns = {};
grid.columns.items =
[{attribute:"jsxtext",caption:"Text",width:"*"}];
// template xml
grid.getTemplateXML = function() {
return ['',
this.onbeforeedit(objEvent,objEvent.srcElement().parentNode,objEve
nt.srcElement());
};
grid.onscroll = function(objEvent,objGUI) {
//called when the data rows are scrolled
this.getRenderedBox("header_tbl").style.left =
-objGUI.scrollLeft + "px";
};
grid.onblur = function(objEvent,objGUI) {
//handle the primitive event (onblur) with the prototype
(instance) event
this.onafteredit(objEvent,objGUI);
};
grid.onkeydown = function(objEvent,objGUI) {
var objCell = this._getTargetProfile().target;
var intCellIndex = objCell.cellIndex
var objRow;
//if the current row isn't the last row, apply edit mask to
the next row down
objRow = (objCell.parentNode !=
objCell.parentNode.parentNode.lastChild) ?
objCell.parentNode.nextSibling :
objCell.parentNode.parentNode.firstChild;
} else if(objEvent.upArrow() || (objEvent.enterKey() &&
objEvent.shiftKey())) {
//commit the value; advance edit to the next cell up
this.onafteredit(objEvent,objGUI);
//if the current row isn't the first row, apply edit mask to
the next row up;
objRow = (objCell.parentNode !=
objCell.parentNode.parentNode.firstChild) ?
objCell.parentNode.previousSibling :
objCell.parentNode.parentNode.lastChild;
} else if(objEvent.rightArrow()) {
//commit the value; advance edit to the next cell down
this.onafteredit(objEvent,objGUI);
var objRow = objCell.parentNode;
intCellIndex = objRow.lastChild == objCell ? 0 :
intCellIndex+1;
} else if(objEvent.leftArrow()) {
//commit the value; advance edit to the next cell up
this.onafteredit(objEvent,objGUI);
var objRow = objCell.parentNode;
intCellIndex = objRow.firstChild==objCell
?objRow.lastChild.cellIndex :
intCellIndex-1;
}
this.onbeforeedit(objEvent,objRow,objRow.childNodes[intCellIndex])
;
};
grid._getTargetProfile = function() {
//when an edit event happens, a target profile is created that
describes the context
return this._jsxtargetprofile || {};
};
grid._setTargetProfile = function(objP) {
//when an edit event happens, a target profile is created that
describes the context
this._jsxtargetprofile = objP;
};
grid.onbeforeedit = function(objEvent,objRow,objCell) {
//use a sleep delay to stop repeated clicks and key strokes
from taxing performance
jsx3.sleep(function() {
this._onbeforeedit(objEvent,objRow,objRow.childNodes[objCell.cellI
ndex]);
},"my.GRID",this);
};
grid._onbeforeedit = function(objEvent,objRow,objCell) {
//get the id for the row that was clicked
var strCdfId =
objRow.getAttribute("id").substr(this.getId().length+1);
var strAttName =
this.columns.items[objCell.cellIndex].attribute;
var strValue =
this.getRecordNode(strCdfId).getAttribute(strAttName);
{objEVENT:objEvent,strID:strCdfId,objCELL:objCell,strVALUE:strValu
e});
if(objReturn !== false) {
//determine information about the target cell being edited
(left, top, etc)
var objThis = this.getRendered(objRow);
var objDataContainer =
this.getRenderedBox("datarows",objThis);
var objMask = this.getRenderedBox("txt_editmask",objThis);
//query the system for the location of the target table cell
var objPos =
jsx3.html.getRelativePosition(objDataContainer,objCell);
//when running on firefox, builds earlier than 3.6.1 have a
bug
if(!(jsx3.getVersion() < "3.6.1" &&
/fx/.test(jsx3.CLASS_LOADER.getType()))) {
objPos.L = objPos.L + objDataContainer.scrollLeft;
objPos.T = objPos.T + objDataContainer.scrollTop;
}
grid.onafteredit = function(objEvent,objGUI) {
//get the profile object
var objP = this._getTargetProfile();
this.insertRecordProperty(objP.id,objP.attribute,objGUI.value,fals
e);
grid.setColumns = function(arrColumns) {
//call to change the columns to render for the table. The
schema is as follows.:
this.columns.items = arrColumns;
//the GI serializer only saves scalar types
this.encodeColumns(arrColumns);
this.repaint();
};
grid.encodeColumns = function(arrColumns) {
//serialize the columns array as a string so the GI class
serializer
var a = [];
for(var i=0;i<arrColumns.length;i++) {
var cur = arrColumns[i];
a.push("{attribute:\"" + cur.attribute + "\",caption:" +
jsx3.util.strEscapeJSON(cur.caption) + ",width:\"" + cur.width +
"\"}");
}
this._columns = "[" + a.join(",") + "]";
};
grid.onAfterAssemble = function() {
//when a serialized object is restored in GI (deserialized),
this method is called;
if(!jsx3.util.strEmpty(this._columns))
this.columns.items = jsx3.eval(this._columns);
};
grid.getIterableRecords = function() {
var objCDF = this.getXML();
return objCDF ? objCDF.selectNodeIterator("//record") :
(new jsx3.util.List([])).iterator();
};
grid.getIterableColumns = function() {
return new jsx3.util.List(this.columns.items.length ?
this.columns.items : []).iterator();
};
});
System resolvers help simplify authoring HTML templates by obviating the need
to declare local variables. Table 5 describes the system resolvers, the instance
property to which they map, and relevant triggers.
<div value="12"
style="color:red;">
<attribute-group>{$attribute-gro
up}</attribute-group>
</div>
@xmlns http://gi.tibco.co This is the namespace declaration for the template. All element
m/t nodes within the template belong to this namespace.
ransform/
@xmlns:u http://gi.tibco.co Use to add custom attributes to a given HTML tag in your
m/t template without having the given attribute render to the view.
ransform/user
version 1.0 This is the template version. General Interface release 3.6
supports version 1.0.
model var | import Contains all locally declared variables (resolvers) used by the
template when painting. It is unnecessary to declare this tag if
you do not need any special formatting for your template.
<transform>
<model>
<var id="mycolor" name="background-color"
type="css" triggers="jsxcolor"
defaultvalue="red">
return this.jsxcolor;
</var>
</model>
<template>
<span style="
background-color:{mycolor};"><text>whirled
peas</text></span>
</template>
</transform>
@id #text This is the ID for the variable. It should be unique for the
template. It should not begin with a $ , because the system owns
this prefix to identify its own variable resolvers.
var[parent #text The name attribute is used when the variable is rendered as final
::model]/@ HTML. For css and box type variables, this is the CSS property
name
name. For attributes and events, this is the attribute or event
name. For example, this structure
<transform>
<model>
<var id="mycolor" name="background-color"
type="css" triggers="jsxcolor"
defaultvalue="red">
return this.jsxcolor;
</var>
</model>
<template>
<span style="
background-color:{mycolor};"><text>whirled
peas</text></span>
</template>
</transform>
<transform>
<model>
<var id="url" name="src" type="attribute"
triggers="url">
return this.url;
</var>
</model>
<template>
<img src="{url};"/>
</template>
</transform>
<transform>
<template>
<img src="{url};"/>
</template>
</transform>
function setURL(strURL) {
this.setProperty("url", strURL);
}
<transform>
<model>
<var id="url" name="src" type="attribute"
triggers="url">
return this.url;
</var>
</model>
<template>
<img src="{url};"/>
</template>
</transform>
or
<img src="{url|abc.gif}"/>
<img/>
<img src=""/>
<import resolver="$color"/>
<import library="my.Spinner"/>
<transform>
<model>
<import resolver="color" set="my.Spinner"/>
</model
<template>
<img src="{url};"
style="position:{$position}"/>
</template>
</transform>
@onbeforei text/javascript Called immediately before the template profile is created for a
nit given GUI widget. The template profile is a list of variables that
are merged with the template to render the final HTML.
@onpaint text/javascript Called after the HTML has been painted and rendered by the
browser. Called in a separate call stack from the paint method.
@onbeforer text/javascript Called immediately before a resize event. Return a Boolean false
esize to cancel the resize for the control.
@onresize text/javascript Called immediately after a resize event. The object's dimensions
will have been updated in both the model and view, but its
descendant objects may not have been resized yet.
@onbeforer text/javascript Called immediately before a child is resized. The method will be
esize passed one parameter: a reference to the child about to be resized.
child
Return a Boolean False to cancel the resize for the child.
@recalc true|false Called immediately after the HTML has been painted and
(default) rendered by the browser, causing the resize engine to apply any
dynamic position calculations to the painted control. Use this
feature in your template if you declare any < v a r > elements
within the < t e m p l a t e > section that affect box properties like
left, top, width, padding, border, etc.
<for-each select="this.getMyRows()">
<var
id="rowid">$$target.getAttribute(\'jsxid\')</va
r>
<var
id="rtext">$$target.getAttribute(\'jsxtext\')</
var>
<div id="{rowid}"><text>{rtext}</text></div>
</for-each>
<transform>
<template>
<var id="abc">"red"</var>
<span>
<text>{abc}</text>
</span>
</template>
</transform>
@u:protect true Use on an HTML tag to denote that its dimensions should not be
ed managed by the General Interface layout engine. The tag can still
use the replacement variable engine, but its layouts will be
unaffected. For example, the following template:
<input u:protected="true"
style="width:100%;height:100%;color:{mycolor};"
/>
results in the following HTML when painted
<input
style="width:100%;height:100%;color:red;"/>
<input
style="width:80px;height:235px;color:red;"/>
<span style="position:absolute;">
<left>$$parentwidth - 10</left>
</span>
Note: The syntax above is not required, but can be used for
greater control over layout, since it accepts any valid JavaScript
statement. However, it generally more common to simply declare
the style inline and have the system automatically handle the
calculation. For example, note how the CSS accepts the calculated
value, 100% - 10:
<span
style="position:absolute;left:100%-10px;"></spa
n>
<span style="position:absolute;">
<left>{$left}</left>
</span>
or inline:
<span
style="position:absolute;left:{$left};"></span>
<span
style="position:absolute;right:10px;"></span>
or
<span
style="position:absolute;left:100%-10px;"></spa
n>
<input style="color:blue;">
<position>relative</position>
</input>
The template engine does not required the expanded syntax, but
can be used for greater control over layout, since it accepts any
valid JavaScript statement. However, it is generally more
common to simply declare the style inline and have the system
automatically handle the calculation. For example:
<input style="position:relative;color:blue"/>
Replacement variables can be used in both situations. Whether
expanded:
<input style="color:blue;">
<position>{$position}</position>
</input>
or inline:
<input
style="position:{$position};color:blue;"/>
<transform>
<model>
<var id="myposition" type="css"
name="position">return "fixed";</var>
</model>
<template>
<div u:protected="true"
style="position:{myposition};">
<text>{abc}</text>
</div>
</template>
</transform>
<input type="text">
<empty>true</empty>
</input>
will render as:
<input type="text"/>
attribute-grou #text Use to inject a group of attributes (that is, s r c = " a . g i f "
p w i d t h = " 1 " ) into an HTML
node in the template. For example:
<img>
<attribute-group>{myAttListVar}</attribute-grou
p>
</img>
will render as:
<img>
<style-group>{someVarWithStyles}</style-group>
</img>
<img
style="font-size:10px;style-group:{someVarWithS
tyles};"/>
<img style="
font-size:10px;color:blue;font-weight:bold ;"/>
<span>
<border>solid 1px red;dashed 2px red;0px;solid
1px red;</border>
</span>
<span style="border:{$border};"></span>
Due to the way that the template engine parses styles, you should
not specify complex border declarations or CSS border variants
such as b o r d e r - t o p and b o r d e r - c o l o r inline:
<span>
<padding>8 2 2 8</padding>
</span>
Or inline:
CSS syntax can be used inline, but it must use pixels and it must
not use any padding variants such as p a d d i n g - t o p a n d
p a d d i n g - l e f t . For example:
<span style="padding:8px;"></span>
tagname #text See the description for position. Use to dynamically replace the
tagname for a given HTML node. For example, if a given node
should render as a DIV or a SPAN, use the following to allow this
property to be dynamic, using a replacement variable:
<span>
<tagname>{$tagname}</tagname>
</span>
text #text Use to wrap any text content in your template. All text content
must come first before any descendant nodes and must be
wrapped in a single text tag. For example, this template structure:
<span>
<text>Hello world</text>
</span>
<span>Hello world</span>
attach drawspace | @select Use to attach descendant content. Similar to a for-each iterator,
the select statement expects an instance of
j s x 3 . u t i l . I t e r a t o r . The context variable, $ $ t a r g e t is
available within the attach tag, meaning any variable declaration
can access the current item in the iterator by using it. For
example:
<div>
<attach select="(new
jsx3.util.List(this.getChildren())).iterator()"
>
<drawspace top="$$target.getChildIndex() *
8"/>
</attach>
</div>
<table>
<tbody>
<for-each
select="this.getIterableRecords()">
<var
id="myid">$$target.getAttribute("jsxid")</var>
<if
test="jsx3.util.strEmpty(myid)"><continue/></if
>
<var
id="mytext">$$target.getAttribute("jsxtext")</v
ar>
<tr
id="{myid}><td><text>{mytext}</text></td></tr>
</for-each>
</tbody>
</table>
break Similar to continue, except that the entire loop is exited early, not
just the iteration.
For example, you can abort the update event for your template
(assuming you know that there is no reason to continue resizing
descendant objects):
<if-else>
<if test="somevalue == 1">
<span><text>a</text></span>
</if>
<if test="somevalue == 2">
<span><text>b</text></span>
</if>
<else>
<span><text>c</text></span>
</else>
</if-else>
<div>
<attach select="(new
jsx3.util.List(this.getChildren())).iterator()"
>
<drawspace parentwidth="100"/>
</attach>
</div>
@left text/javascript The attributes, left, top, width and height, border, margin,
position, and padding, can be set on a drawspace node to force
the next HTML node into a particular position, regardless of its
setting. For example, the following SPAN will be placed at left
position 12, even though it specifies that it render at position 10:
<div style="width:100%;height:100%;">
<drawspace left="12"/>
<span style="left:10px;"></span>
</div>
@border text/javascript See the description for @left. In the following example, note how
the border is wrapped in apostrophes, because it is a string:
<div>
<attach select="(new
jsx3.util.List(this.getChildren())).iterator()"
>
<drawspace border="'solid 1px gray'"/>
</attach>
</div>
When the template is run, the system provides contextual variables to give
additional information that is available only when the template is run. For
example, because the system manages layouts, the drawspace changes
(decreases) as each nested tag claims its dimensions. In this example, the
$ $ p a r e n t w i d t h field is used to define how much width is available to render
within. Each variable listed in Table 7 is available within the < t e m p l a t e / > section.
This means if you declare a variable (< v a r / > ) or drawspace (< d r a w s p a c e / > ), you
can use this value in your calculations.
$$parent provides the height of the Render the HTML tag10 pixels from
height containing drawspace the bottom of its container:
<span
style="position:absolute;">
<top>$$parentheight -
10</top>
<span>
<text>{rowtext}</text>
</for-each>
</div>
</for-each>