General Interface is an open source project hosted by the Dojo Foundation

Recent Posts

Last changed Jul 13, 2012 09:58 by Darren Hwang
Labels: custom, addin, component

Introduction

Creating a completely new component for GI can be a big task. However most of the difficulties lies in the need to provide GI Builder integration. User want to be able to drag and drop the new component just like any other existing ones. Without providing GI Builder integration, it's actually not that hard to create a new component as a GI Addin that can be reused across projects.

Custom Button

Here we have an example of a Round corner button that makes use of round corner background images. This "button" was created using JQuery and was meant to convert Anchor element with a given class (fvbutton) into a button.

The following function inserts 3 span elements (one for a left side round corner image, one for the middle background image x-repeated, and one for the right side round corner) for the given anchor elm JQuery element.


function buttonify(elm) {
  var label = elm.text();
  elm.html('<span class="left"></span><span class="center"><span>' + label + '</span></span><span class="right"></span>');
  var a = elm.filter("a");
  if (a.attr("href") == "" || a.attr("href") == "#")
    a.attr("href", "#" + label.replace(/[^w]/g, ""));

  var fct = elm[0].onclick;
  if (fct)
    elm[0].onclick = function(event) {
      if ($(this).hasClass("disabled"))
        return false;
      else
        return fct.call(this, event);
    };
  else 
    elm.click(function(e) {
      if ($(this).hasClass("disabled")) {
        e.stopPropagation();
        return false;
      }
    });

  // allow space key to invoke onclick action (like a submit button would do by default)
  elm.keydown(function(e) {
    if (e.keyCode == 32 && !e.shiftKey && !e.altKey && !e.ctrlKey)
      elm.click();
  });
}

Wrapping the custom button for GI

Since we have a basic button type in GI already it makes sense to extend this class and provide our own paint method, which is the function that generates the HTML to be inserted into the DOM for all jsx3.gui classes.

jsx3.require( "jsx3.gui.Block", "jsx3.gui.Form", "jsx3.gui.Button");

jsx3.Class.defineClass("pf.Button", jsx3.gui.Button, [jsx3.gui.Form], function(Button, Button_prototype) {
  
  var Block = jsx3.gui.Block,
      Form = jsx3.gui.Form,
      Interactive = jsx3.gui.Interactive;

  Button.DEFAULTCLASSNAME = "fvbutton";
  
  Button_prototype.paint = function() {
    this.applyDynamicProperties();

    var bEnabled = this.getEnabled() == Form.STATEENABLED;

    var strEvents = bEnabled ? this._renderHandler("click", "_ebClick") : "";
    //render custom attributes
    var strAttributes = this.renderAttributes(null, true);

    var attr = 'id="' + this.getId() + '"' + this.paintIndex() + (this.jsxtip ? ' title="' + this.jsxtip + '"' : '') +
        ' class="fvbutton' + (bEnabled ? "" : " disabled") + (this.jsxdefault ? " default" : "") + '"' + strEvents + strAttributes;
    var styles = this.paintDisplay() + this.paintMargin();
  
    return '<a href="#" ' + attr + ' style="' + styles + '">' + this.getText() + '</a>';
  };
      
  Button_prototype.onAfterPaint = function(objGUI) {
    buttonify($(objGUI));
  };
  
  Button_prototype.paintMargin = function() {
    var m = this.getMargin();
    return m ? "margin:" + m.replace(/\b(\d+)\b/g, "$1px") + ";" : "";
  };
  
  Button_prototype.paintDisplay = function() {
    //TO DO: the following logic is wrong...change force explicit none???
    var d = this.getDisplay();
    return (jsx3.util.strEmpty(d) || d == Block.DISPLAYBLOCK) ? "" : "display:none;";
  };
  
  Button_prototype.paintIndex = function(intIndex) {
    if (intIndex == null) intIndex = this.getIndex();
    // HACK: see IE's jsx3.html.isFocusable()
    return (intIndex != null && this.jsxenabled != 0) ? ' tabindex="' + intIndex + '" jsxtabindex="' + intIndex + '"' : '';    
  };

 Button_prototype._ebClick = function(objEvent, objGUI) {
    if (objEvent.leftButton() && objEvent.isMouseEvent())
      this.doEvent(Interactive.EXECUTE, {objEVENT:objEvent});
  };
  
  Button_prototype.doExecute = function(objEvent) {
    this._ebClick(objEvent);
  };
   
  Button_prototype._renderHandler = function(eventType, strMethod) {
    var eventHandler = "on" + eventType;
    var bridgeEvent = "jsx3." + Interactive._EB + "(event,this,'" + strMethod + "',0);";
    return " " + eventHandler + '="' + bridgeEvent + '"';
  };

});

Since the JQuery version of the button uses a custom CSS and image files, we will need to provide them in our project.

Making it into a shareable Addin

To make it into an Addin for GI we need to change the project config.xml file slightly to give it the Addin properties including : name, description, long_description, namespace, and addin.

More information here:
Deploying the custom Add-in

<?xml version="1.0" encoding="utf-8"?>
<data>
  <record jsxid="version" type="string">1.0</record>
  <record jsxid="jsxversion" type="string">3.9</record>
  <record jsxid="id" type="string">pf.Button</record>
  <record jsxid="name" type="string">pf.Button</record>
  <record jsxid="description" type="string">Round corner button</record>
  <record jsxid="long_description" type="string">This is an addin with round corner button</record>
  <record jsxid="namespace" type="string">pf.APP</record>
  <record jsxid="addin" type="string">pf.APP</record>
  <record jsxid="includes" type="array">
    <record jsxid="1" type="map">
      <record jsxid="id" type="string">logic_js</record>
      <record jsxid="type" type="string">script</record>
      <record jsxid="load" type="number">1</record>
      <record jsxid="src" type="string">js/jquery-1.7.1-min.js</record>
    </record>
    <record jsxid="2" type="map">
      <record jsxid="id" type="string">pfbutton_js</record>
      <record jsxid="type" type="string">script</record>
      <record jsxid="load" type="number">1</record>
      <record jsxid="src" type="string">js/pfbutton.js</record>
    </record>
    <record jsxid="3" type="map">
      <record jsxid="id" type="string">button_css</record>
      <record jsxid="type" type="string">css</record>
      <record jsxid="load" type="number">1</record>
      <record jsxid="src" type="string">jss/button.css</record>
    </record>
  </record>
  <record jsxid="bodyhotkeys" type="boolean">false</record>
  <record jsxid="onunload" type="string"/>
</data>

Adding the component

Since we are sub-classing jsx3.gui.Button, we save some effort in creating the palette model and event palette model that would be needed to drag and drop the custom component. Instead we can drop in a regular jsx3.gui.Button, then go to the component XML editor and change the object type to "pf.Button".

Download the Addin

Download it here

Unzip the content to your "workspace/Addins" directory

Posted at Feb 08, 2012 by Darren Hwang | 0 comments
Last changed Oct 04, 2011 11:06 by Darren Hwang
Labels: firefox, builder

GI 3.9.1 does not officially support Firefox 5. [*Edit* _This applies to Firefox 5+ (5,6,7, and probably 8+_)] However...

As far as we know GI 3.9.1 does run on Firefox 5. However there's been report of issue with GI Builder that turn out to be related to the old same origin policy issue. To run GI Builder on Firefox 5, you must make sure that the security.fileuri.strict_origin_policy flag is set to false in the about:config tab.

NOTE
Only change this flag on your GI Firefox profile and not your web-browsing default Firefox profile. For instruction on how to create your custom GI Builder profile, See http://support.mozilla.com/en-US/kb/Profiles

You will no longer be able to run webservice call directly from inside Builder due to this change in Firefox 5.

See the security note https://developer.mozilla.org/En/Using_XMLHttpRequest#Security

Firefox 5 note
Versions of Firefox prior to Firefox 5 could use netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); to request cross-site access. This is no longer supported, even though it produces no warning and permission dialog is still presented.

The recommended way to enable cross-site scripting is to use the Access-Control-Allow-Origin HTTP header in the response to the XMLHttpRequest.

Posted at Jun 27, 2011 by Darren Hwang | 0 comments
Last changed Apr 27, 2011 18:27 by Darren Hwang
Labels: firefox

While you can run GI 3.9 on Firefox 4, you must get pass the browser support warning dialog. As far as we can tell, there is no compatibility issue for GI 3.9 and Firefox 4. So, you can get pass this warning dialog by adding a deployment parameter

Deployment Parameters

jsx_browsers. Overrides the default set of supported browsers. The format of this parameter is

bt={allow,warn}[,...]

where bt is the browser type returned by the getType() method in the jsx3.lang.ClassLoader class. See jsx3.lang.ClassLoader in General Interface API Reference.

allow launches the application in the browser.
warn provides a warning for unsupported browsers and allows the user to proceed

For example, to show a warning in Internet Explorer 6 and allow Firefox 4, modify the application launch page as follows:

jsx_browsers="ie6=warn,fx4=allow"
 document.write('<script type="text/javascript" src="JSX/js/JSX30.js"
 jsxapppath="' + appPath + '" jsx_browsers="ie6=warn,fx4=allow" ><'+'/script> ');

The next version of GI should have Firefox 4 support enabled by default, so you will not need to do this. However, if you can't wait this is how you do it.

Posted at Apr 01, 2011 by Darren Hwang | 1 comment
Last changed Apr 01, 2011 11:42 by Darren Hwang
Labels: crossdomain, cross-origin, google-chrome, file-url

You get a strange error on Google chrome when launching GI application from file URL (when you open the launch.html directly from filesystem)
that says something like

"Invalid character at column 1 character 1 of logger.xml"

If you open the Developer Tool and look at the Console output you will see that it is throwing all these Error for each XML file resource request by GI

"Cross origin requests are only supported for HTTP."

Turns out that similar to Firefox/Mozilla strict origin policy Google Chrome doesn't allow you to load resource from different paths

So for test purpose you can launch Chrome with this flag to get around this restriction

google-chrome --allow-file-access-from-files

or

chrome.exe --allow-file-access-from-files

Posted at Feb 16, 2011 by Darren Hwang | 3 comments
Last changed Mar 21, 2011 18:52 by Darren Hwang
Labels: matrix, editmask

This format handler will style a Select mask column with the down arrow indicator. This replaces the default @lookup format handler for a select mask column

    var selectFormat = function(objDiv, strCDFKey, objMatrix, objMatrixColumn, intRowNumber, objServer) {
        var objMask = objMatrixColumn.getEditMask();
        if (objMask != null && typeof(objMask.getRecordNode) == "function") {
          var objNode = objMatrix.getRecordNode(strCDFKey);
          if (objNode) {
            var strValue = objMatrixColumn.getValueForRecord(strCDFKey);
            var lookupNode = objMask.getRecordNode(strValue);
            objDiv.innerHTML = jsx3.util.strEscapeHTML(
                lookupNode ? lookupNode.getAttribute("jsxtext") : (strValue != null ? strValue : ""));
          }
          objDiv.style.backgroundImage = "URL(JSX/images/select/arrowmask.gif)";
          objDiv.style.backgroundRepeat= "no-repeat";
          objDiv.style.backgroundPosition = "top right";
        }
      };

Note:

Using Format Handler is very Javascript intensive, especially for large number of rows(and or columns). Do not use this if you need to support IE6/7. You've been warned.

http://www.generalinterface.org/docs/display/GIDOCS/Content+Formatting+and+Localization#ContentFormattingandLocalization-ww1852740

Posted at Jan 19, 2011 by Darren Hwang | 0 comments
Last changed Feb 02, 2011 23:24 by Darren Hwang

Question came up on the forum regarding how to auto-populate a selection/combo control. The idea is to trigger a service request that will fetch the content for the select control.

To make use of onAfterDeserialize more "granular"

  1. ctrl+n to create a new canvas
  2. go back to the original canvas with your component
  3. open the Component Hiearchy palette and locate your component
  4. drag your component to the new canvas tab (the [ unsaved ] tab)
    The [ unsaved ] tab will show at this point
  5. drop your component into the new canvas
  6. save this new cavas containing only a single component as its own autoPopulateSelect.xml file
  7. enter your custom onBeforeDeserialize/onAfterDeserialize code, save and reload.

You've just created a single component serialization file with its own onBeforeDeserialize / onAfterDeserialize logic
To use this new custom component file

  1. Select the container block in original canvas in the Component Hiearchy palette and right click
  2. Choose Import Asynch to load the autoPopulateSelect.xml
About onAfterDeserialize and onBeforeDeserialize
Edit added before/after code. onAfterDeserialize is safer, but on onBeforeDeserialize is useful for things like manipulating the serialization file content before passing it to be rendered which is faster than modifying things on screen after the fact.
On Component Ready
Sometimes the component you are trying to access may not be ready to be manipulated inside onAfterDeserialize. Component such as Matrix control uses the GI asynch queue to provide better responsiveness in its operations. This can cause the component to be unavailable. In such case, you can place your code inside a jsx3.sleep() call to add it to the asynch queue and be called when it is ready.

Configuring the initial state of components is one of the use case for using onBeforeDesrialize. By modifying the serialization file content before the compoents are actually rendered. This helps optimize load performance by avoiding a double "paint" that can happen when using onAfterDeserialize, which re-render(repaint) the component after it's already rendered.

The following sample code will prompt you to choose an initial state of enabled=1 or disabled=0 for a Select/Combo control

<serialization jsxversion="3.9">
<name>
Layout - Top/Over
</name>
<icon>
images/prototypes/layout-over.gif
</icon>
<description>
2 top-over-bottom panes in an adaptive layout grid.
</description>
<onBeforeDeserialize>
var t = prompt(&quot;enable = 1, disable = 0&quot;); jsx3.log(t); var node = objXML.selectSingleNode(&quot;//jsx1:object[@type='jsx3.gui.Select']/jsx1:variants&quot; ); node.setAttribute(&quot;jsxenabled&quot;, &quot;&quot;+t);
</onBeforeDeserialize>
<onAfterDeserialize>
</onAfterDeserialize>
<object type="jsx3.gui.LayoutGrid">
<variants jsxrelativeposition="0" jsxleft="0" jsxtop="0" jsxoverflow="2"/>
<strings jsxname="layoutRows" jsxrows="100,*" jsxwidth="100%" jsxheight="100%"/>
<object type="jsx3.gui.Block">
<variants jsxoverflow="2"/>
<strings jsxname="pane1" jsxwidth="100%" jsxheight="100%"/>
<dynamics jsxborder="@Outset"/>
<object type="jsx3.gui.Select">
<variants jsxenabled="1" jsxwidth="150" jsxheight="18" jsxtype="1" jsxxmlasync="1"/>
<strings jsxname="combo" jsxxmlurl="jsx:///xml/sample.xml" jsxmargin="0 4 0 0" jsxdefaulttext="Select" jsxvalue="" jsxdisable=""/>
<events jsxchange="var x = this.getXML(); "/>
</object>
</object>
<object type="jsx3.gui.Block">
<variants jsxoverflow="1"/>
<strings jsxname="pane2" jsxwidth="100%" jsxheight="100%"/>
<dynamics jsxborder="@Outset"/>
</object>
</object>
</serialization>
Posted at Jan 18, 2011 by Darren Hwang | 0 comments
Last changed Jan 18, 2011 17:28 by Darren Hwang
Labels: matrix

Usage scenario

We have a requirement to display 3 different type of image buttons under a single column. Normally, you can only use one edit mask control under a column. So you cannot specify 3 different image button under a single column. This is one of the use case where custom column formatter comes in handy.

By using a custom column formatter we can generate 3 different type of image button depending on a record attribute (status).

Matrix Custom Format Handler
  instance.formatScheduleButton = function (objDiv, strCDFKey, objMatrix, objMatrixColumn, intRowNumber, objServer) {
    var status = objMatrix.getRecordNode(strCDFKey).getAttribute("status");

    var action = "Publish";
    if (status) {
      if (status == "started") {
        action = "Stop";
      } else if (status == "stopped") {
        action = "Start";
      }
    }
    var objPL = objMatrix.getAncestorOfType(pf.ProjectList);
    var buttonImage = KLASS.IMAGE_BUTTON_PATH + "Button_" + action + KLASS.IMAGE_EXT ;
    var sGetter = "jsx3.GO('" + objPL.getId() + "')";
    var handlers = " onmouseover=\"" + sGetter + ".onImageMouseover(event, this, '" + action + "')\" " +
                   " onmouseout=\"" + sGetter + ".onImageMouseout(event, this, '" + action + "')\" " +
                   " onmousedown=\"" + sGetter + ".onImageMousedown(event, this, '" + action + "')\" " +
                   " onclick=\"" + sGetter + ".onImageClick(event, this, '" + strCDFKey + "', '" + action + "')\" ";

    var strImageButton = "<span id='pfib_" + strCDFKey + "' " + " class='jsx30imagebutton' " + handlers + ">"
        + "<img width='80' height='22' src='" + objPL.getPlugIn().resolveURI(buttonImage) + "'/>" + "</span>";

    //jsx3.log(strImageButton);
    objDiv.innerHTML = strImageButton;

  };

The onmouseover, onmouseout and onmousedown DOM event handler are implemented to display the HOVER, Normal and DOWN images for each 3 types of buttons. onclick is the click event handler and there we decouple the view component from the model by using custom event publishing

Image button event handlers
  instance.onImageMouseover = function(evt, elm, action, imgExt) {
    var ext = (!imgExt) ? KLASS.IMAGE_EXT : imgExt;
    var imagePath = KLASS.IMAGE_BUTTON_PATH + "Button_" + action + "_rollover" + ext;
    elm.childNodes[0].src = this.getPlugIn().resolveURI(imagePath);
  };


  instance.onImageMouseout = function(evt, elm, action, imgExt) {
    var ext = (!imgExt) ? KLASS.IMAGE_EXT : imgExt;
    var imagePath = KLASS.IMAGE_BUTTON_PATH + "Button_" + action + ext;
    elm.childNodes[0].src = this.getPlugIn().resolveURI(imagePath);
  };


  instance.onImageMousedown = function(evt, elm, action, imgExt) {
    var ext = (!imgExt) ? KLASS.IMAGE_EXT : imgExt;
    var imagePath = KLASS.IMAGE_BUTTON_PATH + "Button_" + action + "_down" + ext;
    elm.childNodes[0].src = this.getPlugIn().resolveURI(imagePath);
  };


  instance.onImageClick = function(evt, elm, project, action) {
    LOG.debug("action=" + action + ", project=" + project);
    if (action == "Publish")
      this.getServer().publish({subject:"project.list.publish", name: project, status:action});
    else
      this.getServer().publish({subject:"project.list.schedule", name: project, status:action});
  };
Posted at Jan 18, 2011 by Darren Hwang | 0 comments
Last changed Apr 27, 2010 15:23 by Jesse Costello-Good

Following up on my earlier post I want to go into much more detail on creating a build of GI that is optimized for a particular GI application. We highly recommend that you create an optimized build of GI for your critical applications. Creating an optimized build can dramatically shorten the initial load time of your application. I've seen 20 second load times reduced to 6 seconds just by deploying an optimized build.

Background

In the directory on your computer where you installed GI you have several things. The "GI runtime" consists of JSX/, jsx3.gui.window.* and logger.xml. The GI runtime is the platform that a GI application runs on. GI Builder is one such application that runs on the GI runtime. GI Builder consists of the files GI_Builder/, GI_Builder.* and shell.* from the GI installation directory.

When you deploy an application you copy the GI runtime and your application resources to a server. Your application runs on this copy of the GI runtime. If you copy the runtime from the GI installation directory then your application is running on the same runtime as is GI Builder when you run GI Builder on your computer.

There's no reason that GI Builder and your application have to run on the exact same runtime. In fact there are many reasons why you would want to have them running on different runtimes. For example, you probably want to run GI Builder on the "debug"
runtime so that you can see more warnings while you are developing your application. But when you deploy your application you want it to run on a runtime that is as small and fast as possible.

This article will show you how to compile the best runtime for your deployed application. This is the runtime that you will want to upload with your application when you deploy it on a public HTTP server.

Load Performance

Load performance in a web application running over HTTP comes down to a few simple principles:

  1. Load as little as possible
  2. Load what you need in as few HTTP requests as possible
  3. If requesting multiple resources, load them in parallel as much as possible

Recent versions of GI are good at loading runtime and application resources in parallel so you don't have to worry too much about the third point. Creating an optimized runtime build will take care of the first two points.

Sometimes points one and two conflict with each other. For example, part of your application might use jsx3.gui.Matrix but that part is not commonly used. Should you reduce the number of requests by merging jsx3.gui.Matrix with the set of required classes? Or should you load as little as possible up front and load Matrix as needed? There is some art as well as science to resolving these decisions. My usual suggestion is to merge all resources that must load for your application to reach its first interactive state, and nothing else.

Determining What Classes to Load

The GI runtime consists of a number of required classes (e.g. jsx3.lang.*, jsx3.app.*) and many optional classes (e.g. most of jsx3.gui.*). To create the optimal build for your application you need to determine which optional classes are used by your application. To do this, follow these steps:

  1. Make sure you are running Builder on either the source distribution of GI or the standard or debug packages. Running on the max build won't work for these instructions.
  2. Open your application in Builder running on Firefox with the Firebug extension installed, IE8 or Safari 4.
  3. Make sure that your logger.xml contains the following lines:
    <handler name="console" class="jsx3.util.Logger.ConsoleHandler"/>
    ...
    <logger name="bench.jsx3.lang.ClassLoader" level="TRACE"/>
  4. Launch your application from Builder
  5. Open your browser's JavaScript console for the window that contains your running application. You may have to reload your application after you open the console.
  6. Wait for your application to reach its first interactive state, such as the home page or login screen.

At this point you should see a bunch of log messages in the JavaScript console like

2010-04-27 14:18:11.778 bench.jsx3.lang.ClassLoader (INFO) - JSX/js/jsx3/gui/Button.js : js.eval : 10ms

These are the optional GI classes that your application is loading before it reaches its first interactive state. All of these classes should be included in the build of GI optimized for your application. Make a note of the full list of classes. We'll use this list in the next step.

Configuring and Running the Build

Make sure you are set up to make custom builds of GI as described in my earlier post. Create a new properties file in GIHOME/build and call it something like app1-build.properties. This file will contain the parameters for the build optimized for your "app1" application.

Into this properties file add the list of classes you saw in the JavaScript console. So if the entire list of classes was jsx3.gui.Button and jsx3.gui.CheckBox your properties file would like like:

build.gi.includes.jsx = default,jsx3.gui.Button,jsx3.gui.CheckBox

Since you don't need to build Builder or the documentation you can add these lines to speed up the build.

build.gi.includes.jsx = default,jsx3.gui.Button,jsx3.gui.CheckBox

build.gi.docs = false
build.gi.docs.html = false
build.gi.ide = false

To create the build run the following commands.

$ cd GIHOME/build
$ ant clean; ant -propertyfile app1-build.properties

Once you have the build you should run your application on this build. To do this create a launch page with Builder and then hand edit it so that it points to the JSX30.js from your custom build. Make sure to copy the logger.xml file from your Builder installation to the new runtime.

This time when you run your application you should not see any logging messages with subject bench.jsx3.lang.ClassLoader. Repeat the steps in Determining What Classes to Load to make sure that all optional classes that your application uses are included in the optimized build. It may take your several iterations to finalize the list.

Once you have a fully optimized build simply deploy it along with your application to your web host. All the optional classes that your application uses will be baked into jsx.js, meaning that many fewer JavaScript resources must load when your application loads.

Congratulations, you have created an optimized runtime build for your GI application. Remember that every GI application has a unique load profile. You may want to create an optimized build for each of your applications or, if they are similar enough, create just one optimized build. The advantage of having just one build for multiple GI applications is that the runtime will be in the browser cache when the user visits the second and subsequent applications.

Additional Deployment Parameters Affecting Load Time

Here are a few more things you can do to speed up the loading of your application.

Don't load logger.xml

If you are not going to be monitoring the runtime logs of your application you can opt not to load logger.xml. It's just one small file but when your application is loading over HTTP every resource counts. Modify your launch page as follows.

<script src="gi/JSX/js/JSX30.js" jsx_logger_config=""></script>

Don't load messages.xml

The file messages.xml contains the English translations of all system error messages. If you do not load messages.xml you still see errors in the logs but instead of a nice English message you just see a string key. Again, it saves you just one file.

<script src="gi/JSX/js/JSX30.js" jsx_no_messages="true"></script>

Don't load locale.xml

The file locale.xml is required if you are using DateFormat, NumberFormat, DatePicker, TimePicker, Select or any of the Cacheable controls with XML Async = true. If you use none of these you can safely prevent locale.xml from being loaded.

<script src="gi/JSX/js/JSX30.js" jsx_no_locale="true"></script>
Posted at Apr 26, 2010 by Jesse Costello-Good | 0 comments
Last changed Apr 26, 2010 15:31 by Jesse Costello-Good

Introduction

GI developers who want to create high performance GI deployments should take the time to understand and use the suite of build tools that are part of the open source GI project. Only by leveraging the GI build tools will you be able to create applications with the shortest load times and fastest code.

With the source distribution and the build tools you can:

  • Create a build of GI with a load profile optimized for a specific GI application
  • Compile your own API documentation
  • Create an un-obfuscated build of GI for easier debugging
  • Obfuscate, merge and optimize the JavaScript code in your own application
  • Create a build of GI or your own application with a default locale other than English
  • Conditionally include or exclude blocks of JavaScript code
  • Optimize your AMP based application
  • Create a completely automated build and deployment work flow

I'll try to cover most of these use cases in subsequent posts. First, we need to get set up with the GI source code and build tools.

Set up

GI has an Ant-based build process so you need to have a few things installed on your computer to use the build tools:

Ant works on pretty much any operating system. I use Mac OS X Terminal so my examples will look like a Linux prompt. If you are using Windows I recommend installing Cygwin.

Once you have Java and Ant installed you need to get a source distribution of GI. You can download the source distribution of the latest release or grab the code from SVN.

If you are using the latest release ZIP, unzip the contents of the archive. I'll refer to this directory as GIHOME (GIHOME contains logger.xml, JSX/ and several other files).

Your first GI build

The build tools, scripts and configuration files are all contained in the build/ directory. To make your first build, go to that directory.

$ cd GIHOME/build

To make a build of GI with all of the default settings simply invoke Ant.

$ ant
If you see many warnings like "Warning: The encoding 'UTF-8' is not supported by the Java runtime" your Java set up has a XML library that conflicts with the library that GI uses. As a work around always invoke Ant as ant -lib tools/lib/xml/xalan-serializer-2.7.jar.
You may get an out of memory error with Ant's default Java settings. You will probably need to increase Ant's maximum heap size to 512 MB. On most systems you can do this by setting the ANT_OPTS environment variable to -Xmx512m.

The full build process takes about 90 seconds on my machine with the default settings. Later, I'll describe some ways of making the build even faster.

Now, if you look in GIHome/dist/gi you'll see the build of GI that you just created. Open GI_Builder.html in a browser to verify that the build was successful.

Modifying the default build settings

All build settings are contained in the file GIHome/build/build.properties. There are two recommended ways of overriding these default settings. The first is on the command line and the second is with the user.properties file.

To modify a property on the command line when you invoke Ant, add -DpropName=propValue to the command invocation. See the Ant documentation for more information.

To modify a property semi-permanently so that you don't have to add it to the command line each time you invoke Ant, create a file GIHome/build/user.properties. Any properties defined in this file take precedence over those in build.properties.

Don't modify build.properties yourself as this file is owned and maintained by the GI distribution and often changes between GI releases.

As a first example, let's make a build of GI where the jsx3.gui.Matrix class is included in the load profile. Including Matrix in the load profile will speed up the load time of applications that use Matrix. First, invoke the clean target in Ant. This step is usually necessary to prevent errors.

$ ant clean

Next, invoke the default target while overriding the build.gi.includes.jsx build property.

$ ant -Dbuild.gi.includes.jsx=jsx3.gui.Matrix

(To include multiple classes format them as a comma-delimited list.) If you check the build you will see that the Matrix.js file does not exist, meaning that it has been baked into jsx.js, which defines the load profile.

$ ls -la ../dist/gi/JSX/js/jsx3/gui
...
-rw-r--r--   1 jesse  staff  28495 Apr 26 13:55 List.js
-rw-r--r--   1 jesse  staff  28144 Apr 26 13:55 Menu.js
...

Making the build process faster

You can make the build process faster by having it do less. Often when creating a custom build of GI you do not need to compile the API documentation or Builder documentation. To remove these steps from the build process add the following to your user.properties.

build.gi.docs = false
build.gi.docs.html = false

If you don't need to create an internationalized version of GI add the following, which excludes all locales other than en_US.

build.locales.jsx = en, en_US

If you are creating a build of GI for deployment only and not for running Builder, you can exclude Builder from the build by adding the following.

build.gi.ide = false

If I include all these overrides I can get the build time down to about 20 seconds.

More to come

I plan to go into much more detail describing all the advanced things you can accomplish with the GI build tools. Watch this blog for updates.

Posted at Apr 26, 2010 by Jesse Costello-Good | 0 comments
Last changed Jul 16, 2010 18:10 by Darren Hwang
Labels: firefox, builder, strict, origin, policy, security, prompt
Why do I keep getting prompt for "Script from file:// is requesting enhanced abilities that are UNSAFE and could be used to compromise your machine or data."?

GI Builder requires access to the file system, enhanced abilities, to save the files created in a project.

The reason you are repeatly asked even thought you selected the "Remember this decision" checkbox is because there are multiple javascript file that requires enhanced abilities. In Firefox 3, files residing in different folders are consider to be of different origin by the strict origin policy. However the security prompt applies to the entire "file://" URL, which is not accurate. So you get prompted every time you start builder.

Solution

For the security minded, you should create a new developer-only Firefox profile and make the following configurations in this new profile. Security minded users should not use your default profile for this.

First you must have the security.fileuri.strict_origin_policy property disabled by setting it to false. You can do this by going to about:config and add/edit this property.

Next, by selecting "Allow" in the security prompt you create the following settings in the pref.js file

Alternate method
You can also edit Pref.js (or create a User.js, which is the Mozilla recommended way) found under "<user_home>/Application Data/Mozilla/Firefox/Profiles" as shown below.

Add these settings if they do not exist; if these settings exist, edit them as follows:

user_pref("security.fileuri.strict_origin_policy", false);
user_pref("capability.principal.codebase.p0.granted", "UniversalXPConnect UniversalBrowserRead");
user_pref("capability.principal.codebase.p0.id", "file://");
user_pref("capability.principal.codebase.p0.subjectName","");
Posted at Apr 15, 2010 by Darren Hwang | 0 comments
Last changed Apr 15, 2010 13:23 by Darren Hwang
Labels: matrix, disable, edit, mask

First there are two types of edit masks in Matrix (editable grid Matrix control).

The first type is the always-on edit mask such as : button, checkbox, radio button, etc. An always-on edit mask is always displayed without user action.

The second type is the non-always-on edit mask such as : textbox, select, datepicker, etc. These editmask are display only when user focus on the cell.

Always on edit masks

Always on edit mask, such as checkbox and button, are disabled by the jsxdisabled="1" in the CDF record E.g.

  <data jsxid="jsxroot">
  <record jsxid="WEBM" jsxtext="WEBM" open="10" close="1" jsxdisabled="1"/>
  </data>
Non-always on edit masks

Disabling the Non-always-on edit masks means preventing the cell focus from displaying the mask control. You can do so by adding code logic to Before Edit event handler to evaluate to false. For example, set the event property "Before Edit" with following code fragment (follwing sample disables the non-always-on edit mask ,such select/combo.

 if (objCOLUMN.child(0).instanceOf(jsx3.gui.Select)) false;

To disable both the non-always-on and always-on edit mask on the same row and thus disabling the entire row, you can take advantage of the jsxdisalbed record property and use that as the condition. Add the following to the Before Edit event

 if (this.getRecord(strRECORDID).jsxdisabled == 1) false;

Non-always-on edit mask of an entire column can be disabled by setting event property Before Edit (Following code disables the "comboColumn" by evaluating to false in Before Edit event handler).

These sample code fragment goes into the event handler it self, but the code fragment is not part of a function, so should not use return statement. It should simply evaluate to true or false.
if (objCOLUMN.getName() == "comboColumn") 
  false;
else {
  jsx3.log('EVENT: (jsxbeforeedit). Record: ' + strRECORDID);
 }

Follow similar logic to disable edit mask of a cell based on different factors such as the value of the another column. In "Befor Edit" event property, this code

 if (this.getRecord(strRECORDID).status == "Deployed" 
    && objCOLUMN.getName() == "selectMachine" ) 
  false;

will disable the "selectMachine" cell (column) if status cell (column) has the value "Deployed".

Disabling individual always on control

That's nice but what about displaying the always on edit mask, like button and checkbox, as disabled?
For example, you like to display one checkbox in a checkbox column as disabled.

There's only one checkbox mask control object under a checkbox column, so you can't disable just one checkbox in an entire column of checkbox by simply calling checkboxMask.setEnabled(0); after the Matrix is painted. That won't work.

To display (paint) disabled always-on-edit-mask you will have to provide your own format handler. Remember that the format handler allows developer to customize the display of each cell. So here's an example

// Setting this format handler to a Checkbox column will disable the checkbox 
// of the record with jsxid="WEBM" (as an example).

var checkFormat = function(objDiv, strCDFKey, objMatrix, objColumn, rowIndex, objServer) { 
  if (objMatrix.getRecord(strCDFKey).jsxid == "WEBM") {
    var checkmask = objColumn.getChild(0); 
    checkmask.setEnabled(0);
    var html = checkmask.paint();
// override the cell's innerHTML with a disabled checkbox.
    objDiv.innerHTML = html;
    checkmask.setEnabled(1);
  }
}

The Format Handler property of the checkbox column will be set to checkFormat

Posted at Mar 18, 2010 by Darren Hwang | 0 comments
Last changed Nov 22, 2009 21:34 by Eric Shi

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

Posted at Nov 22, 2009 by Eric Shi | 0 comments
Last changed Nov 18, 2009 09:57 by Jesse Costello-Good
Labels: gi, 3_9, forms

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:

Posted at Nov 17, 2009 by Jesse Costello-Good | 0 comments
Last changed Nov 09, 2009 17:28 by Jesse Costello-Good
Labels: gi, 3_9, builder

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.

Posted at Nov 09, 2009 by Jesse Costello-Good | 5 comments
Last changed Sep 28, 2009 10:49 by Jesse Costello-Good
Labels: gi, 3_9, cdf, xml, schema

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.

Posted at Sep 28, 2009 by Jesse Costello-Good | 2 comments
Last changed Sep 26, 2009 17:35 by Jesse Costello-Good
Labels: gi, 3_9, builder, scriptlet

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!

Posted at Sep 26, 2009 by Jesse Costello-Good | 3 comments
Last changed Sep 03, 2009 19:36 by Darren Hwang

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

  1. Locate under your "gi-3.8" the file "logger.xml", open this file for editing.
  2. 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

  3. 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>
    
  4. Add jsx3.log("some log message"); to your code
  5. 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)

Posted at Sep 03, 2009 by Darren Hwang | 0 comments
Last changed Sep 02, 2009 11:54 by Darren Hwang
Labels: matrix, table, paging

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

Posted at Aug 31, 2009 by Darren Hwang | 2 comments
Last changed Aug 24, 2009 10:56 by Jesse Costello-Good
Labels: crossdomain, gi, 3_9, ant, build

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.

Posted at Aug 24, 2009 by Jesse Costello-Good | 0 comments
Last changed Aug 05, 2009 18:26 by Darren Hwang

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

  1. 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.
 }
Posted at Aug 05, 2009 by Darren Hwang | 0 comments
Last changed Jul 16, 2009 14:17 by Darren Hwang

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.

Posted at Jul 16, 2009 by Darren Hwang | 0 comments
Last changed Apr 27, 2011 18:18 by Darren Hwang
Labels: asynch, xml

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

When Cache document are loaded (or "set"), you get a event notification from Cache. The subject is your cache document name.

MYAPP.getCache().getOrOpenAsync(this.APP.resolveURI("components/list_of_states.xml"), "list_of_states");
MYAPP.getCache().subscribe("list_of_states", function() {jsx3.log("loaded"); }
Posted at Jun 11, 2009 by Darren Hwang | 2 comments
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.