Search
Categories
Tags
Latest Comments

Entries in javascript (5)

Friday
Mar262010

Two Helpful Functions in the XSP Clientside Javascript Object

There are two helpful (among others) functions in the client-side XSP Javascript Object:

XSP.startsWith(string, substring)

XSP.endsWith(string, substring)

They are string functions that return true/false if the passed in string starts or ends with substring.  Very helpful when coordinating client-side IDs to Server side IDs, for example, inside event handlers.

As an example, You can connect 1 event handler to two objects, that basically do the exact same thing, but may have slight variations for one object or the other. Take objectA and objectB; objectA is a button with the XPage ID 'niceGuy' and objectB is a button with an XPage ID of 'badGuy' and do (client-side):

dojo.connect(objectA, 'onclick', myHandler);
dojo.connect(objectB, 'onclick', myHandler);

var myHandler = function(e) {

  e = (e) ? e : window.event;
  alert('You clicked me! How dare you. The nerve of some people, i swear.');

  if (XSP.endsWith(e.target.id, 'niceGuy'))
    alert('Just Kidding, you seem like a nice ol' chap.')
  else
    alert('Shove off...');

}

While doing the check on your own, with your own code isn't all that difficult, you'd have to ask yourself why? Its already there for you!

Happy Coding!

Wednesday
Mar172010

Reusable Javascript Part IV - Putting the C in MVC

In the first three post of this series, I've introduced the concepts of the "Dojo Class", the ability to load them from an NSF repository, and the use of a namespaced class as the foundation of your coding practices for client-side javascript in your XPages applications.  In this post, I am going to further flesh out how to implement an MVC design pattern in your XPages, both server and client side, and then show you how to practically apply the use of the dojo class as your controller in an XPage application as part of the Model-View-Controller pattern.

First lets define how we can implement the MVC design pattern in XPages. We have three basic elements with which to work with in XPages

  • The XSP Markup document (The MODEL)
  • CSS & Themes (The VIEW)
  • Java/Javascript (both Client & Server Side) (The CONTROLLER)

First, let's talk briefly about the XSP document, and what you should, and more importantly, what you shouldn't be putting into the XSP.

What you should: Data, semantic markup (for example, div's, ul's, p's), "block UI devices" CSS classes, and hooks into your Controller.  What you shouldn't: styles, inline client-side javascript code, and as little inline server-side javascript code as possible.  Why? Following these guidlines will help you create re-usable components that can be assembled or displayed differently based on different a VIEW or different CONTROLLER. Your XSP document is your model, if it is filled with inline styles that describe how it is supposed to look, or scripts that define how its supposed to act, then your VIEW and CONTROLLER will have a hard time displaying the model the way its supposed to.

So for example, here is an example of bad markup for an MVC XPages Application:

<xp:div id="myExample1" style="width:150px;height:200px;background-color:#EFEFEF;border:1px solid: #CCC; overflow: auto; position: absolute; top:20px; left:50%;margin-left:-75px">
    <xp:this.rendered>
        <![CDATA[
            var x = database.getView('myView');
            var y = x.getDocumentByKey(viewScope.someScopedVar);
            return ((y.getItemValueString('VendorActive') == 'YES') ? true : false);
        ]]>
    </xp:this.rendered>
    <h3>Vendor Address</h3>
    <xp:text escape="false">
        <xp:this.value>
            <![CDATA[#{javascript:
                var x = database.getView('myView');
                var y = x.getDocumentByKey(viewScope.someScopedVar);
                var z = y.getItemValueString('Address1') + '<br />' + y.getItemValueString('Address2') + '<br />' + y.getItemValueString('City') + ', ' + y.getItemValueString('State') + ' ' + y.getItemValueString('ZipPlus4')
            ]]>
        </xp:this.value>
    </xp:text>
    <xp:button id='inactivateVendor' value="Inactivate'>
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script>
                <![CDATA[
                    confirm('Are you sure you want to inactive this customer?');
                    // ... more client side javascript here ...
                ]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>
</xp:div>

While syntactically correct, and it "gets the job done," this code can be used once, where it's at, and how it's defined.  Also, on the face of it, its very hard code hard to read. Even all nicely laid out like i have it. If you were to let that same code be generated by designer, it is much worse. Next, any and all chance to re-use the above code in other aspects of the page is nil, null, nothing, 0, {},  and [].

As a start, let's take the same block, and follow our rules from above on what should go into our Model, just replacing inline styles with CSS classes, and replacing our javascript with hooks into a controller.

<xp:div id="myExample1" styleClass="addressBlock" rendered="{javascript:thisController.isVendorActive(viewScope.someScopedVar)}">
    <h3>Vendor Address</h3>
    <xp:text escape="false" value="#{javascript:thisController.getAddressBlock(viewScope.somScopedVar)}"></xp:text>
    <xp:button id='inactivateVendor' value="Inactivate'>
        <xp:eventHandler event="onclick" submit="false" script="clientController.inactivateVendor()">
        </xp:eventHandler>
    </xp:button>
</xp:div>

Very tight, and easy to read isn't it? Not to mention, further down the page we can reuse the same CSS classes for another address block, and the same javascript functions can be used for their functions as well.

Ok, so now you can see the difference between an XPage with and without an MVC pattern applied.  Now lets look at how we can apply the controllers to an XPage. We really have two controllers when it comes to an XPage, one for the server side, and one for the client side. The "controlling" controller is the server side controller script, as it can change out or decide which client side controller to apply to your XPage. Of the two controllers, the easiest to implement is the client side. With the server side controller, we have to take into consideration things like what phase of the JSF lifecycle the page is in, we have to worry about data caching, we have to worry about object restoration, etc.  Because of that, I'll introduce the use of server-side controllers at a later time. Let's focus in on the client side for now.

Ok, so in previous posts, I've introduced the concepts of the dojo namespace and the "class" as defined by dojo. Quite simply, our client-side controller is going to be a class that represents our client side page. If you have developed in another "class" based development environment such as Delphi, Visual Basic, or the like, you are probably very familiar with the concept of your UI being defined by a class, but its something new to Notes development, and in general not really practiced in web development. For the same reasons you get simplicity and power from class-based languages like Delphi and Visual Basic, having a class represent your page can be very powerful as well.

Let's start off with defining how we would implement a class on our page. First let's create a client-side javascript library in our database, following the namespace rules we've laid out before. Let's assume our xsp page (our model) is called "vendorRecord.xsp", in a "vendor" application ... so we could namespace our class like this:

com.ZetaOne.app.vendor.vendorRecord

They way I prefer to implement the controller is to have one script that is the "controller" for the page, and that controller implements other scripts that are "class" scripts. For example, I will create a script called:

com/ZetaOne/app/vendor/vendorRecord/controller.js

and one script called

com/ZetaOne/app/vendor/vendorRecord/class.js

Sometimes, I may actually implement 2 or 3 or more classes within a given page, depending on the model, and the elements I have included in the model. For example. If I have a custom control called vendorPerformanceReview that I have included on the page I would include that class script as well.  Each of these scripts are loaded and initialized by the controller.js script.

For example, lets take a look at a sample controller.js script:

dojo.require("dojo._base._loader.loader_nsf");

dojo.requireFromNsf('com.ZetaOne.app.vendor.vendorRecord._class', window.location.href.split('.nsf')[0] + '.nsf/');
dojo.requireFromNsf('com.ZetaOne.app.vendor.vendorPerformanceReview._class', window.location.href.split('.nsf')[0] + '.nsf/');

XSP.addOnLoad(function() {

  window.vendorRecord = new com.ZetaOne.app.vendor.vendorRecord._class();
  window.vendorRecord.performanceReview = new com.ZetaOne.app.vendor.vendorPerformanceReview._class()

  // Here you can do additional non-class related functions after the loading of your page

});

The main reasons I use the controller.js to get things started is because it provides a single initialization point for the page where I can coordinate the load/configration process, and I can use the server-side controller to load different client side controllers that load different classes that do different functions at run time based upon different conditions, such as user security level, or the data's position in a process, etc.  This allows my server-side controller to be fairly agnostic to the the requirements of each condition, it just needs to associate my desired cireteria with the proper controller script.

For simplicity's sake at the moment, let's just assume we have a single controller. In this case, to apply it to the page, I just add the controller.js as a script library resource to the XPage. In a future post, when we review server side controllers, we can review how to include different client side controllers, etc.

Ok, now that we've looked at how the main client-side controller script could operate by loading individual classes that represent our page, let's look at how a class actually operates.

Let's start out with the basic class definition for our client side controller:

dojo.provide("com.ZetaOne.app.vendor.vendorRecord._class");

(function(){
   
        dojo.declare("com.ZetaOne.app.vendor.vendorRecord._class", null, {
            constructor: function() {

               return this;
            },
            destroy: function() {

            }
        })
})();

This is the very basic start of our class. When our controller does a " = new com.ZetaOne.app.vendor.vendorRecord._class", it basically creates a copy of this object, and then executes the constructor function.  The constructor function needs to return the actual constructed object, hence the line "return this;". The constructor is where we do all our initialization work to hook our class into the HTML page as needed, initialize variables, or work over the HTML page as it is at the time the class is initialized.

The destroy function is a basic destructor. It preps the class to be garbage collected. Garbage collection can be a new concept as well for many of us who aren't used to languages like java, C, etc. Javascript is an automatically garbage collecting language, however, there are things you can do to prevent memory from being collected, and you can cause a memory leak in the browser.  As a practice, there are certain things you should do to prevent this from happening, and you should do them in your class's destructor.  For example, each and every time you do a dojo.connect() to hook into an event in the DOM or another widget, you need to later dojo.disconnect(), or you'll eat memory.  I've seen applications with deep use of dojo classes and dojo.connects() that didn't properly manage their resources quickly gobble up GB's of memory in a browser.  If you are careful, and you follow a few basic rules, you'll be good.

So as an example, here's an instance of connecting the onclick event on a set of DIV tags to a function in our class, and then later we'll take care to dispose of them when the page gets unloaded.

dojo.provide("com.ZetaOne.app.vendor.vendorRecord._class");

(function(){
   
        dojo.declare("com.ZetaOne.app.vendor.vendorRecord._class", null, {
            constructor: function() {
                    this._connects = [];

                    var myDivs = dojo.query('div.myDivClass');
                    for (i=0; i<myDivs.length;i++) {
                       this._connects.push(dojo.connect(myDivs[i], 'onclick', this, this.clickAlert));
                    }

                    // make sure we destroy ourselves if they unload us...
                   this._connects.push(dojo.connect(document, 'onbeforeunload', this, this.destroy());

                   return this;
            },

            destroy: function() {

                   for (i=0;i<this._connects.length;i++) {
                       dojo.disconnect(this._connects.pop());
                   }

            },

            clickAlert: function() {
                  var e = (arguments[0]) ? arguments[0] : window.event;
                  alert('You just clicked the DIV tag with an ID of ' + e.target.id);
            }
        })
})();

There we have a basic class that hooks into the HTML, adds some onclick events, and then gracefully cleans up after itself when the page is unloaded.

Now, in our XSP markup we can reference our class as well. For example, we know the controller script is going to place our controller in the window.vendorRecord object, so in our XSP code, we can hook into the class as well.

For example, we could add a button that calls the clickAlert function from our class above:

    <xp:button id='inactivateVendor' value="Inactivate'>
        <xp:eventHandler event="onclick" submit="false" script="window.vendorRecord.clickAlert(arguments);">
        </xp:eventHandler>
    </xp:button>

This is just the start of the capability of what you can do, and the portability you can create in your code. I'll have some downloadable samples soon that you can download and look through an actual implementation.

In the mean time, Happy Coding, and stayed tuned to the XPages Blog for more tips and tricks!

Sunday
Mar142010

Writing Client-Side Javascript for Re-Use, Part III


This is the third post in a series on reuseable javascript objects. If you have not read them yet, start with the first and second posts.

Now, lets discuss how we get dojo to load our new class from an NSF. Normally you would do this by including a dojo module through the XPage's resources tab, or you would use the javascript code:

dojo.require('com.ZetaOne.widget.listControl');

Here's where we have to perform a little "magic" to make dojo aware of our javascript repository that exists in an nsf.  You see, all dojo javascript is located in the domino\js\ folder of your data directory, and this folder is mapped to /domjs on the webserver.  If we were to ask dojo to load our class "com.ZetaOne.widget.listControl" it would look for the file called "listControl.js" in the /domjs/dojo-1.3.2/com/ZetaOne/widget folder of our webserver. Which it won't find.

So, I've made a slight modification to the dojo.require function, and named it dojo.requireFromNsf so we can easily require javascript objects located in any nsf.  You'll need to download this file, and place it in the folder:

[DOMINO DATA]/domino/js/dojo-1.3.2/dojo/_base/_loader

name the file loader_nsf.js

The file you are placing is called loader_nsf.js. This file is NOT obfuscated or compressed at this point, so you can review it. I'd recommend compressing it before rolling out into production.

The dojo.requireFromNsf() function takes an extra parameter that dojo.require does not. It's the base path to our reusable javascript library NSF. So, to load our listControl widget, we'd include the following line in a client side javascript library.

dojo.requireFromNsf('com.ZetaOne.widget.listControl', '/zojs.nsf/');

The '/zojs.nsf/' is the path the re-usable javascript component database you created from post #2. Make sure to include the full path, including the beginning and ending '/'. For more details about the dojo.requireFromNsf function, review the source code.

Now, we can load our javascript code from our repository. To do so, let's open the database where we are implementing the sample listControl widget, and create a client side javascript library.  Let's practice our namespacing here so create the library with a name that represents your reverse domain and put it in the app namespace, along with listControlSample.js as the actual class name. For example, my fully qualified javascript library name would be com/ZetaOne/app/listControlSample.js .

Let's include our basic class construct:

dojo.provide("com.ZetaOne.app.listControlSample");

(function(){
   
        dojo.declare("com.ZetaOne.app.listControlSample", null, {
            constructor: function() {

            },
            destroy: function() {

            }
        })
})();


Then, lets add the code to requireFromNsf our listControl widget, and a line to alert us that our class has been created so we can see it in action:

dojo.provide("com.ZetaOne.app.listControlSample");

dojo.require("dojo._base._loader.loader_nsf");
dojo.requireFromNsf('com.ZetaOne.widget.listControl', '/zojs.nsf/');

(function(){
   
        dojo.declare("com.ZetaOne.app.listControlSample", null, {
            constructor: function() {
                alert('com.ZetaOne.app.listControlSample Created!');
            },
            destroy: function() {

            }
        })
})();

And for now, lets add a line to end of this library to actually create an instance of our new class. Normally, we would seperate this out into a different controller script rather than in the class script itself, but we'll review that later.  So for now, and the following line to the end of the script:

window.listControlSample = new com.ZetaOne.app.listControlSample();

Now, save and include this client side javascript library in the xpage you created in Part II.  You should be able now preview your XPage in the browser, and watch it load the com.ZetaOne.widget.listControl object from your NSF javascript repository, AND you should be able to select different nodes in the list. In my next post I'll post a live sample of this in play and have a set of downloadable sample databases, as well as explain how to further this functionality to your individual XPages and custom controls to create a TRUE Model-View-Controller implementation of your XPage apps, with your own re-usable javascript component library.

Happ Coding!

Sunday
Mar142010

Writing Client-Side Javascript for Re-Use, Part II

This is the second post in a series on reuseable javascript objects. If you have not read it yet, start at the first post here.

Now, let's continue on by creating a new, blank database somewhere on your Domino webserver to serve as your new re-usable javascript code repository. A simple URL denoting reusable javascript objects is probably best. For example, at ZO, I've created a database titled "ZetaOne Javascript Repository" in the root of the data directory called zojs.nsf.  Give anonymous reader access to the database so it can be referenced from any application. In that new database, lets create a new client side javascript library. with the name "com/ZetaOne/widget/listControl.js".  The name is important.  The script library name needs to match what we are going to define our new class as. I'll explain why later.

Lets get started by entering a few basic lines to define the actual class:


dojo.provide("com.ZetaOne.widget.listControl");

(function(){
        dojo.declare("com.ZetaOne.widget.listControl", dijit._Widget, {
            create: function(params, srcNodeRef) {

            },
            destroy: function() {

            }
        })
})();

This is the very basic definition if our new class. the first line "dojo.provide" plugs our new class into the dojo runtime, so dojo knows it has been loaded. This is important, as different objects can reference the same class multiple times, but it will only be loaded one time to save download time and bandwidth, and to prevent existing data from getting over-written by one module reloading an existing module.

The next line "(function(){" creates whats known as an anonymous function that allows us to define our new "class". Which is where the third line "dojo.declare" comes in. That block of code creates a new "class" called com.ZetaOne.widget.listControl that inherits from dijit._Widget (dijit._Widget is the base class in dojo from which most all UI controls are derived. We won't get into why we're inheriting from this class in this post, just know that we have basically now extended dijit._Widget).  Our new class also has two new members "create" and "destroy" which are just like standard constructors and destructors in languages that implement a real class.

We can add additional member objects, variables and functions by declaring them just like the create and destroy functions are defined.  For reference, here is the entire implementation of the com.ZetaOne.widget.listControl object as it has been implemented as a class object. We won't get into the nitty gritty of what is going on with this class, but it's here as an example. In a later post, we'll get deeper into this actual object to describe the mechanations at work.

dojo.provide("com.ZetaOne.widget.listControl");

(function(){
        // PUBLIC MEMBERS
        dojo.declare("com.ZetaOne.widget.listControl", dijit._Widget, {
            create: function(params, srcNodeRef) {
                    if (typeof srcNodeRef == 'undefined') {
                        console.error('com.ZetaOne.widget.listControl requires a DOM node!');
                        return false;
                    } else
                        this.domNode = srcNodeRef;
        
                    this.id = this.domNode.id;
                    this.inherited(arguments);
                    this.selectMultiple = dojo.attr(this.domNode, 'selectMultiple');                            
                    this.groupClass = dojo.attr(this.domNode, 'groupClass');
                    this.itemClass = dojo.attr(this.domNode, 'itemClass');
                    this.selectedClass = dojo.attr(this.domNode, 'selectedClass');
                    this.lastSelectedClass = dojo.attr(this.domNode, 'lastSelectedClass');                    
                            
                    this.selectMultiple = (this.selectMultiple == null) ? "true" : eval(this.selectMultiple);
                    
                    this.groupClass = (this.groupClass == null) ? 'zoListControlGroup' : this.groupClass;
                    this.itemClass = (this.itemClass == null) ? 'zoListControlItem' : this.itemClass;
                    this.selectedClass = (this.selectedClass == null) ? 'zoListControlSelected' : this.selectedClass;
                    this.lastSelectedClass = (this.lastSelectedClass == null) ? 'zoListControlLastSelected' : this.lastSelectedClass;
                                    
                    this._itemClassQuery = '.' + this.itemClass;
                    this._groupClassQuery = '.' + this.groupClass;
                    this._selectedClassQuery = '.' + this.selectedClass;
                    this._lastSelectedClassQuery = '.' + this.lastSelectedClass;
                
                    if (dojo.isIE)
                        document.onselectstart = function() { return false; }
                    else {            
                        document.onmousedown=this._disableselect;
                        document.onclick=this._reEnable;
                    }
                    
                    // associate onclick events
                    var selectables = dojo.query(this._itemClassQuery);
                    for (i=0;i<selectables.length;i++) {
                        dojo.addClass(selectables[i], this.groupClass);
                        this.connect(selectables[i], 'onclick', this._captureClick);
                    };
                    
                    return;                            
            },
            destroy: function() {
                    dijit.registry.remove(this.id);
                    dojo.forEach(this._connects, this.disconnect);
                    this.inherited(arguments);        
            },
            pop: function() {
                    var nodes = dojo.query(this._groupClassQuery + this._selectedClassQuery);
                    for (i=0;i<nodes.length;i++) {
                        nodes[i].parentNode.removeChild(nodes[i]);
                        this.disconnect(this._connects[nodes[i].id]);
                    }
                    nodes.removeClass(this.selectedClass);
                    nodes.removeClass(this.itemClass);
                    nodes.removeClass(this.groupClass);
                    nodes.removeClass(this.lastSelectedClass);
                    return nodes;                    
            },
            push: function(nodes, select) {
                    nodes.addClass(this.groupClass);
                    nodes.addClass(this.itemClass);
                    if (select) nodes.addClass(this.selectedClass);
                    
                    for (i=0;i<nodes.length;i++) {
                        this.domNode.appendChild(nodes[i]);
                        this.connect(nodes[i], 'onclick', this._captureClick);
                    }
            },
            connect: function(
                    /*Object|null*/ obj,
                    /*String|Function*/ event,
                    /*String|Function*/ method){
                var d = dojo;
                var dc = dojo.connect;
                this._connects[obj.id] = dc(obj, event, this, method);
                return this._connects[obj.id];
            },
            disconnect: function(/*Object*/ handle){
                // summary:
                //        Disconnects handle created by this.connect.
                //        Also removes handle from this widget's list of connects
                // tags:
                //        protected
                var i = dojo.indexOf(this._connects, handle);
                dojo.disconnect(handle);
                this._connects.splice(i, 1);
                return;
            },            
            // PRIVATE MEMBERS    
            _selectItem: function(selection, e) {
                    
                    var selNode = dojo.byId(selection);
                    
                    if (((!e.ctrlKey) && (!e.shiftKey)) || !this.selectMultiple) {
                    
                      dojo.query(this._groupClassQuery + this._itemClassQuery).removeClass(this.selectedClass);
                      dojo.addClass(selNode, this.selectedClass);
                    
                    } else if ((e.ctrlKey) && (!e.shiftKey))
                    
                      if (dojo.hasClass(selNode, this.selectedClass))
                        dojo.removeClass(selNode, this.selectedClass)
                      else
                        dojo.addClass(selNode, this.selectedClass);
                    
                    else if (e.shiftKey) {
                    
                      var idx = dojo.indexOf(selNode.parentNode.childNodes, selNode);
                      var lastIdx = dojo.indexOf(selNode.parentNode.childNodes, dojo.query(this._groupClassQuery +
                              this._groupClassQuery + this._lastSelectedClassQuery)[0])
                    
                      if (idx == lastIdx) {
                          // user just shift clicked the same node again, don't do anything
                          return true;
                      }
                
                      if (!e.ctrlKey) {
                        // clear all previous selections first, re-add lastSelected, then process on
                        dojo.query(this._groupClassQuery + this._selectedClassQuery).removeClass(this.selectedClass);
                        dojo.query(this._groupClassQuery + this._selectedClassQuery).addClass(this.selectedClass);
                        dojo.addClass(selection, this.selectedClass);
                      }
                      
                      if (lastIdx == -1) {  // the last selected was not in the same parent, so we can't shift click, so just reset the focused nodes and select the single node ...
        
                        dojo.query(this._groupClassQuery + this._selectedClassQuery).removeClass(this.selectedClass);      
                        dojo.addClass(selNode, this.selectedClass);
        
                      } else {
        
                          if (idx < lastIdx) {
                            var el = selNode;
                            for ( ; lastIdx >= idx; idx++) {
                              if ((el.nodeType == 1) && (dojo.hasClass(el, this.groupClass)))
                                dojo.addClass(el, this.selectedClass);
                              el = el.nextSibling;
                            }        
                          } else if (idx > lastIdx) {
                            var el = selNode;
                            for ( ; lastIdx <= idx; idx--) {
                              if ((el.nodeType == 1) && (dojo.hasClass(el, this.groupClass)))
                                dojo.addClass(el, this.selectedClass);
                              el = el.previousSibling;
                          }
                        }        
                      }    
                    }
                    
                    dojo.query(this._groupClassQuery + this._lastSelectedClassQuery).removeClass(this.lastSelectedClass);
                    dojo.addClass(selNode, this.lastSelectedClass)
            },    
            _disableselect: function(e) { return false; },
            _reEnable: function() { return true; },
            _captureClick: function() {
                var e = (!arguments[0]) ? window.event : arguments[0];
                var selection = (e.target) ? e.target : e.srcElement;
                this._selectItem(selection.id, e);
        
            }
        })
})();    

Copy and paste that code into your "com/ZetaOne/widget/listControl.js" library, then save and close it.
Now in a different database, let's create an XPage that consumes this object. First create an XPage with the following code, making sure to set dojo ParseOnLoad and dojoTheme to true in the XPage's All Properties:

        <xp:div dojoType="com.ZetaOne.widget.listControl" styleClass="sourceUserList"
            id="userSourceList">
            <xp:this.dojoAttributes>
                <xp:dojoAttribute name="groupClass" value="userGroup" />
                <xp:dojoAttribute name="itemClass" value="userItem" />
                <xp:dojoAttribute name="selectedClass" value="focused" />
            </xp:this.dojoAttributes>
            <xp:repeat value="#{javascript:return new Array('Item 1','Item 2', 'Item 3', 'Item 4');}" var="profileVar" indexVar="profileIndex"
                rows="4">
                <xp:div id="userSelectable" styleClass="userSelectable userItem">
                    <xp:text escape="true" value="#{profileVar}" disableTheme="true" />
                </xp:div>
            </xp:repeat>
        </xp:div>


There are a couple of things to point out in the above code. First, notice the first xp:div tag has a dojoType that matches the class we defined earlier "com.ZetaOne.widget.listControl".  This instructs dojo to create an object out of this HTML element based on that class.  The <xp:dojoAttribute> tags are used to pass parameters to the constructor function of the class. Additionally, if you have been following along in this series so far, you might notice that we are don't have code in the XSP markup to handle the onClick events when the user selects an item. This has been automated in the class, and we'll review in a future post.

To make this a working model, you'll also need to assemble the Cascading Style Sheet from the previous two posts, place it in the database, and include it as a reference on this page.
We now have a complete implementation of the listControl object in an application, BUT we still need to tell the browser to actually load the javascript class that we created in our new javascript repository.

We'll cover that in Part 3.

Sunday
Mar142010

Writing Client-Side Javascript for Re-Use

Many of us "grew up" through the evolution of Lotus Notes and Domino and have been conditioned to not use our tools in the the cleanest, most effective manner. For example, quite often we have scores of LotusScript libraries, chocked full of one-off sub-routines, functions that work together, but have no logical connection in code like a class, and quite frequently these libraries are not well document, coded "ugly" instead of readable, and have lots of "legacy implementations" making it difficult for us to adopt better, if not best practices.

With the advent of XPages, and a new set of tools with which to build our applications, we get a unique opportunity to "get it right" from the beginning. The thoughts of "best practices" and how to implement Server Side and Client Side Javascript libraries for re-use in XPages has been on my mind heavily of late. I've come to the conclusion that we don't have to re-invent the wheel. We already have a powerful model to follow in Dojo.

What does this mean? Well over the next few posts, I'll take a look at a few concepts, and some practical applications around them.

Lets talk first about client-side javascript and namespaces.

If you haven't spent a lot of time learning the intricacies of how Dojo, or any other javascript framework works, such as Ext, jQuery, or the like, and haven't delved into Java, you may not be that familiar  with the concept of namespaces.  While the full bredth and scope of why namespaces are a "best practice" and what they mean overall is beyond this post, let me briefly describe them and point out some obvious reasons why they are a good idea.

If you looked at all into using dojo in XPages, or looked at the XSP object at all, you may have noticed how all the dojo modules are named similar to "dojo.dnd.Source" or "dijit.layout.BorderContainer". Quite simply these are "namespaces" and are meant to uniquely identify a block of code, similar in concept to classes. While javascript does not have classes per se, the flexibility of the language has allowed the creation of constructs that give javascript objects class-like functionality.

From the simplest perspective, these namespaces help us to keep code from one script from stepping on code from another. The flexibility inherent in javascript to .prototype objects, or to create objects without private or protected members easily allows this to happen.

So as a best practice, you should start to define your own namespaces in your code, creating a base of re-usable components that you can use from XPage application to XPage application.

That being said, because domino is SO good at being a distributed platform, it often makes it difficult to effectively share objects between applications. Especially javascript libraraies. Code control can become a nightmare as you develop revisions, and then place them in different nsf stores. Sure you can do overly complex multi-template inheritance to create a psuedo code-revision control "framework", but it is very fragmented at best.

What I've done here at ZetaOne is to create a single script library database for all re-usable client side Javascript libraries. What this allows me to do is "require" any reusable piece of javascript code from this single library. Every logical block of code is then coded into a namespace, designed after the "Dojo" way of defining namespaces and objects. As we continue this series, we'll start to build onto these namespaces to enable greater and greater functionality, and integration into the dojo namespace.

So, lets look specifically at what at namespace is, and how to implement it. Simply put, namespaces are a unique identifier assigned to a block of code. The "Dojo Way" is to start with a base names set (in dojo's namespace, its "dojo", and then from there you add a "." and then the name of the "class" you want to implement. For example, all dojo drag and drop functionality is located under the "dojo.dnd" namespace.

Ok, so what do we use for our own namespace?  Well, the accepted convention is your reverse domain name. For example, all my code is under the "com.ZetaOne" namespace. I then add on from there for each particular class or application I create.  For example, as I build reusable objects that represent backend functions and operations, I create them under the "com.ZetaOne.api" namespace.  Objects that are UI controls, for users to interact with are under the "com.ZetaOne.widget" namespace. In single database applications, each application has it's own namespace under "com.ZetaOne.app", and larger, multi-applcation, multi-database systems usually get their own namespace directly under "com.ZetaOne", such as "com.ZetaOne.AppName".

Ok, so there is the basics on what we call our namespace, how do we actually define it? Well, in my next post, i'll start by taking the HTML selection function I wrote about in my last few posts, and convert it to a re-usable, namespaced object.

Stay tuned and Happy Coding!