Archive for the ‘javascript’ Category

jQuery: Grid like table with keyboard navigation

Thursday, October 8th, 2009

Guess what time is it kids? It’s time for yet another boring, technical post. It’s that sort of week. Or rather it was – I usually queue my posts approximately 8-10 days in advance. This means that if I get hit by a bus one day, you won’t know anything is wrong until a week or so later. But I digress…

One of the web apps I maintain has a big table that looks like this:

<table>
	<tr>
		<td><input class='flat' name='foo'></td>
		<td><input class='flat' name='bar'></td>
		<td><input class='flat' name='baz'></td>
		<td><input class='flat' name='bin'></td>
		<td><input class='flat' name='bom'></td>
		<td><input class='flat' name='bam'></td>
	</tr>
 
	. . .
	. . .
	. . .
 
	<tr>
		<td><input class='flat' name='foo'></td>
		<td><input class='flat' name='bar'></td>
		<td><input class='flat' name='baz'></td>
		<td><input class='flat' name='bin'></td>
		<td><input class='flat' name='bom'></td>
		<td><input class='flat' name='bam'></td>
	</tr>
</table>

Basically, every cell contains an input box. Each of them has an onChange trigger which will submit it’s contents to the database via AJAX call. It basically allows the user to pull up a full page of records and edit them en masse without doing a lot of clicking.

Unfortunately this setup is a pain in the ass to navigate. Tabbing over works fine but only in one direction. The most intuitive way to traverse such a structure would be with the arrow keys – you know, just like a big spreadsheet. Sadly it just doesn’t work that way. But we can make it work like that with just a pinch of jQuery magic:

$(document).ready(function() {
    $("input.flat").keypress( function (e) {
        switch(e.keyCode)
        {
            // left arrow
            case 37:
                $(this).parent()
                        .prev()
                        .children("input.flat")
                        .focus();
                break;
 
            // right arrow
            case 39:
                $(this).parent()
                        .next()
                        .children("input.flat")
                        .focus();
                break;
 
            // up arrow
            case 40:
                $(this).parent()
                        .parent()
                        .next()
                        .children("td")
                        .children("input.flat[name="
                            +$(this).attr("name")+"]")
                        .focus();
                break;
 
            // down arrow
            case 38:
                $(this).parent()
                        .parent()
                        .prev()
                        .children("td")
                        .children("input.flat[name="
                            +$(this).attr("name")+"]")
                        .focus();
                break;
        }
    });
});

How does this work? Each time you press a key while one of the input boxes is in focus, I check the key code. If the code corresponds to one of the arrow key values I switch focus. The statements up there look a bit convoluted so let me explain one in more detail. Let’s do the left arrow:

  1. First we use the parent() function to get the parent node of the input box. This gives us the <td> tag
  2. Second, we use the prev() to get the previous sibling of our <td> node
  3. Third, we get the list of children of that node – and we narrow it down to just input boxes with the specific class. Because of the way our table is structured, we know there will always be exactly one child there
  4. Finally we call the focus() function on all the children (remember, there is only one there) which moves the cursor over

Moving up and down, requires extra steps. We call parent() twice to back out to the <tr> tag and grab previous or next sibling of that. Then we grab all the <td> children, and their children but narrowing it down to input boxes with the same name as the one that triggered the focus change. Once again there will always be exactly one node there.

End result is intuitive keyboard navigation that allows you to move in the table very much the way you move around in an Excel spreadsheet. I thought this was a pretty neat effect so I decided to share it here.

I created a live demo to demonstrate this effect. Mess around with it and let me know what you think. It’s a simple little tweak, but makes a huge usability difference – at least in my opinion.

Does anyone has an idea how to make this prettier, faster, better, stronger and etc? This code is probably suboptimal, but it works. I actually tested it on large-ish data sets, and it didn’t seem to cause visible slowdowns. But of course I’m always open for constructive criticism.

Free Application Cloud Hosting Not Feasible?

Thursday, June 4th, 2009

Back in December I wrote about AppJet – a fun and promising new Google App Engine like service. It was an incredibly well designed Javascript based framework that allowed you to deploy web applications on their cloud. The hosting was free, just like App Engine, but their interface was much more user friendly.

While App Engine makes you download a special toolkit, and memorize command line switches to deploy your code, AppJet utilized a web based IDE. You could create your application right there in your browser. What is more, you could could look the source code of existing apps, and even “clone” them at a press of a button. It was by far the most newbie friendly application hosting environment that I have seen on the web. Nothing else even came close with respect to ease of use, learning curve and intuitiveness of the interface.

Unfortunately that service is now gone. AppJet Inc decided to discontinue their cloud hosting framework to concentrate on their flagship product EtherPad which (as opposed to the hundreds of poorly written applications they were supporting) they can actually market for profit. I’m not sure what prompted this decision, but I can make an educated guess. Supporting the framework and it’s community was probably a resource drain that did create some hype and drive people to their website but ultimately made them no money. Some shrewd business-monkey probably noticed that and decided to axe the project and re-purpose it’s resources towards their money making product.

After all there is no such thing as free hosting (or fee lunch) – someone has to pay for it. Google can support their App Engine because… Well, because they are Google. They probably have more money to burn on superfluous projects in their budget this month than I will earn in my whole life. So for them offering cloud hosting for applications is entirely feasible and realistic. If you are a small startup like AppJet was it’s a whole different story.

Its sad to see this neat little service go away. Fortunately AppJet is not discontinuing their stand-alone server package so those who put time and effort into creating applications using their service can still migrate and self host them. Still, it is disheartening to see this happen to such a promising project. I was hoping that this type of hosting will catch on and other companies will jump on the bandwagon. AppJet gave me hope that in the near future it will be as easy and as straightforward to publish a personal application for free as it is to publish a personal web page right now. These days just about anyone can start a blog or a forum – there is nothing to it. You press a button and you are done.

Seeing AppJet make it possible to host a complex web application just as easily gave me think that this bright future is just around the cornet. I expected to see an explosion of Cloud hosted application frameworks everywhere. The opposite has happened. AppJet folded and got out of the cloud hosting business. I guess they were simply ahead of their time.

I’m sure we will see this type of project again at some point. The whole idea of in-browser IDE, one-button application cloning and rapid deployment is just to neat to abandon. Perhaps in a few years someone else will pick up this thread and hit it big. Perhaps it will be one of the big boys (Google, Yahoo, Microsoft). I’m pretty sure people would actually be willing to pay for this type of user friendly web interface. That said, AppJet can probably squeeze more profits from Enterprise version of EtherPad than they would from paid hosting. That of course does not mean that such a service would not be profitable.

SQL Emulation Tool in Javascript Part 2

Tuesday, May 19th, 2009

As promised, I’m posting my semi-working sql parser below. You should keep in mind that the code is still very immature and full of bugs. One of my reasons for posting it here is that people will start playing around with it and break it in numerous ways helping me to discover the way I can improve the code.

I think I covered most of the architecture in the previous post. One bit of code I wanted to share here is my metaprogramming function generator. This bit of code takes in a list of conditions, and will generate a jaascript function that will test for these conditions and return a boolean value.

When my SQL parser evaluates the WHERE condition it constructs an array that looks a little bit like this:

Conditions Array (click to embigen)

Conditions Array (click to embigen)

Each member object is composed of five elements. The first one is the name of the column, the second one is the value that is used in comparison, and the third one is the comparison symbol. The fourth value is just a flag which indicates whether or not the parser was able to successfully generate this object. It is needed there since malformed SQL could produce only partially generated object to be added into this array. All new objects are generated with this flag set to false, and it is changed only after they are populated without errors. This let’s us spot and ignore malformed and incomplete entries.

The fifth field is the logical operator which is used to join the comparison described in the current object to the previous one. The first element of the array will always have it’s logic field un-initialized. The following elements will have it set to a legal logical operator – this is enforced by the parser.

Once I have this array I pass it into this function:

function generateCondFunction (conditions) {
 
	var tmp = "cond = function(row) { if(";
 
	for(i in conditions)
	{
		current = conditions[i];
 
		if(!current.full)
			throw "Incomplete WHERE statement";
 
		if(current.action == "<>")
			action = "!=";
		else if(current.action == "=")
			action = "==";
		else
			action = current.action;
 
		if(current.logic != null)
		{
			if(current.logic == "and")
				tmp += " && ";
			else 
				tmp += " || ";
		}
 
		tmp += " row[\"" + current.first + "\"] " + 
			action + " " + current.second;
	}
 
	tmp += ") return true; else return false; };";
 
	eval(tmp);
 
	return cond;
}

The beauty of Javascript is that you can build a string, and then execute it as code. This is precisely what I’m doing here. I use the elements from my array to build a comparison function, then evaluate it and return a function pointer. For example, the array from the picture above will yield the following function:

function(row)
{ 
    if( row["foo"] > 2 and row["bar"] != "poo" ) 
        return true; 
    else 
       return false; 
}

I take this function pointer and pass it into the table rendering function. Then as I iterate over the elements stored in my mock db-obects I pass them through this function fitst to see if they ought to be displayed or not. This is of course not the most efficient way of doing things, but it works.

Anyway, go check out the working demo here and let me know of weird bugs that you encounter. Please keep in mind that a lot of stuff still doesn’t work the way you would expect it. Here is the stuff that I know is still broken:

  • The canonical SELECT * FROM FOO doesn’t work – I have not implemented the wildcard selectors yet
  • If you don’t put spaces between listed columns, the thing will break
  • If you don’t put spaces in the WHERE condition, things will break. Typing in “foo < 2″ works fine but “foo<2″ does not
  • No common procedures (like NOW() and etc..) are implemented

Don’t report these things. Anything else though, will help me debugging the code. If you want to read through the whole thing, and nitpick or criticize my questionable coding practices you can find all the code here.

Also, Chris already told me that someone already created an SQL parser in Javascript. As far as I can tell TrimQuery is far superior to my hackish code here, so if you would want to use something like this in your project, you are probably much better off stealing code from there rather than from here.