Friday, December 28, 2007

My favorite web tools of 2007

As the year comes to a close, I thought it would be fitting to reflect on what development tools helped make 2007 easier for me. These tools weren't necessarily created in 2007, but as someone who has a tendency to want to re-invent the wheel, I previously found myself overlooking great third-party products and ideas, so consider this a thank you to those people who contribute great, free code to the community.

1. Scriptaculous (http://script.aculo.us)
Provides: Easy drag and drop, animation, and AJAX functionality

Building on the Prototype Javascript framework, Thomas Fuchs, with the help of a community of open source contributors, has given us an easy-to-use but extremely powerful toolkit for some of the more tedious Javascript tasks we face - namely UI features (e.g. drag and drop) and AJAX functionality. Those of us who have been around long enough to have had to work with positioning and controlling user interface elements know the headaches involved in getting your event handlers to fire as you expect them or tracking the x and y coordinates of the mouse. In fact, I suspect the #1 most common reason for keyboard smashing and monitor punching among developers goes something like "graphic element expanded beyond its container instead of properly wrapping to the next line".
With Scriptaculous, implementing drag and drop, including sortable lists, is about as easy as it can feasibly be, and the AJAX-related classes are a godsend. Even someone with only moderate experience in javascript should be able to implement advanced UI features and AJAX functionality without much trouble. Scriptaculous is well integrated with Ruby on Rails, and I expect that it will soon be driving a lot of the AJAX functionality in my own PHP MVC framework, FUSE

2. The Free Rich Text Editor (http://www.freerichtexteditor.com/)
Provides: a full WYSIWYG editor, a la blogger's "compose" feature

Definitely file this under "things I would NOT like to have to write from scratch"! Released under the Creative Commons License (thankfully), this rich text editor control allows you to easily implement full WYSIWYG functionality into your web application. With a wide range of editing functions, from bulleted lists and visual color selection to drag and drop image placement, it just doesn't get any better than this. A simple .js configuration file provides you with customization options, and once you add in a few tags, you're all set. Custom WYSIWYG in under ten minutes. This one has been integrated into FUSE since the day I found it!




3. Model / View / Controller architecture
Provides: Sanity

Ok, this isn't exactly a "tool", and the idea has certainly been around for a long time (since 1979 according to Wikipedia) , but MVC has finally started to gain real acceptance in the web development community to the point where it's becoming the standard for large scale, web-based applications. Due in some part to the popularization of Ruby on Rails, along with other MVC frameworks like Cake, Symfony(, or FUSE), web developers have finally started realizing that procedural scripting, especially the sort that mixes application code and presentation (e.g. PHP and HTML in the same file), isn't the best method for creating scalable, manageable applications. At this point, most MVC developers will look at you with a true sense of confusion and disappointment if you suggest ANY other way of developing a web app, but it seems that the majority of web programmers are still stuck in the "Chapter 1 of 'Building your first PHP applicatoin'" mentality, mixing procedural code in with HTML and packaging it all up in ugly URLs with long query strings. Get with the program, guys!


So there you have it - some things that helped me keep what's left of my sanity for the past year. I may add to the list if I think of something that really needs to be mentioned, but the three items listed were part of my daily development life in '07, and I suspect that all of them will keep me company for '08 as well.

Thursday, February 01, 2007

Managing include files in Javascript

As I continue to write Javascript libraries for use on various projects, I decided it was time to write a simple include() function so that I don't have to manually add superfluous <script></script> tags that I may or may not need on any given page.

Using the XMLHttpRequestObject, I was able to create an asynchronous request to fetch the source of my .js file. Then I create a new script object and set the .text or .innerHTML attribute of that object (depending on browser - IE requires .text) to the fetched source. So far I've tested it in IE 6 and FF 1.5.

Usage for including "/script/myscript.js":
Note: in reality, the include() call would be done from inside another .js file.

<head>
<script language="Javascript" src="ScriptManager.js" type="text/javascript"></script>
<script language="Javascript" type="text/javascript">
ScriptManager.script_base_link = '/script'; //set up our base link
//ScriptManager.script_file_extension = '.js'; //defaults to .js
ScriptManager.ErrorHandler.silent = false; //set to true or remove in production

try {
ScriptManager.include( 'myscript' );
}
catch (e) {
ScriptManager.ErrorHandler.handle_error( e );
}

</head>

And that's all.
The code for ScriptManager.js is below.
Copy & paste this into ScriptManager.js:


function ErrorHandler() {


}

ErrorHandler.handle_error = function ( e ) {

try {
ErrorHandler.show_error( e.message );
}
catch(e) {
}

}

ErrorHandler.show_error = function( message ) {

try {
if ( !ErrorHandler.silent ) {
alert( message );
}
}
catch(e) {
}

}

function HTTPRequest() {

//
// Properties
//
this.request_object = null;
this.async = true;
this.onreadystatechange = null;

//
// Methods
//
this.get_request_object = get_request_object;
this.send = send;
this.is_busy = is_busy;
this.abort = abort;
this.get_readyState = get_readyState;
this.get_status = get_status;
this.get_responseText = get_responseText;

function get_request_object() {

try {

if ( !this.request_object ) {

var http_request;

if ( typeof(XMLHttpRequest) != 'undefined' ) {
http_request = new XMLHttpRequest();
}
else {

try {
http_request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch(inner_e) {

try {
http_request = new ActiveXObject('Microsoft.XMLHTTP');
}
catch(inner_e2) {
}
}
}

this.request_object = http_request;
}

return this.request_object;
}
catch(e) {
throw e;
}
}

function get_readyState() {

try {
if ( request_obj = this.get_request_object() ) {
return request_obj.readyState;
}

return null;
}
catch( e ) {
throw e;
}

}

function get_status() {

try {
if ( request_obj = this.get_request_object() ) {
return request_obj.status;
}

return null;
}
catch( e ) {
throw e;
}

}

function get_responseText() {

try {
if ( request_obj = this.get_request_object() ) {
return request_obj.responseText;
}

return null;
}
catch( e ) {
throw e;
}

}

function send( which_url ) {

try {
var http_request_obj;

if ( !(http_request_obj = this.get_request_object()) ) {
throw 'Your web browser does not support this function.';
}

if ( this.is_busy() ) {
this.abort();
}

if ( this.onreadystatechange ) {
http_request_obj.onreadystatechange = this.onreadystatechange;
}
http_request_obj.open("GET", which_url, this.async);
http_request_obj.send(null);

}
catch(e) {
throw e;
}


}

function is_busy() {

try {
if ( request_obj = this.get_request_object() ) {

switch ( request_obj.readyState ) {
case 1,2,3:
return true;
break;
default:
return 0;
break;
}
}

return 0;
}
catch(e) {
throw e;
}


}

function abort() {

try {
if ( this.request_object ) {
this.request_object.onreadystatechange = null;
this.request_object.abort();
}
}
catch(e) {
throw e;
}
}

}

HTTPRequest.READYSTATE_COMPLETED = 4;
HTTPRequest.STATUS_OK = 200;

////
//// ScriptManager
////
function ScriptManager() {

}

ScriptManager.include = function( file_name ) {

try {
var file_path;
var head;
var body;
var request = new HTTPRequest();
var script_obj;

script_obj = document.createElement('script');
script_obj.setAttribute('type', 'text/javascript');

if ( !ScriptManager.has_script_file_extension(file_name) ) {
file_name = file_name + ScriptManager.script_file_extension;
}

file_path = ScriptManager.script_base_link + '/' + file_name;

request.async = false;
request.send( file_path );

if ( typeof(script_obj.text) != 'undefined' ) {
script_obj.text = request.get_responseText();
}
else if ( typeof(script_obj.innerHTML) != 'undefined' ) {
script_obj.innerHTML = request.get_responseText();
}

if ( head = document.getElementsByTagName('head')[0] ) {
head.appendChild(script_obj);
}
else if ( body = document.getElementsByTagName('body')[0] ) {
body.appendChild(script_obj);
}
else {
throw 'ScriptManager::include requires a or tag';
}

}
catch ( e ) {
ScriptManager.ErrorHandler.handle_error(e);
}
}

ScriptManager.has_script_file_extension = function( file_name ) {

try {
var ext = ScriptManager.script_file_extension;

if ( ext ) {
if ( file_name.substring(file_name.length - ext.length, file_name.length) == ext ) {
return true;
}
}

return 0;
}
catch ( e ) {
throw e;
}

}

ScriptManager.script_base_link = '';
ScriptManager.script_file_extension = '.js';

ScriptManager.ErrorHandler = ErrorHandler;
ScriptManager.ErrorHandler.silent = true;




How to include files in javascript
javascript including files
javascript require file
javascript include()
javascript xmlhttprequest include
script object