In this chapter, you will learn the following recipes:
Detecting browsers and platforms used by clients
Retrieving DOM nodes and elements
Acquiring references to Ext JS components
Running high-performance DOM queries
Encoding and decoding JSON
Encoding and decoding URL data
Determining the object type and converting empty references to a default value
Finding objects in an array and removing array items
Manipulating strings à la Ext JS
Effortless range checking for numbers
Formatting, parsing, and manipulating dates
Preventing naming conflicts and scoping non-global variables
Extending JavaScript objects, the Ext JS way
Adding features to the Ext JS classes
Building custom JavaScript classes that inherit the functionality of Ext JS
In this chapter, you will learn how to accomplish tasks related to working with different browsers, platforms, the Document Object Model (DOM), and the Ext JS data types. You will also take in how to create custom data types that extend the functionality of the native Ext JS types.
Although Ext JS is a cross-browser library, there are instances when your application needs to show a different behavior depending on the user's browser or platform. Browser and platform detection are very simple tasks with Ext JS, and this recipe shows how it's done.
You can detect various browsers and platforms used by your clients in the following way:
You can use
Ext.isChrome
to find out if the detected browser is Chrome:var browser = ""; if (Ext.isChrome) { browser = "Hi! I'm the new kid on the block"; }
The browsers such as Mozilla and Firefox that use the Gecko rendering engine are detected with
Ext.isGecko, Ext.isGecko2
, andExt.isGecko3:
if (Ext.isGecko) { browser = "Gecko"; } if (Ext.isGecko2) { browser = "Gecko2"; } if (Ext.isGecko3) { browser = "We like Firefox!"; }
The Internet Explorer versions are flagged by
Ext.isIE, Ext.isIE6, Ext.isIE7
, andExt.isIE8:
if (Ext.isIE) { browser = "IE"; } if (Ext.isIE6) { browser = "Get a decent browser, now!"; } if (Ext.isIE7) { browser = "IE7"; } if (Ext.isIE8) { browser = "IE8"; }
Opera is detected with
Ext.isOpera:
if (Ext.isOpera) { browser = "Opera"; }
And finally, Safari is detected with
Ext.isSafari, Ext.isSafari2, Ext.isSafari3
, andExt.isSafari4:
if (Ext.isSafari) { browser = "Safari"; } if (Ext.isSafari2) { browser = "Safari2"; } if (Ext.isSafari3) { browser = "Safari3"; } if (Ext.isSafari4) { browser = "Safari4"; }
When it comes to platform detection, Adobe's Air is detected with
Ext.isAir:
var platform = ""; if (Ext.isAir) { platform = "Air"; }
Linux is detected with
Ext.isLinux:
if (Ext.isLinux) { platform = "Linux"; }
Mac OS is detected with
Ext.isMac:
if (Ext.isMac) { platform = "Mac"; }
Windows is detected with
Ext.isWindows:
if (Ext.isWindows) { platform = "Windows "; }
As you can imagine, the values for Ext JS's browser and platform type flags are all obtained from parsing the value of the userAgent
property of the JavaScript navigator object.
Use this recipe when you need to alter the features or behavior of your application depending on the browser or platform being used. For example, depending on the platform used, you can provide Mac or Windows versions of a browser plugin; or you can account for rendering differences of various browsers when positioning DOM elements.
Web development involves hefty doses of DOM manipulation. This recipe shows how you can use Ext JS to get a handle on any DOM element.
Using a div element as an example, you can get a handle to the div in the following way:
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css"
href="../ext/css/ext-all.css"/>
<script type="text/javascript" src="../ext/ext-base.js"></script>
<script type="text/javascript"
src="../ext/ext-all-debug.js"></script>
<script type="text/javascript"> Ext.BLANK_IMAGE_URL = '../ext/images/default/s.gif';
Ext.onReady(function() {
var firstDiv = Ext.get("div-1");
Ext.Msg.alert('Using Ext.get(el)', firstDiv.id);
});
</script>
</head>
<body>
<div id="div-1">This is the first div</div>
</body>
</html>
The previous code uses Ext.get(element)
, a shorthand for Ext.Element.get(element)
, to acquire a reference to a div element in the document. You can use this function to retrieve references that encapsulate DOM elements.
The Ext.get(element)
function uses simple caching to consistently return the same object. Note that Ext.get(element)
does not retrieve Ext JS components. This is can be accomplished using Ext.getCmp()
, explained in the next recipe.
As Ext JS is all about working with components, it's essential to learn how to acquire a reference to any component in your code. For example, this recipe shows how easy it is to reference a ComboBox
component.
You can reference a ComboBox
component as shown in the following code:
<html> <head> <title></title> <link rel="stylesheet" type="text/css" href="../ext/css/ext-all.css"/> <script type="text/javascript" src="../ext/ext-base.js"></script> <script type="text/javascript" src="../ext/ext-all-debug.js"></script> <script type="text/javascript"> Ext.BLANK_IMAGE_URL = '../ext/images/default/s.gif'; Ext.onReady(function() { var colorsStore = new Ext.data.SimpleStore({ fields: ['name'], data: [['Blue'],['Red'],['White']] }); var combo = new Ext.form.ComboBox({ store: colorsStore, displayField: 'name', typeAhead: true, mode: 'local', forceSelection: true, triggerAction: 'all', emptyText: 'Select a color...', selectOnFocus: true, applyTo: 'colors-combo', id: 'colors-combo' }); // Get a reference to the combobox using Ext.getCmp(id). var combo = Ext.getCmp("colors-combo"); // Using the reference to the combo, add a handler to the //'select' event. combo.on('select', function() { Ext.Msg.alert('Using Ext.getCmp(id)', The selected color is ' + combo.getValue(); }); }); </script> </head> <body> <input type="text" id="colors-combo"/> </body> </html>
References to components are obtained using the Ext.getCmp(id)
function, where id
is the ID of the component. Keeping track of components is possible, thanks to the ComponentMgr
class. It provides for easy registration, un-registration and retrieval, as well as notifications when components are added or removed.
This method is particularly useful when explicit component references do not already exist in your code, for example when components are defined as part of the items collection of a container. (Think of a tab panel and its tabs, or a border layout and its contained panels.)
There are other DOM and component utilities provided by Ext JS:
Ext.getBody()
returns the body of the document as anExt.Element
Ext.getDoc()
returns the current HTML document as anExt.Element
Ext.getDom(id)
returns the DOM node for the supplied ID, DOM node, or element
Now you'll see how to run queries against the DOM using Ext JS—a must-have when you need to manipulate or perform actions on multiple, related DOM elements. The examples show how to reference all the div elements in a document, obtain all the elements with a CSS class name msg
, and iterate over the options of a select element.
The following code snippets are examples of how to run high-performance queries against the DOM using Ext JS:
When you need to retrieve the elements that match the div selector to find the div elements in the document, use the following code snippet:
Ext.onReady(function() { // Get all the div elements. var nodes = Ext.query('div'); Ext.each(nodes, function(item, index, allItems) { document.write(index + '<br/>'); }); });
When you need to reference the elements with the class name
msg
, use the following code snippet:var msgLinks = Ext.query('.msg'); Ext.each(msgLinks, function(item,index) { // Do something with the element here. });
When you want to iterate over the options of a select element, use the following code snippet:
var select = Ext.get('countries-select'); Ext.each(select.options, function(item,index) { // Do something with the item here. });
The previous examples use Ext.query(path, [root])
, a shorthand of Ext.DomQuery.select(path, [root])
, to retrieve an array of DOM nodes that match a given selector.
DomQuery
provides high-performance selector/XPath processing by compiling queries into reusable functions. It works on HTML and XML documents, supports most of the CSS3 selector's specification as well as some custom selectors and basic XPath, and allows you to plug new pseudo classes and matchers.
The Retrieving DOM nodes and elements recipe, covered earlier in this chapter, shows how you can use Ext JS to get a handle on any DOM element.
The Acquiring references to Ext JS components recipe, covered earlier in this chapter, explains how to acquire a reference to any component in your code.
Converting JavaScript and Ext JS objects to JSON, and converting JSON data back to JavaScript objects is easily achievable with Ext JS. For example, here's how to JSON-encode an array and how to rebuild the array from its JSON representation:
Note
JavaScript Object Notation (JSON) is a lightweight text format where an object is represented with an unordered set of name/value pairs and an array with an ordered collection of values.
JSON is completely language independent, easy for humans to read and write, and easy for machines to parse and generate. These properties make JSON an ideal data-interchange format.
Find out more about JSON at www.json.org.
Let's encode an array of colors using the following steps:
1. Create an array called
colorsArray:
var colorsArray = new Array();
2. Put some values in the array:
colorsArray[0] = 'Blue'; colorsArray[1] = 'Red'; colorsArray[2] = 'White';
3. Now, convert to JSON:
var colorsJson = Ext.encode(colorsArray);
The value of the
colorsJson
variable should be the string["Blue","Red","White"]
string4. Let's re-create the array based on its JSON string. Take the JSON representation of
colorsArray:
var colorsJson = '["Blue","Red","White"]';
5. Parse the JSON and rebuild the array:
var colorsArray = Ext.decode(colorsJson);
After this, colorsArray
contains the colors data: colorsArray[0]
is 'Blue', colorsArray[1]
is 'Red', and colorsArray[2]
is 'White'.
To obtain a JSON representation of an array, object, or other value, pass the value to Ext.util.JSON.encode(object)
. You can also use the shorthand, Ext.encode(object)
.
You can parse a JSON string by using Ext.util.JSON.decode(json)
, or its shorthand Ext.decode(json)
.
While decoding JSON involves simply calling the JavaScript eval(String)
function, the encoding process is more complicated and requires different implementations depending on the data type being encoded. Internally, the encode(object)
function calls specialized encoding functions for arrays, dates, Boolean values, strings, and other types.
You can also set the Ext.USE_NATIVE_JSON
property to true, which will cause calls to encode(object)
and decode(json)
to use the browser's native JSON handling features. This option is turned off by default. If you turn it on, beware that native JSON methods will not work on objects that have functions, and that property names should be quoted in order for the data to be correctly decoded.
JSON encoding and decoding is a pillar of modern web development, given the role of JSON—a language-independent, data-interchange format—in facilitating communications between the client-side and server-side of web applications. For instance, you can expect to find JSON manipulation when your application needs to send data to the server, as well as when the application needs to dynamically create objects from server-supplied data.
Two-way conversion between objects and URL data is a challenge that Ext JS can help with. Let's examine how a JavaScript object can be encoded for transmission through the URL query string, as well as how information contained in a URL can be used to build a JavaScript object.
The following steps will guide you through the process of encoding and decoding URL data:
1. Take a
selectedColors
object as the data to be passed in a URL:var selectedColors = {color1:'Blue', color2:'Red', color3:'White'};
2. Convert the object to URL data like this:
var encodedUrl = Ext.urlEncode(selectedColors); // encodedUrl is an encoded URL query string: //color1=Blue&color2=Red&color3=White.
3. Now, a URL can be built using the data just created. For example,
http://MyGreatApp/SetSelectedColors?color1=Blue&color2=Red&color3=White.
4. You can easily create objects from the encoded URL. Assuming we obtained the data from the URL we used above
(http://MyGreatApp/SetSelectedColors?color1=Blue&color2=Red&color3=White)
, obtain the URL data like this:encodedUrl = location.search;
5. Re-create the
selectedColors
object as follows:var selectedColors = Ext.urlDecode(encodedUrl); // Now the value of selectedColors' color1 property is 'Blue', // color2's value is 'Red' and color3's value is 'White'.
Ext.urlEncode(object)
and Ext.urlDecode(string, overwrite)
provide object serialization to URL data and URL data deserialization to objects respectively. Encoding is accomplished by creating the URL query string's key-value pairs based on each object property, or array value passed to the encoding function. Decoding is accomplished by creating an object with a property for each key-value pair that exists in the URL's query string.
You can use this recipe when your application needs to send information to the server via AJAX or standard HTTP requests, as well as when you need to use the URL's query string to feed the application data that can later be converted to JavaScript objects.
This recipe teaches you how to determine the types of different objects using the facilities of Ext JS, as well as a simple method that can be used to initialize empty references with a default value.
You can determine the types of different objects in the following way:
1. Create some dummy data structures:
var colorsArray = new Array(); colorsArray[0] = 'Blue'; colorsArray[1] = 'Red'; colorsArray[2] = 'White'; var colorsObject = { color1: 'Blue', color2: 'Red', color3: 'White' }; var aNumber = 1; var aString = '1'; var sample; var empty;
2. Check the types of our variables:
var colorsArrayType = Ext.type(colorsArray); // colorsArrayType's value is "array". var isArray = Ext.isArray(colorsArray); // isArray is true var colorsObjectType = Ext.type(colorsObject); // colorsObjectType's value is "object". var isArray = Ext.isArray(colorsObject); // isArray is false var number = Ext.num(aNumber, 0); // number is 1. number = Ext.num(aString, 0); // Since aString is not numeric, the supplied // default value (0) will be returned. var defined = Ext.util.Format.undef(sample); // defined is an empty string sample = "sample is now defined"; defined = Ext.util.Format.undef(sample); // defined is now "sample is now defined". var notEmpty = Ext.value(empty, 'defaultValue', false); // notEmpty is 'defaultValue'
The Ext.type(object)
function is capable of detecting elements, objects, text nodes, whitespaces, functions, arrays, regular expressions, numbers, and node lists.
As its name indicates, Ext.isArray(object)
simply checks whether the passed object is a JavaScript array. Ext.num(value, defaultValue)
, in turn, does a numeric type check on the passed value and returns the default value when the argument is not numeric.
Ext.util.Format.undef(value)
is a very useful function when you need to test for undefined values. It returns either the supplied argument or an empty string if the argument is undefined.
Ext.value(value, defaultValue, allowBlank)
also allows you to specify a default value when the value being tested is undefined.
The main task in this recipe is to find out whether an arbitrary object exists in an array. A way to remove objects from the array is also explored.
The following steps illustrate how you can perform object existence tests and object removal in an array:
1. Create a sample array as follows:
var colorsArray = new Array(); colorsArray[0] = 'Blue'; colorsArray[1] = 'Red'; colorsArray[2] = 'White';
2. Determine whether an object exists in an array by trying to find its position in the array:
var position = colorsArray.indexOf('White'); // postition is 2, the index of 'White' in the array. position = colorsArray.indexOf('Brown'); // 'Brown' does not exist in the array, // position is -1.
3. Remove one of the objects from the array:
colorsArray.remove('Blue'); position = colorsArray.indexOf('Blue'); // 'Blue' does not exist anymore, // position is -1.
Ext JS augments the native Array
class with Array.indexOf(object)
and Array.remove(object)
. While indexOf(object)
works by examining each array element until it finds one that matches the supplied argument, remove(object)
uses the native Array.splice(index, howmany, element1,....., elementX)
function to remove the supplied argument from the array.
String manipulation has always been a challenging area in JavaScript. Here, you will learn how to escape special characters, trim, pad, format, truncate, and change the case of your strings with the help of the utilities of Ext JS.
You can manipulate strings as shown in the following steps:
1. Create your sample values as shown here:
var needsEscape = "ExtJS's String Class will escape this"; var needsTrimming = " Needs trimming "; var cls = 'color-class' var color = 'Blue'; var sort = 'ASC'; var sample = "some text"; var longText = "This text should be truncated, it's really long."; var withScript = 'Some text<script type="text/javascript">var color = "Blue";<\/script>'; var longText = "Only a part of this text is needed."; var multiLineText = "One line\nAnother line\nYet another line"; var money = 29.99; var sample1 = '22'; var sample2 = '550'; var sample3 = '1500';
2. Now, let's use the string manipulation functions:
var escaped = String.escape(needsEscape); document.write(escaped + '<br/>'); // The escaped string is "ExtJS\'s String Class will escape this". var trimmed = needsTrimming.trim(); document.write(trimmed + '<br/>'); // the trimmed string is "Needs trimming" var formatted = String.format('<span class="{0}">{1}</span>', cls, color); document.write(formatted + '<br/>'); // formatted is '<div class="color-class">Color</div>' sort = sort.toggle('ASC', 'DESC'); document.write(sort + '<br/>'); // instead of conditional logic: //sort = (sort == 'ASC' ? 'DESC' : 'ASC'); var converted = Ext.util.Format.uppercase(sample); document.write(converted + '<br/>'); // converted is now "SOME TEXT". sample = "SOME TEXT"; converted = Ext.util.Format.lowercase(sample); // converted is now "some text". document.write(converted + '<br/>'); sample = "some text"; converted = Ext.util.Format.capitalize(sample); document.write(converted + '<br/>'); // converted is now "Some text". var truncated = Ext.util.Format.ellipsis(longText, 20); document.write(truncated + '<br/>'); // truncated is "This text should ...". // Removing script tags var noScript = Ext.util.Format.stripScripts(withScript); document.write(noScript + '<br/>'); // noScript is "Some text". // Returning a portion of a string var subString = Ext.util.Format.substr(longText, 0, 11); document.write(subString + '<br/>'); // subString is "Only a part". // Converting newline characters to the html tag <br/> var html = Ext.util.Format.nl2br(multiLineText); document.write(html + '<br/>'); // html is // One line // Another line // Yet another line var usCurrency = Ext.util.Format.usMoney(money); document.write(usCurrency + '<br/>'); // usCurrency is $29.99 // Normalizing strings var normalized1 = String.leftPad(sample1, 4, '0'); // normalized1 is '0022' var normalized2 = String.leftPad(sample2, 4, '0'); // normalized3 is '0550'; var normalized3 = String.leftPad(sample3, 4, '0'); // normalized2 is '1500' document.write(normalized1 + '<br/>'); document.write(normalized2 + '<br/>'); document.write(normalized3 + '<br/>');
The useful functions escape(string), trim(), format(value, start, length), toggle(value1, value2)
, and leftPad(string, size, [char])
all belong to the Ext.String
class, which is an extension of the JavaScript String
object.
The rest of the functions mentioned in this recipe belong to the Ext.util.Format
class. Format
is a singleton class that contains reusable formatting functions. Other useful functions in Format
are htmlEncode(string)
and htmlDecode(string)
.
Now, you'll see how to use Ext JS in order to guarantee that a number falls within a certain range.
The following steps illustrate how to perform range checking on numeric values:
1. Create your sample numbers:
var number1 = 30; var number2 = 75;
2. Check whether your numbers are within a range:
var constrained = number1.constrain(25, 50); // constrained is 30 because number1 is // within the specified range constrained = number2.constrain(25, 50); // constrained is 50 because number2 is // greater than the max. value in the range
Ext.Number
is a one-function extension of the JavaScript Number
object. The only function of Ext.Number
is constrain(min, max)
, which simply uses methods of the Math
JavaScript object to accomplish the range checks on the given number.
constrain: function(min, max) { return Math.min(Math.max(this, min), max); }
Another area where the dynamic nature of JavaScript creates challenges is dates manipulation. This recipe covers formatting, conversion, and range checking for dates.
You can format, convert, and range check dates as show in the following steps:
1. Add the date patterns you will use to format dates in your code:
Date.patterns = { ISO8601Long: "Y-m-d H:i:s", ISO8601Short: "Y-m-d", ShortDate: "n/j/Y", LongDate: "l, F d, Y", FullDateTime: "l, F d, Y g:i:s A", MonthDay: "F d", ShortTime: "g:i A", LongTime: "g:i:s A", SortableDateTime: "Y-m-d\\TH:i:s", UniversalSortableDateTime: "Y-m-d H:i:sO", YearMonth: "F, Y" };
3. Format the date using the patterns:
var ISO8601Long = now.format(Date.patterns.ISO8601Long); //ISO8601Long is similar to 2009-03-05 14:01:45 var ISO8601Short = now.format(Date.patterns.ISO8601Short); //ISO8601Long is similar to 2009-03-05 var ShortDate = now.format(Date.patterns.ShortDate); //ISO8601Long is similar to 3/5/2009 var LongDate = now.format(Date.patterns.LongDate); //ISO8601Long is similar to Thursday, March 05, 2009 var FullDateTime = now.format(Date.patterns.FullDateTime); //ISO8601Long is similar to Thursday, March 05, 2009 2:01:45 PM var MonthDay = now.format(Date.patterns.MonthDay); //ISO8601Long is similar to March 05 var ShortTime = now.format(Date.patterns.ShortTime); //ISO8601Long is similar to 2:01 PM var LongTime = now.format(Date.patterns.LongTime); //ISO8601Long is similar to 2:01:45 PM var SortableDateTime = now.format(Date.patterns.SortableDateTime); //ISO8601Long is similar to 2009-03-05T14:01:45 var UniversalSortableDateTime = now.format(Date.patterns.UniversalSortableDateTime); //ISO8601Long is similar to 2009-03-05 14:01:45-0500 var YearMonth = now.format(Date.patterns.YearMonth); //ISO8601Long is similar to March, 2009
4. Create a variable to hold your parsed date:
var aDate = new Date();
5. Convert a string to a date:
aDate = Date.parseDate("March, 2009", Date.patterns.YearMonth); //aDate = Thu Mar 5 00:00:00 EST 2009 aDate = Date.parseDate("2:01:45 PM", Date.patterns.LongTime); //aDate = Thu Mar 5 14:01:45 EST 2009 aDate = Date.parseDate("2009-03-05", Date.patterns.ISO8601Short); //aDate = Thu Mar 5 00:00:00 EST 2009
6. For range checking, create range limits:
var low = Date.parseDate("July, 2008", Date.patterns.YearMonth); var high = Date.parseDate("July, 2009", Date.patterns.YearMonth);
7. Check whether your date is in the range:
var now = new Date(); var inRange = now.between(low, high); // inRange is true
Ext JS enhances the JavaScript Date
object with the Ext.Date
class, which provides a number of properties and functions that simplify your work with dates.
Regarding date formats, although there isn't a central repository of format patterns in Ext JS, the Ext JS API documentation provides the ones used in the previous example. In order for these formats to become available on the Date
object, they should be copied into any script that is included after Date.js
.
Besides the functions in the examples above, Ext.Date
allows you to do things such as:
Getting the numeric representation of the year
Getting the number of days in the current month
Determining the number of milliseconds between two dates
Getting the date of the first day of the month in which a date resides
Getting the first day of the current month
Getting the offset from GMT of the current date
Getting the date of the last day of the month in which a date resides
Getting the last day of the current month
Getting the month number for the given short/full month name
Getting the short day name for a given day number
Getting the short month name for a given month number
Determining if a date is in a leap year
Naming conflicts and scoping problems increase as applications gain size, and you start to work with multiple code files and modules. In Ext JS, you can resolve these issues by creating namespaces where you can logically organize your code.
The following steps will show how to create a namespace and "hide" local variables in it. These variables will not collide with similarly-named variables that are stored in other namespaces or have global scope:
1. Define a namespace for the variables that are not global:
Ext.namespace('ExtJSCookbook.Samples');
2. Create a local variable and a global variable with the same name:
Ext JSCookbook.Samples.var1 = 'var1 (local)'; // ExtJSCookbook.Samples.var1 is limited to the ExtJSCookbook.Samples namespace var var1 = 'var1 (global)'; // var1 is a global variable
3. Prevent name collisions by putting any custom types inside the namespace you created:
// A custom type inside the Ext JSCookbook.Samples namespace ExtJSCookbook.Samples.Person = function() { return { firstName: '', lastName: '', show: function() { alert(this.firstName + ' ' + this.lastName); } } } var person1 = new Ext JSCookbook.Samples.Person(); person1.firstName = 'Jorge'; person1.lastName = 'Ramon'; person1.show();
Ext.namespace(namespace1, namespace2, namespace3,…)
and its shorthand Ext.ns(…)
allow you to create an arbitrary number of namespaces that you can use to scope variables and classes that are not global. For example, have a look at the following piece of code:
Ext.namespace('MyApplication', 'MyApplication.UI', 'MyApplication.Data', 'MyApplication.Services');
This namespace's definition above is equivalent to the following statements:
MyApplication = {}; MyApplication.UI = {}; MyApplication.Data = {}; MyApplication.Services = {};
You can use Ext JS to enhance the native JavaScript classes by making your own functions appear as if they were members of these classes. This recipe uses the Array
class as an example, explaining how to augment its features by adding a function that will allow an array to copy itself into another array.
Adding a new function to the Array
class is shown in the following steps:
1. Use Ext JS to add a new function,
copyTo(array, startIndex)
, to theArray
class's prototype:Ext.applyIf(Array.prototype, { copyTo: function(dest, startIndex) { l = this.length; for (var i = 0; i < l; i++) { dest[startIndex + i] = this[i]; } } })
2. Create a
source
array and adestination
array in order to test the new function:var source = new Array(); var destination = new Array(); source[0] = '1'; source[1] = '2'; source[2] = '3'; destination[0] = '4'; destination[1] = '5'; destination[2] = '6'; destination[3] = '7'; destination[4] = '8'; destination[5] = '9';
3. Verify that the function is available in the
Array
class:
Ext.applyIf(object1, object2)
copies all of the properties of object2
to object1
, if they do not already exist. This effectively allows you to add new functionality to object1
.
It is possible to add new functions to the Ext JS classes, as well as modify the behavior of the native functions. To illustrate this point, this recipe explains how you can modify the MixedCollection
class so that it features a new function which allows items to be added only when they don't already exist in the collection.
The following example shows how to add a new function to the MixedCollection
class:
1. Define the new
addUnique(key, object)
function within theMixedCollection
class:// Add a function to the MixedCollection Class. Ext.override(Ext.util.MixedCollection, { addUnique: function(key, object) { if (this.indexOf(object) > -1) return; this.add(key, object); } });
2. Now, we can use the new feature here:
Ext.onReady(function() { // Create our enhanced collection. var col = new Ext.util.MixedCollection(); // Confirm that the same value cannot be added twice. col.add("key 1", "value 1"); document.write("Original count: " + col.getCount() + "<br/>"); // Use the added function to make sure duplicates are not //added. col.addUnique("key 2", "value 1"); // Number of elements in the collection is still 1. document.write("Count after trying to add a duplicate: " + col.getCount() + "<br/>"); });
The magic in this recipe is achieved through the use of Ext.override(originalClass, overrides)
. This function adds a list of functions to the prototype of an existing class, replacing any existing methods with the same name:
override: function(origclass, overrides) { if (overrides) { var p = origclass.prototype; Ext.apply(p, overrides); if (Ext.isIE && overrides.toString != origclass.toString) { p.toString = overrides.toString; } } }
Using Ext.override(originalClass, overrides)
, it is also possible to modify the behavior of a class's native functions.
Let's modify the add(key, object)
function of the MixedCollection
class so that only unique values can be added to the collection.
Use Ext.override(originalClass, overrides)
to redefine the add
function as shown in the following code:
Ext.override(Ext.util.MixedCollection, { // The new add function, with the unique value check. add: function(key, o) { // The unique value check. if (this.indexOf(o) > -1) return null; //From this point, the code is the add function's original //code. if (arguments.length == 1) { o = arguments[0]; key = this.getKey(o); } if (typeof key == "undefined" || key === null) { this.length++; this.items.push(o); this.keys.push(null); } else { var old = this.map[key]; if (old) { return this.replace(key, o); } this.length++; this.items.push(o); this.map[key] = o; this.keys.push(key); } this.fireEvent("add", this.length - 1, o, key); return o; } });
Now, we can use the new behavior:
Ext.onReady(function() { // Create our enhanced collection. var col = new Ext.util.MixedCollection(); // Confirm that the same value cannot be added twice. col.add("key 1", "value 1"); document.write("Original count: " + col.getCount() + "<br/>"); // Try to add a duplicate. col.add("key 2", "value 1"); // Number of elements in the collection is still 1. document.write("Count after trying to add a duplicate: " + col.getCount() + "<br/>"); });
You can incorporate features of Ext JS into your own JavaScript classes. For example, the ObservableList
class created in this recipe will use the features of the framework's Ext.util.Observable
class to fire notifications when items are added, removed, or when the list is cleared. The list's interface will be as follows:
add(object):
A function that inserts an item in the list and returns the position into which the item was insertedinsert(index, object):
A function that inserts an item to theList
at the specified indexitem(index):
A function that returns the element at the specified indexremove(object):
A function to remove the first occurrence of a specific objectremoveAt(index):
A function in charge of removing the item at the specified indexeach(fn, scope):
A method that executes the specified function once for every item in the list
Let's proceed to build and test the ObservableList
class as shown in the following steps:
1. Define the
ObservableList
class:Ext.namespace("Samples"); Samples.ObservableList = function() { this.items = []; this.length = 0; // The events our custom class will expose. // The parent Class, Observable, will handle event publishing //for us. this.addEvents("add", "remove", "clear"); Samples.ObservableList.superclass.constructor.call(this); };
2. Inherit the
Observable
class's functionality by establishing our class as an extension ofObservable:
3. Now, implement our class's interface:
Ext.extend(Samples.ObservableList, Ext.util.Observable, { allowFunctions: false, //Adds an item to the list and //returns the position into which the new element was inserted. add: function(o) { this.items.push(o); this.length++; // Fire the add event, returning the position // into which the new element was inserted. pos = this.length - 1; this.fireEvent("add", pos); return pos; }, // Inserts an item to the List at the specified index. insert: function(index, o) { //If the index is outside the list, insert the element at // the end of the list. if (index >= this.length) { return this.add(o); } this.length++; this.items.splice(index, 0, o); this.fireEvent("add", index); }, // Removes all items from the list. clear: function() { this.length = 0; this.items = []; this.fireEvent("clear"); }, // Determines the index of a specific item in the list. indexOf: function(o) { return this.items.indexOf(o); }, // Determines whether the List contains a specific value. contains: function(o) { return this.indexOf(o) != -1; }, // Our enumerator function. Executes the specified function //once for every element in the list. each: function(fn, scope) { var items = [].concat(this.items); for (var i = 0, len = items.length; i < len; i++) { if (fn.call(scope || items[i], items[i], i, len) === false) { break; } } }, custom JavaScript classescustom JavaScript classesbuilding// Removes the item at the specified index. removeAt: function(index) { if (index < this.length && index >= 0) { this.length--; var o = this.items[index]; this.items.splice(index, 1); this.fireEvent("remove", o); } }, // Removes the first occurrence of a specific object. remove: function(o) { this.removeAt(this.indexOf(o)); }, // Return the element at the specified index. item: function(index) { var item = this.items[index]; return item; } }); Samples.ObservableList.prototype.get = Samples.ObservableList.prototype.item;
4. It's time to test our class. Let's do it as follows:
Ext.onReady(function() { list = new Samples.ObservableList(); for (var i = 0; i < 15; i++) { pos = list.add("test " + i); } // Add handlers for the list's events. list.on("remove", function(o) { alert("Removed: " + o); }); list.on("add", function(index) { alert("Added at position: " + index); }); list.on("clear", function() { alert("List length is: " + list.length); }); document.write("List length is " + list.length + "<br/>"); // Insert an additional element and //check that the add event fires. var index = 2; list.insert(index, "A new item"); document.write("Just inserted: " + list.item(index) + "<br/>"); document.write("List length is: " + list.length + "<br/>"); // Remove an item an verify that the remove event fires. index = 5; document.write("Removing item at position" + index + "<br/>"); list.removeAt(index); document.write("List length after removal: " + list.length + "<br/>"); document.write("Clearing list...<br/>"); // Remove all items and check that the clear event fires. list.clear(); document.write("List length after clear: " + list.length + "<br/>"); });
A powerful mechanism for extending classes is provided by Ext JS with Ext.extend(subclass, superclass, [overrides])
. This function allows you to extend one class with another class and, optionally, to override the superclass's members.
Our example first defines the custom ObservableList
class and passes its events to the parent, Ext.Observable
. It then uses Ext.extend(subclass, superclass, [overrides])
not only to establish that the custom class implements Ext.Observable
, but also to define the bulk of its own interface—the add(object), insert(index, object), clear(), indexOf(object), each(fn, scope), removeAt(index), remove(object)
, and item(index)
functions.
Multiple versions of this approach are used by Ext JS to define its own class hierarchy. I encourage you to examine the source code of the library in order to get familiar with them.
The Adding features to the Ext JS classes recipe, covered earlier in this chapter, explains how to add new functions to the Ext JS classes
The A custom column layout recipe from Chapter 2 is an example of how to extend the native Ext JS layouts
The A three-panel application layout with one line of code recipe from Chapter 2 shows how to build a reusable Ext JS component that encapsulates a three-panel layout