Saturday, December 24, 2005

Prototype.js: Event

If you write a lot of JavaScript code it is likely that you have written pleanty of event hendlers in your day. For simple tasks, this is usually failry straight forward to write. If you need to get data from the Event object, well, you are in for some pain. When Microsoft won the browser wars you could, for the most part, ignore other browsers. Firefox currently has about 10% of the market, and Safari has about 5%, neither of which can be ignored.

In this installment of the Prototype series I cover the Event API. It only has a few methods, but these methods are built to be cross-platform compatible. It also includes a few extensions that are rather useful for some types of work.

API Constants

The Event object includes the following constants which can be used for the handling of keypress events so that you don't need to remember the key code for these keys.


Event.KEY_BACKSPACE
Event.KEY_TAB
Event.KEY_RETURN
Event.KEY_ESC
Event.KEY_LEFT
Event.KEY_UP
Event.KEY_RIGHT
Event.KEY_DOWN
Event.KEY_DELETE


The Prototype Event object doesn't include any helpers for handling keypress events, but you could perform a check like this.


function keypressHandler (event)
{
// I think this next line of code is accurate,
// but I don't have a good selection of browsers
// with me today to test this effectivly.
var key = event.which || event.keyCode;

switch (key) {
case Event.KEY_RIGHT:
alert('moved right');
break;
case Event.KEY_LEFT:
alert('moved left');
break;
}
}


API Summary


Event.element(event)
Event.findElement(event, tagName)
Event.isLeftClick(event)
Event.observe(element, name, observer, useCapture)
Event.observers
Event.pointerX(event)
Event.pointerY(event)
Event.stop(event)
Event.stopObserving(element, name, observer,
useCapture)
Event.unloadCache()


API Details

Event.element(event)

Params:
event - An Event object

Returns:
The target element for the event.

This method, as well as all of the Event methods, are static methods. This means (for those not familiar OOP) that you call the method on the Event class, and not an instance of an Event. The returned element would be the button that was clicked, the text field that changed, or whatever element triggered the event.

Event.findElement(event, tagName)

Params:
event - An Event object
tagName - The name of a tag (e.g. "div", "a", etc.)

Returns:
An HTML element.

This is a cool little helper function that could potentially be very useful in certain cases. It takes an event and a tag name, and returns an HTML element of that type that is closest to the target event (see Event.element(event)). The tag name you pass is used in a case-insensitive manner.

The method determines the result by starting at the target element and traversing the HTML DOM backwards, meaning through the parent nodes until the document root is reached. If the tag is not found, the document root is returned. An easy way to test a failure to find an element of the specified tag name is to check for a value in the tageName property of the returned node.


function doStuff (event)
{
var node = Event.findElement(event, 'div');

if (node.tagName) {
// we found a tag
}
else {
// there was no tag by that name found
}
}


Event.isLeftClick(event)

Params:
event - An Event object

Returns:
Boolean value, true if the left mouse button was clicked.

With this method the rule is buyer beware. The method SHOULD return true if the left mouse button is clicked, but this may not always work as expected. For example, on my Dell laptop clicking a button in Firefox will return true for left click, while in Internext Explorer it will not. The Prototype library does it's best to accuratly return the correct value, but as with all JavaScript projects, be sure to cross-browser test your work.

Event.observe(element, name, observer, useCapture)

Params:
element - An element object, or element ID
name - the event name to observe
observer - A function reference
useCapture - see Gecko DOM reference

Returns:
nothing.

This is the most important method in the Event class, allowing you to set event handlers in a browser agnostic fashion. On some browsers (notably IE) the event object is NOT passed to the event handler, which makes it a pain to code handlers in a cross-browser compatible way. This solves that issue, and will guarantee that the event object will be passed to the hendler on all browsers.

Note that you can not attach an event to an element until after the page has loaded. So you should call this method only when you are sure that the page load is complete, as in the example below. Also notice that the event name is the name of the event without the familiar "on" prefix. For example, use "click", not "onclick".


window.onload = function ()
{
// handle onclick event for element of ID="foo"
Event.observe('foo', 'click', doStuff);

// handle onchange event for element of ID="bar"
Event.observe('bar', 'change', doStuff2);
}


Event.observers

Returns:
false if no observers, otherwise an Array

This property will return a list of Array references, one for each event being observed, or false when no events are being observed. The Array referece contains 4 elements, the same as the arguments passed to the Event.observe() method, namly [element, name, observer, useCapture]. This is useful is you ever have a reason to inspect the events currently being handled.

Event.pointerX(event)

Params:
event - An Event object

Returns:
The x-coordinate of the mouse

Returns the x-coordinate of the mouse at the time the event is triggered, even if the mouse did not trigger the event. This is done in a cross-browser compatible way.

Event.pointerY(event)

Params:
event - An Event object

Returns:
The y-coordinate of the mouse

Returns the y-coordinate of the mouse at the time the event is triggered, even if the mouse did not trigger the event. This is done in a cross-browser compatible way.

Event.stop(event)

Params:
event - An Event object

Returns:
nothing.

This method attempts to stop the propogation of the event in a cross-browser compatible way. Again, milage may vary, and cross-browser testing is essential.

Event.stopObserving(element, name, observer, useCapture)

Params:
element - An element object, or element ID
name - the event name to observe
observer - A function reference
useCapture - see Gecko DOM reference

Returns:
nothing.

This method removes an event handler that was set using Event.observe(). To get a list of observed events as runtime, see the Event.observers property.

Event.unloadCache()

Params:
event - An Event object

Returns:
nothing.

This method removes all event handlers registered using Event.observe(). This method is automatically called on page unload so as to prevent memory leaks.

Friday, December 16, 2005

A Short Update

I have fallen off my pace of roughly 5 entries a week due to a extraordinarily hectic past few weeks. The holidays are here which didn't help, but primarily the cause was a large project at work that has finally launched. So hopefully I can find my groove again, and get back to the pace that I was at.

Some Topic Ideas

A reader of the entry "Build Your Own Personalized Google Page" suggested that I write an article on transparently persisting changes that occur in the browser, such as dragging and dropping widgets. Not a bad idea.

It also looks like my entry "Prototype.js : PeriodicalExecuter" gets a large proportion of my traffic. In writing that entry I felt that the PeriodicalExecuter was severly underpowered compared to rest of the Prototype library. My thought was that I could create a better mouse trap, and write an article on using it. To me it just seems that there should be methods to stop, restart, and possibly change the execution rate.

It has been several weeks since the launch of Google Analytics, and I think it may be good to revisit this and see how Google is doing now after a rocky start. I will also share some of my reports, including the Geo Map Overlay that shows you where your traffic is comming from (Hello to all my readers from Turkey, Germany, Netherlands, Australia, Japan, and everywhere else!).

If you have other ideas, please comment, and I'll see what I can do.

Thanks for reading.

Thursday, December 08, 2005

Taming Log4J

Note: My posts are coming a little slower recently due to impending deadlines and holiday chores. I still plan on posting as much useful content as possible, but I expect the slower than usual pace will last for the next few weeks.

A co-worker and myself were working late, trying to solve one last problem before playing some foosball and calling it a night. We were not only baffled by the problem, but also by our inability to get Log4J to print out debugging information. We changed the properties file, and restarted the app server via a script that we have permission to run but not edit, but the app server wasn't seeing the changes. Unfortunately we had very limited access regarding the app server, which apparently wasn't behaving properly, and the hour was late, so trying to find an admin with access would be difficult. I did though have the ability to install a web application, in a round-about way, and another co-worker had found a web application that allowed for the configuration of Log4J without a server restart. This is when I installed LogWeb.

On the LogWeb struts application created by codeczar that allows you to configure Log4J through a web application. It features view all of the configured loggers, add additional loggers, set their log levels, list appenders, add and configure appenders, and pretty much everything else you can do in the Log4J configuration file.

There is the ability to download the in memory Log4J configuration as a log file. To compliment this feature is tool which allows you to upload a configuration file. For me, this feature alone has a high cool factor. You could create several Log4J configuration files for debugging issues, or perhaps just to spy on things to make sure that everything is running as you expected. You could then use LogWeb to load the Log4J properties file that best matched the information that you wanted to extract. For example, maybe you want to capture the database activity on your production server for a period of ten minutes, just long enough to get a solid dataset to browse through. With LogWeb you could upload the new properties file, then in ten minutes reload the original, all without restarting or reconfiguring the server.

If you rely with Log4J, Ii strongly recommend taking this tool for a test drive. It worked out of the box, just deploy the war file and use. Of course, you should probably configure your server to hide the tool from public view. But maybe you don't have the ten minutes (I know the feeling), you can go to the LogWeb website and take LogWeb for a test drive with slightly reduced functionality.

Next I need to find myself a good JMX web-based tool...

Sunday, December 04, 2005

ADV: Advertising in RSS Feeds

The holiday spirit has come to visit me today! I just picked up my Christmas tree yesterday, and this morning I woke up to the first snowfall of the season. This is a great time of the year.

With the holidays or course comes shopping, so that I can buy gifts for family, friends (and of course my self). With shopping comes a flurry of advertising, so much that it can be difficult to extract the zillions of catalogs from your mailbox. And on this past Friday I had the pleasure of seeing my first RSS advertisement. Oh the joy!

I did a little digging, and found a couple of resources that helped me put this into perspective. The first is RSS Advertising Case Study, which compares, to a degree, RSS vs. Email advertising. The moral of the study is that a relevant ad placed in an RSS feed will outperform an email advertisement. There are several reasons for this.

1. RSS is 100% opt-in. You are pulling the feed, and not being pushed to. You have control over the relationship, and can break it at any time.

2. RSS has a 100% delivery rate. RSS isn't being blocked by spam filters, and is always delivered.

3. Email ads are annoying. Because anyone can send you an email, everyone does. The response is of course to block ads entirely, and if that means that some useful ads also get blocked, the n so be it.

But again, the key word is relevant. A good example of a poorly placed RSS ad would be the ad that I saw this past Friday. It was a Symantec feed, and the title of the top article in the feed was "ADV: Low mortgage rates". I like the fact that it was fairly unobtrusive by being clearly marked as an advertisement, but what does mortgage rates have to do with a feed about virus protection? Perhaps they would have been better off promoting some data security service, or maybe even one of their own products.

As a developer I understand that the reason for site, and now RSS advertising, is that it is used to pay for bandwidth, software, hardware, and people like me to maintain those systems. As a user I accept that I will see advertising on the sites I visit, in the same way that I accept the presence of commercials on television. Of course there is a limit, which is why many people run ad and popup blockers. The next site I visited discussed this in an article called, "Blocking RSS advertising".

In the article several RSS aggregator applications discussed the possibility of adding ad blocking to their software. For the most part it seems that the answer was no, they weren't going to add specific blocking technology at this time. Most of them though have filter support though, so in theory you could write filters to block ads from appearing. I'm not sure if there will ever be a strong need for this though, because if you don't like a feed, then you can turn it off. This single fact means that the feed producer now has something to lose if they abuse RSS advertisements, which differs greatly from the email model.

So, are RSS feeds a good thing? Yes, I think so, you need to pay the bills somehow. Will marketeers use RSS feeds the wrong way? Yes, I expect them to, but then we can just drop their feed.

Friday, December 02, 2005

Java Decompilers

Good... Bad... I'm the guy with the gun.
- Ash, Army of Darkness

Sometimes you need to pull out the tools of the trade that are a little controvercial, like Java decompilers. In some cases, usually do to poor documentation on a closed system, it is the only way to get the details that you need. I ran into such a case recently, and did some searching.

After trying out a few decomilers I settled on Jad, for two reasons. First, it didn't have a problem decomiling some files that Mocha choked on. Second, there is an Eclipse plug-in for it, which is my IDE of choice.

The following is based on my experience with installing Jad on Windows XP, and Eclipse 3.1.

Installing Jad is easy. Just go to the Jad site, download the distribution for your platform (I am using WinXP), and unzip the archive somewhere on your PC. From here you can use the Jad stand-alone executable. I also added the path to the Jad executable to my Windows path, but I don't think that this is required for using the Eclipse plug-in.

Next you need to download JadClipse plugin. I tried using the latest release, which is at the time of this writing 3.2, but it didn't work for me. I then tried version 3.1 which worked like a charm. So if one doesn't work, then try the ohter.

Place the downloaded JadClipse jar file into your Eclipse plug-ins directory, then start Eclipse from the command line using the -clean switch (e.g. "eclipse -clean"). From here you should see JadClipse show up in the Eclipse preferences under the Java category. You will need to set the path to the Jad executable before it will work.

Once this was done I was able to click on any .class file attached to my project, and the source code would be displayed in the editor view. This works even when you select a class that is part of a jar file in your project classpath. Note that this isn't the same exact code as was originally written. For example, variables will have different names, and there will be no Java comments.

If this doesn't work, perhaps because you are using an older version of eclipse, you can find another tutorial on DevX. If you want information on other decompilers, I also found a nice How-To by Al Dav.

Thursday, December 01, 2005

The Design is the Requirements

I have been checking out Google Sitemaps, but don't have anything to post yet, so I found this article that I wrote a little over a year ago, which had not been posted publicly. Although I think the grammar could use some work, I still agree with the concept, especially for smaller scale web projects.

In software design we are always hearing about the requirements gathering process. I got to thinking about this over the weekend, and wondered if this really applies to web applications. Does it help to have a list of test cases or features when building a small to medium sized web application? In my experience the answer is yes, but not in the traditional sense.

Typically we recieve the HTML pages from the client, and they say "make this work". The pages include sample data, they include the navigation and layout. The design is what they are comfortable discussing because they can see it, and they know that this is what it should look like when the project is complete.

So I contend that the design is the requirements.

If you think about it, this simplifies the requirements gathering process for us, mostly because we aren't part of it :). The stake holder talks to their designer, and says it should look like this, it should do this. The designer then builds the pages to spec, which is in turn reviewed and tweaked. By the time we see the files, they represent the finished design.

From the design we can interpret how the application should work. We look at the links provided and follow them to see which sub-page they go to. We see an "add to cart" button, and we see the finished cart page design.

All we need to do is make the pages work by adding code to them.

Of course it isn't quite as simple as that, but after dealing with this sequence of events time after time you begin to gain the skills in reading the requirments from the design. You train yourself to go over each page, realize the linkage between pages, realize the work done by each page, and understand what the stake holder wants.

Every once in a blue moon someone decides a formal requirements process is required. Based on the few times this happend with projects I was a part of, the process faulters fairly quickly. The stake holder doesn't really know what should be in a requirements document, or how it should be written. And use cases are a foreign concept to those that are not developers.

So yes, we need requirements. We require them to be able to do our job. But there is no rule saying how to create the document. So let the designer become the requirements document creator, and let the design be the document itself. This is something we can all understand with a little practice.