Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Tuesday, March 11, 2008

Object Oriented Programming with Prototype.js

Object oriented programming has been around for quite some time and was made popular by C++ back in the day. Nowadays, even web scripting languages support the paradigm. JavaScript does not have true OO but allows you to use the design pattern in your code. Today, we will dive into using object oriented programming with the popular JavaScript framework Prototype.

Prototype makes it easy to declare your own objects by using the "Class.create()" method:

var sampleObject = Class.create();


Once you do that you can start writing your class specific methods and properties inside the "prototype" object:

sampleObject.prototype = {
}


To declare private properties you simply specify the name of the property followed by a colon and the value:

sampleObject.prototype = {
	linkIDs: ['mainPageLink', 'cdmScreenLink', 'adminLink', 'helpLink'],
	statusMessage: '',
	currentLinkID: 'myLink'
}


The important thing to remember is that each property needs to be separated from the next by a comma. As shown above, property values can be a array, a number, a string but cannot be empty value. While:

currentLinkID: 'myLink',


is valid while

currentLinkID:,


is not.

Declaring your own functions is not any different. Before we look into functions however, there is one function that requires special attention: the "initialize" function:

initialize: function() {
}


The initialize function is what JavaScript calls automatically when you create an instance of your object. If you are familiar with OO, this function is the constructor of your object. Any setup and initial requirements for using your object should be done in here.

So to get back to regular functions, they are declared in the format functionName: function() {} as in:

doSomething: function() {
}

So far your object should like like this:

var sampleObject = Class.create();
sampleObject.prototype = {
	linkIDs: ['mainPageLink', 'cdmScreenLink', 'adminLink', 'helpLink'],
	statusMessage: '',
	currentLinkID: 'myLink',
	initialize: function() {
	},
	doSomething: function() {
	}
}


Big deal right, including that in your html page and/or a separate JavaScript file does not do anything for you. The next step in making it of any use is to actually create an instance of the object like so:

var sampleObjectInstance = new sampleObject();


Creating an instance of the object automatically calls all your code inside the "initialize" function. Here, a good practice is to wrap the creation of your object inside the windows load or dom:loaded (Prototype v1.6) event like:

Event.observe(window, 'load', function() {
	var sampleObjectInstance = new sampleObject();
});


Or

document.observe("dom:loaded", function() {
	var sampleObjectInstance = new sampleObject();
});


To expand on using properties inside your object, whenever you want to access a property such as "currentLinkID", you have to prefix it with "this" as in:

this.statusMessage = 'Who Am I?';


That is because inside your object's function, without "this", the code does not know about the property. The same applies to using function so you cannot simply called the "doSomething" function with:

doSomething();


but instead if you have to use:

this.doSomething();


This can get a little more complicated when it comes to using event listeners inside your code. Let me elaborate. To tie an event observer that will call the "doSomething" function when a users clicks the link with ID 'myLink', you would usually do:

$(this.currentLinkID).observe('click', this.doSomething);


While that will work, if you try to access any class properties (such as "statusMessage") inside the "doSomething" function, you will get "undefined" for their values. That is because, again as pointed out above, the function is not aware that it belongs to an object so it does not know that the object has properties. The remedy is simple, simply append .bind(this) to the function when it is tied to the event as in:

$(this.currentLinkID).observe('click', this.doSomething.bind(this));


A similar approach needs to be applied when using the prototype built-in Ajax object. If you want to tie your custom functions to the "onFailure", "onComplete" or "onSuccess" functions, you need to use the "bindAsEventListener" function:

onSuccess: this.showContent.bindAsEventListener(this)


"Bind" also needs to be used whenever you employ the "each" construct as described in Gotcha with Prototype.Bind and Arrays

Below is the full class:

var sampleObject = Class.create();
sampleObject.prototype = {
	linkIDs: ['mainPageLink', 'cdmScreenLink', 'adminLink', 'helpLink'],
	statusMessage: '',
	currentLinkID: 'myLink',
	initialize: function() {
		this.statusMessage = 'Who Am I?';

		$(this.currentLinkID).observe('click', this.doSomething.bind(this));
	},
	doSomething: function() {
		alert(this.statusMessage);

		new Ajax.Request(
			$(this.currentLinkID).href,
			{
			method: 'get',
			onSuccess: this.processContent.bindAsEventListener(this),
			evalScripts: true
			}
		);
	},
	processContent: function(request) {
	}
}

Event.observe(window, 'load', function() {
	var sampleObjectInstance = new sampleObject();
});


To call the functions of the sampleObject from outside you would simply do:

sampleObjectInstance.doSomething();

While you can access it's properties with the syntax:

alert(sampleObjectInstance.statusMessage);

That concludes the basic guide to using object oriented programming with Prototype. Did I miss anything to get you started?

Wednesday, February 06, 2008

Quick Tip on Including JavaScript files in Your Model-Glue Views

While programming with Model-Glue, I find that I have to often include JavaScript files in each individual view. I most often do this for form validation but it could be for whatever other reason. To keep things consistent, my project layout is based on the standard Model-Glue project layout with the editions of "css", "js" and "images":

  • trunk
    • config
    • controller
    • css
    • images
    • js
    • model
    • views

As you might have guessed, all my JavaScript files reside under "js". So until recently, I would include a JavaScript file as follows:

<script language="javascript" type="text/javascript">
<cfinclude template="../js/frmScanBadge.js" />
</script>

While this works, I do not like to have the hard coded path of the file. So instead, I settled on a solution that will get the name of the current view, look for a corresponding JavaScript file under the "js" directory and if finds one, it will include it:

<!--- If a JavaScript file exists with the name of the current template under the /js directory, include it --->
<cfif fileExists(expandPath("js/" & getFileFromPath(getCurrentTemplatePath()).replaceAll(".cfm", ".js")))>
<script language="javascript" type="text/javascript">
<cfinclude template="#'../js/' & getFileFromPath(getCurrentTemplatePath()).replaceAll('.cfm', '.js')#" />
</script>
</cfif>
This solution allows me to keep the name of the view and the corresponding JavaScript file the same. For new views, I can just drop a JavaScript file with the correct name in the "js" directory and it will be included in my view. Not insanely useful but nice non the less.

Friday, February 01, 2008

Create a Paragraph Preview with Prototype.js

Creating a paragraph with expandable text can be quite useful if you have a lot of text on your page but do not want all of it to show in the same time. Something to the effect of:

This is a short preview of the paragraph (More)


so when the user clicks on "More" the full paragraph will be displayed like so:

This is a short preview of the paragraph and here is the more text you wanted to see.(Less)

You can see the simple example in action at http://tech-cats.net/blog/examples/createParagraphPreview.html

Here is how to create this with some simple HTML and JavaScript. The JavaScript relies on Prototype.js but it can be easily re-implemented with a different JavaScript framework.

<div>
	This is a short preview of the paragraph
	<div id="moreText" style="display: none">
	and here is the more text you wanted to see.
	</div>

	<a class="showMore" href="#" rel="moreText">
		<span id="do_more_less">More</span>
	</a>
</div>

And the JavaScript to do the magic:
<script type="text/javascript">
// For each link element that has the class name "showMore",
// tie the 'click' event to the 'showHide' function
Event.observe(window, 'load',
function() {
	$$('a.showMore').invoke('observe', 'click', showHide);
});

function showHide(event)
{
  // Use the event to get the id of the element that invoked the function
  var currentElement = Event.element(event);

  // Get the parent node of that element and the value of it's "rel" attribute
  var toggleContainer = $(currentElement).parentNode.getAttribute('rel');

  // Get the toggle caption based on the html of the clicked element
  var toggleCaption = ($(currentElement).innerHTML.toLowerCase() == "more") ? "Less" : "More";

  // Update the current element with the new caption value
  $(currentElement).update(toggleCaption);

  // If the container to be toggled exists, toggle it's visibility
  if ($(toggleContainer)) {
    $(toggleContainer).toggle();
  }

  // Return false so the link does not go anywhere
  return false;
}
</script>

Something to keep in mind is that you cannot simply copy the JavaScript in the head of the HTML page. That is because the line "$$('a.showMore').invoke('observe', 'click', showHide);", needs to fire after the HTML has been rendered. To resolve this, we can one of two things:

  1. Put the script in the head tag but wrap it inside a window load observer or dom:loaded event (if you are using Prototype v1.6) like so:
    Event.observe(window, 'load',
    function() {
    	$$('a.showMore').invoke('observe', 'click', showHide);
    });
    
    or
    document.observe("dom:loaded",
    function() {
    	$$('a.showMore').invoke('observe', 'click', showHide);
    });
    
  2. Put the script as the last thing on the page, after the html.

The code can also be downloaded at http://tech-cats.net/blog/examples/createParagraphPreview.txt

DZone Launches DZone Communities

Developer web site DZone has launched several new web sites each with specific focus - .NET, ColdFusion, Ajax, PHP, etc. Each web site or "zone" (as DZone folks are referencing the web sites) has zone leaders to write articles and contribute content. Their some famous names among the zone leaders such as John Resig (jQuery), Rey Bango, Brian Rinaldi and Dan Wilson.

I volunteered to write for the .NET, ColdFusion, Ajax and SQL zones. So from now on content that appears on my blog will also be posted on each appropriate zone web site.

Thursday, January 17, 2008

Prototype.js Extensions by Nick Stakenburg

I was working on a project today with the excellent Prototip extension for Prototype.js. Prototip makes it easy to add tooltips to any element. Here is what a custom styled tooltip would look like:

toolitpExample

While browsing the support forum that Nick has put together for Prototip, I realized that he has created more than one Prototype extension. First is Starbox which allows you to add a star rating system to your page:

 Starbox - Rating stars for prototype_1200573851295

Next is Lightview which is very similar to Lightbox and it creates an overlay window to display your images:

Lightview_1200574518998

Friday, January 11, 2008

Unobtrusive JavaScript - Tie Events to Links with Prototype

Often, it is a good practice to separate your presentation layer from the the logic of your application. In web application, the presentation layer is your html pages, while the logic usually resides on the server or in JavaScript on the client (such as validation of user submitted values). Separating presentation and logic usually has to do with as little as possible logic inside your presentation layer.
With this in mind, it would be nice to have your application do something with JavaScript when you click on a link. In previous years that would be written something like this:

<a href="##" onClick="alert('You Clicked on Link 1');">Link 1</a>
<a href="##" onClick="alert('You Clicked on Link 1');">">Link 1</a>
While that works for this simple example, it is much nicer not to duplicate your code and not to mix your html with JavaScript. There is more than one reason the above approach is no longer the desired on, but here are few reasons: you need to write the "alert" call in each link you create, if your functionality changes, you will have to track each one of those calls and change them. The newer, desired, approach is to leave the JavaScript in a separate file and instead use the element's CSS class name to apply some logic to the element. So instead of the way above, you will simply define your links as follows:
<a href="##" class="clickMe">Link 1</a>
<a href="##" class="clickMe">Link 2</a>

Now you just need to write the JavaScript which will look for all "a" elements with the CSS class name of "clickMe" and for each one found, it will invoke the "whatDidIClick" function.

<script type="text/javascript" language="javascript">
// For each link element with CSS class name "clickMe"
$$('a.clickMe').invoke('observe', 'click', whatDidIClick);

function whatDidIClick(event) {
// Event.element(event) returns the element that caused the click event
alert('You clicked on ' + Event.element(event).innerHTML));
}
</script>

Wednesday, November 14, 2007

Useful Prototype.js String Functions

Often while doing web development with JavaScript there is a need to do some kind of string manipulation. Some of the more common tasks are checking if the string is empty and replacing some parts of the string but it doesn't have to stop there. Sometimes I want to capitalize the first letter in a string or check if the string ends with a certain character. In the past I would have to write functions most often involving regular expressions to do each of those trivial tasks. Luckily, in the present my favorite JavaScript library Prototype.js already comes with the mentioned above plus a bunch more string functions. Here are some that I use often:

  • empty() - checks if a string is empty
    var myEmptyString = "";
    if (myEmptyString.empty()) alert ('myEmptyString is empty');
  • blank() - checks if a string is either empty or contains white space
    var myBlankString = " ";
    if (myBlankString.blank()) alert ('myBlankString is blank');
  • capitalize() - capitalizes the first letter in a string
    var myString = "lower_case_string";
    // Will show an alert with "Lower_case_string"
    alert(myString.capitalize());
  • gsub(pattern, replacement) - replaces a pattern in the string with the string specified in 'replacement'
    var myString = "some_string";
    // Will show an alert with "some string"
    alert(myString.gsub('_', ' '));
  • endsWith(substring) - checks if the specified string ends with the provided 'substring'
    var myString = "some string_";
    // Will show an alert with "myString ends with _"
    if (myString.endsWith('_')) alert('myString ends with _');
Many more can be found at the Prototype API reference

Tuesday, October 02, 2007

Prototype.js Object Initialization and Bind While Enumerating Arrays

If the title sounds confusing, that is because it is. I couldn't figure out a better way to describe the issue I was running into. Let see if I can better demonstrate. I have a "initialize" function defined in my JavaScript object as such:

initialize: function() {
// siteID contains a list of ids such as "1,2,3,4"
// Convert the id list in siteIDs to a prototype array $A(siteIDs) and
// for each element in the array, run a function passing it the single siteID
$A(siteIDs).each(function(siteID) {
// If an element exists on the page with the id of setCurrentSiteElementPrefix + current siteID
if ($(this.setCurrentSiteElementPrefix + siteID)) {
 // Add an observer to the 'click' event of that element and bind it
 // to the 'getContent' function
 $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this));
}
});
}
where "siteIDs" is a comma delimited list of ids ("1,2,3,4") and "setCurrentSiteElementPrefix" is a part of the name of link element with a dynamically generated id (such as setCurrentSiteElementPrefix_1, setCurrentSiteElementPrefix_2). Here is the above loop in pseudo code (just for your kids Doug): Convert the list of siteIDs to a prototype array and for each found array element, run a function passing the individual site id. $A(siteIDs) converts the list of site ids to an array, while .each() executes the inline function for each array element. // $A(siteIDs).each(function(siteID) If an element exists on the page with the id starting with setCurrentSiteElementPrefix (definition not shown here) + current siteID (passed by the inline function above) // if ($(this.setCurrentSiteElementPrefix + siteID)) Add an observer to the "click" event of that element and bind it to the "getContent" function. The "getContent" function has to bound to the "this" scope since the "getContent" function will not otherwise know what "this" means when trying to refer to elements defined in the object. // $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this)); Never mind why I am doing this, I will follow up on that with another post. So I was trying to tie an event observer to the 'click' event of each link element by looping through the list of dynamic ids ($A(siteIDs).each(function(siteID)). However, that was giving me some issues with the message "this.getContent is not defined". Why the heck not, the code is inside the "initialize" method of my object?! It turns out while looping over an array as above the "this" scope is not preserved. So the function inside the loop has no idea what "this" means. The simple fix is to surround the function with parenthesis and append ".bind(this)":
initialize: function() {
 // Note the extra parenthesis before 'function(siteID)'
 $A(siteIDs).each((function(siteID) {
  if ($(this.setCurrentSiteElementPrefix + siteID)) {
   $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this));
  }
 }).bind(this));
 // Note the ').bind(this)' before the semicolon
}
If you like to learn more on bind, check out Understanding bind and bindAsEventListener in Javascript and Understanding bind and bindAsEventListener in Javascript - Part II

Thursday, September 20, 2007

JSValidation Kicks Butt - Validation Library for Prototype.js

to get it to work, simply include the library like so:

<script language="javascript" type="text/javascript" src="js/jsvalidate/jsvalidate.js"></script>
Add some css styles:
.jsvalidation { color: #ff0000; font-size: 14px; }
add "jsrequired" (for a required field) and the jsvalidate type (such as "jsvalidate_number") to the "class" attribute of your text element.
<input type="text" name="year" id="year" size="4" class="jsrequired jsvalidate_number" />
and finally set the message to be displayed when the validation fails in the "alt" attribute of the element:
<input type="text" name="year" id="year" size="4" class="jsrequired jsvalidate_number" alt="Please enter a Year" />
More on the jsvalidation types at the docs

Monday, August 20, 2007

A couple of good Prototype.js articles

Came across a couple of good articles on using Prototype.js:

How well do you know prototype

How well do you know prototype (part II)

Thursday, July 26, 2007

Prototype.js Add-ons

I came across some add-ons based on Prototype. Here a small list:

TableKit: http://www.millstream.com.au/view/code/tablekit
Create sortable, resizable and editable tables

Control Suite: http://livepipe.net/projects/control_suite/
From the website: "Collection of six high quality widgets and controls for Web 2.0 applications built using the Prototype JavaScript framework"

Tidbits: http://livepipe.net/projects/prototype_tidbits/
Odds and ends for the Prototype framework

Window: http://prototype-window.xilinus.com/
Resizable/model/skinnable overlay window.

LightWindow: http://stickmanlabs.com/lightwindow/
Skinnable overlay window with many possibilities