Jan 6 2010

Submitting an Ext.form.Checkbox when unchecked

datchley

ExtJS is a great Javascript UI library and API. In using ExtJS for UI components to create forms, the Ext checkbox widget (Ext.form.Checkbox) mimics the behavior of standard HTML checkboxes in that if they are not checked, they are not submitted via POST/GET on the form. This makes sense in some instances, in that if I don’t set a value for a given checkbox field I probably don’t want it on the back-end – except when that checkbox represents an on/off type value and that value, whether on or off, needs to be updated in a database record somewhere.

So, what do you do in those instances where you want to know if a field value has been toggled off, and not just on? You could cycle through those particular boolean fields for each record you want to update, checking if the existing value is set on the record; and if that field isn’t present in the form POST data unset it explicitly. However, that’s a lot of work and a lot of hard-coding of boolean indicator fields on your back-end. A better solution would be if we could actually make those stubborn checkboxes send a value on form submission even when they aren’t checked! And lo and behold, with ExtJS’s Ext.override() functionality for classes, we can do that with the standard Ext.form.Checkbox component.

In the code below, we use Ext.override() to modify the basic Ext.form.Checkbox component to include a hidden input field that will contain the “off” or “unchecked” value that we want submitted via our forms. However, in order to avoid sending duplicate fields when the primary checkbox is checked (we don’t want to send an “on” and an “off” value) we remove that hidden field each time the checkbox is checked by overriding the setValue() method of Ext.form.Checkbox.

We only need to override two methods: Ext.form.Checkbox.onRender() and Ext.form.Checkbox.setValue() to handle all the cases we need. Here’s the code…

(function() {
	origCheckboxRender = Ext.form.Checkbox.prototype.onRender;
	origCheckboxSetValue = Ext.form.Checkbox.prototype.setValue;

	Ext.override(Ext.form.Checkbox, {
		onRender: function() {
			// call the original onRender() function
			origCheckboxRender.apply(this, arguments);

			// Handle initial case based on this.checked
			if (this.checked == false) {
				this.noValEl = Ext.DomHelper.insertAfter(this.el, {
            		tag: 'input',
            		type: 'hidden',
            		value: '0',
            		name: this.getName()
        		}, true);
			}
			else {
				this.noValEl = null;
			}
		},
		setValue: function() {
			// call original setValue() function
			origCheckboxSetValue.apply(this, arguments);
			if (this.checked) {
				if (this.noValEl != null) {
					// Remove the extra hidden element
					Ext.select('input[id=' + this.noValEl.id + ']').remove();
					this.noValEl = null;
				}
			}
			else {
				// Add our hidden element for (unchecked) value
				this.noValEl = Ext.DomHelper.insertAfter(this.el, {
            		tag: 'input',
            		type: 'hidden',
            		value: '0',
            		name: this.getName()
        		}, true);
			}
		}
  });
})();

Important to note here, and not completely necessary, is that we’re using a closure by wrapping our override in an anonymous function and immediately calling it to run the override. This simply keeps us from polluting the global namespace; and once the override is done there’s no need to reference any of those objects again anyway.

Before calling Ext.override(), we save the original functions for onRender() and setValue(), since we don’t want to lose any existing Checkbox functionality they provide, we simply want to make them do a bit more. We could have chosen to use Ext.extend() here and create an entirely new type of Checkbox that handled this functionality. But, in my case on this project, that would have impacted too many other applications that already depended on the original Checkbox functionality; and, we didn’t want to go through all of our code and change every occurrence of Checkbox to SmartCheckbox or some such. Too much regression testing at this point. So, we choose to override instead of extend – but if you have the opportunity, extending might be the better option and provide you with a way to set the “unchecked” value you want for each Checkbox instance.

So, in each overridden function we simply call the original function first to retain existing functionality and then add our bits to extend the object. We use Ext.DomHelper.insertAfter() to handle inserting the new hidden form field and it has the same name as the actual Checkbox field. If the checkbox starts off initially checked, we add the hidden field; and if not we simply set our noValEl variable to null. Same for setValue(), which is called each time the checkbox is “checked.”

This is the kind of simple solution that the ExtJS library allows you to perform and which makes it a very extendable API for cross-browser Javascript UI development. Enjoy!


Jan 4 2010

IE and RegExp.exec()

datchley

In some recent code, I’m using Javascript to parse through the result set of an AJAX call, which happens to be returning a full HTML page. Yes, ideally, I’d have an AJAX call return something usable like JSON, but in this case the PHP back-end code had to remain as is and the front-end adjust to handle the legacy HTML it returned.

I needed to grab a link (1 or more) from the returned HTML page so that I could immediately display those links in separate windows (each was a generated report).  So, my first stab at this is shown in the following code example. Basically, we have setup a string to represent the returned HTML, in this case it contains 3 <a> links; and we want to use the standard Javascript RegExp object’s exec() method to grab the URLS (href parameter) for each of those links.  In our example, we just print them out in an unordered list to see what we’ve captured. The important lines of code we’ll be looking at are highlighted in the example below.

var s='<a href="x">X</a>\n<a href="y">Y</a>\n<a href="z">Z</a>\n';
document.write('Found the following link URLs in the string:<br/><ul>');
while (matches = /<a href=['"](.*)['"]>.*<\/a>/g.exec(s)) {
  document.write('<li>' + matches[1] + '</li>\n');
}
document.write('</ul>');

Which, when run, we get the following results in Firefox/Safari/Chrome:

Found the following link URLs in the string:

  • x
  • y
  • z

Our while loop using RegExp.exec() on our in-line regular expression does what it’s supposed to and continues to match from where it left off in the string giving us our captured portion in the matches[] array.  However, when run in Internet Explorer, we get the following lovely result (at least up until IE tells us the script is no longer responding and asks us to kill it):

Found the following link URLs in the string:

  • x
  • x
  • x
  • x
  • x
  • x
  • x
  • x
  • x
  • …ad infinitum…

Obviously, we have generated an infinite loop using our code above in IE; but why?  The issue is that IE doesn’t correctly maintain the lastIndex member for the regular expression object each iteration through the loop.  Each time through the loop, which if you look at the highlighted code is in-lined, IE creates a new RegExp object and hence resets the lastIndex member to the beginning of the string. Therefore, we match the first link in the string infinitely as the lastIndex pointer never progresses between matches.  There is a way around this, and that is to declare the regular expression separately, outside the loop, (it gets created just once) and then call exec() on that singular RegExp object as follows:

var rx = /<a href=['"](.*)['"]>.*<\/a>/g;
var s='<a href="x">X</a>\n<a href="y">Y</a>\n<a href="z">Z</a>\n';
document.write('Found the following link URLs in the string:<br/><ul>');
while (matches = rx.exec(s)) {
  document.write('<li>' + matches[1] + '</li>\n');
}
document.write('</ul>');

Now, the lastIndex member of our RegExp object gets updated correctly and we get the results we expected.  Somewhat related to this item is the following interesting lastIndex bug in IE with zero-length matches.  Hopefully, this will save someone a headache when trying to debug using Javascript RegExp.exec().


Dec 15 2009

URL Hackery Followup: Regex

datchley

In a short follow up on my last post, I thought I’d cover briefly the regular expressions I’m using and provide some links to good references on regular expressions in Javascript. You can find a good introduction to basic regular expressions online, so I won’t belabor that summary here; and I’ll assume you are familiar with regex in any of the number of languages that support them, ie. Perl, PHP, Javascript, grep, et. al..

Here’s the first snippet from our previous post that uses regex:

var loc = /(?:foo(?:-([^\.]+)?)?)\.*/.exec(window.location.href);

What we’re trying to do is match just the leading part of the domain name, which we get from window.location.href.  For example, if the domain name of our current URL is foo.company.com, then we want to match ‘foo’ and have that returned to us in our array of matches in the variable loc.  In Javascript, exec() returns an array of matches, the first element of which is the part of the string that the pattern matched.  If there are capturing parenthesis, which we have here, then each captured portion will be in the following elements, in the order they are matched in the string.

In this case, some of our parenthesis are “non-capturing“, those that start with “(?:“.  So, we’d like to match a domain name that starts with the string “foo“, optionally followed by a ‘-‘ with one or more characters that are not periods in it, followed by a ‘.‘ (period) and zero or more of anything else (which would be the remaining part of the domain name).

The ‘?‘ question marks at the end of the parenthesis groupings means – match 0 or 1 of the preceding thing. This gives us the ability to make parts of the match optional, which is good, since in our case the production domain names don’t have a ‘-xxx‘ in the first part, only a ‘foo‘.  So, let’s break this down in a table and see what exec() would return to us for a number of different domain names:

url (string)

exec() returns

foo.company.com ["foo.", undefined]
foo-bar.company.com ["foo-bar.", "bar"]
bar.company.com null
foobar.company.com ["foo", undefined]

Notice in the last example, the full part of the string that exec matched for this pattern doesn't have the trailing '.' like the other examples. Remember, the first element in the array is the largest part of the string that the pattern could match completely, which in this case is just 'foo', since it's followed directly by a 'bar' instead of a period '.'.  Unless your quantifiers, like '+' and '*' are followed by a question mark, they are considered "greedy" and will match as much as they can while still satisfying the given pattern.

Hopefully this makes some sense.  If you want to play around with the above line of Javascript and you run Firebug, just open up the console and cut-paste the line above in the 'Run' area and try it out.  Firebug is a boon for Javascript development and I wouldn't go anywhere without it.

And for your further education, a link to some Javascript specific regular expression tutorials and examples.

Enjoy!


Dec 14 2009

Javascript URL hackery

datchley

In one of the projects I have at work we have a number of environments that run the system (a web based application) – production, testing environments and development.  Each environment is assigned it’s own domain name. So for instance, our Systems Integration Testing (SIT) environment would be accessed as foo-sit.company.com.  The environment is always suffixed at the end of the system name in the URL with a ‘-’ dash.  The only time this is not the case is in production, where the URL would just be foo.company.com. This is pretty straight forward and an easy way to access multiple web application environments.

However, there are times when I want the application running in one environment to hit the database of another environment. For instance, when running reports that I’m coding in development, I want to run them against a more recent copy of the database in our SIT environment. Our backend PHP code allows this by accepting a ‘dbenv=ENV‘ POST parameter to tell it to use a different database environment than its default. So, I could have my development environment reports hit the SIT testing environment database by changing the URL to

http://foo-dev.company.com?dbenv=SIT

So, in our Javascript code in a number of these reports, we need to determine if the user is coming in wanting a different database environment than the default and pass the dbenv=ENV parameter ourself via the AJAX call to generate the report. Since we’re using Javascript and AJAX, this has to be dynamic as we build the parameters for the report dynamically as well. Here’s the snippet of code that does that in Javascript land, with the names changed to protect the innocent:

var o = {};
o = Ext.urlDecode((window.location.href.split('?',2))[1]);

if (!o.dbenv || !o) {
 var loc = /(?:foo(?:-([^\.]+)?)?)\.*/.exec(window.location.href);
 if (loc.length == 2) {
     o.dbenv = (loc[1]) ? loc[1].toUpperCase() : 'PROD';
 }
 else {
     o.dbenv = 'DEV';
 }
}
else {
 o.dbenv = o.dbenv.replace(/[^A-Za-z]+/,'');
}

var DBENV = o.dbenv;

Our team is using ExtJS as a cross-browser Javascript framework for our applications, so the ‘Ext.urlDecode()‘ call is the only non-plain Javascript portion of this code. It returns the decoded URL parameters in a Javascript object. Note that we’re only decoding the parameter portion of the URL here ('window.location.href.split('?',2))[1], since we’re just trying to determine if there is already a ‘dbenv=ENV‘ attached to it. Once we have the parameter portion of the URL as an object, we look to see if there is a ‘dbenv’ attribute on this object. If not, or if the object is undefined (no parameters were available to parse) we need to look at the URL itself and the suffix we talked about before to determine the database environment to pass. That’s where we see

var loc = /(?:foo(?:-([^\.]+)?)?)\.*/.exec(window.location.href);

Here, we take the URL itself, looking for the system name (in this case ‘foo’) followed by a ‘-’ dash and some optional text which we hope to be the environment name in lower case (‘-dev’, ‘-sit’, etc.). Line 4 gives us back an array from the regular expression. This array will always have at least 1 element, which is the part of the URL that matched the pattern entirely, accessible via loc[0] at this point. If we matched a database environment, it will be in loc[1], the second element of the array.

The time when this is NOT true, is in production, which has no ‘-env’ suffix on the URL – in which case loc[1] will be ‘undefined’. In the code, I’ve never tested any occurrence when loc has a length of less than 2 (since in our regex match if there is no suffix, we always get ‘undefined’ back because of the capturing parenthesis). But, in case some weird URL rewriting happens because of our web server admins (and they do change things without telling us sometimes) we have a simple check and if we don’t get a length of 2, we simply default the database environment to DEV.

Otherwise, we set the database environment to whatever was matched in the suffix, uppercased or ‘PROD’ if it was undefined. This is fairly straight forward and has worked for us so far. But, if you’ve got other ideas or suggestions, I’d love to hear them in the comments.

Enjoy!