The case for Ext.applyOnly

Update: Ext 3.0RC1 has included something like this, but called Ext.copyTo. Obviously my name is better though.

We should have something like this:


Ext.applyOnly(this, config, ['width', 'height']);

Ext.applyOnly(this, config, ['width', 'height']);

You could use this every time you write a method or class that requires a config Object as one of it's parameters. These methods ought to only apply those properties of the config object they actually need, but usually this will just be done with an Ext.apply(this, config). This means anything in your object could be overwritten by this config object. Sometimes that's a good thing, but sometimes it's definitely not.

Ext.applyOnly() applies only a whitelist of the properties in the config object. These are specified by an optional third argument, which is an array of property names. Here's how you might write applyOnly:


/**
* Applies only a pre-specified set of properties from one object to another
* @param {Object} receiver The object to copy the properties to
* @param {Object} sender The object to copy the properties from
* @param {Array} whitelist The whitelist of properties to copy (e.g. ['width', 'height'])
* @return {Object} The receiver object, with any of the whitelisted properties overwritten if they exist in sender
*/
Ext.applyOnly = function(receiver, sender, whitelist) {
if (receiver && sender) {
Ext.each(whitelist || [], function(item) {
if (typeof sender[item] != 'undefined') receiver[item] = sender[item];
}, this);
};

return receiver;
};

/**
* Applies only a pre-specified set of properties from one object to another
* @param {Object} receiver The object to copy the properties to
* @param {Object} sender The object to copy the properties from
* @param {Array} whitelist The whitelist of properties to copy (e.g. ['width', 'height'])
* @return {Object} The receiver object, with any of the whitelisted properties overwritten if they exist in sender
*/
Ext.applyOnly = function(receiver, sender, whitelist) {
if (receiver && sender) {
Ext.each(whitelist || [], function(item) {
if (typeof sender[item] != 'undefined') receiver[item] = sender[item];
}, this);
};

return receiver;
};

While you can't stop code maliciously overwriting properties this way, it would stop people from unknowingly overwriting your object's properties. They could overwrite them manually, but they'll do this knowing that this wasn't an intended use for the class. Let's have a look at an extension that would do a great job opening popups telling people they've won lots of money:


/**
* Pops up windows telling lucky visitor she's won big money!!!!
*/
Ext.ux.WinnerEarnings = Ext.extend(Ext.util.Observable, {
/**
* @property accessibleProperties
* @type Array
* All properties intended to be mass-updatable
*/
accessibleProperties: ['height', 'width'],

/**
* Message to show lucky winners!! You can't change this!!!!!
*/
message: "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!",

constructor: function(config) {
//apply only the fields that are deemed writable
Ext.applyOnly(this, config, this.accessibleProperties);

Ext.ux.WinnerEarnings.superclass.constructor.apply(this, arguments);

Ext.applyIf(this, {
version: 2.9,
coolFeature: Ext.util.TaskRunner({
interval: 1000,
scope: this,
run: function() {
//version, coolFeature, updateDetails, closable and close won't be sent to Ext.Window
new Ext.Window(Ext.applyOnly({}, this, ['height', 'weight', 'message'])).show();
}
}).start()
}
},

/**
* Updates this WinnerEarnings opportunity with options from the supplied object
* @param {Object} updates An object containing updates to make to this precious opportunity
* @return {Ext.ux.WinnerEarnings} The WinnerEarnings object
*/
updateDetails: function(updates) {
return Ext.applyOnly(this, updates, this.accessibleProperties)
},

//secret tricks to let the user stop the popups
closable: false,
close: function() {
this.coolFeature.stop();
}
})

/**
* Pops up windows telling lucky visitor she's won big money!!!!
*/
Ext.ux.WinnerEarnings = Ext.extend(Ext.util.Observable, {
/**
* @property accessibleProperties
* @type Array
* All properties intended to be mass-updatable
*/
accessibleProperties: ['height', 'width'],

/**
* Message to show lucky winners!! You can't change this!!!!!
*/
message: "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!",

constructor: function(config) {
//apply only the fields that are deemed writable
Ext.applyOnly(this, config, this.accessibleProperties);

Ext.ux.WinnerEarnings.superclass.constructor.apply(this, arguments);

Ext.applyIf(this, {
version: 2.9,
coolFeature: Ext.util.TaskRunner({
interval: 1000,
scope: this,
run: function() {
//version, coolFeature, updateDetails, closable and close won't be sent to Ext.Window
new Ext.Window(Ext.applyOnly({}, this, ['height', 'weight', 'message'])).show();
}
}).start()
}
},

/**
* Updates this WinnerEarnings opportunity with options from the supplied object
* @param {Object} updates An object containing updates to make to this precious opportunity
* @return {Ext.ux.WinnerEarnings} The WinnerEarnings object
*/
updateDetails: function(updates) {
return Ext.applyOnly(this, updates, this.accessibleProperties)
},

//secret tricks to let the user stop the popups
closable: false,
close: function() {
this.coolFeature.stop();
}
})

How it works:


var myObj = new Ext.ux.WinnerEarnings({height: 200, width: 150});

myObj.updateDetails({width: 300, message: "My Message"})
myObj.width: // => 300
myObj.message; // => "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!"

//updating message didn't work, but we can still do it manually
myObj.message = "My message";
myObj.message; // => "My message"

var myObj = new Ext.ux.WinnerEarnings({height: 200, width: 150});

myObj.updateDetails({width: 300, message: "My Message"})
myObj.width: // => 300
myObj.message; // => "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!"

//updating message didn't work, but we can still do it manually
myObj.message = "My message";
myObj.message; // => "My message"

In my example class I've added the whitelist as an accessibleProperties property on the class, which makes it easy for others to see what they should and should not be updating.

In this example we're also sanitizing output with applyOnly. WinnerEarnings lightly wraps around a series of Ext.Windows and we'd like to be able to pass our WinnerEarnings object as config. We want to make sure we're not passing our 'closable' property, 'close()' function and others to the Ext.Window constructor, so we pass that in via a whitelist too, inside the run() function in our constructor.

Check out the unit tests for the function to see a couple more use cases. Here's one final example - sanitizing output from a function:


myFunction = function(input) {
//do some stuff to make input useful

//guarantee our returned object only has relevant properties
return Ext.applyOnly({}, input, ['important-thing-1', 'important-thing-2']);
}

myFunction = function(input) {
//do some stuff to make input useful

//guarantee our returned object only has relevant properties
return Ext.applyOnly({}, input, ['important-thing-1', 'important-thing-2']);
}

Share Post:

What to Read Next

If you're interested in exploring more about Ext JS methods and techniques, consider reading How Ext.apply works, and how to avoid a big headache, which offers insights into the Ext.apply method and its potential pitfalls. Additionally, Ext.override - Monkey Patching Ext JS provides valuable advice on enhancing and overriding Ext JS classes safely.