IE 8 Ext bug- searchSortBy?

I just ran into a problem with an ExtJS application I am building that involves a bug in ExtJS for Internet Explorer, or perhaps a bug in IE itself. I created an Ext ComboBox with an ID of searchSortBy. When referencing the ComboBox by ID in a save function, I set a local variable:

   var ssb = Ext.getCmp("searchSortBy");

Which works just fine. But referencing:

   ssb.getValue();

yields an error of "Object does not support this property or method", meaning that the object being referenced is not the same as the object that I created. A simply ID change solved the problem, but it serves as a reminder of the continuing complexities of Javascript-basedWeb application UIs.

PureMVC for Javascript

While working on a large-scale ExtJS-based Javascript UI, I became acutely aware of the limitations of the minimalist code architecture that dominated Javascript code development over the last several years. Accordingly, I was very pleased to see that there is a Javascript port of the PureMVC application framework in the works. It is in beta at the moment, but I will be looking at it in conjunction with ExtJS as the architectural basis of our Javascript-based application UIs moving forward.

ColdFusion Struct Key Names and JS/AS Code

If you use ColdFusion in conjunction with Javascript libraries like ExtJS or Actionscript in Flex or Flash, you are probably used to seeing ColdFusion structures rendered in JS/AS in ALLCAPS, like so:

 

CF code:
<cffunction name="getStruct" access="remote" returntype="struct">
     <cfset mystruct = structnew()/>
     <cfset mystruct.somekey = "somevalue"/>
     <cfreturn mystruct>
</cffunction>

JS result:
// a little pseudo-code
myObject = myService.getStruct();

//alert will show "somevalue"
alert(myObject.SOMEKEY);

By default, ColdFusion translates all struct keys into all caps when it returns a structure via a Web service or AMF call. If you would rather maintain your struct keys in lowercase or camelCase, you can simply use bracket notation instead of dot notation. Personally, I prefer dot notation for convenience, but sometimes I encounter a situation where dot notation would not work because the keyshave spaces or other characters that are illegal in ColdFusion variable names. As we can see with this example, there is another good reason to use bracket notation - case matching between client and server struct keys.

If we change our ColdFusion code to use bracket notation instead of dot notation, we'll get the keys in the case they were written:

CF code:
<cffunction name="getStruct" access="remote" returntype="struct">
     <cfset mystruct = structnew()/>
     <cfset mystruct["someKey"] = "somevalue"/>
     <cfreturn mystruct>
</cffunction>

JS result:
// a little pseudo-code
myObject = myService.getStruct();

//alert will show "somevalue"
alert(myObject.someKey);

 

ExtJS Tree with Context Menu

I've been playing with the tree control in ExtJS, with an eye toward building a decent tree control to handle management of tree-based content, generally web pages in a basic web site layout. After some experimentation, I put together a tree that has a context menu and supports drag and drop of nodes. I am looking to extend it to enable drag and drop for external objects into the tree. 

Here is a screenshot, right click and select "View Image" to see a full size version:

click for full size image

Introducing the Colony application platform

For the past three years, I have been working on and off on an open-source CFML-based Web application. It  started out as a simple system to store arbitrary structured and unstructured content. I started using it to build more and more complex Web applications, and over time it grew in size and scope. I thought about it for awhile as a content management system, but content management is not what I was aiming for, and not where the platform has really evolved.

After struggling with terminology and purpose, I started thinking about the application as an application platform. What is that? I see it as an implementation of typical application patterns in an integrated package that allows a develoepr to use it in whole or in part, building on the core libraries to create a new solution. 

Once I had the concept clear in my head, I started casting about for a name. After lots of pondering and brainstorming sessions with my colleagues on the CF-Community list, I decided to call the platform Colony. To me, Colony is all about staking out new territory on the Web, building compelling new services, and advancing the state of software.

Colony is also about shared effort and shared reward. To that end, we have just released the platform in alpha under the Apache Software License 2.0. You can get the alpha code and see more about the platform at www.cfcolony.org. The site is graphically challenged and light on content at the moment, but that wil cahnge soon. 

ColdFusion and ExtJS - Pre-populating a TreePanel in Ext

If you have used ExtJS, you know that it contains a rich TreePanel control that, dare I say, rivals Flex in its functionality. The TreePanel control offers ColdFusion developers a very easy way to pre-populate the nodes of the Tree when the Tree loads, using nothing but Javascript. Take a look at the code below. (Note that this entire code block, and other Ext code, would normally be enclosed in an Ext.onReady(function(){}); command, but that code has been truncated for brevity. 

The first line creates a new Ext.tree object. The next section creates a TreePanel object to display the contents of the tree on the page. Note that the TreePanel has an attribute called loader that is tied to a TreeLoader object. The TreeLoader allows us to specify a URL that will be called to load the array of nodes for the tree. The last section of the code renders the tree and expands thenodes to be visible once the tree renders.

    var Tree = Ext.tree;
   
    var tree = new Tree.TreePanel({
        el:'tree-div',
        useArrows:true,
        autoScroll:true,
        animate:true,
        enableDD:true,
        containerScroll: true,
           rootVisible: false,
        root: {
            nodeType: 'async',
            text: '--',
            draggable:false,
            id:'0'
           
        },
         loader: new Ext.tree.TreeLoader({
          url:'/services/ObjectProxy.cfc?method=getChildren',
          requestMethod:'GET',
           preloadChildren: true,
          baseParams:{}
        })
    });

    // render the tree
    tree.render();
    tree.getRootNode().expand();

In this code, the TreeLoader calls url:'/services/ObjectProxy.cfc?method=getChildren'. Let's take a look at that function to see what data the tree needs to populate its nodes. This code is taken from our forthcoming open source application platform in its RoseCMS implementation. 

 

    <cffunction name="getChildren" access="remote" returntype="string" returnformat="plain">
        <cfset var applicationFacade = application.RoseBeanFactory.getBean("applicationFacade")/>
       
        <cfset var cmTreeManager = application.RoseBeanFactory.getBean("cmTreeManager")/>
        <cfset var cmTreeNodeManager = application.RoseBeanFactory.getBean("cmTreeNodeManager")/>
        <cfset var treeID = cmTreeManager.getcmTreesQuery(TreeName="Site Navigation").qList.TreeID />
        <cfset var json = createobject("component","views.util.json")>

            <!--- otherwise, get the tree from the database and set it into a TreeWalker --->
            <cfset siteNavTree= application.RoseBeanFactory.getBean("cmTreeWalker") />
            <cfset treeNodesQ = cmTreeNodeManager.getcmTreeNodesQuery(TreeID=treeID,orderby="ParentID, sortOrder asc") />
            <cfset siteNavTree.setTree(treeNodesQ.qList) />
            <cfset applicationFacade.set("siteNavTree",duplicate(siteNavTree))/>       
            <cfset tarray = json.encode(siteNavTree.getTree(),"array") />
           
        <cfset tarray = replaceNoCase(tarray,"treenodeid","id","all")/>
        <cfset tarray = replaceNoCase(tarray,"nodename","text","all")/>
        <cfset tarray = replace(tarray,"PARENTID","parentid","all")/>
        <cfset tarray = replace(tarray,"CHILDREN","children","all")/>
        <cfset tarray = replace(tarray,"SORTORDER","sortorder","all")/>
        <cfset tarray = replace(tarray,"OBJECTID","objectid","all")/>
        <cfset tarray = replace(tarray,"TYPEID","typeid","all")/>
        <cfset tarray = replace(tarray,"TREEID","treeid","all")/>
        <cfset tarray = replace(tarray,"TYPENAME","typename","all")/>
        <cfset tarray = replaceNoCase(tarray,"typename"":""webPage""","typename"":""webPage"",""icon"":""/scripts/ext/resources/images/default/tree/leaf.gif""","all")/>
        <cfset tarray = replaceNoCase(tarray,"typename"":""menuSection""","typename"":""menuSection"",""icon"":""/scripts/ext/resources/images/default/tree/folder.gif""","all")/>
     
        <cfreturn tarray />
    </cffunction>

Again for brevity, I will skip the first section of the code and focus on the code that retrieves the tree in question and formats the nodes. 

This line gets a cmTreeWalker object from ColdSpring:
<cfset siteNavTree= application.RoseBeanFactory.getBean("cmTreeWalker") />

cmTreeWalker is a mini-TreeWalker object designed to hold the contents of a tree. For now, let's just understand that it contains the definition of the Tree and its nodes and their relationships.

This line gets the nodes of the tree out of the database:
<cfset treeNodesQ = cmTreeNodeManager.getcmTreeNodesQuery(TreeID=treeID,orderby="ParentID, sortOrder asc") />

This line feeds the nodes into the cmTreeWalker object for organization into a tree:
<cfset siteNavTree.setTree(treeNodesQ.qList) />

This line drops the tree into the application scope, we're not concerned with it at this time:
<cfset applicationFacade.set("siteNavTree",duplicate(siteNavTree))/>   

This line encodes the array of nodes into JSON format, which is what the TreeLoader object is expecting to receive:   
<cfset tarray = json.encode(siteNavTree.getTree(),"array") />

The last section performs string manipulation on the encoded array. Without going into the reason for the string manipulation, I really like being able to perform string manipulation on JSON-encoded data like arrays.

But what do we actually get back? Take a look at the return from the browser. I have added line breaks for readability, but you get the picture.

[{"typeid":24,"id":1,"sortorder":1,"objectid":1,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"home","treeid":2,"parentid":"","children":
[{"typeid":30,"id":8,"sortorder":1,"objectid":13,"typename":"menuSection","icon":"/scripts/ext/resources/images/default/tree/folder.gif"
,"text":"Services","treeid":2,"parentid":1,"children":
[{"typeid":24,"id":22,"sortorder":0,"objectid":28,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Overview","treeid":2,"parentid":8,"children":
[]},{"typeid":24,"id":21,"sortorder":1,"objectid":27,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Staffing Solutions","treeid":2,"parentid":8,"children":
[]},{"typeid":24,"id":24,"sortorder":2,"objectid":30,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Project Based Consulting","treeid":2,"parentid":8,"children":
[]},{"typeid":24,"id":34,"sortorder":4,"objectid":41,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Strategic IT Consulting","treeid":2,"parentid":8,"children":
[]}]},{"typeid":30,"id":26,"sortorder":2,"objectid":32,"typename":"menuSection","icon":"/scripts/ext/resources/images/default/tree/folder.gif"
,"text":"Case Studies","treeid":2,"parentid":1,"children":
[{"typeid":24,"id":31,"sortorder":1,"objectid":37,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Business Credit Services","treeid":2,"parentid":26,"children":
[]},{"typeid":24,"id":32,"sortorder":2,"objectid":38,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Delaney Educational Enterprises","treeid":2,"parentid":26,"children":
[]},{"typeid":24,"id":33,"sortorder":3,"objectid":39,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Invitrogen","treeid":2,"parentid":26,"children":
[]}]},{"typeid":30,"id":11,"sortorder":3,"objectid":17,"typename":"menuSection","icon":"/scripts/ext/resources/images/default/tree/folder.gif"
,"text":"Company","treeid":2,"parentid":1,"children":
[{"typeid":24,"id":19,"sortorder":1,"objectid":25,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Who We Are","treeid":2,"parentid":11,"children":
[]},{"typeid":24,"id":17,"sortorder":2,"objectid":23,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Management","treeid":2,"parentid":11,"children":
[]},{"typeid":24,"id":12,"sortorder":3,"objectid":18,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Mission Statement","treeid":2,"parentid":11,"children":
[]},{"typeid":24,"id":18,"sortorder":4,"objectid":24,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Safe Harbor Policy","treeid":2,"parentid":11,"children":
[]}]},{"typeid":30,"id":27,"sortorder":5,"objectid":33,"typename":"menuSection","icon":"/scripts/ext/resources/images/default/tree/folder.gif"
,"text":"Community","treeid":2,"parentid":1,"children":
[{"typeid":24,"id":28,"sortorder":1,"objectid":34,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"San Diego Adobe Developers User Group ","treeid":2,"parentid":27,"children":
[]},{"typeid":24,"id":30,"sortorder":2,"objectid":36,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"March Mingle","treeid":2,"parentid":27,"children":
[]}]},{"typeid":24,"id":20,"sortorder":6,"objectid":26,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Blog","treeid":2,"parentid":1,"children":
[]},{"typeid":24,"id":6,"sortorder":8,"objectid":11,"typename":"webPage","icon":"/scripts/ext/resources/images/default/tree/leaf.gif"
,"text":"Contact Us","treeid":2,"parentid":1,"children":
[]}]}]

The function creates a JSON-encoded representation of the tree nodes that Ext can digest.

Next time I will go into some detail about the format of the array of nodes in ColdFusion to give you an idea of how to format the data on the server before sending it off to Ext.

ColdFusion and ExtJS - Ext.Ajax.request

Lately we have been working with the ExtJS 2.2 library on a large ColdFusion-based project, and I wanted to share some of the techniques we are using to implement AJAX-based functionality.

At its core, AJAX functionality is all about building a richer experience on the client, and that richer experience generally starts with refreshing data on the screen without refreshing the entire page from the server. With Ext, we are often accomplishing this task by sending a request via Ext.Ajax.request() and replacing div contents with HTML fragments returned from the server. Developers can use Ext to draw more compelling UI elements programmatically, but we have found that in many cases, simply using Ext as a transport for HTML fragments serves our needs just as well. 

Just as often, we combine the use of HTML fragments with returned JSON-encoded objects to build an interface that is both easy to construct from a developer/designer perspective and has the ability to handle complex data.

In this code sample, the Javascript function getAccountSummary() calls a like-named method on the server, which checks the user's session information and returns both a form filled out with the user's details and several JSON-encoded objects. In particular, the addresses object contains an array of addresses that the user can edit in the account form, using more JS magic.

     function getAccountSummary(divID){
        setCursor('wait');
        Ext.Ajax.request({
         
          url: '/services/IdentityProxy.cfc?wsdl&method=getAccountSummary',
          
          success: function(response,options){
              var main = document.getElementById("main");
              var ret = Ext.util.JSON.decode(response.responseText);
              user = ret.USER;
              userphone = ret.PHONE;
              userfax = ret.FAX;                           
            addresses = ret.ADDRESSES;
                     
            main.innerHTML=ret.body;       
            showDiv("cancelpwdEdit","hide");     
            showDiv("pwdconfirm","hide");     
            setCursor('default');

          },
          failure: function (response,options){
              alert(response.responseText);
              setCursor('default');
              },

           params:{returnFormat:'JSON'}
        });
               
    } 

As you can see in the function, the returned data is set into variables that are scoped to the browser, meaning they are accessible to other Javascript functions. The div called "main" is then populated with the account form using main.innerHTML = ret.body. 

This technique suffers from a potential security risk (doesn't everything?)- cross-site scripting attacks. There are ways to guard against XSS attacks, more on that another time. 

BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.