docs: function UI() {
  		var submitHandler; // Function which is later bound to handle form submits
  		var formDataProperty = "smartyForm"; // Indicates whether we've stored the form already
  		var autocompleteResponse; // The latest response from the autocomplete server
  		var autocplCounter = 0; // A counter so that only the most recent JSONP request is used
  		var autocplRequests = []; // The array that holds autocomplete requests in order
  		var loaderWidth = 24,
  			loaderHeight = 8; // TODO: Update these if the image changes
  		var uiCss = "<style>" + ".smarty-dots { display: none; position: absolute; z-index: 999; width: " +
  			loaderWidth + "px; height: " + loaderHeight + "px; " +
  			"background-image: url(\"data:image/gif;base64,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\"); }" +
  			".smarty-ui { position: absolute; z-index: 999; text-shadow: none; text-align: left; text-decoration: none; }" +
  			".smarty-popup { padding: 20px 30px; background: #FFFFFF; display: inline-block;" +
  			"box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); }" + ".smarty-popup-header { " +
  			"text-transform: uppercase; font: bold 10pt/1em \"Helvetica\", sans-serif; color: #CEA737; padding: 12px 0px 0px; text-align: center;}" +
  			".smarty-popup-ambiguous-header { color: #CEA737; }" + ".smarty-popup-invalid-header { color: #D0021B; }" +
  			".smarty-popup-missing-input-header { color: #CEA737; }" +
  			".smarty-popup-typed-address{ font-family: sans-serif; font-size: 10pt; font-style: italic; text-align: center; margin: 15px 0px;}" +
  			".smarty-popup-secondary-number-form { font-family: sans-serif; margin: 10px auto 20px; padding: 0; border: none; float: none; background: none; width: auto; text-align: center; }" +
  			"#smarty-popup-secondary-number-input-box {width: 200px; font-size: 11pt; margin-bottom: 10px; text-align: center;}" +
  			"#smarty-popup-secondary-number-form-submit-button { line-height: 23px; background: #606060; border: none; color: #fff; border-radius: 3px;" +
  			" padding: 2px 15px; font-size: 11pt; width: 215px; }" + "#smarty-popup-secondary-number-form-submit-button:hover { background: #333; }" +
  			".smarty-hr { margin-bottom: 15px; }" +
  			".smarty-choice-list .smarty-choice { background: #FFF; padding: 10px 15px; color: #9B9B9B; margin-bottom: 10px; }" +
  			".smarty-choice { display: block; font: 300 10pt/1em sans-serif; text-decoration: none; " +
  			"border: 1px solid #D4D4D4; }" + ".smarty-choice-list .smarty-choice:hover { color: #333; " +
  			"background: #F7F7F7; text-decoration: none; border: 1px solid #333 }" + ".smarty-choice-alt { " +
  			"background: inherit; clear: both; }" + ".smarty-choice-alt" +
  			" .smarty-choice-abort, .smarty-choice-override { padding: 8px 10px; color: #FFF; " +
  			"font-size: 10pt; text-decoration: none; background: #606060; border-radius: 3px; border: none; }" +
  			" .smarty-choice-override { float: right }" +
  			" .smarty-choice-abort { float: left }" + ".smarty-choice-alt " + ".smarty-choice:first-child { border-top: 0; }" +
  			".smarty-choice-abort:hover { background: #333; }" +
  			".smarty-choice-override:hover { background: #333; }" + ".smarty-tag { position: absolute; " +
  			"display: block; overflow: hidden; font: 15px/1.2em sans-serif; text-decoration: none; width: 20px; " +
  			"height: 18px; border-radius: 25px; transition: all .25s; -moz-transition: all .25s; " +
  			"-webkit-transition: all .25s; -o-transition: all .25s; }" + ".smarty-tag:hover { width: 70px; " +
  			"text-decoration: none; color: #999; }" + ".smarty-tag:hover .smarty-tag-text " +
  			"{ color: #000; }" + ".smarty-tag-grayed { border: 1px solid #B4B4B4; " +
  			"color: #999; background: #DDD; box-shadow: inset 0 9px 15px #FFF; }" +
  			".smarty-tag-green { border: 1px solid #407513; color: #407513; " +
  			"background: #A6D187; box-shadow: inset 0 9px 15px #E3F6D5; }" + ".smarty-tag-grayed:hover " +
  			"{ border-color: #333; }" + ".smarty-tag-check { padding-left: 4px; " +
  			"text-decoration: none; }" + ".smarty-tag-text { font-size: 12px; position: absolute; " +
  			"top: 0; left: 16px; width: 50px; text-align: center; }" + ".smarty-autocomplete " +
  			"{ border: 1px solid #777; background: white; overflow: hidden; white-space: nowrap; " +
  			"box-shadow: 1px 1px 3px #555; }" + ".smarty-suggestion { display: block; color: #444; " +
  			"text-decoration: none; font-size: 12px; padding: 1px 5px; }" + ".smarty-active-suggestion " +
  			"{ background: #EEE; color: #000; border: none; outline: none; }" + ".smarty-no-suggestions " +
  			"{ padding: 1px 5px; font-size: 12px; color: #AAA; font-style: italic; } " +
  			".smarty-debug-input { background: #ffffcc !important; } " +
  			".smarty-debug-button { color: #4ba341 !important; } " + "</style>";
  
  		this.postMappingOperations = function () {
  			// Injects materials into the DOM, binds to form submit events, etc... very important.
  
  			if (config.ui) {
  				// Prepend CSS to head tag to allow cascading and give their style rules priority
  				$("head").prepend(uiCss);
  
  				// For each address on the page, inject the loader and "address verified" markup after the last element
  				var addresses = instance.getMappedAddresses();
  				for (var i = 0; i < addresses.length; i++) {
  					var id = addresses[i].id();
  					$("body").append("<div class=\"smarty-ui\"><div title=\"Loading...\" class=\"smarty-dots smarty-addr-" + id + "\"></div></div>");
  					if (config.smartyTag) {
  						var offset = uiTagOffset(addresses[i].corners(true));
  						$("body").append("<div class=\"smarty-ui\" style=\"top: " + offset.top + "px; left: " + offset.left +
  							"px;\"><a href=\"javascript:\" class=\"smarty-tag smarty-tag-grayed smarty-addr-" + id +
  							"\" title=\"Address not verified. Click to verify.\" data-addressid=\"" + id +
  							"\"><span class=\"smarty-tag-check\">&#10003;</span><span class=\"smarty-tag-text\">Verify</span></a></div>");
  					}
  
  					// Move the UI elements around when browser window is resized
  					$(window).on("resize.smarty", {
  						addr: addresses[i]
  					}, function (e) {
  						var addr = e.data.addr;
  						if (config.smartyTag) {
  							var offset = uiTagOffset(addr.corners(true)); // Position of lil' tag
  							$(".smarty-tag.smarty-addr-" + addr.id())
  								.parent(".smarty-ui")
  								.css("top", offset.top + "px")
  								.css("left", offset.left + "px");
  						}
  
  						var addrOffset = addr.corners(); // Position of any popup windows
  						$(".smarty-popup.smarty-addr-" + addr.id())
  							.parent(".smarty-ui")
  							.css("top", addrOffset.top + "px")
  							.css("left", addrOffset.left + "px");
  
  						if (config.autocomplete) { // Position of autocomplete boxes
  							var containerUi = $(".smarty-autocomplete.smarty-addr-" + addr.id()).closest(".smarty-ui");
  							var domFields = addr.getDomFields();
  							var mainInput = "";
  							if (domFields.address1) {
  								mainInput = "address1";
  							} else if (domFields.freeform) {
  								mainInput = "freeform";
  							}
  							if (mainInput !== "") {
  								containerUi.css({
  									"left": $(domFields[mainInput]).offset().left + "px",
  									"top": ($(domFields[mainInput]).offset().top + $(domFields[mainInput]).outerHeight(false)) + "px"
  								});
  							}
  						}
  					});
  				}
  
  				if (config.smartyTag) {
  					$("body").on("click", ".smarty-tag-grayed", function (e) {
  						// "Verify" clicked -- manually invoke verification
  						var addrId = $(this).data("addressid");
  						instance.verify(addrId);
  					});
  
  					$("body").on("click", ".smarty-undo", function (e) {
  						// "Undo" clicked -- replace field values with previous input
  						var addrId = $(this).parent().data("addressid");
  						var addr = instance.getMappedAddressByID(addrId);
  						addr.undo(true);
  						// If fields are re-mapped after an address was verified, it loses its "accepted" status even if no values were changed.
  						// Thus, in some rare occasions, the undo link and the "verified!" text may not disappear when the user clicks "Undo",
  						// The undo functionality still works in those cases, but with no visible changes, the address doesn't fire "AddressChanged"...
  					});
  				}
  
  				// Prepare autocomplete UI
  				if (config.autocomplete && config.key) {
  					// For every mapped address, wire up autocomplete
  					for (var i = 0; i < forms.length; i++) {
  						var f = forms[i];
  
  						for (var j = 0; j < f.addresses.length; j++) {
  							var addr = f.addresses[j];
  							var domFields = addr.getDomFields();
  							var mainInput = "";
  							if (domFields.address1) {
  								mainInput = "address1";
  							} else if (domFields.freeform) {
  								mainInput = "freeform";
  							}
  							if (mainInput !== "") {
  								var strField = $(domFields[mainInput]);
  								var containerUi = $("<div class=\"smarty-ui\"></div>");
  								var autoUi = $("<div class=\"smarty-autocomplete\"></div>");
  
  								autoUi.addClass("smarty-addr-" + addr.id());
  								containerUi.data("addrID", addr.id());
  								containerUi.append(autoUi);
  
  								containerUi.css({
  									"position": "absolute",
  									"left": strField.offset().left + "px",
  									"top": (strField.offset().top + strField.outerHeight(false)) + "px"
  								});
  
  								containerUi.hide().appendTo("body");
  
  								containerUi.on("mousedown", ".smarty-suggestion", {
  									addr: addr,
  									containerUi: containerUi
  								}, function (event) {
  									var sugg = autocompleteResponse.suggestions[$(this).data("suggIndex")];
  									useAutocompleteSuggestion(event.data.addr, sugg, event.data.containerUi);
  								});
  
  								containerUi.on("mouseover", ".smarty-suggestion", function () {
  									$(".smarty-active-suggestion").removeClass("smarty-active-suggestion");
  									$(this).addClass("smarty-active-suggestion");
  								});
  
  								containerUi.on("mouseleave", ".smarty-active-suggestion", function () {
  									$(this).removeClass("smarty-active-suggestion");
  								});
  
  								strField.keydown({
  									containerUi: containerUi,
  									addr: addr
  								}, function (event) {
  									var suggContainer = $(".smarty-autocomplete", event.data.containerUi);
  									var currentChoice = $(".smarty-active-suggestion:visible", suggContainer).first();
  									var choiceSelectionIsNew = false;
  
  									if (event.keyCode == 9) { // Tab key
  										if (currentChoice.length > 0) {
  											var domFields = event.data.addr.getDomFields();
  											if (domFields.zipcode)
  												$(domFields.zipcode).focus();
  											else
  												$(domFields[mainInput]).blur();
  											useAutocompleteSuggestion(event.data.addr, autocompleteResponse.suggestions[currentChoice.data("suggIndex")], event.data.containerUi);
  											return addr.isFreeform() ? true : suppress(event);
  										} else
  											ui.hideAutocomplete(event.data.addr.id());
  									} else if (event.keyCode == 40) { // Down arrow
  										if (!currentChoice.hasClass("smarty-suggestion")) {
  											currentChoice = $(".smarty-suggestion", suggContainer).first().mouseover();
  											choiceSelectionIsNew = true;
  										}
  
  										if (!choiceSelectionIsNew) {
  											if (currentChoice.next(".smarty-addr-" + event.data.addr.id() + " .smarty-suggestion").length > 0)
  												currentChoice.next(".smarty-suggestion").mouseover();
  											else
  												currentChoice.removeClass("smarty-active-suggestion");
  										}
  
  										moveCursorToEnd(this);
  									} else if (event.keyCode == 38) { // Up arrow
  										if (!currentChoice.hasClass("smarty-suggestion")) {
  											currentChoice = $(".smarty-suggestion", suggContainer).last().mouseover();
  											choiceSelectionIsNew = true;
  										}
  
  										if (!choiceSelectionIsNew) {
  											if (currentChoice.prev(".smarty-addr-" + event.data.addr.id() + " .smarty-suggestion").length > 0)
  												currentChoice.prev(".smarty-suggestion").mouseover();
  											else
  												currentChoice.removeClass("smarty-active-suggestion");
  										}
  
  										moveCursorToEnd(this);
  									}
  								});
  
  								// Flip the on switch!
  								strField.keyup({
  									form: f,
  									addr: addr,
  									streetField: strField,
  									containerUi: containerUi
  								}, doAutocomplete);
  							}
  						}
  
  						$(document).keyup(function (event) {
  							if (event.keyCode == 27) // Esc key
  								$(".smarty-autocomplete").closest(".smarty-ui").hide();
  						});
  					}
  
  					// Try .5 and 1.5 seconds after the DOM loads to re-position UI elements; hack for Firefox.
  					setTimeout(function () {
  						$(window).trigger("resize.smarty");
  					}, 500);
  					setTimeout(function () {
  						$(window).trigger("resize.smarty");
  					}, 1500);
  				}
  
  			}
  
  			if (config.submitVerify) {
  				// Bind to form submits through form submit and submit button click events
  				for (var i = 0; i < forms.length; i++) {
  					var f = forms[i];
  
  					submitHandler = function (e) {
  						// Don't invoke verification if it's already processing or autocomplete is open and the user was pressing Enter to use a suggestion
  						if ((e.data.form && e.data.form.processing) || $(".smarty-active-suggestion:visible").length > 0)
  							return suppress(e);
  
  						/*
  						 IMPORTANT!
  						 Prior to version 2.4.8, the plugin would call syncWithDom() at submit-time
  						 in case programmatic changes were made to the address input fields, including
  						 browser auto-fills. The sync function would detect those changes and force
  						 a re-verification to not let invalid addresses through. Unfortunately, this
  						 frequently caused infinite loops (runaway lookups), ultimately preventing
  						 form submission, which is unacceptable. As a safety measure to protect our
  						 customer's subscriptions, we've removed syncWithDom(). The website owner is
  						 responsible for making sure that any changes to address field values raise the
  						 "change" event on that element. Example: $("#city").val("New City").change();
  						 */
  
  						if (!e.data.form.allActiveAddressesAccepted()) {
  							// We could verify all the addresses at once, but that can overwhelm the user.
  							// An API request is usually quick, so let's do one at a time: it's much cleaner.
  							var unaccepted = e.data.form.activeAddressesNotAccepted();
  							if (unaccepted.length > 0)
  								trigger("VerificationInvoked", {
  									address: unaccepted[0],
  									invoke: e.data.invoke,
  									invokeFn: e.data.invokeFn
  								});
  							return suppress(e);
  						}
  					};
  
  					// Performs the tricky operation of uprooting existing event handlers that we have references to
  					// (either by jQuery's data cache or HTML attributes) planting ours, then laying theirs on top
  					var bindSubmitHandler = function (domElement, eventName) {
  						if (!domElement || !eventName)
  							return;
  
  						var oldHandlers = [],
  							eventsRef = $._data(domElement, "events");
  
  						// If there are previously-bound-event-handlers (from jQuery), get those.
  						if (eventsRef && eventsRef[eventName] && eventsRef[eventName].length > 0) {
  							// Get a reference to the old handlers previously bound by jQuery
  							oldHandlers = $.extend(true, [], eventsRef[eventName]);
  						}
  
  						// Turn them off...
  						$(domElement).off(eventName);
  
  						// ... then turn ours on first ...
  						$(domElement)[eventName]({
  							form: f,
  							invoke: domElement,
  							invokeFn: eventName
  						}, submitHandler);
  
  						// ... then turn on theirs last:
  						// First turn on their onclick="..." or onsubmit="..." handles...
  						if (typeof domElement["on" + eventName] === "function") {
  							var temp = domElement["on" + eventName];
  							domElement["on" + eventName] = null;
  							$(domElement)[eventName](temp);
  						}
  
  						// ... then finish up with their old jQuery handles.
  						for (var j = 0; j < oldHandlers.length; j++)
  							$(domElement)[eventName](oldHandlers[j].data, oldHandlers[j].handler);
  					};
  
  					// Take any existing handlers (bound via jQuery) and re-bind them for AFTER our handler(s).
  					var formSubmitElements = $(config.submitSelector, f.dom);
  
  					// Highlight the submit button
  					if (config.debug) {
  						for (var j = 0; j < formSubmitElements.length; j++) {
  							$(formSubmitElements[j]).addClass("smarty-debug-button");
  						}
  					}
  
  					// Form submit() events are apparently invoked by CLICKING the submit button (even jQuery does this at its core for binding)
  					// (but jQuery, when raising a form submit event with .submit() will NOT necessarily click the submit button)
  					formSubmitElements.each(function (idx) {
  						bindSubmitHandler(this, "click"); // These get fired first
  					});
  
  				}
  			}
  
  			trigger("MapInitialized");
  		};
  
  		function doAutocomplete(event) {
  			var addr = event.data.addr;
  			var streetField = event.data.streetField;
  			var input = $.trim(event.data.streetField.val());
  			var containerUi = event.data.containerUi;
  			var suggContainer = $(".smarty-autocomplete", containerUi);
  
  			if (!input) {
  				addr.lastStreetInput = input;
  				suggContainer.empty();
  				ui.hideAutocomplete(addr.id());
  			}
  
  			if (event.keyCode == 13) { // Enter/return
  				if ($(".smarty-active-suggestion:visible").length > 0)
  					useAutocompleteSuggestion(addr, autocompleteResponse.suggestions[$(".smarty-active-suggestion:visible").first().data("suggIndex")], containerUi);
  				ui.hideAutocomplete(addr.id());
  				streetField.blur();
  				return suppress(event);
  			}
  
  			if (event.keyCode == 40) { // Down arrow
  				moveCursorToEnd(streetField[0]);
  				return;
  			}
  
  			if (event.keyCode == 38) { // Up arrow
  				moveCursorToEnd(streetField[0]);
  				return;
  			}
  
  			if (!input || input == addr.lastStreetInput || !addr.isDomestic())
  				return;
  
  			addr.lastStreetInput = input; // Used so that autocomplete only fires on real changes (i.e. not just whitespace)
  			if (addr.isDomestic() && config.target.indexOf("US") >= 0) {
  				trigger("AutocompleteInvoked", {
  					containerUi: containerUi,
  					suggContainer: suggContainer,
  					streetField: streetField,
  					input: input,
  					addr: addr
  				});
  			}
  		}
  
  		this.requestAutocomplete = function (event, data) {
  			if (data.input && data.addr.isDomestic() && autocompleteResponse)
  				data.containerUi.show();
  
  			var autocplrequest = {
  				callback: function (counter, json) {
  					var patt = new RegExp("^\\w+\\s\\w+|^[A-Za-z]+$|^[A-Za-z]+\\s\\w*");
  					var filtering = patt.test(data.input);
  					autocompleteResponse = json;
  					data.suggContainer.empty();
  
  					if (!json.suggestions || json.suggestions.length == 0) {
  						data.suggContainer.html("<div class=\"smarty-no-suggestions\">" + config.noSuggestionsMessage + "</div>");
  						return;
  					}
  
  					if (config.waitForStreet && filtering == false) {
  						var message = "";
  						if (config.stateFilter || config.cityFilter || config.geolocate || config.cityStatePreference) {
  							message = "filtered";
  						} else {
  							message = "address";
  						}
  						data.suggContainer.html("<div class=\"smarty-no-suggestions\">Type more for " + message + " suggestions</div>")
  					} else {
  						for (var j = 0; j < json.suggestions.length; j++) {
  							var suggAddr = json.suggestions[j].text.replace(/<|>/g, "");
  							suggAddr = suggAddr.replace(new RegExp("(" + data.input.replace(/[#-.]|[[-^]|[?|{}]/g, "\\$&") + ")", "ig"), "<b>$1</b>");
  							var link = $("<a href=\"javascript:\" class=\"smarty-suggestion\">" + suggAddr + "</a>");
  							link.data("suggIndex", j);
  
  							data.suggContainer.append(link);
  						}
  					}
  
  					data.suggContainer.css({
  						"width": Math.max(data.streetField.outerWidth(false), 250) + "px"
  					});
  
  					data.containerUi.show();
  
  					// Delete all older callbacks so they don't get executed later because of latency
  					autocplRequests.splice(0, counter);
  				},
  				number: autocplCounter++
  			};
  
  			autocplRequests[autocplrequest.number] = autocplrequest;
  
  			var ajaxSettings = {
  				url: "https://us-autocomplete.api.smartystreets.com/suggest",
  				traditional: true,
  				dataType: "json",
  				data: {
  					"auth-id": config.key,
  					"auth-token": config.token,
  					prefix: data.input,
  					city_filter: config.cityFilter,
  					state_filter: config.stateFilter,
  					prefer: config.cityStatePreference,
  					suggestions: config.autocomplete,
  					geolocate: config.geolocate,
  					geolocate_precision: config.geolocatePrecision,
  					prefer_ratio: config.preferRatio,
  					agent: ["smartystreets (plugin:website@" + instance.version + ")", config.agent]
  				}
  			};
  
  			$.ajax($.extend({}, config.ajaxSettings, ajaxSettings)).done(function (json) {
  				trigger("AutocompleteReceived", $.extend(data, {
  					json: json,
  					autocplrequest: autocplrequest
  				}));
  			});
  		};
  
  		this.showAutocomplete = function (event, data) {
  			if (autocplRequests[data.autocplrequest.number])
  				autocplRequests[data.autocplrequest.number].callback(data.autocplrequest.number, data.json);
  		};
  
  		function useAutocompleteSuggestion(addr, suggestion, containerUi) {
  			addr.usedAutocomplete = false;
  			var domfields = addr.getDomFields();
  			ui.hideAutocomplete(addr.id()); // It's important that the suggestions are hidden before AddressChanged event fires
  
  			if (addr.isFreeform()) {
  				$(domfields.freeform).val(suggestion.text).change();
  				addr.usedAutocomplete = true;
  			} else {
  				if (domfields.postal_code) {
  					$(domfields.postal_code).val("").change();
  				}
  				if (domfields.address1)
  					$(domfields.address1).val(suggestion.street_line).change();
  				// State filled in before city so autoverify is not invoked without finishing using the suggestion
  				if (domfields.administrative_area) {
  					if (domfields.administrative_area.options) { // Checks for dropdown
  						for (var i = 0; i < domfields.administrative_area.options.length; i++) {
  							// Checks for abbreviation match and maps full state name to abbreviation
  							if (domfields.administrative_area.options[i].text.toUpperCase() === suggestion.state || allStatesByName[domfields.administrative_area.options[i].text.toUpperCase()] === suggestion.state) {
  								$(domfields.administrative_area)[0].selectedIndex = i;
  								$(domfields.administrative_area).change();
  								break;
  							}
  						}
  					} else {
  						$(domfields.administrative_area).val(suggestion.state).change();
  					}
  				}
  				if (domfields.locality) {
  					$(domfields.locality).val("").change();
  					addr.usedAutocomplete = true;
  					$(domfields.locality).val(suggestion.city).change();
  				}
  			}
  			if (domfields.country && !domfields.country.options) {
  				$(domfields.country).val("USA").change();
  			}
  			trigger("AutocompleteUsed", {
  				address: addr,
  				suggestion: suggestion
  			});
  		}
  
  		// Computes where the little checkmark tag of the UI goes, relative to the boundaries of the last field
  		function uiTagOffset(corners) {
  			return {
  				top: corners.top + corners.height / 2 - 10,
  				left: corners.right - 6
  			};
  		}
  
  		// This function is used to find and properly map elements to their field type
  		function filterDomElement(domElement, names, labels) {
  			/*
  			 Where we look to find a match, in this order:
  			 name, id, <label> tags, placeholder, title
  			 Our searches first conduct fairly liberal "contains" searches:
  			 if the attribute even contains the name or label, we map it.
  			 The names and labels we choose to find are very particular.
  			 */
  
  			var name = lowercase(domElement.name);
  			var id = lowercase(domElement.id);
  			var selectorSafeID = id.replace(/[\[|\]|\(|\)|\:|\'|\"|\=|\||\#|\.|\!|\||\@|\^|\&|\*]/g, "\\\\$&");
  			var placeholder = lowercase(domElement.placeholder);
  			var title = lowercase(domElement.title);
  
  			// First look through name and id attributes of the element, the most common
  			for (var i = 0; i < names.length; i++)
  				if (name.indexOf(names[i]) > -1 || id.indexOf(names[i]) > -1)
  					return true;
  
  			// If we can't find it in name or id, look at labels associated to the element.
  			// Webkit automatically associates labels with form elements for us. But for other
  			// browsers, we have to find them manually, which this next block does.
  			if (!("labels" in domElement)) {
  				var lbl = $("label[for=\"" + selectorSafeID + "\"]")[0] || $(domElement).parents("label")[0];
  				domElement.labels = !lbl ? [] : [lbl];
  			}
  
  			// Iterate through the <label> tags now to search for a match.
  			for (var i = 0; i < domElement.labels.length; i++) {
  				// This inner loop compares each label value with what we're looking for
  				for (var j = 0; j < labels.length; j++)
  					if ($(domElement.labels[i]).text().toLowerCase().indexOf(labels[j]) > -1)
  						return true;
  			}
  
  			// Still not found? Then look in "placeholder" or "title"...
  			for (var i = 0; i < labels.length; i++)
  				if (placeholder.indexOf(labels[i]) > -1 || title.indexOf(labels[i]) > -1)
  					return true;
  
  			// Got all the way to here? Probably not a match then.
  			return false;
  		}
  
  		// User aborted the verification process (X click or esc keyup)
  		function userAborted(uiPopup, e) {
  			// Even though there may be more than one bound, and this disables the others,
  			// this is for simplicity: and I figure, it won't happen too often.
  			// (Otherwise "Completed" events are raised by pressing Esc even if nothing is happening)
  			$(document).off("keyup");
  			$(uiPopup).slideUp(defaults.speed, function () {
  				$(this).parent(".smarty-ui").remove();
  			});
  			trigger("Completed", e.data);
  		}
  
  		// When we're done with a "pop-up" where the user chooses what to do,
  		// we need to remove all other events bound on that whole "pop-up"
  		// so that it doesn't interfere with any future "pop-ups".
  		function turnOffAllClicks(selectors) {
  			if (Array.isArray(selectors) || typeof selectors == "object") {
  				for (var selector in selectors) {
  					if (selectors.hasOwnProperty(selector)) {
  						$("body").off("click", selectors[selector]);
  					}
  				}
  			} else if (typeof selectors === "string") {
  				$("body").off("click", selectors);
  			} else {
  				alert("ERROR: Not an array, string, or object passed in to turn off all clicks");
  			}
  		}
  
  		// Utility function
  		function moveCursorToEnd(el) { // Courtesy of http://css-tricks.com/snippets/javascript/move-cursor-to-end-of-input/
  			if (typeof el.selectionStart == "number")
  				el.selectionStart = el.selectionEnd = el.value.length;
  			else if (typeof el.createTextRange != "undefined") {
  				el.focus();
  				var range = el.createTextRange();
  				range.collapse(false);
  				range.select();
  			}
  		}
  
  		// Hides the autocomplete UI
  		this.hideAutocomplete = function (addressID) {
  			$(".smarty-autocomplete.smarty-addr-" + addressID).closest(".smarty-ui").hide();
  		};
  
  		//shows the SmartyUI when activating 1 address
  		this.showSmartyUI = function (addressID) {
  			var smartyui = $(".deactivated.smarty-addr-" + addressID);
  			smartyui.push(smartyui[0].parentElement);
  			smartyui.removeClass("deactivated");
  			smartyui.addClass("activated");
  			smartyui.show();
  		};
  
  		//hides the SmartyUI when deactivating 1 address
  		this.hideSmartyUI = function (addressID) {
  			var smartyui = $(".smarty-addr-" + addressID + ":visible");
  			var autocompleteui = $(".smarty-autocomplete.smarty-addr-" + addressID);
  			smartyui.addClass("deactivated");
  			smartyui.parent().addClass("deactivated");
  			autocompleteui.addClass("deactivated");
  			smartyui.hide();
  			smartyui.parent().hide();
  			autocompleteui.hide();
  		};
  
  		// If anything was previously mapped, this resets it all for a new mapping.
  		this.clean = function () {
  			if (forms.length == 0)
  				return;
  
  			if (config.debug)
  				console.log("Cleaning up old form map data and bindings...");
  
  			// Spare none alive!
  
  			for (var i = 0; i < forms.length; i++) {
  				$(forms[i].dom).data(formDataProperty, "");
  
  				// Clean up each form's DOM by resetting the address fields to the way they were
  				for (var j = 0; j < forms[i].addresses.length; j++) {
  					var doms = forms[i].addresses[j].getDomFields();
  					for (var prop in doms) {
  						if (doms.hasOwnProperty(prop)) {
  							if (config.debug) {
  								$(doms[prop]).removeClass("smarty-debug-input").attr("placeholder", "");
  								var submitButtons = $(config.submitSelector);
  								for (var k = 0; k < submitButtons.length; k++) {
  									$(submitButtons[k]).removeClass("smarty-debug-button");
  								}
  							}
  							$(doms[prop]).off("change", actionsOnChange);
  						}
  					}
  					if (doms.address1)
  						$(doms.address1).off("keyup").off("keydown").off("blur");
  					else if (doms.freeform)
  						$(doms.freeform).off("keyup").off("keydown").off("blur");
  				}
  
  				// Unbind our form submit and submit-button click handlers
  				$.each(forms, function (idx) {
  					$(this.dom).off("submit", submitHandler);
  				});
  				$(config.submitSelector, forms[i].dom).each(function (idx) {
  					$(this).off("click", submitHandler);
  				});
  			}
  
  			$(".smarty-ui").off("click", ".smarty-suggestion").off("mouseover", ".smarty-suggestion").off("mouseleave", ".smarty-suggestion").remove();
  
  			if (config.smartyTag) {
  				$("body").off("click", ".smarty-undo");
  				$("body").off("click", ".smarty-tag-grayed");
  			}
  
  			$(window).off("resize.smarty");
  			$(document).off("keyup");
  
  			forms = [];
  			mappedAddressCount = 0;
  
  			if (config.debug)
  				console.log("Done cleaning up; ready for new mapping.");
  		};
  
  		function disableBrowserAutofill(address) {
  			//Does not disable autofill if config.autocomplete is disabled
  			if (config.autocomplete > 0) {
  				Object.keys(address).map(function (key) {
  					var elem = $(address[key]);
  					if (elem.length > 0) {
  						elem.attr("autocomplete", "smartystreets");
  					}
  				});
  			}
  		}
  
  		function addDefaultToStateDropdown(dom) {
  			if (dom.getElementsByTagName("option").length > 0) {
  				if (arrayContains(stateNames, dom.getElementsByTagName("option")[0].text.toUpperCase()) ||
  					arrayContains(stateAbbreviations, dom.getElementsByTagName("option")[0].text.toUpperCase())) {
  					var option = document.createElement("OPTION");
  					option.innerText = "Pick a state";
  					option.selected = true;
  					$(dom.getElementsByTagName("select")[0]).prepend(option);
  					$(dom).change();
  				}
  			}
  		}
  
  		// ** MANUAL MAPPING ** //
  		this.mapFields = function (map) {
  			// "map" should be an array of objects mapping field types
  			// to a field by selector, all supplied by the user.
  
  			if (config.debug)
  				console.log("Manually mapping fields given this data:", map);
  
  			this.clean();
  			var formsFound = [];
  			map = map instanceof Array ? map : [map];
  
  			for (var addrIdx in map) {
  				if (map.hasOwnProperty(addrIdx)) {
  					var address = map[addrIdx];
  
  					if (!address.country && config.target.indexOf("US") < 0)
  						continue;
  
  					// Convert selectors into actual DOM references
  
  					var match = "";
  
  					for (var fieldType in address) {
  						if (address.hasOwnProperty(fieldType)) {
  							if (fieldType == "match") {
  								match = address[fieldType];
  								delete address[fieldType];
  							}
  							if (fieldType != "id") {
  								if (!arrayContains(acceptableFields, fieldType)) { // Make sure the field name is allowed
  									if (config.debug)
  										console.log("NOTICE: Field named " + fieldType + " is not allowed. Skipping...");
  									delete address[fieldType];
  									continue;
  								}
  								var matched = $(address[fieldType]);
  								if (matched.length == 0) { // Don't try to map an element that couldn't be matched or found at all
  									if (config.debug)
  										console.log("NOTICE: No matches found for selector " + address[fieldType] + ". Skipping...");
  									delete address[fieldType];
  								} else if (matched.parents("form").length == 0) { // We should only map elements inside a <form> tag; otherwise we can't turn on submit handlers later
  									if (config.debug)
  										console.log("NOTICE: Element with selector \"" + address[fieldType] + "\" is not inside a <form> tag. Skipping...");
  									delete address[fieldType];
  								} else
  									address[fieldType] = matched[0];
  							}
  						}
  					}
  					if (config.target.indexOf("INTERNATIONAL") >= 0) {
  						if (!((address.country && address.freeform) || (address.country && address.address1 && address.postal_code) || (address.country && address.address1 && address.locality && address.administrative_area))) {
  							if (config.debug)
  								console.log("NOTICE: Address map (index " + addrIdx + ") was not mapped to a complete street address. Skipping...");
  							continue;
  						}
  					} else {
  						if (!((address.freeform) || (address.address1 && address.postal_code) || (address.address1 && address.locality && address.administrative_area))) {
  							if (config.debug)
  								console.log("NOTICE: Address map (index " + addrIdx + ") was not mapped to a complete street address. Skipping...");
  							continue;
  						}
  					}
  
  					// Acquire the form based on the first member
  					var formDom = $(address.address1).parents("form")[0];
  					if (!formDom) {
  						formDom = $(address.freeform).parents("form")[0];
  					}
  					var form = new Form(formDom);
  
  					// Persist a reference to the form if it wasn't acquired before
  					if (!$(formDom).data(formDataProperty)) {
  						// Mark the form as mapped then add it to our list
  						$(formDom).data(formDataProperty, 1);
  						disableBrowserAutofill(address);
  						addDefaultToStateDropdown(form.dom);
  						formsFound.push(form);
  					} else {
  						// Find the form in our list since we already put it there
  						for (var i = 0; i < formsFound.length; i++) {
  							if (formsFound[i].dom == formDom) {
  								form = formsFound[i];
  								break;
  							}
  						}
  					}
  
  					// Add this address to the form
  					mappedAddressCount++;
  					form.addresses.push(new Address(address, form, address.id, match));
  
  					if (config.debug)
  						console.log("Finished mapping address with ID: " + form.addresses[form.addresses.length - 1].id());
  				}
  			}
  
  			forms = formsFound;
  			trigger("FieldsMapped");
  		};
  
  		this.disableFields = function (address) {
  			// Given an address, disables the input fields for the address, also the submit button
  			if (!config.ui)
  				return;
  
  			var fields = address.getDomFields();
  			for (var field in fields) {
  				if (fields.hasOwnProperty(field)) {
  					$(fields[field]).prop ? $(fields[field]).prop("disabled", true) : $(fields[field]).attr("disabled", "disabled");
  				}
  			}
  
  			// Disable submit buttons
  			if (address.form && address.form.dom) {
  				var buttons = $(config.submitSelector, address.form.dom);
  				buttons.prop ? buttons.prop("disabled", true) : buttons.attr("disabled", "disabled");
  			}
  		};
  
  		this.enableFields = function (address) {
  			// Given an address, re-enables the input fields for the address
  			if (!config.ui)
  				return;
  
  			var fields = address.getDomFields();
  			for (var field in fields) {
  				if (fields.hasOwnProperty(field)) {
  					$(fields[field]).prop ? $(fields[field]).prop("disabled", false) : $(fields[field]).removeAttr("disabled");
  				}
  			}
  
  			// Enable submit buttons
  			if (address.form && address.form.dom) {
  				var buttons = $(config.submitSelector, address.form.dom);
  				buttons.prop ? buttons.prop("disabled", false) : buttons.removeAttr("disabled");
  			}
  		};
  
  		this.showLoader = function (addr) {
  			if (!config.ui || !addr.hasDomFields())
  				return;
  
  			// Get position information now instead of earlier in case elements shifted since page load
  			var lastFieldCorners = addr.corners(true);
  			var loaderUI = $(".smarty-dots.smarty-addr-" + addr.id()).parent();
  
  			loaderUI.css("top", (lastFieldCorners.top + lastFieldCorners.height / 2 - loaderHeight / 2) + "px")
  				.css("left", (lastFieldCorners.right - loaderWidth - 10) + "px");
  			$(".smarty-dots", loaderUI).show();
  		};
  
  		this.hideLoader = function (addr) {
  			if (config.ui)
  				$(".smarty-dots.smarty-addr-" + addr.id()).hide();
  		};
  
  		this.markAsValid = function (addr) {
  			if (!config.ui || !addr || !config.smartyTag)
  				return;
  
  			var domTag = $(".smarty-tag.smarty-tag-grayed.smarty-addr-" + addr.id());
  			domTag.removeClass("smarty-tag-grayed").addClass("smarty-tag-green").attr("title", "Address verified! Click to undo.");
  			$(".smarty-tag-text", domTag).text("Verified").hover(function () {
  				$(this).text("Undo");
  			}, function () {
  				$(this).text("Verified");
  			}).addClass("smarty-undo");
  		};
  
  		this.unmarkAsValid = function (addr) {
  			var validSelector = ".smarty-tag.smarty-addr-" + addr.id();
  			if (!config.ui || !addr || !config.smartyTag || $(validSelector).length == 0)
  				return;
  
  			var domTag = $(".smarty-tag.smarty-tag-green.smarty-addr-" + addr.id());
  			domTag.removeClass("smarty-tag-green").addClass("smarty-tag-grayed").attr("title", "Address not verified. Click to verify.");
  			$(".smarty-tag-text", domTag).text("Verify").off("mouseenter mouseleave").removeClass("smarty-undo");
  		};
  
  		this.showAmbiguous = function (data) {
  			if (!config.ui || !data.address.hasDomFields())
  				return;
  
  			var addr = data.address;
  			var response = data.response;
  			var corners = addr.corners();
  			corners.width = 294;
  
  			var html = "<div class=\"smarty-ui\" style=\"top: " + corners.top + "px; left: " + corners.left + "px;\">" +
  				"<div class=\"smarty-popup smarty-addr-" + addr.id() + "\" style=\"width: " + corners.width + "px;\">" +
  				"<div class=\"smarty-popup-header smarty-popup-ambiguous-header\">" + config.ambiguousMessage + "</div>" +
  				"<div class=\"smarty-popup-typed-address\">" + addr.toString() + "</div>" +
  				"<div class=\"smarty-choice-list\">";
  
  			if (addr.isDomestic()) {
  				for (var i = 0; i < response.raw.length; i++) {
  					var line1 = response.raw[i].delivery_line_1,
  						city = response.raw[i].components.city_name,
  						st = response.raw[i].components.state_abbreviation,
  						zip = response.raw[i].components.zipcode + "-" + response.raw[i].components.plus4_code;
  					html += "<a href=\"javascript:\" class=\"smarty-choice\" data-index=\"" + i + "\">" + line1 + "<br>" + city + ", " + st + " " + zip + "</a>";
  				}
  			} else {
  				var numCandidates = config.candidates;
  				if (response.raw.length < numCandidates) {
  					numCandidates = response.raw.length;
  				}
  				for (var i = 0; i < numCandidates; i++) {
  					var ambigAddr = "";
  					if (response.raw[i].address1) {
  						ambigAddr += response.raw[i].address1;
  					}
  					if (response.raw[i].address2) {
  						ambigAddr = ambigAddr + "<br>" + response.raw[i].address2;
  					}
  					if (response.raw[i].address3) {
  						ambigAddr = ambigAddr + "<br>" + response.raw[i].address3;
  					}
  					if (response.raw[i].address4) {
  						ambigAddr = ambigAddr + "<br>" + response.raw[i].address4;
  					}
  					if (response.raw[i].address5) {
  						ambigAddr = ambigAddr + "<br>" + response.raw[i].address5;
  					}
  					if (response.raw[i].address6) {
  						ambigAddr = ambigAddr + "<br>" + response.raw[i].address6;
  					}
  					html += "<a href=\"javascript:\" class=\"smarty-choice\" data-index=\"" + i + "\">" + ambigAddr + "</a>";
  				}
  			}
  
  			html += "</div><div class=\"smarty-choice-alt\">";
  			html += "<a href=\"javascript:\" class=\"smarty-choice smarty-choice-abort smarty-abort\">" + config.changeMessage + "</a>";
  			if (!config.enforceVerification) {
  				html += "<a href=\"javascript:\" class=\"smarty-choice smarty-choice-override\">" + config.certifyMessage + "</a>";
  			}
  			html += "</div></div></div>";
  			$(html).hide().appendTo("body").show(defaults.speed);
  
  			// Scroll to it if needed
  			if ($(document).scrollTop() > corners.top - 100 || $(document).scrollTop() < corners.top - $(window).height() + 100) {
  				$("html, body").stop().animate({
  					scrollTop: $(".smarty-popup.smarty-addr-" + addr.id()).offset().top - 100
  				}, 500);
  			}
  
  			data.selectors = {
  				goodAddr: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-choice-list .smarty-choice",
  				useOriginal: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-choice-override",
  				abort: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-abort"
  			};
  
  			// User chose a candidate address
  			$("body").on("click", data.selectors.goodAddr, data, function (e) {
  				$(".smarty-popup.smarty-addr-" + addr.id()).slideUp(defaults.speed, function () {
  					$(this).parent(".smarty-ui").remove();
  					$(this).remove();
  				});
  
  				turnOffAllClicks(e.data.selectors);
  				delete e.data.selectors;
  
  				trigger("UsedSuggestedAddress", {
  					address: e.data.address,
  					response: e.data.response,
  					invoke: e.data.invoke,
  					invokeFn: e.data.invokeFn,
  					chosenCandidate: response.raw[$(this).data("index")]
  				});
  			});
  
  			// User wants to revert to what they typed (forced accept)
  			$("body").on("click", data.selectors.useOriginal, data, function (e) {
  				$(this).parents(".smarty-popup").slideUp(defaults.speed, function () {
  					$(this).parent(".smarty-ui").remove();
  					$(this).remove();
  				});
  
  				turnOffAllClicks(e.data.selectors);
  				delete e.data.selectors;
  				trigger("OriginalInputSelected", e.data);
  			});
  
  			// User presses Esc key
  			$(document).keyup(data, function (e) {
  				if (e.keyCode == 27) { //Esc
  					turnOffAllClicks(e.data.selectors);
  					delete e.data.selectors;
  					userAborted($(".smarty-popup.smarty-addr-" + e.data.address.id()), e);
  					suppress(e);
  				}
  			});
  
  			// User clicks "x" in corner or chooses to try a different address (same effect as Esc key)
  			$("body").on("click", data.selectors.abort, data, function (e) {
  				turnOffAllClicks(e.data.selectors);
  				delete e.data.selectors;
  				userAborted($(this).parents(".smarty-popup"), e);
  			});
  		};
  
  		this.showInvalid = function (data) {
  			if (!config.ui || !data.address.hasDomFields())
  				return;
  
  			var addr = data.address;
  			var corners = addr.corners();
  			corners.width = 300;
  
  			var html = "<div class=\"smarty-ui\" style=\"top: " + corners.top + "px; left: " + corners.left + "px;\">" +
  				"<div class=\"smarty-popup smarty-addr-" + addr.id() + "\" style=\"width: " + corners.width + "px;\">" +
  				"<div class=\"smarty-popup-header smarty-popup-invalid-header\">" + config.invalidMessage + "</div>" +
  				"<div class=\"smarty-popup-typed-address\">" + addr.toString() + "</div>" +
  				"<div class=\"smarty-choice-alt\">" +
  				"<a href=\"javascript:\" class=\"smarty-choice smarty-choice-abort smarty-abort\">" + config.changeMessage + "</a>";
  			if (!config.enforceVerification) {
  				html += "<a href=\"javascript:\" class=\"smarty-choice smarty-choice-override\">" +
  					config.certifyMessage + "</a>";
  			}
  			html += "</div></div>";
  
  			$(html).hide().appendTo("body").show(defaults.speed);
  
  			data.selectors = {
  				useOriginal: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-choice-override ",
  				abort: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-abort"
  			};
  
  			// Scroll to it if necessary
  			if ($(document).scrollTop() > corners.top - 100 || $(document).scrollTop() < corners.top - $(window).height() + 100) {
  				$("html, body").stop().animate({
  					scrollTop: $(".smarty-popup.smarty-addr-" + addr.id()).offset().top - 100
  				}, 500);
  			}
  
  			turnOffAllClicks(data.selectors.abort);
  			// User rejects original input and agrees to double-check it
  			$("body").on("click", data.selectors.abort, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("InvalidAddressRejected", e.data);
  			});
  
  			turnOffAllClicks(data.selectors.useOriginal);
  			// User certifies that what they typed is correct
  			$("body").on("click", data.selectors.useOriginal, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("OriginalInputSelected", e.data);
  			});
  
  			// User presses esc key
  			$(document).keyup(data, function (e) {
  				if (e.keyCode == 27) { //Esc
  					turnOffAllClicks(e.data.selectors);
  					$(data.selectors.abort).click();
  					userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				}
  			});
  		};
  
  		this.showInvalidCountry = function (data) {
  			if (!config.ui || !data.address.hasDomFields())
  				return;
  
  			var addr = data.address;
  			var corners = addr.corners();
  			corners.width = 300;
  
  			var html = "<div class=\"smarty-ui\" style=\"top: " + corners.top + "px; left: " + corners.left + "px;\">" +
  				"<div class=\"smarty-popup smarty-addr-" + addr.id() + "\" style=\"width: " + corners.width + "px;\">" +
  				"<div class=\"smarty-popup-header smarty-popup-invalid-header\">" + config.invalidCountryMessage + "</div>" +
  				"<div class=\"smarty-popup-typed-address\">" + addr.toString() + "</div>" +
  				"<div class=\"smarty-choice-alt\"><a href=\"javascript:\" class=\"smarty-choice smarty-choice-abort smarty-abort\">" +
  				config.changeMessage + "</a></div>";
  			if (!config.enforceVerification) {
  				html += "<a href=\"javascript:\" class=\"smarty-choice smarty-choice-override\">" +
  					config.certifyMessage + "</a>";
  			}
  			html += "</div></div>";
  
  			$(html).hide().appendTo("body").show(defaults.speed);
  
  			data.selectors = {
  				useOriginal: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-choice-override ",
  				abort: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-abort"
  			};
  
  			// Scroll to it if necessary
  			if ($(document).scrollTop() > corners.top - 100 || $(document).scrollTop() < corners.top - $(window).height() + 100) {
  				$("html, body").stop().animate({
  					scrollTop: $(".smarty-popup.smarty-addr-" + addr.id()).offset().top - 100
  				}, 500);
  			}
  
  			turnOffAllClicks(data.selectors.abort);
  			// User rejects original input and agrees to double-check it
  			$("body").on("click", data.selectors.abort, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("InvalidAddressRejected", e.data);
  			});
  
  			turnOffAllClicks(data.selectors.useOriginal);
  			// User certifies that what they typed is correct
  			$("body").on("click", data.selectors.useOriginal, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("OriginalInputSelected", e.data);
  			});
  
  			// User presses esc key
  			$(document).keyup(data, function (e) {
  				if (e.keyCode == 27) { //Esc
  					turnOffAllClicks(e.data.selectors);
  					$(data.selectors.abort).click();
  					userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				}
  			});
  		};
  
  		this.showMissingSecondary = function (data) {
  			if (!config.ui || !data.address.hasDomFields())
  				return;
  			var addr = data.address;
  			var corners = addr.corners();
  			corners.width = 300;
  
  			var html = "<div class=\"smarty-ui\" style=\"top: " + corners.top + "px; left: " + corners.left + "px;\">" +
  				"<div class=\"smarty-popup smarty-addr-" + addr.id() + "\" style=\"width: " + corners.width + "px;\">" +
  				"<div class=\"smarty-popup-header smarty-popup-missing-secondary-header\">" + config.missingSecondaryMessage +
  				"</div>" + "<div class=\"smarty-popup-typed-address\">" + addr.toString() + "</div>" +
  				"<form class=\"smarty-popup-secondary-number-form\">" +
  				"<input id=\"smarty-popup-secondary-number-input-box\" class=\"smarty-addr-" + addr.id() +
  				"\" type=\"text\" name=\"secondarynumber\" placeholder=\"Enter number here\"><br>" +
  				"<input id=\"smarty-popup-secondary-number-form-submit-button\" class=\"smarty-addr-" + addr.id() +
  				"\" type=\"submit\" value=\"Submit\">" +
  				"</form>" +
  				"<hr class=\"smarty-hr\">" + "<div class=\"smarty-choice-alt\">" + "<a href=\"javascript:\" " +
  				"class=\"smarty-choice smarty-choice-abort smarty-abort\">" + config.changeMessage + "</a>";
  			if (!config.enforceVerification) {
  				html += "<a href=\"javascript:\" class=\"smarty-choice smarty-choice-override\">" +
  					config.certifyMessage + "</a>";
  			}
  			html += "</div></div></div>";
  
  			$(html).hide().appendTo("body").show(defaults.speed);
  
  			data.selectors = {
  				useOriginal: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-choice-override ",
  				abort: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-abort",
  				submit: "#smarty-popup-secondary-number-form-submit-button.smarty-addr-" + addr.id()
  			};
  
  			// Scroll to it if necessary
  			if ($(document).scrollTop() > corners.top - 100 || $(document).scrollTop() < corners.top - $(window).height() + 100) {
  				$("html, body").stop().animate({
  					scrollTop: $(".smarty-popup.smarty-addr-" + addr.id()).offset().top - 100
  				}, 500);
  			}
  
  			turnOffAllClicks(data.selectors.abort);
  			// User rejects original input and agrees to double-check it
  			$("body").on("click", data.selectors.abort, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("InvalidAddressRejected", e.data);
  			});
  
  			turnOffAllClicks(data.selectors.useOriginal);
  			// User certifies that what they typed is correct
  			$("body").on("click", data.selectors.useOriginal, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("OriginalInputSelected", e.data);
  			});
  
  			turnOffAllClicks(data.selectors.submit);
  			// User enters a secondary address
  			$("body").on("click", data.selectors.submit, data, function (e) {
  				e.data.address.secondary = $("#smarty-popup-secondary-number-input-box.smarty-addr-" + e.data.address.id()).val();
  				if (e.data.address.isFreeform()) {
  					e.data.address.address1 = e.data.response.raw[0].delivery_line_1;
  				}
  				e.data.address.zipcode = e.data.response.raw[0].components.zipcode;
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				suppress(e);
  				trigger("AddedSecondary", e.data);
  			});
  
  			// User presses esc key
  			$(document).keyup(data, function (e) {
  				if (e.keyCode == 27) { //Esc
  					turnOffAllClicks(e.data.selectors);
  					$(data.selectors.abort).click();
  					userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				}
  			});
  		};
  
  		this.showMissingInput = function (data) {
  			if (!config.ui || !data.address.hasDomFields())
  				return;
  
  			var addr = data.address;
  			var corners = addr.corners();
  			corners.width = 300;
  
  			var html = "<div class=\"smarty-ui\" style=\"top: " + corners.top + "px; left: " + corners.left + "px;\">" +
  				"<div class=\"smarty-popup smarty-addr-" + addr.id() + "\" style=\"width: " + corners.width + "px;\">" +
  				"<div class=\"smarty-popup-header smarty-popup-missing-input-header\">" + config.missingInputMessage + "</div>" +
  				"<div class=\"smarty-popup-typed-address\">" + addr.toString() + "</div>" +
  				"<div class=\"smarty-choice-alt\"><a href=\"javascript:\" " +
  				"class=\"smarty-choice smarty-choice-abort smarty-abort\">" + config.changeMessage + "</a>";
  			if (!config.enforceVerification) {
  				html += "<a href=\"javascript:\" class=\"smarty-choice smarty-choice-override\">" +
  					config.certifyMessage + "</a>";
  			}
  			html += "</div></div>";
  
  			$(html).hide().appendTo("body").show(defaults.speed);
  
  			data.selectors = {
  				useOriginal: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-choice-override ",
  				abort: ".smarty-popup.smarty-addr-" + addr.id() + " .smarty-abort"
  			};
  
  			// Scroll to it if necessary
  			if ($(document).scrollTop() > corners.top - 100 || $(document).scrollTop() < corners.top - $(window).height() + 100) {
  				$("html, body").stop().animate({
  					scrollTop: $(".smarty-popup.smarty-addr-" + addr.id()).offset().top - 100
  				}, 500);
  			}
  
  			turnOffAllClicks(data.selectors.abort);
  			// User rejects original input and agrees to double-check it
  			$("body").on("click", data.selectors.abort, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("InvalidAddressRejected", e.data);
  			});
  
  			turnOffAllClicks(data.selectors.useOriginal);
  			// User certifies that what they typed is correct
  			$("body").on("click", data.selectors.useOriginal, data, function (e) {
  				userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				delete e.data.selectors;
  				trigger("OriginalInputSelected", e.data);
  			});
  
  			// User presses esc key
  			$(document).keyup(data, function (e) {
  				if (e.keyCode == 27) { //Esc
  					turnOffAllClicks(e.data.selectors);
  					$(data.selectors.abort).click();
  					userAborted(".smarty-popup.smarty-addr-" + e.data.address.id(), e);
  				}
  			});
  		};
  
  	}