docs: function Address(domMap, formObj, addressID, match) {
  		// PRIVATE MEMBERS //
  
  		var self = this; // Pointer to self so that internal functions can reference its parent
  		var fields; // Data values and references to DOM elements
  		var id; // An ID by which to classify this address on the DOM
  
  		var state = "accepted"; // Can be: "accepted" or "changed"
  		// Example of a field:  street: { value: "123 main", dom: DOMElement, undo: "123 mai"}
  		// Some of the above fields will only be mapped manually, not automatically.
  
  		// Private method that actually changes the address. The keepState parameter is
  		// used by the results of verification after an address is chosen; (or an "undo"
  		// on a freeform address), otherwise an infinite loop of requests is executed
  		// because the address keeps changing! (Set "suppressAutoVerify" to true when coming from the "Undo" link)
  		var doSet = function (key, value, updateDomElement, keepState, sourceEvent, suppressAutoVerify) {
  			if (!arrayContains(acceptableFields, key) || !domMap[key]) { // Skip "id" and other unacceptable fields, also skip fields that weren't included in mapping
  				return false;
  			}
  
  			if (!fields[key])
  				fields[key] = {};
  
  			if (typeof fields[key].dom !== "undefined" && fields[key].dom.tagName === "SELECT" && key !== "administrative_area" && fields[key].dom.selectedIndex >= 0) {
  				value = fields[key].dom[fields[key].dom.selectedIndex].text.replace(/<|>/g, "");
  			} else {
  				value = value.replace(/<|>/g, ""); // prevents script injection attacks (< and > aren't in addresses, anyway)
  			}
  
  			var differentVal = fields[key].value != value;
  
  			fields[key].undo = fields[key].value || "";
  			fields[key].value = value;
  
  			if (fields[key].dom && updateDomElement) {
  				if (fields[key].dom.tagName === "INPUT") {
  					$(fields[key].dom).val(value);
  				} else if (fields[key].dom.tagName === "SELECT" && key === "administrative_area" && self.isDomestic()) {
  					var selectedVal = "";
  					$(fields[key].dom).find("option").each(function () {
  						if ($(this).text().toUpperCase() === value) {
  							selectedVal = $(this).val();
  							return false;
  						} else {
  							for (var stateName in allStatesByName) {
  								if (allStatesByName[stateName] === value && stateName === $(this).text().toUpperCase()) {
  									selectedVal = $(this).val();
  									return false;
  								}
  							}
  						}
  					});
  					$(fields[key].dom).val(selectedVal);
  				}
  			}
  
  			var eventMeta = {
  				sourceEvent: sourceEvent, // may be undefined
  				field: key,
  				address: self,
  				value: value,
  				suppressAutoVerification: suppressAutoVerify || false
  			};
  
  			if (differentVal && !keepState) {
  				ui.unmarkAsValid(self);
  				var uiTag = config.ui && config.smartyTag ? $(".smarty-ui .smarty-tag.smarty-addr-" + id) : undefined;
  				if (config.target.indexOf("US") >= 0 && config.target.indexOf("INTERNATIONAL") < 0) {
  					if (self.isDomestic()) {
  						if (uiTag && !uiTag.is(":visible"))
  							uiTag.show(); // Show checkmark tag if address is in US
  						self.unaccept();
  						trigger("AddressChanged", eventMeta);
  					} else {
  						if (uiTag && uiTag.is(":visible"))
  							uiTag.hide(); // Hide checkmark tag if address is non-US
  						self.accept({
  							address: self
  						}, false);
  					}
  				} else if (config.target.indexOf("INTERNATIONAL") >= 0 && config.target.indexOf("US") < 0) {
  					if (uiTag && !uiTag.is(":visible"))
  						uiTag.show(); // Show checkmark tag if address is in US
  					self.unaccept();
  					trigger("AddressChanged", eventMeta);
  				} else if (config.target.indexOf("US") >= 0 && config.target.indexOf("INTERNATIONAL") >= 0) {
  					if (uiTag && !uiTag.is(":visible"))
  						uiTag.show(); // Show checkmark tag if address is in US
  					self.unaccept();
  					trigger("AddressChanged", eventMeta);
  				}
  			}
  
  			return true;
  		};
  
  		// PUBLIC MEMBERS //
  
  		this.form = formObj; // Reference to the parent form object (NOT THE DOM ELEMENT)
  		this.match = match; // Determines how matches are made and what kind of results are returned.
  		this.verifyCount = 0; // Number of times this address was submitted for verification
  		this.lastField; // The last field found (last to appear in the DOM) during mapping, or the order given
  		this.active = true; // If true, verify the address. If false, pass-thru entirely.
  		this.lastStreetInput = ""; // Used by autocomplete to detect changes
  
  		// Constructor-esque functionality (save the fields in this address object)
  		this.load = function (domMap, addressID) {
  			fields = {};
  			id = addressID ? addressID.replace(/[^a-z0-9_\-]/ig, "") : randomInt(1, 99999); // Strips non-selector-friendly characters
  
  			if (typeof domMap === "object") { // can be an actual map to DOM elements or just field/value data
  				// Find the last field likely to appear on the DOM (used for UI attachments)
  				this.lastField = domMap[Object.keys(domMap)[Object.keys(domMap).length - 1]];
  
  				var isEmpty = true; // Whether the address has data in it (pre-populated) -- first assume it is empty.
  
  				for (var prop in domMap) {
  					if (domMap.hasOwnProperty(prop)) {
  						if (!arrayContains(acceptableFields, prop)) // Skip "id" and any other unacceptable field
  							continue;
  
  						if (typeof domMap[prop] == "object" && domMap[prop].getBoundingClientRect().top > this.lastField.getBoundingClientRect().top) {
  							this.lastField = domMap[prop];
  						}
  
  						var elem, val, elemArray, isData;
  						try {
  							elem = $(domMap[prop]);
  							elemArray = elem.toArray();
  							isData = elemArray ? elemArray.length == 0 : false;
  						} catch (e) {
  							isData = true;
  						}
  
  						if (isData) // Didn't match an HTML element, so treat it as an address string ("street1" data) instead
  							val = domMap[prop] || "";
  						else
  							val = elem.val() || "";
  
  						fields[prop] = {};
  						fields[prop].value = val;
  						fields[prop].undo = val;
  
  						if (!isData) {
  							if (config.debug) {
  								$(elem).addClass("smarty-debug-input");
  								elem.attr("placeholder", prop + ":" + id);
  							}
  							fields[prop].dom = domMap[prop];
  						}
  
  						// This has to be passed in at bind-time; they cannot be obtained at run-time
  						var data = {
  							address: this,
  							field: prop,
  							value: val
  						};
  
  						// Capture the element that is clicked on so we know whether or not to close the autocomplete UI
  						// (http://stackoverflow.com/a/11544766/4462191)
  						$(document).mousedown(function (e) {
  							// The latest element clicked
  							clicky = $(e.target);
  						});
  
  						// when "clicky == null" on blur, we know it was not caused by a click
  						// but maybe by pressing the tab key
  						$(document).mouseup(function (e) {
  							clicky = null;
  						});
  
  						$(document).keydown(function (e) {
  							typey = $(e.target);
  						});
  
  						$(document).keyup(function (e) {
  							typey = null;
  						});
  
  						// Bind the DOM element to needed events, passing in the data above
  						// NOTE: When the user types a street, city, and state, then hits Enter without leaving
  						// the state field, this change() event fires before the form is submitted, and if autoVerify is
  						// on, the verification will not invoke form submit, because it didn't come from a form submit.
  						// This is known behavior and is actually proper functioning in this uncommon edge case.
  						!isData && $(domMap[prop]).change(data, actionsOnChange);
  					}
  				}
  
  				var address = this;
  				$(domMap.address1, domMap.freeform).blur(function () {
  					ui.hideAutocomplete(address.id());
  				});
  
  				state = "changed";
  			}
  		};
  
  		// Run the "constructor" to load up the address
  		this.load(domMap, addressID);
  
  		this.set = function (key, value, updateDomElement, keepState, sourceEvent, suppressAutoVerify) {
  			if (typeof value !== "undefined") {
  				if (typeof key === "string" && arguments.length >= 2)
  					return doSet(key, value, updateDomElement, keepState, sourceEvent, suppressAutoVerify);
  				else if (typeof key === "object") {
  					var successful = true;
  					for (var prop in key) {
  						if (key.hasOwnProperty(prop)) {
  							successful = doSet(prop, key[prop], updateDomElement, keepState, sourceEvent, suppressAutoVerify) ? successful : false;
  						}
  					}
  					return successful;
  				}
  			}
  		};
  
  		this.replaceWith = function (resp, updateDomElement, e) {
  			// Given the response from an API request associated with this address,
  			// replace the values in the address... and if updateDomElement is true,
  			// then change the values in the fields on the page accordingly.
  
  			if (typeof resp === "array" && resp.length > 0)
  				resp = resp[0];
  
  			// Sent via US api
  			if (typeof resp.candidate_index != "undefined") {
  				if (self.isFreeform()) {
  					var singleLineAddr = (resp.addressee ? resp.addressee + ", " : "") +
  						(resp.delivery_line_1 ? resp.delivery_line_1 + ", " : "") +
  						(resp.delivery_line_2 ? resp.delivery_line_2 + ", " : "") +
  						(resp.components.urbanization ? resp.components.urbanization + ", " : "") +
  						(resp.last_line ? resp.last_line : "");
  					var fieldKey = "freeform";
  					if (fields.address1) {
  						fieldKey = "address1";
  					}
  					self.set(fieldKey, singleLineAddr, updateDomElement, true, e, false);
  				} else {
  					self.set("organization", resp.addressee, updateDomElement, true, e, false);
  					self.set("address1", resp.delivery_line_1, updateDomElement, true, e, false);
  					if (resp.delivery_line_2)
  						self.set("address2", resp.delivery_line_2, updateDomElement, true, e, false); // Rarely used; must otherwise be blank.
  					else
  						self.set("address2", "", updateDomElement, true, e, false);
  					self.set("locality", resp.components.city_name, updateDomElement, true, e, false);
  					self.set("administrative_area", resp.components.state_abbreviation, updateDomElement, true, e, false);
  					self.set("postal_code", resp.components.zipcode + "-" + resp.components.plus4_code, updateDomElement, true, e, false);
  				}
  				self.set("address3", "", updateDomElement, true, e, false);
  				self.set("address4", "", updateDomElement, true, e, false);
  				self.set("country", "USA", updateDomElement, true, e, false);
  			} else { // Sent via international API
  				if (self.isFreeform()) {
  					var singleLineAddr = (resp.organization ? resp.organization + ", " : "") +
  						(resp.address1 ? resp.address1 : "") +
  						(resp.address2 ? ", " + resp.address2 : "") +
  						(resp.address3 ? ", " + resp.address3 : "") +
  						(resp.address4 ? ", " + resp.address4 : "") +
  						(resp.address5 ? ", " + resp.address5 : "") +
  						(resp.address6 ? ", " + resp.address6 : "") +
  						(resp.address7 ? ", " + resp.address7 : "") +
  						(resp.address8 ? ", " + resp.address8 : "") +
  						(resp.address9 ? ", " + resp.address9 : "") +
  						(resp.address10 ? ", " + resp.address10 : "") +
  						(resp.address11 ? ", " + resp.address11 : "") +
  						(resp.address12 ? ", " + resp.address12 : "");
  					var countryLine = resp.components.country_iso_3 ? resp.components.country_iso_3 : "";
  					self.set("freeform", singleLineAddr, updateDomElement, true, e, false);
  					self.set("country", countryLine, updateDomElement, true, e, false);
  				} else {
  					self.set("organization", resp.organization, updateDomElement, true, e, false);
  					self.set("locality", resp.components.locality, updateDomElement, true, e, false);
  					self.set("administrative_area", resp.components.administrative_area, updateDomElement, true, e, false);
  					if (resp.components.postal_code_short) {
  						var fullPostalCode = resp.components.postal_code_short;
  						if (resp.components.postal_code_extra)
  							fullPostalCode = fullPostalCode + "-" + resp.components.postal_code_extra;
  						self.set("postal_code", fullPostalCode, updateDomElement, true, e, false);
  					}
  					removeComponentsFromAddressLines(resp);
  					if (this.getDomFields().address4) {
  						self.set("address1", resp.address1, updateDomElement, true, e, false);
  						self.set("address2", resp.address2, updateDomElement, true, e, false);
  						self.set("address3", resp.address3, updateDomElement, true, e, false);
  
  						var addressLine4 = resp.address4;
  						addressLine4 = addAddressLine(addressLine4, resp.address5, resp.address6);
  						addressLine4 = addAddressLine(addressLine4, resp.address6, resp.address7);
  						addressLine4 = addAddressLine(addressLine4, resp.address7, resp.address8);
  						addressLine4 = addAddressLine(addressLine4, resp.address8, resp.address9);
  						addressLine4 = addAddressLine(addressLine4, resp.address9, resp.address10);
  						addressLine4 = addAddressLine(addressLine4, resp.address10, resp.address11);
  						addressLine4 = addAddressLine(addressLine4, resp.address11, resp.address12);
  						self.set("address4", addressLine4, updateDomElement, true, e, false);
  					} else if (this.getDomFields().address3) {
  						self.set("address1", resp.address1, updateDomElement, true, e, false);
  						self.set("address2", resp.address2, updateDomElement, true, e, false);
  
  						var addressLine3 = resp.address3;
  						addressLine3 = addAddressLine(addressLine3, resp.address4, resp.address5);
  						addressLine3 = addAddressLine(addressLine3, resp.address5, resp.address6);
  						addressLine3 = addAddressLine(addressLine3, resp.address6, resp.address7);
  						addressLine3 = addAddressLine(addressLine3, resp.address7, resp.address8);
  						addressLine3 = addAddressLine(addressLine3, resp.address8, resp.address9);
  						addressLine3 = addAddressLine(addressLine3, resp.address9, resp.address10);
  						addressLine3 = addAddressLine(addressLine3, resp.address10, resp.address11);
  						addressLine3 = addAddressLine(addressLine3, resp.address11, resp.address12);
  						self.set("address3", addressLine3, updateDomElement, true, e, false);
  					} else if (this.getDomFields().address2) {
  						self.set("address1", resp.address1, updateDomElement, true, e, false);
  
  						var addressLine2 = resp.address2;
  						addressLine2 = addAddressLine(addressLine2, resp.address3, resp.address4);
  						addressLine2 = addAddressLine(addressLine2, resp.address4, resp.address5);
  						addressLine2 = addAddressLine(addressLine2, resp.address5, resp.address6);
  						addressLine2 = addAddressLine(addressLine2, resp.address6, resp.address7);
  						addressLine2 = addAddressLine(addressLine2, resp.address7, resp.address8);
  						addressLine2 = addAddressLine(addressLine2, resp.address8, resp.address9);
  						addressLine2 = addAddressLine(addressLine2, resp.address9, resp.address10);
  						addressLine2 = addAddressLine(addressLine2, resp.address10, resp.address11);
  						addressLine2 = addAddressLine(addressLine2, resp.address11, resp.address12);
  						self.set("address2", addressLine2, updateDomElement, true, e, false);
  					} else if (this.getDomFields().address1) {
  						var addressLine1 = resp.address1;
  						addressLine1 = addAddressLine(addressLine1, resp.address2, resp.address3);
  						addressLine1 = addAddressLine(addressLine1, resp.address3, resp.address4);
  						addressLine1 = addAddressLine(addressLine1, resp.address4, resp.address5);
  						addressLine1 = addAddressLine(addressLine1, resp.address5, resp.address6);
  						addressLine1 = addAddressLine(addressLine1, resp.address6, resp.address7);
  						addressLine1 = addAddressLine(addressLine1, resp.address7, resp.address8);
  						addressLine1 = addAddressLine(addressLine1, resp.address8, resp.address9);
  						addressLine1 = addAddressLine(addressLine1, resp.address9, resp.address10);
  						addressLine1 = addAddressLine(addressLine1, resp.address10, resp.address11);
  						addressLine1 = addAddressLine(addressLine1, resp.address11, resp.address12);
  						self.set("address1", addressLine1, updateDomElement, true, e, false);
  					}
  					self.set("country", resp.components.country_iso_3, updateDomElement, true, e, false);
  				}
  			}
  		};
  
  		var removeComponentFromAddressLine = function (addressComponent, componentName, addressLineComponent, resp) {
  			if (addressComponent.indexOf(componentName) !== -1) {
  				var regex = new RegExp(resp.components[componentName], "g");
  				var newAddressLine = resp[addressLineComponent].replace(regex, "");
  				resp[addressLineComponent] = newAddressLine;
  			}
  		};
  
  		var removeExtraWhitespace = function (addressLineComponent, resp) {
  			var addressLine = resp[addressLineComponent];
  
  			removeLeadingWhitespace();
  			removeTrailingWhitespace();
  			replaceMultipleWhitespaceWithSingle();
  			resp[addressLineComponent] = addressLine;
  
  			function removeLeadingWhitespace() {
  				addressLine = addressLine.replace(/^\s+/g, "");
  			}
  
  			function removeTrailingWhitespace() {
  				addressLine = addressLine.replace(/\s+$/g, "");
  			}
  
  			function replaceMultipleWhitespaceWithSingle() {
  				addressLine = addressLine.replace(/\s+/g, " ");
  			}
  		};
  
  		var emptyLastNonEmptyAddressLine = function (resp) {
  			for (var lineNumber = 12; lineNumber >= 1; lineNumber--) {
  				var addressLineComponent = "address" + lineNumber;
  
  				if (resp.hasOwnProperty(addressLineComponent) && resp[addressLineComponent] !== "") {
  					resp[addressLineComponent] = "";
  					return;
  				}
  			}
  		};
  
  		var removeComponentsFromAddressLines = function (resp) {
  			if (resp.metadata.hasOwnProperty("address_format")) {
  				var addressFormatLines = resp.metadata.address_format.split("|");
  
  				for (var addressLineNumber in addressFormatLines) {
  					var componentsToRemove = [
  						"locality",
  						"administrative_area",
  						"postal_code",
  						"country"
  					];
  					var addressComponent = addressFormatLines[addressLineNumber];
  					var lineNumberAsInt = parseInt(addressLineNumber) + 1;
  					var addressLineComponent = "address" + lineNumberAsInt;
  
  					componentsToRemove.map(function (componentName) {
  						removeComponentFromAddressLine(addressComponent, componentName, addressLineComponent, resp);
  					});
  					removeExtraWhitespace(addressLineComponent, resp);
  				}
  			} else {
  				emptyLastNonEmptyAddressLine(resp);
  			}
  		};
  
  		var addAddressLine = function (fullLine, addressLine, nextAddressLine) {
  			if (typeof addressLine !== "undefined" && typeof nextAddressLine !== "undefined") {
  				if (fullLine !== "") {
  					fullLine += ", ";
  				}
  				fullLine += addressLine;
  			}
  			return fullLine;
  		};
  
  		this.corners = function (lastField) {
  			var corners = {};
  
  			if (!lastField) {
  				for (var prop in fields) {
  					if (fields.hasOwnProperty(prop)) {
  						if (!fields[prop].dom || !$(fields[prop].dom).is(":visible"))
  							continue;
  
  						var dom = fields[prop].dom;
  						var offset = $(dom).offset();
  						offset.right = offset.left + $(dom).outerWidth(false);
  						offset.bottom = offset.top + $(dom).outerHeight(false);
  
  						corners.top = !corners.top ? offset.top : Math.min(corners.top, offset.top);
  						corners.left = !corners.left ? offset.left : Math.min(corners.left, offset.left);
  						corners.right = !corners.right ? offset.right : Math.max(corners.right, offset.right);
  						corners.bottom = !corners.bottom ? offset.bottom : Math.max(corners.bottom, offset.bottom);
  					}
  				}
  			} else {
  				var jqDom = $(self.lastField);
  				corners = jqDom.offset();
  				corners.right = corners.left + jqDom.outerWidth(false);
  				corners.bottom = corners.top + jqDom.outerHeight(false);
  			}
  
  			corners.width = corners.right - corners.left;
  			corners.height = corners.bottom - corners.top;
  
  			return corners;
  		};
  
  		this.verify = function (invoke, invokeFn) {
  			// Invoke contains the element to "click" on once we're all done, or is a user-defined callback function (may also be undefined)
  			if (!self.enoughInput()) {
  				return trigger("AddressWasMissingInput", {
  					address: self,
  					invoke: invoke,
  					invokeFn: invokeFn,
  					response: new Response([])
  				});
  			}
  
  			ui.disableFields(self);
  			self.verifyCount++;
  			var addrData = self.toRequestIntl();
  			var credentials = config.token ? "auth-id=" + encodeURIComponent(config.key) + "&auth-token=" +
  				encodeURIComponent(config.token) : "auth-id=" + encodeURIComponent(config.key);
  			var requestUrl = config.requestUrlInternational;
  			var headers = {};
  			if (self.isDomestic() && config.target.indexOf("US") >= 0) {
  				requestUrl = config.requestUrlUS;
  				addrData = self.toRequestUS();
  			}
  
  			var agent = "&agent=" + encodeURIComponent("smartystreets (plugin:website@" + instance.version + ")");
  			if (config.agent)
  				agent += "&agent=" + encodeURIComponent(config.agent);
  
  			var ajaxSettings = {
  				url: requestUrl + "?" + credentials + agent,
  				contentType: "jsonp",
  				data: addrData,
  				timeout: config.timeout
  			};
  
  			$.ajax($.extend({}, config.ajaxSettings, ajaxSettings))
  				.done(function (response, statusText, xhr) {
  					trigger("ResponseReceived", {
  						address: self,
  						response: new Response(response),
  						invoke: invoke,
  						invokeFn: invokeFn
  					});
  				})
  				.fail(function (xhr, statusText) {
  					var indexOfCountry = -1;
  					if (xhr.responseText) {
  						indexOfCountry = xhr.responseText.split("\n")[0].indexOf("country");
  					}
  					if (xhr.status === 422 && indexOfCountry > -1) {
  						return trigger("CountryWasInvalid", {
  							address: self,
  							response: new Response([]),
  							invoke: invoke,
  							invokeFn: invokeFn
  						});
  					} else {
  						trigger("RequestTimedOut", {
  							address: self,
  							status: statusText,
  							invoke: invoke,
  							invokeFn: invokeFn
  						});
  					}
  					self.verifyCount--; // Address verification didn't actually work, so don't count it
  				});
  
  			// Remember, the above callbacks happen later and this function is
  			// executed immediately afterward, probably before a response is received.
  			trigger("RequestSubmitted", {
  				address: self
  			});
  		};
  
  		this.enoughInput = function () {
  			// Checks for state dropdown
  			var stateText;
  			if (fields.administrative_area) {
  				stateText = fields.administrative_area.value;
  				if (fields.administrative_area.dom !== undefined && fields.administrative_area.dom.length !== undefined) {
  					if (fields.administrative_area.dom.selectedIndex < 1)
  						stateText = "";
  					else
  						stateText = fields.administrative_area.dom.options[fields.administrative_area.dom.selectedIndex].text;
  				}
  			}
  
  			if (fields.country && !fields.country.value) {
  				return false;
  			}
  			if (fields.freeform) {
  				return fields.freeform.value;
  			}
  			if (fields.address1 && !fields.address1.value) {
  				return false;
  			}
  
  			var postalCodeIsPresentAndHasValue = !!(fields.postal_code && fields.postal_code.value);
  			var administrativeAreaIsPresentAndHasValue = !!(fields.administrative_area && stateText.length > 0);
  			var localityIsPresentAndHasValue = !!(fields.locality && fields.locality.value);
  
  			return postalCodeIsPresentAndHasValue || (localityIsPresentAndHasValue && administrativeAreaIsPresentAndHasValue);
  		};
  
  		this.toRequestIntl = function () {
  			var obj = {};
  			if (fields.hasOwnProperty("freeform") &&
  				fields.hasOwnProperty("address1") &&
  				fields.hasOwnProperty("locality") &&
  				fields.hasOwnProperty("administrative_area") &&
  				fields.hasOwnProperty("postal_code")) {
  				delete fields.address1;
  				delete fields.locality;
  				delete fields.administrative_area;
  				delete fields.postal_code;
  			}
  			for (var key in fields) {
  				if (fields.hasOwnProperty(key)) {
  					var keyval = {};
  					if (fields[key].dom && fields[key].dom.tagName === "SELECT" && fields[key].dom.selectedIndex >= 0) {
  						keyval[key] = fields[key].dom[fields[key].dom.selectedIndex].text;
  					} else {
  						keyval[key] = fields[key].value.replace(/\r|\n/g, " "); // Line breaks to spaces
  					}
  					$.extend(obj, keyval);
  				}
  			}
  			obj.geocode = config.geocode;
  			return obj;
  		};
  
  		this.toRequestUS = function () {
  			var obj = {};
  			if (fields.address1 && fields.address1.dom && fields.address1.dom.value) {
  				obj.street = fields.address1.dom.value;
  			} else if (fields.address1 && fields.address1.value) { // Covers a special case where the user calls .verify on the instance
  				obj.street = fields.address1.value;
  			}
  			if (fields.address2 && fields.address2.dom && fields.address2.dom.value) {
  				obj.street2 = fields.address2.dom.value;
  			}
  			if (fields.address3 && fields.address3.dom && fields.address3.dom.value) {
  				if (typeof obj.street2 === "undefined") {
  					obj.street2 = fields.address3.dom.value;
  				} else {
  					obj.street2 = obj.street2 += ", " + fields.address3.dom.value;
  				}
  			}
  			if (fields.address4 && fields.address4.dom && fields.address4.dom.value) {
  				if (typeof obj.street2 === "undefined") {
  					obj.street2 = fields.address4.dom.value;
  				} else {
  					obj.street2 = obj.street2 += ", " + fields.address4.dom.value;
  				}
  			}
  			if (fields.locality && fields.locality.dom) {
  				if (fields.locality.dom.tagName === "SELECT" && fields.locality.dom.selectedIndex >= 0) {
  					obj.city = fields.locality.dom[fields.locality.dom.selectedIndex].text;
  				} else {
  					obj.city = fields.locality.dom.value;
  				}
  			}
  			if (fields.administrative_area && fields.administrative_area.dom.value) {
  				if (fields.administrative_area.dom.tagName === "SELECT" && fields.administrative_area.dom.selectedIndex >= 0) {
  					obj.state = fields.administrative_area.dom[fields.administrative_area.dom.selectedIndex].text;
  				} else {
  					obj.state = fields.administrative_area.dom.value;
  				}
  			}
  			if (fields.postal_code && fields.postal_code.dom.value) {
  				obj.zipcode = fields.postal_code.dom.value;
  			}
  			if (fields.freeform && fields.freeform.dom.value) {
  				obj.street = fields.freeform.dom.value;
  			}
  			if (typeof this.secondary !== "undefined") {
  				obj.secondary = this.secondary;
  				delete this.secondary;
  			}
  			if (typeof this.address1 !== "undefined") {
  				obj.street = this.address1;
  				delete this.address1;
  				if (typeof this.zipcode !== "undefined") {
  					obj.zipcode = this.zipcode;
  					delete this.zipcode;
  				}
  				delete obj.freeform;
  			}
  
  			obj.match = this.match;
  			obj.candidates = config.candidates;
  			return obj;
  		};
  
  		function getStringOfPossibleDropdown(field) {
  			if (field.dom) {
  				if (field.dom.tagName !== "SELECT") {
  					return field.dom.value + " ";
  				} else if (field.dom.selectedIndex > 0) {
  					return field.dom[field.dom.selectedIndex].text + " ";
  				}
  			}
  			return "";
  		}
  
  		this.toString = function () {
  			if (fields.freeform) {
  				return (fields.freeform ? fields.freeform.value + " " : "") + (fields.country ? fields.country.value : "");
  			} else {
  				var addrString = (fields.address1 ? fields.address1.value + " " : "") + (fields.address2 ? fields.address2.value + " " : "") +
  					(fields.address3 ? fields.address3.value + " " : "") + (fields.address4 ? fields.address4.value + " " : "") +
  					(fields.locality ? fields.locality.value + " " : "");
  				if (fields.administrative_area) {
  					addrString += getStringOfPossibleDropdown(fields.administrative_area);
  				}
  				addrString += (fields.postal_code ? fields.postal_code.value + " " : "");
  				if (fields.country) {
  					addrString += getStringOfPossibleDropdown(fields.country);
  				}
  				return addrString;
  			}
  		};
  
  		this.abort = function (event, keepAccept) {
  			keepAccept = typeof keepAccept === "undefined" ? false : keepAccept;
  			if (!keepAccept)
  				self.unaccept();
  			delete self.form.processing;
  			return suppress(event);
  		};
  
  		// Based on the properties in "fields," determines if this is a single-line address
  		this.isFreeform = function () {
  			return fields.freeform;
  		};
  
  		this.get = function (key) {
  			return fields[key] ? fields[key].value : null
  		};
  
  		this.undo = function (updateDomElement) {
  			updateDomElement = typeof updateDomElement === "undefined" ? true : updateDomElement;
  			for (var key in fields) {
  				if (fields.hasOwnProperty(key)) {
  					this.set(key, fields[key].undo, updateDomElement, false, undefined, true);
  				}
  			}
  		};
  
  		this.accept = function (data, showValid) {
  			showValid = typeof showValid === "undefined" ? true : showValid;
  			state = "accepted";
  			ui.enableFields(self);
  			if (showValid) // If user chooses original input or the request timed out, the address wasn't "verified"
  				ui.markAsValid(self);
  			trigger("AddressAccepted", data);
  		};
  
  		this.unaccept = function () {
  			state = "changed";
  			ui.unmarkAsValid(self);
  			return self;
  		};
  
  		this.getUndoValue = function (key) {
  			return fields[key].undo;
  		};
  
  		this.status = function () {
  			return state;
  		};
  
  		this.getDomFields = function () {
  			// Gets just the DOM elements for each field
  			var obj = {};
  			for (var prop in fields) {
  				if (fields.hasOwnProperty(prop)) {
  					var ext = {};
  					ext[prop] = fields[prop].dom;
  					$.extend(obj, ext);
  				}
  			}
  			return obj;
  		};
  
  		this.hasDomFields = function () {
  			for (var prop in fields) {
  				if (fields.hasOwnProperty(prop) && fields[prop].dom) {
  					return true;
  				}
  			}
  		};
  
  		this.isDomestic = function () {
  			var countryValue = "";
  			if (fields.country && fields.country.dom) {
  				countryValue = $(fields.country.dom).val();
  				var selectedOption = $(fields.country.dom).children(":selected");
  				if (selectedOption.length > 0 && selectedOption.index() > 0)
  					countryValue = selectedOption.text();
  			}
  			countryValue = countryValue.toUpperCase().replace(/\.|\s|\(|\)|\\|\/|-/g, "");
  			var usa = ["", "0", "1", "US", "USA", "USOFA", "USOFAMERICA", "AMERICAN", // 1 is AmeriCommerce
  				"UNITEDSTATES", "UNITEDSTATESAMERICA", "UNITEDSTATESOFAMERICA", "AMERICA",
  				"840", "223", "AMERICAUNITEDSTATES", "AMERICAUS", "AMERICAUSA", "UNITEDSTATESUS",
  				"AMERICANSAMOA", "AMERIKASĀMOA", "AMERIKASAMOA", "ASM",
  				"MICRONESIA", "FEDERALSTATESOFMICRONESIA", "FEDERATEDSTATESOFMICRONESIA", "FSM",
  				"GUAM", "GM",
  				"MARSHALLISLANDS", "MHL",
  				"NORTHERNMARIANAISLANDS", "NMP",
  				"PALAU", "REPUBLICOFPALAU", "BELAU", "PLW",
  				"PUERTORICO", "COMMONWEALTHOFPUERTORICO", "PRI",
  				"UNITEDSTATESVIRGINISLANDS", "VIR"
  			]; // 840 is ISO: 3166; and 223 is some shopping carts
  			return arrayContains(usa, countryValue) || fields.country.value == "-1";
  		};
  
  		this.autocompleteVisible = function () {
  			return config.ui && config.autocomplete && $(".smarty-autocomplete.smarty-addr-" + self.id()).is(":visible");
  		};
  
  		this.id = function () {
  			return id;
  		};
  	}