If you've worked through JavaScript recursion primer and More recursion - parents and children you should be pretty comfortable with how recursion works by now. Now we're going to apply it to create a simple version of jQuery.extend(). If you are not familiar with it, its a useful function that allows you merge multiple objects - usually used for setting default properties. Let's say you have this as the default parameters to something like a charts API
and you want to use most of those settings, but make just a couple of changes, with jQuery you can say
and you'll get back an object like this
DeeperSo far pretty simple .. but you may need to go deeper into the object if there are properties beyond the first level.
Still not too bad, but you'd have to do this. So you can imagine if you continue this out to multiple levels, we have a good candidate for a solution involving recursion
The cUseful libraryMany of the snippets in this section of the site are part of the cUseful library. You can find the details below. This extend function uses a couple of functions from that, and will be implemented there too if you want to use it from a library. The extend functionYou use it like this
You can have as many default objects as you like and the result will be the merger of all those defaults applied to your options, very much like jQuery.extend(). So how to do it ?Here's the code. We'll do a walkthrough later on once we've done some testing /** * a little like the jquery.extend() function * the first object is extended by the 2nd and subsequent objects - its always deep * @param {object} ob to be extended * @param {object...} repeated for as many objects as there are * @return {object} the first object extended */ function extend () { // we have a variable number of arguments if (!arguments.length) { // default with no arguments is to return undefined return undefined; } // validate we have all objects var extenders = [],targetOb; for (var i = 0; i < arguments.length; i++) { if (!isObject(arguments[i])) { throw 'extend arguments must be objects'; } if (i ===0 ) { targetOb = arguments[i]; } else { extenders.push (arguments[i]); } } // set defaults from extender objects extenders.forEach(function(d) { recurse(targetOb, d); }); return targetOb; // run do a deep check function recurse(tob,sob) { Object.keys(sob).forEach(function (k) { // if target ob is completely undefined, then copy the whole thing if (isUndefined(tob[k])) { tob[k] = sob[k]; } // if source ob is an object then we need to recurse to find any missing items in the target ob else if (isObject(sob[k])) { recurse (tob[k] , sob[k]); } }); } } Some testsSee if you can figure out how these cases should be handled. function textend() { // expect an empty object var ob = cUseful.extend ({}); Logger.log(ob); // expect ob to look like cUseful.extended var ob = cUseful.extend (ob , {a:true,b:true}); Logger.log(ob); // expect ob to look like cUseful.extended a/b/c/d/e var ob = cUseful.extend (ob , {c:true,d:true}, {e:true}); Logger.log(ob); // none of these should change, but add f var ob = cUseful.extend (ob, {e:false,d:false}, {a:false} , {c:false}, {f:true}); Logger.log(ob); // see that arrays work var ob = cUseful.extend (ob, {a1:[true,2,3]}); Logger.log(ob); // now some recursing - should not cUseful.extend because a is not an object var ob = cUseful.extend (ob , {a:{aa:false}}, {a:{aa:false}}); Logger.log(ob); // should add first one because g is new object var ob = cUseful.extend (ob , {g:{gg:true}}, {g:{gg:false}}); Logger.log(ob); // should add an extra property to g var ob = cUseful.extend (ob , {g:{ggg:true}}, {g:{gg:false}}); Logger.log(ob); // should not add an extra property to g.ggg since that is not an object var ob = cUseful.extend (ob , {g:{ggg:{gggg:false}}}, {g:{gg:false}}); Logger.log(ob); } and the results [15-02-08 13:32:34:591 GMT] {} [15-02-08 13:32:34:592 GMT] {b=true, a=true} [15-02-08 13:32:34:593 GMT] {d=true, e=true, b=true, c=true, a=true} [15-02-08 13:32:34:594 GMT] {f=true, d=true, e=true, b=true, c=true, a=true} [15-02-08 13:32:34:595 GMT] {f=true, d=true, e=true, b=true, c=true, a=true, a1=[true, 2.0, 3.0]} [15-02-08 13:32:34:596 GMT] {f=true, d=true, e=true, b=true, c=true, a=true, a1=[true, 2.0, 3.0]} [15-02-08 13:32:34:597 GMT] {f=true, g={gg=true}, d=true, e=true, b=true, c=true, a=true, a1=[true, 2.0, 3.0]} [15-02-08 13:32:34:597 GMT] {f=true, g={gg=true, ggg=true}, d=true, e=true, b=true, c=true, a=true, a1=[true, 2.0, 3.0]} [15-02-08 13:32:34:598 GMT] {f=true, g={gg=true, ggg=true}, d=true, e=true, b=true, c=true, a=true, a1=[true, 2.0, 3.0]} Let's check on our initial example. var defaultOptions = { dimensions: { width: 20 , height:100 }, title:"this is the default title", type:"bar" }; Logger.log(cUseful.extend ( { dimensions: { height:200 }, type:"pie" } , defaultOptions)) ; and the result { "dimensions": { "height": 200, "width": 20 }, "type": "pie", "title": "this is the default title" } Walkthrough
// we have a variable number of arguments if (!arguments.length) { // default with no arguments is to return undefined return undefined; }
// validate we have all objects var extenders = [],targetOb; for (var i = 0; i < arguments.length; i++) { if (!isObject(arguments[i])) { throw 'extend arguments must be objects'; } if (i ===0 ) { targetOb = arguments[i]; } else { extenders.push (arguments[i]); } }
// set defaults from extender objects extenders.forEach(function(d) { recurse(targetOb, d); }); return targetOb;
// run do a deep check function recurse(tob,sob) { Object.keys(sob).forEach(function (k) { // if target ob is completely undefined, then copy the whole thing if (isUndefined(tob[k])) { tob[k] = sob[k]; } // if source ob is an object then we need to recurse to find any missing items in the target ob else if (isObject(sob[k])) { recurse (tob[k] , sob[k]); } }); } And that's all there is to it. Happy recursing. For more like this, see Google Apps Scripts snippets. Why not join our forum,follow the blog or follow me on twitter to ensure you get updates when they are available. You want to learn Google Apps Script?Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly. If you prefer Video style learning I also have two courses available. also published by O'Reilly. |
Services > Desktop Liberation - the definitive resource for Google Apps Script and Microsoft Office automation > Google Apps Scripts snippets >