Wednesday, December 19, 2007

Accordion select list

Kevin Miller has created an accordion widget using prototype and script.aculo.us. In this article I'll demonstrate how to build a selection list that has sub-sections utilizing the accordion control.

Here is a demonstation comparing a basic select list control and the accordion select list:


To make the control work, you first add a read-only text box with the drop down button image floating to the right of it, for example:

<span style="float: right">
<img id="accSelectToggle" src="images/btn_dropdown.png" width="21" height="16" alt="Expand list" title="Expand list" />
</span>
<input type="text" id="accSelect" title="List" readonly="readonly" value="Option 1.1" />


You then define a div which contains the accordion control, such as:

<div id="accSelectOptions" class="accordion_container" style="display: none">
<h1 class="accordion_toggle">Category 1</h1>
<div class="accordion_content">
<ul>
<li value="option11">Option 1.1</li>
<li value="option12">Option 1.2</li>
<li value="option13">Option 1.3</li>
</ul>
</div>
...
</div>


Add your preferred CSS styling. In my example the CSS looks like:

#accSelect {
width: 169px;
height: 14px;
border-style: none;
padding-left: 4px;
}

.accordion_container {
z-index: 10;
position: absolute;
width: 198px;
border: solid 1px black;
}

.accordion_toggle {
display: block;
padding: 0.3em 0.5em;
font-weight: normal;
font-size: 1em;
text-decoration: none;
outline: none;
border-bottom: 1px solid #def;
color: #000000;
cursor: pointer;
margin: 0px;
background-image: url('images/expand.gif');
background-position: 99% 50%;
background-repeat: no-repeat;
padding-right: 5px;
background-color: #9BE;
}

.accordion_toggle_active {
color: #ffffff;
font-weight: bolder;
background-image: url('images/ns-expand.gif');
background-color: #47B;
}

.accordion_content {
background-color: #ffffff;
overflow: auto;
display: block;
}

.accordion_content ul {
padding-left: 1em;
margin: 0;
padding-right: 0;
padding-bottom: 0px;
list-style-type: none
}

.accordion_content li {
margin: auto;
padding: 0px;
}

.highlight {
background-color: #DDD;
}


And finally add a little JavaScript to make the magic happen:

function toggleAccordionSelect(event) {
var image = event.target;
var collapseIcon = "images/btn_dropdown-selected.png";
var expandIcon = "images/btn_dropdown.png";

if ($('accSelectOptions').style.display === 'none') {
image.src = collapseIcon;
image.alt = "Collapse List";
image.title = "Collapse List";
$('accSelectOptions').show();
}
else {
image.src = expandIcon;
image.alt = "Expand List";
image.title = "Expand List";
$('accSelectOptions').hide();
}
}

function toggleHighlight(event) {
var item = event.target;
item.toggleClassName('highlight');
}

function selectAccordionOption(event) {
var selectedOption = Event.element(event);

// update the selection input field value to the selected display value
$('accSelect').value = selectedOption.innerHTML;

// internal value is stored as an attribute, do whatever you need to with it
var realValue = selectedOption.getAttribute('value');

// hide the accordion list
toggleAccordionSelect({target: $('accSelectToggle')});
}


var accordionSelect = new accordion('accSelectOptions', {
classNames : {
toggle : 'accordion_toggle',
toggleActive : 'accordion_toggle_active',
content : 'accordion_content'
},
onEvent : 'mousedown'
});

// By default, open first accordion in the drop down
var accordionToggles = $$('#accSelectOptions .accordion_toggle');
accordionSelect.activate(accordionToggles[0]);

// register event observers
Event.observe('accSelectToggle', 'mousedown', toggleAccordionSelect);
$$('#accSelectOptions .accordion_content li').each(function(accSelectOption) {
Event.observe(accSelectOption, 'mousedown', selectAccordionOption);
Event.observe(accSelectOption, 'mouseover', toggleHighlight);
Event.observe(accSelectOption, 'mouseout', toggleHighlight);
});

Aborting Ajax requests (for prototype.js)

Sometimes your slick Ajax application may have submitted a request that you no longer care about and want to abort. For example, perhaps you've implemented a type-ahead feature and the user has now typed another character prior to the first results returning. Or perhaps you fetch values for other parts of a form based on user selections and the user changed their choice prior to the first response returning.

I was surprised to see that the prototype.js library does not include an abort method for its Ajax.Request object. So, here's my implementation of Ajax.Request.abort():

/**
* Ajax.Request.abort
* extend the prototype.js Ajax.Request object so that it supports an abort method
*/
Ajax.Request.prototype.abort = function() {
// prevent and state change callbacks from being issued
this.transport.onreadystatechange = Prototype.emptyFunction;
// abort the XHR
this.transport.abort();
// update the request counter
Ajax.activeRequestCount--;
};


To use this function, you need to keep a handle on the Ajax request you want to abort. So, somewhere in you code you'll have something like:

var myAjaxRequest = new Ajax.Request(requestUrl, {[request options]});


If you want to abort that request, simply call:


myAjaxRequest.abort();


Update - Feb. 22, 2008:

It was pointed out in the comments that the Ajax.activeRequestCount can occasionally become negative. I have been able to replicate that situation, while at the same time confirming that it does not consistently happen. This leads me to believe that it's most likely a timing issue such as the abort is issued after the response has already started to be received and/or processed so that both the response processing decrements the counter as well as the abort.

My personal work-around for this is to add these lines to the end of my onComplete handler:

if (Ajax.activeRequestCount < 0) {
Ajax.activeRequestCount = 0;
}

I don't want to remove the counter decrement from the abort function or else cleanly aborted requests will leave the activeRequestCount > 0 when there are no real outstanding requests.

If anyone has a better solution, I'd be interested to hear from you.

Thursday, December 13, 2007

Performance-based Web App Functionality

In the development of some CPU intensive web applications, I've come to realize that sometimes I need to throttle down some of the features of the application in order to maintain good response times for a better user experience. So, I've added what I refer to as jsBogoMips to some of my applications.



The above example shows throttling of visual effects, but it can be used to throttle other things as well. For example, in the application I actually developed this for, while it does use script.aculo.us visual effects, what I really needed to be throttled was the generation of an SVG chart from an arbitrary amount data elements received in XML data. So, based on the jsBogoMips value, I control how many data points are charted.

If you're interested in some alterative JavaScript performance testing, you might also be interested in Celtic Kane's JavaScript Speed Test and Performance Tests for Opera 9.5.

Tuesday, October 02, 2007

Bishop Swap Puzzle

As one final tribute to the 7th Guest chess puzzles, I've added the Bishop Swap Puzzle

GIVE IT A TRY!


Summary of Puzzle Experience

Each of these puzzles had unique problems which were fun to work out and gained in complexity (though each whole puzzle only took a couple hours to code, so they weren't terribly complex).


  • The 8 Queens Puzzle wasn't moving any pieces, so I could just put the queen wherever the user clicked. The only complexity was to check for conflicting queens in all directions and remove them. Of course, my initial goal wasn't to make an interactive game, but a solution generator so my wife's move selection strategy was key, but very simple to implement.

  • The Knight Puzzle added the animation for user moves which was more visually interesting. However, since there's only one open square, you don't have to worry about multiple moves for any given piece. The piece can either be moved to the open square or it can't. There were two interesting algorithms necessary for the knight puzzle. The first is pretty simple and is just the computation of valid "L" shaped moves which is done taking the absolute value of the differences of the coordinates of the piece to move and the blank square. If the absolute value of the differences is 1 & 2 (or 2 & 1), then it's valid.
    The second algorithm is a little more interesting. It is how to determine if all the pieces are in the correct location. For this, I added a little weighting of the row and column indicies as follows:
    • let the computedValue = (row * 2) + (column * 3)

    • not done if:
      • any white knights have a computedValue > 10

      • any black knights have a computedValue < 10

      • any piece has a computedValue == 10

    The following matrix demonstrates this:


    y\x01234
    0036912
    12581114
    247101316
    369121518
    4811141720

  • The Bishop Puzzle added the potential of multiple moves for any given bishop selection. So, I had to keep track of what piece was selected, determine which possible moves were available, and when multiple moves are available, highlight them and allow the user to select one.

Thursday, September 27, 2007

Knight Swap Puzzle

I decided to write another 7th Guest inspired game, the Knight Swap game.

This time I added some animations from the Script.aculo.us library to make it a little more visually interesting.

GIVE IT A TRY!

Tuesday, September 25, 2007

Eight Queens Puzzle

Recently, my wife decided to re-visit some of our old games and was playing The 7th Guest.  Several of the puzzles in that games are chess based, and one is the Eight Queens Puzzle.

I decided it would be a fun and fairly simple task to build a JavaScript implementation of that game which would also attempt to solve it.

My first pass merely randomly placed queens on the board (and subsequently removed the conflicting pieces) as a brut force method to stumble upon the solution.  I let that version run all day and it never found a solution -- which according to the Wikipedia entry, there are 92 of.

Then I added the logic she used to solve the puzzle which was to only add a new queen if it either didn't remove any others, or only removed one other queen.  That technique worked surprisingly well usually only requires the program to take from the 10s to the low 100s of attempts before it has a solution and it much less complex that the solution described in the Wikipedia entry.

In only took a couple hours to build, and in the end, I have a very simple program which will allow you to try your hand at solving the puzzle yourself, or can quickly figure out a solution from whatever you have on the board when you give up.

GIVE IT A TRY!


Wednesday, August 22, 2007

Tips for new Samsung Sync Users

As I mentioned earlier, I recently acquired a Samsung A707 which is provided by at&t/Cingular. In this article I'm going to recommend some software and other customizations to make it more useful.

  1. Access high-speed Internet everywhere
    I described this in my earlier article, Enjoying High Speed Wireless Connectivity Anywhere, so I won't do so again, but the important tip from that article is to download at&t Communications Manager to connect your laptop to the Internet via Bluetooth or USB.
  2. Easily manage address book, calendar, email, multimedia, and more
    You may think you need to buy a USB cable in order to connect with your phone and synchronize your address book, appointments, music library, etc., but the good news is that you don't! If you're laptop is equipped with Bluetooth (like mine is), simply download Samsung PC Studio (it's free!). It will run through a simple wizard to connect to your phone either via Bluetooth or USB (it also supports Infrared and Serial connections, but since the phone doesn't, that doesn't help).


    Once you're connected you can manually manage your address book, email, etc. or you can synchronize them with Outlook/Outlook Express. You can use this application to configure an Internet connection as well, but it's easier with the at&t application (see tip 1).
    The "Manage Multimedia Files" option allows you to copy music, photos, and videos to/from the phone -- including Podcasts.
  3. Add useful apps to your phone
    • Google Maps
      Other than making phone calls, one of the most useful thing a cell phone can do for you is help you find businesses while on the road. Google Maps is a "must have" application for this. Not only can if find local businesses by name or keywords, with a click of a button you can call them You can get directions to them including step-by-step instructions to get you there. It also adds real-time traffic information, can display satellite imagery, and provide navigation to move around the map and zoom in/out.
    • GMail
      Yes, the Sync comes with a mobile email program which connects to Yahoo!, AOL, Windows Live Mail (aka Hotmail), Juno, NetZero, and more. However, the one service it lacks it my preference, GMail. Fortunately, Google has provided their own application which can be installed on your phone to access GMail accounts. Unfortunately, I haven't found anyway to make it the default email program so that your phone will tell you when you get new mail like it will for the other email types.
    • Opera Mini
      The built in browser works fine for WAP/WML content, but when you need a more powerful browser, Opera Mini is the way to go. If you have iPhone envy and want a browser like it demonstrates showing the whole page and zooming in on pertinent sections, try the beta of version 4 which can do that too.
    • mWebPlayer
      The Sync includes "XM Radio Mobile" which allows you to stream XM Radio content to your phone. The only problem is, you have to pay for the service. As an alternative, mWebPlayer will stream radio stations off the Internet (for free!). The free version of mWebPlayer doesn't save changes you make to the station list, however, since you can load your Live365.com favorites, it's quick and easy to just load that list each time you start the application.
    • Instant Messaging
      The Sync includes an instant messaging application which can connect to either AIM, MSN, or Yahoo! However, it has two drawbacks. You can only connect to one at a time, and it uses your text messaging count for each message (which when you have unlimited data but limited text messages is rather stupid). Fortunately, there are a large number of IM clients out there which can connect to multiple IM communities at once and don't waste your messaging total. The biggest drawback is that, like the email, when you're using one of these apps you won't get notified when you're not using the phone and you get a message. You have to be running the application at the time. Since there are lots to choose from, I'm not specifying a particular one since I haven't evaluated them all, just follow the link and pick the one that sounds best to you.
    • Games
      There's going to be plenty of times when you're waiting in some lobby and need something to do to pass the time. Here are a few staple games that are both free and well done.
      • Backgammon
        This is a nice implementation of an old favorite. The LITE version is free and fully functional except that you can't play against other people online.
      • Chess
        Another nice (and free) rendition of a timeless classic.
      • Tetris
        A good way to waste time.
  4. Add useful URLs to add to your browser favorites
    • Google
      Who can surf the net without Google?
    • YouTube
      You too can watch everyone's homevideos on your cell phone (who needs an iPhone? -- the Sync can toggle between portrait and landscape too!)
    • TV Guide
      Who wants to flip through a paper guide and translate it to the correct station number? Get a customized list of what's on right now for your TV provider.
    • Bible
      Don't want to carry the Bible with you to church, no problem, get it online.
    • Weather (NOAA)
      I happen to live in a hurricane zone, so when a hurricane it coming, I want to be able to get up-to-date weather reports.
    • Java Applications
      Many of those aforementioned Java application, and many more can be downloaded from GetJar.com
    • Palm Mobile Portal
      True, this isn't a Palm, but Palm does have a nice portal site for other mobile sites.
  5. Free 411 Information
    Add a contact to your address book for 1-800-GOOG-411 (1-800-466-4411) to get local business information.
  6. Customize appearance
    Using pictures you take or upload with PC Studio (tip 2), be sure to set the "Caller ID" field for your address book contacts to pictures of them which will show up when they call. Until you get a picture of your friend, the Dilbert characters work nicely. Also, customize your ring tone and wallpaper. To the right is a picture I took at Clearwater Beach (Florida) which has been resized to fit the screen (240x320) in case you don't have a picture of your own.

Friday, August 17, 2007

Automatically Minimize (Minify) JavaScript

One of the 13 Simple Rules for Speeding Up Your Website is to Minify your JavaScript.

Sidenote: These rules can be easily checked using the YSlow add-on to Firebug.

The recommended tool to minify your JavaScript is JSMin. However, the default use of JSMin is to minimize your JavaScript in a separate step before you deploy your code and then have two JavaScript files - the original JavaScript you use to develop the code and the production "minified" version. For me the problem with that is the fact you have two versions to keep track of and keep in sync. Plus before you deploy your code, you need to make sure all your HTML pages use the minified filename not the original version.

Fortunately, there's a port of JSMin to PHP which allows you minify your JavaScript on the fly, but the usage instructions make it appear that you need to change your script source to point to separate PHP file for each JavaScript, or have a single PHP with all your JavaScript files hard-coded in it -- which is a nice option to get multiple JavaScript files with a single request.

What I'm going to show in this post is how to use jsmin-php to create a filter on your server so that all your JavaScript files are transparently minified as your retrieve them.

First off, download the current version of jsmin-php and save it in your directory. As the current version at the time of this writing is jsmin-1.1.0.php, I'll assume that is the file name.

Next, save the script below as jsmin.php:

<?php
// PHP filter to invoke JSMin on all JavaScript files
// see http://code.google.com/p/jsmin-php/
require_once('jsmin-1.1.0.php');

$js_file = pathinfo($_SERVER['ORIG_PATH_INFO']);
$js_path = substr($js_file['dirname'], 11);
$js_file = $js_file['basename'];

$charset = "utf-8";
$mime = "text/javascript";

header("Content-Type: $mime;charset=$charset");

// Output a minified version of JavaScript file
if (file_exists($js_file)) {
echo JSMin::minify(file_get_contents($js_file));
} else {
echo JSMin::minify(file_get_contents($js_path . '/' . $js_file));
}

?>


Note: If you change the name of this script, you'll probably need to change the 11 on the $js_path definition. That rips the script name (/jsmin.php/) off the beginning of the incoming path on the request so I can use a relative path to find scripts in a subdirectory. It should be the length of the script name plus 2 (for the leading and trailing '/'s).

Now, update your .htaccess file and add the lines:

# make all JavaScript files be minimized via JSMin
AddHandler jsmin .js
Action jsmin /jsmin.php


That should be it! If you fetch a JavaScript file from your server, it should now be minified.


Here are a few quick additions you could add to make YSlow happy.

At the beginning of jsmin.php, adding the line:

ob_start("ob_gzhandler");

should GZIP the output for you.



Also, where the Content-Type header is set, adding the following lines will add an expiration of 49 hours (needs to be > 48 hours to by sufficiently "far future" to make YSlow happy. If you really want to cut it close you could use (60 * 60 * 48) + 1):

$offset = 60 * 60 * 49;
$ExpStr = "Expires: " . gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
header($ExpStr);

Wednesday, August 15, 2007

Enjoying High-Speed Wireless Connectivity Anywhere

In the interest of providing other technology posts other than just JavaScript code snippets, I thought I'd summarize my experience using at&t's 3G+ network. More than basic 3G, I actually get to utilize their HSDPA network here in Tampa where I live.

What's the difference? Well, according to the Wikipedia sources I linked to above, the standard 3G allows the transmission of 384 kbit/s for mobile systems whereas current HSDPA deployments support down-link speeds of 1.8, 3.6, 7.2 and 14.4 Mbit/s.

I recently acquired a Samsung A707 (aka Samsung Sync) which is a considerable improvement over my prior Motorola V180 -- though the V180 served me well as a basic phone, and even survived a fall into a river. I don't intend to review the A707 as there are plenty of other reviews out there (1, 2, 3, 4, 5), nor do I care to discuss the merits of one carrier's wireless technology over another's (ex HSDPA vs. EV-DO). All I want to talk about is my experience utilizing my phone to access the internet, particularly on my laptop and PocketPC through my phone, and what kind of performance I've seen.

Once I got the phone, I signed up for a MEdia Max bundle which gave me unlimited data on the phone. I plan on doing LOTS of data with the phone, so I can't have some sort of puny 1MB or 5MB limit.

To see what kind of bandwidth I could get on the cell phone, I pointed my Opera Mini browser to The Speed Test* and got results such as:


In the interest of full disclosure, that was one of the higher scores, however several other tests were in the same vicinity (ex. 2801.8kbps) so it could not be dismissed as an anomaly. The lower end of the scores were around 1913.5kbps. These are all respectable scores and inline with the statement that the initial HSDPA networks deployments support 3.6 Mbit/s peak with phase one deployments to eventually reach achieve peak data rates of 14.4 Mbit/s. Who says internet access on cell phones is slow!?

Now, it's all well and good that the phone can attain that high bandwidth and it certainly makes streaming videos off of YouTube or at&t's Cellular Video service to your cell phone work smoothly, but at some point, you just want a full size screen and keyboard for your internet access.

The A707 doesn't come with a USB cable, so I utilized its built-in Bluetooth. To me, the Bluetooth is actually nicer since I can leave myphone in my pocket or in my belt click and still connect your laptop to it. Plus, I can connect my laptop and PocketPC to it simultaneously which is nice too. However, it does drain the battery faster.

To use the phone for internet access via Bluetooth, you need to pair it to your laptop (or other Bluetooth enabled device). So, enable Bluetooth in the phone settings. I won't provide my own instructions for this as at&t has a nice tutorial online. Then you need to pair your phone and laptop together. Again, at&t provides vendor specific instructions for this. at&t has made a simple client called at&t Communication Manager which allows you to easily connect with the phone. Setup instructions are provided on the download page. The only thing I had to do special was manually select the GSM device (see Tools -> Settings... -> GSM -> Device Selection and select the Bluetooth Modem that was setup for me when I did the Bluetooth pairing). Once the Communication Manager finds your device, you simply click on the "Connect" button and you're in business.

My laptop is a few years old, so I believe it's built in Bluetooth is version 1, not version 2 which significantly limits it's bandwidth potential (see Wikipedia entry on Bluetooth). By accessing the internet via my cell phone via Bluetooth, I got speeds varying from around 274kbps to 347kbps.



These results are significantly reduced from the cell phone alone, though still tolerable for common internet tasks (reading email, viewing typical web pages, etc).

The results for my laptop are comparable to the results I saw on my Dell Axim x51v as well. I had to do a little more manual configuration to get the Axim connected to the internet through the phone, but once I got it working, I got results such as:



Just for comparison, the bandwidth I got at a local WiFi hotspot was around 1Mbps:


So the conclusion is that if WiFi is available, it's still the faster option (at least for me without a USB connection nor Bluetooth 2.0 speeds), but having the ability to have broadband speed internet access ANYWHERE I go (in the city) is certainly a convenience! And possibly it's more secure if you're in a public hotpost with packet sniffers lurking around.

I'd be interested to here if anyone has similar experience but using Bluetooth 2.0 instead. Is it faster? Is a USB connection faster?

*Note: I also verified my laptop speed results with Speedtest.net which has a really nice interface. However, since it's Flash based, it doesn't run in the cell phone browser.

Update - December 19, 2007:


I have replaced my laptop and am now running Red Hat Enterprise Linux on my new one. Therefore, I could no longer use the at&t Communications Manager to make a connection. Fortunately, I found these instructions for How to use a Bluetooth phone as [a] modem in Linux which were very helpful.

Tuesday, August 14, 2007

Synthesizing Events in JavaScript

According to JavaScript: The Definitive Guide (4th ed), section 19.2.9 on Synthesizing Events, it says,

Unfortunately, at the time of this writing, the synthetic event API is not supported by Netscape 6, nor by the current version of Mozilla



But, it does work in the current version of Firefox (and IE). I had a situation where I needed to simulate events, so I wrote the following function to make it simple:


// simulateEvent
//
// simulate a user action
//
function simulateEvent(eventType, targetElement) {
var event;
targetElement = $(targetElement);

if (targetElement) {
// check for IE
if (window.ActiveXObject) {
event = document.createEventObject();
targetElement.fireEvent("on"+eventType,event);
} else {
switch (eventType) {
case "abort":
case "blur":
case "change":
case "error":
case "focus":
case "load":
case "reset":
case "resize":
case "scroll":
case "select":
case "submit":
case "unload":
event = document.createEvent("HTMLEvents");
event.initEvent(eventType, "true", "true");
break;
case "click":
case "mousedown":
case "mousemove":
case "mouseout":
case "mouseover":
case "mouseup":
event = document.createEvent("MouseEvents");
event.initMouseEvent(eventType, true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
break;
}
targetElement.dispatchEvent(event);
}
}
};

Thursday, March 08, 2007

Top Web Technologies

Jim Rapoza of eWeek compiled his list of the Top Web Technologies of All Time as a collection of slides.

Here is his list:

  • XML
  • HTML
  • Netscape Navigator
  • HTTP
  • Apache
  • NCSA Mosaic
  • CERN httpd
  • Internet Explorer 3
  • NCSA httpd
  • Firefox
  • SSL
  • ViolaWWW
  • WAIS
  • CGI
  • Internet Information Server
  • Squid
  • Java
  • HotMetal
  • Flash
  • PHP
  • Dreamweaver
  • RSS
  • WebTrends
  • Blogger
  • PlaceWare
  • Lynx
  • Perl
While I would definitely concur on many items on the list, there are others that I would disagree with or substitute.

Regarding the standardized protocols and languages: XML, HTML, HTTP, RSS, SSL, Java, PHP, Perl, RSS, and even Flash, I have no objections. They are certainly the foundations of the web. However, I do think he's missing a biggie of JavaScript which, while it had a dubious start and I avoided it for quite a while, is shaping out to be the scripting language of Web 2.0.

Regarding his list of browsers: NCSA Mosaic, Netscape Navigator, and Internet Explorer, Firefox? Absolutely. ViolaWWW? I don't think so. Yes, it came out before NCSA Mosaic, but Mosaic was really more responsible for bringing the WWW to the masses, and if you really want to show the start of the web browsers, why not point to the actual original browser named WorldWideWeb by Tim Berners-Lee? Spyglass? Come on, it's just a commercial re-branding of NCSA Moasic. Yes, it became Internet Explorer, but you could just as well say NCSA Moasic became Internet Explorer and not bother to mention this interim step. Similarly, Netscape was first called Mosaic Netscape, so do we need to list that too? Is wasn't really significant in itself. Lynx? Sure, it comes in handy from time to time, but I don't think it's a top web technology. Opera? It's a nice browser and all, but I don't think it's ever made that much of an impact overall. Yes, it introduced some new ideas like mouse gestures, but I just don't think it was that significant. Why not Safari for the Mac, or Galleon or Konqueor on Linux? Same thing. They're nice, but more of an also ran than a top web technology.

Then we have our web servers: CERN httpd and NCSA httpd which begot Apache, no question. IIS? I guess in some circles, but Apache is still significantly more important. If it's on his list because it provides the .Net platform, what about Tomcat, JBoss, or other commercial products like WebSphere and BEA for J2EE? I don't think I'd put IIS on the list, but if I did, I think I'd be remiss not to include Tomcat or some of the other Java application servers.

Next are HTML editors HotMetal and DreamWeaver. Personally, I think the Netscape/Mozilla Composer and/or FrontPage had a wider impact toward bringing HTML editing and composition to the masses originally. Jim makes the comment that, nearly all serious Web developers now use Dreamweaver, but I know plenty of serious web developers who don't use Dreamweaver so I think that was a personal bias on his part.

Finally he have various web services like WAIS and Blogger. WAIS had it's place as a search technology (though more familiarly recognized as Gopher), but I think Yahoo and subsequently Google had a more significant impact in organizing the Web to make it useful to the masses. It's hard to classify it as a Web technology when Gopher/WAIS was essentially replaced by the web. Blogger is certainly important and can stay on the list, but if it is, I think some other services should also be on the list. For example, Yahoo's provision for free email (as well as Hotmail, GMail, and others) was more important toward getting people to use the web for online services. Also, services like MySpace are more significant toward advancing the collaboration ideas of Web 2.0. And is PlaceWare really more significant than all the other similar solutions? Why not CUSeeMe or NetMeeting?

Also, what about other key technologies that make the web work like databases (MySQL anyone?).

Just my thoughts. Any other suggestions?

Tuesday, March 06, 2007

The Truth about Global Warming?

A local talk show personality, Todd Schnitt, has compiled a list of articles by respected scientists who dispute global warming.

It is virtually shoved down our throats that scientists are in complete agreement about global warming. Al Gore and the media assure us that there is an absolute consensus in the scientific community that humans are heating the planet and irreversible damage is looming as a result of man's carbon output. How about some intellectual honesty? Reasonable debate?
-- SchnittShow.com



It's nice to hear the other side of the story.

Thursday, March 01, 2007

Simple draggable HTML using prototype.js

I know there are plenty of JavaScript packages out there to allow you to create draggable elements on your HTML page, but to me, most of them appear to be overly complicated for a simple function and are often part of a larger framework that you may not need.

The solution I'm providing today does depends on the prototype.js package (I'm using version 1.5 currently) which allows the code size to stay smaller.

Here's how to make any HTML element draggable in 5 easy steps.

  1. Add the class of draggable to whatever you want to move. Actually, you can use any class identifier you want, but draggable is what I'm looking for in my code.

  2. Give your draggable element a position style (position: absolute; or position: relative;) and an initial top and left value. Generally, you'd want to put this in your styles in an external CSS with the draggable class, but the top and left values need to be set as an attribute in the HTML. Hint: setting the position to relative with the top and left values set to 0px will allow it to start located where it normally would be in the browser. You'll probably want to give it a higher z-index and solid background-color as well, but that's up to you.

  3. Include the 3 drag event handling functions I'll describe below in your code somewhere

  4. Register all your draggable elements to the event listeners.

  5. Have fun dragging window elements around.


The event handlers


Before you can use the following functions, you need to define some variables to hold some information during the drag. If you make the subsequent functions part of another containing object, you can simply prepend all occurrences of these variables in the functions with this. and they will be stored in the containing object instead.

var dragObj;
var cursorStartX;
var cursorStartY;
var elementStartX;
var elementStartY;
var dragGofn;
var dragStopfn;

startDrag

startDrag will initialize a mouse drag event. It captures the initial location of the mouse when beginning the drag in order to calculate movement later. It also captures the current coordinates of the dragged element for correct positioning as the location of the mouse are most likely not the origin of the element being dragged. Finally, it associates the additional event observers to the element to capture the mouse movement (onmousemove) and release of the mouse button (onmouseup).

function startDrag(event) {
dragObj = Event.element(event);
// if the target of the event is not a "draggable" element, then search
// through it's parents for an element that is draggable.
// if we can't find a draggable element before reaching the root document
// element, then bail out
while (!Element.hasClassName(dragObj,"draggable")) {
dragObj = $(dragObj).up('.draggable');
}
cursorStartX = Event.pointerX(event);
cursorStartY = Event.pointerY(event);
elementStartX = parseInt(dragObj.style.left, 10);
elementStartY = parseInt(dragObj.style.top, 10);

Event.observe(dragObj, 'mousemove', dragGofn);
Event.observe(dragObj, 'mouseup', dragStopfn);
}

dragGo

Once the drag event has been initiated by the startDrag function, the dragGo function will update the location of the element being dragged so that it moves around the screen with the mouse.

function dragGo(event) {

var x, y;
x = Event.pointerX(event);
y = Event.pointerY(event);

// move the target object
dragObj.style.left = (x - cursorStartX + elementStartX) + "px";
dragObj.style.top = (y - cursorStartY + elementStartY) + "px";

Event.stop(event);
}

dragStop

Once the user has release the mouse button, we need to stop observing the mouse movements, so the dragStop function will un-register the event handlers.

function dragStop(event) {
// Stop capturing mousemove and mouseup events.
Event.stopObserving(dragObj, 'mousemove', dragGofn);
Event.stopObserving(dragObj, 'mouseup', dragStopfn);
dragObj = null;

Event.stop(event);
}

Registering the draggable elements


You're almost ready to start moving items on the screen, but first you have to register all your draggable elements to the startDrag function so that they will be notified of the drag events. So, we utilize prototype's element-by-classname selector to find all the draggable elements and associate the event listener to them.

Note: in order to un-register the event listeners, you have to have the handle to the event listener you used to register it originally. Thus, we used the variables dragGofn and dragStopfn to register and un-register the event listeners. These variables are defined here during the original event registration.

function registerEvents() {
dragGofn = dragGo.bindAsEventListener();
dragStopfn = dragStop.bindAsEventListener();

$$(".draggable").each(function(draggableElem) {
Event.observe(draggableElem, 'mousedown', startDrag.bindAsEventListener(), true);
});

}


Note: the final true on the Event.observe tells prototype to request capturing instead of bubbling. This is necessary for this function to work in Safari (see Kir's blog)

Monday, February 26, 2007

Adding a Processing indicator with Prototype.js

Here's a simple method to add a Processing/Loading/Working indicator to indicate when an Ajax request is being processed with prototype.js.

First, define a hidden div (or other preferred HTML element) which will be displayed to indicate that some background processing is going on. Mine happens to look like:

<div id="loading" style="display:none">Loading...
<div style="background-color: white; position: absolute; top: 0px; right: 0px; width: 5px; height: 5px; cursor:pointer;"
onclick="$('loading').hide()"></div>
</div>

In my code, I added another div on the upper right corner which I can click to close this indicator in case something goes wrong with the Ajax event handling which I'll provide in a moment.

I then added a little CSS magic to make it look the way I wanted. In my case, I want the indicator to be on top of everything, centered on the page, with a processing image () on the right side of the text. The background color is white with a double gray border and large, bold font.

#loading {
z-index: 100;
position: absolute;
top: 40%;
left: 40%;
background-image: url("../images/progress-running.gif");
background-repeat: no-repeat;
background-position: 5px;
background-color: white;
padding-left: 25px;
padding-top: 8px;
border-style: double;
border-color: #c0c0c0;
width: 120px;
height: 30px;
font-size: 1.5em;
font-weight: bolder;
}


Ok, so now I have the indicator I want to popup. This can be tailored to your preferences. Now, you simply tell prototype to display it whenever it is processing an Ajax request. This is done by registering some event listeners to the prototype Ajax class.

// register event listeners on the Ajax requests to show/hide the processing indicator
Ajax.Responders.register({
onCreate: function() {
if (Ajax.activeRequestCount === 1) {
$('loading').show();
}
},
onComplete: function() {
if (Ajax.activeRequestCount === 0) {
$('loading').hide();
}
}
});


That's it! Now you should get an indicator whenever you issue an Ajax request that goes away whenever the processing is done.

Friday, February 23, 2007

Cookies and Flash for local storage

In previous posts I provided code for using cookies in JavaScript. I've now enhanced that code to utilize the flash storage mechanism which was described by Christoph Khouri, however his page seems to be down currently, so I'll provide the files you need. Note: this is the same technique used by the Dojo Storage module, without the need for Dojo which adds extra overhead costs.

Using flash storage alleviates the 4k size limit of cookies and initially provides 100k of storage seamlessly and can be increased to any size by the user. It also eliminates unnecessary network traffic as all the cookies are transmitted to the server all the time, and makes it harder for prying eyes to see what's being stored as vs browsing the cookies in the browser.

In order to use the flash storage, first, download flashStorage.zip which came from Christoph Khouri's site. Next, download my current cookies.js file.

Now, include the flashStorage JavaScript and my cookies.js JavaScript in the header of your HTML page:

<script type="text/javascript" src="jscript/flashStorage/swfobject.js">lt;script>
<script type="text/javascript" src="jscript/flashStorage/flashStorage.js"><script>
<script type="text/javascript" src="jscript/cookies.js"></script>


Next, somewhere in the body your page add the following code:

<div style="float: right;" id="storageHolder">
<embed type="application/x-shockwave-flash"
src="jscript/flashStorage/storage.swf" id="storageMovie" name="storageMovie" bgcolor="#ffffff" quality="high"
swliveconnect="true" wmode="transparent" height="137" width="214" />
</div>
<script type="text/javascript">
var so = new SWFObject("jscript/flashStorage/storage.swf", "storageMovie", "50", "10", "8", "#ffffff");
so.addParam("swLiveConnect", "true");
so.addParam("wmode", "transparent");
so.write("storageHolder");

//Initialize the flash storage by passing the id of the flash movie
Storage.init("storageMovie");
</script>


That's it! Now whenever you create a cookie like Cookies.create("myCookie", "myValue") it will actually store that in the flash storage instead. However, if you don't wish to include the Flash storage component, it will default to store the value in a browser cookie instead.

Maybe sometime in the future I'll add detection to see if the Flash plugin is installed in addition to the Storage object and default to standard cookies if Flash isn't installed.
Update: I've added a check for the Flash plugin being installed as a pre-requisite to using the Flash storage object in addition to the check for the JavaScript code/object being included. FYI, my check for Flash for those needing to do the same thing is:

var hasFlash = (navigator.plugins &&
(navigator.plugins["Shockwave Flash"] || navigator.plugins["Shockwave Flash 2.0"])) ||
(navigator.mimeTypes &&
navigator.mimeTypes['application/x-shockwave-flash']);

Ajax Cross-Domain Issue

A common solution to the XMLHttpRequest cross-domain issue (aka cross site scripting or XSS), where you can only communicate with the originating domain, is to configure Apache to use either mod_proxy or mod_rewrite for URL rewriting and a pass-through proxy.

With Apache you need to enable the proxy functions (configure --enable-proxy). If you want to handle it all with mod_proxy, then define you proxy settings in httpd.conf such as:

ProxyPass /doExt http://external.com/doAction
ProxyPassReverse /doExt http://external.com/doAction

If you want to use mod_rewrite then turn on mod_rewrite in httpd.conf or .htaccess with RewriteEngine on and define your rewriting rules such as:

RewriteRule ^/doExt$ http://external.com/doAction [P]

Then when you post to your server via /doExt, Apache will pass the request on to the external site.

That's all well and good if you have access to the Apache configuration or the server is already configured with the proper modules. But, when your on a hosted environment, that's not always feasible. Another solution would be to provide a proxy servlet to make the remote request for you and return the result. What I am providing is in Java and PHP, but it could be written you language of choice.

Java proxy


The following servlet class will invoke some external URL on behalf of the original request and return the result from the external request as the result of the original local request. Note: In the following code I hard-coded the real destination. However, it could be determined from a parameter in the original request just as easily.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Enumeration;

/**
* This is a simply proxy class in order to bypass AJAX security contraints.
*
*/

public class ServerProxy extends javax.servlet.http.HttpServlet {

private static final long serialVersionUID = 1L;

public void doGet(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException {
performTask(request, response);
}

public void doPost(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException {
performTask(request, response);
}

public void performTask(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException {

// Get the external URL to invoke
String realUrl = "http://www.external.com";

// Copy the request parameters from the original request
Enumeration paramNames = request.getParameterNames();
String paramName;
while (paramNames.hasMoreElements()) {
paramName = (String) paramNames.nextElement();
realUrl = realUrl.concat(paramName + "="
+ request.getParameter(paramName));
if (paramNames.hasMoreElements()) {
realUrl = realUrl.concat("&");
}
}

// real URL will be returning XML data
response.setContentType("text/xml");
PrintWriter out = response.getWriter();

// invoke the real URL and copy the result into the response
// for the original request
URL real = new URL(realUrl);
BufferedReader in = new BufferedReader(
new InputStreamReader(real.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
out.println(inputLine);
in.close();

return;
}

}


PHP proxy


Here is a similar solution using PHP:

$remoteServer = "http://www.external.com";
$path = $_GET[”path”];
$proxyTarget = $remoteServer.$path;
$connection = curl_init($proxyTarget);
curl_setopt($connection, CURLOPT_HEADER, false);
curl_setopt($connection, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($connection);
curl_close($connection);
header(”Content-Type: text/xml”);
echo $data;

Google Apps Premier Edition, a good thing?

I see on Google's Blog that they are announcing Google Apps Premier Edition which is a collection of the existing Google Apps (Gmail, Google Talk, Google Calendar, Page Creator, and Google Docs & Spreedsheets) which have additional collaboration features, APIs and support for which they will charge $50/yr per user.

I certainly think Google's apps are very useful, of high quality, and worthy of compensation and they certainly offer good service with a 99.9 percent uptime service-level agreement in which customers will receive credits for downtime. However, the reason I and so many others use Google apps is because they are free so I hope this is not a sign of things to come. In the blog entry for this announcement they say,

Google Apps also won't forget its roots anytime soon. The Standard and Education Editions will continue to be offered for free...



It's the "anytime soon" clause that causes me apprehension. Does that infer that there is a future plan to charge for their services? I've been migrating to Google more and more, I don't want to go through the trouble of migrating somewhere else.

Monday, January 15, 2007

Erasing all your cookies

In a previous post I provided some [non-original] code to do basic cookie manipulation in JavaScript. Well, if you're now using cookies to save a whole bunch of user settings and preferences for your web application, you may want to provide an option to let users clear all their saved settings (or perhaps just provide a hidden hot-spot so you can clear your settings for testing purposes which is why I needed this function).

You could keep track of all your cookie names and call the eraseCookie function repeatedly yourself for each cookie. Or, you could name all your cookies with a common prefix for your whole application and use the following function to clear them all out for you.


function eraseCookiesStartingWith(prefix) {
var allCookies = document.cookie.split(';');
for(var i=0;i < allCookies.length;i++) {
var aCookie = allCookies[i];
// remove any whitespace from start of cookie name
while (aCookie.charAt(0)==' ') aCookie = aCookie.substring(1,aCookie.length);
var aCookieName = (aCookie.split('='))[0];
if (aCookie.indexOf(prefix) == 0) {
createCookie(aCookieName,"",-1);
}
}
}