Time for action converting a unit convertor into a plugin

Re-using one of the many well designed jQuery UI widgets is good as it saves us development and maintenance time but the true power of the jQuery UI framework is the manner in which it enables us to devise completely new widgets that merge seamlessly with the rest of the framework and are indistinguishable in their use from the standard widgets. To illustrate what is possible, let's implement our unit converter again, but this time as a jQuery plugin:

  1. Go to the directory containing the example code for Chapter 2.
  2. Double-click the file unitconverter2.py, the CherryPy console will again open in a window.
  3. Enter http://localhost:8080 in the address bar of your browser (or click refresh if it is still open on that address). You will now see a slightly restyled unit converter:
    Time for action converting a unit convertor into a plugin

The interaction with this new unit converter is exactly the same as our previous one.

What just happened?

Instead of structuring a widget with a<form> element containing a number of additional elements, we now take a simpler approach. We will design a reusable unit converter widget that can be inserted into any<div> element. Our HTML backbone becomes much simpler now, as its body will just contain a single<div> element:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/
TR/html4/strict.dtd">
<html>
<head>
<link rel="stylesheet" href="static/css/redmond/jquery-ui-
1.8.1.custom.css" type="text/css" media="screen, projection" />
<script type="text/javascript" src="static/jquery-1.4.2.js" ></script>
<script type="text/javascript" src="static/jquery-ui-1.8.1.custom.min.
js" ></script>
<script type="text/javascript" src="unitconverter2.js" ></script>
</head>
<body id="spreadsheet_example">
<div id="example"></div>
<script type="text/javascript">
$("#example").unitconverter(
{
'km_mile':1.0/0.621371192,
'mile_km':0.621371192
});
</script>
</body>
</html>

The first highlighted line includes the JavaScript file that contains the new implementation of the unit converter. We refer to the plugin defined in this file in the JavaScript code near the end of the<body> element (last highlighted line). This script refers to the<div> element to which we want to add a unit converter by its id (in this case #example) and apply the unitconvertor() method.

As we will see when we look at the JavaScript code that implements our converter plugin, unitconverter() takes an option object as its single argument. This option object may contain any number of keys defining additional conversion factors for this instance of the plugin. In this case, we pass additional information to allow for conversion from miles to kilometers, and vice versa.

Pop quiz adding conversions to a unitconverter instance

What would the JavaScript look like when we want to add a unit converter plugin with the possibility of converting from cubic feet to liters?

JavaScript: creating a jQuery UI plugin

All jQuery UI plugins are defined in the same way by adding a new function to the fn attribute of the jQuery object (the object we mostly refer to by its alias $). In unitconverter2.js, this is exactly what we do, as it is seen in the first line of the following code.

The next thing we do is merge any options passed to the plugin with defaults (highlighted). jQuery provides an extend() method that merges the attributes of any number of objects and returns the first one. As we do not want to overwrite the default options that we have defined in $.fn.unitconverter.conversion_map, we pass it an empty object. This object will receive the default attributes and any attributes defined in the options object, overwriting the ones with a name that is the same. This set of merged attributes is stored in the cmap variable:

jQuery.fn.unitconverter = function(options){
	var cmap = $.extend({},$.fn.unitconverter.conversion_map,options);

The conversion factors are referred to by keys of the form unit1_unit2. To construct two drop-down selectors from the keys, we iterate over all these keys and use JavaScript's split() method to retrieve the individual units (highlighted). These are then stored in the from and to arrays:

var from = new Array();
var to = new Array();
for (var key in cmap){
	var units = key.split("_");
	from.push(units[0]);
	to.push(units[1]);
}

The next step is to construct the HTML needed by the plugin to present to the user. The structure is similar to the handcrafted one used in the previous example, a<form> with<input> and<select> elements, and a<button>. The<form> element is adorned with a random id attribute. This way we may refer to it later even if there is more than one unit converter present on the page.

The<select> elements contain a number of<option> elements that are created by retrieving the unit names stored in the from and to arrays one-by-one with the pop() method. The first of these options is selected by default (highlighted). The HTML code is then passed to the append() method of this. this is a variable that is available to the function implementing the plugin that contains the selected elements the plugin is applied to, in our example the<div> element with the #example id:

	var id = "unitconverter" + new String(Math.floor(Math.random() 
* 255 * 255));
	var html = '<form id="' + id + '"><input name="from" type="text" 
value="1" />';
	html += '<select name="fromunit">';
	html += '<option selected="true">'+from.pop()+'</option>';
	var len = from.length;
	for (var i=0; i<len; i++){
html += '<option>' + from.pop() + '</option>' };
	html += '</select> = ';
	html += '<input name="to" type="text" readonly="true" />';
	html += '<select name="tounit">';
	html += '<option selected="true">' + to.pop() + '</option>';
	var len = to.length;
	for (var i=0; i<len; i++){
html += '<option>' + to.pop() + '</option>'};
	html += '</select>';
	html += '<button name="convert" type="button">convert</button>'
html += '</form>';
	this.append(html);

The randomly generated id for the form element now comes in handy to select just the<button> element within the form we are currently constructing and convert it to a button: we construct a suitable selector by concatenating relevant parts with "#"+id+" button".

Note that it is perfectly valid to include other plugins or widgets within a custom plugin. This time we choose to construct a slightly different looking button with just an icon and no text by passing an appropriate options object. From the numerous icons shipped with jQuery UI, we choose the one that represents the function of the button best: ui-icon-refresh (highlighted).

The conversion that happens when the user clicks the button is implemented by a function that we will encounter shortly and that is passed by the button object (available to the click() method as the this variable) and the merged map of conversion factors:

$("#"+id+" button").button({
			icons: {
					primary: 'ui-icon-refresh'
			},
			text: false
	}).click(function(){return convert(this,cmap);});

The finishing touch is to style our widget in a consistent manner. jQuery provides us with a css() method that allows us to directly manipulate the style attributes of any element. We first deal with a layout matter: we apply a float:left style to the<form> element to make sure it doesn't fill the page completely, but shrink/wraps itself around the elements it contains:

$("#"+id).css('float','left'),

We then copy a number of background style attributes from the<button> element to the<form> element to give the<form> element a look that is consistent with the theme applied to the standard button widget. Other style elements from the theme like font face and font size are applied to the form element by adding the ui-widget class (highlighted). We end by returning the this variable (which in our example contains the<div> element we selected, but now with the<form> element we just added to it). This allows for chaining additional jQuery methods:

	$("#"+id).css('background-color',
$("#"+id+" button").css('background-color'));
	$("#"+id).css('background-image',
$("#"+id+" button").css('background-image'));
	$("#"+id).css('background-repeat',
$("#"+id+" button").css('background-repeat'));
	$("#"+id).addClass("ui-widget");
	return this;
};

Of course, we still need to define a function that does the actual conversion when the button of the unit converter is clicked. It differs slightly from the previous implementation.

The convert() function is passed both the<button> element that is clicked and a map with conversion factors. The<form> element enclosing the button is determined with the parent() method and stored in the form variable.

The input value we want to convert is retrieved from the<input> element with a name attribute equal to from. We can find this specific element by selecting all children of the<form> element stored in form and filtering these children by passing a suitable selector to the .children() method (highlighted).

In a similar way, we determine which option is selected in the two<select> elements:

function convert(button,cmap){
	var form = $(button).parent();
	var value = form.children("input[name='from']").val();
	var f = form.children("select[name='tounit']").
children("option:selected").val();
	var t = form.children("select[name='fromunit']").
children("option:selected").val();

What is left is the actual conversion. If the conversion units are not equal, we retrieve the conversion factor from the map (highlighted) and then multiply it by the contents of the<input> element interpreted as a floating point number. If the input can't be interpreted as a floating point number or there wasn't a suitable conversion factor in the map, the result of the multiplication is a NaN (Not a Number) and we signal that fact by placing an error text in the result. However, we convert the result to a number with four decimal digits with JavaScript's toFixed() method if everything goes well:

var result = value;
	if(f != t){
		var c=cmap[f+'_'+t];
		result=parseFloat(value)*c;
		if (isNaN(result)){
				result = "unknown conversion factor";
		}else{
				result = result.toFixed(4);
		}
	}
	form.children("input[name='to']").val(result);
};

unitconverter2.py concludes by defining an object with defaults:

jQuery.fn.unitconverter.conversion_map = {
	inch_cm":1.0/2.54,
	"cm_inch":2.54
}

Pop quiz changing option defaults

If we would:

  1. Add a unitconvertor to a <div> element with an ID #first.
  2. Add the possibility of converting from cubic feet to liters to the default conversion map.
  3. And finally, add a unitconverter to a <div> element with an id #last.

The code would look something like this:

$("#first").unitconverter();
$.extend($.fn.unitconverter.conversion_map, {'cubic feet_
litres':1.0/28.3168466});
$("#last").unitconverter();

If we would execute the preceding code, which <div> element(s) would get a unitconverter with the added conversion possibility?

  1. The div with the #first ID
  2. The div with the #last ID
  3. Both
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.168.203