/* Class: AddyAjax Class that takes a form and validates the address against the USPS. Author: Eric Clemmons License: MIT-style license Copyright: Copyright (c) 2007 Eric Clemmons & WhiteFence Script Dependencies: - JsonP.js Mootools Dependencies (v963): - Source/Core/Core.js - Source/Core/Browser.js - Source/Native/Array.js - Source/Native/String.js - Compatibility/Class/Class.js Example #1: For backwards-compatible use on a site calling "ZipCheck()" inside of fields. Validation occurs when "onKeyUp" is triggered in the 'zipcode" field by default. (start code) (end code) This automatically instantiates and validates the following fields. o streetAddress o apartmentNumber o city o state o zipcode Example #2: For backwards-compatible use on a site calling "ZipCheck()" and passing the fields. (start code) (end code) This is primarily for fields that don't use the default names. Example #3: This is the proper use of where you pass in a form to validate. (start code) // Pass the form name ... checkIt = new AddyAjax('selfReg'); // or pass the object ... checkIt = new AddyAjax(document.selfReg); // or pass by ID checkIt = new AddyAjax('selfReg'); (end code) This will automatically pass all of the form fields to the validator. Example #4: Another example of proper use, but with a custom onComplete function to run after completion. (start code) checkIt = new AddyAjax('selfReg', { onStart: function() { alert("I just started!"); }, onComplete: function() { alert("I Successfully Completed!"); } }); (end code) This is useful for beginning/ending visual indicators that the validation is occurring. */ window.addEvent('domready', AutoAddyAjax); function AutoAddyAjax() { var form = $E('form[name=selfReg]'); if(!form) return false; var fields = ['streetAddress', 'apartmentNumber', 'city', 'state', 'zipcode']; var address = new Hash(); $A(fields).each(function(field) { var input = form.getElement('[name='+field+']'); if(!input) { input = new Element('input', { 'type': 'hidden', 'name': field }); form.adopt(input); } address.set(field, input); }); address.zipcode.addEvent('keyup', ZipCheck.pass([address.streetAddress, address.apartmentNumber, address.city, address.state, address.zipcode])); } var AddyAjax = new Class({ form: null, address: null, options: { trustUSPS: false, timeOut: 5000, animate: true, autoValidate: true, url: 'http://www.whitefence.com/scripts/server/gateway.php', onStart: function() {}, onComplete: function() {}, onFailure: function() {}, progressURL: 'http://www.whitefence.com/images/loaders/arrows/green_on_white.gif', progressPosition: [-18, 2] }, /* Function: initialize Internal function (as referenced in Example #1, Example #2, Example #3, and Example #4). Parameters: form - This may either be empty (in conjunction with _options_), a name/id, or an element. If empty, form is automatically set to _selfReg_ (if it exists). If an object is passed, the object is treated as _options_. options - If you pass the options as the first parameter for , this must be *empty*! Otherwise, this passes _onStart_ and _onComplete_, which are both functions. See Also: <_setForm> */ initialize: function(form, options) { if(!!form && $type(form) == "object") this.setOptions(form); if(!!options) this.setOptions(options); if(!!form && $type(form) != "object") this._setForm(form); }, /* Function: _setForm Internal function used find the form referenced by . Parameters: form - Name/ID of form, or direct object. See Also: <_findFields> */ _setForm: function(form) { switch($type(form)) { case 'string': this.form = $(form) || document.getElementsByName(form)[0]; break; case 'element': this.form = form; break; } $(this.form); // Ensures we extend with Mootools if(!this._findFields()) return false; }, /* Function: addFields Function used to manually assign event handlers to specified fields. This is really used when the user assigns the address fields names that don't contain _street, apartment, city, state, or zip_. Parameters: streetAddress - Street Address to be validated. apartmentNumber - Apartment/Suite to be validated. city - City to be validated. state - 2-letter abbreviation (TX, WA, etc.) zipcode - 5-digit zipcode without spcaes or dashes. See Also: <_watchField> */ addFields: function(streetAddress, apartmentNumber, city, state, zipcode) { var array = [streetAddress, apartmentNumber, city, state, zipcode]; this.address = {}; for(var i = 0; i < array.length; i++) $(array[i]).removeEvents(); this.address.streetAddress = this._watchField($(streetAddress), 'blur'); this.address.apartmentNumber = this._watchField($(apartmentNumber), 'blur'); this.address.city = this._watchField($(city), 'blur'); this.address.state = this._watchField($(state), 'change'); this.address.zipcode = this._watchField($(zipcode), 'keyup'); }, /* Function: _findFields Internal function called by <_setForm> to automatically locate address fields. See Also: <_findFieldWith>, <_watchField>, & <_addRequired> */ _findFields: function() { if(!!!this.form) return false; this.address = {}; this.address.streetAddress = this._watchField(this._findFieldWith('street') || this._findFieldWith('address'), 'blur'); this.address.apartmentNumber = this._watchField(this._findFieldWith('apartment') || this._findFieldWith('apt'), 'blur'); this.address.city = this._watchField(this._findFieldWith('city'), 'blur'); this.address.state = this._watchField(this._findFieldWith('state'), 'change'); this.address.zipcode = this._watchField(this._findFieldWith('zip'), 'keyup'); return true; }, /* Function: _findFieldWith Internal function used to find a field with the name matching a string. Parameters: text - String to match against the _name_ attribute of input fields. Returns: First match is returned, otherwise *false* is returned. */ _findFieldWith: function(text) { var inputs = this.form.getElements('input'); return this.find(text, inputs); }, /* Function: _watchField Internal function to assign an event handler to a particular object. is called when the event is triggered. All existing events are removed prior to assign the handler, to ensure there's no doubling. Parameters: input - The element with the event handler trigger - Event trigger, such as _blur_, _change_, _keyup_, etc. */ _watchField: function(input, trigger) { if(!!!input) return false; if(this.options.autoValidate) //input.removeEvents().addEvent(trigger, this.validate.bind(this)); input.addEvent(trigger, this.validate.bind(this)); return input; }, /* Function: _checkFields Internal function to ensure all required fields have at least *5* characters. See Also: <_addRequired> Returns: Returns *true* if all required fields have at least *5* characters and there has been a value change in at least *1* input since previous check. Otherwise, *false*. */ _changed: function() { var changed = false; for(var field in this.address) { if(this.address[field].oValue != this.address[field].value) { var changed = true; this.address[field].oValue = this.address[field].value; } } if(this.address.zipcode.value.length < 5 || $empty(this.address.streetAddress.value)) return false; else return changed; }, /* Function: _updateFields Internal function to update form fields with validated address. Parameters: addy - Object passed by Skinner containing _streetAddress, aptNumber, city, state, & zipcode_. */ _updateFields: function(addy) { //if(!this._changed()) return false; // Fields have been changed since AJAX for(var field in addy.fields) if(!$defined(addy.fields[field])) addy.fields[field] = ''; this.address.streetAddress.value = addy.fields.streetAddress.value; this.address.apartmentNumber.value = addy.fields.aptNumber.value; this.address.city.value = addy.fields.city.value; this.address.state.value = addy.fields.state.value; this.address.zipcode.value = addy.fields.zipcode.value; if(this.options.animate) for(input in this.address) if(!!this.address[input]) new Fx.Style(this.address[input], 'background-color', {duration: 750}).start("#88FF88", "#FFFFFF"); }, /* Function: _showProgress Called internally to display a progress indicator while validating. Creates an image with the ID *addyajax_progress* position relative to the zipcode. Parameters: Relies on defaults set in Options when instantiating. progressURL - Image URL (relative to calling page). Default is *common/scripts/ajax-loader.gif*. progressPosition - Array of X & Y values to add/subtract to position of progress indicator. Defaults to *[0, 0]*. See Also: <_hideProgress>, */ _showProgress: function() { this.options.onStart(); if(!this.options.animate) return false; if($('addyajax_progress')) $('addyajax_progress').remove(); var zip = this.address.zipcode; //this.find("zip", this.fields) if(!zip) return false; var coords = zip.getCoordinates(); var image = new Element('img', { 'src': this.options.progressURL, 'id': 'addyajax_progress' }); image.setStyles({ 'position': 'absolute', 'top': coords.top + this.options.progressPosition[1], 'left': coords.left + coords.width + this.options.progressPosition[0], 'z-index': 1000 }); image.injectInside(document.body); if(this.options.animate) var fade = new Fx.Style(image, 'opacity').set(0).start(1); }, find: function(needle, haystack) { for(var i = 0; i < haystack.length; i++) if(haystack[i].getProperty('name').toLowerCase().contains(needle) || haystack[i].getProperty('id').toLowerCase().contains(needle) || haystack[i].getProperty('class').contains(needle)) return haystack[i]; return false;//new Element('input', { 'class': needle }); }, /* Function: _hideProgress Called internally by to remove the image with ID *addyajax_progress* created by <_showProgress>. */ _hideProgress: function() { if(!this.options.animate) return false; var fade = new Fx.Style('addyajax_progress', 'opacity', { onComplete: function() { $('addyajax_progress').remove.attempt(); } }).start(0); return true; }, /* Function: validate Called internally & externally to pass an address to _Skinner_ and update the form's fields accordingly. See Also: <_checkFields> & <_updateFields>. */ validate: function() { var addy = { 'streetAddress': this.address.streetAddress.value, 'apartmentNumber': this.address.apartmentNumber.value, 'zipcode': this.address.zipcode.value, 'json': 'true' } if(!this._changed()) return false; this._showProgress(); var query = { url: 'http://skinner2.qcorpssecure.com/cgi-bin/skinner.dll', query: addy, method: 'get' }; var req = new JsonP(this.options.url, { method: 'get', autoCancel: true, queryString: "gateway_call="+Json.encode(query), onFailure: function() { this._hideProgress(); this.options.onFailure("Failed to Validate Address."); }.bind(this), onComplete: function(addy) { this._hideProgress(); if(addy.fields.errorcode.value != 0 && this.options.trustUSPS) return this.options.onFailure(responses.response.guess); this._updateFields(addy); this.options.onComplete(addy); }.bind(this) }).request(); } }).implement(new Options); /* Function: ZipCheck Function called by javascript in older web applications to validate address. It can be called with or without parameters. If ZipCheck is called, it instantiates on the first attempt. If _streetAddress_ is specified, it calls . > or > onkeyup="ZipCheck('address', 'apartment', 'city', 'state', 'zipcode');" > /> Parameters: streetAddress - Street Address to be validated. apartmentNumber - Apartment/Suite to be validated. city - City to be validated. state - 2-letter abbreviation (TX, WA, etc.) zipcode - 5-digit zipcode without spcaes or dashes. See Also: & . */ function ZipCheck(streetAddress, apartmentNumber, city, state, zipcode) { var street = $(streetAddress); if($defined(street.addyCheck)) return; street.addyCheck = true; var tmp = new AddyAjax({ animate: false }).addFields(streetAddress, apartmentNumber, city, state, zipcode); }