Medryx Observations

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).

June 9, 2008

Building (and Testing) Dojo Widgets — MVC?

Filed under: dojo, javascript, oop, unit testing — Tags: , , , , , — maulin @ 8:04 am

There are plenty of tutorials out there about Dojo widgets, so I won’t write another one. But I would like to document here my philosophy for writing widgets, allowing for separation of concerns and perhaps even an MVC architecture.

In my mind, a widget is a hybrid of view component and model component. It builds a UI, then receives events on that UI that it passes on the model. But this can get messy, especially for testing, debugging, and changes later on down the road. So I propose that we separate the widget into its elemental components, and then only combine them in the “final widget”. Here’s what I mean:

Lets create widget called TeamList, that creates a list of teams. When you click on a team you get a dialog with team details.

/widgets
---/ui
------/tests
---------/all.js
---------/runTests.html
---------/TeamListTest.js
------/_TeamList
---------/TeamList.html
---------/TeamListView.js
---------/TeamListController.js
---------/tundra.css
------TeamList.js
---/themes
------/tundra.css

As you can see, I actually seperate my widgets into several files:

  1. TeamList.html: the standard widget template
  2. TeamListView.js: a class that DOES NOT derive from the usual _Widget or _Templated, but that “pretends to”.

    This class is used to manipulate everything related to the UI, and to create the events that the widget will generate (or translate the low level user events to more useful ones for the application).

    The key is that none of the “high-level” events will trigger ANYTHING to happen in the model. That is the job of the controller.

    This makes this class quite isolated, and fairly easy to test and change, as long as it adheres to its contract of high-level events that it will trigger.

    Here is the code for my TeamListView:

     
    dojo.declare("medryx.widgets.ui._TeamList.TeamListView", medryx.AbstractBase, {
        //*** attributes
        //filter the teams
        filter:null,
     
        //sort the teams. this can be an array of sort objects (see AbstractDao.sort)
        //or can be a comma-seperated list of fields (simpler, but less flexible)
        sort:null,
        headerContent:"",
    	footerContent:"",
     
        //*** internal
        templatePath: dojo.moduleUrl("medryx","widgets/ui/teamList/teamList.html"),
        controller:null,
        teams:null,
     
        //** nodes defined in the template -- only defined here so we remember them
        list:null,
     
        /**
         *  abstract method
         *  MUST return a valid initialized controller
         */
        $buildController:null,
     
        postCreate:function() {
            this.controller = $buildController();
            this.loadTeams({
                filter: this.filter,
                sort: this.sort
            });
        },
       /**
         * delegate to the controller.
         */
        loadTeams:function() {
            var deferred = this.controller.loadTeams(arguments);
            deferred.addCallback(dojo.hitch(this, this.displayTeams, this.list);
        },
       displayTeams:function(list, teams) {
    	dojo.forEach(teams, function(team) {
    		var li = document.createElement("li");
    		li.innerHTML = team.getName();
    		list.appendChild(li);
    		dojo.connect(list, "onclick", dojo.hitch(this, "teamClicked", team));
    	});
       },
       teamClicked:function(team, event) {
       }

    So lets point out some interesting things about this “View”. Notice that it does not do anything about actually loading teams, or handling what happens once a team gets clicked. It just translates the low level click event to a high level teamClicked event that passes the team of interest to the event.

    Next, notice that while we implement postCreate(), we are not inheriting (yet) from _Widget or _Templated. Here’s why. Lets say I want to test this class. If it had already inherited from these classes, it would automatically call the lifecycle events of the class. But I don’t necessarily want that to happen… yet. I want to call my postCreate method manually and run my tests to be sure everything in this class is working right, before I start wiring things up.

    One other small point — look at displayTeams. Notice how I actually pass the list to which I want to attach the items to this method. Obviously I could use “this.list” throughout displayTeams, and then I wouldn’t have to pass the argument. But stylistically, I find it easier to write tests for methods that are “self contained” and have as little reference to “this.*” as possible. If I hadn’t passed the list as a parameter, I would have had to remember in my tests to set this.list to a new list before running my test. And you can easily forget to do that. In other words, I try to make my widgets as “stateless” as possible in the View. I like to let the final widget inject all the state that is necessary.

    Here is the (very simple) controller class:

    dojo.declare("medryx.widgets.ui._TeamList.TeamListController", null, {
        view:null,
        constructor:function(view) {
            this.view = view;
            dojo.connect(view, "teamClicked", this, "pageTeam");
        },
     
     /**
      * return a deferred that will callback for all teams
      * that are to be display. the arguments are filter and sort
      * either as parameters or as kw arguments
      */
        loadTeams:function() {
            return medryx.dao.TeamsDao.getAll(arguments);
        },
     
        pageTeam:function(team) {
            var pageForm = new medryx.widgets.ui.PageForm();
            pageForm.setTeam(team);
            pageForm.show();
        }
    });

    This is where I connect this widget to other layers of the application. So all interactions outside of the widget should occur through this controller. Notice how I connect up the “teamClicked” event to a “pageTeam” function. Another advantage of separating the view from the controller is that allI have to do is a use a different controller, and I can get a totally different widget. Again, this is a fairly easy class to test .

    So where does this all come together? In medryx.widgets.ui.TeamList, of course!

    dojo.declare("medryx.widgets.ui.TeamList", [dijit._Widget, dijit._Templated, medryx.widgets.ui._TeamList.TeamListView], {
    	$buildController:function() {
                 return new medryx.widgets.ui._TeamList.TeamListController	(this);
              }
    });

    All this does is create the actual widget class, mixin the _Widget, _Templated to allow the magic of dojo to take over, and to define the controller that will be used.

Powered by WordPress