Search This Blog

Loading...
Showing posts with label Prototype. Show all posts
Showing posts with label Prototype. 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?

Monday, March 03, 2008

Creating a Gmail Like Ajax Status Display

Most of us geeks know and love Gmail. It has a very nice interface and it is an inspiration to constantly improve our own web applications. Today, I set out to create an unobtrusive Gmail like page status message, much like the one shown in the screen shot:

 gmail_screnshot

My requirements were simple:

  1. Should look like the one used in Gmail
  2. Should be able to just include the source JavaScript file without any further configuration
  3. Should allow the user to specify a custom status message to be displayed
  4. Should allow for the use of custom styles but none are required
  5. Integrates with Prototype.js Ajax requests

The only prerequisite is Prototype.js v1.6

Let's get started. If all you care about is the end result. Here how to include it in your page:

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="ajaxStatusDisplay.js"></script>


That's it! Now whenever you make an Ajax request with Prototype, you will see a message popup with the text "Loading...". You can check out the end results at http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Simple.html

Now to the advanced features.

As stated #3 and #4 above, two of our requirements are to allow the user to specify a custom status message and custom CSS styles for the displayed message. Satisfying the first one of those requirements is done by having the user create an element on the page with the custom message like so:

<div id="ajaxStatusDisplay_userMessage">
Loading...Wait a Minute!
</div>


This element does not have to be a div. It could be a any text container such as span or even input (text or hidden). What is important is the id of the element, it has to be set to "ajaxStatusDisplay_userMessage" for the custom message to be picked up.

The next requirement works much in the same way. Custom styles can be specified by creating two CSS classes with certain names: "ajaxStatusDisplay_userStyle" and "ajaxStatusDisplay_userMessageStyle". Here an example that will produce the default look:

<style type="text/css">
.ajaxStatusDisplay_userStyle {position:absolute;left:45%;top:2px;height:10px;}
.ajaxStatusDisplay_userMessageStyle {
	background:#FFF1A8 none repeat scroll 0%;color:#000;padding: 0pt 5px;
	font-family:Arial, Helvetica, sans-serif;font-size:14px;font-weight: bold;
	text-align:center;width:100%
}
</style>

And here is another example that will produce a white text on black background in the top right corner:
 

<style type="text/css">
.ajaxStatusDisplay_userStyle {position:absolute;left:94%;top:0px;height:10px;}
.ajaxStatusDisplay_userMessageStyle {
	background-color:#000;
	color:#fff;
	font-family:Arial, Helvetica, sans-serif;
	padding:2px;
	width:100%;
}
</style>

To use the your own CSS styles, you do not need to do anything but define them as shown above (as compared to the initial version where you needed to edit the file "ajaxStatusDisplay.js" and set the "useUserCssStyles" variable to "true").

A small extra feature is the ability to change the status message at runtime. This is done by using (you guessed it), the "setStatusMessage" function as follows:

// Set the status message based on the value in the "userMessage" input field
ajaxStatusDisplay.setStatusMessage($F('userMessage'));


To see setting the status message in action along with using a custom CSS style, check out http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay-Advanced.html

You can view all the sources at:

JavaScript Source: ajaxStatusDisplay.txt
Simple Example Source: ajaxStatusDisplay-Simple.txt
Advanced Example Source: ajaxStatusDisplay-Advanced.txt

You can download the JavaScript source at:

http://blog.tech-cats.net/examples/ajax/ajaxStatusDisplay.js

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

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 09, 2007

Neat Prototype Trick - Activate First Form Element Without a Certain Class Name

Let's say you have form with a bunch of fields that you have disabled with the "read only" attribute so the user can't change the data. Also, you have applied a css class called "readOnlyInput" so you can style the input field as read only field (different color or whatever else you like). I had such form this evening but I wanted the focus to automatically go to the first non read only element when the page is loaded. Usually, you can pass focus on a form element by doing this:

// 'myFormField' is the id of form field you want to focus on
$('myFormField').focus();
However, if the input field already contains data, you might want to focus the field and highlight it in the same time. In that case you will do:
// 'myFormField' is the id of form field you want to focus on
$('myFormField').activate();
That is all fine and great but what when I have a bunch of input elements and some of them might be marked "read only". To make things more complicated (as I love to do), I would hate to hard code the id of the first element I want to focus/activate. So here is the solution:
// For each input element on the form
$A($('myForm').getInputs()).each(function(formElement) {
 // If the form element does not have the class "readOnlyInput"
 if (!$(formElement).hasClassName('readOnlyInput')) {
  // Activate that element
  $(formElement).activate();
  // Break out of the loop (prototype specific syntax here)
  throw $break;
 }
});
Just so Doug's kids would understand this, here it is in psudo code:
  1. Get a list of form input elements by using Prototype's built in "getInputs" function $('myForm').getInputs()
  2. Convert the list of form inputs to a Prototype array $A($('myForm').getInputs())
  3. Now for each of the elements in the array, run a function pass in the current element $A($('myForm').getInputs()).each(function(formElement)
  4. If the current form element has a css class not matching "readOnlyInput" if (!$(formElement).hasClassName('readOnlyInput'))
  5. Activate that form element $(formElement).activate();
  6. We found the first element so break out of the loop throw $break;

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

Thursday, September 13, 2007

Disabling Forms with Prototype - Gotcha

I love using Prototype for all my JavaScript needs. Ajax calls are real easy with or without parameters. To get some form parameters to pass with your request, you would simply do:

var params = $('myForm').serialize();
and then you would disable the form like so:
$('myForm').disable();
However, here is a little gotcha I always seem to forget:
  • DO NOT disable the form before you read your form values
If you do, code such as:
$F('myFormFiled');
or
$('myForm').serialize();
will no longer work. By not working, I mean will just return empty and you will be beating your head against the desk.