Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Saturday, March 1, 2008

Using For In Loops Safely

Many of the JavaScript Frameworks and Libraries used today have the bad habit of attaching methods to the Prototype of Array and Object. While, in theory it would be really nice to be able to extend these Object, in practice, doing so breaks “For … in” loops. For example, if you include Json.js” in your project, you have the power to easily work with JSON objects. However, it attaches “toJSONString” method to “Array.prototype”. So when you use the “For … In” loop on any array, one of the keys will be the bogus key “toJSONString”.

This is frustrating as “For … In” loops are the only way, in JavaScript, that you can iterate on the keys of an Object and associative Arrays. Most Frameworks that modify these objects also add functions that allow you to work around this limitation, but then you become dependent on them to do any Array iterations. Douglas Crockford (see JSLint) and other JavaScript guru’s recommend keeping a list of keys that you want to ignore and always filtering your “For … In” loop with it. This is one way to fix the problem, but what does one do when they don’t know the keys or Array/Object prototypes change dynamically as your JavaScript executes, such as when you use YUI to extend Object and it attaches the “constructor” variable to Object’s prototype.

Often you need the ability to dynamically determine what keys to filter before each “For … In” loop. To accomplish this, I have developed the following function:

Example 1: GetKeysToIgnore

var getKeysToIgnore = function() { var tarr = [], // empty array tobj = {}, // empty object tdom = document.getElementsByTagName(”body”), // empty nodelist keys = {’item’: true, ‘length’: true};// default keys to ignore // iterate on the native array object for (var k in tarr) {keys[k] = true;} d // iterate on the native object object for (var l in tobj) {keys[l] = true;} // iterate on the nodelist, but don’t ignore indices for (var m in tdom) { if (isType(parseInt(m, 10), ‘number’)) {continue;} keys[m] = true; } return keys; };

This method creates an instance of each of the three type of iterable JavaScript objects: Array, Object, NodeList (especially important of Safari). Since, these objects are empty, they contain only the keys for elements that have been attached to their Prototypes. Therefore, when we iterate on it we can create a hash Object of all these keys. The default list is just some keys that you want to reserve for your project, which may or may not always be attached to one of these 3 objects.

Call “getKeysToIgnore” before any “For … In” loop and then filter these keys out:

Example 2: Filtering

var test = { key1: 1, key2: “1223′, key3: new Date() }; var ikeys = getKeysToIgnore(); for (var key in test) { // key is not in our ignore hash if (! ikeys[key]) { // put your logic here } }

Using this technique, you could modify the “batch” Function of Core.js to use this technique, thereby supporting both Array and Object iteration:

Example 3: Batch

Core.batch = function(o, fn) { var args = Array.prototype.slice.apply(arguments, [2]); var ikeys = getKeysToIgnore(); args.unshift(null, null); // iterate on the items, executing the function, and stoping when function returns true or visited all elements for (var key in o) { // key is not in our ignore hash if (! ikeys[key]) { args[0] = o[i]; args[1] = i; var rs = fn.apply(this, args); if (rs) { return rs; // allows the batch function to return a found result } } } };

While this is a little more powerful than the Array based “batch” Function, it is a little slower because the ikeys array must be created each time before batching. Something to think about is how to improve/cache getKeysToIgnore better and increase the performance of this Function.

posted by Matt Snider at 12:46 pm  

Sunday, January 27, 2008

Date Functions

Like String, the native JavaScript Date object also does not have as many helper functions as you might like. Using a similar approach as we did with String, we can attach additional functionality to Date.

Example 1: Date Augmentation

YAHOO.lang.augmentObject(Date, { /** * Date constant for full month names * * @property MONTHS * @type string */ MONTHS: [’January’,'February’,'March’,'April’,'May’,'June’,'July’,'August’,'September’,'October’,'November’,'December’], /** * Returns a date object from the string; expects ‘MonthName, DayNr Year Hrs:Min:Sec’, may not work properly on other strings in all browsers * * @method getDate * @param s {string} the date as a string * @return {date} a date object, defined by the passed string * @static */ getDate: function(s) { var d = new Date(); d.setTime(Date.parse(s)); return d; } }, true);

I try to only attach constants and static methods to the JavaScript Native Object (Date) itself. Dynamic objects/methods should be attached to the prototype of the Object (Date.prototype) so that all instantiations of that Date have access to them. In example 1, we attach a constant array (the full American month names) and one method ‘getDate’ (retrieves the date from a string). The constants can be used to retrieve the month name for a date, a feature that is missing from JavaScript, most likely because of localization concerns. ‘getDate’ is a nice shortcut, when you have a date string (say from an AJAX request) and need to convert it to a Date object for manipulation.

Example 2: Date.prototype Augmentation

// Extending the Date.prototype Object YAHOO.lang.augmentObject(Date.prototype, { /** * Retrieves the name of the month * * @method getMonthName * @return {string} the month name * @public */ getMonthName: function() { return Date.MONTHS[this.getMonth()]; }, /** * Retrieves the abbreviated name of the month * * @method getMonthNameAbbr * @return {string} the abbreviated month name * @public */ getMonthNameAbbr: function() { return this.getMonthName().substr(0,3); }, /** * Converts the JavaScript Date into a date string. Recognizes the following format characters: y = year, m = month, d = day of month, h = hour, i = minute, s = second * * @method toDateString * @param (string} format OPTIONAL: The string format to convert the JavaScript Date into (ie. ‘m/d/y’ or ‘m. d, y’); default is ‘m/d/y’ * @param {boolean} showZeros OPTIONAL: Forces trailing zeros, so 9/1/2006 becomes 09/01/2006 * @param {Mixed} useMonthName OPTIONAL: string or boolean, use the month name instead of the digit, (’abbr’ uses the short name) * @return {string} the JavaScript Date as a string * @public */ toDateString: function(format, showZeros, useMonthName) { if (! isType(format, ’string’)) {format = ‘m/d/y’;} format = format.toLowerCase(); // cast all values to strings var day = ” + this.getDate(), month = ” + (this.getMonth() + 1), hour = ” + this.getHours(), minute = ” + this.getMinutes(), second = ” + this.getSeconds(), year = ” + this.getFullYear(); // pad leading zeros if (showZeros) { if (1 === day.length) {day = ‘0′ + day;} if (1 === month.length) {month = ‘0′ + month;} if (1 === hour.length) {hour = ‘0′ + hour;} if (1 === minute.length) {minute = ‘0′ + minute;} if (1 === second.length) {second = ‘0′ + second;} } // use month name if (useMonthName) { month = (isType(useMonthName, ’string’) && ‘abbr’ === useMonthName.toLowerCase())? this.getMonthNameAbbr(): this.getMonthName(); } return format.replace(’y', year) .replace(’d', day) .replace(’h', hour) .replace(’i', minute) .replace(’s’, second) .replace(’m', month); // do month last as some months contain reserved letters }, /** * Converts JavaScript Date into a MySQL dateTime string ‘1969-12-31 00:00:00″ * * @method toTimeString * @return {string} the JavaScript Date as a MySQL time string * @public */ toTimeString: function() { return this.toDateString(’y-m-d h:i:s’, true); } }, true);

These are the few methods that I find immensely helpful for almost any project. The first two return either the month name or the abbreviated month name of the date. I first used this for a content management system and often find a need for them. ‘toDateString’ is one of my favorite functions and it converts a Date object into most useful date strings. I was writing a lot of PHP at the time and modeled it after the ‘date’ Function in PHP (it needs some work as it was written ages ago). Ideally, I could find the PHP source code and model my JavaScript objects after it, but this method has still been very useful anytime I needed to insert a Date from JavaScript into the page. The last method ‘toTimeString’ allows you to pre-format your MySQL date times, before sending them to the back-end… most languages will do these conversions for you, but I have occasionally found it useful (remember to always validate any parameter on the back-end before using one in a database query).

If you are looking for more functionality, YUI has a huge collection of date math static function in Calendar that you could also attach to the “Date.prototype”.

posted by Matt Snider at 11:39 pm  

Friday, January 18, 2008

String Functions

Strings in JavaScript do not have as many helper functions as one might like. As a result you will probably need to write a collection of methods yourself. You can either create a static utility Function or extend the “String.prototype”. String is one of the few objects that it is fine to extend the prototype object of, as you do not need to use “for … in” on strings.

For our discussion today, I will be using YAHOO.lang.augmentObject to augment the String.prototype, which is a simple method that adds the values of the object in parameter2 to the object in parameter1. For more information, see YUI yahoo.js.

Next we need to consider what functionality is most needed and missing: word capitalization, stripping characters (alpha, numbers, etc…), stripping tags (script, or all html tags), and trimming white spaces. Obviously, you may want a lot more, but this is these are the ones I use most:

Example 1: Extending Strings.prototype

YAHOO.lang.augmentObject(String.prototype, { /** * Capitolize the first letter of every word; ucfirst, ensures that all non-first letters are lower-case * * @method capitalize * @param ucfirst {boolean} OPTIONAL: when truthy, converts non-first letters to lower-case * @return {string} the converted string * @static */ capitalize: function(ucfirst) { var words = this.split(/\b/g), rs = []; Core.batch(words, function(w, i) { if (w.trim()) { rs[i] = w.charAt(0).toUpperCase() + (ucfirst? w.substring(1).toLowerCase(): w.substring(1)); } }); return rs.join(’ ‘); }, /** * Checks if a string contains any of the strings in the arguement set * * @method contains * @param argument {string} as many strings you want to test * @return {boolean} true, if string contains any of the arguements * @static */ contains: function() { var hasValue = false; Core.batch(arguments, function(arg) { hasValue = -1 < str.indexOf(arg); // terminates iteration if this becomes true return hasValue; }); return hasValue; }, /** * Removes the rx pattern from the string * * @method remove * @param rx {regex} a regex to find characters to remove * @public */ remove: function(rx) { return this.replace(rx, ''); }, /** * Remove all non-alpha characters;space ok * * @method stripNonAlpha * @public */ stripNonAlpha: function() { return this.remove(/[^A-Za-z ]+/g); }, /** * Remove all non-alpha-numeric characters; space ok * * @method stripNonAlphaNumeric * @public */ stripNonAlphaNumeric: function() { return this.remove(/[^A-Za-z0-9 ]+/g); }, /** * Removes non-numeric characters, except minus and decimal * * @method stripNonNumeric * @public */ stripNonNumeric: function() { return this.remove(/[^0-9\-\.]/g); }, /** * Remove all characters that are 0-9 * * @method stripNumeric * @public */ stripNumeric: function() { return this.remove(/[0-9]/g); }, /** * HTML script tags from the string * * @method stripScripts * @public */ stripScripts: function() { return this.remove(new RegExp("(?: )((\n|\r|.)*?)(?:<\/script>)", "img")); }, /** * HTML tags from the string * * @method stripTags * @public */ stripTags: function() { return this.remove(/<\/?[^>]+>/gi); }, /** * Replaces the white spaces at the front and end of the string * OPTIMIZED: http://blog.stevenlevithan.com/archives/faster-trim-javascript * * @method trim * @public */ trim: function() { return this.remove(/^\s\s*/).remove(/\s\s*$/); } });

Some of these methods I have put a lot of thought into, such as trim, which I use frequently, so I ensure that I have the most efficient regex. As you can see, many string manipulations can/should be handled by regex, so it is a good idea to understand regex (hopefully you do). Steven’s Blog is a great place for your regex questions, especially when looking for the best way to write an expression.

Most of these are pretty easy to understand, especially if you look at my comments (feel free to leave a comment if you have questions). The best part is, because you have extended “String.prototype”, every String throughout your entire project will be able to use them. Often times, you will find tasks that are specific toward the current project, but maybe not relevant to every project. For example, Mint.com is a financial site where I often need to search for numbers and/or currency, so I have special methods for that project. It is best to keep these in a separate file and bring them into your project as necessary.

posted by Matt Snider at 5:28 pm  

Tuesday, January 15, 2008

XJSON Objects Continued

Today we are concluding our discussion of the xJSON object package. You should have read the following two articles and know what Model.js does, and understand the “extend” function with super class inheritance:

Improving Extend With Super
XJSON Objects

Download the most up-to-date xJSON package.

There are 4 JSON models in this package: JsonArray, JsonObject, XJsonArray, and XJsonObject. As a recap, the xJSON Objects are used to improve the performance of large, repeating data sets. For example, if you have the JSON array:

Example 1: JSON Array

[ {id:1, name: ‘name1′}, {id:2, name: ‘name2′}, {id:3, name: ‘name3′}, {id:4, name: ‘name4′}, {id:5, name: ‘name5′}, … ]

Each object in the array contains key/value pairs, with the keys repeated in each object. You can reduce the size of such a message by using a compression technique, known as xJSON:

Example 2: xJSON Array

{ scheme: [’id’, ‘name’], set: [ [1, ‘name1′], [2, ‘name2′], [3, ‘name3′], [4, ‘name4′], [5, ‘name5′], … ] … }

These xJSON Classes should produce similar structures (at least publicly) to their JSON counterparts. This way, you don’t care if you have a JsonArray or XJsonArray, only if you have an array versus an object. For the most part, XJsonObject will only be instantiated from XJsonArray, as you are required to pass in a schema, which requires an extra variable that normal objects will not have. One of the trickiest parts is that a JSON object could contain nested JSON objects/arrays, which we need to detect and instantiate on demand. For this purpose I wrote the Model method “XJson” which detects whether the passed object is a JSON object/array or not and returns the appropriate class, null, or simply the variable itself. Therefore, I can use this class anytime I want JSON object, whether from a new JSON object or when evaluating the values inside of a JSON object.

Example 3: XJson

Core.Model.XJson = function(o) { if (! o) { // test 1 return null; } else if (isType(o, ‘array’) && isType(o[0], ‘object’)) { // test 2 return new Core.Model.JsonArray(o); } else if (isType(o, ‘object’) && ! isType(o, ‘array’)) { // test 3 return (o.scheme)? new Core.Model.XJsonArray(o): new Core.Model.JsonObject(o); } else { // test 4 return o; } };

This Function examines the passed object, returning null if no parameter is passed (ie. there is no data) and the parameter itself when it does not match JSON criteria. The second test checks to see if the parameter is an array and that the first member is an object, which generally means it is a JsonArray (this is a weak assumption, please comment if you have a better idea). The third check tests if the parameter is an object, but not an array primitive. Then if it has a scheme value, the data object is assumed to an XJsonArray, otherwise, it is a JsonObject (the assumption here is that you never use ’scheme’ as a key for a non-xJSON object).

Event though we already discussed JsonObject and JsonArray, I want to revisit them today, as they have changed quite a bit and will form the basis on which we can discuss the xJSON objects.

Example 4: JSON Object

Core.Model.JsonObject = function(json) { this.update(json); }; Core.extend(Core.Model.JsonObject, Core.Model.Model, { /** * The number of values in the json object * * @property length * @type int */ length: 0, /** * Updates the object to use the passed data set * * @method update * @param json {array} jsonobject object * @public */ update: function(json) { // validation if (! isType(json, ‘object’)) {throw(’JsonObject - Invalid data passed into Update’);} this.parent.update.call(this, json); // private variables var data = {}, that = this, n = 0; var fx = function(key, o) { var ckey = key.replace(/^is/, ”).capitalize(); data[key] = Core.Model.XJson(o); that[(isType(o, ‘boolean’)? ‘is’: ‘get’) + ckey] = function() {return data[key];}; that[’set’ + ckey] = function(o) { if ($type(o) !== $type(data[key])) {throw(’JSONObject - invalid object passed into setter for: ‘ + key);} data[key] = o; }; }; // iterate through the JSON object keys for (var key in json) { fx(key, json[key]); n += 1; }; // update public variables this.length = n; } });

The first thing to notice is that this object and all the future object will extend the Model object. They will also have a public value “length”, which for the (x)JsonObject types will be the number of key/value pairs and for (x)JsonArray types will be the length of the JSON array. Each object also has an update method that first validates the data passed to it, then calls the update method of the super class “Model”.

For JsonObject Class we first create a local “data” Object and the Function “fx” to create a closure around the “data” variable. Next we iterate through all the keys in the JSON Object and fill the “data” variable with the evaluated values. Lastly, we create the setter and getter function for each key/value pair, which set or update the value in the “data” variable. The only caveat is that booleans values get an “is” instead of a “get” in front of them. Booleans should always start with question verbs (such as “is”, “has”, etc.). I find that I use “is” 90% of the time, so I just went ahead and now use “is” 100% of the time. You can remove the ternary statement and just preface the methods with “get” if you do not structure your booleans this way. Lastly, when using a setter method, we check to ensure that both the object we are replacing and the object we are passing at least have the same type, which should help reduce errors.

Example 5: JSON Array

Core.Model.JsonArray = function(json) { this.update(json); }; Core.extend(Core.Model.JsonArray, Core.Model.Model, { /** * The number of values in the json array * * @property length * @type int */ length: 0, /** * Execute function ‘fn’ on all elements in collection * * @method batch * @param fn {function} the function to execute * @public */ batch: function(fn) { Core.batch(this.data, function(o, i, scope) { fn(scope.get(i), i); }, this); }, /** * Retrieve the element at ‘i’ from the collection; lazy definition to allow for converting objects on demand * * @method get * @param i {int} index in the data * @public */ get: function(i) { var that = this, data = []; this.get = function(i) { // commented because this is very strict and don’t always want to use, sometimes returning undefined is ok //if (! that.data[i]) {throw(’JsonArray - Invalid index passed into Get: ‘ + i);} if (! data[i] && that.data[i]) { data[i] = Core.Model.XJson(that.data[i]); } return data[i]; }; return this.get(i); }, /** * Inserts another value into the data structure * * @method push * @param o {array} new row of data * @public */ push: function(o) { if (! o) {throw(’JsonArray - Invalid Object passed into Push’);} this.data.push(o); this.length = this.data.length; }, /** * Updates the object to use the passed data set * * @method update * @param json {array} jsonarray object * @public */ update: function(json) { // validation if (! isType(json, ‘array’)) {throw(’JSONArray - Invalid JSON Array Object passed into Update’);} this.parent.update.call(this, json); // update public variables this.length = json.length; } });

First, notice that this Class has a similar structure to that of JsonObject (all of the JSON object types do). Now, a JSON array should just be a collection of JSON objects, and we have made that assumption here. The get method, uses lazy initialization to create an internal “data” variable, which will be updated each time an index “i” is requested. This prevents us from having to initialize each JsonObject until it is requested, thereby making this an inexpensive transformation. If you instantiated each JsonObject in the update method, you run the risk of monopolizing the client’s CPU when working with medium to large JSON arrays. The “batch” function is a handle shortcut that calls “get” on all indexes, then executes the passed Function on the result. Lastly, I added “push” for convenience because I occasionally find myself adding elements to the object. A “pop” or “remove” method is probably useful, but I have not had a need for one yet.

Example 6: xJSON Object

Core.Model.XJsonObject = function(scheme, json) { if (! isType(scheme, ‘array’)) {throw(’XJsonObject - Invalid scheme passed into Constructor’);} this.scheme = scheme; this.update(json); }; Core.extend(Core.Model.XJsonObject, Core.Model.Model, { /** * The number of values in the xjson object * * @property length * @type int */ length: 0, /** * The object schema * * @property scheme * @type array */ scheme: [], /** * Updates the object to use the passed data set * * @method update * @param json {object} XJsonObject object * @public */ update: function(json) { // validation if (! isType(json, ‘array’)) {throw(’XJsonObject - Invalid data passed into Update’);} if (json.length !== this.scheme.length) {throw(’XJsonObject - Invalid data (does not match scheme) passed into Update’);} this.parent.update.call(this, json); // private variables var data = this.data, that = this; // update public variables this.length = data.length; Core.batch(this.scheme, function(key, i, json) { var o = json[i], ckey = key.replace(/^is/, ”).capitalize(); data[i] = Core.Model.XJson(o); that[(isType(o, ‘boolean’)? ‘is’: ‘get’) + ckey] = function() {return data[i];}; that[’set’ + ckey] = function(o) { if ($type(o) !== $type(data[key])) {throw(’XJSONObject - invalid object passed into setter for: ‘ + key);} data[i] = o; }; }, data); } });

XJsonObject is actually very similar to JsonObject, except instead of having to iterate on the key/value pairs of a JavaScript Object, you just iterate through an array of schema and a data array. The scheme is stored in a global array and except in rare circumstances when you have a constant schema that you use in several places, this method will only be called by XJsonArray.

Example 7: xJSON Array

Core.Model.XJsonArray = function(json) { this.update(json); }; Core.extend(Core.Model.XJsonArray, Core.Model.Model, { /** * The number of values in the xjson array * * @property length * @type int */ length: 0, /** * The object schema * * @property scheme * @type array */ scheme: [], /** * Execute function ‘fn’ on all elements in collection * * @method batch * @param fn {function} the function to execute * @public */ batch: function(fn) { Core.batch(this.data, function(o, i, scope) { fn(scope.get(i), i); }, this); }, /** * Retrieve the element at ‘i’ from the collection; lazy definition to allow for converting objects on demand * * @method get * @param i {int} index in the data * @public */ get: function(i) { var that = this, data = []; // internally redeclaring to scope the data structure, which will contain already converted objects this.get = function(i) { // commented because this is very strict and don’t always want to use, sometimes returning undefined is ok //if (! that.data[i]) {throw(’XJsonArray - Invalid index passed into Get: ‘ + i);} if (! data[i]) { var o = that.data[i]; // if array, then it is an XJsonObject if (isType(o, ‘array’) && o.length === that.scheme.length) { data[i] = new Core.Model.XJsonObject(that.scheme, o); } // otherwise, use generic method else { data[i] = Core.Model.XJson(o); } } return data[i]; }; return this.get(i); }, /** * Inserts another value into the data structure * * @method push * @param o {array} new row of data * @public */ push: function(o) { if (! (isType(o, ‘array’) && o.length === this.scheme.length)) {throw(’XJsonArray - Invalid data passed into Push’);} this.data.push(o); this.length = this.data.length; }, /** * Updates the object to use the passed data set * * @method update * @param json {object} xjsonarray object * @public */ update: function(json) { // validation if (! (json && isType(json, ‘object’) && isType(json.scheme, ‘array’) && isType(json.set, ‘array’))) {throw(’XJsonArray - Invalid data passed into Update’);} this.parent.update.call(this, json.set); // update public variables this.length = json.length; this.scheme = json.scheme; } });

This Class is very similar to JsonArray, but with a public “scheme” array as with XJsonObject. When the “update” Function calls the super class “update”, it passes in the “set” parameter, so the public “data” variable is an array like that of JsonArray. The “get” Function is only different from JsonArray, in that it first looks to see if the value at “i” is an xJSON object before it defers to other objects.

Obviously, this these Classes are fairly complex in their implementation, but they are easy to use. Simply, call “Mint.Model.XJson(yourJsonObject)” and the results will be evaluated and easy to use.

Download “xJson.js”

See My Simple Test Page

posted by Matt Snider at 10:15 pm  

Saturday, January 12, 2008

xJson Objects

Alright everyone, I have finished the xJSON object suite, or at least a decent first pass. This was a bigger project than I expected and I apologize for dragging it out over the past couple weeks. It seemed like every time I was about to finish, I would uncover a corner case that would break an entire second of the project. Anyway, I was away over the passed weekend and had to do a lot work in the evening last week, and missed your Tuesday post as well. I hope to be better about getting two articles written a week. I am also open to guest posts, if you are interest just leave a comment on any article.

Anyway, I have written the following objects: JsonObject, JsonArray, XJsonObject, and XJsonArray. Here they are:

/** * The JsonObject class manages a JSON object by creating getter and setters * * @namespace Core.Model * @class JsonObject * @dependencies library */ Core.Model.JsonObject = function(json) { this.update(json); }; Core.extend(Core.Model.JsonObject, Core.Model.Model, { /** * The number of values in the json object * * @property length * @type int */ length: 0, /** * Updates the object to use the passed data set * * @method update * @param json {array} jsonobject object * @public */ update: function(json) { // validation if (! isType(json, ‘object’)) {throw(’JsonObject - Invalid data passed into Update’);} this.parent.update.call(this, json); // private variables var data = {}, that = this, n = 0; var fx = function(key, o) { var ckey = capitalize(key); data[key] = Core.Model.XJson(o); that[(isType(o, ‘boolean’) && -1 !== key.indexOf(’is’))? key: ‘get’ + ckey] = function() {return data[key];}; that[’set’ + ckey] = function(o) { if ($type(o) !== $type(data[key])) {throw(’JSONObject - invalid object passed into setter for: ‘ + key);} data[key] = o; }; }; // iterate through the JSON object keys for (var key in json) { fx(key, json[key]); n += 1; }; // update public variables this.length = n; } }); /** * The JsonArray class manages … * * @namespace Core.Model * @class JsonArray * @dependencies library */ Core.Model.JsonArray = function(json) { this.update(json); }; Core.extend(Core.Model.JsonArray, Core.Model.Model, { /** * Boolean used to inform the ‘get’ method to clean the cache * * @property clean * @type boolean */ clean: false, /** * The number of values in the json array * * @property length * @type int */ length: 0, /** * Execute function ‘fn’ on all elements in collection * * @method batch * @param fn {function} the function to execute * @public */ batch: function(fn) { Core.batch(this.data, function(o, i, scope) { fn(scope.get(i), i); }, this); }, /** * Retrieve the element at ‘i’ from the collection; lazy definition to allow for converting objects on demand * * @method get * @param i {int} index in the data * @public */ get: function(i) { var that = this, data = []; this.get = function(i) { // commented because this is very strict and don’t always want to use, sometimes returning undefined is ok //if (! that.data[i]) {throw(’JsonArray - Invalid index passed into Get: ‘ + i);} // purges cached data on an update if (that.clean) { data = []; that.clean = false; } if (! data[i] && that.data[i]) { data[i] = Core.Model.XJson(that.data[i]); } return data[i]; }; return this.get(i); }, /** * Inserts another value into the data structure * * @method push * @param o {array} new row of data * @public */ push: function(o) { if (! o) {throw(’JsonArray - Invalid Object passed into Push’);} this.data.push(o); this.length = this.data.length; }, /** * Updates the object to use the passed data set * * @method update * @param json {array} jsonarray object * @public */ update: function(json) { // validation if (! isType(json, ‘array’)) {throw(’JSONArray - Invalid JSON Array Object passed into Update’);} this.parent.update.call(this, json); this.clean = true; // update public variables this.length = json.length; } }); /** * The XJsonObject class manages … * * @namespace Core.Model * @class XJsonObject * @dependencies library */ Core.Model.XJsonObject = function(scheme, json) { if (! isType(scheme, ‘array’)) {throw(’XJsonObject - Invalid scheme passed into Constructor’);} this.scheme = scheme; this.update(json); }; Core.extend(Core.Model.XJsonObject, Core.Model.Model, { /** * The number of values in the xjson object * * @property length * @type int */ length: 0, /** * The object schema * * @property scheme * @type array */ scheme: [], /** * Updates the object to use the passed data set * * @method update * @param json {object} XJsonObject object * @public */ update: function(json) { // validation if (! isType(json, ‘array’)) {throw(’XJsonObject - Invalid data passed into Update’);} if (json.length !== this.scheme.length) {throw(’XJsonObject - Invalid data (does not match scheme) passed into Update’);} this.parent.update.call(this, json); // private variables var data = this.data, that = this; // update public variables this.length = data.length; Core.batch(this.scheme, function(key, i, json) { var o = json[i], ckey = capitalize(key); data[i] = Core.Model.XJson(o); that[(isType(o, ‘boolean’) && -1 !== key.indexOf(’is’))? key: ‘get’ + ckey] = function() {return data[i];}; that[’set’ + ckey] = function(o) { if ($type(o) !== $type(data[key])) {throw(’XJSONObject - invalid object passed into setter for: ‘ + key);} data[i] = o; }; }, data); } }); /** * The XJsonArray class manages … * * @namespace Core.Model * @class XJsonArray * @dependencies library */ Core.Model.XJsonArray = function(json) { this.update(json); }; Core.extend(Core.Model.XJsonArray, Core.Model.Model, { /** * Boolean used to inform the ‘get’ method to clean the cache * * @property clean * @type boolean */ clean: false, /** * The number of values in the xjson array * * @property length * @type int */ length: 0, /** * The object schema * * @property scheme * @type array */ scheme: [], /** * Execute function ‘fn’ on all elements in collection * * @method batch * @param fn {function} the function to execute * @public */ batch: function(fn) { Core.batch(this.data, function(o, i, scope) { fn(scope.get(i), i); }, this); }, /** * Retrieve the element at ‘i’ from the collection; lazy definition to allow for converting objects on demand * * @method get * @param i {int} index in the data * @public */ get: function(i) { var that = this, data = []; // internally redeclaring to scope the data structure, which will contain already converted objects this.get = function(i) { // commented because this is very strict and don’t always want to use, sometimes returning undefined is ok //if (! that.data[i]) {throw(’XJsonArray - Invalid index passed into Get: ‘ + i);} // purges cached data on an update if (that.clean) { data = []; that.clean = false; } if (! data[i]) { var o = that.data[i]; // if array, then it is an XJsonObject if (isType(o, ‘array’)) { data[i] = new Core.Model.XJsonObject(that.scheme, o); } // otherwise, use generic method else { data[i] = Core.Model.XJson(o); } } return data[i]; }; return this.get(i); }, /** * Inserts another value into the data structure * * @method push * @param o {array} new row of data * @public */ push: function(o) { if (! (isType(o, ‘array’) && o.length === this.scheme.length)) {throw(’XJsonArray - Invalid data passed into Push’);} this.data.push(o); this.length = this.data.length; }, /** * Updates the object to use the passed data set * * @method update * @param json {object} xjsonarray object * @public */ update: function(json) { // validation if (! (json && isType(json, ‘object’) && isType(json.scheme, ‘array’) && isType(json.set, ‘array’))) {throw(’XJsonArray - Invalid data passed into Update’);} this.parent.update.call(this, json.set); this.clean = true; // update public variables this.length = json.length; this.scheme = json.scheme; } }); /** * The XJson class manages … * * @namespace Core.Model * @class XJson * @dependencies library */ Core.Model.XJson = function(o) { if (! o) { return null; } else if (isType(o, ‘array’) && (isType(o[0], ‘array’) || ! o.length)) { return new Core.Model.JsonArray(o); } else if (isType(o, ‘object’) && ! isType(o, ‘array’)) { return (o.scheme)? new Core.Model.XJsonArray(o): new Core.Model.JsonObject(o); } else { return o; } };

This is a lot of code to discuss, which I do not have time for today (Tuesday’s article will have a detailed explanation). I have introduced a String manipulation method ‘capitalize’, which converts strings like ‘mattsnider’ to ‘Mattsnider’. This way all getter and setter methods have the appropriate capitalization. This method is from a small package of string functions, which we will discuss after these JSON objects. For now, let me know your thoughts about these objects and take a look at the test page.

posted by Matt Snider at 12:01 pm  

Saturday, January 5, 2008

Improving Extend With Super

Alright, it looks like I will need to eat my words about not needing super class support in my extend Function, see Object Extension. As many of you know, I have been working on Objects to represent XJson in JavaScript. One of the changes from my original JSONObject Model is that I now use “Core.extend” to extend a core Model object. This object has an onChange event and an update function. Here is what I have so far:

Example 1: Model.js

Core.Model.Model = function(o) { this.update(o); }; Core.Model.Model.prototype = { addChangeListener: function(fx) { var evt = new YAHOO.util.CustomEvent(’model’ + Math.random(), this, false, YAHOO.util.CustomEvent.FLAT), that = this; this.addChangeListener = function(fx) {evt.subscribe(fx, that);}; this.addChangeListener(fx); }, update: function(o) { if (! o) {throw(’Model - Invalid object passed into Update’);} this.data = o; } };

Model does little right now, but what I realized is that any Object that extends from it and overrides the update method, should first call the update method of Model. This way the passed Object is always evaluated and “this.data” is always created. In the future, I may also find other important values to attach. In order to make this call, I needed access to the super class of an extending Object.

As I used YUI as a model for my extend function, it is also a good model for how to do super classes. OOP in JS, Part 2 is a useful article as well. Here is a revamped version of extend to support super classes:

Example 2: Superclasses

var extend = function(subc, superc, overrides) { if (! superc || ! subc) { throw new Error(’Core.extend failed, please check that all dependencies are included.’); } var F = function() {}; F.prototype = superc.prototype; subc.prototype = new F(); subc.prototype.constructor = subc; subc.prototype.parent = superc.prototype; if (overrides) { for (var i in overrides) { subc.prototype[i] = overrides[i]; } } };

I have taken a very similar approach to YUI, however, there are two notable changes: 1) I use ‘parent’ as the super class reference, instead of ’superclass’; as a DOMScripter parent is a familiar naming convention and it feels more intuitive, 2) when the super class is ‘Object’ nothing is attached to the prototype of ‘Object’; I hate attaching anything to the prototype of Objects and Arrays as it messes up ‘for … in’ statements.

So, lastly, here is an example of how you call the super class from the child class:

Example 3: Calling the Super Class

Core.Model.XJsonArray = function(data) { this.update(data); }; Core.extend(Core.Model.XJsonArray, Core.Model.Model, { length: 0, … /* More to come in my Tuesday Article */ /** * Updates the object to use the passed data set * * @method update * @param data {object} xjsonarray object * @public */ update: function(data) { if (! (data && isType(data, ‘object’) && isType(data.scheme, ‘array’) && isType(data.set, ‘array’))) {throw(’XJsonArray - Invalid data passed into Update’);} this.parent.update.call(this, data.set); this.length = this.data.length; this.scheme = data.scheme; } });
posted by Matt Snider at 1:35 am  

Tuesday, December 18, 2007

Semantic HTML for a Simple 5-Star Rating System

I am going to deviate this week from the Json Object discussion, as I have not had any time to work on it. Instead, I wanted to share a technique you can use to write semantic HTML for a rating system. If you have ever used a site like Amazon.com or Yelp.com, then you are familiar with the star rating system (0 stars = worst rating, 5 stars = best rating). However, most rating systems do not do a good job when browser font-size or zoom changes, making them inaccessible. Also, most tend to use a different graphic for each rating, requiring up to 10 graphics to load. The technique I worked on this week, not only scales well, but only requires 2 graphics, and you can change the color of your ratings with simple CSS.

Step 1: Create 2 Images

First decide which size you want the image to normally be (for me this was 80px wide by 16px high); this is a 1 to 5 ratio, because I am going to use a 5 point rating system, which will play an important role for the size of each rating point symbol. Use a program like Photoshop to create an image with these dimensions and a transparent background. In a separate window create an 16 x 16 (or 1/5 of the default width you chose) transparent image and draw in the symbol you want to use for the rating point (I used black for the color of my symbol, but it is up to you). I used a star, but it doesn’t matter what you choose so long as the center is transparent and the symbol fills the dimensions. All spaces outside the symbol should be the background color of your page (this is white for my example page). Here is an example of my star:

Example 1: Star Graphics

Single star graphic

Single star graphic

The first graphic shows what “star.gif” looks like by default. The second show, the difference when a background color is applied. I choose red, but you can change it to whatever hex you prefer.

Next copy your 16×16 symbol image 5 times into the original image you created. You should have something that looks like the following:

Example 2: Rating Graphics

5 star graphic

5 star graphic

Example 2 is the first image we will need. I made the background yellow to show how easy it is to change. The second image we need is a background masking image used to cover the part of the rating inverse (if the rating is 1.5, then this will cover the other 3.5 of the symbols). The background should be the color of the site background (white for me), and the left-most pixels should all be the color of the symbols (black for me). This image should be taller and wider than the rating image, because it will need to scale as the browser does. I usually do 2x the original dimensions, which supports up to a +4 font-size. Therefore, the dimensions of my “ratingbg.gif” is 160×32px:

Example 3: Rating Background Graphics

5 star graphic

The gray border is only here to show contrast so you can see what the image looks like. The small sliver of black on the left will be the line that marks the rating position inside of the symbols. This completes all the images and HTML you will need to get the rating system to work.

Step 2: Create Styles to Control the Ratings

Next we style the image tag to support our ratings. I suggest using classes, here are the styles I used:

Example 4: Rating Styles

img.rating { background: #FC0 url(/assets/img/bg/ratingbg.gif) 5em 0 no-repeat; /* specify your color; 5 em is the 5:1 ratio; and the url is wherever the ratingbg.gif is located */ font-size: 1.6em; /* this is the height of a single symbol; mine is 16px */ height: 1em; /* this will cause the image to scale as the font-size changes, default is 16px */ width: 5em; /* this the 5 to 1 ratio that I mentioned, but could be different if your symbols aren’t symetric */ } /* higher specificity than img.rating */ html body img.r9 { background-position: 4.5em 0; } html body img.r8 { background-position: 4.0em 0; } html body img.r7 { background-position: 3.5em 0; } html body img.r6 { background-position: 3.0em 0; } html body img.r5 { background-position: 2.5em 0; } html body img.r4 { background-position: 2.0em 0; } html body img.r3 { background-position: 1.5em 0; } html body img.r2 { background-position: 1.0em 0; } html body img.r1 { background-position: 0.5em 0; } html body img.r0 { background-position: 0 0; }

Step 3: Tie it all Together

Here is a rating of 2.5 in yellow, 1 in blue, and 4.5 in green:

Example 5: 3 Star Rating Variations

2.5 in yellow rating example
1 in blue rating example
4.5 in green rating example

The image heights in this article are slightly off by a fraction, due to rounding issues with other styles inherited in this wordpress theme. I have also created a test page so you can play with the uncorrupted styles and view them in different browsers. Try to scale the font-size and see how nicely the images scale and the rating stays in the appropriate place.

posted by Matt Snider at 3:45 pm  

Wednesday, December 12, 2007

Issues With JsonArray

My goal today was to finish most of the models that I have been working on and share this with you. However, it is turning out to be a much more complicated problem than I originally thought. I want the JsonArray object to dynamically create JsonObjects (as necessary) when retrieving elements out of the array. However, the JsonObject declaration requires that you know what Object is being passed, and more importantly, what keys to expect.

For example, here is what I have for JsonArray:

Example 1: JsonArray

/** * The JsonArray class manages … * * @namespace Mint.Model * @class JsonArray * @dependencies library */ Mint.Model.JsonArray = function(data) { this.update(data); }; Mint.Model.JsonArray.prototype = { update: function(data) { var that = []; this.get = function(i) { if (! that[i]) { var o = data[i]; if (isArray(o)) { that[i] = new Mint.Model.JsonArray(o); } else if (isObject(o)) { that[i] = new Mint.Model.JsonObject(o); } else { that[i] = o; } } return that[i]; }; } });

This will work just fine if all values of the array are non-object. However, if it is an Object (most likely, it is), then the JsonObject method is instantiated. The JKEYS array will be empty, because you haven’t defined it yet and will throw errors. This has been a real thorn in my side and I have not found a good solution around the issue, except possibly in xJson.

I was planning on playing with xJson separately, but since xJson defines the keys in the JsonArray definition, you can leverage them during the JsonObject instantiation. Also, this has the added benefit of being server driven, without requiring client-side awareness and could make these model objects a little more tamper resistant. I will look into xJson this week and post about it on Friday.

posted by Matt Snider at 3:03 am  

Friday, December 7, 2007

JsonObject Model Prototype

I have been doing a lot of server-side work in PHP and JAVA this past month. One of the design practices used by server-side webapp languages is the MVC (model-view-controller) framework. An MVC makes writing extensible and scalable code, much easier. In JavaScript I have seen a couple of attempts at a similar MVC framework, but most of the time it is overkill or poorly implemented. Shutterfly.com on the other-hand, has a lot of JavaScript and a fairly solid MVC implementation.

I have been toying with JavaScript MVC concepts all week and want to share what I developed to improve working with JSON objects. Keep in mind this is a work in progress and I am open to any suggestions:

Example 1: Sample Code

/** * The JsonObject class manages … * * @namespace Core.Model * @class JsonObject * @dependencies core */ Core.Model.JsonObject = function(data) { this.update(data); }; Core.Model.JsonObject.prototype = { JKEYS: [], MODEL: ‘JsonObject’, update: function(data) { var obj = {}, that = this, i = 0; // iterate through the JSON object keys for (var key in data) { var o = data[key]; if (! isType(o, ‘function’)) { // this key is not in the model if (-1 < that.JKEYS.indexOf(key)) { throw('Invalid key (' + key + ') passed into ' + that.MODEL); } obj[key] = o; that[isType(o, 'boolean')? key: 'get' + ckey] = function() {return obj[key];}; that['set' + ckey] = function(o) {return obj[key] = o;}; i += 1; } }; // the keys in the object do not match the keys in the model if (i != JKEYS.length) { throw('Invalid number of keys passed into ' + that.MODEL); } } });

This method takes a JSON object and applies getter/setter methods to that object. Each time update is called, it validates against JSKEYS, which should be an array of the expected keys. We also remove any functions that might have been attached to the ‘data’ object. You may also note that I have a special check for boolean values that does not attach the ‘get’ preface to the getter method. This is because I always preface boolean values with ‘is’ or ‘has’ (or something like that), and I want that preserved in the Function attached to the JsonObject.

Here is an example of how you might use it:

Example 2: Using JsonObject

// a JSON object, most likely returned from the server, representing a car var carJson = { make: ‘ford’, model: ‘contour’, year: ‘1996′, price: ‘$2,000′, isNew: false }; // use the extend (Object Extension) method to create a CarObject, with the appropriate JKEYS set Core.extend(Core.Model.CarObject, Core.Model.JsonObject, { JKEYS: [’make’, ‘model’, ‘year’ , ‘price’], MODEL: ‘CarObject’ }); var car = new Core.Model.CarObject(carJson);

Example 2 will create a CarObject (extended JsonObject), with the following members:

Example 3: CarObject Structure

CarObject = { // constants defined by the prototype object JKEYS: [’make’, ‘model’, ‘year’ , ‘price’], MODEL: ‘CarObject’, getMake(), setMake(), getModel(), setModel(), getYear(), setYear(), getPrice(), setPrice(), isNew(), setIsNew() }

I find it much easier to work with the JsonObjects once the getters and setters have been created, because you have defined what should be there. Plus, with the validation you can be sure that you received the correct JSON Object. Some possible improvements would be to support nested JsonObjects and JsonArrays, and setter type validation (although, this might be overkill).

There are really only 3 types of models that you might need to use in JavaScript: JSON Objects/Arrays, Query strings, and XML. Creating the Models is a good first step in building an MVC as you will need to pass these between the controller and the view, to update the view and send messages to the server. Next week, I will look into these other model types and improve this one a bit.

posted by Matt Snider at 3:35 pm  

Saturday, December 1, 2007

Object Extension

Object extension is one of the most important aspects of an OOP language. Many people believe that a prototype based OOP languages, such as JavaScript, are unable to support this classical OOP feature. Frankly, these people are wrong, and most likely have never studied a prototype based OOP. There are, in fact, two ways (if you know of more, please let me know) of extending objects in JavaScript: Member Copy and Prototype Clone.

Member Copying

This is a brute force technique and the most common method used for extending Objects. Most frameworks started using this technique, see jQuery (<= v1.2) and prototype (<=1.5) extend methods. In this technique you iterate through each member in Object A and add it to Object B. Often Object B is created when the extend Function is executed. Here is an example snippet from jQuery 1.2 extend:

Example 1: Memeber Copy as Implemented By jQuery 1.2

jQuery.extend = jQuery.fn.extend = function() { // copy reference to target object var target = arguments[0] || {}, a = 1, al = arguments.length, deep = false; // Handle a deep copy situation if ( target.constructor == Boolean ) { deep = target; target = arguments[1] || {}; } // extend jQuery itself if only one argument is passed if ( al == 1 ) { target = this; a = 0; } var prop; for ( ; a < al; a++ ) // Only deal with non-null/undefined values if ( (prop = arguments[a]) != null ) // ACTUAL COPY BEGINS HERE // Extend the base object for ( var i in prop ) { // Prevent never-ending loop if ( target == prop[i] ) continue; // Recurse if we’re merging object values if ( deep && typeof prop[i] == ‘object’ && target[i] ) jQuery.extend( target[i], prop[i] ); // Don’t bring in undefined values else if ( prop[i] != undefined ) target[i] = prop[i]; } // ACTUAL COPY ENDS HERE // Return the modified object return target; };

There is a lot going on here, because jQuery allows for deep copy (recursively copying members of the extension objects) and the simultaneous extension of multiple objects. My all-caps “ACTUAL COPY …” comments wrap the actual copy code. The “for in” statement iterates through the extension Object (prop[i]) and applies it to the extending Object (target[i]).

The benefits of the Member Copy method are that it is easily understood and makes multiple inheritance trivial. The negatives are that it is much slower than the Prototype Clone method, it is easy to accidentally override methods, and a bit of hackery is needed to do a deep extension of an Object.

It is slower because you have to iterate through every member of the extension Object and then apply each of those members to the extending Object. In the Prototype Clone method, this can be done in 3 lines of non-looping code. Accidentally overriding methods is a problem, because you might be extending Object A, which has a method ‘toString’ with Object B, which also has a method ‘toString’. However, you wanted to keep the ‘toString’ method of Object A. The Prototype Clone method only allows for single inheritance, which prevents such pit-falls.

Prototype Clone

This is my favored technique as performance is the most important aspect of my applications. The user should never have to wait for your JavaScript to execute and this method can be much faster than the Member Copy method as the operation cost is a constant time operation versus an O(n) operation. Here is an example of the Prototype Clone method from YUI:

Example 2: Prototype Clone as Implemented By YUI 2.3

/** * Utility to set up the prototype, constructor and superclass properties to * support an inheritance strategy that can chain constructors and methods. * Static members will not be inherited. * * @method extend * @static * @param {Function} subc the object to modify * @param {Function} superc the object to inherit * @param {Object} overrides additional properties/methods to add to the * subclass prototype. These will override the * matching items obtained from the superclass * if present. */ extend: function(subc, superc, overrides) { if (! superc || ! subc) { throw new Error(”YAHOO.lang.extend failed, please check that ” + “all dependencies are included.”); } var F = function() {}; F.prototype = superc.prototype; subc.prototype = new F(); subc.prototype.constructor = subc; subc.superclass = superc.prototype; if (superc.prototype.constructor == Object.prototype.constructor) { superc.prototype.constructor = superc; } if (overrides) { for (var i in overrides) { subc.prototype[i] = overrides[i]; } YAHOO.lang._IEEnumFix(subc.prototype, overrides); } },

In this method you first create an anonymous Function ‘F’ and copy the prototype of the superclass to that Function (this prevents changes to the subclass from affecting the superclass). Then instantiate ‘F’ assigning it to the prototype of the subclass. This creates memory pointers on the subclass’ prototype Object to the members attached to the prototype of the superclass. Now when you instantiate the subclass Object, it will have all members of the superclass. It is important to note that you should not be extending an Object that already has values on its prototype, but instead you should use this technique when you first create the subclass Object.

YUI also adds some additional Object pointers (constructor and superclass), which make these Objects behave more like a Classical OOP language. I disagree with this action, because you can extend the Object object, and this method will modify the prototype of Object and break future “for in” statements on all Object objects. They use these methods in detecting Object types and the inheritance model, but I have not yet been convinced of the need for this.

I do like how they allow you to pass in overrides. This is an Object whose members are applied to the subclass after the extension occurs. This allows you to extend and initialize the subclass in one statement.

I suggest a simpler function (which I use in my projects), as follows:

Example 3: Simple Prototype Clone

Core.extend = function(subc, superc, overrides) { if (! superc || ! subc) { throw new Error(”Core.extend failed, please check that all dependencies are included.”); } var F = function() {}; F.prototype = superc.prototype; subc.prototype = new F(); if (overrides) { for (var i in overrides) { subc.prototype[i] = overrides[i]; } } };

And here is an example of how you might use the extend Function:

Example 4: Using Extend

var Item= {}; var Book = {}; Core.extend(Item, Object, { toString: function() { var sb = []; for (var i in this) { if (typeof this[i] !== ‘function’) { sb.push(’&'); sb.push(i); sb.push(’='); sb.push(this[i]); } return sb.join(”); } }); Core.extend(Book, Item, { getTitle: function() { return this.title; }, setTitle: function(s) { this.title = s; } });

If you really need to know the inheritance chain, then I suggest attaching a stack to each new Object that contains the inheritance chain. That way you won’t break ‘for in’ operations of the Object object.

…………………………
Edited: March 15, 2008

I eventually, found a good use for referencing super class, and here is how I modified this method to support it:

Improving Extend With Super

posted by Matt Snider at 3:18 pm  
« Previous PageNext Page »

Powered by WordPress