Medryx Observations

May 31, 2009

A Professional JavaScript Build System: Part II – Bootstrapping

Filed under: dojo, javascript — maulin @ 6:31 am

In my last post I described the parts that can make up a robust build system for a complex JavaScript library that would allow for simplifying development, promotion to QA, and deployment to live systems. I outlined in some detail the importance of abstracting any use of external systems (via XHR, stores, or any other resource access that can change from environment to environment), and using a service container to request those services as needed. In this post, I will describe how I bootstrap my JavaScript library (based on Dojo).

The goal of a bootstrapping script is to allow page developers (i.e. consumers of widgets) to have to know a minimal amount (if any) JavaScript to utilize the library. While I know that in most circumstance, the page author is also the widget author, from the standpoint of testing and separation of concerns (making pages robust as requirements change), it is also good practice to keep your pages as much about layout, content, and style as possible. Your JavaScript widgets are there to bring in the functionality and interactivity of the page.

In my ideal scenario, using my JavaScript library will require exactly one line of markup to include the library, bootstrap it, and parse the page rendering any widgets as needed. Widgets will be ridiculously simple to use, and will only expose attributes (or only require attributes) that have a real impact on their function or style. Configuration should be done elsewhere.So here is my ideal page:

<html>

<head>

    <script type="text/javascript" src="path/to/bootstrap.js"></script>

</head>

<body>

    <div dojoType="medryx.widgets.HeadshotGrid" columns="4" style="width:400px;></div>

</body>

</html>

Notice how clean this is from an HTML perspective. Even though I love Dojo’s idea of namespacing CSS with html and body class names, I think that in a standard page, the page author shouldn’t have set the body class. The page author shouldn’t have to think about if they want “xd” version of the toolkit or same domain (especially considering that during development they probably want local, and during deployment they will likely want xd). The page author shouldn’t have to do any dojo.require calls. They simply call the widgets they need, and the rest is magic. If they want to do some JavaScript, they can, if they want to override the body class they can. But in the simplest (and most common) case, they don’t need to do anything. And indeed, in the simplest case, no matter what environment the page is executed in, it should just work. No changes to the HTML required.

So how do we get to this end? Its actually easier than it may seem. It takes a little from the school of thought that favors convention to configuration. It does not bar configuration, but if you don’t do any configuration, and simply adhere to convention, things should just work.

Lets think about what we need our bootstrap to do:

  1. Load the right version of dojo
  2. Load the right version of the environment
  3. Require any necessary scripts
  4. Parse the page

Loading the right version of dojo

In all of my projects, I put my library on the same server as the one serving Dojo. This may or may not be the same as the HTML page that is including the libraries (so we still have to deal with XD issues), but it does simplify a few things. If I know the path to my bootstrap script, then I can derive my paths to everything else, regardless of where the bootstrap lives.

So the first thing the bootstrap does, is some introspection. What is the path to the bootstrap file, relative to the calling page?

This is actually a cinch to do with Dojo. But at the time of loading, we don’t yet have Dojo loaded. So it requires some old fashioned DOM scripting:

var script = document.getElementsByTagName("script");


    for (var i = 0; i < script.length; i++) {

        var src = script[i].getAttribute("src");

        if (src) {

            //if the src of this script element is "bootstrap.js"

            //then everything before the text bootstap.js

            //is the path that got us here

            var match = src.match(/(.*)\/bootstrap.js/);

            if (match && match.length) {

                djConfig.baseUrl = match[1] + "/dojo/dojo/";

            }

        }


    }

So in this snippet I am simply loading all the “script” tags in the already loaded DOM. Since this script is executing, then by definition, at the very least the script tag that called this script is loaded in the DOM. Once I find the script tag that loaded this script, grab the path to this script, and map the path to dojo relative to this script. Not too bad, but a little but of a funky concept if you don’t get your head around it.

So now we can create the script element to load dojo onto the page. But remember, the DOM is probably not fully loaded yet. So the safest thing to do will be to us a document.write to load dojo. But using a document.write to “inject” dojo into a page is not the normally expected use of dojo. And you don’t necessarily know that the moment after you perform your write statement that your script will have access to dojo.addOnLoad. As such, you must define a djConfig variable (for many reasons) before you do your document.write, and that djConfig should have an addOnLoad method defined. This is your first method that “knows” that it has access to Dojo. So the next bit of code looks something like:

djConfig = {

    isDebug: true,

    modulePaths: {

        dojo: "../dojo",

        dijit: "../dijit",

        dojox: "../dojox",

        pages: "../../pages",

        medryx: "../../medryx"

    },

    addOnLoad: BOOTSTRAP,

    useXDomain: false

}


//now include the dojo.js -- if cross domain, then use xd.js

var extension = djConfig.useXDomain ? ".xd.js" : ".js";


//load dojo!

document.write("<scr" + "ipt type='text/javascript' src='" + djConfig.baseUrl + "dojo" + extension + "'></scr" + "ipt>");


So djConfig declares the appropriate modulePaths (note that the dojo/dijit/dojox are included in case this is a cross-domain load). If defines the addOnLoad method. Then we put together our path to dojo, and decide which version of dojo to use based on the useXDomain flag in djConfig.

Loading the right environment

Dojo will call BOOTSTRAP as soon as it is loaded. Which is where we will start to load our application. The first thing our BOOTSTRAP method does is figure out which environment to load. The first place it looks is in a custom configuration in djConfig called “environment”. If that value is set to “page”, then it will look for a custom attribute on the script tag that loaded the bootstrap to see if any environment was described there. This is useful in development or testing, if for example, you want to temporarily force a page to use a particular environment. So the modified djConfig and bootstrap looks like this:

// Below this line are configurations that are usually swapped in and out at build time

//==============================================================================================================

///BEGIN CONFIG

djConfig = {

    isDebug: true,

    modulePaths: {

        dojo: "../dojo",

        dijit: "../dijit",

        dojox: "../dojox",

        medryx: "../../medryx",

        pages: "../../pages"

    },

    addOnLoad: BOOTSTRAP,

    optimize: false, //for loading the compiled js.

    medryxConfig: {

        environment: "page" // use 'page' to read the 'environment' attribute on the iforms <script> tag

    }

    //may need to put this in a common location


    // dojoBlankHtmlUrl: "blank.html", //required for xdomain

    // dojoCallbackUrl: "blank.html" //required for xdomain

    // useXDomain: false

};

///END CONFIG

//===================================================================================================================


medryx = {

    _initializers: [],


    addOnLoad: function(/* Function */f){

        // summary:

        //    this implementation is to be sure pages have access to addOnLoad immediately.

        //    a more formal/robust implementation is loaded later

        //    that handles the more complex use cases (like calling

        //    addOnLoad after page is loaded)

        iforms._initializers.push(f);

    },


    config: djConfig.medryxConfig,


    ready: function(){

        //    summary:

        //        event called back when form is ready to display

    }

}


function BOOTSTRAP() {

    var environment = medryx.config.environment;

    if (environment == "page") { //then this is a development environment

        environment = dojo.query("script[environment]").attr("environment")[0] || "Static"; //default is static

    }

}


(function(){ //scope protection

    //we don't have dojo yet, and we need the path to this file.

    var script = document.getElementsByTagName("script");


    for (var i = 0; i < script.length; i++) {

        var src = script[i].getAttribute("src");

        if (src) {

            var match = src.match(/(.*)\/bootstrap.js/);

            if (match && match.length) {

                djConfig.baseUrl = match[1] + "/dojo/dojo/";

            }

        }


    }


    if (!djConfig.baseUrl) {

        throw new Error("could not find path to JS. this file must be name bootstrap.js for everything to work");

    }

    //now include the dojo.js -- if cross domain, then use xd.js

    var extension = djConfig.useXDomain ? ".xd.js" : ".js";


    //load dojo!

    document.write("<scr" + "ipt type='text/javascript' src='" + djConfig.baseUrl + "dojo" + extension + "'></scr" + "ipt>");


})();

Okay, so its getting a little more complicated, but still not overwhelmingly so. This script did everything I described above, and added just a bit more. First, it declared the global namespace of “medryx” and it created a config, as well as its own addOnLoad method.  Don’t worry too much about that right now except to say, its used to be sure certain scripts are executed after all the dojo.addOnLoad methods are executed. Again in a local build the order is pretty reliable, so its not a big deal, but when using a cross-domain “compiled” script, getting the order of things right can be a bit tricky, so this helps solve that. More on that in another post in the future. I also check if djConfig.baseUrl gets set, if it doesn’t, we get a meaningful error message.

So now I know what environment I want. I’ve loaded dojo. Next step dojo.require() the environment. And once I have the environment, I am ready to load any scripts needed to run the page.

This is where convention over configuration comes back to play. You may have noticed my “pages” namespace. This is actually a special namespace. Each page has a script in the pages namespace. And in fact, you can figure out the pages.* script you need by looking at the calling html page’s pathname. Pick whatever convention you want, but just be consistent. Then you can deduce what js needs to be loaded next and you can just do it, without the page have to do any configuration. Of course, if you choose, you could create another “custom attribute” on the bootstrap script tag with the name of the page module to load, or you could simply require() it into the HTML page on the HTML page itself. But following the convention gives you the benefit of zero configuration. Less code, less configuration = fewer silly bugs.

So here is a snippet in my BOOTSTRAP method that first checks if any of the script tags has the special “module” attribute. If they don’t it tries to figure out the module attribute based on a path name. This version assumes all HTML pages will be under a folder called “pages”.

var module = dojo.query("script[module]").attr("module")[0];

    if (!module) {

        var page = window.location.pathname.match(/pages\/(.*)\..*/);

        if (!page) {

            console.warn("Pages that use this bootstrapper must be in a subdirectory of 'pages'");

            console.warn("Using mock module name: MOCK");

            page = "MOCK";

        } else {

            page = page[1].replace(/\//g, '.').toLowerCase();

        }


        module = "pages." + page;

    }

So its time do some dojo.require()’ing. But before we do, lets consider the case of the “optimized” flag I have in my djConfig. Note that this is NOT a standard djConfig attribute. I simply use this to decide if I want to include a script on the page which is my compiled library. This is the version of the library that takes my hundreds of files and folders and compresses them into a single js file. With that in mind:

if (djConfig.optimize) {

        //load the compiled js: a single large file that

        //encompasses the code needed for each environment

        var head = dojo.query("head")[0];


        var extension = djConfig.useXDomain ? ".xd.js" : ".js";


        dojo.create("script", {

            src: djConfig.baseUrl + "/medryx-" + environment + extension

        }, head, "last");


    }


    //this contains all of the most common scripts a page 

    //uses -- like form, layout, etc.. if optimized, then

    //this does nothing, since its already loaded


    dojo.require("medryx.environment." + environment);


    //in case the module forgets... or if there is no module page (and makes this "first")

    dojo.require("pages.common");

    try {

        dojo.require(module);

    } catch (e) {

        console.warn("Unable to load module for this page (Did you remember to create a script for this?): ", module);

    }

I’ll describe the optimize flag and the location/name of the “built” js file later (when I discuss my profile layers). You can see I do a simple dojo.require for the environment. Also note that you must build your compiled scripts as one-per-environment, since you can only have one environment included on a page (or the “last one loaded wins”). The module dojo.require is in a try/catch block. I chose this instead of other ways to load the module that don’t throw an error when not found because I wanted to create the appropriate development warning to remind people to create their “module” file in the pages namespace.

Loading the style sheets

One more step, and we are done with our bootstrap. I wanted to be sure that our appropriate CSS file is loaded on the page. This is the CSS that is needed for widgets to work at all. Its a silly error when everything seems broken, and its just because the CSS didn’t import and failed silently. I also add the class name to the body  (though I should probably check if none exists first).

//import the style to the page

dojo.create("link", {

    href: dojo.moduleUrl("medryx", "../../themes/tundra.css").toString(),

    type: "text/css",

    rel: "stylesheet"

}, dojo.query("head")[0], "first"); //use "first" so that any locally declared style/css takes precedence

//add the css namespace so that the calling page doesn't have to remember to

dojo.query("body").addClass("tundra");

Okay, so what have we done here? We now have a single script that gets dojo into your page, loads you environment, decides if you need a cross-domain build, and handles incorporating your widget stylesheets. Not too shabby. And now with the use of a single <script> tag, your HTML page just works.

But aside from the niceness of everything just working, how else does this help us? Did you notice my comments strings above and below the djConfig variable? While I will describe in detail in my next post how I replace this depending on the environment desired, suffice it to say for now, that my build system can swap out the djConfig section depending on the kind of build I would like done. So a simple call to:

build.bat environment=DynamicLocal

…builds the entire library, and makes all the pages use the pretty optimized JS if needed and use all the right resources. Voila.

Next post: how to modify the dojo build system to handle environment swaps (warning – some mild dojo hacking is required for now until I can get them to fix some issues in the build system…)

May 30, 2009

Prequel: Professional JavaScript Build System

Filed under: dojo, javascript — maulin @ 11:43 pm

It occurs to me after reading my previous post that perhaps I wasn’t clear why I needed to have a “services container” to provide services to my scripts. So I thought I would put together this prequel – a brief tour of how I got to thinking that my the method for service abstraction I outlined in that post was worth the extra glue-code it required.

Like everyone else, I started with hard-coding my services into the objects that use them. So in the Headshot example, I simply appended the absolute path to the relative path like:

   1: dojo.declare("medryx.widgets.Headshot", [dijit._Widget, dijit._Templated], {

   2:     templatePath: dojo.moduleUrl("medryx.widgets", "templates/Headshot.html"),

   3:     pathToPhotos: "http://blog.medryx.org/photos",

   4:     person: null,

   5:     postMixInProperties: function() {

   6:         this.imagePath = this.pathToPhotos + this.person.imagePath

   7:     }

   8: });

 

Pros: a reasonable default pathToPhotos, that is easily over-ridden by any page. Cons: My widgets start looking very messy in the markup when they have so many attributes that have to be specified; I have to change my web page attribute for pathToPhotos depending on my environment. This is probably the most common way to handle changing environments.

My second stab was to create a set of scripts that aliased the service I needed (or the constant I needed) to a common object. For example:

dojo.provide("medryx.environment.Static");

 

dojo.mixin(medryx.environment, {

    pathToPhotos: "http://blog.medryx.org/images"

});

Then I could create a seperate file as:

dojo.provide("medryx.environment.DynamicLocal");

 

dojo.mixin(medryx.environment, {

    pathToPhotos: "http://localhosts/images"

});

And, similar to my previous post, I just include the environment I need in the page, and modify my widget to use medryx.environment.pathToPhotos as its pathToPhotos attribute.

This actually works fairly well. Especially for constants (and indeed I still use this model for handling constants). But lets think about this model when I need a service – say a data store.

dojo.provide("medryx.environment.Static");

 

dojo.mixin(medryx.environment, {

    MyStore: new dojox.data.JsonRestStore({/* omitted */});

});

Now, to reference my store, I simply use medryx.environment.MyStore. This too, works fairly well. Except for a few problems:

  1. It mandates that you initialize your services at load time. There may be many services that you will never use on a given page, or that you certainly do not need to initialize up front, and this could slow down your boot of your application.
  2. This does not fail gracefully. For example, if you forget to create the MyStore in your environment, and then you attempt to use the store, you may or may not get an error, and it may or may not be useful. But either way, things won’t work until you figure out that you forgot to create the MyStore service.
  3. If the service was dependent on some other outside process being completed – such as the DOM having to be loaded before it would work, or something else – then this would work in a local install (where you can guarantee the order in which your scripts are loaded and executed), but could fail (in very mysterious “sometimes it works, sometimes it doesn’t” ways) in a cross-domain load when that guarantee is not present.

So, now we are getting to my previously suggested “service container”. By using a simple call to a method to “get” the service I need, I can lazily load the services if required, I can guarantee when the initialization will occur, and I don’t incur much overhead. And even as I write, this, I can think of at least one improvement, since the “service container” is accessed quite frequently, lets make it more terse:

dojo.provide("medryx.environment.Static");

 

(function() {

 

medryx.$ = medryx.service = function(serviceName) {

    switch(serviceName) {

        case "medryx.environment.pathToPhotos":

            return "http://blog.medryx.org/images";

 

        case "medryx.environment.MyStore":

            return getInitializedMyStore();

                        

        default:

            throw new Error("Could not find service: " + serviceName);

    }

}

 

var myStore;

function getInitializedMyStore() {

    if (!myStore) {

        myStore = new dojox.data.JsonRestStore();

    }

    return myStore;

}

 

})();

And the calling class simply calls either medryx.service(“name.of.Service”) or even medryx.$(“name.of.Service”) and is guaranteed to get the initialized service, lazily initialized if needed, and with a nice, clear “Could not find service: name.of.Service” if you forget to define your service before you use it.

I hope that makes the rationale behind the service container easier to understand. Now, my next installment will describe how we can better decide which “environment” script to include, and make a build system that will be easy to maintain.

A Professional JavaScript Build System

Filed under: dojo — maulin @ 4:05 pm

Its been a long time since my last post. That’s not to say I have run out of things to say. But the list of topics has gotten long enough, that I think its high time I start writing again.

In this post, I will discuss a build system for JavaScript development based on the Dojo build system. Having done plenty of server side development, “building” a project was not a foreign concept to me. And the Dojo build system is quite nice. It has a rich feature set, and does an amazing job of optimizing scripts. If you look at your set of scripts after they have been build and optimized, they resemble binary code more than they resemble any meaningful text. And as long as you adhere to the basic design principles encouraged by Dojo (namespacing, using dojo.moduleUrl when loading resources, using templatePath in widgets, etc), you get most of this for free.

But when we build server software, we frequently think of various “build environments” – dev, QA, and live, for example. That is not a concept that I have seen applied in the Dojo community in any of the many example projects that exist. In a previous post, I had described a “MockRestService” that allowed you to test all your JsonRestStore functionality without an app server. The MockRestService simply called static json files and “served them” to your store.

When I develop complicated applications, and when I try to apply Test Driven Development principles, I am constantly striving to simplify my test. I want to remove as many complicating factors as possible. Having an app server running, even if its just a simple PHP script, is just another layer of complexity that can be weeded out when developing.

As another example, I frequently have other xhr calls in my applications, hitting multiple internal and external resources. I would love to be able to hit one version of the service for local development and testing, another for QA, and yet another for live systems. To this end, I have developed my own work pattern, that I think makes my development much more productive. This breaks down into a few main concepts:

  1. Using a bootstrap JavaScript to load the library
  2. Abstracting any calls to resources that can change into a service layer
  3. Modifying the build system to allow easy swapping of resources.
  4. Creating multiple build environments to facilitate development – static resources (statically served files without the need of an app server), dynamic resources (using a local app server, and using a foreign app server if you will need to test cross-domain access), qa, and live.

In some ways, you can think of this whole system as a “dependency injection” framework for JavaScript. The bootstrap is your “container”, and controls which services get sent to your scripts. Your scripts only use “interfaces” to access resources – never making “low-level” calls to xhr, but instead letting a service layer handle that. A store is one example of a service – abstracting your calls to the actual resource. If you always use a data store, then you simply pass your script a different store depending on which resource you want to access.

So lets look at how to do this. I recently build an application that displays a set of people, their picture and their title. Clicking on their picture sends them a message. Not rocket science. This application contained three widgets:

  1. The individual “headshot” for each person
  2. The “grid” of headshots that uses a data store to access the information about each person, and creates headshot for each person and places them in a table (not a dojox.grid, just a simple table).
  3. The messaging system that presents a dialog to the user when they click on a headshot to send a message to that person.

(This application actually had quite a bit more complexity which warranted all this scaffold, but I’m trying to simplify.)

The headshot widget is given a JS object with a “image” path and a person’s name and uses them to create something like this:

[Need to insert image here]

But the path to the image is a relative path, and depending on our environment, that absolute path may be different. So this is the first “service” we are going to require. A very simple service that is given a relative path to an image and returns the absolute path.

dojo.provide("medryx.services.static.PhotoPathService");
 
medryx.services.static.PhotoPathService = function(relativePath) {
    return "http://blog.medryx.org/pics/" + relativePath;
}

So that is NOT a lot of code by any stretch. But if you were to  dojo.require(“medryx.services.PhotoPathService”) in your widget, you would be defeating the purpose. You would still have to actually change the service to get a different build environment. And remember, the goal is to have to update exactly one file to get a completely different environment behavior. So we need to build one more script – a service accessor. This is very similar to dojo.require (you could even argue that you could simply extend dojo.require – but I just now thought of that, so I haven’t tried it).

dojo.provide("medryx.environment.Static");
dojo.require("medryx.services.static.PhotoPathService");
 
medryx.service = function(service) {
    switch(service) {
        case "medryx.services.PhotoPathService":
            return medryx.services.static.PhotoPathService;
        default:
            throw new Error("Could not find the requested service: " + service);
    }
}

So this to, is not a complicated class. It just takes a string name of the desired interface, and returns an instance that “implements” it. So now if my bootstrapper changes its “require” from “medryx.environment.Static” to “medryx.environment.DynamicLocal” (for example), I could change what services I return.

<pre lang="javascript">dojo.provide("medryx.environment.DynamicLocal");
dojo.require("medryx.services.dynamic.PhotoPathService");
 
medryx.service = function(service) {
    switch(service) {
        case "medryx.services.PhotoPathService":
            return medryx.services.dynamic.PhotoPathService;
        default:
            throw new Error("Could not find the requested service: " + service);
    }
}

As long as the “environment” scripts define a “medrxy.service” method, then all my other scripts know that as long as an environment has been required, then they can call medryx.service() with the name of the service they need. So my headhsot widget does:

dojo.provide("medryx.widgets.Headshot");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
 
dojo.declare("medryx.widgets.Headshot", [dijit._Widget, dijit._Templated], {
    templatePath: dojo.moduleUrl("medryx.widgets", "templates/Headshot.html"),
    person: null,
    postMixInProperties: function() {
      var pathService = medryx.service("medryx.services.PhotoPathService");
      this.imagePath = pathService(this.person.imagePath);
    }
});

So already, even without the bootstrapping and building described above we can change the behavior of a page with the change of one line:

<html>
 <head>
 <script type="text/javascript" src="/path/to/dojo.js"></script>
 <script type="text/javascript">
    dojo.require("medryx.environment.Static");
    dojo.require("medryx.widgets.Headshot");
</script> 
</head>
<body>
<div person="p" dojotype="medryx.widgets.Headshot"></div>
</body>
</html>

And this behaves differently than:

<html>
  <head>
  <script type="text/javascript" src="/path/to/dojo.js"></script>
  <script type="text/javascript">
    dojo.require("medryx.environment.DynamicLocal");
    dojo.require("medryx.widgets.Headshot");
  </script>
</head> 
<body>
<div person="p" dojotype="medryx.widgets.Headshot"></div>
</body>
</html>

So this is a very simple way to abstract one part of my application that has a dependency on what environment the application is running in. Clearly, this is a bit of overkill for this simple example. In fact, in this example, I still do use a service, but I have a generic “ConstantsService” that gets me information like which server I should hit, or which absolute path to use. But, for more complicated services – likely stores, or services that have many methods that use various XHR calls, this is the pattern I use.

So what about the bootstrapper? What does that look like? Because hard-coding the dojo.require to which environment I am using might be okay for a test page, but for a live page, I don’t want to have to write a script (or manually) change all those require statements to include the right environment. That is what I will write about in my next post.

Let me know if you have any comments or suggestions!

September 10, 2008

dijit.Search Part 2

Filed under: dojo, javascript — Tags: , — maulin @ 7:27 pm

Well its been a while since my first post about a search widget for dojo.data stores. There are a lot of reasons — the beautiful Portland, Oregon summers being the most significant. But now the kids are back in school, life is normalizing, and its time to show you how I finished what I think is a very nifty widget.

First a recap: My goal was to make a pretty widget that simply allowed you to search one or more dojo.data stores. You could use this in a lot of ways. I’ve posted a demo here that uses several dojox.data stores to allow you simultaneously search Google, Yahoo!, and Wikipedia from your site. While this is one use case, the use case I find most interesting is doing a “global” search on your own dojo.data stores that are driving your web application. But the sky is the limit! You can use this to build OSX Searchlight-style functionality into your app. You can “skin” the widget itself to make it match your site’s style. You an change the prompt from “Search” to anything you want, that may be more useful in your app. You can even include a dropdown menu that appears upon clicking the magnifying glass to the left of the search box that allows your users to choose which stores (or which methods) they would like to use to search. You can define a template for your result to make it display in just about any way you can imagine. (The default simply shows the “label” text from the resulting items.)

Before going any further, I want to make it clear that this works, but is clearly alpha-level code. And I think there are some simplifications that can be made in the usage of the widget in the future if this is useful to more people than me. But for now, think of it as the 0.9 dojox.grid — way too complicated, but really functional. And hopefully this widget will evolve into a simpler and more elegant widget just as the grid has. FYI, this widget is dependent on dojo 1.2 (primarily because it uses the 1.2 dojox DataGrid).

So lets dive into how to use the widget. The first concept we need to go over is how to create an interface into your stores to allow searching them in various ways. To solve this, you need to create a SearchStore. A SearchStore is really a composite of interfaces into other dojo.data stores. As is my usual practice, sometimes things are explained more easily with code, so here goes. Lets build the demo widget. We’ll start with just the WikipediaStore:

dojo.require("medryx.widget.Search");
dojo.require("dojox.data.WikipediaStore");
 
//create the WikipediaStore
var wikiStore = new dojox.data.WikipediaStore();
 
//create the SearchStore
var searchStore = new medryx.widget.SearchStore();
 
//add the wikiStore to the SearchStore
searchStore.addStore({
  name:"Wikipedia", // what will this search be called (useful when there is more than one store
                    // in your SearchStore (or more than one method or more than one method of
                    // searching the same store
  store:wikiStore, //the source store
 
  //this is the most important piece. given a search string (searchTerms) and
  //and a requested searchType, return the query that should be passed to the
  //the store (in this case to wikiStore.fetch())
  queryBuilder:function(searchTerms, searchType) {
    //if the user specifies "Wikipedia" search, or any search, then use this store
    if (!searchType || searchType == "Wikipedia" || searchType == "*") {
      return {query:{text:searchTerms, action:"query"}}; //see WikipediaStore for how to query Wikipedia
    }
  }
});

Okay, that does seem a little complicated. But conceptually its pretty simple. When a user types a string into the search box, the widget needs to know how to handle it with respect to your source stores — so you provide a “queryBuilder” method that converts generic search terms to your specific query. When there is just one member of you SearchStore, this seems like overkill. So lets add some more stores. I used the dojox.data.ServiceStore to create a Google store and Yahoo store that can search those services. The code for how to create those services is a bit beyond the scope of this article, but you can see the full code in the demo. For now, lets take it on faith that there is a yahooStore and a googleStore that I have created. So lets wire them up to our SearchStore:

searchStore.addStore({
  name:"Google",
  store:googleStore,
  queryBuilder:function(searchTerms, searchType) {
    if (!searchType || searchType == "Google" || searchType == "*") {
      return {query:{q:searchTerms}}; //see google smd for format
    }
  }
}); 
 
searchStore.addStore({
  name:"Yahoo",
  store:yahooStore,
  queryBuilder:function(searchTerms, searchType) {
    if (!searchType || searchType == "Yahoo" || searchType == "*") {
      return {query:{query:searchTerms}}; //see yahoo smd for format
    }
  }
});

So now you can see how we can create a very complicated method for searching multiple different stores with the same search string. There are several more features to the SearchStore, but I’ll come back to those. Right now, lets change gears and see how we can actually use this widget with this SearchStore. In the simplest case we simply declare the following in our markup:

<div dojoType="medryx.widget.Search" store="searchStore">
  <script type="dojo/connect" event="onHitSelected" args="item, store, hit">
    console.debug("Hit selected:", item, store, hit);
  </script>
  <script type="dojo/connect" event="showAllResults" args="terms">
    alert("search for more results for \"" + terms + "\" yourself, you lazy bum!");
  </script>
</div>

You can see that you can easily connect to the “onHitSelected” event to decide how you will handle the user selecting one of your search results. This event passes the original item, and its original source store as parameters. It also provides access to the “hit” itself, which is an item in the SearchStore that maps to the original item from your source store. This can be very useful, once you know about some more features of the SearchStore. We’ll get there, I promise.

Notice also that you can connect the the “showAllResults” event, which is really expected to behave as a link to your “complete search” page (or dijit.Dialog, or whatever). The “Complete search results” link is at the bottom of the search results in all cases.

Lets look at some ways to customize the widget a little more:

<div dojoType="medryx.widget.Search" store="searchStore">
  <div dojoType="dijit.Menu">
    <div dojoType="dijit.MenuItem" searchType="Google" iconClass="Google">Google</div>
    <div dojoType="dijit.MenuItem" searchType="Yahoo" iconClass="Yahoo">Yahoo</div>
    <div dojoType="dijit.MenuItem" searchType="Wikipedia" iconClass="Wikipedia">Wikipedia</div>
  </div>
  <script type="dojo/connect" event="onHitSelected" args="item, store, hit">
    console.debug("Hit selected:", item, store, hit);
  </script>
  <script type="dojo/connect" event="showAllResults" args="terms">
    alert("search for more results for \"" + terms + "\" yourself, you lazy bum!");
  </script>
</div>

If you include a dijit.Menu inside your declaration, then this menu will be displayed when you click the magnifying glass to the left of the search box. You can see there is a special (REQUIRED) attribute on each MenuItem called “searchType”. This is the searchType that is sent to your “queryBuilder”, and can be any string that your queryBuilder understands. If a user does not expand the menu, and simply presses “Enter”, then the searchType is by default set to “*” (though you can change this via the defaultSearchType attribute).

There are plenty of attributes to help you customize your search widget, and they are all documented in the source of medryx.widget.Search.

So now lets look at how we might do something useful with our “onHitSelected” event. Let say we want to open an iframe with the url of the hit (as in the demo). First add the iframe to your markup:

<div align="center">
  <iframe style="width:90%; height:600px; border:1px solid black; display:none;" id="preview"></iframe>
</div>

So one way to get to the “right” url regardless of which store your result came from would be to make a big, complicated event handler (can you tell this will not be the preffered method?)

var hitHandler = function(item, store, hit) {
  var src = "";
  if (store == yahooStore) {
   src = store.getValue("ClickUrl");
 } else if (store == googleStore) {
   src = store.getValue(item, "unescapedUrl", store.getValue(item, "url", defaultValue));
 } else if (store == wikiStore) {
   src = "http://en.wikipedia.org/w/index.php?title=" + store.getValue(item, "title");
 }
  dojo.byId("preview").src = src;
  dojo.byId("preview").style.display = "";
}

This is perfectly reasonable, but its quite “spaghetti-like”. And every time you add a store, you have to change you handler. What if we could define for each store how to obtain the “src” string, and then use a common way of accessing it? Much cleaner IMHO. So SearchStore has another attribute you can include when calling addStore — “map”. A map is a list of pseudo-fields that will be accessible for each hit from the SearchStore itself. Each pseudo-field begins with “search”. So lets upgrade our searchStore as follows, adding in the maps:

searchStore.addStore({
  name:"Google",
  store:googleStore,
  queryBuilder:function(searchTerms, searchType) {
    if (!searchType || searchType == "Google" || searchType == "*") {
      return {query:{q:searchTerms}};
    }
  },
  map:{
    searchUrl:function(item,attribute,defaultValue) {
	return this.getValue(item, "unescapedUrl", this.getValue(item, "url", defaultValue));
    }
  }
}); 
 
searchStore.addStore({
  name:"Yahoo",
  store:yahooStore,
  queryBuilder:function(searchTerms, searchType) {
    if (!searchType || searchType == "Yahoo" || searchType == "*") {
      return {query:{query:searchTerms}};
    }
  },
  map:{
    searchUrl:"ClickUrl" //simple! so no "function" needed, just map from "searchUrl" to yahoo's "ClickUrl"
  }
}); 
 
searchStore.addStore({
  name:"Wikipedia",
  store:wikiStore,
  queryBuilder:function(searchTerms, searchType) {
    if (!searchType || searchType == "Wikipedia" || searchType == "*") {
      return {query:{text:searchTerms, action:"query"}};
    }
  },
  map:{
    searchUrl:function(item, attribute, defaultValue) {
      return "http://en.wikipedia.org/w/index.php?title=" + this.getValue(item, "title");
    }
  }
});

Now the event handler can be simplified, using the “hit” parameter:

var hitHandler = function(item, store, hit) {
    dojo.byId("preview").src = searchStore.getValue(hit, "searchUrl");
    dojo.byId("preview").style.display = "";
}

Much nicer, don’t you think? And you could add more stores to this widget or more ways to search the same store, and never have to touch the event handler.

Last but not least, you can customize how you “hits” appear on the dropdown list, as well as how the “category headers” appear. Simple include a child of your widget with an attribute of template=”category” or template=”hit”. You can then access any of  the attributes (or pseudo-attributes) of your item with the familiar ${attributeName} notation.

Building this widget taught me a LOT about dojo widgets. Unfortunately it wasn’t until I was almost done with the widget, that I read about the dojo 1.2 attributeMap. So obviously, to really take this widget to the next level, some work would need to be done. But right now it is quite functional, and can quickly and easily add a lot of robust functionality to your applications with very little additional work.

For now, you’ll need to grab the files you need from my dev server at http://dev.medryx.org/lib/js/medryx/. I’ll try to submit this to dojo as a dojox.widget sometime soon.

Let me know what you think, and if I can answer any questions!

August 15, 2008

Transparent PNG’s on IE with dojo

Filed under: dojo, javascript — Tags: , , , — maulin @ 5:55 am

Transparent PNG’s are fabulous. They are the key to making your app look really professional and slick. And they allow you to make easily themed web pages without having to redesign all your images, since you can just rely on the background colors shining through the transparency. Transparent PNG support is available on Firefox and Safari, and IE7. But IE6 does not support it.

There are, however, workarounds. And when it comes to making all browsers behave the same in the background, equalizing browser differences so you can just focus on writing great web applications — dojo is the best tool around. So it seems only natural to me that we should try to create a solution to this problem with dojo.

Fortunately, there has already been great work done by Angus Turnbull (which by the way is a GREAT name!) He has created a nifty solution to the problem. It turns out that IE6 does support transparent PNG’s when a special “filter” is used to load the image. Well angus has created a script that uses IE6’s “behaviors” via *.htc javascript files to implement a simple solution that will replace all your images with this filter-loaded version. This includes css background images and IMG tag images. And in his latest version he handles background-repeat and background-position, so this really becomes a simple drop-in solution, and your transparent PNG’s suddenly work perfectly on all the major browsers.

There is just a *little* configration needed to get Angus’s IEPNGFix to work. First you drop the iepngfix.htc, iepngfix_tilebg.js, and blank.gif files somewhere on your server. You then add the following to your CSS file:

img, div { behavior: url(/path/to/iepngfix.htc) }

You also need to open and change the path to blank.gif in iepngfix.htc to the absolute path to the blank.gif file. (Or a relative path, but relative to *any* page that will actually use the file, which is inconvenient). Finally you use a typical script tag to include iepngfix_tilebg.js in your page if you want background-repeat and background-position to work.

Its really not too bad, but its still more work that I want to do. And I would love to be able to use it any dojo web app without all this configuration. Well, dojo to the rescue! With a few modifications, you can make this available with a simple:

dojo.require("medryx.util.png");

(Or you can use whatever namespace you want!) Ideally, this could some day be included in dojo natively, so you wouldn’t have to do any work to get this support. In this technique, you don’t worry about absolute or relative paths — because you replace all that stuff with calls to dojo.moduleUrl
and you can eliminate the change to your CSS with a nifty CSS trick that Angus provides. Finally, you can eliminate the script tag to load iepngfix_tilebg.js by just adding a dojo.provide() call at the top of that file and using a dojo.require() to get it.

Sounds like a lot of work, but really it wasn’t, and now I can use it in any of my web applications without any real work. So here is the step-by-step process:

  1. download the IEPNGFix.zip file
  2. create the following directory structure and copy the files from the zip as follows — lets assume you will put this in a “medryx” module, though obviously yours would be named differently:
    dojotoolkit/
    -- dojo/
    --dojox/
    --dijit/
    --medryx/
    ----util/
    ------png.js --> see below
    ------png/
    --------iepngfix.htc
    --------iepngfix.php --> optional, see below
    --------iepngfix_tilebg.js
    --------blank.gif
  3. Create a file called “png.js” whose contents are quite simple. It simply checks your browser, and if needed, adds a rule to your first stylesheet to handle png’s with the behavior fix (this is a slight modification right from Angus’s demo page). The content of the file is as follows:

    dojo.provide("medryx.util.png");
     
    (function() {
    //a fix for transparent png in ie6 (only works with php, or change this to the iepngfix.htc file if you can get the server to set content-type correctly!
     
    if (dojo.isIE && dojo.isIE < 7 &&  document.styleSheets && document.styleSheets[0] && document.styleSheets[0].addRule){
    	//dojo.require("medryx.util.png.iepngfix_tilebg"); needed to support repeat, but doesn't work for me.
    	var fixUrl = dojo.moduleUrl("medryx.util.png", "iepngfix.php"); //use *.php if your server doesn't server the *.htc file correctly
    	document.styleSheets[0].addRule('*', 'behavior: url("' + fixUrl.path + '")');
    }
     
    })();

    One small thing to notice — the iepngfix.php file is really only needed to set the content-type header of the *.htc file correctly so IE6 will know what to do with it. Obviously there are other (better) ways of doing this on each type of server. But the php file is a simple workaround if you don’t have access to changing how *.htc content-type is set on your server.

  4. Open the iepngfix.htc file and search for “blank.gif”. Replace that line with:

    IEPNGFix.blankImg = dojo.moduleUrl("medryx.util.png", 'blank.gif').path;

    You might also want to search for “alert” and change it to console.warn instead, but that’s not mandatory!

  5. Open the iepngfix_tilebg.js file and add the following to the top line:

    dojo.provide("medryx.util.png.iepngfix_tilebg");
  6. Now on any app that you want to use transparent PNG, just add to your set of dojo.require statements:

    dojo.require("medryx.util.png");

A couple more side notes — I can’t seem to get background-repeat or background-position to work right with the *.js file. I haven’t had time to really troubleshoot it, since I don’t need it right now. If anyone has the solution, please let me know! A second note is that you should consider NOT using 1px images that are repeated, but instead use a more reasonable size, to limit the number of repeats. So if you know the smallest size of your display is 100px wide, then make your PNG 100px wide, and then repeat. Each repeat takes browser processing, and these days the download size of a 1px image and 100px image is not significant enough to make much of a difference.

Hope this helps someone! It has certainly helped me. Thanks Angus!

August 14, 2008

A Dojo Search Widget: dijit.Search

Filed under: dojo, javascript — Tags: , , , — maulin @ 6:33 am

Search has become ubiquitous on web sites and applications. Its long since replaced categorization or filing. On my new mac, I can’t remember if I have ever dug through my Applications folder looking for an app. I just type a few letters in Spotlight, and voila. On the dojo website its the same story.

But what is conspicuosly missing (IMHO) from the dojo widget set (dijit) is a robust “search” widget. While you can certainly use the widgets available, or easily create new widgets to make a search field and present the results, this seems to me like it would have a lot of broad applicability throughout the dojo community. And honestly, without someone spending a lot of time up front, most of these quickly put together solutions don’t look great.

In my opinion the search feature on apple.com is an ideal “Web 2.0″ flavored search. Type a few letters and you get pertinent results, almost instantly, and without a page refresh. The results are present in a categorized fashion, and there is a link to see all of your results or do a more complete search of the apple website. Aside from its functionality, I think its just plain pretty. A nice rounded text box, that is very intuitive to use, and declares it purpose without any real prompting or explanation.

So I’ve embarked on creating a search widget for dojo — dijit.Search. (Well that is awfully presumptious of me. Obviously , none of this is sanctioned Dojo code, nor has it even been submitted, let alone accepted as a part of Dojo. So for now, I’ll keep it in my own namespace — medryx.widget.Search).

So let’s get started. First up — lets describe what the widget will do:

  1. A pretty search field that gives feedback about what’s going on, and allows the user to select a specific type of search to use if desire (similar to the Firefox 3 search field).
  2. Allow searching via a few mechanisms: perhaps searching more than one data store on the client, or more than one field in the same store for a match. But also obviously provide the ability to perform the search on the server and return pertinent results. I think this is an important point — I think search has really come into its own as a “navigation tool” since it has been able to search all over the place for a text string, and provide relevant results with links to either launch a function, view a page, or view result details.
  3. Create a nice looking, categorizable search result window that pops up in some fashion on the screen, behaves correctly from an A11y standpoint, and provides the ability to request a more detailed search if the “preliminary” search results have not delivered satisfactory results.

Okay, so its not exactly a “spec”, but its something to work with. I always find it easiest to start really thinking about the “backend” of a widget, by first fleshing out the front end. So lets get to work on the pretty search field.

I want the field to have rounded edges, a magnifying glass that can be clicked to reveal search options, a “prompt” inside the text field that disappears/re-appears correctly, and an animated gif of some sort inside the field to tell you when the search is in progress.

The easiest way to accomplish this will be with thee background images (that may eventually merged into the same sprite) — one for the left semicircle with magnifying glass, one for the right semicircle, and one for the text field portion. So I want my final search box to look like this:

A couple notes: obviously not everyone will be working with a gray background onto which to put the search box. But I don’t want to create an infinite number of colors. So I want to use PNG images which have excellent transparency support. That way you cut put the box on any color background with good effect:

Then only other configuration I will need will be if there is no background (white or transparent background). But I’ll deal with that later.

So what’s the catch? IE6 (of course). It does not support transparent PNG’s natively. Thankfully there is a nifty fix available via LGPL called IEPNGFix that solves this problem rather well. And with very little effort you can dojo-ize this solution (which I have done). Now I just make a call to dojo.require("medryx.util.png") and I can reliably use transparent PNG’s in a cross-browser compliant way. (That piece in and of itself took me a whole night! See why it would be great to have an even richer set of dijits? If someone else had solved that problem or the pretty search widget problem already, I could’ve spent my time doing better things last night! Nah, who am I kidding? I would’ve been doing something else nerdy…)

So we want to be able to use these boxes anywhere on the page, and allow them to be of any size. I think the best solution is to have them adhere to the dimensions of their original DOM node as such:

<div dojoType="medryx.widget.Search" style="width:400px;"></div>

So here is the CSS and html that can put this together to make the images above:

<div class="medryxSearch">
	<label for="search_${id}">
		<div class="medryxSearchBox" style="width:${paddedWidth}px">
			<span class="left" dojoAttachEvent="onmousedown:_ignoreBlur, onclick:onOptionsClicked" dojoAttachPoint="optionsNode" id="searchOptions_${id}"></span>
			<div style="width:${width}px"><input style="width:${inputWidth}px" id="search_${id}" autocomplete="off" dojoAttachPoint="inputField" dojoAttachEvent="onkeyup:_onInputKeyUp, onblur:_onInputBlur, onclick:clearPrompt" /></div>
			<span class="right"></span>
			<span class="searching" dojoAttachPoint="searchingNode"></span>
		</div>
	</label>
</div>

I’ll explain some of the template stuff a bit later. Lets focus on the “medryxSearchBox”: This is a fixed-width box, whose size is taken from the contentBox of the declaring node. The width is then padded to include the width of the left/right portions of the widget. The first span is the left bit, the next is the text field, and the next span is the right bit. The “searching” span is where the animated gif is created, and displayed as needed. The reason that the node after the “left” span is wrapped in a div has to do with the way different browsers render background images on a text field. Suffice it to say it is incosistent, and much more easily managed on a div.

So here is the CSS:

.tundra .medryxSearch {
	margin: 0;
	padding: 0;
}

.tundra .medryxSearchBox {
	position:relative;
	margin:0;
	padding:0;
	left:19px;
}

.dj_ie .tundra .medryxSearchBox .left {
	top:-1px;
}

.tundra .medryxSearchBox .left {
	background: transparent url('../themes/images/searchLeft.png') no-repeat scroll left;
	width: 19px;
	height: 24px;
	top:0;
	margin:0;
	left:-19px;
	padding:0;
	position: absolute;
	cursor:pointer;
}

.tundra .medryxSearchBox .right {
	background: transparent url('../themes/images/searchRight.png')
		no-repeat scroll right;
	height: 24px;
	top:0;
	right: 19px;
	width: 10px;
	margin:0;

	padding:0;
	position: absolute;

}

.tundra .medryxSearchBox .searching {
	background: transparent url('../themes/images/searching.gif') no-repeat scroll right;
	width:16px;
	height:16px;
	padding-left:16px;
	position:absolute;
	top:-9999px;
	right:25px;
	margin-top:4px;

}

.dj_ie .tundra .medryxSearchBox .right {
	top:-1px;
	right:18px;

}

.tundra .medryxSearchBox div {
	background: transparent url('../themes/images/search.png') repeat-x	scroll bottom;

	margin:0;
	padding:0;
	height: 24px;
}

.dj_ie .tundra .medryxSearchBox input {
	font-weight: 500;
}

.tundra .medryxSearchBox input {
	color: white;
	background: transparent;
	border: 0;
	font-size: 13px;

	margin:2px 0 0 5px;
	padding:0;

	height: 24px;
	outline-color: -moz-use-text-color;
	outline-style: none;
	outline-width: medium;
	text-shadow:0 1px 1px #323232;
}

Okay, that’s a lot of CSS. Let’s break it down. First off, it uses the standard dojo method of “namespacing” classes by prefixing each declaration with “.tundra”. (On a side note, I think I should probably break this out into pieces that apply to any theme vs those that are more themeable. Those that apply regardless of theme should not be prefixed, but this is a work in progress…)

A couple cool tricks you may not know about. Instead of beating yourself over the head with the different ways browsers render background images, and fighting the different interpretations of margin and padding, you can use a .dj_ie prefixed class to tweak you CSS class for IE. Dojo quietly adds the dj_ie class to the HTML tag of the page, so this is always the top-most class. Brilliant!

Notice also that I have nested the meat of my widget within my DOM node. This is so I can allow the positioning of the widget to be relative (actually absolute) to the DOM node and get positioning anywhere on the page. CSS position:absolute interprets “absolute” as placement on the page not within the containing element, unless the containing element is relatively positioned.

Okay, now we have complete the first step of widget creation — we’ve made a pretty widget that loads nicely when placed on a page.

Now lets handle the “prompt” bit. I want some variable text presented to the user, but when they click on the field it should disappear, but if they leave the field empty, it should re-appear. So on my widget declaration we add prompt:"Search", and users can easily override this. Then in postCreate, we set this.inputField.value = this.prompt. Finally we wire the onclick event from the inputField to the clearPrompt method on the widget, which clears the input field of the prompt via a string.replace — this.inputField.value.relplace(new RegExp(”^” + this.prompt), “”). Then when a search is done, we could reinstate the prompt. Certainly if there is a blur-event and the field is empty, we should reinstate the prompt.

The last “easy” thing to do before we have to really have to start thinking hard about the “backend” of this widget is to connect the magnifying glass icon to a menu of options. Since I want this to be pretty flexible, I simply check if the declaration has a dijit.Menu as a child, and if it does, it wires it up correctly. So the usage would be something like:

<div dojoType="medryx.widget.SearchBox" style="width:300px;">
  <div dojoType="dijit.Menu">
    <div dojoType="dijit.MenuItem">Google</div>
    <div dojoType="dijit.MenuItem">Yahoo</div>
  <div>
</div>

So how do I wire this up? This took me a few minutes to figure out. The first thing to realize is that the child widgets have not been instantiated yet when the parent is being instantiated by dojo.parser.parse(). So I can’t attach to this yet. My options are to wait until my child widget is instantiated (by connecting to the menu in some way in startup() and perhaps making this widget a “Container”). But that seemed like a lot of work. My first attempt was to simply call dojo.attr and add the targetNodeIds value and leftClickToOpen values to the DOM node of the dijit.Menu before it has been parsed. And it works! In FF3. Unfortunately IE6 doesn’t like this and throws an “Unexpected method or property access” exception. Damn!

So here is my trick: I parse the dijit.Menu myself! So, do a dojo.query(”[dojoType='dijit.Menu']“, this.srcNodeRef) to get the DOM node. Then call dojo.parser.parse on the returned node. Then use the return value from the the call to parse to get a handle to my Menu widget. Then use widget.attr to change leftClickToOpen to true, and then call bindDomNode to my optionsNode (the magnifying glass). So the code looks like this:

var menuWidgets = dojo.query("&gt; [dojoType='dijit.Menu']", this.srcNodeRef);
var menuNode = menuWidgets.length &amp;&amp; menuWidgets[0];
if (menuNode) {
// DOESN'T WORK IN IE6!
//	dojo.attr(menuNode, "targetNodeIds", dojo.attr(this.optionsNode, "id"));
//	dojo.attr(menuNode, "leftClickToOpen", "true");
//parse the menu now so we can get a handle for it
	this.menu = dojo.parser.instantiate([menuNode])[0];
	this.menu.attr("leftClickToOpen", true);
	this.menu.bindDomNode(this.optionsNode);
 
	this.connect(this.menu, "onItemClick", "menuItemClicked");
}

And now you have a “search menu”.

Okay, enough procrastination, time to think about the model. But wait, before we do, we should think through how the results might be displayed. With the almost infinite flexibility of the dojox.grid, and its inherent connection to dojo.data via DataGrid, I think it would be silly to roll my own table to display the results of the search. I imagin wrapping the grid into some kind of popup node that display once a search is initiated. That is work to be done.

But if I want to use dojo.data, how will I meet my need for allowing searching multiple different fields of a store, or multiple different data sources? I pondered this for a day or two, with lots of complicated ideas. And then it came to me: a composite data store. Call it medryx.data.SearchStore. This store takes other dojo.data stores as part of its initialization parameters. And for each store, you can describe a way to create a “search” query on the store. Heck you can even supply the same store multiple times, each with a different “search strategy”. When the Search widget calls fetch, the query will be {searchTerms:"foo", searchMethod:"*"} where searchMethod is the value of any item that was selected from your menu of search methods, the searchTerms are the just the text from the search box. The composite SearchStore will initiate a fetch on each of its member stores, using the “search strategy” to create store-specific queries. As each store returns we’ll glue the results together, with some indication as to where they came from, and the DataGrid will know how to display them. Its actually quite flexible.

So, now all I have to do is built the SearchStore, subclass the DataGrid to display these results flexibly but correctly, add in “more…” type functionality, and I’m done. Jeez. All this just for a lousy little search field. Man how I wish someone had done this before me! Well, I’ll finish writing this widget, and then post another entry on how it goes, and hopefully include the source so you all won’t have to go through what I have!

July 29, 2008

A Dynamic Tab Container

Filed under: dojo — Tags: , , — maulin @ 4:37 pm

I think tab containers are great. And I think dojo’s tab container implementation is better than great. Its so easy with simple markup to make tabs and really create elegant user interfaces. Further, you lazy load the content of the tabs to make for a reall good user experience, all without much effort at all.

But I frequently use tabs as a tool to filter data views. For example, if I have a table, and want a few standard “views” of that table, I’ll put each view in a tab. Well this leads to a lot of repetitive code. What I really want is to tell the tab container what tabs I want to build, and then create a few small bits of data that can be plugged into each view to “customize” it.

So the “old way” of doing this looked like the following. I’ll use the “stateStore” from many of dijit’s tests as my sample data.

<div>
<div title="All">
<h2>All</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>
<div title="Countries">
<h2>Countries</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>
<div title="Cities">
<h2>Cities</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>
</div>

That’s a lot of code to have three tabs each with a slightly different filtered view of the same table. Enter DataTabContainer.

<div>
<h2>${name}</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>

Ahhh.. Much better. Well, once I had this working for a simple array of tabs, I thought — “how about I swap in a store for the tabs?”. So instead of (or in addition to) the “data” attribute of the widget, you can provide a “store”, “query”, “queryOptions”, and “sort” attribute, much like DataGrid. If you have both a store and data, the tabs in “data” are pre-pended to the store objects. This allows you to include an “All” tab quite easily at the front of the container.

This was actually quite a bit trickier to build than I initially thought. I ended up using a lot of code from dojox.widgets.Iterator, munged with the store/query code from DataGrid to put it together. It basically works like this:

  1. Convert the child elements of the DataTabContainer to a custom widget via dijit.Declaration (DataTabContainer, it may surprise you, actually extends dijit.Declaration, and not TabContainer).
  2. In the buildRendering stage, copy down to the location of the node (since dijit.Declaration is a “pseudo” widget that destroys its own domNode after parsing it).
  3. In place of the destroyed node, create a TabContainer.
  4. Iterate over the data array (or fetch the store items) and create tab for each item in the array. (Using the “titleAttribute” attribute of DataTabContainer as the title of each tab).
  5. In startup(), create an instance of the dijit.Declaration class for the currently selected tab. Before doing so, do a string substitution on the dijit.Declaration class’s prototype.templateString. Note that this is not a simple dojo.string.substitute because it uses the store’s getValue method to get the value to place in the substitution.

And that is it. It works pretty well. Other features I could imagine:

  • If the DataTabContainer is the child of an already existing TabContainer, then use an attribute like “embed=’true’” to skip the creation of the TabContainer step in buildRendering, and instead, just add the tabs to the surrounding container.
  • Allow a “pre-data” and “post-data” to add items around the store fetch items. Right now it only supports “pre” data.

Let me know what you think!


Update:

Well you can forget almost everything I mention about the implementation of this above. I have figured out a much simpler implementation that creates a mixin class to TabContainer, AccordionContainer, or StackContainer and uses the content of the container as a “templateString” that is sent to ContentPane.setContent. Much easier. And now you can do dynamic Accordions and Stacks. Nice! So the updated source is here. And you can try it here. Just remember, to use this code you need to dojo.require the original container (TabContainer, AccordionContainer, or StackContainer), the original pane (ContentPane), and the mixed in stuff (medryx.widgets.DynamicStackContainer).

July 24, 2008

JsonRestStore — Custom Services, Schemas, and Lazy Loading

Filed under: dojo, javascript — Tags: , , — maulin @ 7:02 am

The newest and most complete store in the dojox.data arsenal is dojox.data.JsonRestStore. Its primary purpose is to make interaction between a RESTful data source and dojo.data seamless and simple, and it accomplishes that goal with ease. But a quick peek under the covers and you will find that you may very well be using JsonRestStore for all your Json dojo.data needs. This article aims to show you how you can use JsonRestStore and its more advanced features like lazy entity and property loading and client side filtering, and how you can adapt JsonRestStore to any data source that provides JSON data to your client.

RESTful web services map CRUD services to the HTTP protocol: Create = Put, Read = Get, Update = Post, and Delete = Delete. You can easily sort through the data that would be passed to each of these services as well — Create gets the full data of a new object without an id, Update gets the full data of the updated object with an id, read gets an id who’s object should be returned and delete gets an id whose object should be deleted. Its not rocket science. But if you broaden your thinking from REST, you’ll see this is essentially an API for interfacing with server side data stores. REST is simply one way of implementing it. Instead of REST, you could use an RPC service, or any other service, so long as you could map the service to this API.

JsonRestStore expects you to provide it a service that implements the REST API. The easiest way to do this is to simply specify a target url as a String. JsonRestStore will then automatically send that URL the commands in the API using the REST protocol described above. However, instead of simply providing a target URL, you can actually initialize a JsonRestStore with your own service. The service is simple an object that has the following methods that each return a dojo.Deferred:

  • A directly callable function that takes an id or a query/queryOptions pair and returns the results of the query
  • A put function that takes an id and value and creates the value at the given id
  • A post function that takes an id and value and appends/updates the value at the given id
  • A delete function that takes an id and deletes the value at that id

It is important to know that if you do provide a custom service, you must still provide a unique string as the “target” attribute for the JsonRestStore. As I will explain later — JsonRestStore actually manages interdependencies between objects and between data stores — and the target attribute is how it uniquely identifies one store from another, as well as how it identifies which store a particular object belongs to.

So lets start coding. First lets define a custom service that we will be providing to JsonRestStore. Lets say we are building a system for paging physicians. Each physician has a pager. We’ll start simple (this is illustrative code, not real code!):

var mdService = function(query, queryOptions) {
return dojo.xhrGet({url:"queryUrl.php", handleAs:'json', content:{query:query, queryOptions:queryOptions}});
}
mdService.put = function(id, value) {
mdService.post(id, value);
}
mdService.post = function(id, value) {
return dojo.xhrPost({url:"actionUrl.php", handleAs:'json', content:{action:"update", id:id, value:value}});
}
mdService['delete'] = function(id) {
return dojo.xhrPost({url:"actionUrl.php", handleAs:'json', content:{action:"delete", id:id}});
}

And that is it. Now lets feed this service to JsonRestStore and create new store for our physicians:

var mdStore = new dojox.data.JsonRestStore({target:"md", service:mdService});

You can now use this store as you would any other store — feed it to a tree, or a grid, or to any other dojo.data enabled widget, and everything just works. But it gets better. Much better.

As I stated before, lets build our paging system. In our system we have about 10 pagers and 30 physicians. At any one time, each pager is assigned to 0 or 1 physicians. So in addition to being able to navigate from physician to pager given a list of physicians, we need to be able to navigate from pager to physician given a list of pagers. So lets build that store:

var pagerService = function(query, queryOptions) {
return dojo.xhrGet({url:"pagers.php", handleAs:'json',content:{query:query, queryOptions:queryOptions}});
}
pagerService.put = function(id, value) {
  var d = new dojo.Deferred();
  d.callback(); //put is a noop for pagers
  return d;
}
pagerService.post = function(id, value) {
return dojo.xhrPost({url:"updatePager.php", handleAs:'json', content:{id:id, value:value}});
}
pagerService['delete'] = function(id) {
  var d = new dojo.Deferred();
  d.callback(); //delete is a noop for pagers
  return d;
}
 
var pagerStore = new dojox.data.JsonRestStore({target:"pager", service:pagerService});

This where the magic starts. Lets create some data:

[
  {"id":"1", "name":"Maulin Shah, MD", "pager":{$ref:"pager/1"} },
  {"id":"2", "name":"Doogie Howser, MD", "pager":{$ref:"pager/2"} }
]

and here are some pagers

[
  {"id":"1", "md":{$ref:"md/1"}, "number":"12345", "name":"Yellow Pager"},
  {"id":"2", "md":{$ref:"md/2"}, "number":"54321", "name":"Triage Pager"}
]

Notice the “$ref” notation. This is JsonSchema style notation for indicating references to other objects. This lets you simply define the reference without loading the referenced object, and the referenced object will be lazily loaded when necessary. Notice the the $ref is essentially a “path” to the referenced object — or in less RESTy lingo, its the “class” of the referenced object along with its id (or perhaps its the “table” of the referenced object along with its primary key, if that’s the lingo that floats your boat).

Note that you do not have to make a reference lazy, or you make the reference partially lazy. I’ll go over these concepts a bit later.)

So what happens when we load some physicians into a grid?

<table border="0">
<thead>
<tr>
<th>Physician</th>
<th>Pager</th>
</tr>
</thead>
</table>

This would create a grid that looks something like:

Physician Pager
Maulin Shah, MD [Object object]
Doogie Howser, MD [Object object]

Woops. Not quite what we wanted. The problem here is that the pager object has no toString() method. So we have two options to solve this. Thie first involves using a schema and object prototype with your store.

Each store accepts a “schema” attribute as an initialization parameter. The schema is a JsonSchema style object describing the properties of the json objects that are returned for that store. In addition to the usual “properties” attribute of the schema, the schema can have a special “prototype” attribute that defines an object prototype that is used when the object of this class are instantiated. Thus you can mixin your own custom methods or properties into each instance of objects managed by your store. So lets create the prototype for the pager store:

 var pagerSchema = { prototype: {
      toString:function() {
        return this.number;
      }
    }
  }

Feed this to the store when it is created, and we’ve solved our problem:

  var pagerStore = new dojox.data.JsonRestStore({target:"pager", service:pagerService, schema:pagerSchema});

Now your table will look like you expect:

Physician Pager
Maulin Shah, MD 12345
Doogie Howser, MD 54321

(The second way to solve this problem involves over-riding the getValue method of JsonRestStore to allow dot-path notation. I’ll describe that in a future post that discusses my BeanStore extension of JsonRestStore.)

So what happened behind the scenes in rendering this table? The grid called physicianStore.fetch with no query (which is the same as “fetch all”). The Json described above was returned, with its references to the pager objects. JsonRestStore magically instantiates a new first class javascript object for each item in the Json, using the prototype in our schema as the prototype for the object. When mdStore.getValue(item, “pager”) is called by the Grid, the mdStore initiates a synchronous lazy call to pagerStore’s fetchItemByIdentity method which returns the pager, instantiated with the pager schema, which has a toString method which returns the number.

You could see how laziness could get out of hand very quickly. If we have a hundred physicians, we would have 100 synchronous calls to the server to lazy load pagers. That is not ideal. Lazy loading should really be reserved for more of a master-detail type usage when most lazily referenced items are never actually returned. So how could we make this more efficient? We can modify the data returned in our physicians data to include the pager number as well as a reference to the pager.

[
  {"id":"1", "name":"Maulin Shah, MD", "pager":{$ref:"pager/1", "number":"12345"} },
  {"id":"2", "name":"Doogie Howser, MD", "pager":{$ref:"pager/2", "number":"54321"} }
]

Now this is really cool. Now when rendering the grid, we incur no new requests to the server, since the pager number is already available locally. But now lets say we added a function where clicking on a row of our grid popped up an alert with the name of the pager. Since the name is not available, it would now lazily load the pager (i.e. call pagerStore.fetchItemByIdentity) and flesh out the rest of the pager object. Pretty clever eh?

In this article I have touched on just a few advanced features available with JsonRestStore — using a custom service, using a schema to provide an object prototype, lazily loading objects, and sparsely loading objects. But there is plenty more goodness to be found in this store! Stay tuned for an article on client-side caching and other great features of JsonRestStore!

JsonRestStore retires MedryxORM

Filed under: MedryxORM, dojo — Tags: , , , — maulin @ 5:46 am

When I was first designing the MedryxORM, I had a need for a data store that could perform advanced functions that no store in Dojo 1.1 had available. However with the addition and significant improvement of dojox.data.JsonRestStore, I am happy to say, that I think, after only one month, I can safely retire MedryxORM. This is a good thing. I always opt for off-the-shelf over custom solutions. And, you get the whole dojo communtiy supporting you when you use an official dojo.data store.

In the last month, I’ve worked with Kris Zyp — the Json guru at Dojo — and I really think the JsonRestStore has come a long way to solving many problems with complicated client side data management. First off, let me make this very clear — despite its name, JsonRestStore does not require you to use a RESTful data source or web service. Any web service will do — RPC, or home-grown. You just plug in your services into the JsonRestStore, and the store handles the rest. I actually think its name is its biggest flaw. Perhaps it should simply be JsonStore, so that more people would use it… but I digress.

So here are the features of JsonRestStore that have allowed my to say goodbye to the EntityManager and MedryxORM:

  1. Unique Object Identity: One of my requirements was that if the server sent down an entity, I wanted to be sure that however I accessed that entity, that I got the same entity. So whether I used store.getValue() or item.foo, or item.getFoo(), or item.getBar().getFoo(), I always want the same entity. In my design, there was therefore one store that handled all kinds of entities. This proved a bit clunky, because to access anything from the store via fetch, you had to provide an “entityClass”. JsonRestStore accomplishes this same goal without the clunkiness. With JsonRestStore you create one store for each entityClass. Behind the scenes, JsonRestStore uses dojox.rpc.JsonRest to be sure object identity is maintained. This means that you need a store for each entityClass, but that makes it crystal clear what you are querying for when you do a fetch. Overall, a better design IMHO.
  2. Conversion of JSON hashes to first-class JS objects, allowing for custom functions and business logic to be implemented within entities: If I have an entity, I want to, for example be able to create a toString() method on it, and each time an entity of that type created, it should inherit the toString(). The server should not worry about javascript methods, so sending it in the native JSON data is not the right solution for lots of reasons. My EntityManager used the “class metadata” to determine the class of information being retrieved from the server, and instantiating each object from the metadata, and then mixing in the server data into the object instance. JsonRestStore has a similar “class metadata” concept, but it is more standardized — and based on JsonSchema. If you provide a schema, and it has “prototype” property, then any methods/fields in the prototype are used when the entity is instantiated. Since standardization is a good thing, and since JsonSchema is emerging in the JS world as a standard, I think this is probably a better approach than the custom metadata that was created for MedryxORM — despite the fact that it was loosely based on EJB3/Hibernate.
  3. Built-in laziness: Use store.getValue() and you lazy load items for free. There is a little bit of learning on how to handle laziness on the server side, but its pretty straightforward (and I hope to write a post about it soon).
  4. Client side filtering/sorting: JsonRestStore allows you to cache query results on the client and then filter them and sort them seamlessly on the client. This safely retires my “named queries”, and is both more efficient and more extensible.

Honestly, the only thing missing from JsonRestStore is de-novo support for javabean style getters and setters that also give you free laziness, and support for dot-path notation in getValue(). I’ve added this with a simple additional class called BeanStore that extends JsonRestStore, and I have submitted BeanStore to Dojo to see if they think it is broadly applicable enough to include in dojox.data. We’ll see.

I have to say, the most irritating part of all of this is that I lose 2 weeks of work. But I can always look back at how intricately I now understand dojo.data and JsonRestStore and know the time was not completely wasted. In my next post, I hope to share with you my understanding of JsonRestStore and its use.

June 24, 2008

MedryxORM and dot-path notation

Filed under: MedryxORM, dojo, javascript — Tags: , , — maulin @ 7:05 pm

Another nifty feature of MedryxORM is dot-path support. Since this ORM is all about relationships beween domain objects, it only makes sense to give the you access to traversing your relationship paths. This is used in two places primarily: getValue and properties. Let me give you a couple examples.

Lets say you are making a table of patients. You want to display their name, room number, and insurance. The object model looks something like:

In javascript, you would represent this model as:

 
dojo.declare("model.BaseEntity", null, {
	id:new medryx.orm.Identifier()
});
 
dojo.declare("model.Patient", model.BaseEntity, {
	firstName:new medryx.model.Property({label:2}),
	lastName:new medryx.model.Property({label:1}),
	visit:new medryx.model.Association({associationClass:"model.Visit"})
 
	getDisplayName:function() {
		return this.getLastName() + ", " + this.getFirstName();
	}
 
	queries:[
		{name:"AllPatients", properties:["*", "visit.*"]}
	]
});
 
dojo.declare("model.Visit", model.BaseEntity, {
	room:new medryx.model.Property(),
	bed:new medryx.model.Property(),
	insurance:new medryx.model.Association({associationClass:"model.Insurance")
});
 
dojo.declare("model.Insurance", model.BaseEntity. {
	companyName:new medryx.model.Property(),
	groupNumber:new medryx.model.Property()
});
 
entityManager.registerClass("model.Patient", "Patient");
entityManager.registerClass("model.Visit", "Visit");
entityManager.registerClass("model.Insurance", "Insurance");

So after running all this code, you now have a managed model that you can query (assuming you have set up your perisistenceService) and display the results, lets say in a grid. So lets define a grid:

	var view = {
		cells:[
			[
				{name:"Name", field:"displayName"},
				{name:"Room", field:"visit.room"},
				{name:"Bed", field:"visit.bed"},
				{name:"Insurance", field:"visit.insurance.companyName"},
		]]
	};
	var grid = new dojox.grid.DataGrid({
		store:entityManager,
		query:{},
		queryOptions:{entityClass:"Patient", namedQuery:"AllPatients", properties:dojo.map(view.cells[0], function(column) {
			return column.field;
		})},
		structure:[view]
	}, dojo.byId("patientsGridNode");

And that is it. So what have we done here?

As you can see, I’ve declare 3 simple classes. Each has some simple properties, and for kicks, I’ve declared a simple getDisplayName() method on my Patient.

I’ve defined a named query that get all patients from the server, and for each, returns all properties of the Patient and the patient’s associated Visit. (Note that this means the server will return the id of the insurance association, but not any attributes of the insurance company (i.e. the companyName and the groupNumber are not returned.)

Next we declare a simple dojox.grid.DataGrid. (This uses the grid model from Dojo 1.2, where DojoData has been removed and essentially plunked into DataGrid itself.)

As you can see, the “field” name in the view attached to the grid’s stucture uses “dot-path” notation to get to the desired field to display. Since our query is returning Patient’s, we need to navigate from the patient to the desired display field. This fails gracefully, retuning null if any part of the path does not exist.

Now lets look at the queryOptions in the grid. First, see how there is no query? I don’t absolutely have to have one — which means by default I will show all patients. The queryOptions are used to tell the entityManager which query to run. We then send in a new list of properties for the entityManager. Since we know that we will certainly need all the displayed fields in our data set, we can simply map() the view into a list of properties that we would like to instruct the entityManager to fetch.

Here is where things get really nice. Without any work on your part, the entityManager will have only made one call to the server when all of this is done, and it will have all the data you need to display your grid.

Now lets say you wanted to create a “detail” view that allowed people to see the patient’s insurance groupNumber if they clicked on the insurance information. Simple!

dojo.connect(grid, "onRowClick", function(e) {
	var patient = grid.getItem(e.rowIndex);
	alert("GroupNumber: " + patient.getVisit().getInsurance().getGroupNumber());
});

Notice how there is no query here, or anything else for you to write! You just use your getter, and you get lazy initialization of the groupNumber for free!

Older Posts »

Powered by WordPress