Header Bar

Building Mobile and Web Apps with jsLinb, Sigma Visual Builder, and Rebol Server Code

By: Nick Antonaccio
Updated 9-18-2015
A video which demonstrates parts of this tutorial can be found at https://youtu.be/6yrDNluQSwo

Contents:

1. What are the jsLinb Library and Sigma Visual Builder?
2. Installing Sigma Builder on a Web Server
3. Basic jsLinb Code and Sigma IDE Examples
4. Connecting jsLinb Apps to Rebol CGI Server Applications
5. Example Apps made with jsLinb and Rebol CGI Code
5.1 To-Do List
5.2 Image Gallery
5.3 Days Between 2 Dates
5.4 Tip Calculator
6. Saving and Deploying your jsLinb Apps in Sigma IDE
7. Some Data Grid Examples
8. Powerful Layout Widgets
9. jsLinb and Sigma Builder Documentation Features
10. Using the jsLinb Databinder to Collect and Set Form Data
11. A Larger Example App
12. Connecting to Stand-Alone Rebol Server Apps
13. CrossUI
14. The End

1. What are the jsLinb Library and Sigma Visual Builder?

To build GUI client apps for operating systems which R2/View and R3-GUI don't support, a simple solution is to use the toolkit at http://sourceforge.net/projects/ajaxuibuilder/ (6Mb). That download contains a JavaScript library called "jsLinb", and the "Sigma Visual Builder" IDE which can be used to create jsLinb GUI apps by clicking & dragging. The library and the visual IDE are both lightweight yet strikingly powerful. Not only do the created client apps run in virtually any JavaScript enabled browser (from Firefox 1.5 and IE6 to just about every modern mobile and desktop browser in existence), but the entire IDE, documentation tools, and everything needed to create apps, also run on all those same platforms.

That means you can use any version of iPhone, iPad, Android phones and tablets, Blackberry devices, Windows phones and tablets, or any version of any old or new desktop operating system, to create GUI front end apps which connect to Rebol server code. The created client apps can then immediately run on any other operating system, or on your web site, without any changes to the front end GUI code. You can also use the jsLinb/Sigma IDE to easily connect and move data between back-end server applications written in any other language. The whole system is totally server agnostic, using standard http:// protocols and Ajax methods to transfer data in plain text, JSON, XML, and other common data formats. The jsLinb library and Sigma builder IDE are both exhaustively documented, they're free to use for commercial projects (licensed LGPL), and with the help of 3rd party tools such as Cordova, Phonegap Build, Node Webkit, or others, you can easily package the created browser-based client UIs into stand-alone apps for distribution in the app stores of any modern OS platform.

The jsLinb library can be used entirely on its own, via JavaScript code on any web page. No other tools are required beyond pure handwritten jsLinb API code (just distribute a copy of the jsLinb JavaScript runtime file(s) with your code). The Sigma Visual Builder software is an optional browser based IDE, created using jsLinb, which makes life easier by visually generating even the most complex jsLinb code. It can help you get started writing jsLinb API code, improve general productivity, and help reduce syntax errors by visually laying out jsLinb user interfaces, even after you become thoroughly familiar with the API. The code for any jsLinb/Sigma app can be contained entirely in a single text file, and the syntax is extraordinarily simple to read and write manually, with or without the visual IDE.

You don't need previous JavaScript experience to use the Sigma IDE, or the jsLinb code API. This text will explain how to start using the visual builder, along with the basic code constructs needed to connect with Rebol server apps. You can get started creating useful browser based clients to Rebol server data management apps in one sitting of about an hour.

2. Installing Sigma Builder on a Web Server

In order to use the Sigma Visual builder, the contents of the zip package above need to be unzipped into a publicly viewable folder on any web server (for example, in .../www/ , or .../htdocs/ , etc.). You can install it on any web server with which you're familiar, on any operating system, as long as PHP and Rebol CGI scripts can run there. To use all the features of the Sigma Visual IDE, your server should have minimum PHP4+ running (PHP5+ is recommended). You don't need to know anything about PHP to use jsLinb or the Sigma builder, and your created apps will have absolutely no dependencies on PHP whatsoever. You won't see or use any PHP code while developing jsLinb apps. The PHP code in the Sigma builder simply provides some file management features in the IDE.

The examples in this tutorial have been tested using inexpensive LAMP shared hosting accounts at Lunarpages and HostGator, on Android devices using the KSWeb server app, and on Windows desktop machines using the Uniserver WAMP package: http://sourceforge.net/projects/miniserver/files/Uniform%20Server/3.3/ (that old version works just fine, and is chosen here for demonstration because of its small 6Mb download size). Several other LAMP and WAMP packages on various OSs have been tested by this author, and they all work out of the box, with every single browser tested (from IE6 and Firefox 1.5, to the default browsers that come with Android 2.2 and the very first iPhone, along with a wide assortment of various new and old desktop and mobile browsers).

For the sake of this tutorial, here's a quick explanation of how to install jsLinb/Sigma builder in the Uniform Server on Windows:

Actually, no real installation is required - just unpack the Uniform Server package above into any folder on your hard drive, and then unpack the Sigma Builder zip file into some subfolder of wherever you unpacked .../UniformServer/udrive/www/ . You may want to rename the .../UniformServer/udrive/www/sigma-visual-builder-2.0.2_full/ folder to something shorter (for this tutorial, we'll rename it to .../UniformServer/udrive/www/sigma/). Click the Server_Start.bat file in the UniformServer folder, then open your browser to http://localhost/sigma/. There you will see the sigma visual builder home page:

Click the "Try it now" link, or go directly to http://localhost/sigma/VisualJS/index.html in your browser, and you will see the Sigma Visual Builder app:

Click the "Normal View" tab, and you can edit the jsLinb API code which makes up your current app:

Switch back over to the "Design View" tab, and any changes you've made in the code will appear in the Visual Builder, and visa-versa. The code of your app can be created/edited manually from scratch, or created/edited entirely visually using click-drag operations and property/event checkbox settings in the IDE. You can go back and forth between fully visual development and fully code based development, or mix either of the two approaches, at any point in the development process.

3. Basic jsLinb Code and Sigma IDE Examples

The minimal boilerplate code which appears by default in the "Normal View" code editor tab of the Sigma IDE is:

/*
* The default code is a com class (inherited from linb.Com)
*/
Class('App', 'linb.Com',{
    Instance:{
        //base Class for this com
        base:["linb.UI"],
        //requried class for this com
        required:[],

        properties:{},
        events:{},
        iniResource:function(com, threadid){
        },
        iniComponents:function(com, threadid){
        },
        iniExComs:function(com, hreadid){
        }
    }
});

Replace that code with the following (copy/paste this code directly into the Normal View tab):

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:[], 
        events:{
            onReady:'_onready'
        }, 
        _onready:function(){
            alert("hi");
        }
    }
});

Notice that the events:{} block in the code above now contains an onReady event, which fires the "_onready" function beneath. Notice also that the _onready() function runs an alert() function, which displays the message "hi".

Now click the "Run" button in the IDE:

and you will see the created app open up and run in a new browser tab:

Next, paste the following code into the IDE Normal View tab (completely replace the previous code):

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.Button"], 
        events:{
        }, 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(70)
                .setTop(110)
                .setCaption("Say Hi")
                .onClick("_button1_onclick")
            );

            return children;

        }, 
        _button1_onclick:function (profile,e,src,value){
            alert ("Hi");
        }
    }
});

Notice that the iniComponents() function above now contains an append() function. That append() function is used to add every possible widget in the jsLinb library to any user interface layout. Notice that the appended button widget is labeled "button1", the position and caption properties are set, and the onClick event is set to fire the "_button1_onclick" function. Notice that the _button1_onclick() function below simply runs an alert() function which pops up the message "Hi". Note that because this is a JavaScript toolkit, function parameters are enclosed in parentheses, and lines end with a semicolon.

Now click back over to the Design View tab, and you'll see that a button with the text "Say Hi" has appeared on the visual builder canvas:

With your mouse, select the new button in the visual builder, and then click the "properties" drop down tree on the right side of the screen. You'll see that the .properties settings in the pasted app code are reflected exactly in those visually editable properties settings:

If you edit the properties in the visual IDE settings, the changes will be immediately reflected in the code, whenever you switch back to the Normal View tab - and visa versa. If you make changes to the code, they will be immediately reflected in the the visual layout and the listed properties tree. For example, try changing the caption or the position of the button, in both the code and the visual editor, and you'll see those changes echoed back and forth between the code and the visual editor. You can work with every aspect of any jsLinb application in this way - move round trip, back and forth between code and visual editing, using whichever approach you prefer for a given task. The visual editor can often produce error proof code much more quickly than you could type it by hand.

Now click the "events" drop down tree on the right side of the screen, and double-click the "onClick" event. You'll see a code editor pop up, with the code to be executed for that event:

This is the exact same code that you see in the Normal View tab, in the _button1_onclick() function:

Click the "Run" button on the top of the screen, and you'll see the created application open up and run in a new tab. Try clicking the button:

4. Connecting jsLinb Apps to Rebol CGI Server Applications

To learn the basics of using Rebol on a web server, with the CGI interface, see http://business-programming.com/business_programming.html#section-14 . To use Rebol with the Uniform Server we installed earlier, a copy of the Rebol/Core interpreter (rebol.exe) needs to be placed in the .../UniformServer/udrive/usr/bin/ folder. If you're using any other web server, just put the Rebol/Core interpreter, for the operating system on which you're running the web server, into a folder your web server software can use to process CGI requests (check your server software documentation if you're unsure where this should be). Be sure to point the first line of each Rebol CGI script to the location of your Rebol interpreter.

Now copy this Rebol CGI script to a file named echo.cgi, in the cgi-bin folder of your web server (.../UniformServer/udrive/cgi-bin/ in Uniserver - check your server's documentation if you're unsure where CGI apps can be located in your server software):

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: text/html^/"
data: decode-cgi raw: read-cgi

save %data.txt raw
prin data/2

Notice a few things about the code above. First, the shebang line points to the location of the Rebol interpreter, described above. The first four lines of the script are just some standard boilerplate code for Rebol CGI apps. The two lines after that save the submitted data to a text file (data.txt), and then print out the submitted data value. So, what this app does is echo back any text which is submitted to it. You can test it by submitting a GET request string directly as a URL in your browser: http://localhost/cgi-bin/echo.cgi?data=Hello!

When you have the Rebol CGI script above set up, paste the following code into the Sigma IDE codeview tab:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:[
            "linb.UI.Label", "linb.UI.Button", 
            "linb.UI.Input", "linb.UI.Tag"
        ], 
        properties:{}, 
        events:{}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Input)
                .host(host,"input1")
                .setLeft(30)
                .setTop(40)
                .setWidth(550)
                .setHeight(150)
                .setMultiLines(true)
                .setValue("6ct7y6g78u8hi9o0")
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(30)
                .setTop(210)
                .setCaption("button1")
                .onClick("btn6c")
            );

            append((new linb.UI.Input)
                .host(host,"input2")
                .setLeft(30)
                .setTop(250)
                .setWidth(550)
                .setHeight(130)
                .setMultiLines(true)
            );

            return children;

        }, 
        btn6c:function(){
            var self=this;
            linb.Ajax(
                "http://localhost/cgi-bin/echo.cgi", 
                ('data=' + self.input1.getUIValue()),
                function(s){
                    self.input2.setUIValue(s);
                }
            ).start();
        }
    }
});

If you find that the top of the Sigma IDE is no longer visible after pasting or editing longer bits of code, just reduce the zoom in your browser window several times ([CTRL] + [-] in most desktop browsers, or pinch+sqeeze on mobile screens), and then reset the zoom to 100% ([CTRL] + [0] in desktop browsers, or pinch+stretch on mobile devices). In most browsers, you can also click and scroll with the mouse back to top of the IDE.

Notice in the code above that 3 widgets have been appended to the canvas layout: a text input, a button, and an additional text input. Both input widgets have the multiline property set to true, and the default value displayed in the first widget has been set to "6ct7y6g78u8hi9o0". Here's what the layout looks like, when you switch to Design View:

IMPORTANT: Notice that the onClick event of the button widget has been set to run the "btn6c" function. That function is the most important part of this example. It runs the linb.Ajax method, which is what we'll use throughout the rest of this tutorial to connect with Rebol code running on a server. Notice that the first argument of the linb.Ajax function is the URL of the Rebol echo.cgi script we installed above. The second argument is the data which we're sending to that CGI script. In this case, were sending some concatenated text, 'data=' plus self.input1.getUIValue(), which is the value displayed in the input1 widget. The third argument is an action that can be taken with the data returned by the Rebol server script, in this case that data is labeled "s". That returned data is displayed in the input2 widget, using the jsLinb API code "self.input2.setUIValue(s)".

That basic linb.Ajax construct is the main thing which needs to get learned in order to get data back and forth between your jsLinb GUIs and your Rebol server apps. Note that the linb.Ajax method is meant to be used only for user interfaces which originate from a server at the same domain as the CGI server app. For example, if your jsLinb code is served from http://localhost , it would not connect with a Rebol server script hosted at http://yourdomain.com . There are other jsLinb API calls which handle cross domain calls, but in most cases, your jsLinb user interfaces and your Rebol CGI scripts will be served from the same server (i.e., from the same domain), so we'll deal with cross domain Ajax methods later in the tutorial.

Click the run button in the Sigma builder and run the script above. You'll see that any text entered into the input1 widget is submitted to the Rebol server script, processed, and then the echoed data is returned to the jsLinb app, and displayed in the input2 widget. You can check that this is the case by looking at the data.txt file in the cgi-bin folder on your web server (remember, the echo.cgi script contained 1 line of code which saved the submitted data to the data.txt file).

As you play with the above app, you may notice a little problem. The returned data doesn't contain any line breaks entered into the input1 widget:

If you check the data.txt file in your server's cgi-bin folder, you'll see that the raw data was submitted without line breaks. To fix that, replace the code above with the following:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:[
            "linb.UI.Label", "linb.UI.Button", 
            "linb.UI.Input", "linb.UI.Tag"
        ], 
        properties:{}, 
        events:{}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Input)
                .host(host,"input1")
                .setLeft(30)
                .setTop(40)
                .setWidth(550)
                .setHeight(150)
                .setMultiLines(true)
                .setValue("6ct7y6g78u8hi9o0")
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(30)
                .setTop(210)
                .setCaption("button1")
                .onClick("btn6c")
            );

            append((new linb.UI.Input)
                .host(host,"input2")
                .setLeft(30)
                .setTop(250)
                .setWidth(550)
                .setHeight(130)
                .setMultiLines(true)
            );

            return children;

        }, 
        btn6c:function(){
            var self=this;
            linb.Ajax(
                "http://localhost/cgi-bin/echo.cgi", 
                ('data=' + _.serialize(self.input1.getUIValue())),
                function(s){
                    self.input2.setUIValue(_.unserialize(s));
                }
            ).start();
        }
    }
});

The only difference in the code above is found in the linb.Ajax call. Notice that the self.input1.getUIValue() function has been wrapped in the _.serialize() function. This function is part of the linb API, and it ensures that all data gotten from the input1 widget is properly packaged up before being sent off to the server. Notice that the _.unserialize() function is used to unpack the data from the server (held in that 's' variable), before it is displayed back in the input2 widget. Using the _.serialize() and _.unserialize() functions will save you headaches when sending data back and forth between jsLinb UIs and server apps.

The simple code constructs you've seen so far are enough to begin building many sorts of useful data management apps. You can use the GUI widgets in jsLinb/Sigma to get data from users, send it to Rebol server scripts to do the dirty work of data processing, permanent storage, etc., and then send results back to be displayed in the user's browser. The jsLinb Ajax API provides everything needed to exchange data back and forth with your Rebol server code. Using the visual builder to lay out GUI widgets, you already know enough to create some basic CRUD apps. The next section contains a few example apps.

5. Example Apps made with jsLinb and Rebol CGI Code

This section demonstrates some simple apps created using the jsLinb/Sigma features you've seen so far. Several additional jsLinb widgets and methods will be explained along the way.

5.1 To-Do List

Save the following Rebol script to a file named todo.cgi, in the cgi-bin folder of your web server:

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: text/html^/"
data: decode-cgi raw: read-cgi
save %todo.log raw
either data/2 = "loaddata" [
    either error? try [
        sv: read %todo.txt
    ][prin "[]"] [prin sv]
][
    write %todo.txt data/2
    prin "Saved!"
]
quit

Paste the code below into the Normal View tab of the Sigma IDE:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.List", "linb.UI.Button", "linb.UI.Input"], 
        properties:{}, 
        events:{"onReady":"_onready"}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.List)
                .host(host,"list1")
                .setTips("double-click to remove item")
                .setLeft(20)
                .setTop(20)
                .setWidth(460)
                .setHeight(190)
                .onDblclick("_list1_ondblclick")
            );

            append((new linb.UI.Input)
                .host(host,"input1")
                .setLeft(20)
                .setTop(230)
                .setWidth(460)
                .onBlur("_input1_onblur")
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(360)
                .setTop(270)
                .setHeight("22")
                .setCaption("Save To Server")
                .onClick("_button1_onclick")
            );

            return children;
        }, 
        iniExComs:function(com, hreadid){
        }, 
        _list1_ondblclick:function (profile,item,src){
            this.list1.removeItems([item.id])
        }, 
        _input1_onblur:function (profile){
            this.list1.insertItems([{
                id : this.input1.getUIValue(),
                caption : this.input1.getUIValue()
            }],null,false)
            this.input1.setUIValue("")
        }, 
        _button1_onclick:function (profile,e,src,value){
            linb.Ajax(
                "http://localhost/cgi-bin/todo.cgi",
                ('savedata=' + _.serialize(this.list1.getItems())),
                function(s){
                    alert(s);
                }
            ).start();
        }, 
        _onready:function (com,threadid){
            var self=this
            linb.Ajax(
                "http://localhost/cgi-bin/todo.cgi",
                'loaddata=loaddata',
                function(s){
                    self.list1.setItems(_.unserialize(s));
                }
            ).start();
        }
    }
});

Now switch over to the Design View tab in Sigma IDE and click the Run button:

You can test the app by typing some entries into the text input field. Press the [ENTER] key after each text entry (or the "go" option on Android's popup keyboard, or you can also click anywhere outside the text widget to submit each entry, if you're using a mobile device which doesn't provide some sort of go/enter keyboard option). You'll see each of the submitted entries get added to the list widget:

To delete any item in the list widget, double-click the item:

When you're done adding/removing items, click the "Save to Server" button:

Close the app in your browser and run it again (or just refresh the app's browser page), and you'll see that your saved data is automatically loaded from the server and displayed in the list widget:

Open the todo.txt file in the cgi-bin folder on your web server, and you'll see that the data has been saved to that file on the server (it's just some simpe Json):

Now let's take a closer look at both the jsLinb GUI code, and the Rebol CGI server code. You can see, both in the Sigma visual builder and in the pure jsLinb code, that there are three widgets (a list, input, and button) appended to the canvas. Here's the relevant jsLinb widget code from the example above. This was all created by dragging widgets onto the canvas in the Design View tab of the Sigma IDE (the visual builder), and then visually clicking/editing properties of the added widgets, using the visual properties tree:

append((new linb.UI.List)
    .host(host,"list1")
    .setTips("double-click to remove item")
    .setLeft(20)
    .setTop(20)
    .setWidth(460)
    .setHeight(190)
    .onDblclick("_list1_ondblclick")
);

append((new linb.UI.Input)
    .host(host,"input1")
    .setLeft(20)
    .setTop(230)
    .setWidth(460)
    .onBlur("_input1_onblur")
);

append((new linb.UI.Button)
    .host(host,"button1")
    .setLeft(360)
    .setTop(270)
    .setHeight("22")
    .setCaption("Save To Server")
    .onClick("_button1_onclick")
);

Notice that each of those widgets handles some onXX event (onDblclick, onBlur, onClick). Each of those event handlers, and the empty function skeletons, were created by clicking on the visual event tree in the Design view. When one of those events occurs on any of the widgets, the appropriate function is called. So far, no code in this app has been written by hand. The entire process was accomplished with just a couple minutes of dragging widgets and clicking/editing properties. We just need to add some code to each of the functions to do something useful for our purposes:

_list1_ondblclick:function (profile,item,src){
    this.list1.removeItems([item.id])
}, 
_input1_onblur:function (profile){
    this.list1.insertItems([{
        id : this.input1.getUIValue(),
        caption : this.input1.getUIValue()
    }],null,false)
    this.input1.setUIValue("")
}, 
_button1_onclick:function (profile,e,src,value){
    linb.Ajax(
        "http://localhost/cgi-bin/todo.cgi",
        ('savedata=' + _.serialize(this.list1.getItems())),
        function(s){
            alert(s);
        }
    ).start();
},

When the onDblclick event occurs on the list widget, the .removeItems() method is called on the selected item (referred to by the [item.id] property). This process deletes the currently selected item in the list widget.

When the onBlur event occurs on the text input widget (when focus moves away from that widget), the .insertItems() method is called on the list widget. The data inserted into the list is gotten using the .getUIValue() method of the text input widget. Finally, the value displayed in the text input widget is erased (set to "") using the .setUIValue() method. This process adds the submitted text entry to the list widget.

When the onClick event occurs on the button widget, a linb.Ajax request is sent to the Rebol todo.cgi script on the server. Notice that the 'savedata=' key is set to the data gotten using the .getItems() method of the list widget. This list of items is serialized and sent to the server app. This process saves the list data to the server. The data returned from the CGI app (s) is alerted to the user. In the case of a successful save, the returned data is the text "Saved!", which is alerted to the user.

Next, the visual editor is used to add an onReady event to the app canvas. This event is activated when the app has been fully loaded in the user's browser. That event fires the automatically created _onready() function. We just need to add some code there to make the function do something useful. For our purposes, we'll run a linb.Ajax method that submits the value "loaddata" to the todo.cgi server app, and gets a list of data in return (s). That data is then unserialized, and set to be displayed in the list1 widget, using the .setItems() method. The end effect of this code is that the saved data is loaded from the server and displayed in the list widget, whenever the browser app is loaded:

_onready:function (com,threadid){
    var self=this
    linb.Ajax(
        "http://localhost/cgi-bin/todo.cgi",
        'loaddata=loaddata',
        function(s){
            self.list1.setItems(_.unserialize(s));
        }
    ).start();
}

That covers all the custom parts of the jsLinb GUI app which are not just stock boilerplate code.

Now if you look at the todo.cgi server script, you'll see how it handles each of the 2 possible requests from the browser code above. It starts off with 4 normal lines of CGI boilerplate code. Then it evaluates whether or not the "loaddata" value was submitted from the browser. If so, it tries to read the local todo.txt file. If an error occurs during that process (presumably because the app has never been run, and that file does not yet exist) it prints out an empty block to display back in the browser list widget. Otherwise, it prints out the data list read from the saved file on the server:

either data/2 = "loaddata" [
    either error? try [
        saved-data: read %todo.txt
    ][prin "[]"] [prin saved-data]
]...

If the submitted data value is not "loaddata" (i.e., a save request has been sent by the browser), then the submitted data value (the serialized list of items from the list widget) is written to the local todo.txt file, and the "Saved!" message is sent back to the browser, to be alerted to the user.

... [
    write %todo.txt data/2
    prin "Saved!"
]

That's it. Just a handful of lines of custom code are required in both the GUI app and the server code. Everything else is created automatically by the builder, or is stock boilerplate code. This takes just a few minutes to create, once the routine is understood. The coding process is similar to that required to assemble much more complicated data management interfaces. In more complex apps, you'll just use more widgets and layouts to get input from the user, and to display data output. And of course, the data manipulations which occur on the server can encompass any computing work you require or imagine. All the beloved productivity of Rebol can be put to work there, with results displayed in jsLinb widgets.

5.2 Image Gallery

Save the following Rebol code to a file named gallery.cgi in the cgi-bin folder of your web server:

#!/usr/bin/rebol.exe -cs
REBOL [title: "Image Gallery"]
print "content-type: text/html^/"
data: decode-cgi raw: read-cgi

folder: data/2
count: 1
json: copy "["
files: read to-file join "../www/" folder
foreach file files [
    if find [%.jpg %.gif %.png %.bmp] suffix? file [
        append json rejoin [
            "{" 
            {"id" : "} count 
            {", "caption" : "} count " -- " file 
            {", "image" : "http://localhost/} folder file {"}
            "},"
        ]
        count: count + 1
    ]
]
append json "]"
prin json
quit

Paste the following jsLinb code into the Normal View tab of the Sigma IDE:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:[
            "linb.UI.Gallery", "linb.UI.Label", 
            "linb.UI.Input", "linb.UI.Button"
        ], 
        properties:{}, 
        events:{"onReady":"_onready"}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Label)
                .host(host,"label1")
                .setLeft(10)
                .setTop(10)
                .setWidth("60")
                .setHeight("20")
                .setCaption("Folder:")
            );

            append((new linb.UI.Input)
                .host(host,"input1")
                .setLeft(80)
                .setTop(10)
                .setWidth("220")
                .setValue("someimages/")
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(310)
                .setTop(10)
                .setCaption("Submit")
                .onClick("_button1_onclick")
            );

            append((new linb.UI.Gallery)
                .host(host,"gallery1")
                .setLeft(10)
                .setTop(50)
                .setWidth("700")
                .setHeight("400")
                .setItemWidth("auto")
                .setItemHeight("auto")
                .setImgWidth("auto")
                .setImgHeight("auto")
            );

            return children;
        }, 
        iniExComs:function(com, hreadid){}, 
        _button1_onclick:function (profile,e,src,value){
            var self=this
            linb.Ajax(
                "http://localhost/cgi-bin/gallery.cgi",
                ("data=" + self.input1.getUIValue()),
                function(s){
                    self.gallery1.setItems(_.unserialize(s));
                }
            ).start();
        }
    }
});

Create a folder .../www/someimages/ on your web server, and copy a few random image files into it. Then click the Run button in the Sigma IDE and you'll see an app displaying a plain layout:

Click the Submit button, and if you've added some images to the someimages/ folder, then you'll see them all appear in a scrolling gallery widget in the app:

Let's take a look at the jsLinb code first. As you can see, 4 widgets have been added to the canvas. As in previous examples, this was all done using the visual builder. The generated code is easy to read, with properties echoed directly from the visually edited settings:

append((new linb.UI.Label)
    .host(host,"label1")
    .setLeft(10)
    .setTop(10)
    .setWidth("60")
    .setHeight("20")
    .setCaption("Folder:")
);

append((new linb.UI.Input)
    .host(host,"input1")
    .setLeft(80)
    .setTop(10)
    .setWidth("220")
    .setValue("someimages/")
);

append((new linb.UI.Button)
    .host(host,"button1")
    .setLeft(310)
    .setTop(10)
    .setCaption("Submit")
    .onClick("_button1_onclick")
);

append((new linb.UI.Gallery)
    .host(host,"gallery1")
    .setLeft(10)
    .setTop(50)
    .setWidth("700")
    .setHeight("400")
    .setItemWidth("auto")
    .setItemHeight("auto")
    .setImgWidth("auto")
    .setImgHeight("auto")
);

Notice that an onClick event was added to the button1 widget. The custom code we need to create for that function is a linb.Ajax method which sends the text of the input1 widget to the server CGI app. The data returned from the server is unserialized and set to be displayed in the gallery widget:

var self=this
linb.Ajax(
    "http://localhost/cgi-bin/gallery.cgi",
    ("data=" + self.input1.getUIValue()),
    function(s){
        self.gallery1.setItems(_.unserialize(s));
    }
).start();

That's all there is to the jsLinb code. It's similar in many ways to all the other examples you've seen so far.

The Rebol gallery.cgi code starts off by assigning the label 'folder to the value submitted from the browser (the folder name which the user typed into the input1 widget). A 'count variable is set to 1, a string labeled 'json is created, and the files from the submitted folder are read:

folder: data/2
count: 1
json: copy "["
files: read to-file join "../www/" folder

Next, a 'foreach loop iterates through each of the file names in the folder. If the file is some image type (jpg, gif, png, or bmp), then some text is appended to the 'json string. The appended text is simply some concatenated characters, in the format required by the gallery widget "items" property, to display images. If you click the Gallery widget in Sigma visual builder, and look at its "items" property, you'll see that the item list requiers this format: [{"id" : "id text", "caption" : "caption text", "image" : "url"},]. The 'count variable is also updated in each iteration of the 'foreach loop (and included in the image caption text). When the loop is done, the final 'json string is printed back to the browser, where that returned list of items is displayed in the gallery widget:

foreach file files [
    if find [%.jpg %.gif %.png %.bmp] suffix? file [
        append json rejoin [
            "{" 
            {"id" : "} count 
            {", "caption" : "} count " -- " file 
            {", "image" : "http://localhost/} folder file {"}
            "},"
        ]
        count: count + 1
    ]
]
append json "]"
prin json
quit

That's the entire gallery app. At this point you should be getting a good sense about how jsLinb UIs and Rebol server apps interact to form complete client-server applications.

5.3 Days Between 2 Dates

Here's a simple app which calculates the number of days between 2 dates. This is strictly a jsLinb app (there is no Rebol server needed). Paste the following code into the Sigma code editor:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.Button", "linb.UI.DatePicker"], 
        properties:{}, 
        events:{}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.DatePicker)
                .host(host,"datepicker1")
                .setLeft(10)
                .setTop(10)
            );

            append((new linb.UI.DatePicker)
                .host(host,"datepicker2")
                .setLeft(250)
                .setTop(10)
            );

            append((new linb.UI.Button)
                .host(host,"button")
                .setLeft(170)
                .setTop(190)
                .setCaption("Calculate")
                .onClick("_button_onclick")
            );

            return children;

        }, 
        iniExComs:function(com, hreadid){}, 
        _button_onclick:function (profile,e,src,value){
            alert(
                linb.Date.diff(
                    this.datepicker1.getUIValue(),
                    this.datepicker2.getUIValue(),
                    'd'
                )
            );
        }
    }
});

You can see that 2 Datepicker widgets and 1 button are added to the canvas. When the button is clicked, the difference between the user-selected dates is calculated, using the linb.Date.diff() method. That result is alerted to the user. That's all there is to the entire app:

We can check the calculation in the Rebol console:

5.4 Tip Calculator

Here's another simple jsLinb app (no Rebol server needed). The code looks long, but nearly all of it was created using the visual builder:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.Label", "linb.UI.Input", "linb.UI.Button"], 
        properties:{}, 
        events:{}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Label)
                .host(host,"label1")
                .setLeft(20)
                .setTop(30)
                .setWidth(70)
                .setCaption("Price ($):")
            );

            append((new linb.UI.Label)
                .host(host,"label2")
                .setLeft("20")
                .setTop(70)
                .setWidth(70)
                .setCaption("Tip (%):")
            );

            append((new linb.UI.Input)
                .host(host,"input1")
                .setLeft(100)
                .setTop(30)
                .setValueFormat(
                 "^-?(\\d\\d*\\.\\d*$)|(^-?\\d\\d*$)|(^-?\\.\\d\\d*$)"
                 )
                .setValue("100")
            );

            append((new linb.UI.Input)
                .host(host,"input2")
                .setLeft(100)
                .setTop(70)
                .setValueFormat(
                 "^-?(\\d\\d*\\.\\d*$)|(^-?\\d\\d*$)|(^-?\\.\\d\\d*$)"
                 )
                .setValue(".20")
                .onBlur("_button1_onclick")
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(100)
                .setTop(110)
                .setCaption("Calculate Tip")
                .onClick("_button1_onclick")
            );

            append((new linb.UI.Input)
                .host(host,"input3")
                .setLeft(100)
                .setTop(150)
            );

            return children;
        }, 
        iniExComs:function(com, hreadid){}, 
        _button1_onclick:function (profile,e,src,value){
            this.input3.setUIValue(
                this.input1.getUIValue() * this.input2.getUIValue()
            )
        }
    }
});

Notice that the .valueFormat properties for the first two input fields were both set using visual selectors in the IDE. This requires the user to type in numerical values. There are a number of other default valueFormat options (and these regex options can be manually edited in the code):

The only custom code required for this entire app is in the _button1_onclick function. It multiplies the numerical values in the price and tip input widgets, and displays the result of that calculation in the input3 widget:

this.input3.setUIValue(
    this.input1.getUIValue() * this.input2.getUIValue()
)

Finally, notice that an .onBlur event has been added to the input2 widget, which also runs the above function. That way, the user can either click the button, or just press [ENTER] after typing the tip value, and the calculation will run:

6. Saving and Deploying your jsLinb Apps in Sigma IDE

The Sigma Builder IDE provides several options for saving code and deploying finished apps. One nice thing about jsLinb is that the code for entire apps can be saved as a single contiguous text file (there are interesting options for separating code into multiple files, and dynamically loading remote code, but those topics are saved for another tutorial). As you've already seen, all of the layout and logic/action code in a complete jsLinb app can be copied and pasted directly to/from the Normal View code tab in Sigma IDE. You can move code around in this way, edit your code in any preferred local text editor, IDE, etc. and copy/paste back an forth between files, web pages, other text displays, and the Sigma IDE, without using any of the Sigma save/load features. By pasting examples from this web page, for example, you've seen that the single file, pure code nature of jsLinb apps can keep the mechanics simple. You may also find this feature useful, for example, when working on small mobile devices, where a native code editor app may be easier to work with than the Sigma IDE code tab.

If you click the "Load" button at the top right corner of the Sigma IDE, you'll see that files can be loaded from any URL location (i.e., http://localhost/myapp.js):

The load button also allows you to browse and load any of the code snippet demo apps which ship with the Sigma IDE. Those demo code examples are really helpful when learning how to use all the widget and UI features in the jsLinb library (we'll cover more about the code snippets library later).

By simply saving your code files to an accessible web server, you can load them back directly into the IDE from any other location, no matter what type of operating system is available, wherever you are. This feature helps improve productivity if you move around regularly between development machines and devices.

If you click the "Save" button at the top right corner of the Sigma IDE, you'll see that there are four options available for saving and deploying your application code:

The first save option allows you to save your jsLinb application code file directly back to where it was opened, if you've loaded it from a drive on your local machine.

The second save option allows you to download the entire application code to your local machine, as a single .js file (that is, it allows you to simply download the contents of the Normal View code tab in the Sigma IDE). This is the most likely method you'll use to regularly save your work.

The third save option allows you to package your code into an HTML file, for deployment to a server. This option wraps your code in all the necessary HTML, including code which loads the jsLinb runtime and skin (appearance) files (CSS, images, etc.). You can upload the jsLinb runtime files just one time, to a single folder on your deployment server, and then simply upload any new single page HTML wrapped apps, and that's all which is needed to deploy. This is really helpful when you're creating numerous apps which run from a single server.

The fourth save option creates a fully self contained zip file, with all your application code, the HTML wrapper document, and the full jsLinb runtime, with skin files - absolutely everything required to run your created jsLinb application. For most apps, the size of the zip file is approximately 200k (.2 Mb - tiny). You can simply unpack the zip file to a folder on any web server, point a browser to the URL of the created .html file, and your app is up and running. Note that absolutely no other server technologies are required to run your jsLinb app. Neither PHP, nor any other server languages or deployment environment are required at all. Remember, the PHP files in the Sigma builder app are only used to provide file management features for the IDE (wherever you run the Visual Builder IDE, to create your apps). JavaScript in your user's browser is the only requirement to run the created jsLinb apps. In fact, the created apps don't even need a web server to run. You can unzip the zip file to a folder on your hard drive, SD card, etc., open the HTML page directly from its file location, and it will run without problems.

The zip files produced by Sigma IDE can also be uploaded directly to app packagers such as Phonegap build. The zip package contains everything needed for those packagers to produce stand alone apps which can be distributed to iTunes, Google Play, Blackberry World, and other app stores.

NOTE: the zip file format produced by Sigma IDE does not unpack properly with some versions of the Windows extraction wizard, and a similar problem has occurred when using the unzip tool in Lunarpages cPanel (not all of the skin files unpack properly). Tugzip and several other various zip applications do, however, work without problems. If you run into this difficulty with your deployment server, try a different zip application - all of the necessary files are included in the zip file. Another option is to simply copy the jsLinb runtime files to your deployment server manually, repackage them into a zip file with your app files manually, etc.

7. Some Data Grid Examples

Data grids are useful in many types of apps. This section of the tutorial explains how to use the jsLinb Treegrid widget with Rebol server back ends.

The following example code is found in ...\sigma\Samples\comb\GridEditor\App\js\index.js:

Class('App', 'linb.Com',{
    Instance:{
        //base Class for linb.Com
        base:["linb.UI"], 
        //requried class for the App
        required:["linb.UI.Block", "linb.UI.TreeGrid", "linb.UI.Div"], 
        iniComponents:function(){
            // [[code created by jsLinb UI Builder
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Block)
                .host(host,"block1")
                .setLeft(70)
                .setTop(50)
                .setWidth(540)
                .setHeight(270)
            );

            host.block1.append((new linb.UI.TreeGrid)
                .host(host,"tg")
                .setTabindex("2")
                .setHeader([])
                .setRows([])
                .onDblClickRow("_tg_ondblclickrow")
            );

            append((new linb.UI.Div)
                .host(host,"div9")
                .setLeft(70)
                .setTop(10)
                .setWidth(270)
                .setHeight(30)
                .setHtml("DblClick row  to open edit dialog!")
            );

            return children;
            // ]]code created by jsLinb UI Builder
        }, 
        _changeRow:function(v1,v2,v3){
            var cells=this._activeRow.cells;
            if(cells[0]!=v1)
                this.tg.updateCell(cells[0],{value:v1});
            if(cells[1]!=v2)
                this.tg.updateCell(cells[1],{value:v2});
            if(cells[2]!=v3){
                this.tg.updateCell(cells[2],{value:v3});
            }
        }, 
        _tg_ondblclickrow:function (p,row, e, src) {
            this._activeRow = row;
            var self=this;
            linb.ComFactory.newCom('App.Dlg' ,function(){
                this.$parent=self;
                this
                .setProperties({
                    fromRegion:linb([src]).cssRegion(true),
                    col1:row.cells[0].value,
                    col2:row.cells[1].value,
                    col3:row.cells[2].value
                })
                .setEvents('onOK', self._changeRow);
                this.show(linb([document.body]));
            });
        }, 
        events:{
            onReady:'_onready'
        }, 
        _onready:function(){
            var self=this;
            linb.Ajax("data/data.js",'',function(s){
                var hash=_.unserialize(s);
                self.tg.setHeader(hash.header).setRows(hash.rows);
            }).start();
        }
    }
});

This code is a simplified version of the code above, which demonstrates only the parts needed to use a Treegrid widget, along with the code needed to fill the grid with data from a Rebol CGI server app:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.TreeGrid"], 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.TreeGrid)
                .host(host,"tg")
                .setHeader([])
                .setRows([])
            );

            return children;

        }, 
        events:{
            onReady:'_onready'
        }, 
        _onready:function(){
            var self=this;
            linb.Ajax("
                http://localhost/cgi-bin/readjsonfile.cgi",
                '',
                function(s){
                    var hash=_.unserialize(s);
                    self.tg.setHeader(hash.header).setRows(hash.rows);
                }
            ).start();
        }
    }
});

Notice in the code above that the linb.Ajax call is made to a Rebol readjsonfile.cgi script in the cgi-bin folder on the web server. Paste the following code into a text file, and save it as readjsonfile.cgi in your server's cgi-bin folder:

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: application/json^/"
prin read %data.js

The code above does only one thing. It prints the contents of the data.js file. Paste the following data into a text file, and save it as data.js in your server's cgi-bin folder (you can find this original file in the jsLinb/Sigma zip package, in ...\sigma\Samples\comb\GridEditor\App\js\index.js):

{
    header: [
       {
           "id" : "col1",
           "caption" : "col1",
           "type" : "input",
           "width" : 50
       },
       {
           "id" : "col2",
           "caption" : "col2",
           "type" : "number",
           "format":"^-?\\d\\d*$",
           "width" : 80
       },
       {
           "id" : "col3",
           "caption" : "col3",
           "type" : "checkbox",
           "width" : 40
       },
       {
           "id" : "col4",
           "caption" : "col4",
           "type" : "label",
           "width" : 40
       }
   ],
   rows: [
       {
           "id" : "row1",
           "cells" : ["cell11",1,true,'label1']
       },
       {
           "id" : "row2",
           "cells" : ["cell21",2,true,'label2']
       },
       {
           "id" : "row3",
           "cells" : ["cell31",3,false,'label3']
       },
       {
           "id" : "row4",
           "cells" : ["cell41",4,false,'label4'],
           "sub" : [
               {
                   "id" : "row5",
                   "cells" : ["in51",5,false,'label5']
               },
               {
                   "id" : "row6",
                   "cells" : ["in61",6,false,'label6']
               },
               {
                   "id" : "row7",
                   "cells" : ["in71",7,false,'label7']
               },
               {
                   "id" : "row8",
                   "cells" : ["in81",8,false,'label8']
               }
           ]
       },
       {
           "id" : "row9",
           "cells" : ["cell91",9,false,'label9'],
           "sub" : [
               {
                   "id" : "row10",
                   "cells" : ["in101",10,false,'label10']
               },
               {
                   "id" : "row11",
                   "cells" : ["in111",11,false,'label11']
               },
               {
                   "id" : "row12",
                   "cells" : ["in121",12,false,'label12']
               },
               {
                   "id" : "row13",
                   "cells" : ["in131",13,false,'label13']
               }
           ]
       }
   ]
}

Now run the jsLinb example code above, and you'll see the following grid of data displayed:

The code format of the data file should make sense when looking at the image above. Notice that the header block in the data file describes the format of the header bar displayed in the data grid. Notice also that the structure of the displayed grid data, including sub-tree grids, are echoed in the hierarchical structure of the code in the data file. Here is a Rebol script which produces a similar data file:

rebol [title: "Create jsLinb json grid data"]

data: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
cols: 4

header: copy "{header: ["
repeat i cols [
    append header rejoin [
        "{" {"id" : "col} i {", "caption" : "col} i {"} "},"
    ]
]
append header {],}

griddata: copy "rows: ["
repeat i ((length? data) / cols) [
    append griddata rejoin [ "{" {"id" : "row} i {", "cells" : [} ]
    for j cols 1 -1 [
        append griddata rejoin [
           {"} form data/(i * cols - j + 1) {",} 
        ]    
    ]
    append griddata "]},"
]
append griddata "]}"

editor join header griddata

Notice that each 'for loop above concatenates a list of values, together with the proper formatting characters required to form the data structure used by the Treegrid widget. One loop creates the header data structure, and the other loop creates the row data structure. Run that code in your desktop Rebol interpreter and you'll see the following output:

That output is not pretty formatted for human viewing, but the data syntax follows the rules demonstrated in the original demo data file above (note that the data set is slightly simplified, without any sub-rows).

We can convert the code above to run as a CGI script, just by adding the proper CGI boilerplate, and using 'prin to output the final data structure. Save the following code as createjson.cgi in the cgi-bin folder of your server:

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: application/json^/"         ; text/html^/"
submitted: decode-cgi submitted-bin: read-cgi

data: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
cols: 6

header: copy "{header: ["
repeat i cols [
    append header rejoin [
        "{" {"id" : "col} i {", "caption" : "col} i {"} "},"
    ]
]
append header {],}

griddata: copy "rows: ["
repeat i ((length? data) / cols) [
    append griddata rejoin [ "{" {"id" : "row} i {", "cells" : [} ]
    for j cols 1 -1 [
        append griddata rejoin [
           {"} form data/(i * cols - j + 1) {",} 
        ]    
    ]
    append griddata "]},"
]
append griddata "]}"

prin join header griddata
quit

Now run the following jsLinb code in the Sigma IDE:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.Block", "linb.UI.TreeGrid", "linb.UI.Div"], 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.TreeGrid)
                .host(host,"tg")
                .setHeader([])
                .setRows([])
            );

            return children;

        }, 
        events:{
            onReady:'_onready'
        }, 
        _onready:function(){
            var self=this;
            linb.Ajax(
                "http://localhost/cgi-bin/createjson.cgi",
                '',
                function(s){
                    var hash=_.unserialize(s);
                    self.tg.setHeader(hash.header).setRows(hash.rows);
                }
            ).start();
        }
    }
});

Notice that the linb.Ajax call in the code above gets its data from the Rebol CGI script we just saved to the server. That CGI script sends back the formatted data, which is labeled "s" in the code here, as in previous examples. And, as in previous examples, the _.unserialize() function is used to unpack the returned data structure, and that unpacked data is stored in the variable "hash". The header of the datagrid display, labeled "tg" in this example, is assigned the hash.header data, and the rows are set to display the hash.rows data. Notice that all this occurs in a function fired by the onReady event, when the page finishes loading in your browser. Here's what you see when you run the app:

If you change the code in the Rebol createjson.cgi, and then refresh the running jsLinb app, you'll see the grid display update to show the new data output by the CGI script. Try changing the line "cols: 6", in the createjson.cgi file, to "cols: 3". Refresh the jsLinb app running in your browser, and you'll see:

One thing to be aware of is that you can choose to either put the header layout info in the data returned by the server, as in the examples above, or you can set it directly in your jsLinb code. As you saw in the examples above, the grid will (re)configure itself to display data as instructed, if the data file contains header information. To simplify data output, you can specify the header layout in your jsLinb code (or by adjusting the grid settings in the Visual IDE), and simply output data from the server script which fits that format. Save the following code as createjson2.cgi in your cgi-bin folder:

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: application/json^/"

data: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
cols: 4

griddata: copy "["
repeat i ((length? data) / cols) [
    append griddata rejoin [ "{" {"cells" : [} ]
    for j cols 1 -1 [
        append griddata rejoin [
           {"} form data/(i * cols - j + 1) {",} 
        ]    
    ]
    append griddata "]},"
]
append griddata "]"

prin griddata
quit

Compare the code above with the createjson.cgi script shown earlier. There is only one loop, which is used to create the row data displayed in the Treegrid. Look a bit closer and you'll see that the data format above is also a bit simpler than in the previous example (there are fewer key identifiers, only the "cells:" blocks are labeled). Here's a desktop version of the script above, which you can use to play with the data format:

REBOL [title: "simpler jsLinb treegrid data format"]

data: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
cols: 4

griddata: copy "["
repeat i ((length? data) / cols) [
    append griddata rejoin [ "{" {"cells" : [} ]
    for j cols 1 -1 [
        append griddata rejoin [
           {"} form data/(i * cols - j + 1) {",} 
        ]    
    ]
    append griddata "]},"
]
append griddata "]"

editor griddata
quit

Now paste the following jsLinb app code into the Sigma IDE:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:["linb.UI.TreeGrid", "linb.UI.Button"], 
        properties:{}, 
        events:{"onReady":"_onready"}, 
        iniResource:function(com, threadid){
        }, 
        iniComponents:function(){

            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.TreeGrid)
                .host(host,"treegrid1")
                .setDock("none")
                .setWidth(370)
                .setHeader(["col1", "col2", "col3", "col4"])
            );

            return children;

        }, 
        iniExComs:function(com,threadid){
        }, 
        _onready:function (com,threadid){
            self=this;
            linb.Ajax(
                "http://localhost/cgi-bin/createjson2.cgi",
                '',
                function(s){
                    var hash=_.unserialize(s);
                    self.treegrid1.setRows(hash);
                }
            ).start();
        }
    }
});

Note that the code above also uses a simplified data format to layout the grid header. Only the column headers are named. You can include more description, as in the previous example, but it's not required. If you switch to the Design View tab in Sigma IDE, select the Treegrid widget on the visual canvas, and then click the "header" property on the right, you'll see that the data structure has been fully filled in by jsLinb:

Now run the jsLinb code above, and you'll see the following:

The Treegrid widget provides important functionality for data management apps which deal with columns of information. You'll find that it's useful in virtually every type of business application, and in any sort of app which deals with collections/lists of organized data (that's potentially any type of app for which computers and devices are used to manage useful info). You'll find that the other features of the Treegrid widget (various display, edit, sort options, etc.) will likely become an integral part of your app design routine in jsLinb/Sigma.

It's beyond the scope of this tutorial, but the makers of jsLinb/Sigma have created a completely stand alone Grid library and API which has many powerful and useful features (printing to PDF, Excel, and other formats, filtering, many advanced display options, server interaction capabilities, etc.). That tool is also freely usable for commercial work (LGPL), it's very well documented, and worth a look if you expect to do lots of data grid work. It's the subject of a whole other tutorial:

http://www.sigmawidgets.com/products/sigma_grid2/

8. Powerful Layout Widgets

After that quick look at the Treegrid widget, you may have started to get a sense that the jsLinb library is quite powerful. Next, we'll take a quick look at a handful of the other interesting widgets in jsLinb, which make it simple to layout lots of information and controls on a single screen, in ways which are intuitive and easily controlled by the user, and also fast and simple to layout with the Sigma IDE, or with pure jsLinb API code.

If you look at the Tools Box in the Design View (visual editor tab) of the Sigma IDE, you'll see that the widgets and tools are divided into 6 categories:

  1. Data
  2. Form Elements
  3. Containers
  4. Navigators
  5. Schedules
  6. Medias

Open the Containers accordion and drag a ButtonViews widget onto the visual canvas:

Set the "barLocation" property to "left" in the IDE properties tree:

Set the "barSize" property to "120":

Click the "items" property and edit the key/value pair as follows:

[{
    "id" : "a",
    "caption" : "item a",
    "image" : "img/demo.gif"
},
{
    "id" : "b",
    "caption" : "item b",
    "image" : "img/inedit.gif"
},
{
    "id" : "c",
    "caption" : "item c",
    "image" : "img/app.gif"
}]

Here's how it looks:

Drag a few random widgets into each of pages of the ButtonViews widget, just to see that each of the pages does actually contain a totally different layout:

Run the app and look at each page of the ButtonViews widget:

You can see that a multi page layout takes just a few moments to create. Here's the code for the above layout example:

Class('App', 'linb.Com',{
    Instance:{
        //base Class for this com
        base:["linb.UI"], 
        //requried class for this com
        required:[
            "linb.UI.ButtonViews", "linb.UI.Button", 
            "linb.UI.ProgressBar", "linb.UI.DatePicker"
        ], 

        properties:{}, 
        events:{}, 
        iniResource:function(com, threadid){
        }, 
        iniComponents:function(){
            // [[code created by jsLinb UI Builder
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.ButtonViews)
                .host(host,"buttonviews6")
                .setItems([
             {"id":"a", "caption":"item a", "image":"img/demo.gif"},
             {"id":"b", "caption":"item b", "image":"img/inedit.gif"},
             {"id":"c", "caption":"item c", "image":"img/app.gif"}
                 ])
                .setBarLocation("left")
                .setBarSize("120")
                .setValue("a")
            );

            host.buttonviews6.append((new linb.UI.Button)
                .host(host,"button13")
                .setLeft(10)
                .setTop(10)
                .setCaption("button13")
            , 'a');

            host.buttonviews6.append((new linb.UI.ProgressBar)
                .host(host,"progressbar3")
                .setLeft(10)
                .setTop(10)
            , 'b');

            host.buttonviews6.append((new linb.UI.DatePicker)
                .host(host,"datepicker1")
                .setLeft(10)
                .setTop(10)
            , 'c');

            return children;
            // ]]code created by jsLinb UI Builder
        }, 
        iniExComs:function(com, hreadid){
        }
    }
});

Here's another little example which demonstrates some additional widgets and their properties. Open the Containers accordian and drag a Layout widget onto the visual canvas:

Set the "type" property to "horizontal" in the IDE properties tree:

Click the "items" property, and edit the key/value pairs as follows (remove the 3rd layout separator):

[{
    "id" : "before",
    "pos" : "before",
    "min" : 10,
    "size" : 20,
    "locked" : false,
    "hide" : false,
    "cmd" : true,
    "caption" : "before"
},
{
    "id" : "main",
    "min" : 10,
    "caption" : "main"
}]

Drag a Tabs widget into the right (larger) section of the layout widget:

Click and drag the layout divider bar to the right, to provide more room on the left side of the layout widget:

Drag a gallery widget into the left side of the layout widget:

Click the "Dock" property, and select "Left". This will force the gallery widget size to stretch and fit along the entire left hand wall of its layout container:

Set the "itemHeight" and "itemWidth" properties to 250 and 200 (or any other size you'd like):

Click the "items" property and edit the key/value pairs to the following (or any other captions and images you prefer):

[{
 "id" : "a",
 "caption" : "Nick Guitar",
 "image" : "http://guitarz.org/www/jscript/uploads/collection_vaa.jpg"
},
{
 "id" : "b",
 "caption" : "Nick's Dog",
 "image" : "http://guitarz.org/www/jscript/uploads/dew_electric3.jpg"
}]

Resize the gallery widget and the layout divider so the images fit well:

Now drag a few random widgets into each of the tabs of the tab widget, just to see that each of the tab pages has different content:

Run the app, and play with the layout features. You can close, open, and resize the left side of the layout widget:

View the contents of each tab page in the tab widget:

Adjust the browser screen size, and watch all the widgets resize/realign appropriately:

Here's the code for the above layout example:

Class('App', 'linb.Com',{
    Instance:{
        //base Class for this com
        base:["linb.UI"], 
        //requried class for this com
        required:[
            "linb.UI.Layout", "linb.UI.Tabs", "linb.UI.Button", 
            "linb.UI.List", "linb.UI.ComboInput", "linb.UI.DatePicker",
            "linb.UI.Gallery"
        ], 
        properties:{}, 
        events:{}, 
        iniResource:function(com, threadid){
        }, 
        iniComponents:function(){
            // [[code created by jsLinb UI Builder
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.UI.Layout)
                .host(host,"layout3")
                .setItems([
                    {"id":"before", "pos":"before", "min":10, 
                    "size":237, "locked":false, "hide":false, 
                    "cmd":true, "caption":"before"}, 
                    {"id":"main", "min":10, "caption":"main"}
                 ])
                .setType("horizontal")
            );

            host.layout3.append((new linb.UI.Tabs)
                .host(host,"tabs2")
                .setItems([
                    {"id":"a", "caption":"item a", 
                    "image":"img/demo.gif"}, 
                    {"id":"b", "caption":"item b", 
                    "image":"img/demo.gif"}, 
                    {"id":"c", "caption":"item c", 
                    "image":"img/demo.gif"}, 
                    {"id":"d", "caption":"item d", 
                    "image":"img/demo.gif"}
                ])
                .setValue("a")
            , 'main');

            host.tabs2.append((new linb.UI.Button)
                .host(host,"button10")
                .setLeft(30)
                .setTop(30)
                .setCaption("button10")
            , 'a');

            host.tabs2.append((new linb.UI.List)
                .host(host,"list3")
                .setItems([
                    {"id":"a", "caption":"item a", 
                    "image":"img/demo.gif"}, 
                    {"id":"b", "caption":"item b", 
                    "image":"img/demo.gif"}, 
                    {"id":"c", "caption":"item c", 
                    "image":"img/demo.gif"}, 
                    {"id":"d", "caption":"item d", 
                    "image":"img/demo.gif"}
                ])
                .setLeft(30)
                .setTop(30)
                .setValue("a")
            , 'b');

            host.tabs2.append((new linb.UI.ComboInput)
                .host(host,"comboinput6")
                .setLeft(30)
                .setTop(30)
                .setItems([
                    {"id":"a", "caption":"item a", 
                    "image":"img/demo.gif"}, 
                    {"id":"b", "caption":"item b", 
                    "image":"img/demo.gif"}, 
                    {"id":"c", "caption":"item c", 
                    "image":"img/demo.gif"}, 
                    {"id":"d", "caption":"item d", 
                    "image":"img/demo.gif"}
                ])
                .setValue("a")
            , 'c');

            host.tabs2.append((new linb.UI.DatePicker)
                .host(host,"datepicker2")
                .setLeft(30)
                .setTop(30)
            , 'd');

            host.layout3.append((new linb.UI.Gallery)
                .host(host,"gallery2")
                .setItems([
                    {"id":"a", "caption":"Nick",
"image":"http://guitarz.org/www/jscript/uploads/collection_vaa.jpg"},
                    {"id":"b", "caption":"Nick's Dog",
"image":"http://guitarz.org/www/jscript/uploads/dew_electric3.jpg"}
                ])
                .setDock("left")
                .setWidth(230)
                .setItemWidth("200")
                .setItemHeight("250")
                .setValue("a")
            , 'before');

            return children;
            // ]]code created by jsLinb UI Builder
        }, 
        iniExComs:function(com, hreadid){
        }
    }
});

These are just a few quick extemporaneous examples to encourage experimentation with some of the jsLinb layout widget features. As you get used to using the Sigma builder, screen layouts with much greater complexity than this often take just a few minutes to create. You can fit an enormous number of pages, and a tremendous number of widget layouts all into a single page application, quickly and easily using jsLinb's layout features. Learning how to manipulate and edit the properties of each of the available widgets is the biggest part of learning how to use the entire jsLing/Sigma toolkit. The next section of the tutorial will explain how to explore all the features of the library and the Visual builder, using the included documentation features.

9. jsLinb and Sigma Builder Documentation Features

The jsLinb library and the Sigma Visual IDE are expertly documented. Everything you need comes in the little 6Mb zip file which you've already installed. With your Uniform Server running, go to http://localhost/sigma in your browser (or any other URL where you have jsLinb/Sigma installed, on the web, on your local network, on your local machine, etc.). Click the "Manual" link, and you'll find a quick overview of the Sigma IDE:

Now go back to the main http://localhost/sigma page and click the "More Samples >>" and "More sample & materials >>" links, or go directly to http://localhost/sigma/Samples/all-samples.html. This page contains a number of complex example applications (the Sigma IDE is actually one of those!), which you can inspect to see how features of the jsLinb API can be put together to create "real world" apps. Pay special attention to the examples which demonstrate how to accomplish specific tasks with jsLinb, such as CSS styling, IO (including cross domain Ajax calls and file uploads), GUI drag and drop techniques, distributed UI techniques, etc. There is a gold mine of useful code fragments in the examples which you can copy and paste directly into your own apps. Several of the apps themselves are actually useful documentation and productivity tools which enhance the Sigma IDE suite:

(There are many more example apps, in addition to those shown above).

Another goldmine of useful jsLinb code is found in the code snippets app. On the examples page, click the "Code Snippets" link, or go directly to http://localhost/sigma/CodeSnip/index.html . This collection of code examples demonstrates the main features of all the jsLinb widgets. You'll use these examples extensively as you explore the most commonly used properties, methods, and code constructs used to implement jsLinb widgets in your user interface layouts. The code for each of the widget examples is displayed, and a link at the top right corner of the example allows you to open any of the snippets directly in the Sigma IDE, so that you can manipulate and experiment with it, use it immediately in your own apps, etc.:

The examples in the CodeSnip library are also integrated directly into the Sigma visual builder. Just click the Open button at the top right corner of the IDE, and select "Open from Samples".

Finally, and most importantly, on the examples page, click the "Sigma Linb API" link, or go directly to http://localhost/sigma/API/index.html. That is an API viewer for the jsLinb library, which can be run as a standalone app. This app is also integrated directly into the Sigma visual builder:

The API viewer app provides detailed explanations and reference documentation about every single feature in the jsLinb library. It also contains thousands of short code examples which succinctly demonstrate the required syntax needed to use each property, event, and feature of the jsLinb library. The code examples can be clicked and run inline, directly within the API viewer. This is a tremendous time saver when searching for and learning to use any API call:

The API viewer is also integrated directly into the Sigma IDE. You can double-click any property or event in the tree on the right side of the screen, and the API viewer will open direcly to that item, so you can see exactly how to use it, with detailed description, parameter reference, code examples, etc.

Together, the documentation tools and examples provide every bit of documentation needed, not just for complete reference, but also for guidance about how larger apps can be assembled, as well as some creative inspiration. Learning to use the documentation features is an enjoyable and productive process, and should provide everything you need to make full use of jsLinb and the Sigma IDE, once this introductory tutorial is completed.

10. Using the jsLinb Databinder to Collect and Set Form Data

The jsLinb Databinder allows you to easily collect multiple pieces of information entered into forms, and serialize them into a single Json string (and visa-versa). You can drag a databinder object onto the canvas from the Tools bar:

The object won't show up on the visual layout, but you can see it appended in the code:

Paste the following code into the Normal View tab in Sigma IDE. Notice that a Databinder labeled "myformdata" has been added to the canvas, and that the .setDataBinder() and .setDataField() properties have been set to "field1" and "field2" respectively, for the input1 and input2 widgets. Notice also that the this.myformdata.getValue() and this.myformdata.resetValue() methods have been used in the onClick functions below to collect and set the data in those widgets:

Class('App', 'linb.Com',{
    Instance:{
        base:[], 
        required:[
            "linb.DataBinder", "linb.UI.Input", "linb.UI.Button"
        ],
        events:{}, 
        iniComponents:function(){
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.DataBinder)
                .host(host,"myformdata")
                .setName("myformdata")
            );

            append((new linb.UI.Input)
                .host(host,"input1")
                .setDataBinder("myformdata")
                .setDataField("field1")
                .setLeft(21)
                .setTop(20)
            );

            append((new linb.UI.Input)
                .host(host,"input2")
                .setDataBinder("myformdata")
                .setDataField("field2")
                .setLeft(21)
                .setTop(51)
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(21)
                .setTop(80)
                .setCaption("Get Values")
                .onClick("_button1_onclick")
            );

            append((new linb.UI.Input)
                .host(host,"input3")
                .setLeft(21)
                .setTop(150)
                .setValue("{field1:'1234', field2:'adsf'}")
            );

            append((new linb.UI.Button)
                .host(host,"button2")
                .setLeft(21)
                .setTop(181)
                .setCaption("Set Values")
                .onClick("_button2_onclick")
            );

            return children;
        }, 
        _button1_onclick:function (profile, e, value) {
            var data=this.myformdata.getValue();
            if(!data)
                alert('Ensure all the fields are valid first!');
            else
                alert(_.serialize(data),true);
        }, 
        _button2_onclick:function (profile, e, value) {
            this.myformdata.resetValue(
                _.unserialize(this.input3.getUIValue())
            );
        }
    }
});

When you run the code above, you'll see that the top two text input widgets are empty. Click the "Get Values" button and you'll see the empty data content alerted as a list of Json values {"field1":"","field2":""}:

Now type some text into the input1 and input2 widgets, click the "Get Values" button again and you'll see the new collected data values alerted as a Json object:

Click the "Set Values" button and you'll see that the values you entered into the input1 and input2 widgets have been replaced with the values represented by the Json string in the input3 widget:

Manually edit the Json string in the input3 widget, click the "Set Values" button again, and you'll see the edited data values displayed appropriately back in the input1 and input2 widgets:

You can add as many databinders as you want to your jsLinb apps, each serializing/deserializing any chosen values from/to any set of widgets anywhere on the canvas. This allows for really simple data entry, editing, and collection and/or presentation of multiple pieces of data using any variety of jsLinb widgets. Take a look at the jsLinb example apps and API viewer for more Databinder code examples. It is one of the most helpful features of the jsLinb library.

11. A Larger Example App

This app demonstrates a number of additional features and techniques which are commonly useful:

Notice these features in the code and visual layout:

  1. Two databinder objects are added to collect and set data in the 2 separate forms.
  2. A layout widget is used to fit all the entries in an especially large form onto a single page (the account profile form). The layout presented fits nicely on a small phone screen, without scrolling.
  3. Data in the account profile form is saved, not only to the server, but also to local browser cookies. When the app starts, if the user has saved profile information on the local machine, it is loaded from cookies, and then displayed. The user can also create and save new account profiles, as well as load, update, and switch between existing profiles stored on the server (type in an existing username/password, click the "load" button, edit, then click the "save" button).
  4. The server code demonstrates one way to parse data in the saved serialized format submitted from the forms, along with the logic needed to save and load existing profile data, to compare usernames and passwords, etc.
  5. Several other pieces of data are loaded from the server and the results are displayed on startup. A checkbox in the "data layout" page is checked, based on a true/false value saved in a text file on the server (.../www/checkbox.txt). Some text in a label widget in the data layout page is read from a text file and displayed in the user interface (.../www/text.txt). The first page of the buttonviews widget is also focused and displayed on startup.
  6. Icons in the the buttonviews widget are taken from a single image map file (the collection of images found in /img/widgets.gif). Pay special attention to the syntax in the "items" property of the buttonviews widget, to see how this is accomplished (each icon in this image set is 16 pixels across).
  7. A few other simple layout ideas are demonstrated. Some text is displayed in divs, using HTML formatting. Some group widgets are also displayed, one of which can be opened and hidden by clicking separate buttons, or by clicking directly on the group widget.
  8. Notice that the widgets in the forms, and the data included in the databinders to which they are attached, include items such as date and time pickers, as well as password fields, multiline fields, etc. All of this graphically represented information can be serialized and unserialized, included in the databinder, etc. just as easily as any text field.

It should be noted that the Sigma IDE provides a number of tools for visually duplicating, aligning, and otherwise creating perfectly spaced and neatly layed out multiple widgets:

Class('App', 'linb.Com',{
    Instance:{
        base:["linb.UI"], 
        required:[
            "linb.UI.ButtonViews", "linb.UI.Div", "linb.UI.Label",
            "linb.UI.Input", "linb.UI.Group", "linb.UI.DatePicker",
            "linb.UI.Tabs", "linb.UI.Stacks", "linb.UI.TextEditor",
            "linb.UI.Button", "linb.UI.TimePicker", "linb.UI.Pane",
            "linb.UI.Layout", "linb.DataBinder", "linb.UI.CheckBox"
        ], 
        properties:{}, 
        events:{"onReady":"_onready", "onRender":"_onrender"}, 
        iniResource:function(com, threadid){}, 
        iniComponents:function(){
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.DataBinder)
                .host(host,"databinder1")
                .setName("databinder1")
            );

            append((new linb.DataBinder)
                .host(host,"databinder2")
                .setName("databinder2")
            );

            append((new linb.UI.ButtonViews)
                .host(host,"buttonviews1")
                .setItems([
                  {"id":"1", "caption":"MultiPage Form", 
                  "image":"img/widgets.gif", "imagePos":"-192px top"},
                  {"id":"2", "caption":"Text Layout", 
                  "image":"img/widgets.gif", "imagePos":"-48px top"},
                  {"id":"3", "caption":"Data Layout", 
                  "image":"img/widgets.gif", "imagePos":"-64px top"},
                  {"id":"4", "caption":"Form", 
                  "image":"img/widgets.gif", "imagePos":"-96px top"},
                  {"id":"6", "caption":"Groups", 
                  "image":"img/widgets.gif", "imagePos":"-112px top"}
                ])
                .setBarLocation("left")
                .setBarSize("130")
                .setValue("a")
            );

            host.buttonviews1.append((new linb.UI.DatePicker)
                .host(host,"datepickerWriteYourOwn")
                .setDataBinder("databinder2")
                .setDataField("requestdate")
                .setLeft(20)
                .setTop(170)
                .setCloseBtn(false)
            , '4');

            host.buttonviews1.append((new linb.UI.Pane)
                .host(host,"pane8")
                .setLeft(10)
                .setTop(10)
                .setWidth("469")
                .setHeight(320)
            , '1');

            host.pane8.append((new linb.UI.Layout)
                .host(host,"layout3")
                .setItems([
                    {"id":"before", "pos":"before", "min":10, 
                    "size":318, "locked":false, "hide":false, 
                    "cmd":true, "caption":"before"}, 
                    {"id":"main", "min":1, "caption":"main"}
                ])
            );

            host.layout3.append((new linb.UI.Label)
                .host(host,"label213")
                .setLeft(230)
                .setTop(20)
                .setCaption("Notes:")
                .setHAlign("left")
            , 'main');

            host.layout3.append((new linb.UI.Label)
                .host(host,"label212")
                .setLeft(10)
                .setTop(20)
                .setCaption("Birthdate:")
                .setHAlign("left")
            , 'main');

            host.layout3.append((new linb.UI.DatePicker)
                .host(host,"datepicker19")
                .setDataBinder("databinder1")
                .setDataField("birthdate")
                .setLeft(10)
                .setTop(40)
            , 'main');

            host.layout3.append((new linb.UI.Group)
                .host(host,"groupProfile")
                .setLeft("10")
                .setTop("10")
                .setWidth(450)
                .setHeight(270)
                .setCaption("Account Info:")
                .setToggleBtn(false)
            , 'before');

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelLastName")
                .setLeft(10)
                .setTop(100)
                .setWidth(80)
                .setCaption("Last Name:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelCity")
                .setLeft(10)
                .setTop(160)
                .setWidth(80)
                .setCaption("City:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelAddress")
                .setLeft(10)
                .setTop(130)
                .setWidth(80)
                .setCaption("Address:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelPassword")
                .setLeft(10)
                .setTop(40)
                .setWidth(80)
                .setCaption("Password:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"label1")
                .setLeft(10)
                .setTop(10)
                .setWidth(80)
                .setCaption("Username:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelZip")
                .setLeft(10)
                .setTop(220)
                .setWidth(80)
                .setCaption("Zip Code:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelFirstName")
                .setLeft(10)
                .setTop(70)
                .setWidth(80)
                .setCaption("First Name:")
            );

            host.groupProfile.append((new linb.UI.Label)
                .host(host,"labelState")
                .setLeft(10)
                .setTop(190)
                .setWidth(80)
                .setCaption("State:")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputUsername")
                .setDataBinder("databinder1")
                .setDataField("username")
                .setLeft(110)
                .setTop(10)
                .setWidth(320)
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputPassword")
                .setDataBinder("databinder1")
                .setDataField("password")
                .setLeft(110)
                .setTop(40)
                .setWidth(320)
                .setTabindex("2")
                .setType("password")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputFirstName")
                .setDataBinder("databinder1")
                .setDataField("firstname")
                .setLeft(110)
                .setTop(70)
                .setWidth(320)
                .setTabindex("3")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputLastName")
                .setDataBinder("databinder1")
                .setDataField("lastname")
                .setLeft(110)
                .setTop(100)
                .setWidth(320)
                .setTabindex("4")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputAddress")
                .setDataBinder("databinder1")
                .setDataField("address")
                .setLeft(110)
                .setTop(130)
                .setWidth(320)
                .setTabindex("5")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputCity")
                .setDataBinder("databinder1")
                .setDataField("city")
                .setLeft(110)
                .setTop(160)
                .setWidth(320)
                .setTabindex("6")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputState")
                .setDataBinder("databinder1")
                .setDataField("state")
                .setLeft(110)
                .setTop(190)
                .setWidth(320)
                .setTabindex("7")
            );

            host.groupProfile.append((new linb.UI.Input)
                .host(host,"inputZipCode")
                .setDataBinder("databinder1")
                .setDataField("zipcode")
                .setLeft(110)
                .setTop(220)
                .setWidth(320)
                .setTabindex("8")
            );

            host.layout3.append((new linb.UI.TextEditor)
                .host(host,"texteditor39")
                .setDataBinder("databinder1")
                .setDataField("notes")
                .setLeft(230)
                .setTop(40)
                .setWidth("230")
                .setHeight(150)
                .setTabindex("10")
                .setBorder(true)
            , 'main');

            host.layout3.append((new linb.UI.Label)
                .host(host,"label58")
                .setLeft("176")
                .setTop(290)
                .setWidth("120")
                .setCaption("(more)")
                .setHAlign("center")
            , 'before');

            host.buttonviews1.append((new linb.UI.Tabs)
                .host(host,"tabs")
                .setItems([
                    {"id":"a", "caption":"One", "image":"img/run.gif"},
                    {"id":"b", "caption":"Two", "image":"img/run.gif"}
                ])
                .setValue("a")
            , '3');

            host.tabs.append((new linb.UI.Stacks)
                .host(host,"stacks1")
                .setItems([
                    {"id":"1", "caption":"1"}, 
                    {"id":"2", "caption":"2"},
                    {"id":"3", "caption":"3"}, 
                    {"id":"4", "caption":"4"}
                ])
                .setValue("a")
            , 'a');

            host.stacks1.append((new linb.UI.CheckBox)
                .host(host,"checkbox1")
                .setLeft(20)
                .setTop(20)
                .setCaption("checkbox1")
            , '1');

            host.stacks1.append((new linb.UI.Label)
                .host(host,"label26")
                .setLeft(20)
                .setTop(50)
                .setWidth(340)
                .setCaption(
                    "This checkbox value is loaded from the server"
                )
                .setHAlign("left")
            , '1');

            host.tabs.append((new linb.UI.Label)
                .host(host,"labelGetFromServer")
                .setLeft(20)
                .setTop(20)
                .setWidth(410)
                .setBorder(true)
                .setCaption("Data Page 2")
                .setHAlign("left")
                .setVAlign("middle")
            , 'b');

            host.tabs.append((new linb.UI.Label)
                .host(host,"label27")
                .setLeft(20)
                .setTop(50)
                .setWidth(410)
                .setCaption(
                    "The text above was loaded from a" +
                    "plain text file on the server."
                )
                .setHAlign("left")
            , 'b');

            host.buttonviews1.append((new linb.UI.Group)
                .host(host,"groupShare")
                .setLeft(20)
                .setTop(120)
                .setWidth(400)
                .setHeight(250)
                .setCaption("Text Entry Group")
                .setToggle(false)
            , '6');

            host.groupShare.append((new linb.UI.TextEditor)
                .host(host,"texteditorShare")
                .setLeft("20")
                .setTop("10")
                .setWidth(360)
                .setHeight(170)
                .setVisibility("visible")
                .setBorder(true)
            );

            host.groupShare.append((new linb.UI.Button)
                .host(host,"buttonShareSubmitText")
                .setLeft(260)
                .setTop(190)
                .setVisibility("visible")
                .setCaption("Submit")
            );

            host.buttonviews1.append((new linb.UI.Div)
                .host(host,"divText1")
                .setLeft(40)
                .setTop(89)
                .setWidth(390)
                .setHeight(160)
                .setHtml(
                    "Here's some info about this app:  " +
                    "<br><br>1) Item 1 <br>2) Item 2 <br>3) Item 3"
                )
            , '2');

            host.buttonviews1.append((new linb.UI.Div)
                .host(host,"divText2")
                .setLeft("20")
                .setTop(10)
                .setWidth(310)
                .setHeight(60)
                .setHtml("<font size=20>Some Text</font>")
            , '2');

            host.buttonviews1.append((new linb.UI.Button)
                .host(host,"buttonLoadDatabinder")
                .setLeft(350)
                .setTop(370)
                .setCaption("Load")
                .onClick("_buttonloaddatabinder_onclick")
            , '1');

            host.buttonviews1.append((new linb.UI.TimePicker)
                .host(host,"timepickerWriteYourOwn")
                .setDataBinder("databinder2")
                .setDataField("requesttime")
                .setLeft(230)
                .setTop(170)
                .setWidth(220)
                .setCloseBtn(false)
            , '4');

            host.buttonviews1.append((new linb.UI.Div)
                .host(host,"div24")
                .setLeft("20")
                .setTop(20)
                .setWidth(430)
                .setHeight(20)
                .setHtml("Submit text, date, and time:")
            , '4');

            host.buttonviews1.append((new linb.UI.TextEditor)
                .host(host,"texteditorMakeaRequest")
                .setDataBinder("databinder2")
                .setDataField("requesttext")
                .setLeft(20)
                .setTop(50)
                .setWidth(430)
                .setHeight(100)
                .setBorder(true)
            , '4');

            host.buttonviews1.append((new linb.UI.Button)
                .host(host,"buttonWriteYourOwn")
                .setLeft(320)
                .setTop(340)
                .setCaption("Submit")
                .onClick("_buttonwriteyourown_onclick")
            , '4');

            host.buttonviews1.append((new linb.UI.Group)
                .host(host,"groupShareText")
                .setLeft(20)
                .setTop(20)
                .setWidth(400)
                .setHeight(70)
                .setCaption("Button Group")
                .setToggleBtn(false)
            , '6');

            host.groupShareText.append((new linb.UI.Button)
                .host(host,"buttonShareUploadText")
                .setLeft(20)
                .setTop(10)
                .setWidth(170)
                .setCaption("Open Text Entry Group")
                .onClick("_buttonshareuploadtext_onclick")
            );

            host.groupShareText.append((new linb.UI.Button)
                .host(host,"buttonShareReadTexts")
                .setLeft(200)
                .setTop(10)
                .setWidth(180)
                .setCaption("Close Text Entry Group")
                .onClick("_buttonsharereadtexts_onclick")
            );

            host.buttonviews1.append((new linb.UI.Button)
                .host(host,"buttonSubmitDatabinder")
                .setLeft(350)
                .setTop(340)
                .setTabindex("9")
                .setCaption("Save")
                .onClick("_buttonSubmitDatabinder_onclick")
            , '1');

            return children;
        }, 
        iniExComs:function(com, hreadid){
        }, 
        _buttonshareuploadtext_onclick:function (profile,e,src,value){
            this.groupShare.setToggle(true); ;
        }, 
        _onready:function (com,threadid){
            this.buttonviews1.setValue('1',true);
        }, 
        _buttonSubmitDatabinder_onclick:function (profile,e,src,value){
            var data=this.databinder1.getValue();
            if(!data)
                alert('Ensure all the fields are valid first!');
            else
                var cookie1 = linb.Cookies;
                cookie1.set('profile',_.serialize(data));
                linb.Ajax(
                    "http://localhost/cgi-bin/generic-profile.cgi",
                    (
                        "f=saveprofile&user=" + 
                        this.inputUsername.getUIValue() + "&pass=" +
                        this.inputPassword.getUIValue() + "&d=" + 
                        cookie1.get('profile')
                    ),
                    function(s){
                        alert(s);
                    }
                ).start();
         }, 
        _onrender:function (com,threadid){
            self=this;
            var cookie1 = linb.Cookies;
            this.databinder1.resetValue(
                _.unserialize(cookie1.get('profile'))
            );
            linb.Ajax(
                "http://localhost/text.txt",
                "",
                function(s){
                    self.labelGetFromServer.setCaption(s);
                }
            ).start();
            linb.Ajax(
                "http://localhost/checkbox.txt",
                "",
                function(s){
                    self.checkbox1.setValue(s);
                }
            ).start();
        }, 
        _buttonwriteyourown_onclick:function (profile,e,src,value){
            var data=this.databinder2.getValue();
            if(!data)
                alert('Ensure all the fields are valid first!');
            else
                linb.Ajax(
                    "http://localhost/cgi-bin/generic-form.cgi",
                    (
                        "f=submitrequest&user=" + 
                        this.inputUsername.getUIValue() + "&d=" + 
                        _.serialize(data)
                    ),
                    function(s){
                        alert(s);
                    }
                ).start();
        }, 
        _buttonsharereadtexts_onclick:function (profile,e,src,value){
            this.groupShare.setToggle(false); ;
        }, 
        _buttonloaddatabinder_onclick:function (profile,e,src,value){
            self=this
            linb.Ajax(
                "http://localhost/cgi-bin/generic-profile.cgi",
                (
                    "f=loadprofile&user=" + 
                    this.inputUsername.getUIValue() + 
                    "&pass=" + this.inputPassword.getUIValue()
                ),
                function(s){
                    alert(s);
                    self.databinder1.resetValue(_.unserialize(s));
                }
            ).start();
        }
    }
});

Here's the Rebol generic-profile.cgi file:

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: text/html^/"
data: decode-cgi raw: read-cgi
save %log.txt raw

make-dir %./profiles/
if data/2 = "loadprofile" [
    either error? try [
        sv: read rejoin [%./profiles/ data/4 ".txt"]
        parse sv [thru {"password":} copy pass to {,}]
        if not ((mold data/6) = pass) [
            prin "Incorrect username/password"
            quit
        ]
    ][prin "That user profile does not exist"] [prin sv]
    quit
]
if data/2 = "saveprofile"[
    if exists? existing-file: rejoin [%./profiles/ data/4 ".txt"] [
        sv: read existing-file
        parse sv [thru {"password":} copy pass to {,}]
        if not ((mold data/6) = pass) [
            prin "Incorrect username/password"
            quit
        ]
    ]
    write rejoin [%./profiles/ data/4 ".txt"] data/8
    prin "Your profile info has been saved in local cookies"
    prin ", and in .../cgi-bin/profiles/"
]
quit

The Rebol generic-form.cgi script:

#!/usr/bin/rebol.exe -cs
REBOL []
print "content-type: text/html^/"
data: decode-cgi raw: read-cgi
save %log.txt raw

if data/2 = "readrequest" [
    either error? try [
        sv: read %request.txt
    ][prin "Error Reading!"] [prin sv]
    quit
]
if data/2 = "submitrequest"[
    write/append %request.txt join "^/^/" mold data/4
    write/append %request.txt join "^/^/" data/6
    prin "Your form data has been saved in "
    prin ".../cgi-bin/request.txt on the server!"
]
quit

The contents of the plain .../www/text.txt file:

This text was loaded from .../www/text.txt

And the contents of the .../www/checkbox.txt file:

true

The code in this example should be reusable in many types of applications.

12. Connecting to Stand-Alone Rebol Server Apps

The tutorial at http://re-bol.com/rebol-multi-client-databases.html explains how to build multi-user network based apps entirely in Rebol. In those sorts of app, Rebol client and server apps connect directly across a network. No 3rd party web server is required (i.e., no Apache stack such as Uniform Server is used), no CGI boilerplate is needed, and even the application's data model is created entirely using pure Rebol code (native data structures and series functions, as opposed to a 3rd party DBMS) to perform data manipulation operations, permanent storage, etc. The performance of such a server can be tuned to live up to the challenges of common production traffic situations. We can extend that stand-alone Rebol server idea to be used in combination with jsLinb front end user interfaces.

The following code was taken directly from the tutorial above. It prints out a form to the user's browser, and processes the data submitted back by the form (this script does some parsing and decoding, then prints the submitted object result):

REBOL [title: "HTML Form Server"]
l: read join dns:// read dns://
print join "Waiting on:  " l
port: open/lines tcp://:80
browse join l "?"
forever [
  connect: first port
  if error? try [
    z: decode-cgi replace next find first connect "?" " HTTP/1.1" ""
    prin rejoin ["Received: " mold z newline]
    my-form: rejoin [
      {HTTP/1.0 200 OK^/Content-type: text/html^/^/
      <HTML><BODY><FORM ACTION="} l {">Server:  } l {<br><br>
          Name:<br><INPUT TYPE="TEXT" NAME="name" SIZE="35"><br>
          Address:<br><INPUT TYPE="TEXT" NAME="addr" SIZE="35"><br>
          Phone:<br><INPUT TYPE="TEXT" NAME="phone" SIZE="35"><br>
          <br><input type="checkbox" name="checks" value="i1">Item 1
          <input type="checkbox" name="checks" value="i2">Item 2
          <input type="radio" name="radios" value="yes">Yes
          <input type="radio" name="radios" value="no">No<br><br>
          <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
      </FORM></BODY></HTML>}
    ]
    write-io connect my-form (length? my-form)
    ] [print "(empty submission)"]
    close connect
]

For our purposes here we don't need to print the user an HTML form, because we're using jsLinb to submit data instead. So, we'll adjust the code above slightly to just process the Json formatted data sent by a jsLinb app. Run this code directly in a Rebol console on your computer (notice that this server is running on port 55555, so that it doesn't interfere with Uniform Server's port 80):

REBOL [title: "jsLinb Server"]
l: read join dns:// read dns://
print rejoin ["Waiting on:  " l ":55555"]
port: open/lines tcp://:55555
forever [
  connect: first port
  if error? try [
    z: decode-cgi replace next find first connect "?" " HTTP/1.1" ""
    prin rejoin ["Received: " mold z newline]
    r: rejoin [
      ; {HTTP/1.0 200 OK^/}
      {content-type: text/html^/^/}
      "{" {"data" : "Saved!"} "}"
    ] 
    write-io connect r (length? r)
  ] []    ;[print "(empty submission)"] 
  close connect
]

After the server above is running, paste the following jsLinb code into the Sigma IDE code tab. Notice that this layout collects some data from the user via 2 input widgets, which both have a "myformdata" databinder attached . When the button is clicked, the form data is serialized to a Json string using the myformdata.getValue() method, and then that string is submitted to the server using a linb.Ajax request (you've seen that routine a few times by now):

Class('App', 'linb.Com',{
    Instance:{
        base:[], 
        required:[
            "linb.DataBinder", "linb.UI.Input", "linb.UI.Button"
        ],
        events:{}, 
        iniComponents:function(){
            var host=this, children=[], 
            append=function(child){children.push(child.get(0))};

            append((new linb.DataBinder)
                .host(host,"myformdata")
                .setName("myformdata")
            );

            append((new linb.UI.Input)
                .host(host,"input1")
                .setDataBinder("myformdata")
                .setDataField("field1")
                .setLeft(21)
                .setTop(20)
            );

            append((new linb.UI.Input)
                .host(host,"input2")
                .setDataBinder("myformdata")
                .setDataField("field2")
                .setLeft(21)
                .setTop(51)
            );

            append((new linb.UI.Button)
                .host(host,"button1")
                .setLeft(21)
                .setTop(80)
                .setCaption("Send Form Values")
                .onClick("_button1_onclick")
            );

            return children;
        }, 
        _button1_onclick:function (profile, e, value) {
            var data=this.myformdata.getValue();
            if(!data)
                alert('Ensure all the fields are valid first!');
            else
                linb.Ajax( 
                    "http://localhost:55555", 
                    ("data=" + _.serialize(data)),
                    function(s){
                        alert(s);    
                    } 
                ).start();
        }, 
    }
});

Run the jsLinb code above to see it interact with the pure Rebol coded server:

Rebol's native network IO features make it easy to send data back and forth with jsLinb apps.

13. CrossUI

It should be noted that the jsLinb library and Sigma IDE are precursors of a more recent product called CrossUI. Most of the jsLinb library and Sigma IDE features work exactly the same in the CrossUI toolkit. The Sigma Toolkit explored in this tutorial is actually quite mature, and contains much of what makes CrossUI appealing. Cross UI does offer a number of new widgets and features, including drawing widgets and animation features, as well as beefed up versions of many jsLinb widgets. The CrossUI project manager is also more mature than the one that comes with Sigma IDE. It features direct integration with Phonegap Build, so that your created GUIs can be automatically converted into mobile apps which can be submitted to app stores for virtually every modern platform. The project manager can also produce desktop applications for Windows, Mac, and Linux (32 and 64 bit versions of each OS), using Node-Webkit, and it does that cross platform packaging from any desktop OS to any other (i.e., you can create Mac and Linux applications from Windows, Windows apps on a Mac, etc.)

The 'prototyping' feature in the newest versions of CrossUI is a complete no-code development solution which allows users to create powerful application logic without writing a single line of jsLinb code. It goes far beyond the properties/events settings of the Sigma visual builder, and fully encapsulates all the features of the jsLinb API, as well as variable creation, conditional expression creation, and other fundamental code structures, all managed by clicking on selection lists in a wizard interface which creates any required application code automatically. This is a truly unique and deep feature, which is quite practical for users such as graphic designers who work on UI design, but who aren't interested in diving deeply into code.

The new features of CrossUI do come with a price. Although the "xui" library (the new version of jsLinb found in the CrossUI toolkit) is licensed LGPL, the IDE is licensed commercially. It must be purchased if you use it to create commercial applications (this is NOT the case with Sigma IDE).

It should be noted that the CrossUI tools also come as a single install package for Windows, Mac, or Linux. You don't need to install any separate web server such as Uniform Server. This can be both helpful and detrimental. One great benefit of the Sigma IDE is that it can be run from anywhere, on any operating system, using any web server with PHP (accessible either on a local machine, on a local intranet, or on the web). This means it can be hosted virtually anywhere, and the IDE can run on just about any desktop machine or mobile device that has a web browser. That is one of the absolutely killer features of the toolkit. It is totally portable between any location and any device, and it works exactly the same everywhere, from machines with outdated browsers to the newest mobile devices. In that regard, there really is nothing like the Sigma IDE, anywhere (and it's totally free - it doesn't get much better than that).

If you want commercial support for the toolkit you use, then take a look at CrossUI. You're on your own if you choose to use the Sigma IDE. Future upgrades and support will presumably only be made for the CrossUI product line.

14. The End

The combination of jsLinb/Sigma IDE and Rebol server code forms a very productive and powerful, modern, and totally portable development toolkit for the absolutely widest possible variety of modern and legacy operating systems. JsLinb UI apps can connect to Rebol CGI code running on even the least expensive Apache shared hosting accounts, or to pure Rebol coded network apps, running on your own dedicated server on your own network, or on the web. This allows both the server apps, and the front ends to be created virtually anywhere, on any type(s) of available operating system(s), and to run anywhere too. It's easy to learn, really productive, and free to use for commercial work. It's a great addition to the Rebol toolkit - definitely worth the time investment required to learn how to use it.