Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Some thoughts on a major SmartClient Application out the door

    We recently completed and deployed a major application written in SmartClient and for interested readers I've put together a few notes about how our application is architected and our overall experience.

    In terms of actual deployment of SmartClient, we've had no issues whatsoever with SmartClient itself. 6.5 and the TreeFrog skin came out just in time to be included, and we've had no issue with crashes or mysterious behavior. The client base runs IE6 on contemporary hardware and performance has been fine. This is an intranet application, used by many people throughout our company.

    This project has been in the works for about a year, with the first 4 months or so getting up to speed with basic fundamentals of SmartClient. Things like how to make the various types of callbacks work and how to use closures took a little experimentation. Since our app is object oriented, we don't have global references to things, so the "this.getID() + ".method()" paradigm is used often. When building complex Windows, we also use the "parent: this" trick to allow us to call methods of the Window from sub-objects like ListGrids and DynamicForms. One of the most challenging things, due to the complexity or our app, was getting scope chain resolutions to work right so we could call the functions at various levels of Window widget hierarchy and object hierarchy. Once these basic "best practices" were worked out, most things were amazingly easy to do in SmartClient.

    Our app uses PHP in the middle tier and Oracle on the backend.

    The client application is driven by an Application base class (client-side controller) we wrote that handles requests from the MenuBar to instantiate and initialize ChildWindows that provide actual functionality. The Application object creates a MenuBar and Canvas, into which it draws all ChildWindows. The Application object also controls which users can access each ChildWindow, via a permissions mechanism. Each ChildWindow adheres to a standard design (actually subclassing the same base class), registering itself with the Application object to indicate whether it's a singleton or should get destroyed and created each time it's needed. The window also communicates it's permissions parameters so the Application can deny access to unauthorized users.

    As part of the standard ChildWindow implementation, an initialization method is provided, which if defined is called by the client-side controller (Appliction object) and takes data needed to initialize the ChildWindow (typically a master ID used to fetch detail records).

    Each ChildWindow is defined in a like-named Javascript file, which allowed us to implement a Developer's menu that re-fetches the .js file and re-instantiates and re-initializes the ChildWindow. This was a huge time saver during development and testing, allowing us to walk a complex UI path to the window under development and then dynamically re-load it to test changes to the underlying Javascript code without reloading the entire application.

    The PHP middle tier consists of a middle-tier controller that brokers requests to ServerObjects to satisfy both Data Bound Control operations (requests from ListGrids and DynamicForms) and individual RPC calls. We used a method whereby we would specify the middle-tier object and method to be called in the GET (?m=Object.Method) and all parameters in the POST. Data to/from the client application is serialized to JSON for transport.

    Business logic is present in both PHP and Oracle stored procedures as appropriate.

    From a deployment and release management perspective, we use a set of build/release scripts that automate the production of releases.

    We run a nightly job on the Javascript code base that gathers various metrics and runs jslint on all .js files. This keeps things in check and in particular highlights any dangling-comma issues that would break IE but not be seen in development because of course we use Firefox for that. For the SmartClient app, we "make" by concatenating all required .js files (based on a manifest list) into a single file, run jslint on it, run jsmin on it, and create a versioned release file in CVS.

    For middle-tier releases, we do a similar concatenation and produce a versioned server script in CVS.

    On our Apache server, the client application is launched by a PHP application launcher that inlines the latest version of the app (parameterized with a .ini control file) into an HTML file and sets various parameters into an object called isc.appData. This allows us to "bridge" parameters across from the PHP world into the SmartClient/Javascript world.

    Likewise, when the client makes calls to PHP middle-tier ServerObjects, they come through a wedge that ensures the proper client version/server version matchup is maintained when we do version upgrades during the workday.

    Now that we are deployed and live, I am more convinced than ever that building this application in SmartClient was the right decision. There is absolutely no way we could have produced the richness of user experience with a page-based application, and no other AJAX GUI solution I could find had nearly the completeness of API's that SmartClient provides. You don't really get to understand this until you work with SmartClient for a few months. First the developers see the light; then the users get their eyes on a ListGrid with filterEditor, and the are hooked too.

    [editor's note: fixed line breaks only]
    Last edited by Isomorphic; 23 Jun 2008, 16:59.

    #2
    Thanks so much for posting your experiences in such detail. The tools and processes you describe for working with SmartClient and PHP should be very useful to other PHP developers.

    We particularly liked your technique of piecewise reloading of the UI code in order to cut down on the code/test cycle. Our services team does this all the time when building applications. In fact, you can actually redefine a SmartClient class this way too, so long as you bear in mind that live instances aren't auto-upgraded.

    That kind of power really makes us wonder how anyone can stand to wait for typical Eclipse servlet engine "redeploy" cycles, which seem to range from 45 seconds to 5 minutes in even medium-size Java projects, and to which you must then add servlet engine startup and initialization, page load time, and getting the application into the target test state.

    We prefer the ~1 second code/test cycle that comes from not even having to reload the web page :)

    Kudos on your successful deployment and just wait til you see 7.0 :)

    Comment


      #3
      Hi

      Our application is similar to yours but was migrated from another JS implementation using some third party controls. We had used the prototype library and we are still continuing to use it with SC. We moved to SC as third party controls were incompatible when it came to resizing and layouts. We have been using MVC kind of approach - like each view extending from a standard base class and using model/controller class for interaction with server side objects/views Server side implementation is Java Servlets using JDBC.

      You said that the challenging task was getting scope chain resolutions to work right. If you can please elaborate on it, that would be great.
      Our first application took 1 year to develop without using SC and we took about 3 months to move as the foundation was already built.

      We are still learning SC and found couple of issues while resizing/layout. The scrolling of larger grids (about 1000 rows) takes the CPU to its max. Offcourse we are using pagination for anything beyond 1000.

      Before going to SC, I have evaluated other toolkits and found that SC was
      the best interms of the extensive API, demos and docs.

      Lately I am finding that this forum was infact the best as we are getting the required help within a day.


      Raj Kamineni
      Adv Services
      Neustar, Inc (NSR)
      Product: Number Resolution System GUI for GSMA

      Comment


        #4
        Hi,

        I will soon be attempting to create an application framework like this. I was wondering if you could elaborate just a bit on one of the paragraphs:


        > Things like how to make the various types of callbacks work and how
        > to use closures took a little experimentation.

        I think I understand callbacks, but I'm wondering what is meant by "closures". Does that mean window closures?



        > Since our app is object oriented, we don't have global references
        > to things, so the "this.getID() + ".method()" paradigm is used often.

        This is getting the id of the current object, to call a method of the same object? I'm surmising that if I had a ListGrid called "myListGrid", in my flat model, I can call myListGrid.myMethod() from anywhere. But if the objects are in a hierarchy, then I would use this.getID.myMethod() from within myListGrid. But from another object, how would I call the method?

        Can you provide a quick example?



        > When building complex Windows, we also use the "parent: this" trick to
        > allow us to call methods of the Window from sub-objects like ListGrids
        > and DynamicForms.

        What is the "parent: this" trick, and why is it a trick? Can you show a quick example?




        > One of the most challenging things, due to the complexity or our app,
        > was getting scope chain resolutions to work right so we could call the
        > functions at various levels of Window widget hierarchy and object
        > hierarchy.

        What is "scope chain resolution"? Can you provide a quick example of this?


        Thanks,
        Mike

        Comment


          #5
          Hi,

          In order to handle class instantiation and inheritance, etc. is it recommended to use tools like this:

          http://flesler.blogspot.com/2008/06/jsclass.html

          or this

          http://www.kevlindev.com/tutorials/j...ance/index.htm

          Or can it be done by SmartClient natively using the ClassFactory?

          Or should I "roll my own" OOP JS ?

          Mike
          Last edited by msatkevich; 5 Sep 2008, 21:33.

          Comment


            #6
            Hi,

            When closing a window, my code thus far has been leaving the window in memory.

            I'd like to start "destroying" windows. Is there a preferred method for doing this? destroy() or clear() ?

            I would like for the user to be able to open the window again, but in an initialized state (not the state they left it when they clicked the close button).

            Is it also possible to unload the code for the window too? Then load it back in when needed again? Or is the code typically kept in memory for re-use?

            Thanks,
            Mike

            Comment


              #7
              SmartClient is already inherently object-oriented. Search the docs for ClassFactory, Class and Super.

              If you want to destroy a window on close, destroy() is the method to use. Be aware, however, that it is much more expensive to recreate a destroyed component than to simply hide and re-show it. If your reason for going this way is simply to reset state, you might consider calling a function to do this from the window's show() method (remember to call Super after your own code).

              Comment


                #8
                to msatkevich - Here is a contrived example that shows what I mean by my comments on the use of getID, closures, and the "parent: this" trick. See discussion below.

                Code:
                isa.PreferencesWindow.addProperties({
                
                    preferencesGroup: "BASIC",
                
                    initWidget: function() {
                        this.preferencesForm = isc.DynamicForm.create({
                
                            fields:[ { name:"saveButton", editorType:"button",
                                    align: "right", title:"Save",
                                    click: this.getID() + ".doSave();"
                                }]
                        }
                    }
                
                    this.preferencesDS = isc.DataSource.create({
                        parent: this,
                
                        transformRequest: function(dsRequest){
                            var superClassArguments = this.Super("transformRequest", dsRequest);
                
                            var preferencesGroupProperty = { PREFERENCES_GROUP: this.parent.preferencesGroup };
                
                            return isc.addProperties({}, superClassArguments, preferencesGroupProperty);
                        }
                    });
                
                    doSave: function() {
                        var preferencesGroup = this.preferencesGroup;
                
                        this.preferencesForm.saveData("isc.say(preferencesGroup + ' has been saved.')");
                
                    }
                
                }
                In the form button, we use getID() to build a string function to be executed on click. This allows us to call an object method of the preferencesWindow from anywhere in sub-widgets.

                When preferencesDS is constructed, it's "parent" property is set to the preferencesWindow ("this" at construction time), so we can then reference properties of the preferencesWindow from within the transformRequest. When transformRequest fires, "this" in the DataSource, so we need a reference to the parent object to get at properties of it.

                In doSave(), we use what's called a "closure" (lexical scoping I've heard it called) so allow preferencesGroup to be accessible in the saveData callback.

                Once again, this example is just to show these concepts, I can't speak for it's technical correctness or whether these techniques are actually need in the cases shown.

                Comment


                  #9
                  Thanks for this! I will be digesting this along with your first post for the next few months, as I try my hand at my own framework.

                  One thing that jumps to mind are shared datasources.

                  I have a list of items in one window shown in a datagrid for editing. The same data is used to populate a select list in another window. When the data is edited in the listgrid, the select list should be updated.

                  Would you create the datasource in a 3rd js file, to be shared between the two windows?

                  Mike

                  Comment


                    #10
                    If by "window" you mean isc.Window, the update will happen automatically, because all databound components in a given SmartClient application observe changes to the DataSource automatically. See the "ResultSet" documentation for details.

                    Comment


                      #11
                      Hi pallen (or Isomorphic),

                      I'm doing my best to come up with some "best practices" for splitting up my widgets into separate .js files.

                      I plan to follow your practice of storing each window's widgets in a like-named .js file.

                      If I was to do this as it stands, I would also have to split up my data sources, which up to now, have all been in one "dataSources.js" file.

                      But if I understand your approach correctly, is it possible you are using a single datasource for all databound widgets? And just dynamically setting the dataURL to reference the server object you want to talk with?

                      If not, do you store the data sources separately in their own .js file, or in the same .js file as the window in which they are used?

                      Isomorphic, feel free to speculate on pallen's approach, or suggest one that you think that will be best.

                      Thanks,
                      Mike

                      Comment


                        #12
                        We split our data sources into seperate files, with multiple data sources in each file based on logical groupings. Within each file, we add them into a global structure so they are all easily referenced. For example (detail omitted):

                        Code:
                        isc.DataSources.Contacts = {
                            phoneLineTypes: isc.JSONDataSource.create({}),
                            contactPointPurposes: isc.JSONDataSource.create({})
                        };
                        We are using a PHP middle tier, so we subclass DataSource into JSONDataSource so we can setup some common parameters and implement some default transformRequest and transformResponse logic.

                        Comment

                        Working...
                        X