Background of Problem
We wanted to create a bullet chart like this screen shot. Although it may be easy for us to draw it in HTML directly, but in GI, it’s not easy to show this chart by GI native matrix component. And actual at this time, GI template language will show it is powerful.
Template Language
The General Interface template language simplifies the creation of custom GUI components by using a technique that is already familiar to web developers. It builds on your knowledge of HTML and JavaScript. Using the General Interface template language, you can convert a single snippet of HTML, a widget, into a re-usable component.This means that if you’ve developed user interface components that combine HTML and JavaScript code, you can convert the functional user interface HTML elements into a General Interface template for custom usage.
How to Create a Template
1.Prepare native HTML snippet to generate bullet charts, four values need.
<div style="background-color:#efefef;height:13px;width:200px;position:relative;"> <span id="a1" style="float:left;position:absolute;left:0px; background-color:#ccc;width:*180px*;height:13px;"></span> <span id="a1" style="float:left;position:absolute;left:0px;background-color:#aaa;width:130px;height:13px;"></span> <span id="a1" style=" float:left;position:absolute;left:0px;top:4px;background-color:#000;width:160px;height:5px;font-size:5px"></span> </div>
2.Convert HTML snippet to a template, also add title, label, chart head and x-coordinate. See source code from line 12 to line 62 in logic.js.
- Use <table> to hold whole bullet charts, title is outside of this table.
- <for-each> extract every record from a CDF and draw a <tr> row with three <td>, 1st is row label, 2nd is red notation, 3rd is bullet charts.
- Last row will hold a fixed x-coordiante with gray color.
3.Define a custom class to use this template, we need inherit jsx3.xml.Cacheable, jsx3.xml.CDF interface, because we should accept a CDF xml as data to draw this bullet.
jsx3.lang.Class.defineClass("BulletChart",jsx3.gui.Template.Block, jsx3.gui.Form,jsx3.xml.Cacheable,jsx3.xml.CDF], function(chart, CHART) { … } );
Usage
- Drag a normal jsx3.gui.Block and drop it into your GUI, open this GUI file by text editor and change the name from jsx3.gui.Block to BulletChart
- Use the following code to set data to your bulletChart component:
chart.resetCacheData(); var cdf = jsx3.xml.CDF.Document.newDocument(); cdf.insertRecord({jsxid:"Revenue",jsxbg1:"150", jsxbg2:"90", jsxbg3:"70", jsxvalue:"140"}); cdf.insertRecord({jsxid:"Profit",jsxbg1:"150",jsxbg2:"80",jsxbg3:"70",jsxvalue:"60", jsxred:"true"}); cdf.insertRecord({jsxid:"Avg Order Size",jsxbg1:"150",jsxbg2:"75",jsxbg3:"65", jsxvalue:"69"}); cdf.insertRecord({jsxid:"On Time Delivery", jsxbg1:"150", jsxbg2:"90", jsxbg3:"70", jsxvalue:"120"}); chart.setXMLString(cdf.toString()); chart.repaint();
| CDF comment jsxid value is row label, jsxbg1, jsxbg2, jsxbg3 are three widths to draw different gray background blocks in bullet chart, *jsxvalu*e is the width of thin black block. |
Illustration with final source
More information
- Legend, trend and actual columns are ignored in my implementation, it's easy to do.
- No implement a vertical line in 100%, I don't find an easy way to locate its position, still need some research
- Please replace my special character with a actual red-dot image
- Build in GI 3.8.1 and test in FF3 and IE7.
Source
http://www.generalinterface.org/forums/download/file.php?id=54&sid=6e0b12ce1d10782391f4987b6a736087
There are a bunch of features in GI 3.9 that improve support for HTML forms.
In versions prior to 3.9 the only forms support was via the jsx3.net.Form class. This is a programatic class (not part of the GI DOM and not painted) that requires a lot of custom JavaScript coding. For example, when the user submits the form you must instantiate the class and copy all field names and values from GI DOM objects to the form. An additional significant shortcoming of jsx3.net.Form is that it has never supported file upload on Firefox.
In GI 3.9 you have two options if you want to integrate your GI application with services that expect submissions via HTML forms. You can still use jsx3.net.Form or you can use jsx3.gui.NativeForm, which is new in 3.9. This new class supports file upload (via POST) on all browsers and should require a lot less JavaScript code on your part.
jsx3.gui.NativeForm extends jsx3.app.Model so it is part of the GI DOM. We added it to the Component Libraries palette (under Form Elements) so you can drag and drop it into your GI component. Just like with a native HTML form, any form fields that are DOM children of a jsx3.gui.NativeForm become part of that form. When the form is submitted, all constituent fields are submitted with the form. There is very little magic going on here; a NativeForm simply renders as a <form> element in HTML and most functionality is provided by the native browser implementation.
There are several form element controls that are compatible with NativeForm. They are:
- jsx3.gui.TextBox - (Text Box, Text Area, Number Input, and Password)
- jsx3.gui.NativeSelect - New in 3.9 (jsx3.gui.Select is not compatible with NativeForm)
- jsx3.gui.FileUpload - New in 3.9
- jsx3.gui.NativeHidden - New in 3.9
- jsx3.NativeButton - New in 3.9 (You can use a jsx3.gui.Button to submit a form by writing an event handler on the button. However, only a NativeButton can submit a CGI parameter.)
- jsx3.gui.RadioButton
- jsx3.gui.CheckBox
- jsx3.gui.DatePicker
In each case besides RadioButton the name of the submitted form field (i.e. CGI parameter) is equal to the Name property of the GI object. In the case of RadioButton the form field is the Group Name property and only one value is submitted for all radio buttons that share the same Group Name.
There are a few important properties of NativeForm that control how the form is submitted. First of all you have Method (GET or POST) and Multipart. Multipart must be true (and Method must be POST) if your form includes a file upload field.
The Action property is the URL of the service to which to submit the form. Note that this can be an absolute or a relative URL. If it is relative, then the URL is resolved relative to the application directory (jsx3.app.Server.resolveURI()).
| Don't ignore cross domain issues: you can submit to another domain/scheme/port but you will only be able to read the response from the submission if the URL scheme, domain and port are exactly the same as the URL of the page hosting the GI application. |
Finally, the Target and Target IFrame properties control where the response from the submission is displayed. In most cases you will probably want to submit the form invisibly and process the response programatically. In this case set Target to Invisible IFRAME. You can also have the response render in a native popup window (Target = New Window) or have it replace the page hosting the GI application (Target = Current Window). Using the Target IFrame property you can have the response render in an instance of jsx3.gui.IFrame.
We also added a bunch of new components that aid in creating accessible HTML forms. A new class, jsx3.gui.Label, renders a native <label> element. In HTML forms a label is associated with a form field. Set the For Control property of the label to the name of a GI form field to associate it with that field. Using labels helps screen readers and other accessibility software make better sense of the form.
Along with Label there are now components (all instances of jsx3.gui.Block) for <fieldset>, <legend> and <h1> through <h6>. None of these components can do anything visually that a plain old jsx3.gui.Block can't do with the right CSS. However, in general it is better to use the full range of built-in HTML elements when designing for accessibility because each element has meaning independent of how it is rendered.
Here's a screen shot taken from 3.9 Builder of a form being built:
![]()
And the source code: form.xml
Related JIRA tickets:
I'm excited to announce a completely redesigned JavaScript console for GI Builder. The implementation comes courtesy of community member Eric Shi, who deserves much praise for stepping up and contributing this code.
Here is the JIRA ticket: http://www.generalinterface.org/bugs/browse/GI-421
The new JS console borrows heavily from the Safari Error Console. We really like how the Safari console works so we thought we'd port it to GI Builder so it can run on every browser that Builder supports.
One of the first things you'll notice about the new JS console is that it displays a running list of commands and command output. This is what makes it a "console." In our experience this is much more useful than the test utility in GI 3.8, which only shows the last command and its output.
When focused in the console prompt use the up and down arrow keys to scroll through the command history. This works even across restarts of Builder!
The other key feature of the new JS console is the ability to drill down into nested data structures that a command outputs. For example, enter the command "document.body" or "jsx3" and you'll see the string representation of an object and a collapsed arrow to its left. Click on the arrow to expand the object. Any nested objects can themselves be expanded, indefinitely. Useful hint: property keys are shown in purple except when the value of the property is inherited from the object's prototype, in which case the key is shown in gray. Function and Array outputs are also printed intelligently. No more will you see the cryptic and unhelpful "[Object]" when evaluating a JavaScript expression.
If you ever want to clear the console history just type Ctrl+K or right click on the console for a context menu.
By default the console evaluates the input any time you press the Enter key. The console does support multiline input either by pasting the multiline text at the prompt or by typing Shift+Enter to insert a carriage return.
Eric also implemented GI-705 in the new console, which I described previously in this blog post.

A note about execution context: all code evaluated by the JS console is execute in window scope. That means if you evaluate a statement like:
f = 1;
you have actually set the property f of the top-level object window. If you set a global variable in this way it is available to subsequent expressions that you evaluate in the console. Note that the form var f = 1; does not have the same behavior as f = 1;. The value of f will not be available to subsequent expressions if set in this way.
Since GI 3.0 debuted in 2005 with the Common Data Format (CDF), GI has enforced a standard XML data schema with a root element called data, nested elements called record and record attributes such as jsxid, jsxtext and jsximg. This schema applies to the classes that implement the jsx3.xml.CDF interface: Select, Tree, Menu, Table and Matrix (and previously List and Grid).
<data jsxid="jsxroot"> <record jsxid="1" jsxtext="One" jsximg="one.gif"/> <record jsxid="2" jsxtext="Two" jsximg="two.gif"/> ... </data>
There are a lot of benefits to a standard data schema like this such as simplicity and interoperability. However, a significant problem is that few services expose data as CDF unless they happened to be designed specifically for GI. Ever since 3.0 we've been coming up with new ways to work around this problem.
All of the CDF controls use XSLT to transform their XML data sources into HTML for rendering. GI 3.0 included the XSLURL, XSLId and XSLString properties (in jsx3.xml.Cacheable), which allowed the default XSLT template to be overridden for a particular instance of one of the CDF classes. So a developer could copy jsxtree.xsl to their project, change, for example, @jsxtext to @label and set an instance of Tree to use the modified template.
This approach had a number of problems, the most significant of which was that the template files were implicitly part of the API of each class and would need to be forward compatible with new releases. If we wanted to add support for a new CDF attribute like @jsxstyle (and then if Tree.js somehow depended on the new template behavior) all previously branched templates would break. When I realized this in the mid 3.x's we decided that supporting custom templates was clearly untenable and I deprecated the functionality.
At the same time I introduced a replacement called XSL transformers. This feature remains supported today though it has its own issues. Using XSL transformers a developer can define one or more XSL transformations to convert the source data to CDF before it goes into the XML application cache and is used to render a CDF control. This has the advantage of being completely declarative (compared with a custom JavaScript schema conversion). It can also modify the structure of the source XML so that the CDF ends up with more or fewer records than the source XML. However, it forces the developer to know XSL, which is not necessarily a common skill for front-end developers.
In 3.9 I've come up with a solution that I think is better than both of the previous approaches. It accomplishes 90% of what XML transformers does and is much simpler. It also enables scenarios that neither of the previous approaches did like:
- The core CDF schema elements and attributes data, record and jsxid can be renamed to anything
- A single data source can drive multiple CDF controls, each with its own view of the data
- No additional files need to be loaded over HTTP
Here's how it works. Every CDF control has a schema property of type jsx3.xml.CDFSchema. The value of this property defines how the control views its CDF datasource. In other words it defines its schema. If the schema property is not set then the control uses the default CDF schema, which is the same as the schema used in 3.0-3.8.
To implement this feature in 3.9 each CDF control, instead of querying its datasource for, let's say, the jsxtext attribute of a record, first queries its schema for the name of the text attribute and then queries its datasource for that attribute of a record. For example, in 3.8 jsx3.gui.Tree included many expressions like
this.getRecordNode(id).getAttribute("jsxtext")
In 3.9 this type of expression has been rewritten as
this.getRecordNode(id).getAttribute(this.getSchema().getProp("text"))
This additional level of indirection allows the CDFSchema object to control how each CDF control views its datasource. The default CDFSchema object returns "jsxtext" from a call to getProp("text"). However, you can modify the schema so that it returns "label" instead. This change would allow you to have a CDF datasource like
<data jsxid="jsxroot"> <record jsxid="1" label="One"/> ... </data>
In 3.9 you can control this new functionality from within Builder. To do so,
- Open or create a component with a CDF control in it, such as Matrix, Tree or Menu.
- Find the CDF Schema component in the Component Libraries palette and drag it onto the CDF control. It may be easier to drag it onto the corresponding node in the Component Hierarchy palette.
- Modify the properties of the schema object in the Properties Editor.
In the Properties Editor you'll find an editable property for each attribute in the default CDF schema. In general, the default value of property abc is jsxabc. CDFSchema also has a property called "record." This property allows you to change the element name of the records in the datasource. You can choose another name, such as "item" or you can use set it to "*" to indicate than any XML element should be interpreted as a data record.

For the CDF schema shown in the screenshot, I will have a datasource that looks like
<items id="jsxroot"> <item id="1" label="One"/> <thing id="2" label="Two"/> <object id="3" label="Three"/> ... </items>
Using the same datasource for two different controls is simply a matter of defining two different CDF schemas for the controls. I've put together a simple example project: GI-702.zip.
Enjoy, and leave any feedback here as a comment on this post or on the JIRA ticket.
I've added something to GI 3.9 Builder that will make you happy if you use the JavaScript Test Utility (a.k.a. "the scriptlet pad") very much. Now every GI DOM object can be referenced by its name without writing jsx3.GO(), myApp.getJSXByName() or jsx3.ide.getSelected()[0].
Before you had to type:
jsx3.GO("block1").getWidth();
Now you can just type:
block1.getWidth();

Any object visible in the Component Hierarchy in the active component editor is exposed in this manner. If the current editor is not a component editor then no objects are exposed.
Two caveats:
- Only objects whose names are valid JavaScript variable names are exposed. The name must match [\$a-zA-Z_][\$\w]*.
- The behavior of name collisions is not defined. If you have two or more objects in the same component with the same name, the corresponding variable may reference either one.
Here is the JIRA ticket: http://www.generalinterface.org/bugs/browse/GI-705
Enjoy!
Application Log Monitor
I keep seeing the use of alert() for debug logging purpose in the forums posts. I like to suggest that user don't do this. First, the use of alert() can disrupts asynchronous code executions and create undesirable side effect in AJAX framework such as GI. Second, GI has a easy to use and configure log monitor that can even be configured to pop up only when you press a special hotkeys combination. Third, logging to the log monitor is as easy as calling "jsx3.log()" instead of "alert()".
To enable the log monitor, follow these steps
- Locate under your "gi-3.8" the file "logger.xml", open this file for editing.
- Uncomment the <handler name="appMonitor1" ... section, while commenting out the "serverNamespace" property. This will enable the handler for any applications
<handler name="appMonitor1" class="jsx3.app.Monitor" require="true"> <!--property name="serverNamespace" value="myApp"/--> <property name="disableInIDE" eval="true" value="true"/> <property name="activateOnHotKey" eval="true" value="false"/> <property name="format" value="%t %n (%l) - %M"/> </handler>
Optional : you can change the property activateOnHotKey to true to allow the use of keys Ctrl+Alt+m to open the log monitor
- Also uncomment the global logger's handler reference
<logger name="global" level="INFO"> <handler-ref name="memory"/> <handler-ref name="console"/> <handler-ref name="ide"/> <handler-ref name="fatal"/> <handler-ref name="appMonitor1"/> </logger>
- Add jsx3.log("some log message"); to your code
- Run your project
Firebug and IE8 developer tools console
An even easier option
As most of the advanced users already know, one of the single most useful tool for web application developer is Firebug, which allows the developer to send message to its log console. Since GI 3.7.1, there is a standard "Console" log handler defined for GI log messages. There's no configuration changes needed to be done on the logger.xml file, simply opening Firebug or IE developer tool will show you the log messages logged by jsx3.log() (or your own jsx3.util.Logger instance)
GI Matrix scroll paging
In GI large data set can be displayed within the jsx3.gui.Matrix control which has build in paging mechanism (Paging Model property set to PAGED), which allows for large number of records to be displayed by showing only a subset of records on screen (the page). Other records that are not visible are left un-rendered off the page. To see these off-page records you must scroll through the Matrix Table. This is where the magic of Matrix control happens, as you scroll, the on screen view is updated with new "pages" of records while the previous pages are moved off screen. This means that only the number view panel records (jsxrowsperpanel) * the number of paint queue size (jsxpaintqueuesize) are rendered at any one time. This allows a good performant Matrix table that can handle very large dataset of records.
Source for this sample here
Paging buttons/links or Scroll paging?
A very common Matrix list table question that comes up is how to create a forward and back button/links navigatable 'page' list table, a virtual scrolling mode that loads in data as needed. Why do users want button/links navigation over scrolling? Answers given range from 'Well, I like paging buttons better than scrolling', to 'It's a requirement of this app' ... or a few other reasons along those lines. However, it is most likely that the reason for people to like 'link/button paging' over scrolling comes down to familiarity.
In a traditional JSP/ASP/PHP driven website, you couldn't display huge amounts of data without some sort of paging mechanism on server side that just showed a single page in the browser at a time. It iscommon to see a data view where a forward or next button would post some page position data to the server, and the server would send back a completely new HTML view of the data. So the reason I think user or developer would ask for a 'button paging' version of the jsx3.gui.Matrix comes down to what they know. People are used to clicking forward and back buttons to swap in new data, even through in a real desktop application the ability to scroll through a set of data is more natural, the web users (or at least the web developer believes that they) are conditioned to want clickable 'pages'.
In the following section, I will show how button navigation paging can be done with the jsx3.gui.Table control. While the same can be done for jsx3.gui.Matrix, there's a couple reasons I don't want to do that. First is the the advanced scroll paging mechanism feature build into Matrix makes such excercise a reivention of the wheel (probably less efficient also). Second is the fact that Matrix build in support for edit mask, multiple paging model, hiearchical display, etc. means that it is not suited for a simple usage such as list table view.
Implementing paging filter in jsx3.gui.Table XSL
In GI 3.8, couple paging filter parameters are added into the default jsx3.gui.Table XSL. They are jsxmaxinclusive and jsxmininclusive . By setting these XSL parameters on the Table, you can control the record rows to be displayed.
table.setXSLParam("jsxmininclusive", start ) table.setXSLParam("jsxmaxinclusive", end ); table.repaint();
Following is an example implmentation of button+combo select controlled paging table

The "<" and ">" buttons use the page index from the combo select box to move to previous or next page
samplePaging.gotoPane = function (index) {
var table = samplePaging.APP.getJSXByName("table");
var rowsPerPage = this.getRowsPerPage();
var start = index*rowsPerPage;
jsx3.log(start);
var end = start+rowsPerPage;
table.setXSLParam("jsxmininclusive", start )
table.setXSLParam("jsxmaxinclusive", end );
table.repaint();
}
The combo select box is generated by calculating the number of pages needed to display all the rows by dividing the "rows per page" (the first textbox value) by the total number of records in the Table ( using table.getXML().selectNodes("//record").size() ).
samplePaging.initMax = function() {
var table = this.APP.getJSXByName("table");
if (table) {
samplePaging.max = table.getXML().selectNodes("//record").size();
samplePaging.maxPage= samplePaging.max/this.getRowsPerPage();
}
}
// called in onAfterDeserialize of Table
samplePaging.init = function (indexBox) {
this.initMax();
var max = this.maxPage ? this.maxPage : 1;
if (indexBox) this.indexBox = indexBox; else return;
indexBox.clearXmlData();
for (var i=0; i <= max; i++) {
var rec = {};
rec.jsxid = i;
rec.jsxtext = i;
indexBox.insertRecord(rec);
}
}
To handle user changes of "rows per page", the textbox Change event triggers a recalculation of the combo box index selector.
Download the sample project here
I am excited to announce that nightly builds of GI 3.9 now include the functionality to load the GI runtime and your application from different domains. This will allow you more flexibility when deploying your GI application and open up some exciting new possibilities, such as:
- Hosting the GI runtime on a CDN (content delivery network) for improved load times. Feel free to test it out on this build on our Amazon CDN: http://cdn.generalinterface.org/gi/3.9.0a440/.
- Using a single runtime installation for several GI applications, even ones hosted on different domains. Each app will use the GI runtime saved in the browser's cache if the user has visited just one of the other applications.
- Including GI applications in blogs and other CMS's where hosting files is not permitted.
- Allowing a system administrator to install a single instance of the GI runtime to be shared across an organization, simplifying application deployment for users.
The improvement is described in JIRA issue GI-681.
GI provided two main challenges when implementing this new cross-domain functionality. The first was its reliance on XML/XSL data files. As we all know there is no way of loading arbitrary data, such as XML, across domains with the typical browser security settings. One of the simplest workarounds to this problem relies on the fact that JavaScript can be loaded across domains. JSONP and similar techniques essentially turn a data file into a JavaScript file with one statement. The statement calls a callback function, passing in the file's data as a parameter.
This is the technique that I used for GI. However, JSONP typically uses a server to generate the JavaScript code from data hosted on the server. GI is a server-less architecture so I had to come up with another implementation. What I did was to augment the GI build process so that every XML or XSL file in the GI runtime is compiled and copied to a JSONP-like JavaScript file. For example, the file JSX/locale/messages.xml,
<data jsxnamespace="propsbundle" locales=""> <locale> <!-- jsx3.lang.ClassLoader --> <record jsxid="boot.env_reset" jsxtext="Error redefining JSX environment parameter {0} from ''{1}'' to ''{2}''."/> <record jsxid="boot.class_err" jsxtext="Could not load class {0}."/> <record jsxid="boot.class_ex" jsxtext="Error loading class {0}: {1}"/> <record jsxid="boot.class_undef" jsxtext="Loaded JavaScript file {0} but class {1} was not defined."/> ...
becomes
jsx3.net.Request.xdr( "jsx:/locale/messages.xml", "<data jsxnamespace=\"propsbundle\" locales=\"\">\n\n <locale>\n <record jsxid=\"boot.env_reset\" jsxtext=\"Error redefining JSX environment parameter {0} from ''{1}'' to ''{2}''.\"/>\n <record jsxid=\"boot.class_err\" jsxtext=\"Could not load class {0}.\"/>\n <record jsxid=\"boot.class_ex\" jsxtext=\"Error loading class {0}: {1}\"/>\n <record jsxid=\"boot.class_undef\" jsxtext=\"Loaded JavaScript file {0} but class {1} was not defined.\"/>\n ... ");
This process is completely automated by the build process, which uses the new JsEncodeTask task to compile the XML/XSL resources. By default it is turned off since it would lead to a certain amount of code and data bloat. To enable it you must set the build.gi.xd build property to true, either in build/user.properties or on the command line,
$> ant -Dbuild.gi.xd=true
All this allows the GI runtime to be loaded from a domain other than the one hosting the GI launch page. But what if you want to host your application on a separate domain as well? You'll need to process the XML and XSL resources in your application in a similar manner. Luckily, the same Ant task that GI uses is available for you to use on your own application. In addition, there is a command line interface for the encoder that comes with the source distribution of GI.
This is how to use the command line interface:
$> cd WORKSPACE/JSXAPPS/myApp $> find . -name "*.xml" -or -name "*.xsl" | xargs \ sh GISRC/build/tools/bin/jsencode.sh -user ../
The second significant challenge was the synchronous loading of resources. The "JSONP" technique for cross-domain data access only works asynchronously. Historically, GI has loaded many things synchronously. However, over the past few releases and especially with GI 3.7 and the debut of the AMP architecture, GI has become less and less dependent on synchronous loading. At this point the framework does not require synchronous loading at all and an app as complex as Builder can load completely asynchronously.
Unfortunately GI still exposes several APIs that rely on synchronous loading. These APIs are still available for developers to use in their applications. However, they will break if you try to deploy your application cross-domain using the jsencode.sh tool. Examples of synchronous APIs are:
- jsx3.require()
- jsx3.net.Request.send() (when open() is called with bAsync=false)
- jsx3.app.Model.load()
- jsx3.xml.Document.load()
- jsx3.app.Cache.getOrOpenDocument()
- jsx3.lang.ClassLoader.loadJSFileSync()
- XML URL (of jsx3.xml.Cacheable) when XML Async is false
- Persistence = Referenced (not Referenced Async)
I expect that the trickiest synchronous API to avoid will be jsx3.require, aka "dynamic class loading." Even if you don't use this method directly your application may depend on it. When you load a component file that contains objects of classes that aren't loaded, those classes are loaded synchronously so that Model.load or Model.loadXML can return synchronously.
There are several possible workarounds to synchronous class loading:
- Use a custom build of GI that pre-loads all GI classes that the application uses
- Use the AMP architecture and plug-in pre-requisites to declare the class requirements of each plug-in
- Use the new 3.9 method jsx3.requireAsync()
I am still working on new APIs to 3.9 in order to make it easier to write a completely asynchronous app. Async loading offers more benefit than just cross-domain loading; it is also much more performant than sync loading. Stay tuned for more improvements before 3.9 goes GA.
Decomposing and tuning a slow performing component load
User complains that their component loading function performs poorly. Following is the code fragment used when a tab containing grid data is shown.
function loadMyUniverseData(){
tabActiveNow = "myUniverseAreas";
changeCustomize(); // To change the customize button properties - located in same file
changeCreateBusareaButton(); // To change the create universe areas button properties
var universeAreasObj = CAD.getJSXByName("universeAreas");
var myUniverseAreasObj = CAD.getJSXByName("myUniverseBlock");
var commonBlockObj = CAD.getJSXByName("universeBlockChild");
var doneObj = myUniverseAreasObj.adoptChild(commonBlockObj);
CAD.getJSXByName("myUniverseBlock").repaint();
//CAD.getJSXByName("universeAreas").repaint();
var topic = "DreamData";
var recordCount = 0;
var responseXML = "";
sqlStatement = CAD.getCache()getDocument('myUniverseData_xml' );
recordCount = sqlStatement.selectNodes("//record").getLength();
if(recordCount <= 0) {
var query = "http://mystage.example.com/AUD/giAuditDB/getDataFromRPTServlet?frmWhere=MyUniverse"
var objRequest = new jsx3.net.Request();
objRequest.open('GET',query);
objRequest.send();
responseXML = objRequest.getResponseText();
} else {
responseXML = sqlStatement;
}
var objResponseXMLBusArea = new jsx3.xml.Document();
objResponseXMLBusArea.loadXML(responseXML);
var objRecord = CAD.getCache()setDocument('myUniverseData.xml', objResponseXMLBusArea );
objRecord = CAD.getCache()setDocument('myUniverseData_xml', objResponseXMLBusArea );
var objRecord = CAD.getCache()setDocument('commonUniverseData.xml', objResponseXMLBusArea );
objRecord = CAD.getCache()setDocument('commonUniverseData_xml', objResponseXMLBusArea );
recordCount = 0;
recordCount = CAD.getCache()getDocument('commonUniverseData_xml').selectNodes("//record").getLength();
CAD.getJSXByName("blkMessage").setText(recordCount + " records in set. ").repaint();
CAD.getJSXByName("grid" + topic).repaint();
CAD.getJSXByName("SearchFilter").repaint();
}
Analysis and Suggestions
There are several reasons why switching tabs is taking so long in this code
1. Uneccessary repaint
First, when you adopt a control from one parent to another, there is no need to call repaint. This is handled automatically for you by the system. Any call to repaint, therefore, would be inefficient.
2. Blocking javascript
Next, it is important to note that the user's perception of performance is often tantamount to the actual performance. In my case, I took your existing function...and modified it by splitting it up into three separate functions:
// this function is called when clicked on "My Universe Areas" tab function loadMyUniverseData(){ window.setTimeout(function() { loadMyUniverseDataDelay1(); },0); } function loadMyUniverseDataDelay1() { // To change the customize button properties - located in same file changeCreateBusareaButton(); changeCustomize(); // To change the create universe areas button properties var myUniverseAreasObj = CAD.getJSXByName("myUniverseBlock"); var commonBlockObj = CAD.getJSXByName("universeBlockChild"); var doneObj = myUniverseAreasObj.adoptChild(commonBlockObj); window.setTimeout(function() { loadMyUniverseDataDelay2(); },0); } function loadMyUniverseDataDelay2() { var recordCount = 0; var responseXML; var sqlStatement = CAD.getCache()getDocument('myUniverseData_xml' ); recordCount = sqlStatement.selectNodes("//record").getLength(); if(recordCount <= 0) { var query = "http://mystage.example.com/AUD/giAuditDB/getDataFromRPTServlet?frmWhere=MyUniverse" var objRequest = new jsx3.net.Request(); objRequest.open('GET',query); objRequest.send(); responseXML = objRequest.getResponseText(); } else { responseXML = sqlStatement; } var objResponseXMLBusArea = new jsx3.Document(); objResponseXMLBusArea.load(responseXML); CAD.getCache().setDocument('myUniverseData_xml', objResponseXMLBusArea ); CAD.getCache().setDocument('commonUniverseData_xml', objResponseXMLBusArea ); recordCount = objResponseXMLBusArea.selectNodes("//record").getLength(); CAD.getJSXByName("blkMessage").setText(recordCount + " records in set. ",true); CAD.getJSXByName("grid" + topic).repaint(); CAD.getJSXByName("SearchFilter").repaint(); }
Please note that I also removed the unnecessary repaints and duplicate statements in your original function, but the code flows essentially the same, just spread out across three functions.
3. Avoid Synchronous Request
- 3. Synchronous request - Finally, make sure that when you query for your data that that you do so asynchronously. In your case I see that the call is synchronous:
var objRequest = new jsx3.net.Request(); objRequest.open('GET', query); objRequest.send();
Because your call is synchronous, if there is any delay in the server's response, the entire browser will "lock-up" until the server responds. In a situation like this, what looks like a slow repaint on the front-end is actually a delay in the server's response. If the server takes 5 seconds to respond, you've just added five seconds to the amount of time it takes to switch from one tab to another, effectively confusing the user who will then click the tab multiple times, wondering why the tab won't change.
Here is a sample for how to create an asynchronous call.
Note that I have a handler function (myHandler) that is subscribed to the asynchronous event:
window.myHandler = function(objEvent) {
//get a handle to the http control -- it's the 'target' for this event
var objHttp = objEvent.target;
jsx3.log(objHttp.getResponseText());
// create new document using response text
sqlStatement = new jsx3.xml.Document().loadXML(objHttp.getResponseText();
}
var objHttp = new jsx3.net.Request();
objHttp.open("GET", query, true);
objHttp.subscribe(jsx3.net.Request.EVENT_ON_RESPONSE, myHandler);
objHttp.send();
4. Further optimizations
- a) For loading XML you could even use the Cache.getOrOpenAsync() method.
- b) Reduce the number of calls to set and get XML document
- c) Use XML Bind property to auto-repaint when new document is set to the jsx3.app.Cache.
- d) For Matrix, 1) use repaintData() 2) don't manually repaint at all, use XML Bind.
function loadMyUniverseDataDelay2() {
var query = "http://mystage.example.com/AUD/giAuditDB/getDataFromRPTServlet?frmWhere=MyUniverse";
// renamed sqlStatement to sqlDocument
var sqlDocument = CAD.getCache().getOrOpenAsync(query, 'myUniverseData_xml' );
var recordCount = sqlDocument.selectNodes("//record").getLength();
CAD.getCache().setDocument('commonUniverseData_xml', sqlDocument);
CAD.getJSXByName("blkMessage").setText(recordCount + " records in set. ",true);
//CAD.getJSXByName("grid" + topic).repaintData(); // XML bind will repaint on new cache doc
//CAD.getJSXByName("SearchFilter").repaint(); // Did the filter change? don't repaint if you don't have to.
}
One of the limitation of the Select/Combo component is that it becomes slower to render and harder to navigate (scroll down) a very as the number selection items grows large.
This sample SelectBox Add-in code (Thanks to Luke Birdeau) shows an add-in component created from a composite of TextBox, Dialog and Matrix.
Some of the FAQ that user have on XML resource are: Is there a build in way of doing asynchronous loading and caching of XML document? , How do I fetch xml document into the application cache asynchronously ? and How can I make sure that what I load myself will be used by GI instead loading again from the web server?
Well, quick answer is : Yes, use
myProject.getCache().getOrOpenAsync("component/file.xml")
Asynchronous Loading
- Cache documents can be loaded asychronously with the getOrOpenAsync()_method. This method returns the corresponding document synchronously if it already exists in the cache. If the document does not exist in the cache, then it is loaded asynchronously and the method returns a placeholder document. The namespace URI of this placeholder document isCache.XSDNS_ and its root node name is "loading".
Method getOrOpenAsync(strURL, strId, objClass) Asynchronously loads an xml document and stores it in this cache.
Parameters:
- strURL {String | jsx3.net.URI} – url (relative or absolute) the URI of the document to open.
- strId – the id under which to store the document. If this parameter is not provided, thestrURL parameter is used as the id.
- objClass {jsx3.lang.Class} – jsx3.xml.Document (default value) or one of its subclasses. The class with which to instantiate the new document instance.
Returns:
{jsx3.xml.Document} – the document retrieved from the cache or a placeholder document if the document is in the process of loading asynchronously.
Since:
3.5
