/* Minification failed. Returning unminified contents.
(10005,23-24): run-time error JS1014: Invalid character: `
(10006,1-2): run-time error JS1195: Expected expression: <
(10006,1-2): run-time error JS1195: Expected expression: <
(10009,1-2): run-time error JS1014: Invalid character: `
(10012,27-28): run-time error JS1014: Invalid character: `
(10012,28-29): run-time error JS1195: Expected expression: <
(10012,194-198): run-time error JS1197: Too many errors. The file might not be a JavaScript file: name
(10001,1-27): run-time error JS1301: End of file encountered before function is properly closed: function ActionBar(jquery)
(10012,198-199): run-time error JS1195: Expected expression: =
(10012,199-216): run-time error JS1197: Too many errors. The file might not be a JavaScript file: "twitterComposer"
 */
function VoterVoiceAPI(rootUrl, apiKey, language) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._rootUrl = rootUrl;
    this._apiKey = apiKey;
    if (typeof language !== 'undefined' && language !== null) {
        this._language = language;
    }
}

var vvLatestApi;

function VoterVoiceLogger(api) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._api = api;
    vvLatestApi = api;
}

function EmailAddressService() {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
}

(function() {

    var pendingErrorLocation = false;
    var originalOnError;

    function arrangeForErrorLocation() {
        if (pendingErrorLocation === false) {
            originalOnError = window.onerror;
            window.onerror = logErrorLocation;
            pendingErrorLocation = true;
        }
    }

    function logErrorLocation() {
        window.onerror = originalOnError;
        try {
            var message = "Error Location: " + arguments.callee.caller;
            vvLatestApi.postLogEntry({ severity: 'information', message: message });
        } catch (e) {
        }
        if (originalOnError) {
            originalOnError.apply(this, arguments);
        }
        pendingErrorLocation = false;
    }

    function getStackTrace(error) {
        try {
            var callstack = [];
            var lines, i;
            if (error.stack) { //Firefox
                lines = error.stack.split('\n');
                for (i = 0; i < lines.length; i++) {
                    callstack.push(lines[i]);
                }
            } else if (window.opera && error.message) { //Opera
                lines = error.message.split('\n');
                for (i = 0; i < lines.length; i++) {
                    if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
                        var entry = lines[i];
                        //Append next line also since it has the file info
                        if (lines[i + 1]) {
                            entry += ' at ' + lines[i + 1];
                            i++;
                        }
                        callstack.push(entry);
                    }
                }
            } else { //IE and Safari
                arrangeForErrorLocation();
                var currentFunction = arguments.callee.caller;
                while (currentFunction) {
                    var fn = currentFunction.toString();
                    var fname = fn.substring(fn.indexOf('function') + 8, fn.indexOf('(')) || 'anonymous';
                    callstack.push(fname);
                    currentFunction = currentFunction.caller;
                }
            }
            return callstack;
        } catch (e) {
            return ["getStackTrace error: " + e];
        }
    }

    function getExceptionMessage(error) {
        var message = "getExceptionMessage error";
        try {
            message = (error.name && error.message)
                ? error.name + ": " + error.message
                : typeof error + ": " + error;
            var stackTrace = getStackTrace(error);
            for (var i = 0; i < stackTrace.length; i++) {
                message += "\n" + stackTrace[i];
            }
        } catch (e) {
        }
        return message;
    }

    VoterVoiceLogger.prototype = {
        logException: function logException(error, url) {
            var entry = {
                severity: 'error',
                message: getExceptionMessage(error)
            };
            if (arguments.length > 1) {
                entry.url = url;
            }
            this._api.postLogEntry(entry);
        }
    };
})();
(function() {
    var emailRegExp;
    (function() {
        /*
            see: http://tools.ietf.org/html/rfc5322#section-3.4.1
            We only allow the dot-atom form of local-part and domain
            (that is, we disregard the RFCs allowance for the following:
            any obs- forms, a local-part defined by quoted-string, a domain
            defined by domain-literal).
            We add the extra constraint of needing at least one . in the domain.
        */
        var atext = "a-z0-9!#$%&'*+\\-/=?^_`{|}~";
        var dot_atom = "[" + atext + "]+(\\.[" + atext + "]+)*";
        emailRegExp = new RegExp("^" + dot_atom + "@" + dot_atom + "\\." + dot_atom + "$", "i");
        // "\\" in the above strings regex escapes the - and .s
    })();
    EmailAddressService.prototype = {
        isValid: function(emailAddress) {
            return emailAddress.search(emailRegExp) != -1;
        }
    };
})();
(function(undefined) {
    if (typeof JSON === 'undefined') {
        alert("JSON is undefined ... try using json2.js");
    }
    VoterVoiceAPI.fromJson = JSON.parse;
    VoterVoiceAPI.toJson = JSON.stringify;

    function isAbsolute(url) {
        // RFC 2396 defines URI scheme as follows:
        // scheme        = alpha *( alpha | digit | "+" | "-" | "." )
        return /^[a-z][\w\+\-\.]*:/i.test(url);
    }

    function sameOrigin(absoluteUrl, protocol, hostname) {
        protocol = protocol.toLowerCase();
        hostname = hostname.toLowerCase();

        if (absoluteUrl.toLowerCase().indexOf(protocol) !== 0)
            return false;

        var hostMatch = /^[a-z][\w\+\-\.]*:[\/]*([^:|\/|?]*)/i.exec(absoluteUrl);
        if (hostMatch == null || hostMatch[1].toLowerCase() !== hostname)
            return false;

        return true;
    }

    function isCrossOrigin(url) {
        return window !== undefined
            && isAbsolute(url)
            && !sameOrigin(url, window.location.protocol, window.location.hostname);
    }

    function statusOk(status, okStatus) {
        var ok = status == okStatus;
        if (okStatus.length)
            for (var i = 0; i < okStatus.length; i++) {
                if (okStatus[i] == status)
                    ok = true;
            }
        return ok;
    }

    function createRequest(api, action, url, okStatus, callback) {
        var xhr;
        var request = {
            open: function(action, url) {
                this._action = action;
                this._url = url;
                xhr.open(action, url);
            },
            send: function(content, contentType) {
                // when provided, add Content-Type header
                if (arguments.length >= 2) {
                    // not supported by XDomainRequest
                    if (xhr.setRequestHeader !== undefined) {
                        xhr.setRequestHeader("Content-Type", contentType);
                    }
                }
                // not supported by XDomainRequest
                if (xhr.setRequestHeader !== undefined) {
                    xhr.setRequestHeader("Authorization", api._apiKey);
                    if (api._language !== undefined) {
                        xhr.setRequestHeader("Accept-Language", api._language);
                        xhr.setRequestHeader("VV-Language", api._language);
                    }
                }
                xhr.send(content);
            },
            abort: function() {
                xhr.abort();
            },
            response: null,
            onerror: null,
            onload: null,
            error: function(handler) {
                onerror = handler;
                return request;
            },
            success: function(handler) {
                onload = handler;
                return request;
            }
        };

        if (isCrossOrigin(url) && typeof XDomainRequest !== 'undefined') {
            xhr = new XDomainRequest();
            xhr.onerror = function() {
                if (api.requestError)
                    api.requestError(xhr);
                if (request.onerror)
                    request.onerror(xhr);
            };
            xhr.onload = function() {
                var responseText = xhr.responseText;
                var statusEnd = responseText.indexOf(' ');
                var statusTextEnd = responseText.indexOf('\n');
                var status = responseText.substring(0, statusEnd);
                var statusText = responseText.substring(statusEnd + 1, statusTextEnd);
                responseText = responseText.substring(statusTextEnd + 1);

                request.response = {
                    status: status,
                    statusText: statusText,
                    responseText: responseText
                };
                if (request.onload)
                    request.onload(request.response);
            };
        } else if (window.XMLHttpRequest !== undefined) {
            xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    if (xhr.status == 0) {
                        if (api.requestError)
                            api.requestError(xhr);
                        if (request.onerror)
                            request.onerror(xhr);
                    } else {
                        request.response = {
                            status: xhr.status,
                            statusText: xhr.statusText,
                            responseText: xhr.responseText
                        };
                        if (request.onload)
                            request.onload(request.response);
                    }
                }
            };
        } else {
            throw "XMLHTTP support is disabled";
        }

        // open request
        try {
            request.open(action, url);
        } catch (e) {
            alert(VoterVoiceAPI.toJson(xhr, null, 4));
            alert(action + " " + url + ":\r\n" + e.description);
            throw e;
        }

        bindRequest(request, okStatus, callback, api);
        return request;
    }

    function bindRequest(request, okStatus, callback, api) {
        if (okStatus && callback) {
            request.onload = function(response) {
                processResponse.call(this, response, okStatus, callback, api);
            };
        }
    }

    function processResponse(response, okStatus, callback, api) {
        try {
            callback.responseStatus = response.status;
            if (statusOk(response.status, okStatus)) {
                var obj = response.responseText === ""
                    ? null
                    : VoterVoiceAPI.fromJson(response.responseText);
                callback(obj);
            } else {
                callback(response.responseText, response.status + " " + response.statusText);
            }
        } catch (e) {
            new VoterVoiceLogger(api).logException(e, this._action + ' ' + this._url);
            throw e;
        }
    }

    //
    // public methods
    //

    function postLogEntry(entry) {
        var url = this._rootUrl + "/LogEntries";
        try {
            entry = VoterVoiceAPI.toJson(entry);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }
        var xhr = createRequest(this, "POST", url, 201, null);
        xhr.send(entry, "application/json; charset=utf-8");
    }

    function logError(message) {
        var entry = {
            severity: 'error',
            message: message
        };
        postLogEntry.call(this, entry);
    }

    function logWarning(message) {
        var entry = {
            severity: 'warning',
            message: message
        };
        postLogEntry.call(this, entry);
    }

    function searchAddresses(criteria, callback) {
        var parameters = [];
        for (var c in criteria) {
            parameters.push(c + "=" + encodeURIComponent(criteria[c]));
        }
        var url = this._rootUrl + "/Addresses?" + parameters.join("&");

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function validateAddressCoordinates(address, coordinates, callback) {
        var parameters = [];
        for (var c in address) {
            if (c === "alternateCities" && address[c] !== null) {
                parameters.push(c + "=" + encodeURIComponent(null));

                //if (address[c].length === 0) {
                //    parameters.push(c + "=" + encodeURIComponent(null));
                // Possible implementation #1 depending on serializer
                //} else {
                //    var mvcDeserializableDictionary = '';
                //    for (var i = 0; i < address[c].length; i++) {
                //        var alternateCity = address[c][i];

                //        mvcDeserializableDictionary += JSON.stringify(alternateCity);

                //        if (i < address[c].length - 1) {
                //            mvcDeserializableDictionary += ',';
                //        }
                //    }
                // Possible implementation #2 depending on serializer
                //} else
                //{
                //    mvcDeserializableDictionary = '[' + mvcDeserializableDictionary + ']';
                //    parameters.push(c + "=" + encodeURIComponent(mvcDeserializableDictionary));

                //    var alternateCities = {};
                //    for (var i = 0; i < address[c].length; i++) {
                //        var alternateCity = address[c][i];
                //        alternateCities[alternateCity.Key] = alternateCity.Value;
                //    }
                //    parameters.push(c + "=" + encodeURIComponent(JSON.stringify(alternateCities)));
                //}
            }
            else if (c !== "coordinates" && address[c] !== null) {
                parameters.push(c + "=" + encodeURIComponent(address[c]));
            }
        }
        parameters.push("latitude=" + coordinates.lat());
        parameters.push("longitude=" + coordinates.lng());
        var url = this._rootUrl + "/Addresses?" + parameters.join("&");

        var xhr = createRequest(this, "PUT", url, [200, 201], callback);
        xhr.send();
    }

    function searchGovernments(criteria, callback) {
        var countryUri = criteria.countryCodeISO3166_1 !== undefined ? ("/" + criteria.countryCodeISO3166_1) : "";
        var url = this._rootUrl + "/Governments" + countryUri + "?association=" + criteria.association;
        if (criteria.legislativeProposals !== undefined) {
            url += "&legislativeProposals=" + criteria.legislativeProposals;
        }
        if (criteria.activeScorecards !== undefined) {
            url += "&activeScorecards=" + criteria.activeScorecards;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function searchGovernmentOfficials(uri, association, callback) {
        var url = this._rootUrl + "/Governments/" + uri + "/Officials?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function searchGovernmentLegislativeSessions(uri, callback) {
        var url = this._rootUrl + "/Governments/" + uri + "/LegislativeSessions";

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function searchGovernmentLegislativeProposals(uri, sessions, search, continuationToken, callback) {
        var url = this._rootUrl + "/Governments/" + uri + "/LegislativeProposals?";
        url += "search=" + encodeURIComponent(search);

        for (var i = 0; i < sessions.length; i++)
            url += '&session=' + sessions[i].name;

        if (continuationToken)
          url += "&continuationToken=" + encodeURIComponent(continuationToken);

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getGovernmentLegislativeProposalDetails(legislationUrl, callback) {
        var url = this._rootUrl + "/Governments/" + legislationUrl;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getKeyContactRelationshipTypes(association, callback) {
        var url = this._rootUrl + "/Associations/" + association + "/KeyContactRelationshipTypes";

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getLegislativeProposalSpotlights(association, callback) {
        var url = this._rootUrl + "/Associations/" + association + "/LegislativeProposalSpotlights";

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSponsoredLegislativeProposalSpotlights(association, officialId, callback) {
        var url = this._rootUrl + "/Associations/" + association + "/LegislativeProposalSpotlights?primarySponsorOfficialId=" + officialId;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getCosponsoredLegislativeProposalSpotlights(association, officialId, callback) {
        var url = this._rootUrl + "/Associations/" + association + "/LegislativeProposalSpotlights?cosponsorOfficialId=" + officialId;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function queryScorecards(association, governmentUri, active, callback) {
        var url = this._rootUrl + "/Scorecards?association=" + association + "&government=" + governmentUri + "&active=" + active;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function queryScorees(association, userToken, callback, userAddresses, officialName) {
        var url = this._rootUrl + "/Scorecards/Scorees?association=" + association;
        if (userToken)
            url += "&user=" + userToken;
        for (var type in userAddresses) {
            url += "&" + type + "=" + encodeURIComponent(VoterVoiceAPI.toJson(userAddresses[type]));
        }
        if (officialName) {
            url += "&name=" + officialName;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
        return xhr;
    }

    function queryScorings(scorecardId, callback) {
        var url = this._rootUrl + "/Scorecards/" + scorecardId + "/Scorings";

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getScorecardCriterion(association, criterionId, callback) {
        var url = this._rootUrl + "/Scorecards/Criteria/" + criterionId + "?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getPoliticianScorecard(association, politicianId, user, callback) {
        var url = this._rootUrl + "/Scorecards/Politicians/" + politicianId + "?association=" + association + "&user=" + user;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getOfficialScorecard(association, officialId, userToken, callback) {
        var url = this._rootUrl + "/Scorecards/Officials/" + officialId + "?association=" + association;
        if (userToken)
            url += "&user=" + userToken;
        
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getOfficialTypeAheadSource(association, callback) {
        var url = this._rootUrl + "/Officials/TypeAheadSource" + "?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }
    
    function hasMultipleDistrictsPerEntity(association, zip, useFutureDistricts, callback) {
        var url = this._rootUrl + "/Districts?association=" + association + "&zip=" + zip;
        if (useFutureDistricts === true)
            url += "&context=candidates";
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getCampaigns(association, siteNumber, callback) {
        var url = this._rootUrl + "/Advocacy/CampaignSummaries?association=" + association;
        if (siteNumber)
            url += "&siteNumber=" + siteNumber;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getCampaign(id, callback) {
        var url = this._rootUrl + "/Advocacy/CampaignSummaries/" + id;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getTargetedMessages(association, campaignId, callback) {
        var url = this._rootUrl + "/Advocacy/TargetedMessages?association=" + association;
        if (campaignId)
            url += "&campaignId=" + campaignId;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function matchUser(association, userToken, campaignId, callback, userAddresses) {
        var url = this._rootUrl + "/Advocacy/MatchedTargets?association=" + association;
        if (campaignId !== null)
            url += "&campaignId=" + campaignId;
        if (userToken)
            url += "&user=" + userToken;
        for (var type in userAddresses) {
            url += "&" + type + "=" + encodeURIComponent(VoterVoiceAPI.toJson(userAddresses[type]));
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
        return xhr;
    }

    function getCandidates(association, userToken, callback, userAddresses) {
        var url = this._rootUrl + "/Candidates?association=" + association;
        if (userToken)
            url += "&user=" + userToken;
        for (var type in userAddresses) {
            url += "&" + type + "=" + encodeURIComponent(VoterVoiceAPI.toJson(userAddresses[type]));
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
        return xhr;
    }

    function getDeliveryOptions(targetType, targetId, association, messageId, callback) {
        var url = this._rootUrl + "/Advocacy/MessageDeliveryOptions?association=" + association + "&targetType=" + targetType + "&targetId=" + targetId;
        if (messageId !== null)
            url += "&targetedMessage=" + messageId;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSharedQuestion(id, callback) {
        var url = this._rootUrl + "/Advocacy/SharedQuestions/" + id;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getProfile(association, targetId, targetType, callback) {
        var url = this._rootUrl + "/Profiles/" + targetId + "?type=" + targetType + "&association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function searchProfiles(association, filter, callback) {
        filter = VoterVoiceAPI.toJson(filter);
        filter = encodeURIComponent(filter);
        var url = this._rootUrl + "/Profiles?filter=" + filter + "&association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function shared(association, share, medium, shareUrl, status, callback) {
        var url = this._rootUrl + "/SocialMedia/Shared?association=" + association;
        if (share.userToken != null) {
            url += "&userUrlToken=" + share.userToken;
        }
        url += "&applicationType=" + encodeURIComponent(share.subject.type) +
            "&actionId=" + share.subject.id +
            "&fingerprint=" + share.fingerprint +
            "&referringNugget=" + (share.referringNugget || '') +
            "&promoteUrl=" + encodeURIComponent(shareUrl) +
            "&message=" + encodeURIComponent(status) +
            "&medium=" + encodeURIComponent(medium);
        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send();
    }

    function createVisit(shareNugget, callback) {
        var url = this._rootUrl + "/Shares/" + shareNugget + "/Visits";
        if (document.referrer) {
            url += "?referrer=" + encodeURIComponent(document.referrer);
        }
        var xhr = createRequest(this, "POST", url, 201, callback);
        xhr.send();
    }

    function logVisit(activity, callback) {
        var url = this._rootUrl + '/Activity/Visits';
        try {
            activity = VoterVoiceAPI.toJson(activity);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 201, callback);
        xhr.send(activity, "application/json; charset=utf-8");
    }


    function getNewsletters(association, callback) {
        var url = this._rootUrl + "/Newsletters?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getNewsletter(association, id, callback) {
        var url = this._rootUrl + "/Newsletters/" + id + "?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getPetition(petitionId, association, callback) {
        var url = this._rootUrl + "/Petitions/" + petitionId + "?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function signPetition(petitionId, signature, callback) {
        var url = this._rootUrl + "/Petitions/" + petitionId + "/Signatures";
        try {
            signature = VoterVoiceAPI.toJson(signature);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 201, callback);
        xhr.send(signature, "application/json; charset=utf-8");
    }


    function getSurvey(surveyId, callback) {
        var url = this._rootUrl + "/Surveys/" + surveyId;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSurveys(association, siteNumber, callback) {
        var url = this._rootUrl + "/Surveys?association=" + association;
        if (siteNumber)
            url += "&siteNumber=" + siteNumber;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSurveyResponses(surveyId, userToken, surveyResponseId, meetingId, callback) {
        var url = this._rootUrl + "/Surveys/" + surveyId + '/Responses';

        if (userToken !== undefined && userToken !== null && userToken !== '') {
            url = url + '?user=' + userToken;

            if (surveyResponseId !== undefined && surveyResponseId !== null && surveyResponseId !== '') {
                url = url + '&surveyResponseId=' + surveyResponseId;
            }

            if (meetingId !== undefined && meetingId !== null && meetingId !== '') {
                url = url + '&meetingId=' + meetingId;
            }
        } else if (surveyResponseId !== undefined && surveyResponseId !== null && surveyResponseId !== '') {
            url = url + '?surveyResponseId=' + surveyResponseId;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSurveyResults(surveyId, callback) {
        var url = this._rootUrl + "/Surveys/" + surveyId + "/Results";

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getQuestionResponses(surveyId, questionId, callback) {
        var url = this._rootUrl + "/Surveys/" + surveyId + "/Questions/" + questionId + "/Responses";

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSurveyMeetingReportResponses(association, surveyId, userToken, callback) {
        var url = this._rootUrl + "/Surveys/" + surveyId + "/MeetingReportResponses?association=" + association;

        if (userToken !== undefined && userToken !== null && userToken !== '') {
            url += "&user=" + userToken;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function submitSurvey(surveyId, surveyResponse, userToken, callback) {
        var url = this._rootUrl + "/Surveys/" + surveyId + "/Responses";

        if (userToken !== undefined && userToken !== null && userToken !== '') {
            url = url + '?user=' + userToken;
        }

        try {
            surveyResponse = VoterVoiceAPI.toJson(surveyResponse);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(surveyResponse, "application/json; charset=utf-8");
    }

    function findUserIdentities(criteria, callback) {
        var url = this._rootUrl + "/Users/Identities" + "?";
        if (criteria.emailAddress)
            url += "&email=" + encodeURIComponent(criteria.emailAddress);
        if (criteria.ownershipProof)
            url += "&ownershipProof=" + criteria.ownershipProof;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getUser(association, siteNumber, area, userToken, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "?association=" + association;
        if (siteNumber)
            url += "&siteNumber=" + siteNumber;
        if (area)
            url += "&area=" + area;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveUser(association, siteNumber, userToken, user, callback, standAloneRegistration, broadcastReceipt, visitIdentifier) {
        var action, url;
        if (userToken) {
            action = "PUT";
            url = this._rootUrl + "/Users/" + userToken + "?nocache&";
        } else {
            action = "POST";
            url = this._rootUrl + "/Users" + "?";
        }
        if (association)
            url += "association=" + association;
        if (siteNumber)
            url += "&siteNumber=" + siteNumber;
        try {
            if (standAloneRegistration !== undefined) {
                user.standAloneRegistration = standAloneRegistration;
            }
            if (broadcastReceipt !== undefined) {
                user.broadcastReceipt = broadcastReceipt;
            }
            if (visitIdentifier !== undefined) {
                user.visitIdentifier = visitIdentifier;
            }
            user = VoterVoiceAPI.toJson(user);
            if (!user) {
                this.logWarning("empty user JSON result = " + typeof user + ":" + user + "\nVoterVoiceAPI.toJson = " + VoterVoiceAPI.toJson);
            }
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, action, url, [200, 201], callback);
        xhr.send(user, "application/json; charset=utf-8");
    }

    function saveUserLocation(userToken, address, coordinates, purpose, callback) {
        var parameters = [];
        parameters.push("streetAddress=" + encodeURIComponent(address.streetAddress));
        parameters.push("city=" + encodeURIComponent(address.city));
        if (address.county)
            parameters.push("county=" + encodeURIComponent(address.county));
        parameters.push("state=" + encodeURIComponent(address.state));
        parameters.push("zipCode=" + encodeURIComponent(address.zipCode));
        if (address.zipCodeExtension)
            parameters.push("zipCodeExtension=" + encodeURIComponent(address.zipCodeExtension));
        if (address.country)
            parameters.push("country=" + encodeURIComponent(address.country));
        parameters.push("checksum=" + encodeURIComponent(address.checksum));
        parameters.push("latitude=" + coordinates.lat());
        parameters.push("longitude=" + coordinates.lng());
        parameters.push("purpose=" + purpose);
        var url = this._rootUrl + "/Users/" + userToken + "/Locations?" + parameters.join("&");
        var xhr = createRequest(this, "POST", url, [200], callback);
        xhr.send();
    }

    function checkMobileNumber(association, userToken, mobileNumber, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/MobileNumber" + "?association=" + association
            + "&mobileNumber=" + mobileNumber;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getMembership(association, siteNumber, userToken, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Memberships" + "?association=" + association;
        if (siteNumber)
            url += "&siteNumber=" + siteNumber;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function registerUser(association, siteNumber, userToken, membership, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Memberships" + "?association=" + association;
        if (siteNumber)
            url += "&siteNumber=" + siteNumber;

        if (membership.mobileNumber !== undefined) {
            url += "&receivesMessages=" + membership.receivesMessages;

            if (membership.receivesTextMessages !== null && membership.receivesTextMessages !== undefined) {
                url += "&receivesTextMessages=" + membership.receivesTextMessages;
                url += "&mobileNumber=" + encodeURIComponent(membership.mobileNumber);
            }
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send();
    }

    function getUserRelationships(association, userToken, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Relationships?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getUserGeographicRelationships(association, userToken, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/GeographicRelationships?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function deleteUserRelationship(association, userToken, politicianId, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Relationships?association=" + association + "&politicianId=" + politicianId;

        var xhr = createRequest(this, "DELETE", url, 200, callback);
        xhr.send();
    }

    function deleteUserGeographicRelationship(association, userToken, relationshipId, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/GeographicRelationships?association=" + association + "&relationshipId=" + relationshipId;

        var xhr = createRequest(this, "DELETE", url, 200, callback);
        xhr.send();
    }

    function saveUserRelationship(association, userToken, politicianId, typeId, details, callback) {
        var relationship = {
            politicianId: politicianId,
            type: { id: typeId },
            details: details
        };
        var relationships = [relationship];
        var url = this._rootUrl + "/Users/" + userToken + "/Relationships?association=" + association;
        try {
            relationships = VoterVoiceAPI.toJson(relationships);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(relationships, "application/json; charset=utf-8");
    }

    function saveGeographicRelationship(association, userToken, geographicRelationship, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/GeographicRelationships?association=" + association;
        var contract = geographicRelationship;
        try {
            contract = VoterVoiceAPI.toJson(contract);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(contract, "application/json; charset=utf-8");
    }

    function validateUserFields(userToken, fields, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Validations?";

        for (var i = 0; i < fields.length; i++)
            url += '&field=' + fields[i];

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getSubscriptions(association, userToken, unregisteredUser, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Subscriptions?nocache&association=" + association + '&unregisteredUser=' + unregisteredUser;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveSubscriptions(association, userToken, subscriptions, callback) {

        var url = this._rootUrl + "/Users/" + userToken + "/Subscriptions?nocache&association=" + association;
        try {
            subscriptions = VoterVoiceAPI.toJson(subscriptions);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "PUT", url, 200, callback);
        xhr.send(subscriptions, "application/json; charset=utf-8");
    }

    function getVoterRegistration(association, user, callback) {
        var url = this._rootUrl + "/VoterRegistration?association=" + association + "&user=" + user;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveVoterRegistration(association, user, voterRegistration, callback) {
        var url = this._rootUrl + "/VoterRegistration?association=" + association;

        if (user !== undefined && user != null) {
            url += "&user=" + user;
        }

        try {
            voterRegistration = VoterVoiceAPI.toJson(voterRegistration);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(voterRegistration, "application/json; charset=utf-8");
    }

    function getVoterRegistrationInstructions(state, callback) {
        var url = this._rootUrl + "/VoterRegistration/Instructions?state=" + state;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getEvent(eventId, callback) {
        var url = this._rootUrl + "/Events/" + eventId;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getEventRegistration(association, userToken, eventId, callback) {
        var url = this._rootUrl + "/Events/" + eventId + "/Registration?association=" + association;

        if (userToken !== undefined && userToken != null && userToken !== '') {
            url += "&user=" + userToken;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveEventRegistration(association, userToken, eventRegistration, callback) {
        var url = this._rootUrl + "/Events/" + eventRegistration.eventId + "/Registration?association=" + association + "&user=" + userToken;

        try {
            eventRegistration = VoterVoiceAPI.toJson(eventRegistration);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(eventRegistration, "application/json; charset=utf-8");
    }

    function cancelEventRegistration(association, userToken, eventId, callback) {
        var url = this._rootUrl + "/Events/" + eventId + "/CancelRegistration?association=" + association + "&user=" + userToken;

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send();
    }

    function getBlogPosts(association, callback) {
        var url = this._rootUrl + "/BlogPosts?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getBlogPost(association, blogPostId, callback) {
        var url = this._rootUrl + "/BlogPosts/" + blogPostId +"?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getComments(association, subjectType, subjectId, callback) {
        var url = this._rootUrl + "/Comments?association=" + association + "&subjectType=" + subjectType + "&subjectId=" + subjectId;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveComment(association, user, comment, subjectType, subjectId, callback) {
        var url = this._rootUrl + "/Comments?association=" + association + "&subjectType=" + subjectType + "&subjectId=" + subjectId;

        if (user !== undefined && user != null) {
            url += "&user=" + user;
        }

        try {
            comment = VoterVoiceAPI.toJson(comment);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(comment, "application/json; charset=utf-8");
    }

    function getMeeting(association, userToken, id, callback) {
        var url = this._rootUrl + "/Meetings/" + id + "?association=" + association;

        if (userToken !== undefined && userToken != null && userToken !== '') {
            url += "&user=" + userToken;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getMeetings(association, siteNumber, userToken, politicianId, startDate, endDate, callback) {
        var url = this._rootUrl + "/Meetings?association=" + association + "&startDate=" + startDate + "&endDate=" + endDate;

        if (userToken !== undefined && userToken != null && userToken !== '') {
            url += "&user=" + userToken;
        }

        if (politicianId !== undefined && politicianId != null && politicianId !== '' && politicianId > 0) {
            url += "&politicianId=" + politicianId;
        }

        if (siteNumber) {
            url += "&siteNumber=" + siteNumber;
        }

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getMeetingAttendees(association, id, callback) {
        var url = this._rootUrl + "/Meetings/" + id + "/Attendees?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveMeeting(association, siteNumber, userToken, meeting, thirdPartyVendorId, callback) {
        var url = this._rootUrl + "/Meetings?association=" + association;

        if (userToken !== undefined && userToken != null && userToken !== '') {
            url += "&user=" + userToken;
        }

        if (thirdPartyVendorId !== undefined && thirdPartyVendorId != null && thirdPartyVendorId !== '') {
            url += "&thirdPartyVendorId=" + thirdPartyVendorId;
        }

        if (siteNumber) {
            url += "&siteNumber=" + siteNumber;
        }

        try {
            meeting = VoterVoiceAPI.toJson(meeting);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(meeting, "application/json; charset=utf-8");
    }

    function deleteMeeting(association, id, userToken, callback) {
        var url = this._rootUrl + "/Meetings/" + id + "?association=" + association;

        if (userToken !== undefined && userToken != null && userToken !== '') {
            url += "&user=" + userToken;
        }

        var xhr = createRequest(this, "DELETE", url, 200, callback);
        xhr.send();
    }

    function getMeetingReportResponses(association, userToken, id, callback) {
        var url = this._rootUrl + "/Meetings/" + id + "/MeetingReports/" + userToken + "?association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function saveMeetingReportResponse(association, userToken, response, callback) {
        var url = this._rootUrl + "/Meetings/" + response.meetingId + "/MeetingReports/" + userToken + "?association=" + association;

        try {
            response = VoterVoiceAPI.toJson(response);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(response, "application/json; charset=utf-8");
    }

    function getMeetingReportResponse(association, userToken, id, callback) {
        var url = this._rootUrl + "/MeetingReportResponses/" + id + "?user=" + userToken + "&association=" + association;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getEmailOwnershipVerification(emailAddress, association, callback) {
        var emailOwnershipVerification = {
            emailAddress: emailAddress
        };

        var url = this._rootUrl + "/EmailOwnershipVerifications?association=" + association;
        try {
            emailOwnershipVerification = VoterVoiceAPI.toJson(emailOwnershipVerification);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, [200, 429], callback);
        xhr.send(emailOwnershipVerification, "application/json; charset=utf-8");
    }

    function getEmailOwnershipProof(verificationId, secretCode, callback) {
        var emailOwnershipVerification = {
            code: secretCode
        };

        var url = this._rootUrl + "/EmailOwnershipVerifications/" + verificationId + "/Proof";
        try {
            emailOwnershipVerification = VoterVoiceAPI.toJson(emailOwnershipVerification);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 201, callback);
        xhr.send(emailOwnershipVerification, "application/json; charset=utf-8");
    }

    function sendMessages(userToken, advocacyResponse, callback) {
        var url = this._rootUrl + "/Advocacy/Responses";
        if (userToken !== undefined) {
            url += "?user=" + userToken;
        }
        try {
            if (!advocacyResponse)
                logError.call(this, "Empty advocacyResponse argument:" + advocacyResponse);
            advocacyResponse = VoterVoiceAPI.toJson(advocacyResponse);
            if (!advocacyResponse)
                logError.call(this, "Empty advocacyResponse JSON:" + advocacyResponse + " implying suspicious stringify:" + VoterVoiceAPI.toJson);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 201, callback);
        xhr.send(advocacyResponse, "application/json; charset=utf-8");
    }

    function tellAFriend(userToken, tellAFriendContract, callback) {
        var url = this._rootUrl + "/TellAFriend?user=" + userToken;
        try {
            tellAFriendContract = VoterVoiceAPI.toJson(tellAFriendContract);
        } catch (e) {
            alert("JSON serialization failed (tellAFriendContract)...\r\n" + e);
            return;
        }
        var xhr = createRequest(this, "POST", url, 201, callback);
        xhr.send(tellAFriendContract, "application/json; charset=utf-8");
    }

    function getResponses(userToken, ids, destinationIds, callback) {
        if (arguments.length === 3) {
            callback = destinationIds;
            destinationIds = [];
        }
        var url = this._rootUrl + "/Advocacy/Responses?";
        if (userToken !== '')
            url += "user=" + userToken;
        for (var i = 0; i < ids.length; i++)
            url += "&deliveredMessageIds=" + ids[i];
        for (i = 0; i < destinationIds.length; i++)
            url += "&destinationIds=" + destinationIds[i];

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getAdvocacyResponse(id, callback) {
        var url = this._rootUrl + "/Advocacy/Responses/" + id;

        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function sanitizeHonorific(honorific, callback) {
        var url = this._rootUrl + "/Users/Honorifics?honorific=" + encodeURIComponent(honorific);
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getHonorificEquivalents(honorific, callback) {
        var url = this._rootUrl + "/Users/Honorifics/Equivalents?honorific=" + encodeURIComponent(honorific);
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getActivitiesAuthorization(token, association, callback) {
        var url = this._rootUrl + "/Activities/Authorization/" + token + "?association=" + association;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function requestActivitiesAuthorization(token, association, callback) {
        var url = this._rootUrl + "/Activities/Authorization/" + token + "?association=" + association;

        var contract = {
            token: token
        };

        try {
            contract = VoterVoiceAPI.toJson(contract);
        } catch (e) {
            alert("JSON serialization failed...\r\n" + e);
            return;
        }

        var xhr = createRequest(this, "POST", url, 200, callback);
        xhr.send(contract, "application/json; charset=utf-8");
    }

    function getActivitiesSummaries(userToken, association, callback) {
        var url = this._rootUrl + "/Users/" + userToken + "/Activities?association=" + association;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getOfficialStaffMemberships(association, officeOccupancyId, callback) {
        var url = this._rootUrl + "/Officials/" + officeOccupancyId + "/Staff?association=" + association;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getGeoJson(association, entityId, callback) {
        var url = this._rootUrl + "/Maps/GeoJson?entityId=" + entityId;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getBounds(association, areaId, country, district, callback) {
        var url = this._rootUrl + "/Maps/Bounds?areaId=" + areaId;
        if (country)
            url += "&country=" + country;
        if (district)
            url += "&district=" + district;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getConstituencyColors(association, params, callback) {
        var url = this._rootUrl + "/Maps/ConstituencyColors/" + params.id + '?association=' + association;
        url = formatMapParameters(url, params);
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getConstituencyInfo(association, params, callback) {
        var url = this._rootUrl + "/Maps/ConstituencyInfo/" + params.id + '?association=' + association;
        url = formatMapParameters(url, params);
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getLegend(association, params, callback) {
        var url = this._rootUrl + "/Maps/Legend/" + params.id + '?association=' + association;
        url = formatMapParameters(url, params);
        if (params.strategyName)
            url += "&strategyName=" + params.strategyName;
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function getCampaignMessagesCount(association, params, callback) {
        var url = this._rootUrl + "/Maps/CampaignMessagesCount/" + params.id + '?association=' + association;
        url = formatMapParameters(url, params, association);
        var xhr = createRequest(this, "GET", url, 200, callback);
        xhr.send();
    }

    function formatMapParameters(url, params) {
        if (params.entityId)
            url += "&entityId=" + params.entityId;
        if (params.drillDownEntityId)
            url += "&drillDownEntityId=" + params.drillDownEntityId;

        return url;
    }

    VoterVoiceAPI.prototype = {
        version: 1.0,
        isSupported: function() {
            return window.XMLHttpRequest !== undefined;
        },
        postLogEntry: postLogEntry,
        logWarning: logWarning,

        searchAddresses: searchAddresses,
        validateAddressCoordinates: validateAddressCoordinates,
        searchGovernments: searchGovernments,
        searchGovernmentOfficials: searchGovernmentOfficials,
        searchGovernmentLegislativeSessions: searchGovernmentLegislativeSessions,
        searchGovernmentLegislativeProposals: searchGovernmentLegislativeProposals,
        getGovernmentLegislativeProposalDetails: getGovernmentLegislativeProposalDetails,
        getKeyContactRelationshipTypes: getKeyContactRelationshipTypes,
        getLegislativeProposalSpotlights: getLegislativeProposalSpotlights,
        getSponsoredLegislativeProposalSpotlights: getSponsoredLegislativeProposalSpotlights,
        getCosponsoredLegislativeProposalSpotlights: getCosponsoredLegislativeProposalSpotlights,

        queryScorecards: queryScorecards,
        queryScorees: queryScorees,
        queryScorings: queryScorings,
        getScorecardCriterion: getScorecardCriterion,
        getPoliticianScorecard: getPoliticianScorecard,
        getOfficialScorecard: getOfficialScorecard,
        getOfficialTypeAheadSource: getOfficialTypeAheadSource,

        hasMultipleDistrictsPerEntity: hasMultipleDistrictsPerEntity,
        getCampaigns: getCampaigns,
        getCampaign: getCampaign,
        getTargetedMessages: getTargetedMessages,
        matchUser: matchUser,
        getCandidates: getCandidates,
        getDeliveryOptions: getDeliveryOptions,
        getSharedQuestion: getSharedQuestion,

        getProfile: getProfile,
        searchProfiles: searchProfiles,
        shared: shared,
        createVisit: createVisit,
        logVisit: logVisit,

        getNewsletters: getNewsletters,
        getNewsletter: getNewsletter,

        getPetition: getPetition,
        signPetition: signPetition,

        getSurvey: getSurvey,
        getSurveys: getSurveys,
        getSurveyResponses: getSurveyResponses,
        getSurveyResults: getSurveyResults,
        getQuestionResponses: getQuestionResponses,
        getSurveyMeetingReportResponses: getSurveyMeetingReportResponses,
        submitSurvey: submitSurvey,

        findUserIdentities: findUserIdentities,
        getUser: getUser,
        saveUser: saveUser,
        saveUserLocation: saveUserLocation,
        checkMobileNumber: checkMobileNumber,
        getMembership: getMembership,
        registerUser: registerUser,
        getUserRelationships: getUserRelationships,
        getUserGeographicRelationships: getUserGeographicRelationships,
        deleteUserRelationship: deleteUserRelationship,
        deleteGeographicRelationship: deleteUserGeographicRelationship,
        saveUserRelationship: saveUserRelationship,
        saveGeographicRelationship: saveGeographicRelationship,
        validateUserFields: validateUserFields,

        getSubscriptions: getSubscriptions,
        saveSubscriptions: saveSubscriptions,

        getVoterRegistration: getVoterRegistration,
        saveVoterRegistration: saveVoterRegistration,
        getVoterRegistrationInstructions: getVoterRegistrationInstructions,

        getEvent: getEvent,
        getEventRegistration: getEventRegistration,
        saveEventRegistration: saveEventRegistration,
        cancelEventRegistration: cancelEventRegistration,

        getBlogPosts: getBlogPosts,
        getBlogPost: getBlogPost,

        getComments: getComments,
        saveComment: saveComment,

        getMeeting: getMeeting,
        getMeetings: getMeetings,
        getMeetingAttendees: getMeetingAttendees,
        saveMeeting: saveMeeting,
        deleteMeeting: deleteMeeting,

        getMeetingReportResponses: getMeetingReportResponses,
        saveMeetingReportResponse: saveMeetingReportResponse,

        getMeetingReportResponse: getMeetingReportResponse,

        getEmailOwnershipVerification: getEmailOwnershipVerification,
        getEmailOwnershipProof: getEmailOwnershipProof,

        sendMessages: sendMessages,
        tellAFriend: tellAFriend,
        getAdvocacyResponse: getAdvocacyResponse,
        getResponses: getResponses,
        requestError: null,

        sanitizeHonorific: sanitizeHonorific,
        getHonorificEquivalents: getHonorificEquivalents,

        getActivitiesAuthorization: getActivitiesAuthorization,
        requestActivitiesAuthorization: requestActivitiesAuthorization,
        getActivitiesSummaries: getActivitiesSummaries,

        getOfficialStaffMemberships: getOfficialStaffMemberships,

        getGeoJson: getGeoJson,
        getConstituencyColors: getConstituencyColors,
        getConstituencyInfo: getConstituencyInfo,
        getBounds: getBounds,
        getLegend: getLegend,
        getCampaignMessagesCount: getCampaignMessagesCount
    };
})();;
function AdvocacyResponseFactory(api) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._api = api;
    this._response = {
        association: null,
        messages: null
    };
    this._visitTracker = null;
    this.friendlyError = null;
    this._messageNotifications = new Array();
    this._matchNotifications = new Array();
    this._matchRequest = null;
    this._matches = {};
    this._allDeliveries = [];
    this._sharedQuestions = {};
    this._allDeliveryOptionsSet = false;
    this._allSharedQuestionsFetched = true;
    this._fallbackValues = {};
    this.twitter = {};
}

(function(undefined) {
    function createApiCallback(factory, success, requestToClear) {
        return function(result, error) {
            if (requestToClear !== undefined)
                factory[requestToClear] = null;
            if (arguments.length > 1) {
                if (factory.friendlyError !== null)
                    factory.friendlyError(result, error);
            } else {
                success(factory, result);
            }
        };
    }

    function addMessages(factory, messages) {
        factory._response.messages = messages;
        for (var m = 0; m < messages.length; m++) {
            messages[m].randomSample = function() {
                this.messages.currentIndex = Math.floor(Math.random() * this.messages.length);
                return this.messages[this.messages.currentIndex];
            };
            messages[m].nextSample = function() {
                this.messages.currentIndex = (this.messages.currentIndex + 1) % this.messages.length;
                return this.messages[this.messages.currentIndex];
            };
            messages[m].currentSample = function() {
                return this.messages[this.messages.currentIndex];
            };
            messages[m].messageDeliverable = {};
            messages[m].twitterDeliverable = {};
            messages[m].facebookDeliverable = {};
            messages[m].phoneDeliverable = {};
            messages[m].deliverable = messages[m].messageDeliverable;
        }

        for (var n = 0; n < factory._messageNotifications.length; n++) {
            factory._messageNotifications[n](messages);
        }
    }

    function addMatches(factory, matches) {
        factory._matches = {};
        for (var i = 0; i < factory._allDeliveries.length; i++) {
            var cleanDeliveries = { deliveriesProperty: factory._allDeliveries[i].deliveriesProperty };
            if (factory._deliveries === factory._allDeliveries[i]) {
                factory._deliveries = cleanDeliveries;
            }
            factory._allDeliveries[i] = factory._allDeliveries[cleanDeliveries.deliveriesProperty] = cleanDeliveries;
        }
        for (var m = 0; m < matches.length; m++) {
            var messageId = matches[m].messageId;
            if (factory._matches[messageId] === undefined)
                factory._matches[messageId] = new Array();
            for (var m2 = 0; m2 < matches[m].matches.length; m2++) {
                var match = matches[m].matches[m2];
                factory._matches[messageId].push(match);
                factory._matches[match.type + match.id] = match;
            }
        }

        factory._latestRawMatches = matches;
        for (var n = 0; n < factory._matchNotifications.length; n++) {
            factory._matchNotifications[n](matches);
        }
        if (matches.length == 0 && factory.allOptionsSet)
            factory.allOptionsSet();
    }

    function fetchSharedQuestion(factory, id) {
        if (factory._sharedQuestions[id] === undefined) {
            factory._allSharedQuestionsFetched = false;
            factory._sharedQuestions[id] = null;
            factory._api.getSharedQuestion(id, function(result, error) {
                if (arguments.length > 1) {
                    delete factory._sharedQuestions[id];
                    if (factory.friendlyError !== null)
                        factory.friendlyError(result, error);
                } else {
                    factory._sharedQuestions[id] = result;

                    for (var sq in factory._sharedQuestions)
                        if (!factory._sharedQuestions[sq])
                            return;

                    factory._allSharedQuestionsFetched = true;
                    if (factory._allDeliveryOptionsSet && factory.allOptionsSet)
                        factory.allOptionsSet();
                }
            });
        }
    }

    function isMatchedTargetRateLimited(match, deliveryOption) {
        return indexOf(match.rateLimitedDeliveryMethods || [], (deliveryOption || {}).deliveryMethod) !== -1;
    }

    function foreachMessageDelivery(factory, callback) {
        if (factory._response.messages !== null) {
            for (var m = 0; m < factory._response.messages.length; m++) {
                var message = factory._response.messages[m];
                var matches = factory._matches[message.id];
                if (matches !== undefined) {
                    for (var t = 0; t < matches.length; t++) {
                        var match = matches[t];
                        var option = factory._deliveries[match.type + match.id];
                        if (!isMatchedTargetRateLimited(match, option)) {
                            callback.call(factory, message, match, option);
                        } else {
                            factory._response.isRateLimited = true;
                        }
                    }
                }
            }
        }
    }

    var globalLocalStorage;
    try {
        // firefox errors out simply accessing the field
        globalLocalStorage = window.localStorage;
    } catch (e) {
        globalLocalStorage = null;
    }
    var hasLocalStorage = function() {
        try {
            return !!globalLocalStorage.getItem;
        } catch (e) {
            return false;
        }
    };

    AdvocacyResponseFactory.localStorage = function(ls) {
        if (arguments.length > 0)
            globalLocalStorage = ls;
        return globalLocalStorage;
    };

    AdvocacyResponseFactory.prototype = {
        viaFacebook: function() {
            this._viaFacebook = true;
        },
        loadMessages: function(association, campaignId) {
            this._response.campaignId = campaignId;

            this._api.getTargetedMessages(association, campaignId,
                createApiCallback(this, addMessages));
        },
        initializeComposer: function(deliveriesProperty) {
            this._allDeliveries.push(this._allDeliveries[deliveriesProperty] = { deliveriesProperty: deliveriesProperty });
            if (!this._deliveries) {
                this._deliveries = this._allDeliveries[0];
            }
        },
        switchComposer: function(deliverableProperty, deliveriesProperty) {
            for (var i = 0; i < this._response.messages.length; i++) {
                this._response.messages[i].deliverable = this._response.messages[i][deliverableProperty];
            }
            this._deliveries = this._allDeliveries[deliveriesProperty];
            return true;
        },
        setVisitTracker: function(visitTracker) {
            this._visitTracker = visitTracker;
        },
        beforeMatching: null,
        matchUser: function(association, userToken, campaignId, addresses) {
            if (this._matchRequest !== null) {
                var errorHandler = this._api.requestError;

                this._api.requestError = null;
                this._matchRequest.abort();
                this._api.requestError = errorHandler;

                this._matchRequest = null;
            }

            if (this.beforeMatching) {
                this.beforeMatching();
            }
            this._response.association = association;

            if (arguments.length < 4)
                this._matchRequest = this._api.matchUser(association, userToken, campaignId,
                    createApiCallback(this, addMatches, '_matchRequest'));
            else
                this._matchRequest = this._api.matchUser(association, userToken, campaignId,
                    createApiCallback(this, addMatches, '_matchRequest'),
                    addresses);
        },
        getMatch: function(typeId) {
            return this._matches[typeId];
        },
        allOptionsSet: null,
        getDeliveryOptions: function(targetType, targetId, association, messageId, callback) {
            var factory = this;
            this._api.getDeliveryOptions(targetType, targetId, association, messageId,
                function(result, error) {
                    if (arguments.length > 1) {
                        if (factory.friendlyError !== null)
                            factory.friendlyError(result, error);
                    } else {
                        callback(targetType, targetId, result);
                    }
                });
        },
        setDeliveryOption: function(targetType, targetId, option, deliveriesProperty) {
            for (var d = 0; d < this._allDeliveries.length; d++) {
                if (!deliveriesProperty || this._allDeliveries[d] === this._allDeliveries[deliveriesProperty]) {
                    this._allDeliveries[d][targetType + targetId] = option;
                }
            }
            if (option !== null && option.sharedQuestionIds !== undefined) {
                for (var q = 0; q < option.sharedQuestionIds.length; q++) {
                    var id = option.sharedQuestionIds[q];
                    fetchSharedQuestion(this, id);
                }
            }

            for (var messageId in this._matches) {
                if (this._matches[messageId].length === undefined)
                    continue; // skip the non-array values keyed on type+id
                for (var m2 = 0; m2 < this._matches[messageId].length; m2++) {
                    var t = this._matches[messageId][m2].type + this._matches[messageId][m2].id;
                    for (d = 0; d < this._allDeliveries.length; d++) {
                        if (this._allDeliveries[d][t] === undefined) {
                            return;
                        }
                    }
                }
            }

            this._allDeliveryOptionsSet = true;
            if (this._allSharedQuestionsFetched && this.allOptionsSet)
                this.allOptionsSet();
        },
        isTwitter: function () {
            var _isTwitter = false;
            foreachMessageDelivery(this, function (message, match, option) {
                if (option && option.deliveryMethod === 'twitter') {
                    _isTwitter = true;
                }
            });
            return _isTwitter;
        },        
        missingSocialMediaCredentials: function() {
            var deliveryMethods = new Array();
            foreachMessageDelivery(this, function(message, match, option) {
                if (option) {
                    switch (option.deliveryMethod) {
                        case 'twitter':
                        case 'facebook':
                            if (!this[option.deliveryMethod] && indexOf(deliveryMethods, option.deliveryMethod) === -1) {
                                deliveryMethods.push(option.deliveryMethod);
                            }
                    }
                }
            });
            if (deliveryMethods.length === 0) {
                //If not all the deliveries are available, figure out the delivery method anyway so the machine 
                //can move forward with the Auth Popup while we are still on the button click's thread and can raise the auth popup. 
                var allDeliveriesAcquired = true;
                if (this._response.messages !== null) {
                    for (var m = 0; m < this._response.messages.length; m++) {
                        var message = this._response.messages[m];
                        var matches = this._matches[message.id];
                        if (matches !== undefined) {
                            for (var t = 0; t < matches.length; t++) {
                                var match = matches[t];
                                var option = this._deliveries[match.type + match.id];
                                if (option === undefined) {
                                    allDeliveriesAcquired = false;
                                    break;
                                }
                            }
                        } else {
                            allDeliveriesAcquired = false;
                            break;
                        }
                    }
                }

                function addMissingDeliveryMethod(deliveryMethod) {
                    if (!this[deliveryMethod]) {
                        deliveryMethods.push(deliveryMethod);
                    }
                }
                if (!allDeliveriesAcquired) {
                    //Not all deliveries are 
                    switch (this._deliveries.deliveriesProperty) {
                        case 'twitterDeliveries':
                            addMissingDeliveryMethod.call(this, "twitter");
                            break;
                        case 'facebookDeliveries':
                            addMissingDeliveryMethod.call(this, "facebook");
                            break;
                    };
                }
            }
            return deliveryMethods;
        },
        setSocialMediaCredentials: function(deliveryMethod, authToken) {
            this[deliveryMethod] = authToken;
        },
        getSharedQuestion: function(id) {
            return this._sharedQuestions[id];
        },
        aggregateRequirements: function() {
            var fields = {};
            var fieldRecipients = {};
            var questionIds = {};
            var questions = {};

            function cleanUpCwcQuestionDuplicates(option, question) {
                if (option.deliveryMethod === 'communicatingwithcongressapi') {
                    for (var rq = option.requiredQuestions.length - 1; rq >= 0; rq--) {
                        if (option.requiredQuestions[rq].question === question) {
                            option.requiredQuestions.splice(rq, 1);
                            break;
                        }
                    }
                }
            }

            foreachMessageDelivery(this, function(message, match, option) {
                var t = match.type + match.id;
                if (option) {
                    if (option.requiredUserFields !== undefined) {
                        for (var uf = 0; uf < option.requiredUserFields.length; uf++) {
                            var fieldName = option.requiredUserFields[uf];
                            fields[fieldName] = true;
                            if (!fieldRecipients[fieldName])
                                fieldRecipients[fieldName] = new Array();
                            fieldRecipients[fieldName].push(t);
                        }
                    }
                    if (option.sharedQuestionIds !== undefined) {
                        var hasSharedTopicQuestion = false;
                        for (var sq = 0; sq < option.sharedQuestionIds.length; sq++) {
                            var sharedQuestionId = option.sharedQuestionIds[sq];

                            if (sharedQuestionId === 'US') {
                                hasSharedTopicQuestion = true;
                            }

                            var preSelected = false;
                            if (message.preSelectedAnswers !== null) {
                                for (var psa = 0; psa < message.preSelectedAnswers.length; psa++) {
                                    if (message.preSelectedAnswers[psa].questionId === sharedQuestionId)
                                        preSelected = true;
                                }
                            }
                            if (preSelected === false) {
                                if (!questionIds[sharedQuestionId])
                                    questionIds[sharedQuestionId] = new Array();
                                questionIds[sharedQuestionId].push(t);
                            }
                        }

                        if (hasSharedTopicQuestion) {
                            cleanUpCwcQuestionDuplicates(option, 'Topic');
                        }
                    }
                    if (option.requiredQuestions !== undefined) {
                        for (var rq = 0; rq < option.requiredQuestions.length; rq++) {
                            if (!questions[t])
                                questions[t] = new Array();
                            questions[t].push(option.requiredQuestions[rq]);
                        }
                    }
                }
            });
            var requirements = {
                userFields: new Array(),
                userFieldRecipients: fieldRecipients,
                sharedQuestionRecipients: questionIds,
                questions: questions
            };
            for (var field in fields) {
                requirements.userFields.push(field);
            }
            return requirements;
        },
        answer: function(question, answer) {
            question.answer = answer;

            if (question.question === vvui.translateLanguage('HonorificName')) {
                foreachMessageDelivery(this, function(message, match, option) {
                   if (option) {
                       if (option.requiredQuestions !== undefined) {
                           for (var rq = 0; rq < option.requiredQuestions.length; rq++)
                               if (option.requiredQuestions[rq].question === vvui.translateLanguage('HonorificName'))
                                   option.requiredQuestions[rq].answer = answer;
                       }
                   }
                });
            }
        },
        isQuestionnaireComplete: function(additionalUserFields) {
            var complete = true;
            foreachMessageDelivery(this, function(message, match, option) {
                if (option) {
                    if (option.requiredUserFields !== undefined) {
                        for (var uf = 0; uf < option.requiredUserFields.length; uf++) {
                            for (var auf = 0; auf < additionalUserFields.length; auf++) {
                                if (!additionalUserFields[auf].answer) {
                                    complete = false;
                                }
                            }
                        }
                    }
                    if (option.requiredQuestions !== undefined) {
                        for (var rq = 0; rq < option.requiredQuestions.length; rq++)
                            if (!option.requiredQuestions[rq].answer) {
                                complete = false;
                            }
                    }
                    if (option.sharedQuestionIds !== undefined) {
                        for (var sq = 0; sq < option.sharedQuestionIds.length; sq++) {
                            var sqId = option.sharedQuestionIds[sq];
                            var sharedQuestion = null;
                            if (message.preSelectedAnswers !== null) {
                                for (var psa = 0; psa < message.preSelectedAnswers.length; psa++) {
                                    if (message.preSelectedAnswers[psa].questionId === sqId) {
                                        sharedQuestion = message.preSelectedAnswers[psa];
                                        break;
                                    }
                                }
                            }
                            if (sharedQuestion === null) {
                                sharedQuestion = this.getSharedQuestion(sqId);
                            }
                            if (!sharedQuestion.answer) {
                                complete = false;
                            }
                        }
                    }
                }
            });
            return complete;
        },

        _getFullBody: function(deliverable) {
            //IE7 does not support String.trim
            var trim = typeof String.prototype.trim === 'function'
                ? String.prototype.trim
                : function() { return this.replace(/^\s+|\s+$/g, ''); };

            var parts = new Array();
            if (deliverable.opening !== undefined && deliverable.opening != null) {
                deliverable.opening = trim.call(deliverable.opening);
                if (deliverable.opening !== "")
                    parts.push(deliverable.opening);
            }
            if (deliverable.body !== undefined && deliverable.body != null) {
                parts.push(trim.call(deliverable.body));
            }
            if (deliverable.closing !== undefined && deliverable.closing != null) {
                deliverable.closing = trim.call(deliverable.closing);
                if (deliverable.closing !== "")
                    parts.push(deliverable.closing);
            }
            if (parts.length > 0)
                return parts.join("\r\n\r\n");
        },
        allDeliveriesHaveSubjectAndBody: function() {
            var result = true;
            var subjectRequired = this._deliveries === this._allDeliveries.messageDeliveries;
            foreachMessageDelivery(this, function(message, match, option) {
                if (option && option.deliveryMethod === 'phone') {
                    return;
                }
                if (option) {
                    if (subjectRequired && !message.deliverable.subject) {
                        result = false;
                    }
                    var body = this._getFullBody(message.deliverable);
                    if (body === undefined || body === "") {
                        result = false;
                    }
                }
            });
            return result;
        },
        _findInFullBody: function(regex, maxWordCount) {
            var result = null;
            foreachMessageDelivery(this, function(message, match, option) {
                if (message.phoneCallMethod || result !== null) {
                    return;
                }
                if (option) {
                    var body = this._getFullBody(message.deliverable);
                    result = regex.exec(body);
                    if (result !== null) {
                        result = result.length > 1 ? result[1] : result[0];
                        // max word count:... (trim the ,/:)
                        if (result.slice(0, -1).match(/\S+/g).length > maxWordCount) {
                            result = null;
                        }
                    }
                }
            });
            return result;
        },
        findSalutation: function() {
            var salutationRegex = /^[\s\r\n]*([^\r\n]{1,99}[,:])\s*([\r\n]|$)/;
            return this._findInFullBody(salutationRegex, 5);
        },
        findClosing: function() {
            var closingRegex = /^[\r\n][\s]*Sincerely,/m;
            var closing = this._findInFullBody(closingRegex, 5);
            if (closing != null) {
                //IE8 does not support String.trim
                closing = closing.replace(/^\s+|\s+$/g, '');
            }
            return closing;
        },
        generateResponse: function(userId, signature, defaultAddressType) {
            var tempMessages = {};
            var hasPhoneScripts = false;

            foreachMessageDelivery(this, function (message, match, option) {

                if (option && option.deliveryMethod === 'phone') {
                    hasPhoneScripts = true;
                    if (match.wasPhoneCallCompleted !== true) {
                        return;
                    }
                }

                if (!tempMessages[message.id])
                    tempMessages[message.id] = {
                        message: message,
                        targets: new Array()
                    };
                var targets = tempMessages[message.id].targets;

                if (option) {
                    var questionnaire = new Array();
                    if (option.requiredQuestions !== undefined) {
                        for (var rq = 0; rq < option.requiredQuestions.length; rq++) {
                            questionnaire.push({
                                question: option.requiredQuestions[rq].question,
                                answer: option.requiredQuestions[rq].answer
                            });
                        }
                    }
                    if (option.sharedQuestionIds !== undefined) {
                        for (var sq = 0; sq < option.sharedQuestionIds.length; sq++) {
                            var sharedQuestion = null;
                            if (message.preSelectedAnswers !== null) {
                                for (var psa = 0; psa < message.preSelectedAnswers.length; psa++) {
                                    if (message.preSelectedAnswers[psa].questionId === option.sharedQuestionIds[sq]) {
                                        sharedQuestion = message.preSelectedAnswers[psa];
                                        break;
                                    }
                                }
                            }
                            if (sharedQuestion === null) {
                                sharedQuestion = this.getSharedQuestion(option.sharedQuestionIds[sq]);
                            }
                            questionnaire.push({
                                question: sharedQuestion.question,
                                answer: sharedQuestion.answer
                            });
                        }
                    }
                    var target = {
                        type: match.type,
                        id: match.id,
                        deliveryMethod: option.deliveryMethod,
                        questionnaire: questionnaire
                    };
                    targets.push(target);
                }
            });

            var response = this._createResponseStub(userId, signature, defaultAddressType);
            this._includeMessagesInResponse(response, tempMessages);
            if (hasPhoneScripts === true) {
                response.isPhoneResponses = true;
            }
            return response;
        },
        _createResponseStub: function(userId, signature, defaultAddressType) {
            var response = {
                association: this._response.association,
                userId: userId,
                campaignId: this._response.campaignId,
                messages: new Array(),
                signature: signature,
                defaultAddressType: defaultAddressType,
                broadcastReceipt: null
            };

            var deliveryMethods = [];
            foreachMessageDelivery(this, function(message, match, option) {
                if (option && this[option.deliveryMethod] && indexOf(deliveryMethods, option.deliveryMethod) === -1) {
                    deliveryMethods.push(option.deliveryMethod);
                }
            });
            if (deliveryMethods.length !== 0) {
                var socialMediaCredentials = {};
                for (var i = 0; i < deliveryMethods.length; i++) {
                    var deliveryMethod = deliveryMethods[i];
                    var credentials = this[deliveryMethod];
                    switch (deliveryMethod) {
                        case 'twitter':
                            socialMediaCredentials.twitter = {
                                accessToken: credentials.oauthAccessToken,
                                oauthToken: credentials.oauthToken
                            };
                            break;
                        case 'facebook':
                            socialMediaCredentials.facebook = {
                                accessToken: credentials.oauthAccessToken
                            };
                            break;
                    }
                }
                response.socialMediaCredentials = socialMediaCredentials;
            }

            if (this._visitTracker !== null) {
                this._visitTracker.setOriginalReferrers(response);
            }
            if (this.trackingId !== undefined) {
                response.trackingId = this.trackingId;
            }
            if (this._viaFacebook === true) {
                response.viaFacebook = true;
            }
            return response;
        },
        _includeMessagesInResponse: function(response, tempMessages) {
            var sampleMessageMapping = {};
            for (var id in tempMessages) {
                var message = tempMessages[id].message;
                var targets = tempMessages[id].targets;
                if (targets.length > 0)
                    response.messages.push({
                        messageId: message.id,
                        targets: targets,
                        subject: message.deliverable.subject,
                        body: this._getFullBody(message.deliverable),
                        complimentaryClose: message.deliverable.complimentaryClose,
                        //modified: message.deliverable.modified
                    });
                if (message.deliverable.sampleMessageId !== null) {
                    sampleMessageMapping[message.id] = message.deliverable.sampleMessageId;
                }
            }
            this._sampleMessageMapping = sampleMessageMapping;
        },
        findSampleMessageUsedInResponse: function(targetedMessageId) {
            return this._sampleMessageMapping[targetedMessageId];
        },
        generatePhoneResponse: function(userId, signature, defaultAddressType, match, option, message, content) {
            var response = this._createResponseStub(userId, signature, defaultAddressType);
            var targets = [{
                type: match.type,
                id: match.id,
                deliveryMethod: option.deliveryMethod,
                questionnaire: []
            }];
            response.messages.push({
                messageId: message.id,
                targets: targets,
                subject: "",
                body: content === null ? null : content.recipientResponse,
                complimentaryClose: "",
                //modified: false,
                recipientName: content === null ? null : content.recipientName
            });
            return response;
        },
        respond: function(userToken, response, callback) {
            this._api.sendMessages(userToken, response, callback);
        },
        isUserResponseRequired: function(result) {
            for (var m = 0; m < result.deliveredMessages.length; m++) {
                var message = result.deliveredMessages[m];
                var matches = this._matches[message.targetedId];
                if (matches !== undefined) {
                    for (var r = 0; r < matches.length; r++) {
                        var match = matches[r];
                        var deliveryOption = this._deliveries[match.type + match.id];
                        if (deliveryOption && deliveryOption.userConfirmationRequired) {
                            return true;
                        }
                    }
                }
            }
            return false;
        },
        storeResponse: function(userToken, result, specificMatch) {
            try {
                if (!hasLocalStorage()) {
                    return this._gatherDeliveries(result, specificMatch);
                }

                for (var m = 0; m < result.deliveredMessages.length; m++) {
                    var message = result.deliveredMessages[m];
                    this._storeResponseMessage(userToken, message, specificMatch);
                }
            } catch (e) {
                try {
                    new VoterVoiceLogger(this._api).logException(e);
                } catch (e2) {}
            }
        },
        _gatherDeliveries: function(result, specificMatch) {
            var deliveries = [];

            function gatherDelivery(match, deliveryOption) {
                deliveries.push({ name: match.name, deliveryMethod: deliveryOption.deliveryMethod, type: match.type, id: match.id });
            }
            for (var m = 0; m < result.deliveredMessages.length; m++) {
                var message = result.deliveredMessages[m];
                this._iterateMessageMatches.call(this, message, specificMatch, gatherDelivery);
            }
            return deliveries;
        },
        _iterateMessageMatches: function(message, specificMatch, action) {

            function processMatch(match) {
                var deliveryOption = this._deliveries[match.type + match.id];
                if (deliveryOption) {
                    if (!isMatchedTargetRateLimited(match, deliveryOption)) {
                        action.call(this, match, deliveryOption);
                    }
                }
            }

            if (specificMatch === undefined) {
                var matches = this._matches[message.targetedId];
                if (matches !== undefined) {
                    for (var r = 0; r < matches.length; r++) {
                        processMatch.call(this, matches[r]);
                    }
                }
            } else {
                processMatch.call(this, specificMatch);
            }
        },
        _storeResponseDelivery: function(userToken, childKey) {
            try {
                var visitMetaData = this._visitTracker._referrer.visitMetaData;
                var storageKey = "Deliveries-" + userToken + "-" + visitMetaData.subjectIdentifier;
                var serialized = globalLocalStorage.getItem(storageKey);
                var storedChildren = serialized ? JSON.parse(serialized) : new Array();
                if (indexOf(storedChildren, childKey) < 0) {
                    storedChildren.push(childKey);
                    globalLocalStorage.setItem(storageKey, JSON.stringify(storedChildren));
                }
            } catch (e) {
                if (e.code !== DOMException.QUOTA_EXCEEDED_ERR)
                    throw e;
            }
        },
        _storeResponseMessage: function(userToken, message, specificMatch) {
            var recipients = new Array();

            function addRecipient(match, deliveryOption) {
                recipients.push({ name: match.name, deliveryMethod: deliveryOption.deliveryMethod, type: match.type, id: match.id });
            }

            this._iterateMessageMatches.call(this, message, specificMatch, addRecipient);

            try {
                var storageKey;
                if (message.deliveredId === null) {
                    for (var t = 0; t < message.destinationIds.length; t++) {
                        storageKey = "Transaction-" + message.destinationIds[t];
                        globalLocalStorage.setItem(storageKey, JSON.stringify(recipients));
                        this._storeResponseDelivery(userToken, storageKey);
                    }
                } else {
                    storageKey = "Delivered-" + message.deliveredId;
                    globalLocalStorage.setItem(storageKey, JSON.stringify(recipients));
                    this._storeResponseDelivery(userToken, storageKey);
                }
            } catch (e) {
                if (e.code !== DOMException.QUOTA_EXCEEDED_ERR)
                    throw e;
            }
        },
        getResponseRecipient: function(userToken, transactionId, callback) {
            var recipients = null;
            if (hasLocalStorage()) {
                var serialized = globalLocalStorage.getItem("Transaction-" + transactionId);
                recipients = serialized ? JSON.parse(serialized) : null;
                if (recipients !== null) {
                    for (var r = 0; r < recipients.length; r++) {
                        recipients[r].destinationId = transactionId;
                    }
                }
            }
            if (recipients === null) {
                this._api.getResponses(userToken, [], [transactionId], callback);
            } else {
                callback(recipients);
            }
        },
        downloadLetter: function(userToken, transactionId, callback) {
            this._api.getLetters(userToken, [transactionId], callback);
        },
        getDeliveredTargetsFromLocalStorage: function(userToken, campaignId) {
            if (hasLocalStorage()) {
                var recipients = { message: [], twitter: [], facebook: [], phone: [] };
                var serializedKeys = globalLocalStorage.getItem("Deliveries-" + userToken + "-" + campaignId);
                var keys = serializedKeys ? JSON.parse(serializedKeys) : null;
                if (keys !== null) {
                    for (var i = 0; i < keys.length; i++) {
                        var serializedRecipients = globalLocalStorage.getItem(keys[i]);
                        var sourceRecipients = serializedRecipients ? JSON.parse(serializedRecipients) : [];
                        this._buildDeliveredTargets(sourceRecipients, recipients);
                    }
                }
                return recipients;
            }
            return null;
        },
        getDeliveredTargetsFromSerialized: function(userToken, serialized) {
            var recipients = { message: [], twitter: [], facebook: [], phone: [] };
            var source = serialized ? JSON.parse(serialized) : { userToken: null, deliveries: [] };
            if (source.userToken === userToken) {
                this._buildDeliveredTargets(source.deliveries, recipients);
            }
            return recipients;
        },
        _buildDeliveredTargets: function(source, destination) {
            for (var i = 0; i < source.length; i++) {
                switch (source[i].deliveryMethod) {
                    case 'email':
                    case 'webform':
                    case 'fax':
                    case 'letter':
                    case 'communicatingwithcongressapi':
                    case 'regulationgovapi':
                        destination.message.push(source[i]);
                        break;
                    case 'twitter':
                        destination.twitter.push(source[i]);
                        break;
                    case 'facebook':
                        destination.facebook.push(source[i]);
                        break;
                    case 'phone':
                        destination.phone.push(source[i]);
                        break;
                }
            }
        },
        getResponseRecipients: function(userToken, deliveredId, callback) {
            var recipients = null;
            if (hasLocalStorage()) {
                var serialized = globalLocalStorage.getItem("Delivered-" + deliveredId);
                recipients = serialized ? JSON.parse(serialized) : null;
                if (recipients !== null) {
                    for (var r = 0; r < recipients.length; r++) {
                        recipients[r].deliveredMessageId = deliveredId;
                    }
                }
            }
            if (recipients === null) {
                this._api.getResponses(userToken, [deliveredId], callback);
            } else {
                callback(recipients);
            }
        },

        getProfile: function(association, targetId, targetType, callback) {
            this._api.getProfile(association, targetId, targetType, callback);
        },
        registerMessageNotification: function(callback) {
            this._messageNotifications.push(callback);
        },
        registerMatchNotification: function(callback, fireNowIfMatched) {
            this._matchNotifications.push(callback);
            if (fireNowIfMatched && this._latestRawMatches) {
                callback(this._latestRawMatches);
            }
        },
        getHonorificEquivalents: function(honorific, callback) {
            this._api.getHonorificEquivalents(honorific, callback);
        }
    };

    function indexOf(array, item) {
        //IE8 does not support Array.indexOf
        for (var i = 0; i < array.length; i++) {
            if (array[i] === item) {
                return i;
            }
        }
        return -1;
    }
})();;
var hexcase = 0;
var b64pad = "";
var chrsz = 8;

function obs(s) {
	return b2h(cmc5(s2b(s), s.length * chrsz))
}

function b64_md5(s) {
	return binl2b64(cmc5(s2b(s), s.length * chrsz))
}

function str_md5(s) {
	return binl2str(cmc5(s2b(s), s.length * chrsz))
}

function hex_hmac_md5(a, b) {
	return b2h(core_hmac_md5(a, b))
}

function b64_hmac_md5(a, b) {
	return binl2b64(core_hmac_md5(a, b))
}

function str_hmac_md5(a, b) {
	return binl2str(core_hmac_md5(a, b))
}

function cmc5(x, e) {
	x[e >> 5] |= 0x80 << ((e) % 32);
	x[(((e + 64) >>> 9) << 4) + 14] = e;
	var a = 1732584193;
	var b = -271733879;
	var c = -1732584194;
	var d = 271733878;
	for (var i = 0; i < x.length; i += 16) {
		var f = a;
		var g = b;
		var h = c;
		var j = d;
		a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
		d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
		c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
		b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
		a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
		d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
		c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
		b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
		a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
		d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
		c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
		b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
		a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
		d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
		c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
		b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
		a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
		d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
		c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
		b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
		a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
		d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
		c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
		b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
		a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
		d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
		c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
		b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
		a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
		d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
		c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
		b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
		a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
		d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
		c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
		b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
		a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
		d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
		c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
		b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
		a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
		d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
		c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
		b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
		a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
		d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
		c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
		b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
		a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
		d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
		c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
		b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
		a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
		d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
		c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
		b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
		a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
		d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
		c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
		b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
		a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
		d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
		c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
		b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
		a = safe_add(a, f);
		b = safe_add(b, g);
		c = safe_add(c, h);
		d = safe_add(d, j)
	}
	return Array(a, b, c, d)
}

function md5_cmn(q, a, b, x, s, t) {
	return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
}

function md5_ff(a, b, c, d, x, s, t) {
	return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)
}

function md5_gg(a, b, c, d, x, s, t) {
	return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)
}

function md5_hh(a, b, c, d, x, s, t) {
	return md5_cmn(b ^ c ^ d, a, b, x, s, t)
}

function md5_ii(a, b, c, d, x, s, t) {
	return md5_cmn(c ^ (b | (~d)), a, b, x, s, t)
}

function core_hmac_md5(a, b) {
	var c = s2b(a);
	if (c.length > 16) c = cmc5(c, a.length * chrsz);
	var d = Array(16),
	opad = Array(16);
	for (var i = 0; i < 16; i++) {
		d[i] = c[i] ^ 0x36363636;
		opad[i] = c[i] ^ 0x5C5C5C5C
	}
	var e = cmc5(d.concat(s2b(b)), 512 + b.length * chrsz);
	return cmc5(opad.concat(e), 512 + 128)
}

function safe_add(x, y) {
	var a = (x & 0xFFFF) + (y & 0xFFFF);
	var b = (x >> 16) + (y >> 16) + (a >> 16);
	return (b << 16) | (a & 0xFFFF)
}

function bit_rol(a, b) {
	return (a << b) | (a >>> (32 - b))
}

function s2b(a) {
	var b = Array();
	var c = (1 << chrsz) - 1;
	for (var i = 0; i < a.length * chrsz; i += chrsz) b[i >> 5] |= (a.charCodeAt(i / chrsz) & c) << (i % 32);
	return b
}

function binl2str(a) {
	var b = "";
	var c = (1 << chrsz) - 1;
	for (var i = 0; i < a.length * 32; i += chrsz) b += String.fromCharCode((a[i >> 5] >>> (i % 32)) & c);
	return b
}

function b2h(a) {
	var b = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
	var c = "";
	for (var i = 0; i < a.length * 4; i++) {
		c += b.charAt((a[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) + b.charAt((a[i >> 2] >> ((i % 4) * 8)) & 0xF)
	}
	return c
}

function binl2b64(a) {
	var b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	var c = "";
	for (var i = 0; i < a.length * 4; i += 3) {
		var d = (((a[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) | (((a[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) | ((a[i + 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF);
		for (var j = 0; j < 4; j++) {
			if (i * 8 + j * 6 > a.length * 32) c += b64pad;
			else c += b.charAt((d >> 6 * (3 - j)) & 0x3F)
		}
	}
	return c
};
/*
 * JavaScript browser detector v1.0
 * http://www.corephp.com/
 *
 * @copyright	Copyright (C) 2010 'corePHP' LLC, www.corephp.com. All rights reserved.
 * @license GNU/GPL
 * http://www.corephp.com/License-gnu.txt
 */

try{
	var SEP   = '|';
	ua    = window.navigator.userAgent.toLowerCase();
	opera = ua.indexOf( "opera" ) >= 0;
	ie    = ua.indexOf( "msie" ) >= 0 && !opera;
	iemac = ie && ua.indexOf( "mac" ) >= 0;
	moz   = ua.indexOf( "mozilla" ) && !ie && !opera;
	os    = window.navigator.platform;
}catch(e){
	// Probably IE 6
}

function activeXDetect( componentClassID )
{
	componentVersion = document.body.getComponentVersion('{' + componentClassID + '}', 'ComponentID');

	return (componentVersion != null) ? componentVersion : false;
}

function extractVersions( s )
{
	extractedVersions = "";
	for ( var i = 0; i < s.length; i++ ) {
		charAtValue = s.charAt( i );
		if ( (charAtValue >= '0' && charAtValue <= '9')
			|| charAtValue == '.'
			|| charAtValue == '_'
			|| charAtValue == ','
		) {
			extractedVersions += charAtValue;
		}
	}

	return extractedVersions;
}

function stripIllegalChars( value )
{
	t = "";
	value = value.toLowerCase();
	for (i = 0; i < value.length; i++) {
		if (value.charAt( i ) != '\n' && value.charAt( i ) != '/' && value.charAt( i ) != "\\" ) {
			t += value.charAt( i );
		} else if ( value.charAt( i ) == '\n' ) {
			t += "n";
		}
	}

	return t;
}

function stripFullPath( tempFileName, lastDir )
{
	fileName = tempFileName;
	filenameStart = 0;
	filenameStart = fileName.lastIndexOf( lastDir );
	if (filenameStart < 0) filenameStart = 0;
	filenameFinish = fileName.length;
	fileName = fileName.substring( filenameStart + lastDir.length, filenameFinish );

	return fileName;
}

function fingerprint_browser()
{
	t = ua;
	return t;
}

function fingerprint_os()
{
	t = window.navigator.platform;
	return t;
}

function fingerprint_display()
{
	t = "";
	if ( self.screen ) {
		t += screen.colorDepth + SEP + screen.width + SEP + screen.height + SEP + screen.availHeight;
	}

	return t;
}

function fingerprint_software()
{
	t = "";
	isFirst = true;

	if ( window.navigator.plugins.length > 0 ) {
		if ( opera ) {
			temp = "";
			lastDir = "Plugins";;
			for ( i = 0; i < window.navigator.plugins.length; i++ ) {
				plugin = window.navigator.plugins[i];
				if ( isFirst == true ) {
					temp += stripFullPath( plugin.filename, lastDir );
					isFirst = false;
				} else {
					temp += SEP + stripFullPath( plugin.filename, lastDir );
				}
			}
			t = stripIllegalChars(temp);
		} else {
			for ( i = 0; i < window.navigator.plugins.length; i++ ) {
				plugin = window.navigator.plugins[i];
				if ( isFirst == true ) {
					t += plugin.filename;
					isFirst = false;
				} else {
					t += SEP + plugin.filename;
				}
			}
		}
	} else if ( window.navigator.mimeTypes.length > 0 ) {
		for ( i = 0; i < window.navigator.mimeTypes.length; i++ ) {
			mimeType = window.navigator.mimeTypes[i];
			if ( isFirst == true ) {
				t += mimeType.type;
				isFirst = false;
			} else {
				t += SEP + mimeType.type;
			}
		}
	} else if ( ie ) {
		components = new Array( "7790769C-0471-11D2-AF11-00C04FA35D02", "89820200-ECBD-11CF-8B85-00AA005B4340",
			"283807B5-2C60-11D0-A31D-00AA00B92C03", "4F216970-C90C-11D1-B5C7-0000F8051515",
			"44BBA848-CC51-11CF-AAFA-00AA00B6015C", "9381D8F2-0288-11D0-9501-00AA00B911A5",
			"4F216970-C90C-11D1-B5C7-0000F8051515", "5A8D6EE0-3E18-11D0-821E-444553540000",
			"89820200-ECBD-11CF-8B85-00AA005B4383", "08B0E5C0-4FCB-11CF-AAA5-00401C608555",
			"45EA75A0-A269-11D1-B5BF-0000F8051515", "DE5AED00-A4BF-11D1-9948-00C04F98BBC9",
			"22D6F312-B0F6-11D0-94AB-0080C74C7E95", "44BBA842-CC51-11CF-AAFA-00AA00B6015B",
			"3AF36230-A269-11D1-B5BF-0000F8051515", "44BBA840-CC51-11CF-AAFA-00AA00B6015C",
			"CC2A9BA0-3BDD-11D0-821E-444553540000", "08B0E5C0-4FCB-11CF-AAA5-00401C608500",
			"D27CDB6E-AE6D-11CF-96B8-444553540000", "2A202491-F00D-11CF-87CC-0020AFEECF20"
		);
		document.body.addBehavior( "#default#clientCaps" );
		for (i = 0; i < components.length; i++) {
			ver = activeXDetect( components[i] );
			if ( ver ) {
				if ( isFirst == true ) {
					t += ver;
					isFirst = false;
				} else {
					t += SEP + ver;
				}
			} else {
				t += SEP + "null";
			}
		}
	}

	return t;
}

function form_add_data( fd, name, value )
{
	if ( fd && fd.length > 0 ) {
		fd += "&";
	} else {
		fd = "";
	}

	fd += name + '=' + escape(value);

	return fd;
}

function form_add_fingerprint( fd, name, value ) {
	fd = form_add_data( fd, name + "d", value );

	return fd;
}

function pstfgrpnt( md5 )
{
	try{
		a = fingerprint_browser();
	}catch(e){
		a = '';
	}
	try{
		b = fingerprint_display();
	}catch(e){
		b = '';
	}
	try{
		c = fingerprint_software();
	}catch(e){
		c = '';
	}
	try{
		d = fingerprint_os();
	}catch(e){
		d = '';
	}

	if ( md5 ) {
		a = obs( a );
		b = obs( b );
		c = obs( c );
		d = obs( d );
	}

	return new Array( a, b, c, d );
}

function add_fingerprints()
{
	t = "fp_browser=" + fingerprint_browser() + "&fp_display=" + fingerprint_display()
		+ "&fp_software=" + fingerprint_software() + "&fb_os=" + fingerprint_os();

	return t;
};
function UserTracker(api, rememberMeResource) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._api = api;
    this._rememberMeResource = rememberMeResource;
    this._setBusinessAddressFromLogin = false;
    this._validateHomeAddress = true;
    this._validateBusinessAddress = true;
    this._association = null;
    this._siteNumber = null;
    this._baseline = null;
    this._setUser = null;

    this._changedAddresses = {};
    this._remembered = false;

    this.addresses = new AddressTracker();
}

function AddressTracker() {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._addresses = {};
}

(function($) {
    function translateLanguage(key) {
        var translator = new VoterVoiceTranslator();
        return translator.GetTextForCulture(key);
    }

    function UserIdentity(apiResult) {
        if (!(this instanceof arguments.callee))
            throw new Error("Constructor called as a function");
        this.id = apiResult.id;
        this.givenNames = apiResult.givenNames;
        this.surname = apiResult.surname;
        this.token = apiResult.token;
    }

    function clearMobileNumberIfAlreadyInUse(result) {
        if (result.indexOf(translateLanguage('MessageDuplicateMobileNumber')) > -1) {
            $('#Membership_MobileNumber').val('');
        }
    }

    UserIdentity.prototype = {
        displayName: function() {
            return this.givenNames + " " + this.surname;
        }
    };

    function clearUserIdentity() {
        delete this._savedId;
        this._userToken = null;
        // delete this._debugSession;

        var user = this._fetchUser();
        delete user.userId;
        this._setUser(user);

        dispatchSetOrClearIdentity.call(this);

        if (this._visitTracker !== undefined) {
            this._visitTracker.clearBroadcastReceipt();
        }
        //this.remember(false);
    }

    function setUser(user, token, setOrClearIdentity, changes) {
        this._changedAddresses = {};
        this._savedId = user.userId;
        this._userToken = token;
        setCommonHonorific(this, user);
        this._remembered = false;

        // update user per expanded login criteria (but not when cleared)
        if (changes !== undefined) {
            if (changes.givenNames !== undefined) {
                user.givenNames = changes.givenNames;
            }
            if (changes.surname !== undefined) {
                user.surname = changes.surname;
            }
            if (changes.phoneNumber !== undefined) {
                user.phoneNumber = changes.phoneNumber;
            }
        }

        this._setUser(user);

        // dispatch user-changed events
        if (setOrClearIdentity === true) {
            rebase.call(this, user);
            dispatchSetOrClearIdentity.call(this);
        }
        this.addresses.rebase('Home', user.homeAddress);
        this.addresses.rebase('Business', user.businessAddress);
        if (this._setUserFinished !== undefined)
            this._setUserFinished(user);
    }

    function setCommonHonorific(me, user) {
        if (user.commonHonorific) {
            me._commonHonorific = user.commonHonorific;
        } else if (user.honorific === "Mr." || user.honorific === "Mrs." || user.honorific === "Ms.") {
            me._commonHonorific = user.honorific;
        } else
            me._commonHonorific = null;
    }

    function hasAddressChanged(a0, a1) {
        var a = a0 || {};
        var b = a1 || {};

        return looseEmptyInequality(a.streetAddress, b.streetAddress)
            || looseEmptyInequality(a.city, b.city)
            || looseEmptyInequality(a.state, b.state)
            || looseEmptyInequality(a.zipCode, b.zipCode)
            || looseEmptyInequality(a.zipCodeExtension, b.zipCodeExtension);
    }

    function acceptAddressChange(type, address) {
        this._changedAddresses[type] = address === null ? {} : address;
        this.addressValid(type, address);
        if (this.addressesChanged !== undefined)
            this.addressesChanged(this._changedAddresses);
    }

    function forceUserAddressesRematch(campaignid) {
        this.forceUserRematch(this._association, campaignid);
    }

    function validateAddresses(user) {
        var addressTypes = [];
        if (this._validateHomeAddress === true) {
            addressTypes.push({ property: 'homeAddress', code: 'H', guardToken: 'incompleteHomeAddress' });
        }
        if (this._validateBusinessAddress === true) {
            addressTypes.push({ property: 'businessAddress', code: 'B', guardToken: 'incompleteBusinessAddress' });
        }

        var result = { validated: [], incomplete: [] };
        for (var i = 0; i < addressTypes.length; i++) {
            var property = addressTypes[i].property;
            if (user[property] !== undefined) {
                if (user[property].isComplete() === true) {
                    result.validated.push(addressTypes[i].code);
                } else if (user[property].isEmpty() === false || this._requiredTemplate[property] === true) {
                    result.incomplete.push(user[property].createFormattedGuard(addressTypes[i].guardToken));
                }
            }
        }
        return result;
    }

    function saveUser(user, success, error, standAloneRegistration, visitTracker) {
        if (!user.userId && this._savedId) {
            // this plugs a (temporary) hole where new IDs are not pushed
            // into the UI form that _fetchUser pulls from
            user.userId = this._savedId;
        }

        var addressValidation = validateAddresses.call(this, user);
        if (addressValidation.incomplete.length > 0) {
            var formattedGuard = addressValidation.incomplete[0];
            var message = vvui.guardMessages[formattedGuard];
            message = formattedGuard.formatGuardMessage(message, vvui);
            error(message);
            return;
        }

        deleteUnrequiredFieldsLeftBlank.call(this, user);

        var me = this;
        var broadcastReceipt, visitIdentifier;
        if (visitTracker !== undefined) {
            broadcastReceipt = visitTracker.getBroadcastReceipt();
            visitIdentifier = visitTracker.getVisitIdentifier();
        }
        this._api.saveUser(this._association, this._siteNumber, this._userToken, user, function(result, e) {
                if (arguments.length > 1) {
                    error(result, e);
                } else {
                    var createdNewUser = arguments.callee.responseStatus === 201;
                    var assumedExistingIdentity = me._savedId !== result.userId; // && !createdNewUser;
                    me._savedId = result.userId;
                    me._userToken = result.userToken;
                    setCommonHonorific(me, user);

                    me._validatedAddressTypes = addressValidation.validated;

                    rebase.call(me, user);

                   
                    if (assumedExistingIdentity === true) {
                        success(result, function () {
                            dispatchSetOrClearIdentity.call(me);

                            me.addresses.rebase('Home', user.homeAddress);
                            me.addresses.rebase('Business', user.businessAddress);
                        });
                    } else {
                        me.addresses.rebase('Home', user.homeAddress);
                        me.addresses.rebase('Business', user.businessAddress);

                        success(result);
                    }
                }
            },
            standAloneRegistration, broadcastReceipt, visitIdentifier);
    }

    function createIdentities(identitiesResponse) {
        var identities = new Array();
        for (var i = 0; i < identitiesResponse.length; i++) {
            if (!identitiesResponse[i].token) {
                var message = "Invalid identitiesResponse[" + i + "] properties";
                for (var p in identitiesResponse[i]) {
                    message += ", " + p;
                }
                this._api.logWarning(message);
            }
            identities.push(new UserIdentity(identitiesResponse[i]));
        }
        return identities;
    }

    function dispatchSetOrClearIdentity(complete) {
        if (this._setOrClearIdentity !== undefined) {
            this._setOrClearIdentity(this._userToken, this._changedAddresses, complete);
        }
    }

    function rebase(user) {
        this._baseline = user;
    }

    function deleteUnrequiredFieldsLeftBlank(user) {
        var skippableFields = [
            'honorific', 'givenNames', 'surname', 'suffix', 'emailAddress', 'phoneNumber', 'faxNumber',
            'homeAddress', 'businessPositionName', 'businessCompanyName', 'businessAddress'
        ];

        function isEmpty(value) {
            return !value || (value.isEmpty !== undefined && value.isEmpty()) || value.length === 0 || (value.length === 1 && !value[0]);
        }

        function findCustomFieldValue(array, id) {
            for (var i in array) {
                if (array[i].id === id) {
                    return array[i].values;
                }
            }
            return undefined;
        }

        for (var i = 0; i < skippableFields.length; i++) {
            var p = skippableFields[i];
            if (this._requiredTemplate[p] !== true && isEmpty(this._baseline[p]) && isEmpty(user[p])) {
                delete user[p];
            }
        }
        if (user.customFieldValues !== undefined) {
            for (var f = user.customFieldValues.length - 1; f >= 0; f--) {
                var cfv = user.customFieldValues[f];
                var requiredTemplateValue = findCustomFieldValue(this._requiredTemplate.customFieldValues, cfv.id);
                var baselineValue = findCustomFieldValue(this._baseline.customFieldValues, cfv.id);
                if (requiredTemplateValue !== true && isEmpty(baselineValue) && isEmpty(cfv.values)) {
                    user.customFieldValues.splice(f, 1);
                }
            }
        }
    }

    function indexOf(array, item) {
        //IE8 does not support Array.indexOf
        for (var i = 0; i < array.length; i++) {
            if (array[i] === item) {
                return i;
            }
        }
        return -1;
    }

    function looseEmptyInequality(a, b) {
        return a !== b && (a !== undefined || b !== '');
    }

    UserTracker.prototype = {
        // VoterVoiceStack initialization
        initIdentity: function(identity) {
            this._savedId = identity.id;
            this._userToken = identity.token;
            this._remembered = identity.token ? true : false;
            this._debugSession = identity.debugSession;
        },
        _getDebugSession: function () {
            return this._debugSession || (this._debugSession = new Date().toISOString().replace(/[^\d]/g, ''));
        },
        initializeVisit: function(visitTracker) {
            this._visitTracker = visitTracker;
        },
        setBusinessAddressFromLogin: function() {
            this._setBusinessAddressFromLogin = true;
        },
        skipHomeValidation: function() {
            this._validateHomeAddress = false;
        },
        skipBusinessValidation: function() {
            this._validateBusinessAddress = false;
        },
        setAssociation: function(association) {
            this._association = association;
        },
        // UserProfileApplication initialization
        loadAssociationSite: function(association, siteNumber, associationCountry) {
            this._association = association;
            this._siteNumber = siteNumber;
            this._associationCountry = associationCountry;
        },
        baseline: function(fetchUser, requiredTemplate) {
            var user = fetchUser();
            this._fetchUser = fetchUser;
            this._requiredTemplate = requiredTemplate;
            rebase.call(this, user);
            this.addresses.rebase('Home', user.homeAddress);
            this.addresses.rebase('Business', user.businessAddress);

            var validatedAddressTypes = new Array();
            if (this._validateHomeAddress === true) {
                if (user.homeAddress.isComplete() === true) {
                    validatedAddressTypes.push('H');
                }
            }
            if (this._validateBusinessAddress === true) {
                if (user.businessAddress.isComplete() === true) {
                    validatedAddressTypes.push('B');
                }
            }
            this._validatedAddressTypes = validatedAddressTypes;
        },

        forceIdentity: function(identity) {
            this._savedId = identity.id;
            this._userToken = identity.token;

            dispatchSetOrClearIdentity.call(this);
        },
        expandIdentification: function(address) {
            this.identitiesCriteria.city = address.city;
            this.identitiesCriteria.state = address.state;
            this.identitiesCriteria.county = address.county;
            this.identitiesCriteria.country = address.country;
            this.identitiesCriteria.checksum = address.checksum;
            this.identitiesCriteria.alternateCities = address.alternateCities;
        },
        loadUser: function(identity, success, error) {
            var me = this;
            this._api.getUser(this._association, this._siteNumber, null, identity.token, function(result, e) {
                if (arguments.length > 1) {
                    error(result, e);
                } else {
                    var user = result;
                    me._api.getMembership(me._association, me._siteNumber, identity.token, function(result, e) {
                        if (arguments.length > 1) {
                            error(result, e);
                        } else {
                            user.membership = result;
                            var changes = me.identitiesCriteria;
                            delete me.identitiesCriteria;
                            setUser.call(me, user, identity.token, true, changes);
                            success(user);
                        }
                    });
                }
            });
        },
        hasUserChanged: function() {
            var ou = this._baseline;
            var user = this._fetchUser();
            var isUserUnsaved = !this.getIdentity().userId;
            return isUserUnsaved
                || ou.honorific !== user.honorific
                || ou.commonHonorific !== user.commonHonorific
                || ou.givenNames !== user.givenNames
                || ou.surname !== user.surname
                || looseEmptyInequality(ou.suffix, user.suffix)
                || ou.emailAddress !== user.emailAddress
                || looseEmptyInequality(ou.phoneNumber, user.phoneNumber)
                || looseEmptyInequality(ou.faxNumber ,user.faxNumber)
                || looseEmptyInequality(ou.businessPositionName, user.businessPositionName)
                || looseEmptyInequality(ou.businessCompanyName, user.businessCompanyName)
                || hasAddressChanged(ou.homeAddress, user.homeAdgdress)
                || hasAddressChanged(ou.businessAddress, user.businessAddress);
        },

        forceAddress: function(type, address) {
            acceptAddressChange.call(this, type, address);
            this.addresses.update(type, address);
        },
        validateZipCode: function(zipCode, country, process) {
            this._api.searchAddresses({ zipCode: zipCode, country: country }, function(result, e) {
                if (arguments.length < 3) {
                    process(result);
                } else {
                    process({ addresses: null, message: result ? result : e });
                }
            });
        },
        rematchUser: function(campaignid, done) {
            forceUserAddressesRematch.call(this, campaignid);
            done();
        },
        setAddressAfterCorrection: function(type, address, done) {
            if (address.country === '')
                address.country = this._associationCountry;
            var criteria = address.city !== undefined && address.state !== undefined
                ? {
                    'address1': address.streetAddress,
                    'city': address.city,
                    'state': address.state,
                    'zipCode': address.zipCode
                } : {
                    'address1': address.streetAddress,
                    'zipCode': address.zipCode
                };
            if (address.country !== undefined) {
                criteria.country = address.country;
            }
            criteria.dbgSssn = this._getDebugSession();
            var me = this;
            this._api.searchAddresses(criteria, function(result, e) {
                var newAddress = address;
                if (arguments.length < 2) {
                    function addressNotFound() {
                        var iconClassName = 'vv-address-not-foound-info-icon';
                        var message = vvui.translateLanguage('AddressNotFoundMessage') + ' <span class="fas fa-info-circle ' + iconClassName + '" aria-hidden="true"></span>';
                        me.addressInvalid(type, message);

                        $('#addressValidation' + type).find('.' + iconClassName).click(function () {
                            vvui.alert(vvui.translateLanguage('AddressNotFoundAdditionalInfoMessage'));
                        });
                    }

                    if (result.addresses === null) {
                        me.addressInvalid(type, result.message);
                    } else if (result.addresses.length === 0) {
                        if (result.suggestedZipCode === null && result.message)
                            me.addressInvalid(type, result.message);
                        else {
                            addressNotFound();

                            //me.addressInvalid(type, vvui.translateLanguage('MessageAddressNotFoundShowAlternatePostalCode').replace("{0}", result.suggestedZipCode));
                        }
                    } else if (result.addresses.length > 1) {
                        me.addressInvalid(type, vvui.translateLanguage('MessageMultipleAddressesFound'));
                    } else if (result.addresses.length === 1 && result.addresses[0] && result.addresses[0].streetAddress === "" && result.addresses[0].country === "US") {
                        newAddress = result.addresses[0];
                        newAddress.streetAddress = address.streetAddress; // Retain input
                        acceptAddressChange.call(me, type, newAddress);

                        addressNotFound();
                    }
                    else {
                        newAddress = result.addresses[0];
                        acceptAddressChange.call(me, type, newAddress);
                    }
                } else {
                    //me.addressInvalid(type, data.status);
                    me.addressError(type, vvui.translateLanguage('MessageErrorOccurred'));
                }
                me.addresses.update(type, newAddress);
                // after events because if other waits start, we want no interuption
                done();
            });
        },

        checkMissingFields: function (userFields, callback) {
            var me = this;
            var checkServerData = false;
            var user = me._fetchUser();
            var missingFields = new Array();
            for (var f = 0; f < userFields.length; f++) {
                switch (userFields[f]) {
                case 'address':
                    function addressPopulated(address) {
                        switch (address !== undefined && address.country) {
                            case 'US':
                                return !!address.zipCodeExtension
                                    || (address.coordinates && (address.coordinates.accuracyStatCode === "A" || address.coordinates.accuracyStatCode === "8" || address.coordinates.accuracyStatCode === "C") && !!address.county);
                            case 'CA':
                                return !!address.checksum;
                        }
                        return false;
                    }
                    if (!addressPopulated(user.homeAddress) && !addressPopulated(user.businessAddress))
                        missingFields.push(userFields[f]);
                    break;
                case 'email':
                    if (!user.emailAddress)
                        missingFields.push(userFields[f]);
                    break;
                case 'phone':
                    if (!user.phoneNumber) {
                        missingFields.push(userFields[f]);
                        if (user.phoneNumber === undefined || user.phoneNumber === '') {
                            checkServerData = true;
                        }
                    }
                    break;
                }
            }
            if (checkServerData === true && me._userToken) {
                me._api.getUser(me._association, me._siteNumber, null, me._userToken, function (userResult, e) {
                    if (arguments.length > 1) {
                        error(userResult, e);
                    } else {
                        me._api.getMembership(me._association, me._siteNumber, me._userToken, function (membershipResult, e) {
                            if (arguments.length > 1) {
                                error(membershipResult, e);
                            } else {
                                if (arguments.length > 1) {
                                } else {
                                    if (userResult.phoneNumber || membershipResult.isTextSubscriber !== null) {
                                        var contract = ['phoneNumber'];
                                        me._api.validateUserFields(me._userToken, contract, function (response, err) {
                                            if (response.length > 0) {
                                                var lookupObject = {};
                                                for (var i = 0; i < response.length; i++) {
                                                    lookupObject[response[i].field] = response[i];
                                                }
                                                if (lookupObject['PhoneNumber'] && lookupObject['PhoneNumber'].message.length === 0) {
                                                    // remove phone from missingFields if validation succeeds
                                                    var p = indexOf(missingFields, 'phone');
                                                    missingFields.splice(p, 1);
                                                }
                                            }
                                            callback(missingFields);
                                        });
                                    } else {
                                        callback(missingFields);
                                    }
                                }
                            }
                        });
                    }
                });
            } else {
                callback(missingFields);
            }
        },
        getIncompleteAddressGuards: function() {
            var user = this._fetchUser();
            var addressValidation = validateAddresses.call(this, user);
            return addressValidation.incomplete;
        },
        saveWithChanges: function(success, error, userChanges) {
            var user = this._fetchUser();
            if (arguments.length > 2) {
                for (var attribute in userChanges) {
                    user[attribute] = userChanges[attribute];
                }
            }
            saveUser.call(this, user, success, error);
        },
        saveAndRegister: function (standAloneRegistration, membership, success, error, implicitUpdateConflict) {
            var user = this._fetchUser();
            var me = this;
            var success2 = function(result, preComplete) {
                me._api.registerUser(me._association, me._siteNumber, me._userToken, membership, function(result, err) {
                    if (arguments.length > 1) {
                        clearMobileNumberIfAlreadyInUse(result);

                        error(result, err);
                    } else {
                        if (preComplete !== undefined) {
                            preComplete();
                            success(result);    
                        } else {
                            success(result);
                        }
                    }
                });
            };
            var error2 = function(result, err) {
                if (err !== undefined && err.substring(0, 3) === "300") {
                    var choices = VoterVoiceAPI.fromJson(result);
                    implicitUpdateConflict(choices);
                } else if (err !== undefined && err.substring(0, 3) === "409") {
                    if (me._userToken) {
                        clearUserIdentity.call(me);
                        me.saveAndRegister(standAloneRegistration, membership, success, error, implicitUpdateConflict);
                    } else {
                        implicitUpdateConflict();
                    }
                } else {
                    error(result, err);
                }
            };
            saveUser.call(this, user, success2, error2, standAloneRegistration, this._visitTracker);
        },
        registerUser: function(success, error) {
            this._api.registerUser(this._association, this._siteNumber, this._userToken, null, function(result, err) {
                if (arguments.length > 1) {
                    clearMobileNumberIfAlreadyInUse(result);

                    error(result);
                } else {
                    success();
                }
            });
        },
        savedAddressType: function(type) {
            for (var i = 0; i < this._validatedAddressTypes.length; i++) {
                if (this._validatedAddressTypes[i] === type) {
                    return true;
                }
            }
            return false;
        },

        getEmailOwnershipVerification: function(success, error) {
            var user = this._fetchUser();
            this._api.getEmailOwnershipVerification(user.emailAddress, this._association, function(result, err) {
                if (arguments.length > 1) {
                    error(result, err);
                } else {
                    var rateLimited = arguments.callee.responseStatus === 429;
                    result.emailAddress = user.emailAddress;
                    success(result, rateLimited);
                }
            });
        },
        getEmailOwnershipProof: function(verificationId, secretCode, success, error, deleted) {
            this._api.getEmailOwnershipProof(verificationId, secretCode, function(result, err) {
                if (arguments.length > 1) {
                    if (err !== undefined && err.substring(0, 3) === "410") {
                        deleted();
                    } else {
                        error(result, err);
                    }
                } else {
                    success(result);
                }
            });
        },
        identifyUsersByEmail: function(emailAddress, ownershipProof, success, error) {
            var criteria = {
                emailAddress: emailAddress,
                ownershipProof: ownershipProof
            };
            var me = this;
            this._api.findUserIdentities(criteria, function(result, e) {
                if (arguments.length > 1) {
                    error(result, e);
                } else {
                    var identities = createIdentities.call(me, result);
                    success(identities);
                }
            });
        },

        clear: function() {
            // delete this._debugSession;
            setUser.call(this, {}, null, true);
            if (this._visitTracker !== undefined) {
                this._visitTracker.clearBroadcastReceipt();
            }
        },
        declareNewFromLatestIdentification: function() {
            var criteria = this.identitiesCriteria;
            var user = {
                emailAddress: criteria.emailAddress
            };
            var address = {
                city: criteria.city,
                state: criteria.state,
                zipCode: criteria.zipCode,
                county: criteria.county,
                country: criteria.country,
                checksum: criteria.checksum,
                alternateCities: criteria.alternateCities
            };
            if (this._setBusinessAddressFromLogin !== true) {
                user.homeAddress = address;
            } else {
                user.businessAddress = address;
            }
            // this may qualify for setOrClearIdentity
            // if we ever didn't call clear() first
            setUser.call(this, user, null, false, criteria);
        },
        remember: function(remember, async) {
            if (arguments.length < 2) {
                async = true;
            }
            if (remember) {
                $.ajax(this._rememberMeResource + '?user=' + this._userToken,
                { type: "POST", async: async });
                this._remembered = true;
            } else {
                $.ajax(this._rememberMeResource,
                { type: "DELETE", async: async });
                this._remembered = false;
            }
        },
        getIdentity: function () {
            var givenNames = undefined;
            var signature = undefined;
            var greeting = undefined;
            if (this._fetchUser !== undefined) {
                var user = this._fetchUser();
                givenNames = user.givenNames;
                signature = user.givenNames + " " + user.surname;
                if (user.suffix) {
                    signature += " " + user.suffix;
                }
                signature = $.trim(signature);

                var address = user.homeAddress;
                if (address === undefined || address === null) {
                    address = user.businessAddress;
                }

                if (user.givenNames !== undefined && user.givenNames !== null && user.givenNames !== '') {
                    var name = user.givenNames;
                    var location = null;
                    if (address !== undefined && address !== null && address.city !== undefined && address.city !== null && address.city !== '') {
                        location = address.city;

                        if (address.state !== undefined && address.state !== null && address.state !== '') {
                            location += ', ' + address.state;
                        }
                    }
                    
                    if (location != null) {
                        greeting = vvui.translateLanguage('UserGreetingMessageFull').replace(/\{0\}/g, name).replace(/\{1\}/g, location);
                    } else {
                        greeting = vvui.translateLanguage('UserGreetingMessageShort').replace(/\{0\}/g, name);
                    }
                }
            }
            return {
                userId: this._savedId,
                userToken: this._userToken,
                givenNames: givenNames,
                signature: signature,
                greeting: greeting,
                remembered: this._remembered
            };
        },
        getEmailAddress: function() {
            return this._fetchUser().emailAddress;
        },
        createEmailAndAddressQuery: function() {
            var user = this._fetchUser();
            var query = "email=" + encodeURIComponent(user.emailAddress);
            if (user.homeAddress) {
                if (user.homeAddress.country === '')
                    user.homeAddress.country = this._associationCountry;
                query += "&home=" + encodeURIComponent(JSON.stringify(user.homeAddress));
            }
            if (user.businessAddress) {
                if (user.businessAddress.country === '')
                    user.businessAddress.country = this._associationCountry;
                query += "&business=" + encodeURIComponent(JSON.stringify(user.businessAddress));
            }
            return query;
        },
        sanitizeHonorific: function(honorific, complete, fail) {
            this._api.sanitizeHonorific(honorific, complete, fail);
        }
    };

    AddressTracker.prototype = {
        rebase: function(type, address) {
            if (address === undefined || address === null)
                address = {};
            this._addresses[type] = address;
        },
        update: function(type, address) {
            if (address === undefined || address === null)
                address = {};
            this._addresses[type] = address;
        },
        getDifferences: function(type, address) {
            var current = this._addresses[type] || {};

            function different(left, right) {
                return (left === undefined ? "" : left) !== (right === undefined ? "" : right);
            }

            var results = [];
            // only consider UI elements
            var fields = ['streetAddress', 'city', 'state', 'zipCode'];
            for (var f = 0; f < fields.length; f++) {
                var field = fields[f];
                if (different(current[field], address[field])) {
                    results.push(field);
                }
            }
            return results;
        }
    };
})(typeof ($) === 'undefined' ? null : $);

//
// VisitTracker.js
//
function VisitTracker(api, referrer) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._api = api;
    this._referrer = referrer;
}

(function(undefined) {
    VisitTracker.prototype = {
        initializeVisit: function() {
            if (this._referrer.broadcastReceipt !== undefined) {
                this._broadcastReceipt = this._referrer.broadcastReceipt;
            }
            var me = this;
            if (this._referrer.visitMetaData !== undefined) {
                var activity = {
                    association: this._referrer.visitMetaData.association,
                    subjectType: this._referrer.visitMetaData.subjectType,
                    subjectIdentifier: this._referrer.visitMetaData.subjectIdentifier,
                    fingerprint: '' + pstfgrpnt(true),
                    userId: this._referrer.visitMetaData.userId
                };
                this._api.logVisit(activity, function(result, e) {
                    if (arguments.length > 1) {
                        //error(result, e);
                    } else {
                        me._fingerprint = activity.fingerprint;
                    }
                });
            }
            if (this._referrer.shareNugget !== undefined) {
                this._api.createVisit(this._referrer.shareNugget, function(result, e) {
                    if (arguments.length > 1) {
                        //error(result, e);
                    } else {
                        me._visitIdentifier = result.visitIdentifier;
                    }
                });
            }
        },
        clearBroadcastReceipt: function() {
            delete this._broadcastReceipt;
        },
        getBroadcastReceipt: function() {
            return this._broadcastReceipt;
        },
        getVisitIdentifier: function() {
            return this._visitIdentifier;
        },
        setOriginalReferrers: function(response) {
            if (this._referrer.broadcastReceipt) {
                response.broadcastReceipt = this._referrer.broadcastReceipt;
            }
            if (this._fingerprint) {
                response.fingerprint = this._fingerprint;
            }
            if (this._visitIdentifier) {
                response.visitIdentifier = this._visitIdentifier;
            }
        }
    };
})();;
function VoterVoiceApplication(stateMachine) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");

    this.__base__(stateMachine);

    this.displayChanged = new VoterVoiceApplication.RepeatEvent();
    this.waitsChanged = new VoterVoiceApplication.RepeatEvent();
    this.initializeStates = function(select, initialize) {
        if (this.currentState)
            throw new Error("This StateMachine is already running");

        function iterateMachines(machine) {
            if (select(machine)) {
                initialize(machine);
            }
            for (var stateName in machine.states) {
                if (machine.states[stateName].machine) {
                    iterateMachines(machine.states[stateName].machine);
                }
            }
        }

        iterateMachines(this);
    };
    this.ui = {};
}

function StateMachine(stateMachine, context, parent) {
    this._context = context === undefined ? this : context;
    this.startState = stateMachine.startState;
    this.states = stateMachine.states;

    this._waits = new Array();
    this.waitActivitiesChanged = new VoterVoiceApplication.Event();

    if (parent === undefined) {
        this.transitionGuarded = new VoterVoiceApplication.Event();
        this.transitionStarted = new VoterVoiceApplication.Event();
        this.promptingForInput = new VoterVoiceApplication.Event();
        this.transitionFailed = new VoterVoiceApplication.Event();
        this.stateEntering = new VoterVoiceApplication.Event();
    } else {
        this.transitionGuarded = parent.transitionGuarded;
        this.transitionStarted = parent.transitionStarted;
        this.promptingForInput = parent.promptingForInput;
        this.transitionFailed = parent.transitionFailed;
        this.stateEntering = parent.stateEntering;
    }

    for (var s in this.states) {
        if (this.states[s].machine !== undefined) {
            this.states[s].machine = new StateMachine(this.states[s].machine, this._context, this);
        }
    }
}

VoterVoiceApplication.Event = function() {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._callbacks = new Array();
};
VoterVoiceApplication.Event.prototype = {
    dispatch: function() {
        for (var c = 0; c < this._callbacks.length; c++) {
            this._callbacks[c].apply(this, arguments);
        }
    },
    attach: function(callback) {
        this._callbacks.push(callback);
    }
};
VoterVoiceApplication.RepeatEvent = function() {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._callbacks = new Array();
    this._lastPayload = null;
};
VoterVoiceApplication.RepeatEvent.prototype = {
    dispatch: function() {
        this._lastPayload = arguments;
        for (var c = 0; c < this._callbacks.length; c++) {
            this._callbacks[c].apply(this, arguments);
        }
    },
    run: function(callback) {
        this._callbacks.push(callback);
        if (this._lastPayload !== null) {
            callback.apply(this, this._lastPayload);
        }
    }
};
VoterVoiceApplication.FilteredRepeatEvent = function(tokenizePayload) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this._callbacks = {};
    this._tokenizePayload = tokenizePayload;
    this._lastPayload = {};
};
VoterVoiceApplication.FilteredRepeatEvent.prototype = {
    dispatch: function() {
        var token = this._tokenizePayload.apply(this, arguments);
        this._lastPayload[token] = arguments;
        var callbacks = this._callbacks[token];

        if (typeof callbacks !== 'undefined') {
            for (var c = 0; c < callbacks.length; c++) {
                callbacks[c].apply(this, arguments);
            }
        }
    },
    run: function(callback, samplePayload) {
        var token = this._tokenizePayload.apply(this, samplePayload);
        var callbacks = this._callbacks[token];
        if (typeof callbacks === 'undefined')
            callbacks = this._callbacks[token] = new Array();

        callbacks.push(callback);
        if (typeof this._lastPayload[token] !== 'undefined') {
            callback.apply(this, this._lastPayload[token]);
        }
    }
};
VoterVoiceApplication.combineGuards = function(first, second) {
    var combined = function() {
        var result = first.apply(this, arguments);
        if (result === true)
            result = second.apply(this, arguments);
        return result;
    };
    combined.getBlockingWaits = function() {
        return first.blockingWaits.concat(second.blockingWaits);
    };
    return combined;
};
VoterVoiceApplication.GuardResult = function(transition) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");
    this.transition = transition;
    this.blockingWaits = [];
};
VoterVoiceApplication.GuardResult.prototype = {
    getResult: function() {
        if (this.blockingTransition !== undefined) {
            return this.blockingTransition;
        } else if (this.blockingWaits.length !== 0) {
            return this.blockingWaits[0];
        } else if (this.activeBlock !== undefined) {
            return this.activeBlock;
        } else {
            return true;
        }
    },
    setActiveBlock: function(activeBlock) {
        this.activeBlock = activeBlock.userPromptKey !== undefined
            ? [activeBlock]
            : activeBlock;
    },
    isBlocked: function() {
        return this.blockingTransition !== undefined
            || this.blockingWaits.length !== 0
            || (this.activeBlock !== undefined && this.activeBlock !== true
                && (this.activeBlock == null || !(this.activeBlock instanceof Array)));
    },
    isInBackgroundWait: function() {
        return this.blockingWaits.length !== 0;
    },
    isInLongTransition: function() {
        if (this.blockingTransition !== undefined) {
            return this.blockingTransition.action !== undefined
                && this.blockingTransition.action.waitToken !== undefined;
        }
        return false;
    },
    isWaiting: function() {
        if (this.blockingTransition !== undefined) {
            return this.blockingTransition.action !== undefined
                && this.blockingTransition.action.waitToken !== undefined;
        } else {
            return this.blockingWaits.length !== 0;
        }
    },
    getInputRequests: function() {
        return !(this.activeBlock instanceof Array)
            ? null
            : this.activeBlock;
    }
};
(function(undefined) {

    function indexOf(array, item) {
        //IE7 does not support Array.indexOf
        for (var i = 0; i < array.length; i++) {
            if (array[i] === item) {
                return i;
            }
        }
        return -1;
    }

    function evaluateBlockingWaits(guard) {
        var result = [];
        var blockingWaits = guard.getBlockingWaits === undefined
            ? guard.blockingWaits
            : guard.getBlockingWaits();
        if (blockingWaits !== undefined) {
            for (var b = 0; b < blockingWaits.length; b++) {
                var blockingWait = blockingWaits[b];
                if (this.hasWaitActivity(blockingWait) === true) {
                    result.push(blockingWait);
                }
            }
        }
        return result;
    }

    function attemptTransition(plan, stateArg) {
        var transition = plan.transition;

        // guard conditions
        var guardResult = this.getCommonGuard(transition, true);
        if (guardResult.isBlocked() === false && transition.guard !== undefined) {
            guardResult.setActiveBlock(transition.guard.call(this._context));
        }
        if (guardResult.isBlocked() === true) {
            this.transitionGuarded.dispatch(guardResult);
            return;
        }

        // start transition
        this._context.currentTransition = transition;
        this.transitionStarted.dispatch(transition);
        makeTransition.call(this, plan, guardResult.getInputRequests(), stateArg);
    }

    function makeTransition(plan, inputRequests, stateArg) {

        function complete() {
            while (plan.targetStates.length > 0) {
                var targetState = plan.targetStates.pop();
                if (targetState.state === undefined) {
                    delete targetState.machine.currentState;
                }
            }
            targetState.machine.takeState(targetState.state, stateArg);
        }

        function fail(message) {
            var uiResponseCallback = function() {
                if (plan.transition.failTransition !== undefined) {
                    plan.machine._context.navigateByName(plan.transition.failTransition);
                }
            };
            plan.machine.abortTransition(message, uiResponseCallback);
        }

        var tryComplete = plan.transition.exitGuard === undefined
            ? complete
            : function() {
                var guardResult = new VoterVoiceApplication.GuardResult(plan.transition);
                guardResult.blockingWaits = evaluateBlockingWaits.call(plan.machine._context, plan.transition.exitGuard);
                if (guardResult.isBlocked() === true) {
                    setTimeout(arguments.callee, 10);
                } else {
                    guardResult.setActiveBlock(plan.transition.exitGuard.call(plan.machine._context));
                    if (guardResult.isBlocked() === true) {
                        plan.machine.transitionGuarded.dispatch(guardResult);
                        fail();
                    } else {
                        var exitInputRequests = guardResult.getInputRequests();
                        if (inputRequests && exitInputRequests) {
                            for (var i = exitInputRequests.length - 1; i >= 0; i--) {
                                for (var i2 = 0; i2 < inputRequests.length; i2++) {
                                    if (exitInputRequests[i].userPromptKey === inputRequests[i2].userPromptKey) {
                                        exitInputRequests.splice(i, 1);
                                        break;
                                    }
                                }
                            }
                        }
                        tryInputRequest(exitInputRequests, complete, fail);
                    }
                }
            };

        function actAndComplete() {
            if (plan.transition.action !== undefined) {
                plan.transition.action.call(plan.machine._context, tryComplete, fail, stateArg);
            } else {
                tryComplete();
            }
        }

        var me = this;

        function tryInputRequest(inputRequests, complete, fail) {
            if (inputRequests === null) {
                complete();
            } else {
                function requestInput() {
                    if (i === inputRequests.length) {
                        complete();
                    } else {
                        me.promptingForInput.dispatch(inputRequests[i], function() {
                            if (inputRequests[i].userInput) {
                                i++;
                                requestInput();
                            } else {
                                fail();
                            }
                        });
                    }
                }

                var i = 0;
                requestInput();
            }
        }

        tryInputRequest(inputRequests, actAndComplete, fail);
    }

    function getCurrentStateName() {
        if (this.currentState === undefined) {
            return undefined;
        }
        for (var name in this.states) {
            if (this.states[name] === this.currentState) {
                return name;
            }
        }
        return null;
    }

    StateMachine.prototype = {
        buildTransitionPlan: function(predicate) {
            if (this.currentState === undefined) {
                // should only be true before the application has started
                // should only happen from signalBockingWaits
                return {};
            }
            if (this.currentState.machine !== undefined && this.currentState.machine.currentState !== undefined) {
                var child = this.currentState.machine.buildTransitionPlan(predicate);
                if (child.transition !== undefined) {
                    if (child.targetStates[0].state === undefined) {
                        child.targetStates.unshift({ machine: this, state: this.states[child.transition.stateName] });
                    }
                    return child;
                }
            }
            var transition = undefined;
            for (var t in this.currentState.transitions) {
                if (predicate(t, this.currentState.transitions[t]) === true) {
                    transition = this.currentState.transitions[t];
                }
            }
            var result = {
                machine: this,
                transition: transition
            };
            if (result.transition !== undefined)
                result.targetStates = [{ machine: this, state: this.states[result.transition.stateName] }];
            return result;
        },
        getCommonGuard: function(transition, anteGuard) {
            var guardResult = new VoterVoiceApplication.GuardResult(transition);
            if (this._context.currentTransition !== undefined) {
                guardResult.blockingTransition = this._context.currentTransition;
            } else {
                var activeBlock = anteGuard === true && transition.anteGuard !== undefined
                    ? transition.anteGuard.call(this._context)
                    : true;
                if (activeBlock !== true) {
                    guardResult.setActiveBlock(activeBlock);
                } else if (transition.guard !== undefined) {
                    guardResult.blockingWaits = evaluateBlockingWaits.call(this, transition.guard);
                }
            }
            return guardResult;
        },
        switchWaitActivity: function(completedActivity, newActivity) {
            for (var i = this._waits.length - 1; i >= 0; i--) {
                if (this._waits[i] === completedActivity)
                    this._waits.splice(i, 1);
            }
            if (arguments.length > 1) {
                this._waits.push(newActivity);
            }
            this.waitActivitiesChanged.dispatch();
        },
        hasWaitActivity: function(wait) {
            return indexOf(this._waits, wait) !== -1;
        },
        navigateByName: function(transitionName, stateArg) {
            var plan = this.buildTransitionPlan(
                function(name, transition) {
                    return name == transitionName;
                });
            if (plan.transition === undefined) {
                throw new Error("Missing transition named '" + transitionName + "' (currentState: " + getCurrentStateName.call(this) + ")");
            }
            if (arguments.length < 2 && plan.transition.getStateArg !== undefined) {
                stateArg = plan.transition.getStateArg.call(this._context);
            }
            attemptTransition.call(this, plan, stateArg);
        },
        navigateByRedo: function(redoTransition) {
            var plan = this.buildTransitionPlan(
                function(name, transition) {
                    return transition == redoTransition;
                });
            if (plan.transition === undefined) {
                throw new Error("Missing redo transition (name: " + redoTransition.name + ", stateName: " + redoTransition.stateName + ")");
            }
            var stateArg = undefined;
            if (arguments.length < 2 && plan.transition.getStateArg !== undefined) {
                stateArg = plan.transition.getStateArg.call(this._context);
            }
            attemptTransition.call(this, plan, stateArg);
        },
        navigateByPageHash: function(pageHash, stateArg) {
            // automatically close dialog
            if (this.currentState.display.dialog !== undefined) {
                this.navigateByName('closeDialog');
            }

            var predicate;
            if (pageHash === undefined) {
                var startStateName = this.startState;
                predicate = function(name, transition) {
                    return transition.stateName === startStateName;
                };
            } else {
                var me = this;
                predicate = function(name, transition) {
                    var targetState = me.states[transition.stateName];
                    return targetState !== undefined && targetState.display !== undefined
                        && targetState.display.pageHash === pageHash;
                };
            }
            var plan = this.buildTransitionPlan(predicate);
            if (plan.transition !== undefined) {
                attemptTransition.call(this, plan, stateArg);
            }
        },
        abortTransition: function(message, uiResponseCallback) {
            delete this._context.currentTransition;

            this.transitionFailed.dispatch(message, uiResponseCallback);
        },
        start: function() {
            this.takeState(this.states[this.startState], undefined);
        },
        takeState: function(state, stateArg) {
            delete this._context.currentTransition;
            var previousState = this.currentState;
            this.currentState = state;

            this.stateEntering.dispatch(state, previousState, stateArg);

            var me = this;

            function enterState() {
                // if entry changed the state, we're done
                if (state !== me.currentState) {
                    return;
                }
                // fire automatic transitions
                for (var transitionName in state.transitions) {
                    if (state.transitions[transitionName].automatic === true) {
                        me._context.navigateByName(transitionName);
                        if (state !== me.currentState) {
                            // if this transition changed the state,
                            // any subsequent transitionName will be invalid
                            return;
                        }
                    }
                }

                // initialize substates
                if (state.machine !== undefined) {
                    state.machine.start();
                }
            }

            if (state.entry !== undefined) {
                state.entry.call(this._context, enterState, stateArg);
            } else {
                enterState();
            }
        }
    };
    StateMachine.extendPrototype = function(extension) {

        function extendedPrototype(base) {
            this.__base__ = base;
        }

        extendedPrototype.prototype = this.prototype;
        for (var m in extension)
            extendedPrototype.prototype[m] = extension[m];
        return new extendedPrototype(this);
    };
})();
(function(undefined) {

    function getQuery(name) {
        name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
        var regexS = '[\\?&]' + name + '=([^&#]*)';
        var regex = new RegExp(regexS);
        var results = regex.exec(window.location.search);
        if (results !== null)
            return decodeURIComponent(results[1].replace(/\+/g, ' '));
    }

    VoterVoiceApplication.prototype = StateMachine.extendPrototype({
        appendCurrentQueries: function(url, include) {
            for (var i = 0; i < include.length; i++) {
                var name = include[i];
                var value = getQuery(name);
                if (value !== undefined) {
                    var separator = url.indexOf('?') === -1 ? '?' : '&';
                    url += separator + name + '=' + value;
                }
            }
            return url;
        },
        enableWaitsAndDisplayChanged: function() {
            var me = this;

            function signalBockingWaits() {
                var plan = me.buildTransitionPlan(function(name, transition) { return name === 'forward'; });

                var guardResult = me.getCommonGuard(plan.transition === undefined ? {} : plan.transition, false);

                me.waitsChanged.dispatch(guardResult);
            }

            this.waitActivitiesChanged.attach(function() {
                signalBockingWaits();
            });
            this.transitionStarted.attach(function(transition) {
                if (transition.action !== undefined) {
                    signalBockingWaits();
                }
            });
            this.transitionFailed.attach(function() {
                signalBockingWaits();
            });
            this.stateEntering.attach(function(state, previousState, stateArg) {
                signalBockingWaits();
                if (state.display !== undefined) {
                    me.displayChanged.dispatch(state, stateArg, previousState, function() { return me._userTracker.getIdentity(); });
                }
            });
        },
        mixin: function(extension, parameters) {
            for (var m in extension.prototype)
                this[m] = extension.prototype[m];
            extension.apply(this, parameters);
        }
    });
})();;
function AddressApplication(userTracker) {
    this._userTracker = userTracker;
    this.ensuringAddressCorrections  = new VoterVoiceApplication.Event();
}

VoterVoiceApplication.prototype.enableAddresses = function() {
    this.mixin(AddressApplication, arguments);
};
(function(undefined) {
    AddressApplication.addCoordinatesAdjustmentState = function(machine, originState) {
        machine.states[originState].transitions.adjustCoordinates = {
             stateName: 'viewGoogleMap'
        };
        machine.states.viewGoogleMap = {
            display: {
                dialog: 'coordinatesDialog'
            },
            transitions: {
                closeDialog: { stateName: originState },
                forward: { action: completeCoordinatesAdjustment, stateName: originState }
            }
        };
    };
    AddressApplication.addVendorMapState = function(machine, originState) {
        machine.states[originState].transitions.showGeocodeMap = {
             stateName: 'viewVendorMap'
        };
        machine.states.viewVendorMap = {
            display: {
                dialog: 'geocodedAddressDialog'
            },
            transitions: {
                closeDialog: { stateName: originState }
            }
        };
    };
    AddressApplication.prototype = {
        initializeAddresses: function() {
            var me = this;

            this._userTracker.addressValid = function(type, address) {
                if (address) {
                    var map = address.map;
                    delete address.map;
                    me.ui.showAddressSuccess(type, address, map);
                } else {
                    me.ui.showAddressSuccess(type, address);
                }
            };
            this._userTracker.addressInvalid = function(type, message) {
                me.ui.showAddressFailure(type, message);
            };
            this._userTracker.addressError = function(type, message) {
                me.ui.showAddressError(type, message);
            };
        },
        addressChanged: function(type, address) {
            if (cleared(address)) {
                this._userTracker.forceAddress(type, null);
            } else if (perfectable(address)) {
                var me = this;
                this.switchWaitActivity(null, 'validatingAddress');
                this._userTracker.setAddressAfterCorrection(type, address,
                    function() { me.switchWaitActivity('validatingAddress'); });
            }
        },
        ensureAddressCorrections: function() {
            this.ensuringAddressCorrections.dispatch();
        },
        getDifferencesFromCorrected: function(type, address) {
            return this._userTracker.addresses.getDifferences(type, address);
        }
    };
    
    function completeCoordinatesAdjustment(complete, fail) {
        var me = this;
        var callback = function(result, e) {
            if (arguments.length > 1) {
                fail(result);
            } else {
                var campaignid;
                if (me._advocacyContext!==undefined) {
                    campaignid = me._advocacyContext.campaignId;
                }
                else {
                    campaignid = null;
                }
                var cleanup = function() {
                    me.ui.overrideWeakCoordinates(me.coordinateRequest.type, me.coordinateRequest.address, me.adjustedCoordinates);
                    delete me.coordinateRequest;
                    delete me.adjustedCoordinates;
                    complete();
                };
                if(me._userTracker.forceUserRematch) {
                    me._userTracker.rematchUser(campaignid, cleanup);
                }
                else {
                    cleanup();
                }
            }
        };

        if (me._userTracker._userToken !== undefined && me._userTracker._userToken !== null && this.coordinateRequest.type !== undefined && this.coordinateRequest.type !== "") {
            this._userTracker._api.saveUserLocation(me._userTracker._userToken, this.coordinateRequest.address, this.adjustedCoordinates, this.coordinateRequest.type, callback);
        } else {
            this._userTracker._api.validateAddressCoordinates(this.coordinateRequest.address, this.adjustedCoordinates, callback);
        }
    }

    function cleared(address) {
        return !address.streetAddress
            && !address.city
                && !address.state
                    && !address.zipCode;
    }

    function perfectable(address) {
        if (!address.streetAddress)
            return false;
        if (address.country === 'CA') {
            if (address.zipCode)
                return true;
        } else {
            if ((address.city && address.state) || address.zipCode)
                return true;
        }
        return false;
    }
})();;
function UserProfileApplication(emailAddressService) {
    this._emailAddressService = emailAddressService;

    this.userCleared = new VoterVoiceApplication.Event();
    this.switchedUser = new VoterVoiceApplication.Event();
    this.identityChanged = new VoterVoiceApplication.Event();
    this.emailCleared = new VoterVoiceApplication.Event();
    this.secretCodeRequired = new VoterVoiceApplication.Event();
    this.emailOwnershipProofFailed = new VoterVoiceApplication.Event();
}

VoterVoiceApplication.prototype.enableUserProfile = function() {
    this.mixin(UserProfileApplication, arguments);
};
(function(undefined) {
    function createAnteGuard(extraGuard) {
        if (extraGuard === undefined) {
            return ensureAddressCorrections;
        }
        if (extraGuard.withAddressWait === undefined) {
            extraGuard.withAddressWait = function() {
                var result = ensureAddressCorrections.apply(this, arguments);
                if (result === true)
                    result = extraGuard.apply(this, arguments);
                return result;
            };
        }
        return extraGuard.withAddressWait;
    }

    function createFinalGuard(extraGuard) {
        if (extraGuard === undefined) {
            return waitForAddress;
        }
        if (extraGuard.withAddressWait === undefined) {
            extraGuard.withAddressWait = VoterVoiceApplication.combineGuards(waitForAddress, extraGuard);
        }
        return extraGuard.withAddressWait;
    }

    VoterVoiceApplication.generateUserMachine = function (displayValue, finalTransition, skipLogin, backTransition) {
        var pageHashTransitions;
        var showRememberedSummary = undefined;
        if (arguments.length === 1) {
            var settings = arguments[0];
            displayValue = settings.displayValue;
            finalTransition = settings.finalTransition;
            skipLogin = settings.skipLogin;
            backTransition = settings.backTransition;
            pageHashTransitions = settings.pageHashTransitions;
            showRememberedSummary = settings.showRememberedSummary;
        }

        var startState, clearUserState;
        if (skipLogin === true) {
            startState = 'profile';
            clearUserState = 'profile';
        } else {
            startState = 'pickingProfileView';
            clearUserState = 'login';
        }
        finalTransition.anteGuard = createAnteGuard(finalTransition.anteGuard);
        finalTransition.guard = createFinalGuard(finalTransition.guard);

        var userMachine = {
            startState: startState,
            states: {
                pickingProfileView: {
                    transitions: {
                        login: { automatic: true, stateName: 'login', guard: unknownUser },
                        profile: { automatic: true, stateName: 'profile', guard: knownUser }
                    }
                },
                login: {
                    display: {
                        showLogin: displayValue,
                        showRememberedSummary: showRememberedSummary
                    },
                    transitions: {
                        forward: { name: 'ButtonTextContinue', stateName: 'profile', guard: isValidEmail, action: fetchIdentities }
                    }
                },
                profile: {
                    display: {
                        showUser: displayValue,
                        showRememberedSummary: showRememberedSummary
                    },
                    transitions: {
                        login: { stateName: clearUserState, action: clearUser },
                        forward: finalTransition
                    }
                }
            }
        };

        for (var transitionName in pageHashTransitions || {}) {
            userMachine.states.login.transitions[transitionName] = pageHashTransitions[transitionName];
            userMachine.states.profile.transitions[transitionName] = pageHashTransitions[transitionName];
        }

        if (backTransition !== undefined) {
            userMachine.states.pickingProfileView.transitions.back = backTransition;
            userMachine.states.login.transitions.back = backTransition;
            userMachine.states.profile.transitions.back = backTransition;
        }

        VoterVoiceApplication.addEmailOwnershipVerificationStates(userMachine, 'profile', 'forward');
        if (typeof AddressApplication !== 'undefined') {
            AddressApplication.addCoordinatesAdjustmentState(userMachine, 'profile');
            AddressApplication.addVendorMapState(userMachine, 'profile');
        }
        return userMachine;
    };
    VoterVoiceApplication.addEmailOwnershipVerificationStates = function(machine, triggerState, triggerTransition, retryTransition) {
        if (arguments.length < 4) {
            retryTransition = triggerTransition;
        }
        machine.states[triggerState].transitions[triggerTransition].failTransition = 'verifyEmail';
        machine.states[triggerState].transitions.verifyEmail = { guard: isVerifying, stateName: 'verification' };
        machine.states.verification = {
            display: {
                dialog: 'emailVerificationDialog'
            },
            transitions: {
                forward: { guard: hasVerificationCode, action: verifyCode, stateName: 'selectingUser' },
                cancel: { stateName: triggerState }
            }
        };
        machine.states.selectingUser = {
            transitions: {
                selectUserIdentity: { automatic: true, action: selectUserIdentity, stateName: 'completingVerification', failTransition: 'cancel' },
                cancel: { stateName: triggerState }
            }
        };
        machine.states.completingVerification = {
            entry: function(enterState) {
                machine.states[triggerState].entry = function(enterState) {
                    delete machine.states[triggerState].entry;
                    this.navigateByName(retryTransition);
                    // ideally we'd call this after navigation failed (or succeeded but no need then)
                    enterState();
                };
                enterState();
            },
            transitions: {
                complete: { automatic: true, stateName: triggerState }
            }
        };
    };
    //TODO: Identical to 'addEmailOwnershipVerificationStates' except for the transition and state names. Possibly find way to consolidate. -PJ
    VoterVoiceApplication.addEmailOwnershipVerificationStatesPhone = function (machine, triggerState, triggerTransition, retryTransition) {
        if (arguments.length < 4) {
            retryTransition = triggerTransition;
        }
        machine.states[triggerState].transitions[triggerTransition].failTransition = 'verifyEmailPhone';
        machine.states[triggerState].transitions.verifyEmailPhone = { guard: isVerifying, stateName: 'verificationPhone' };
        machine.states.verificationPhone = {
            display: {
                dialog: 'emailVerificationDialog'
            },
            transitions: {
                forward: { guard: hasVerificationCode, action: verifyCode, stateName: 'selectingUserPhone' },
                cancel: { stateName: triggerState }
            }
        };
        machine.states.selectingUserPhone = {
            transitions: {
                selectUserIdentity: { automatic: true, action: selectUserIdentity, stateName: 'completingVerificationPhone', failTransition: 'cancel' },
                cancel: { stateName: triggerState }
            }
        };
        machine.states.completingVerificationPhone = {
            entry: function (enterState) {
                machine.states[triggerState].entry = function (enterState) {
                    delete machine.states[triggerState].entry;
                    this.navigateByName(retryTransition);
                    // ideally we'd call this after navigation failed (or succeeded but no need then)
                    enterState();
                };
                enterState();
            },
            transitions: {
                complete: { automatic: true, stateName: triggerState }
            }
        };
    };

    UserProfileApplication.userMachine = VoterVoiceApplication.generateUserMachine(
        'full',
        { name: 'ButtonTextSave', getCustomButtonText: getSubmitResponseButtonText, action: saveUserAndWaitForRemember, stateName: 'final' });

    UserProfileApplication.emailZipcodeMachine = {
        startState: 'collectingEmailAndZipcode',
        states: {
            collectingEmailAndZipcode: {
                display: {

                },
                transitions: {
                    signup: { guard: isValidEmail, action: checkZipcodeAndRedirect, stateName: 'final' }
                }
            }
        }
    };

    UserProfileApplication.prototype = {
        initializeUser: function(associationIdName, siteNumber, associationCountry, skipLogin, useInternationalUi, customButtonText) {
            var me = this;

            this._defaultCountry = associationCountry;
            this._customButtonText = customButtonText;
            this._userTracker.loadAssociationSite(associationIdName, siteNumber, associationCountry);
            this._userTracker._setUser = function(user) {
                me.switchedUser.dispatch(user);
            };
            this._userTracker._setOrClearIdentity = function(userToken, changedAddresses) {
                me.identityChanged.dispatch(userToken, changedAddresses);
            };

            if (skipLogin === true) {
                this.initializeStates(function(machine) {
                    return machine.startState === 'pickingProfileView';
                }, function(machine) {
                    machine.startState = 'profile';
                    machine.states.profile.transitions.login.stateName = 'profile';
                });
            }
        },
        baselineUser: function(getUser, requiredTemplate) {
            this._userTracker.baseline(getUser, requiredTemplate);
        },
        requestEmailVerification: function(complete) {
            var me = this;
            this._userTracker.getEmailOwnershipVerification(
                function(verification, rateLimited) {
                    me._emailVerification = {
                        emailAddress: verification.emailAddress,
                        verificationId: verification.verificationId,
                        rateLimited: rateLimited,
                        secretCode: me._emailVerification !== undefined
                            ? me._emailVerification.secretCode
                            : undefined
                    };
                    complete();
                },
                function(result, error) {
                    complete(result);
                });
        },
        setEmailVerificationCode: function(secretCode) {
            this._emailVerification.secretCode = secretCode;
        },
        useEmailVerificationProof: function(ownershipProof, complete, fail) {
            var me = this;
            this._userTracker.identifyUsersByEmail(
                this._emailVerification.emailAddress, ownershipProof,
                function(identities) {
                    me._emailVerification.identities = identities;
                    complete();
                },
                function(result, error) {
                    fail(result);
                });
        },
        sanitizeHonorific: function(honorific, complete, fail) {
            this._userTracker.sanitizeHonorific(honorific, complete, fail);
        },
        verifyCode: function (complete, fail) {
            var me = this;
            verifyCode.call(me, function() {
                selectUserIdentity.call(me, complete, fail);
            }, fail);
        }
    };

    function getSubmitResponseButtonText(context){
        var me = context._application;
        return me._customButtonText;
    }

    function unknownUser() {
        return this._userTracker.getIdentity().userId === undefined;
    }

    function knownUser() {
        return !unknownUser.call(this);
    }

    function isValidEmail() {
        var loginInfo = this.ui.readLoginInfo();
        return this._emailAddressService.isValid(loginInfo.emailAddress)
            ? true
            : 'invalidEmail';
    }

    UserProfileApplication.hasValidLoginEmail = isValidEmail;

    function clearUser(complete, fail) {
        this._userTracker.clear();
        this._userTracker.remember(false);
        this.userCleared.dispatch();
        complete();
    }

    UserProfileApplication.clearUser = clearUser;

    function fetchIdentities(complete, fail) {
        var loginInfo = this.ui.readLoginInfo();

        // simulate old this._userTracker.identifyUsers()
        this._userTracker.identitiesCriteria = loginInfo;

        var me = this;
        this._userTracker.validateZipCode(loginInfo.zipCode, loginInfo.country || me._defaultCountry,
            function(result) {
                if (result.message !== null) {
                    fail(result.message);
                } else if (result.addresses.length === 0) {
                    fail(new VoterVoiceTranslator().GetTextForCulture('CantFindZipcode'));
                } else {
                    me._userTracker.expandIdentification(result.addresses[0]);

                    // simulate old selectIdentity()
                    me._userTracker.declareNewFromLatestIdentification();
                    complete();
                }
            });
    }

    fetchIdentities.waitToken = 'fetchIdentities';
    UserProfileApplication.fetchIdentities = fetchIdentities;

    function ensureAddressCorrections() {
        if (this.hasWaitActivity('validatingAddress') === false) {
            this.ensureAddressCorrections();
        }
        return true;
    }

    function waitForAddress() {
        return true;
    }

    waitForAddress.blockingWaits = ['validatingAddress'];

    VoterVoiceApplication.saveUser = function(complete, fail, standAloneRegistration, waitForRemember) {
        delete this._emailVerification;
        var receivesMessages = this.ui.readReceivesMessages();
        var membership = this.ui.readMembership();

        var me = this;
        var saveAndRegister = function() {
            me._userTracker.saveAndRegister(
                standAloneRegistration, membership,
                function(result) {

                    var rememberMe = me.ui.readRememberMe();

                    if (rememberMe !== undefined) {
                        me._userTracker.remember(rememberMe, !waitForRemember);
                    }
                    complete();
                },
                function(result, error) {
                    fail(result);
                },
                function(choices) {
                    if (arguments.length !== 0) {
                        resolveMultipleChoices(choices,
                            function(identity) {
                                me._userTracker.forceIdentity(identity);
                                saveAndRegister();
                            },
                            function() {
                                me.emailCleared.dispatch();
                                fail();
                            });
                    } else {
                        me.requestEmailVerification(fail);
                    }
                });
        };
        saveAndRegister();
    };
    VoterVoiceApplication.saveUser.waitToken = 'saveUser';

    function resolveMultipleChoices(choices, select, cancel) {
        var radioOptions = [];
        for (var c = 0; c < choices.length; c++) {
            radioOptions.push(choices[c].givenNames + " " + choices[c].surname);
        }
        var translator = new VoterVoiceTranslator();
        radioOptions.push(translator.GetTextForCulture("NoneOfTheAboveEmailAddresses"));
        vvui.choose(
            translator.GetTextForCulture("MultipleIdentitiesByEmailAddressPleaseIdentify"),
            radioOptions,
            function(c) {
                if (c < choices.length) {
                    select(choices[c]);
                } else {
                    cancel();
                }
            });
    }

    function saveUserAndWaitForRemember(complete, fail) {
        var me = this;
        var complete2 = function() {
            if (me.finalRedirect === './Register/ThankYou') {
                // only needed to support Sharing for UNremembered users
                var identity = me._userTracker.getIdentity();
                me.finalRedirect += '?user=' + identity.userToken;
            }
            me.finalRedirect = me.appendCurrentQueries(me.finalRedirect, ['vvcgRD', 'vvsn']);
            complete();
        };
        VoterVoiceApplication.saveUser.call(this, complete2, fail, true, true);
    }

    saveUserAndWaitForRemember.waitToken = 'saveUser';

    function checkZipcodeAndRedirect(complete, fail) {
        var me = this;

        function complete2() {
            me._userTracker.declareNewFromLatestIdentification();
            me.finalRedirect = "./Register?" + me._userTracker.createEmailAndAddressQuery();
            complete();
        }

        fetchIdentities.call(this, complete2, fail);
    }

    checkZipcodeAndRedirect.waitToken = 'saveUser';


    function isVerifying() {
        return this._emailVerification !== undefined;
    }

    function hasVerificationCode() {
        return true; //this._emailVerification.secretCode ? true : false;
    }

    function verifyCode(complete, fail) {
        var me = this;
        var secretCode = this._emailVerification.secretCode;
        if (secretCode === undefined || secretCode.length === 0) {
            me.secretCodeRequired.dispatch();
            fail();
            return;
        }

        this._userTracker.getEmailOwnershipProof(
            this._emailVerification.verificationId, this._emailVerification.secretCode,
            function(result) {
                me.useEmailVerificationProof(result.proof, complete, fail);
            },
            function(result, error) {
                me.emailOwnershipProofFailed.dispatch(false);
                fail();
            },
            function() {
                me.emailOwnershipProofFailed.dispatch(true);
                fail();
            });
    }

    function selectUserIdentity(complete, fail) {
        var identities = this._emailVerification.identities;
        if (identities.length === 0) {
            // this is now completing the transition to accommodate for the verification prompted for unregistered users with an email flagged as bad, which 
            // the identities resource for registered users would return empty once a valid code is entered
            complete();
            //fail("No users found with this email address.");
        } else if (identities.length === 1) {
            this._userTracker.forceIdentity(identities[0]);
            complete();
        } else {
            var me = this;
            resolveMultipleChoices(identities,
                function(identity) {
                    me._userTracker.forceIdentity(identity);
                    complete();
                },
                function() {
                    me.emailCleared.dispatch();
                    fail();
                });
        }
    }

})();

//
// VisitTracker.js
//
function VisitTrackingApplication() {
}

VisitTrackingApplication.prototype = {
    initializeVisit: function(visitTracker) {
        this._visitTracker = visitTracker;
    }
};
VoterVoiceApplication.prototype.enableVisitTracking = function() {
    this.mixin(VisitTrackingApplication, arguments);
};;
function AdvocacyApplication(responseFactory) {
    this._responseFactory = responseFactory;
    this._additionalUserFields = new Array();
    this._questionnaireItems = new Array();

    this.composerChanged = new VoterVoiceApplication.Event();
    this.getProfiles = new VoterVoiceApplication.Event();
    this.hasMessages = new VoterVoiceApplication.RepeatEvent();
    this.matchingStarted = new VoterVoiceApplication.RepeatEvent();
    this.matchingCleared = new VoterVoiceApplication.RepeatEvent();
    this.matchingSucceeded = new VoterVoiceApplication.RepeatEvent();
    this.recipientsDetermined = new VoterVoiceApplication.RepeatEvent();
    this.resetMaximumMessageLength = new VoterVoiceApplication.Event();
    this.deliverabilityDetected = new VoterVoiceApplication.FilteredRepeatEvent(
        function(messageId, hasRecipients, hasDeliveryOptions) {
            return messageId;
        });
    this.signatureChanged = new VoterVoiceApplication.Event();
    this._singleResponses = [];
    this._matchCriteria = {
        byUser: false,
        addressChanged: false,
        byAddress: false
    };
}

VoterVoiceApplication.prototype.enableAdvocacy = function() {
    this.mixin(AdvocacyApplication, arguments);
};
(function(undefined) {

    function translateLanguage(key) {
        var translator = new VoterVoiceTranslator();
        return translator.GetTextForCulture(key);
    }

    VoterVoiceApplication.addPhoneState = function(machine) {
        machine.states.placingPhoneCall = {
            display: {
                dialog: 'phoneCallDialog'
            },
            transitions: {
                closeDialog: { stateName: 'respond' },
                completeCall: { guard: isPhoneResponseValid, action: sendPhoneResponse, stateName: 'respond' }
            }
        };
    };

    function respondGuard() {
        var deliveryMethodsMissingCredentials = this._responseFactory.missingSocialMediaCredentials();
        if (deliveryMethodsMissingCredentials.length !== 0) {
            var socialMediaAuth = new SocialMediaAuth(window, { jQuery: VoterVoiceUserInterface._jQuery() });
            var rootUrl = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '') + '/' + this._advocacyContext.association;
            var initiatingState = this.currentState;
            var me = this;

            function receiveCredentials(deliveryMethod, credentials) {
                me._responseFactory.setSocialMediaCredentials(deliveryMethod, credentials);
                if (me.currentState === initiatingState) {
                    me.navigateByName('forward');
                }
            }

            function getQueryParams(socialMedia) {
                if (me._userTracker._userToken) {
                    // If the person already sent a message to these officials via this method, then force-re-auth to ensure they means to resend as the same social media account.
                    var deliveryTargets = me._responseFactory.getDeliveredTargetsFromLocalStorage(me._userTracker._userToken, me._advocacyContext.campaignId);
                    if (deliveryTargets === null) {
                        var serialized = decodeURIComponent(getParameterByName(location.search, 'deliveredMethods'));
                        deliveryTargets = me._responseFactory.getDeliveredTargetsFromSerialized(me._userTracker._userToken, serialized);
                    }
                    if (deliveryTargets[socialMedia].length > 0)
                        return '?forceLogin=true';
                } else {
                    return '?forceLogin=true';
                }
                return '';
            }

            for (var i = 0; i < deliveryMethodsMissingCredentials.length; i++) {
               
                switch (deliveryMethodsMissingCredentials[i]) {
                    case 'twitter':
                        var twitterKey = 'twitter';
                        var twitterAuthUrl = rootUrl + '/Twitter/Auth' + getQueryParams(twitterKey);
                        twitterAuthUrl = socialMediaAuth.appendBroadcastChannelIdentifierQuerystring(twitterAuthUrl, twitterKey);

                        socialMediaAuth.openTwitterDialog(twitterAuthUrl, function (message) {
                            if (message.oauthToken != undefined && message.oauthAccessToken != undefined) {
                                receiveCredentials(twitterKey, {
                                    oauthToken: message.oauthToken,
                                    oauthAccessToken: message.oauthAccessToken
                                });
                            }
                        });
                        break;
                    case 'facebook':
                        socialMediaAuth.openFbDialog(rootUrl + '/Facebook/Auth' + getQueryParams('facebook'), function (message) {
                            if (/^[0-9A-Za-z]{85}[0-9A-Za-z]+$/.test(message)) {
                                receiveCredentials('facebook', {
                                    oauthToken: undefined,
                                    oauthAccessToken: message
                                });
                            }
                        });
                        break;
                }
            }
        }

        // Open a window/tab for Twitter/X if needed
        if (this._responseFactory.isTwitter()) {
            if (this.twitterWindow) {
                try { // See if we can still control the window by reading the origin.
                    var origin = this.twitterWindow.origin; 
                } catch (error) { // If DomException, then the window's url changed.
                    console.error(error);
                    try { // Try to close twitterWindow.
                        this.twitterWindow.close();
                    } catch (error) { // We have no control over the window anymore, so just move on.
                        console.error(error);
                    }
                    this.twitterWindow = null;
                }
            }
            if (!this.twitterWindow) {
                this.twitterWindow = window.open(undefined, 'twitter_post', { popup: true });
                this.twitterWindow.document.write('Opening X so you can complete the post...');
            }
        }

        return deliveryMethodsMissingCredentials.length === 0;
    }

    VoterVoiceApplication.buildRespondTransition = function(transition, isNotFinalTransition) {
        transition.name = 'ButtonTextSendMessage';
        transition.anteGuard = respondGuard;
        transition.stateName = 'getHonorificEquivalents';

        // Custom Button only for final transition
        if (!isNotFinalTransition)
          transition.getCustomButtonText= getSubmitResponseButtonText;
        return transition;
    };
    VoterVoiceApplication.addRespondStates = function(machines) {
        var states = {
            getHonorificEquivalents: {
                entry: getHonorificEquivalents,
                transitions: {
                    gotoCheckingQuestionnaire: { automatic: true, stateName: "checkingQuestionnaire"}
                }
            },
            checkingQuestionnaire: {
                entry: checkQuestionnaire,
                transitions: {
                    gotoQuestionnaire: { automatic: true, guard: hasQuestionnaireItems, stateName: 'questionnaire' },
                    gotoConfirmation: { automatic: true, guard: hasNoQuestionnaireItems, action: sendResponse, stateName: 'final', failTransition: 'fail' },
                    fail: { stateName: 'respond' }
                }
            },
            questionnaire: {
                display: {
                    pageHash: 'additionalInformation',
                    showQuestionnaire: true
                },
                transitions: {
                    back: { stateName: 'respond' },
                    forward: { name: 'ButtonTextSendMessage', guard: noMoreInformationNeeded, action: sendUserAndResponse, stateName: 'final' }
                }
            }
        };
        for (var s in states) {
            machines.states[s] = states[s];
        }
    };

    function startGettingDeliveryOptions(me, associationIdName, messageId, match, pendingOptions) {
        me._responseFactory.getDeliveryOptions(
            match.type, match.id, associationIdName, messageId === 0 ? null : messageId,
            function(targetType, targetId, options) {
                pendingOptions[messageId].pendingOptionCount--;

                match.gotDeliveryOptions.dispatch(match, options, pendingOptions[messageId]);

                if (!pendingOptions[messageId].deliverabilityDetected) {
                    pendingOptions[messageId].deliverabilityDetected = true;
                    for (var i = 0; i < pendingOptions[messageId].deliverabilityDetections.length; i++) {
                        if (pendingOptions[messageId].deliverabilityDetections[i].isDeliverable === undefined) {
                            pendingOptions[messageId].deliverabilityDetected = false;
                            break;
                        }
                    }
                    if (pendingOptions[messageId].deliverabilityDetected === true) {
                        me.deliverabilityDetected.dispatch(messageId);
                    }
                }
            });
    }

    AdvocacyApplication.prototype = {
        initializeAdvocacy: function(campaignId, associationIdName, confirmResponseUrl, customButtonText) {
            this._advocacyContext = { association: associationIdName, campaignId: campaignId, confirmResponseUrl: confirmResponseUrl };
            var me = this;
            me._initializationComplete = false;
            me._customButtonText = customButtonText;

            this._responseFactory.registerMessageNotification(function(messages) {

                me.hasMessages.dispatch(messages);

            });
            this._responseFactory.loadMessages(associationIdName, campaignId);

            this._responseFactory.beforeMatching = function() { me.switchWaitActivity(null, 'matching'); };
            this._responseFactory.registerMatchNotification(
                function(matchGroups) {
                    // add gotDeliveryOptions & fire matchingSucceeded
                    for (var g = 0; g < matchGroups.length; g++) {
                        var group = matchGroups[g];
                        for (var m = 0; m < group.matches.length; m++) {
                            group.matches[m].gotDeliveryOptions = new VoterVoiceApplication.RepeatEvent();
                            group.matches[m].phoneCallCompleted = new VoterVoiceApplication.Event();
                        }
                    }

                    me.switchWaitActivity('matching', 'fetchingOptions');
                    // raise this AFTER matching->fetchingOptions, so handlers can trigger allOptionsSet
                    me.matchingSucceeded.dispatch({
                        groups: matchGroups,
                        criteria: me._matchCriteria
                    });

                    var pendingOptions = {};
                    for (g = 0; g < matchGroups.length; g++) {
                        // fetch delivery options and fire deliverabilityDetected
                        (function(group) {
                            if (!pendingOptions[group.messageId]) {
                                pendingOptions[group.messageId] = {
                                    messageId: group.messageId,
                                    pendingOptionCount: 0,
                                    deliverabilityDetections: []
                                };
                            }
                            pendingOptions[group.messageId].pendingOptionCount += group.matches.length;
                            for (var m = 0; m < group.matches.length; m++) {
                                if (!group.matches[m].type || !group.matches[m].id) {
                                    var message = "Invalid matches[" + m + "] properties";
                                    for (var p in group.matches[m]) {
                                        message += ", " + p;
                                    }
                                    me._userTracker._api.logWarning(message);
                                }
                                startGettingDeliveryOptions(me, associationIdName, group.messageId, group.matches[m], pendingOptions);
                            }
                            //if (pendingOptions[group.messageId].pendingOptionCount === 0) {
                            //    me.deliverabilityDetected.dispatch(group.messageId, false, false);
                            //}
                        })(matchGroups[g]);
                    }
                },
                false);
            this._responseFactory.allOptionsSet = function() { me.switchWaitActivity('fetchingOptions'); };

            if (this._userTracker._userToken) {
                this._matchCriteria.byUser = true;
                this._responseFactory.matchUser(associationIdName, this._userTracker._userToken, campaignId);
            }

            /*
            User Stuff
            */
            this._userTracker.addressesChanged = function(changedAddresses) {
                var identity = me._userTracker.getIdentity();
                me._matchCriteria.byUser = identity.userToken ? true : false;
                me._matchCriteria.addressChanged = true;
                me._matchCriteria.byAddress = false;
                // find non-empty addresses
                for (var a in me._userTracker._changedAddresses) {
                    for (var b in me._userTracker._changedAddresses[a])
                        me._matchCriteria.byAddress = true;
                }
                me._responseFactory.matchUser(associationIdName, identity.userToken, campaignId, changedAddresses);
            };
            this._userTracker.forceUserRematch = function(association) {
                var identity = me._userTracker.getIdentity();
                me._matchCriteria.byUser = identity.userToken ? true : false;
                me._matchCriteria.addressChanged = false;
                me._matchCriteria.byAddress = false;
                // find non-empty addresses
                for (var a in me._userTracker._changedAddresses) {
                    for (var b in me._userTracker._changedAddresses[a])
                        me._matchCriteria.byAddress = true;
                }
                if (me._matchCriteria.byAddress === true)
                    me._responseFactory.matchUser(associationIdName, identity.userToken, campaignId, me._userTracker._changedAddresses);
                else
                    me._responseFactory.matchUser(associationIdName, identity.userToken, campaignId);
            };
            this.identityChanged.attach(function(userToken, changedAddresses) {
                if (userToken === null) {
                    me.customizeSignature(null);
                    me._completedCalls = [];
                }
                // rematch when loaded or cleared
                me._matchCriteria.byUser = userToken ? true : false;
                me._matchCriteria.addressChanged = false;
                me._matchCriteria.byAddress = false;
                me._responseFactory.matchUser(associationIdName, userToken, campaignId, changedAddresses);
            });
            this.getProfiles = function(filter, callback) {
                me._api.searchProfiles(associationIdName, filter, function(profiles) {
                    callback(profiles);
                });
            }
        },
        initializeComposer: function(deliveriesProperty) {
            this._responseFactory.initializeComposer(deliveriesProperty);
        },
        switchComposer: function(composer) {
            if (this._context.currentTransition === undefined) {
                if (this._responseFactory.switchComposer(composer.targetedMessageDeliverableProperty, composer.deliveriesProperty)) {
                    this.composerChanged.dispatch(composer);
                }
            }
        },
        setDeliveryOption: function(targetType, targetId, option, deliveriesProperty) {
            this._responseFactory.setDeliveryOption(targetType, targetId, option, deliveriesProperty);
        },
        customizeSignature: function(signature) {
            this._customSignature = signature;
            this.signatureChanged.dispatch(signature);
        },
        activateCallee: function(match, option, targetedMessage, sampleMessage) {
            this._currentCallee = {
                match: match,
                option: option,
                targetedMessage: targetedMessage,
                sampleMessage: sampleMessage
            };
        },
        setCalleeResult: function(phoneResponse) {
            this._currentCallee.response = phoneResponse;
        },
        getMessageReadyGuard: function(notAllSingleResponsesAreComplete) {
            var hasSubjectAndBody = this._responseFactory.allDeliveriesHaveSubjectAndBody();
            var salutation = this._responseFactory.findSalutation();
            var closing = this._responseFactory.findClosing();
            if (hasSubjectAndBody === false) {
                //messageDeliveries have subject and body. The rest have only body.
                return this._responseFactory._deliveries.deliveriesProperty === 'messageDeliveries' ? 'noSubjectOrBody' : 'noBody';
            }
            if (notAllSingleResponsesAreComplete === true) {
                return this._singleResponses.length === 0 ? 'noPhoneResponsesGiven' : { userPromptKey: 'LeaveWithoutPlacingCalls' };
            }
            var results = [];
            if (salutation !== null) {
                results.push({ userPromptKey: 'KeepDetectedSalutation', userPromptParameters: [salutation], userPromptButtons: 'Yes|No' });
            }
            if (closing !== null) {
                results.push({ userPromptKey: 'KeepDetectedClosing', userPromptParameters: [closing], userPromptButtons: 'Yes|No' });
            }

            return results.length === 0 ? true : results;
        },
        answerQuestion: function(question, answer) {
            this._responseFactory.answer(question, answer);
        }
    };

    function getSubmitResponseButtonText(context){
        var me = context._application;
        return me._customButtonText;
    }

    function getHonorificEquivalents(enterState) {
        var me = this;
        var honorific = me._userTracker._fetchUser().honorific;
        var commonHonorific = me._userTracker._commonHonorific;
        
        me._responseFactory.getHonorificEquivalents(honorific, function (result, error) {
            if (result.length > 0)
                me._responseFactory._fallbackValues["Honorific"] = result;

            if (commonHonorific && commonHonorific.length > 0) {
                me._responseFactory.getHonorificEquivalents(commonHonorific,
                    function (result, error) {
                        if (result.length > 0)
                            me._responseFactory._fallbackValues["CommonHonorific"] = result;
                        enterState();
                    });
            } else {
                enterState();
            }
        });
    }

    function checkQuestionnaire(enterState) {
        var requirements = this._responseFactory.aggregateRequirements();

        function populateQuestionnaire(missingFieldNames) {
            var additionalUserFields = new Array();
            var questionnaireItems = new Array();

            // check required user fields
            for (var fn = 0; fn < missingFieldNames.length; fn++) {
                var fieldName = missingFieldNames[fn];
                var oldFieldValue = null;
                for (var auf = 0; auf < this._additionalUserFields.length; auf++) {
                    if (this._additionalUserFields[auf].fieldName === fieldName) {
                        oldFieldValue = this._additionalUserFields[auf].answer;
                    }
                }
                additionalUserFields.push({
                    heading: translateLanguage('RequirementsForMultipleRecipients') + " (" + requirements.userFieldRecipients[fieldName].length + ")",
                    fieldName: fieldName,
                    answer: oldFieldValue
                });
            }
            // check shared questions
            for (var id in requirements.sharedQuestionRecipients) {
                var question = this._responseFactory.getSharedQuestion(id);

                questionnaireItems.push({
                    heading: translateLanguage('RequirementsForMultipleRecipients') + " (" + requirements.sharedQuestionRecipients[id].length + ")",
                    question: question
                });
            }
            // check required questions
            var prefixQuestion = {
                questionText: '',
                recipients: [],
                potentialAnswers: []
            };

            for (var t in requirements.questions) {
                for (var f = 0; f < requirements.questions[t].length; f++) {
                    if (requirements.questions[t][f].question !== translateLanguage('HonorificName'))
                        questionnaireItems.push({
                            heading: this._responseFactory.getMatch(t).name,
                            question: requirements.questions[t][f]
                        });
                    else {
                        prefixQuestion.questionText = requirements.questions[t][f].question;
                        prefixQuestion.recipients.push(this._responseFactory.getMatch(t).name);
                        prefixQuestion.potentialAnswers.push(requirements.questions[t][f].validAnswers);
                    }
                }
            }

            // Handle Honorifics
            if (prefixQuestion.recipients.length > 0) {
                var mergedResults = mergeArrays.apply(this, prefixQuestion.potentialAnswers).sort();
                var mergedQuestion = {
                    heading: prefixQuestion.recipients.join(', '),
                    question: {
                        question: prefixQuestion.questionText,
                        validAnswers: mergedResults
                    }
                };
                
                var answer = getPrefixValue(this, mergedResults);
                
                if (answer)
                    this._responseFactory.answer(mergedQuestion.question, answer);
                else 
                    questionnaireItems.push(mergedQuestion);
            }
            
            for (var i = 0; i < questionnaireItems.length; i++) {
                for (var j = 0; j < this._questionnaireItems.length; j++) {
                    if (this._questionnaireItems[j].question.question === questionnaireItems[i].question.question &&
                        this._questionnaireItems[j].question.answer !== undefined) {
                        questionnaireItems[i].question.answer = this._questionnaireItems[j].question.answer;
                        break;
                    }
                }
            }

            this._additionalUserFields = additionalUserFields;
            this._questionnaireItems = questionnaireItems;
            this.ui.populateQuestionnaire(this._additionalUserFields, this._questionnaireItems);
        }

        var me = this;
        this._userTracker.checkMissingFields(requirements.userFields, function(missingFieldNames) {
            populateQuestionnaire.call(me, missingFieldNames);
            enterState();
        });
    }

    function getPrefixValue(vm, mergedQuestionResults) {
        // IE8 indexOf polyfill
        if (!Array.prototype.indexOf) {
            Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
                "use strict";
                if (this == null) {
                    throw new TypeError();
                }
                var t = Object(this);
                var len = t.length >>> 0;
                if (len === 0) {
                    return -1;
                }
                var n = 0;
                if (arguments.length > 0) {
                    n = Number(arguments[1]);
                    if (n != n) { // shortcut for verifying if it's NaN
                        n = 0;
                    } else if (n != 0 && n != Infinity && n != -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {
                    return -1;
                }
                var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
                for (; k < len; k++) {
                    if (k in t && t[k] === searchElement) {
                        return k;
                    }
                }
                return -1;
            }
        }

        if (mergedQuestionResults.indexOf(vm._userTracker._fetchUser().honorific) > -1)
            return vm._userTracker._fetchUser().honorific;
        else if (mergedQuestionResults.indexOf(vm._userTracker._commonHonorific) > -1)
            return vm._userTracker._commonHonorific;
        else {
            for (var fallbackType in vm._responseFactory._fallbackValues) {
                for (var h = 0; h < vm._responseFactory._fallbackValues[fallbackType].length; h++) {
                    if (mergedQuestionResults.indexOf(vm._responseFactory._fallbackValues[fallbackType][h]) > -1) {
                        if (fallbackType === "Honorific") return vm._userTracker._fetchUser().honorific;
                        if (fallbackType === "CommonHonorific") return vm._userTracker._commonHonorific;
                    }
                }
            }
        }
        return null;
    }

    function hasQuestionnaireItems() {
        return !hasNoQuestionnaireItems.call(this);
    }

    function hasNoQuestionnaireItems() {
        return this._additionalUserFields.length === 0
            && this._questionnaireItems.length === 0
            && this._responseFactory.isQuestionnaireComplete(this._additionalUserFields);
    }

    function noMoreInformationNeeded() {
        return this._responseFactory.isQuestionnaireComplete(this._additionalUserFields)
            ? true
            : 'moreInformationNeeded';
    }

    function isPhoneResponseValid() {
        var phoneResponse = this._currentCallee.response;
        if (phoneResponse === undefined) {
            return 'phoneCompletionUnanswered';
        } else if (phoneResponse !== null) {
            if (phoneResponse.recipientName.length === 0) {
                return 'phoneRecipientRequired';
            }
            if (phoneResponse.recipientResponse !== undefined) {
                if (phoneResponse.recipientResponse.length === 0)
                    return 'phoneResponseRequired';
            }
        }
        return true;
    }

    function sendPhoneResponse(complete, fail) {
        var phoneResponse = this._currentCallee.response;
        if (phoneResponse === null) {
            complete();
            return;
        }

        var identity = this._userTracker.getIdentity();
        var signature = this._customSignature ? this._customSignature : identity.signature;
        var defaultAddressType = getDefaultAddressType.call(this);
        if (defaultAddressType === undefined) {
            fail(translateLanguage('NoAddressProvided'));
            return;
        }
        var match = this._currentCallee.match;
        var response = this._responseFactory.generatePhoneResponse(
            identity.userId, signature, defaultAddressType,
            match, this._currentCallee.option, this._currentCallee.targetedMessage,
            phoneResponse
        );
        var me = this;
        this._responseFactory.respond(identity.userToken, response, function(result, error) {
            if (arguments.length > 1) {
                fail(result);
            } else {
                me._singleResponses.push(result);
                me._responseFactory.storeResponse(identity.userToken, result, match);
                me._currentCallee.match.phoneCallCompleted.dispatch();
                complete();
            }
        });
    }

    sendPhoneResponse.waitToken = 'sendResponse';

    function getDefaultAddressType() {
        if (this._userTracker.savedAddressType('H')) {
            return 'H';
        } else if (this._userTracker.savedAddressType('B')) {
            return 'B';
        } else {
            return undefined;
        }
    }

    function sendResponse(complete, fail) {
        var identity = this._userTracker.getIdentity();
        var signature = this._customSignature ? this._customSignature : identity.signature;
        var defaultAddressType = getDefaultAddressType.call(this);
        if (defaultAddressType === undefined) {
            // skip this validation for AnonymousCampaigns
            if (identity.userToken !== undefined) {
                fail(translateLanguage('NoAddressProvided'));
                return;
            }
        }
        var response = this._responseFactory.generateResponse(identity.userId, signature, defaultAddressType);
        if (response.isPhoneResponses === true) {
            processResponseResult(this, identity, response, {deliveredMessages:[]} );
            complete();
        } else {
            var me = this;
            this._responseFactory.respond(identity.userToken, response, function(result, error) {
                if (arguments.length > 1) {
                    fail(result);
                } else {
                    processResponseResult(me, identity, response, result);
                    complete();
                }
            });
        }
    }

    sendResponse.waitToken = 'sendResponse';

    function sendUserAndResponse(complete, fail) {
        if (this._additionalUserFields.length > 0) {
            var userChanges = {};
            for (var f = 0; f < this._additionalUserFields.length; f++) {
                switch (this._additionalUserFields[f].fieldName) {
                case 'phone':
                    userChanges.phoneNumber = this._additionalUserFields[f].answer;
                    break;
                }
            }

            var me = this;
            this._userTracker.saveWithChanges(
                function(result) {
                    sendResponse.call(me, complete, fail);
                },
                function(result, error) {
                    fail(result);
                },
                userChanges);
        } else {
            sendResponse.call(this, complete, fail);
        }
    }

    sendUserAndResponse.waitToken = 'sendResponse';

    function processResponseResult(me, identity, response, result) {
        // Check if we need to open Twitter/X in another window, so the user can actually complete the post over there.
        if (result && result.twitterMessage) {
            var url = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(result.twitterMessage);
            try {
                me.twitterWindow.location.href = url;
            } catch (error) {
                me.twitterWindow = window.open(url, 'twitter_post', { popup: true });
            }
        }

        var deliveries = me._responseFactory.storeResponse(identity.userToken, result);

        var redirect;
        if (me._advocacyContext.confirmResponseUrl) {
            redirect = me._advocacyContext.confirmResponseUrl;
        } else {
            redirect = response.campaignId
                ? './ConfirmResponse?'
                : './Directory/ConfirmResponse?';
        }

        var smIds = [];
        for (var r = 0; r < me._singleResponses.length; r++) {
            redirect = appendDeliveryResults(redirect, me._singleResponses[r], me._responseFactory, smIds);
        }
        redirect = appendDeliveryResults(redirect, result, me._responseFactory, smIds);
        for (var s = 0; s < smIds.length; s++) {
            // group them all together because otherwise SOMETHING seems to be forcing this grouping through redirection
            redirect += 'smIds=' + smIds[s] + '&';
        }
        if (identity.userToken !== undefined) {
            redirect += 'user=' + identity.userToken;
        }
        if (me._responseFactory.isUserResponseRequired(result)) {
            if (redirect.lastIndexOf('&') !== redirect.length - 1)
                redirect += '&';
            redirect += 'userConfirmationRequired=true';
        }
        redirect = appendCurrentQueries(redirect, {
            include: ['vvcgRD', 'vvsn'],
            exclude: ['destinationIds', 'deliveredMessageIds', 'smIds', 'user', 'unregistered', 'userConfirmationRequired']
        });
        if (me._responseFactory._response.isRateLimited === true) {
            if (redirect.lastIndexOf('&') !== redirect.length - 1)
                redirect += '&';
            redirect += 'isRateLimited=true';
        }

        if (deliveries) {
            // LocalStorage of successful Delivery Methods and Officials for the action bar has failed. 
            // Patch the data into the URL. 
            redirect = appendDeliveryMethods(identity.userToken, redirect, deliveries);
        }

        //Add the pairing of UserMessagesId/SampleMessageId into the existing deliveredMessageIds array or into a new one.
        me.finalRedirect = redirect;
    }

    function appendDeliveryResults(redirect, result, responseFactory, smIds) {
        for (var m = 0; m < result.deliveredMessages.length; m++) {
            var messageDelivery = result.deliveredMessages[m];

            var sampleMessageId = responseFactory.findSampleMessageUsedInResponse(messageDelivery.targetedId);
            smIds.push(sampleMessageId === undefined ? 0 : sampleMessageId);

            if (messageDelivery.deliveredId === null) {
                for (var d = 0; d < messageDelivery.destinationIds.length; d++) {
                    redirect += 'destinationIds=' + messageDelivery.destinationIds[d] + '&';
                }
            } else {
                redirect += 'deliveredMessageIds=' + messageDelivery.deliveredId + '&';
            }
        }
        return redirect;
    }

    function appendDeliveryMethods(userToken, redirect, deliveries) {
        if (!('filter' in Array.prototype)) {
            Array.prototype.filter = function (filter, that /*opt*/) {
                var other = [], v;
                for (var i = 0, n = this.length; i < n; i++)
                    if (i in this && filter.call(that, v = this[i], i, this))
                        other.push(v);
                return other;
            };
        }
        //Get existing URL deliveryMethods value
        var param = getParameterByName(redirect, 'deliveredMethods');
        //Clean URL of any existing deliveryMethods value.
        var existingParam = param.replace(/ /g, '+');
        redirect = redirect.replace("&deliveredMethods=" + existingParam, '');
        redirect = redirect.replace("deliveredMethods=" + existingParam, '');

        var serialized = decodeURIComponent(param);
        var existingDeliveries = serialized ? JSON.parse(serialized) : { userToken: null, deliveries: [] };
        if (existingDeliveries.userToken !== userToken) {
            // Different user is active than the URL has, so disregard existingDeliveries
            existingDeliveries.deliveries = [];
        }
        // Concatenate and deduplicate the Delivery list. 
        var newDeliveries = existingDeliveries.deliveries.concat(deliveries.filter(function (item) { return indexOf(existingDeliveries.deliveries, item, function (a, b) { return a.id === b.id && a.deliveryMethod === b.deliveryMethod; }) < 0; }));

        if (redirect[redirect.length-1] !== '?' && redirect[redirect.length-1] !== '&') redirect += '&';
        redirect += 'deliveredMethods=' + encodeURIComponent(JSON.stringify({ userToken: userToken, deliveries: newDeliveries }));
        return redirect;
    }

    function appendCurrentQueries(redirect, options) {
        var includes = '', others = '';
        if (window.location.search) {
            var queries = window.location.search.substring(1).split('&'); // skip ?, separate on &
            for (var i = 0; i < queries.length; i++) {
                var name = queries[i].split('=')[0];
                if (indexOf(options.include, name) !== -1) {
                    // skip if include does not have value
                    if (queries[i] !== name) {
                        includes += '&' + queries[i];
                    }
                } else if (indexOf(options.exclude, name) === -1) {
                    others += '&' + queries[i];
                }
            }
        }
        return redirect + includes + others;
    }

    function mergeArrays() {
        var output = [];
        var obj = {};
        var array, item, count;
        
        for (var i = 0; i < arguments.length; i++) {
            array = arguments[i];
            
            for (var j = 0; j < array.length; j++) {
                item = "-" + array[j];
                count = obj[item] || 0;
                
                if (count == i) {
                    obj[item] = count + 1;
                }
            }
        }
        
        for (item in obj) {
            if (obj.hasOwnProperty(item) && obj[item] === arguments.length) {
                output.push(item.substring(1));
            }
        }
        return (output);
    }

    function indexOf(array, item, comparer) {
        //IE7 does not support Array.indexOf
        if (!comparer) {
            comparer = function(a, b) {
                return a === b;
            }
        }
        for (var i = 0; i < array.length; i++) {
            if (comparer(array[i], item)) {
                return i;
            }
        }
        return -1;
    }
    function getParameterByName(source, name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(source);
        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }

})();;
function CampaignResponseApplication() {
    this.goBack = new VoterVoiceApplication.Event();
    this.share = new VoterVoiceApplication.Event();
    this.scrollToHeader = new VoterVoiceApplication.Event();
}

VoterVoiceApplication.prototype.enableCampaignResponse = function() {
    this.mixin(CampaignResponseApplication, arguments);
};
(function(undefined) {

    CampaignResponseApplication.alertMachine = {
        startState: 'alert',
        states: {
            alert: {
                display: {
                    pageHash: null,
                    showAlert: 'full'
                },
                transitions: {
                    forward: { name: 'ButtonTextShare', action: share },
                    back: { name: 'ButtonTextBack', action: goBack }
                }
            }
        }
    };
    CampaignResponseApplication.nonSharableAlertMachine = {
        startState: 'alert',
        states: {
            alert: {
                display: {
                    pageHash: null,
                    showAlert: 'full'
                },
                transitions: {
                    back: { name: 'ButtonTextBack', action: goBack }
                }
            }
        }
    };

    //One-Click Campaign with validated user
    CampaignResponseApplication.validatedUserRespondMachine = {
        startState: 'respond',
        states: {
            respond: {
                display: {
                    pageHash: null,
                    showAlert: true,
                    showCompose: true
                },
                machine: VoterVoiceApplication.generateUserMachine({
                    displayValue: true,
                    finalTransition: VoterVoiceApplication.buildRespondTransition({ guard: addressesReadyAndRecipientsMatched, action: checkFieldsAndSaveUser, exitGuard: recipientsReady }),
                    skipLogin: true,
                    showRememberedSummary: true
                }),
                transitions: {
                    gotoQuestionnaire: { guard: addressesAndRecipientsReady, action: checkFieldsAndSaveUser, stateName: 'questionnaire' },
                    beginCall: { stateName: 'placingPhoneCall', guard: addressesValidatedAndRecipientsReady, action: checkFieldsAndSaveUserIfChanged, exitGuard: deliveryOptionsReady }
                }
            }
        }
    };
    VoterVoiceApplication.addPhoneState(CampaignResponseApplication.validatedUserRespondMachine);
    VoterVoiceApplication.addRespondStates(CampaignResponseApplication.validatedUserRespondMachine);
    VoterVoiceApplication.addEmailOwnershipVerificationStatesPhone(CampaignResponseApplication.validatedUserRespondMachine, 'respond', 'beginCall');

    //One-Click Campaign with user that has invalid info or is an uploaded contact
    CampaignResponseApplication.validateUserRespondMachine = {
        startState: 'respond',
        states: {
            respond: {
                display: {
                    pageHash: null,
                    showAlert: true,
                    showCompose: true
                },
                machine: VoterVoiceApplication.generateUserMachine(
                    true,
                    VoterVoiceApplication.buildRespondTransition({ guard: addressesReadyAndRecipientsMatched, action: checkFieldsAndSaveUser, exitGuard: recipientsReady }),
                    true),
                transitions: {
                    gotoQuestionnaire: { guard: addressesAndRecipientsReady, action: checkFieldsAndSaveUser, stateName: 'questionnaire' },
                    beginCall: { stateName: 'placingPhoneCall', guard: addressesValidatedAndRecipientsReady, action: checkFieldsAndSaveUserIfChanged, exitGuard: deliveryOptionsReady }
                }
            }
        }
    };
    VoterVoiceApplication.addPhoneState(CampaignResponseApplication.validateUserRespondMachine);
    VoterVoiceApplication.addRespondStates(CampaignResponseApplication.validateUserRespondMachine);
    VoterVoiceApplication.addEmailOwnershipVerificationStatesPhone(CampaignResponseApplication.validateUserRespondMachine, 'respond', 'beginCall');

    //Two-Click Campaign
    CampaignResponseApplication.respondMachine = {
        startState: 'alert',
        states: {
            alert: {
                display: {
                    pageHash: null,
                    showAlert: 'full',
                    showCompose: false
                },
                machine: VoterVoiceApplication.generateUserMachine({
                    displayValue: 'detailed',
                    finalTransition: {
                        name: 'ButtonTextReviewMessage',
                        stateName: 'respond',
                        action: checkFieldsAndSaveUser,
                    },
                    skipLogin: true,
                    pageHashTransitions: {
                        skipToRespond: { stateName: 'respond' }
                    },
                    showRememberedSummary: true
                }),
                transitions: {
                }
            },
            respond: {
                display: {
                    pageHash: 'respond',
                    showAlert: 'full',
                    showCompose: 'full'
                },
                transitions: {
                    back: { stateName: 'alert' },
                    beginCall: { stateName: 'placingPhoneCall', guard: deliveryOptionsReady },
                    forward: VoterVoiceApplication.buildRespondTransition({ guard: recipientsReady }, true),
                    gotoQuestionnaire: { guard: recipientsReady, stateName: 'questionnaire' }
                }
            }
        }
    };
    VoterVoiceApplication.addPhoneState(CampaignResponseApplication.respondMachine);
    VoterVoiceApplication.addRespondStates(CampaignResponseApplication.respondMachine);
    VoterVoiceApplication.addEmailOwnershipVerificationStatesPhone(CampaignResponseApplication.respondMachine, 'respond', 'beginCall');

    CampaignResponseApplication.anonymousMachine = {
        startState: 'respond',
        states: {
            respond: {
                display: {
                    pageHash: null,
                    showAlert: 'full',
                    showCompose: 'full',
                    signatureDisplay: 'first/last'
                },
                transitions: {
                    forward: VoterVoiceApplication.buildRespondTransition({ guard: recipientsReady }),
                    gotoQuestionnaire: { guard: recipientsReady, stateName: 'questionnaire' }
                }
            }
        }
    };
    VoterVoiceApplication.addRespondStates(CampaignResponseApplication.anonymousMachine);


    CampaignResponseApplication.prototype = {
        initializeCampaignResponse: function(showBackButton) {

            this._responseFactory.setVisitTracker(this._visitTracker);

            var me = this;
            this.matchingSucceeded.run(
                function (matchResult) {
                    if (matchResult.groups.length > 0) {
                        me.matchingStarted.dispatch();
                    } else {
                        me.matchingCleared.dispatch();
                    }
                    me.recipientsDetermined.dispatch(matchResult.groups, matchResult.criteria);
                });

            if (showBackButton === false) {
                if (this.states[this.startState].transitions.back) {
                    delete this.states[this.startState].transitions.back;
                }
            }
        }
    };

    function userIsIdentified() {
        var identity = this._userTracker.getIdentity();
        return identity.userToken !== undefined && identity.userToken !== null;
    }

    CampaignResponseApplication.userIsIdentified = userIsIdentified;

    function reportDeliveryOptions() {
        var result = {
            hasRecipients: false,
            hasDeliveryOptions: false,
            requiresSingleResponses: false,
            phoneCount: 0
        };
        for (var tmg = 0; tmg < this._responseFactory._latestRawMatches.length; tmg++) {
            for (var m = 0; m < this._responseFactory._latestRawMatches[tmg].matches.length; m++) {
                result.hasRecipients = true;
                var match = this._responseFactory._latestRawMatches[tmg].matches[m];
                if (this._responseFactory._deliveries[match.type + match.id]) {
                    result.hasDeliveryOptions = true;
                    if (this._responseFactory._deliveries[match.type + match.id].deliveryMethod === 'phone') {
                        result.requiresSingleResponses = true;
                        result.phoneCount++;
                    }
                }
            }
        }
        return result;
    }

    function validateCurrentCalleeMatch() {
        //Is the selected target still targeted
        var callee = this._currentCallee.match;
        var matches = this._responseFactory._matches[this._currentCallee.targetedMessage.id];
        for (var i = 0; i < matches.length; i++) {
            var match = matches[i];
            if (match.id === callee.id && match.type === callee.type) {
                if (match !== callee) {
                    //This callee and match are equal but not the same object in memory because the matches have been refreshed. Refresh the _currentCallee.match as well.
                    this._currentCallee.match = match;
                }
                return true;
            }
        }
        return false;
    }

    function deliveryOptionsReady() {
        var result = validateCurrentCalleeMatch.call(this);
        if (result !== true) {
            return 'noDeliveryOptions';
        }

        result = reportDeliveryOptions.call(this);
        return result.hasRecipients === false ? 'noRecipients'
            : result.hasDeliveryOptions === false ? 'noDeliveryOptions'
            : true;
    }

    deliveryOptionsReady.blockingWaits = ['matching', 'fetchingOptions'];

    function recipientsReady() {
        var result = reportDeliveryOptions.call(this);
        if (result.hasRecipients === false) {
            return 'noRecipients';
        } else if (result.hasDeliveryOptions === false) {
            return 'noDeliveryOptions';
        }

        var readyGuard = this.getMessageReadyGuard(result.requiresSingleResponses === true && this._singleResponses.length !== result.phoneCount);
        
        if (readyGuard === 'noPhoneResponsesGiven')
            this.scrollToHeader.dispatch();

        return readyGuard;
    }

    recipientsReady.blockingWaits = ['matching', 'fetchingOptions'];

    function addressesReadyAndRecipientsMatched() {
        var incompleteGuards = this._userTracker.getIncompleteAddressGuards();
        return incompleteGuards.length > 0
            ? incompleteGuards[0]
            : true;
    }

    addressesReadyAndRecipientsMatched.blockingWaits = ['matching', 'fetchingOptions'];

    function addressesAndRecipientsReady() {
        var result = addressesReadyAndRecipientsMatched.call(this);
        if (result === true) {
            result = recipientsReady.call(this);
        }
        return result;
    }

    addressesAndRecipientsReady.blockingWaits = ['matching', 'fetchingOptions'];

    function addressesValidatedAndRecipientsReady() {
        var result = addressesReadyAndRecipientsMatched.call(this);
        return result;
    }

    addressesValidatedAndRecipientsReady.blockingWaits = ['validatingAddress', 'matching', 'fetchingOptions'];

    // for ARAN
    CampaignResponseApplication.addressesAndRecipientsReady = addressesAndRecipientsReady;

    function checkFieldsAndSaveUser(complete, fail) {
        VoterVoiceApplication.saveUser.call(this, complete, fail);
    }

    checkFieldsAndSaveUser.waitToken = 'saveUser';

    function checkFieldsAndSaveUserIfChanged(complete, fail) {
        if (this._userTracker.hasUserChanged()) {
            VoterVoiceApplication.saveUser.call(this, complete, fail);
        } else {
            complete();
        }
    }

    checkFieldsAndSaveUserIfChanged.waitToken = 'saveUser';

    function goBack(complete, fail) {
        this.goBack.dispatch();
    }

    function share(complete, fail) {
        this.share.dispatch();
    }

})();;
function VoterVoiceUserInterface(application, api, languageTranslator) {
    if (!(this instanceof arguments.callee))
        throw new Error("Constructor called as a function");

    this._application = application;
    this._api = api;
    this.displayUpdaters = [];
    this.guardMessages = {};
    this.modalDialogs = {};
    this._navigationOverrides = {};
    this._buttonTextTranslations = {};
    this._languageTranslator = languageTranslator || new VoterVoiceTranslator();

    try {
        this.modalOpened = new VoterVoiceApplication.Event();
    } catch (e) {
    }
}

(function (undefined) {
    var $;
    var onReady = function (callback) {
        if (arguments.callee.callbacks === undefined) {
            callback();
        } else if (arguments.length === 0) {
            for (var i = 0; i < arguments.callee.callbacks.length; i++) {
                arguments.callee.callbacks[i]();
            }
            delete arguments.callee.callbacks;
        } else {
            arguments.callee.callbacks.push(callback);
        }
    };
    onReady.callbacks = new Array();

    function BrowserNavigation(jQueryAddress) {
        if (!(this instanceof arguments.callee))
            throw new Error("Constructor called as a function");
        this._jQueryAddress = jQueryAddress;
    }

    BrowserNavigation.prototype = {
        _serializePage: function (hash, stateArg) {
            var page = hash;
            if (stateArg !== undefined) {
                if (typeof stateArg === 'string' || stateArg instanceof String) {
                    page += '-' + stateArg;
                } else {
                    page += '-' + JSON.stringify(stateArg);
                }
                
            }
            return page;
        },
        _deserializePage: function (page) {
            var hash, stateArg;
            if (page !== undefined) {
                if (page.indexOf('-') === -1) {
                    hash = page;
                } else {
                    hash = page.substring(0, page.indexOf('-'));
                    if (hash === 'null') {
                        hash = null;
                    }
                    stateArg = page.substring(page.indexOf('-') + 1);
                    if (stateArg.indexOf('%22') === 0) {
                        // Firefox and Safari seem to deliver "page" URL encoded (even when it isn't in the URL).
                        stateArg = decodeURIComponent(stateArg);
                    }
                    try {
                        stateArg = JSON.parse(stateArg);
                    }
                    catch (error) {
                        // allow scorecard bill ids to pass through
                        if (page.indexOf('-') < 0)
                            throw error.message;
                    }
                }
            }
            return {
                hash: hash,
                stateArg: stateArg
            };
        },
        getCurrentPage: function () {
            var page = this._deserializePage(this._jQueryAddress.parameter('page'));
            return page;
        },
        reflectStateChange: function (pageHash, stateArg) {
            if (pageHash !== undefined) {
                var lastPage = this.getCurrentPage();
                if (lastPage.hash !== pageHash || lastPage.stateArg != stateArg) {
                    if (this._browserNavigating !== true) {
                        this._jQueryAddress.parameter('page', this._serializePage(pageHash, stateArg));
                    }
                }
            }
        },
        onNavigated: function (callback) {
            var me = this;
            this._jQueryAddress.externalChange(function (event) {
                me._browserNavigating = true;
                var page = me._deserializePage(event.parameters.page);
                callback(page);
                delete me._browserNavigating;
            });
        }
    };

    function renderSection(selector, visibility) {
        if (visibility) {
            $(selector).show();
            if (visibility === 'full') {
                $(selector)
                    .css('float', 'none')
                    .css('margin-left', '0px')
                    .css('width', 'auto');
            }
        } else
            $(selector).hide();
    }

    function renderNavigation() {
        renderButton.call(this, '#backLink', this._shownState.transitions.back, 'back');
        renderButton.call(this, '#forwardLink', this._shownState.transitions.forward, 'forward');

        if ($('#backLink').is(':visible')) {
            $('#forwardLink').addClass('vv-action-last');
        } else {
            $('#forwardLink').removeClass('vv-action-last');
        }
    }

    function renderButton(selector, transition, transitionName) {
        var buttonTextKey;
        if (this._navigationOverrides[this._shownState.display.pageHash] !== undefined) {
            var override = this._navigationOverrides[this._shownState.display.pageHash][transitionName];
            if (override !== null) {
                buttonTextKey = override;
            }
        } else if (transition !== undefined && transition.name !== undefined) {
            buttonTextKey = transition.name;
        }
        if (transition !== undefined && transition.getCustomButtonText !== undefined) {
            $(selector).show();
            $(selector).val(transition.getCustomButtonText(this).submit);
        } else if (buttonTextKey !== undefined) {
            if (this._buttonTextTranslations[buttonTextKey] !== undefined) {
                buttonTextKey = this._buttonTextTranslations[buttonTextKey];
            }
            $(selector).show();
            $(selector).val(this.translateLanguage(buttonTextKey));
        } else {
            $(selector).hide();
        }
    }

    function renderWaitSpinner(showOrHide, selector) {
        if (arguments.length < 2) {
            selector = '#vvSpinner';
        }
        onReady(function () {
            $(selector).css('visibility', showOrHide ? 'visible' : 'hidden');
        });
    }

    function showState(state, previousState, getIdentity) {
        // automatically close dialog
        if (this._currentDialog !== undefined) {
            this._currentDialog.closingForTransition = true;
            this._currentDialog.dialog('close');
            delete this._currentDialog;
        }

        var display = state.display;
        var transitions = state.transitions === undefined
            ? {}
            : state.transitions;

        if (display.dialog === undefined) {
            for (var u = 0; u < this.displayUpdaters.length; u++) {
                var sectionVisibilities = this.displayUpdaters[u](display, transitions, getIdentity);
                for (var selector in sectionVisibilities) {
                    renderSection(selector, sectionVisibilities[selector]);
                }
            }

            if (display.pageHash !== undefined) {
                var comingBackFromModal = previousState !== undefined
                    && previousState.display !== undefined
                    && previousState.display.dialog !== undefined;

                if (comingBackFromModal === false && VoterVoiceUserInterface.doNotScrollToTop !== true) {
                    // mobile browsers do not go back to the top
                    window.scroll(0, 0);
                }
            }

            this._shownState = {
                display: display,
                transitions: transitions
            };
            renderNavigation.call(this);
        } else {
            this._currentDialog = this.modalDialogs[display.dialog]();
        }
    }

    function getAlertSelectedIndex() {
        var selectedIndex = $('input:radio[name=vvDialogChoice]:checked').val();
        if (selectedIndex === undefined) {
            selectedIndex = false;
        }
        return selectedIndex;
    }


    var loggerApi;
    VoterVoiceUserInterface.uiReady = function uiReady(jQuery) {
        $ = jQuery;
        try {
            onReady();
        } catch (e) {
            try {
                var logger = new VoterVoiceLogger(loggerApi);
                logger.logException(e);
            } catch (e2) {
            }
            throw e;
        }
    };
    VoterVoiceUserInterface._onReady = onReady;
    VoterVoiceUserInterface._jQuery = function () { return $; };

    function logErrors(behaviour, argArray) {
        try {
            // CANNOT use argArray=undefined in IE7
            if (arguments.length > 1) {
                return behaviour.apply(this, argArray);
            } else {
                return behaviour.call(this);
            }
        } catch (e) {
            try {
                new VoterVoiceLogger(this._api).logException(e);
            } catch (e2) {
            }
            throw e;
        }
    }

    VoterVoiceUserInterface.prototype = {
        extend: function (behaviours) {
            for (var b in behaviours) {
                this.attachBehaviour(b, behaviours[b]);
            }
        },
        attachBehaviour: function (b, behaviour) {
            this[b] = function () {
                return logErrors.call(this, behaviour, arguments);
            };
        },
        initialize: function () {
            loggerApi = this._api;

            // initialize browser history tracking
            var me = this;
            onReady(function () {
                if ($.datepicker !== undefined) {
                    $.datepicker.regional[""] = me._languageTranslator.GetDatePickerCultureSettings();
                    $.datepicker.setDefaults($.datepicker.regional[""]);
                }

                if ($.address !== undefined) {
                    var navigation = new BrowserNavigation($.address);
                    var currentPage = navigation.getCurrentPage();
                    if (currentPage.hash !== undefined) {
                        me._application.navigateByPageHash(currentPage.hash, currentPage.stateArg);
                    }
                    me._application.displayChanged.run(function (state, stateArg) {
                        navigation.reflectStateChange(state.display.pageHash, stateArg);
                    });
                    navigation.onNavigated(function (newPage) {
                        me._application.navigateByPageHash(newPage.hash, newPage.stateArg);
                    });
                }
            });

            function getGuardMessage(guardResult) {
                var result = guardResult.getResult();
                if (result === false) {
                    return null;
                } else if (result === null) {
                    me._api.logWarning(
                        "guardResult is null (transition: " + guardResult.transition.name + ", state: " + guardResult.transition.stateName + ")"
                    );
                }

                var tokenSource;
                var token;
                if (typeof result.action === 'function') {
                    tokenSource = "result.action.waitToken";
                    token = result.action.waitToken;
                } else {
                    tokenSource = "result";
                    token = result;
                }

                var message = me.guardMessages[token];
                if (message === undefined) {
                    message = null;
                    me._api.logWarning(
                        "Unknown transitionGuarded token: " + tokenSource + " = " + token + " (transition: " + guardResult.transition.name + ", state: " + guardResult.transition.stateName + ")"
                    );
                } else if (token.formatGuardMessage !== undefined) {
                    message = token.formatGuardMessage(message, me);
                } else if (message !== null && message.toString !== undefined) {
                    message = message.toString();
                }
                return message;
            }

            this._application.transitionGuarded.attach(function (guardResult) {
                if (guardResult.isInBackgroundWait() === true && me._pendingTransition === undefined) {
                    me._pendingTransition = guardResult.transition;
                    renderWaitSpinner(true);
                }

                var message = getGuardMessage(guardResult);
                if (message !== null) {
                    if (guardResult.activeBlock !== undefined) {
                        me.alert(message);
                    }
                }
            });
            this._application.promptingForInput.attach(function (inputRequest, uiResponseCallback) {
                var promptText = me.translateLanguage(inputRequest.userPromptKey);
                if (inputRequest.userPromptParameters !== undefined) {
                    for (var p = 0; p < inputRequest.userPromptParameters.length; p++) {
                        promptText = promptText.replace("{" + p + "}", inputRequest.userPromptParameters[p]);
                    }
                }
                var prompt = {
                    text: promptText,
                    buttons: inputRequest.userPromptButtons
                };
                me.confirm(prompt, function () {
                    inputRequest.userInput = true;
                }, function () {
                    inputRequest.userInput = false;
                }, function () {
                    uiResponseCallback();
                });
            });
            this._application.transitionFailed.attach(function (message, uiResponseCallback) {
                if (message !== undefined) {
                    me.alert(message, uiResponseCallback);
                } else {
                    uiResponseCallback();
                }
            });

            this._application.waitsChanged.run(function (guardResult) {
                onReady(function () {
                    $("#processingImage").toggle(guardResult.isWaiting());
                });

                if (guardResult.isInLongTransition()) {
                    renderWaitSpinner(true);
                } else if (me._pendingTransition === undefined) {
                    renderWaitSpinner(false);
                }
                if (guardResult.isWaiting() === false) {
                    if (me._pendingTransition !== undefined) {
                        var redoTransition = me._pendingTransition;
                        delete me._pendingTransition;
                        me._application.navigateByRedo(redoTransition);
                    }
                }
            });
            this._application.displayChanged.run(function (state, stateArg, previousState, getIdentity) {
                onReady(function () {
                    showState.call(me, state, previousState, getIdentity);
                });
            });

            this._application.states.final = {
                entry: function (enterState) {
                    if (this.finalRedirect !== undefined) {
                        window.location = this.finalRedirect;
                    }
                    enterState();
                }
            };
        },
        registerWaitSpinner: function (selector, options) {
            var waits = this._application._waits;
            this._application.waitActivitiesChanged.attach(function () {
                var show = false;
                for (var i = 0; i < waits.length; i++) {
                    if (waits[i] === options.waitActivity) {
                        show = true;
                        break;
                    }
                }
                onReady(function () {
                    $(selector).toggle(show);
                    if (options.hide !== undefined) {
                        $(options.hide).toggle(!show);
                    }
                });
            });
        },
        navigateForward: function navigateForward() {
            if (typeof vvuimobile !== 'undefined') {
                var forms = $('.vv-form').filter(':visible');
                var anyFormsInViewPort = false;
                if (forms.length > 0) {
                    for (var i = 0; i < forms.length; i++) {
                        var form = $(forms[i]);
                        var inViewport = form.visible(true);
                        if (inViewport) {
                            anyFormsInViewPort = true;
                            break;
                        }
                    }
                }

                if (forms.length === 0 || anyFormsInViewPort) {
                    logErrors.call(this, function () {
                        this._application.navigateByName('forward');
                    });
                } else {
                    var top;
                    var scrollTarget = $('.vv-form-scroll-target').filter(':visible');
                    if (scrollTarget.length > 0) {
                        top = scrollTarget.first().offset().top;
                    } else {
                        top = forms.first().offset().top;
                    }

                    $('html, body').animate({
                        scrollTop: top
                    },
                        2000);
                }

            } else {
                logErrors.call(this, function () {
                    this._application.navigateByName('forward');
                });
            }
        },
        navigateBack: function navigateBack() {
            logErrors.call(this, function () {
                this._application.navigateByName('back');
            });
        },
        navigateCancel: function navigateCancel() {
            logErrors.call(this, function () {
                this._application.navigateByName('cancel');
            });
        },
        navigateSpecial: function navigateSpecial(transitionName) {
            logErrors.call(this, function () {
                this._application.navigateByName(transitionName);
            });
        },
        openModalDialog: function (dialog, options) {
            var me = this;
            var jqoptions = {
                autoOpen: false,
                modal: true,
                // testing Neal's suggestion to remove drag/resize options
                // draggable: options.draggable,
                // resizable: options.resizable,
                draggable: false,
                resizable: false,
                width: options.width,
                open: function () {
                    //jquery ui dialog auto-focuses on first focusable element
                    //wrapped in timeout for android
                    setTimeout(function () {
                        $(':focus').blur();
                    }, 0);
                    $(".ui-dialog-titlebar-close", this.parentNode).empty().remove();
                    if (options.open !== undefined)
                        options.open();

                    if (me.modalOpened !== undefined) {
                        me.modalOpened.dispatch(dialog);
                    }
                },
                close: function () {
                    if (options.closeCallback !== undefined) {
                        options.closeCallback();
                    }

                    if (options.closeTransition !== undefined) {
                        me.closeModalDialog(dialog, options.closeTransition);
                    }

                    if (options.removeDialogContainer === undefined || options.removeDialogContainer === true) {
                        dialog.empty().remove();
                        dialog.parent().empty().remove();
                    }
                }
            };
            if (options.title !== undefined) {
                jqoptions.title = options.title;
            }
            if (options.maxWidth !== undefined) {
                jqoptions.maxWidth = options.maxWidth;
            }

            dialog.dialog(jqoptions);
            var uiParent = dialog.parent('.ui-dialog');
            $('body').append(uiParent);

            if (options.id !== undefined) {
                dialog.attr('id', options.id);
            }

            //uiParent.css({ left: $(window).width() / 2 - dialog.width() / 2 + 'px' });
            if (options.minWidth !== undefined) {
                var availableWidth = $(document).width() - 8;
                var minWidth = availableWidth >= options.minWidth
                    ? options.minWidth
                    : availableWidth;
                dialog.dialog({ minWidth: minWidth + 'px' });
            }

            dialog.dialog('open');

            //profile title bar uses the association color scheme instead of default jQuery title bar.
            if (options.title === undefined) {
                uiParent.find('.ui-dialog-title').html(dialog.find('header').html());
            } else {
                uiParent.find('.ui-dialog-title').html(options.title);
            }
            var titleBar = uiParent.find('.ui-dialog-titlebar');
            titleBar.addClass('vvDialogHeader');
            if (options.showCloseX === true) {
                // Default jQuery image for close button is replaced with a button that matches the association styling
                var closeButtonContainer = $('<div style="display: inline; position: absolute; right: -0.8em; top: 0em;"><input type="button" class="vv_button vvDialogCloseButton" value="&times;" /></div>');
                closeButtonContainer.find('input').click(function () {
                    dialog.dialog('close');
                });
                titleBar.append(closeButtonContainer);
            }

            //css class needs to be added AFTER opening for IE7 compatibility
            dialog.addClass('vvBody');
            uiParent.addClass('vvDialog');

            //check width after styles are applied
            if (options.maxWidth !== undefined) {
                if (options.maxWidth < dialog.dialog("widget").width()) {
                    dialog.dialog('option', 'width', options.maxWidth);
                    dialog.dialog('widget').width(options.maxWidth);
                    dialog.dialog("option", "position", "center");
                }
            }

            if (options.maxHeight === undefined) {

                if (dialog.parent().height() > $(window).height()) {

                    //positioning is performed after opening because position is not accurately set in options due to accurate vertical offsets require certain elements to be rendered
                    setDialogPosition(dialog);

                    //mobile or slow performant browser will require dialog to have rendered before being able to set position with correct vertical offset
                    window.setTimeout(function () { setDialogPosition(dialog); }, 80);
                }
            } else if (dialog.parent().height() > options.maxHeight) {
                dialog.dialog('option', 'height', options.maxHeight);
                dialog.dialog('widget').height(options.maxHeight);
                dialog.dialog("option", "position", "center");

            }

            setDialogPosition(dialog);

            function setDialogPosition(modalDialog) {
                modalDialog.parent().css({ top: ($(window).scrollTop() + 'px') });
                $('.vvDialog').focus();
            }
        },
        closeModalDialog: function closeModalDialog(dialog, transitionName) {
            logErrors.call(this, function () {
                if (dialog.closingForTransition !== true) {
                    this._application.navigateByName(transitionName);
                }
                dialog.empty().remove();
            });
        },
        openPrivacyPolicy: function (url) {
            if (typeof vvuimobile !== 'undefined') {
                url = '/Mobile' + url;
            } else {
                url = '/iframes' + url;
            }

            var me = this;
            var dialog = $('<div></div>');
            dialog.load(url, null, function () {
                dialog.find('section > header').first().hide();
                me.openModalDialog(dialog,
                    {
                        draggable: true,
                        width: $('.vvBody').first().width() - 10 + 'px',
                        maxWidth: 600,
                        maxHeight: $(window).height() - 50,
                        showCloseX: true
                    });
            });
        },
        overrideNavigation: function (statePageHash, overrides) {
            if (overrides === undefined) {
                delete this._navigationOverrides[statePageHash];
            } else {
                this._navigationOverrides[statePageHash] = overrides;
            }
            renderNavigation.call(this);
        },
        translateButtonText: function (originalKey, translationKey) {
            this._buttonTextTranslations[originalKey] = translationKey;
            var button = $('#forwardLink');

            var originalTranslatedButtonText = this.translateLanguage(originalKey);
            if (button.val() === originalTranslatedButtonText) {
                button.val(this.translateLanguage(translationKey));
            }
        },
        translateLanguage: function (key) {
            if (this._languageTranslator !== undefined) {
                return this._languageTranslator.GetTextForCulture(key);
            }
            return "Error: Translate object is null";
        },
        formatDate: function (dateTime) {
            return dateTime.format(this.translateLanguage('JavascriptDateFormat'));
        },
        formatTime: function (dateTime) {
            var hour = this.translateLanguage('JavascriptTimeHourFormat') !== '' ? dateTime.format(this.translateLanguage('JavascriptTimeHourFormat')) : '';
            var separator = this.translateLanguage('JavascriptTimeSeparatorFormat');
            var minute = this.translateLanguage('JavascriptTimeMinuteFormat') !== ' ' ? dateTime.format(this.translateLanguage('JavascriptTimeMinuteFormat')) : '';
            var period = this.translateLanguage('JavascriptTimePeriodFormat') !== '' ? dateTime.format(this.translateLanguage('JavascriptTimePeriodFormat')) : '';

            if (minute === '0') {
                minute = '';
            }

            return $.trim(hour + separator + minute + period);
        },
        formatTimeFrom24Hour: function (hour, minute) {
            if (this._languageTranslator !== undefined) {
                return this._languageTranslator.FormatTimeFrom24Hour(hour, minute);
            }
            return "Error: Translate object is null";
        },
        showNotificationModal: function (template, modalId, prompt, closingCallback, closeCallback, buttonCallbacks, choices) {
            var formattedText = prompt.text.replace(/\r\n/g, '<br />');
            var listItems = '';
            if (arguments.length === 7) {
                for (var c = 0; c < choices.length; c++) {
                    listItems += '<div style="text-align: left;"><label><input type="radio" name="vvDialogChoice" value="' + c + '" display="' + choices[c] + '" onclick="javascript:vvui.selectDialogChoice(this);" />' + choices[c] + '</label></div>';
                }
                formattedText = '<p>' + formattedText + '</p>';
            }
            var dialog = template.clone(true).removeAttr("id");
            dialog.find('.vvNotification').html(formattedText);
            $('#notificationWidth').html(formattedText);

            function bindButton(dialog, buttonTexts, buttonCallbacks, i) {
                var button = dialog.find('.vvNotificationButton').eq(i);
                button.val(buttonTexts[i]);
                button.click(function () {
                    if (buttonCallbacks[i] !== undefined) {
                        buttonCallbacks[i]();
                    }
                    dialog.dialog('close');
                });
            }

            var buttonTexts = prompt.buttons === 'Yes|No'
                ? [this.translateLanguage('ButtonTextYes'), this.translateLanguage('ButtonTextNo')]
                : prompt.buttons === 'Same|Different'
                    ? [this.translateLanguage('ButtonTextSamePerson'), this.translateLanguage('ButtonTextDifferentPeople')]
                    : prompt.buttons === 'ViewMap|Cancel'
                        ? [this.translateLanguage('MapExplanation'), this.translateLanguage('ButtonTextCancel')]
                        : [this.translateLanguage('ButtonTextOkay'), this.translateLanguage('ButtonTextCancel')];

            for (var i = 0; i < buttonCallbacks.length; i++) {
                bindButton(dialog, buttonTexts, buttonCallbacks, i);
            }

            var closingResult;
            dialog.dialog({
                autoOpen: false,
                modal: true,
                draggable: true,
                open: function () {
                    $(".ui-dialog-titlebar", this.parentNode).empty().remove();
                    //jquery ui dialog auto-focuses on first focusable element
                    //wrapped in timeout for android
                    setTimeout(function () {
                        $(':focus').blur();
                    }, 0);
                },
                beforeClose: function () {
                    if (closingCallback !== undefined) {
                        closingResult = closingCallback();
                    }
                    return closingResult;
                },
                close: function () {
                    if (closeCallback !== undefined) {
                        closeCallback(closingResult);
                    }
                    dialog.empty().remove();
                    dialog.parent().empty().remove();
                }
            });

            dialog.attr('id', modalId);
            dialog.dialog("option", "zIndex", 9999999);

            $('body').append(dialog.parent('.ui-dialog'));

            var vvBodyContainerWidth = $('.vvBody').width();
            //there are some wrapper pages where the vvBody is narrow, so an alternate width is compared against
            var minimumWidthBound = $(window).width() / 2;
            var width = vvBodyContainerWidth > minimumWidthBound ? vvBodyContainerWidth : minimumWidthBound;

            var maximumWidthBound = $(window).width() - 20;
            dialog.parent().css({ minWidth: (width < maximumWidthBound ? width : maximumWidthBound) + 'px' });

            //for notifications that can fit in one line within the bounds of the window, format it like
            //a javascript alert where the text is centered
            var notificationWidth = $('#notificationWidth').width() + 50;
            if (notificationWidth < dialog.parent().width()) {
                dialog.parent().css({ minWidth: notificationWidth + 'px' });
                dialog.css({ textAlign: 'center' });
            }

            dialog.dialog('open');
            dialog.addClass('vvBody');
            // append this after display, so radio buttons don't get duplicated
            if (listItems !== '') {
                dialog.find('.vvNotification').append(listItems);
            }

            //adjustment to place modal at top of browser window instead of off-screen if height too large
            if (dialog.parent().height() > $(window).height()) {
                //positioning is performed after opening because position is not accurately set in options due to accurate vertical offsets require certain elements to be rendered
                setDialogPosition(dialog);

                //mobile or slow performant browser will require dialog to have rendered before being able to set position with correct vertical offset
                window.setTimeout(function () { setDialogPosition(dialog); }, 80);
            }

            function setDialogPosition(modalDialog) {
                modalDialog.parent().css({ top: ($(window).scrollTop() + 'px') });
            }
        },
        alert: function (text, closeCallback) {
            var prompt = {
                text: text
            };
            this.showNotificationModal($('#alertTemplate'), 'vvAlert', prompt, undefined, closeCallback, [undefined]);
        },
        choose: function (text, choices, closeCallback) {
            var prompt = {
                text: text
            };
            this.showNotificationModal($('#alertTemplate'), 'vvAlert', prompt, getAlertSelectedIndex, closeCallback, [undefined], choices);
        },
        confirm: function (prompt, continueCallback, cancelCallback, closeCallback) {
            if (prompt.text === undefined) {
                prompt = {
                    text: prompt
                };
            }
            this.showNotificationModal($('#confirmTemplate'), 'vvConfirm', prompt, undefined, closeCallback, [continueCallback, cancelCallback]);
        },
        selectDialogChoice: function (radio) {
            // FireFox is immediately unchecking the radio button (for some reason)
            setTimeout(function () { $(radio).prop('checked', true); }, 1);
        },
        applyDatepicker: function (input) {
            input.datepicker({
                changeMonth: true,
                changeYear: true,
                showOtherMonths: true,
                selectOtherMonths: true
            });
        },
        defaultLinksToNewWindow: function (jQuery) {
            this.appendDefaultAttributeValue(jQuery.find('a'), 'target', '_blank');
        },
        appendDefaultAttributeValue: function (selector, attributeName, defaultValue) {
            var ui = $(selector);
            for (var i = 0; i < ui.length; i++) {
                var element = $(ui[i]);
                var attributeValue = element.attr(attributeName) === null || element.attr(attributeName) === undefined ? '' : $.trim(element.attr(attributeName));
                if (attributeValue === '') {
                    element.attr(attributeName, defaultValue);
                }
            }

            return ui;
        },
        emitContentLoadedEvent: function () {
            const event = document.createEvent('Event');
            event.initEvent('vvContentLoaded', true, true);
            document.dispatchEvent(event);
        }
    };

    //TODO: go through and change all the alert calls instead.  Note that every context of alert() may not visiblity to vvui
    var originalAlert = window.alert;
    window.alert = function (text) {
        if (typeof $ !== 'undefined' && typeof vvui !== 'undefined' && vvui.alert !== undefined) {
            vvui.alert(text);
        } else {
            originalAlert(text);
        }
    };
})();;
(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });
    var initCoordinates;
    var markersArray = [];
    var map;
    var initializeGoogleMap = function(mapDiv, addressCoordinates, application, vvuiLocal) {
        var coordinates = new google.maps.LatLng(addressCoordinates.latitude, addressCoordinates.longitude);
        application.adjustedCoordinates = coordinates;
        initCoordinates = coordinates;
        var mapOptions = {
            center: coordinates,
            zoom: 14,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        map = new google.maps.Map(mapDiv, mapOptions);
        google.maps.event.addListener(map, "click", function (event) {
            enableMapSubmitButton();

            placeMarker(event.latLng, map, application, vvuiLocal);
            application.adjustedCoordinates = event.latLng;
        });
        placeMarker(coordinates, map, application, vvuiLocal);
    };

    function placeMarker(location, map, application, vvuiLocal) {
        deletePreviousMarkers();
        var marker = new google.maps.Marker({
            position: location,
            map: map,
            draggable: true,
            title: vvuiLocal.translateLanguage('MapPickMeUp')
        });
        google.maps.event.addListener(marker, "dragend", function(event) {
            enableMapSubmitButton();

            application.adjustedCoordinates = event.latLng;
        });
        markersArray.push(marker);
    }

    function deletePreviousMarkers() {
        if (markersArray) {
            for (i in markersArray) {
                markersArray[i].setMap(null);
            }
            markersArray.length = 0;
        }
    }

    function enableMapSubmitButton() {
        var dialog = $('#coordinatesAdjustmentDialog');

        var submitButton = dialog.find('nav input').eq(0);
        submitButton.prop('disabled', false);
        submitButton.css('opacity', '');
        submitButton.css('cursor', '');
    }

    function Address(properties) {
        if (!(this instanceof arguments.callee))
            throw new Error("Constructor called as a function");
        for (var p in properties) {
            this[p] = properties[p];
        }
    }

    Address.prototype = {
        isEmpty: function isEmpty() {
            return !this.streetAddress
                && !this.city
                && !this.state
                && !this.zipCode;
        },
        isComplete: function isComplete() {
            if (this.country !== 'US' && this.country !== 'CA') {
                return !!(this.streetAddress && this.city && this.zipCode);
            }
            return !!(this.streetAddress && this.city && this.state && this.zipCode);
        },
        createFormattedGuard: function createFormattedGuard(guardToken) {
            var me = this;
            return {
                toString: function() { return guardToken; },
                formatGuardMessage: function(message, vvuiLocal) {
                    message += '<ul style="text-align: left;">';
                    if (!me.streetAddress)
                        message += '<li>' + vvuiLocal.translateLanguage('UserStreetAddress') + '</li>';
                    if (me.country !== 'US' && me.country !== '' && !me.city)
                        message += '<li>' + vvuiLocal.translateLanguage('UserCity') + '</li>';
                    if (!me.state && me.country === 'CA')
                        message += '<li>' + vvuiLocal.translateLanguage('UserState') + '</li>';
                    if (!me.zipCode)
                        message += '<li>' + vvuiLocal.translateLanguage('UserZip') + '</li>';
                    message += '</ul>';
                    return message;
                }
            };
        }
    };

    function AddressInterface(addressIdPrefix, type, defaultCountry) {
        if (!(this instanceof arguments.callee))
            throw new Error("Constructor called as a function");
        this.prefix = '#' + addressIdPrefix.replace('{0}', type);
        this._defaultCountry = defaultCountry;
    }

    AddressInterface.prototype = {
        createAddress: function() {
            var result = new Address({
                streetAddress: $.trim($(this.prefix + 'StreetAddress').val()),
                city: $.trim($(this.prefix + 'City').val()),
                county: $(this.prefix + 'County').val(),
                state: $(this.prefix + 'State').val(),
                zipCode: $.trim($(this.prefix + 'ZipCode').val()),
                zipCodeExtension: $(this.prefix + 'ZipCodeExtension').val(),
                country: $(this.prefix + 'Country').val(),
                checksum: $(this.prefix + 'Checksum').val(),
                coordinates: this.createCoordinates()
            });
            if (!result.streetAddress && !result.city && !result.county && !result.state
                && !result.zipCode && !result.zipCodeExtension && !result.checksum) {
                result.country = '';
            }

            if (result.country === 'US' && result.streetAddress === '' && result.zipCode === '') {
                result.city = '';
                result.state = '';
            }

            handleCityStateDisplay.call(this, result);

            return result;
        },
        createCoordinates: function () {
            var result = null;
            if ($(this.prefix + 'Coordinates_Latitude').val() && $(this.prefix + 'Coordinates_Latitude').val() !== '' &&
                $(this.prefix + 'Coordinates_Longitude').val() && $(this.prefix + 'Coordinates_Longitude').val() !== '') {
                result = {
                    latitude: $.trim($(this.prefix + 'Coordinates_Latitude').val()),
                    longitude: $.trim($(this.prefix + 'Coordinates_Longitude').val()),
                    accuracyStatCode: $(this.prefix + 'Coordinates_AccuracyStatCode').val() || null
                };
            }

            return result;
        },
        latestGoodAddress: function() {
            if (this._pendingAddress !== undefined) {
                return new Address(this._pendingAddress);
            }
            return this.createAddress();
        },
        populateAddress: function(address) {
            this.cancelRecorrection();

            if (address === undefined || address === null)
                address = {};
            this.reflectCountry(address.country || this._defaultCountry);
            $(this.prefix + 'StreetAddress').val(address.streetAddress);
            $(this.prefix + 'City').val(address.city);
            var originalState = $(this.prefix + 'State').val();
            $(this.prefix + 'State').val(address.state);
            $(this.prefix + 'County').val(address.county);
            $(this.prefix + 'ZipCode').val(address.zipCode);
            $(this.prefix + 'ZipCodeExtension').val(address.zipCodeExtension);
            $(this.prefix + 'Country').val(address.country || $(this.prefix + 'Country').val() || this._defaultCountry);
            $(this.prefix + 'Checksum').val(address.checksum);
            $(this.prefix + 'AlternateCities').val(JSON.stringify(address.alternateCities));

            this.setCoordinates(address);
            
            var homeAddressZipCodeLabel = $('label[for="UserModel_HomeAddress_ZipCode"]');

            if ($(this.prefix + 'Country').val() !== 'US') {
                if (homeAddressZipCodeLabel.text() === 'ZIP Code') {
                    homeAddressZipCodeLabel.text('Postal Code');
                }

                showCityState(this.prefix);
            } else {
                if (homeAddressZipCodeLabel.text() === 'Postal Code') {
                    homeAddressZipCodeLabel.text('ZIP Code');
                }

                disableCityState(this.prefix);

                if (address.zipCode && address.city && address.state) {
                    showCityState(this.prefix);
                }
            }

            // voter registration app
            if (address.state !== undefined && address.state !== null && originalState !== address.state) {
                $(this.prefix + 'State').trigger('change');
            }
        },
        setCoordinates: function(address) {
            if (address.coordinates) {
                $(this.prefix + 'Coordinates_Latitude').val(address.coordinates.latitude);
                $(this.prefix + 'Coordinates_Longitude').val(address.coordinates.longitude);
                $(this.prefix + 'Coordinates_AccuracyStatCode').val(address.coordinates.accuracyStatCode);
            } else {
                $(this.prefix + 'Coordinates_Latitude').val('');
                $(this.prefix + 'Coordinates_Longitude').val('');
                $(this.prefix + 'Coordinates_AccuracyStatCode').val('');
            }
        },
        setCityStateInitialVisibility: function(address) {
            if ($(this.prefix + 'Country').val() === 'US' && !address) {
                hideCityState(this.prefix);
            }
        },
        getVisualCountry: function() {
            var result = $([]);
            if ($(this.prefix + 'Country').is('select') === true) {
                result = $(this.prefix + 'Country');
            }
            return result;
        },
        getVisualElements: function() {
            var result = $(this.prefix + 'StreetAddress')
                .add(this.prefix + 'City')
                .add(this.prefix + 'State')
                .add(this.prefix + 'ZipCode');
            if ($(this.prefix + 'Country').is('select') === true) {
                result = result.add(this.prefix + 'Country');
            }
            return result;
        },
        reflectCountry: function(country) {
            if (this.getVisualCountry().length === 0) {
                return;
            }

            function copyEvents(jqSource, jqDestination) {
                var events = $._data(jqSource.get(0), 'events');
                if (events) {
                    $.each(events, function() {
                        $.each(this, function() {
                            jqDestination.bind(this.type, this.handler);
                        });
                    });
                }
            }

            var currentStateUi = $(this.prefix + 'State');
            var newStateUi = $(this.prefix + 'AllStates_' + country + '_');
            var hasStateDropdownButNoAddress2 = newStateUi.length !== 0;

            if (hasStateDropdownButNoAddress2) {
                newStateUi = newStateUi.clone();
            } else if (currentStateUi.is('select') === true) {
                var placeholder = vvui.translateLanguage('UserState');
                newStateUi = $('<input type="text" placeholder="' + placeholder + '" />');
            }
            if (newStateUi.length !== 0) {
                newStateUi.attr('id', (this.prefix + 'State').replace(/^#/g, ''));
                copyEvents(currentStateUi, newStateUi);
                currentStateUi.replaceWith(newStateUi);
                newStateUi.placeholder();
            }
        },
        matchesLastAddress: function(address) {
            if (!address)
                return false;
            return address.checksum === $(this.prefix + 'Checksum').val();
        },
        hasFocus: function() {
            return this.getVisualElements().is(":focus");
        },
        holdRecorrection: function(address) {
            this._pendingAddress = address;
            $('*').not(this.getVisualElements()).off('focus', populateAddressOnBlur);
            $('*').not(this.getVisualElements()).on('focus', null, this, populateAddressOnBlur);
        },
        applyRecorrection: function() {
            var address = this._pendingAddress;
            this.populateAddress(address);
        },
        cancelRecorrection: function() {
            delete this._pendingAddress;
            $('*').not(this.getVisualElements()).off('focus', populateAddressOnBlur);
        }
    };

    function handleCityStateDisplay(address) {
        var me = this;

        if ($(me.prefix + 'Country').val() === 'US') {
            disableCityState(me.prefix);
        }
        else if ($(me.prefix + 'Country').val() !== 'US') {
            showCityState(me.prefix);
            enableCityState(me.prefix);
        }

        if ($(me.prefix + 'Country').val() === 'US' && address.streetAddress === '' && address.zipCode === '') {
            hideCityState(me.prefix);
        }
    }

    function showCityState(prefix) {
        $(prefix + 'Zip_Instructions_Div').hide();
        $(prefix + 'City_Div').show();
        $(prefix + 'State_Div').show();
    }

    function hideCityState(prefix) {
        $(prefix + 'City_Div').hide();
        $(prefix + 'State_Div').hide();
        $(prefix + 'Zip_Instructions_Div').show();
    }

    function disableCityState(prefix) {
        function getAlternateCities() {
            return typeof $(prefix + 'AlternateCities').val() === 'string' && $(prefix + 'AlternateCities').val().length > 0
                                        ? JSON.parse($(prefix + 'AlternateCities').val())
                                        : null;
        }

        var alternateCities = getAlternateCities();

        if (!alternateCities || alternateCities.length === 0) {
            $(prefix + 'City').prop('readonly', 'readonly');
            $(prefix + 'City').prop('tabindex', '-1');
            $(prefix + 'City').css('border', 'none');

            if ($(prefix + 'City').data('autocomplete')) {
                $(prefix + 'City').autocomplete("destroy");
                $(prefix + 'City').removeData('autocomplete');
            }
        } else {
            $(prefix + 'City').removeProp('readonly');
            $(prefix + 'City').removeProp('tabindex');
            $(prefix + 'City').css('border', '1px solid #CCCCCC');
            
            $(prefix + 'City').autocomplete({
                minLength: 0,
                delay: 0,
                autoFocus: true,
                source: alternateCities.map(function (a) { return a.Key; }),
                change: function (event, ui) {
                    if (!ui.item) {
                        var cities = getAlternateCities();

                        if (cities && cities.length > 1) {
                            var preferredCity = cities.filter(function (city) {
                                return city.Value === true;
                            });

                            $(prefix + 'City').val(preferredCity[0].Key);
                        }
                    }
                }
            });
        }
        
        $(prefix + 'State').prop('disabled', true);
        $(prefix + 'State').addClass('disabledSelect');
    }

    function enableCityState(prefix) {
        $(prefix + 'City').removeProp('readonly');
        $(prefix + 'City').removeProp('tabindex');
        $(prefix + 'City').css('border', '1px solid #CCCCCC');
        $(prefix + 'State').prop('disabled', false);
        $(prefix + 'State').removeClass('disabledSelect');
    }

    function populateAddressOnBlur(event) {
        var me = event.data;
        me.applyRecorrection();
    }

    function createMapDialog() {
        var me = this;
        var request = this._application.coordinateRequest;
        var dialog = $('#coordinatesDialogTemplate').clone(true).removeAttr('id');
        {
            dialog.find('header').eq(0).html(this.translateLanguage('MapTitle'));
            var sections = dialog.find('section');
            var addressName = request.type === 'Home' ? this.translateLanguage('UserHomeAddress') : this.translateLanguage('UserBusinessAddressName');
            sections.eq(0).find('p').eq(0).html(this.translateLanguage('MapMessage').replace('{0}', addressName.toLowerCase()) + ' ' + this.translateLanguage('MapWeakMatchInstructions'));
            dialog.find('nav input').eq(0).click(function () {
                me.navigateForward();
            });
            dialog.find('nav input').eq(1).click(function() {
                dialog.dialog('close');
            });

            // Remove class as it may be added and align improperly. See Account.User.js
            dialog.find('nav').removeClass('vvNavigationRight');

            // Disable Submit initially.
            var submitButton = dialog.find('nav input').eq(0);
            submitButton.prop('disabled', true);
            submitButton.css('opacity', '0.65');
            submitButton.css('cursor', 'not-allowed');
        }

        var mapDivs = dialog.find('.vv_GoogleMap');
        
        initializeGoogleMap(mapDivs[0], request.address.coordinates, this._application, this);
        return dialog;
    }

    function createGeocodeVendorMapDialog() {
        var dialog = $('#geocodedAddressDialogTemplate').clone(true).removeAttr('id');
        dialog.find('header').eq(0).html(this.translateLanguage('MapDialogHeader'));
        var iframe = dialog.find('.vendorMapImage');
        iframe.attr('src', this._application.vendorGeocodeMapUrl);
        return dialog;
    }

    VoterVoiceUserInterface.prototype.extend({
        initializeAddresses: function(addressUi, defaultCountry) {
            this.guardMessages.invalidAddress = this.translateLanguage('MessageInvalidAddress');
            this.guardMessages.validatingAddress = this.translateLanguage('MessageCheckingAddress');
            this.guardMessages.incompleteHomeAddress = this.translateLanguage('IncompleteHomeAddress');
            this.guardMessages.incompleteBusinessAddress = this.translateLanguage('IncompleteBusinessAddress');

            var me = this;

            this.modalDialogs.coordinatesDialog = function() {
                var dialog = createMapDialog.call(me);
                dialog.find('header').eq(0).hide();
                me.openModalDialog(dialog, {
                    draggable: true,
                    closeTransition: 'closeDialog',
                    width: $('.vvBody').first().width() - 10 + 'px',
                    id: 'coordinatesAdjustmentDialog',
                    maxWidth: 800,
                    showCloseX: true,
                    open: function(event, ui) {
                        google.maps.event.trigger(map, 'resize');
                        map.setCenter(initCoordinates);
                    }
                });
                return dialog;
            };
            this.modalDialogs.geocodedAddressDialog = function() {
                var dialog = createGeocodeVendorMapDialog.call(me);
                dialog.find('header').eq(0).hide();
                me.openModalDialog(dialog, {
                    draggable: true,
                    closeTransition: 'closeDialog',
                    width: $('.vvBody').first().width() - 10 + 'px',
                    id: 'vendorMapDialog',
                    maxWidth: 450,
                    showCloseX: true
                });
                return dialog;
            };

            var uis = {};

            function getInterface(type) {
                if (uis[type] === undefined) {
                    if (addressUi.bindUserAddresses === true && (type === 'Home' || type === 'Business')) {
                        uis[type] = new AddressInterface('UserModel_{0}Address_', type, defaultCountry);
                    } else if (addressUi.addressIdPrefix !== undefined) {
                        uis[type] = new AddressInterface(addressUi.addressIdPrefix, type, defaultCountry);
                    } else {
                        uis[type] = new AddressInterface('UserModel_{0}Address_', type, defaultCountry);
                    }
                }
                return uis[type];
            }

            this.buildAddress = function(type) {
                var ui = getInterface(type);
                return ui.latestGoodAddress();
            };

            this.setAddress = function(type, address) {
                getInterface(type).populateAddress(address);
                getInterface(type).setCityStateInitialVisibility(address);
            };

            function overrideWeakCoordinates(type, address, adjustedCoordinates) {
                var ui = getInterface(type);
                if (!ui.matchesLastAddress(address) || !ui.hasFocus()) {
                    if (address != null) {
                        address.coordinates.latitude = adjustedCoordinates.lat();
                        address.coordinates.longitude = adjustedCoordinates.lng();
                        address.coordinates.accuracyStatCode = 'C';
                        address.coordinates.isWeakCoordinates = false; // Set to false so doesn't trigger message

                        ui.setCoordinates(address);
                        me.displayAddressValidation(type, address, null);
                    }
                }
            }

            function showAddressSuccess(type, address, mapUrl) {
                var ui = getInterface(type);
                if (!ui.matchesLastAddress(address) || !ui.hasFocus()) {
                    me.displayAddressValidation(type, address, mapUrl);
                    ui.populateAddress(address);
                } else {
                    ui.holdRecorrection(address);
                }
            }

            function showAddressFailure(type, message) {
                getInterface(type).cancelRecorrection();

                $('#addressValidation' + type).css('color', 'red');
                var iconSize = parseInt($('#addressValidation' + type).css('font-size'), 10);
                $('#addressValidation' + type).html(message.replace(/\{0\}/g, iconSize));
            }

            function showAddressError(type, message) {
                getInterface(type).cancelRecorrection();

                $('#addressValidation' + type).css('color', 'red');
                $('#addressValidation' + type).html('Address error');
                me.alert(message);
            }

            this.bindAddressChanges = function(type) {
                getInterface(type).getVisualCountry().change(function(event) {
                    var country = $(event.currentTarget).val();
                    getInterface(type).reflectCountry(country);
                });
                var application = this._application;
                var callback = function () {
                    var element = $(this);
                    if (element.is(':disabled') || element.is('[readonly]'))
                        return;

                    var address = getInterface(type).createAddress();
                    application.addressChanged(type, address);
                };
                getInterface(type).getVisualElements().change(callback);
                this._application.ensuringAddressCorrections.attach(function() {
                    var address = getInterface(type).createAddress();
                    var differences = application.getDifferencesFromCorrected(type, address);
                    if (differences.length > 1
                        || (differences.length === 1 && (differences[0] !== 'state' || address.state !== ""))) {
                        try {
                            me._api.postLogEntry({
                                severity: 'information',
                                message: "FORCING " + type + " ADDRESS CHANGED; differences: " + differences
                            });
                        } catch (e) {
                        }
                        application.addressChanged(type, address);
                    } else if (address.country === 'US' && address.county === '') {
                        switch (address.state) {
                        case "AA":
                        case "AE":
                        case "AP":
                            break; // "states" with no counties
                        default:
                            application.addressChanged(type, address);
                        }
                    }
                });
            };

            if (this._application.userCleared) {
                this._application.userCleared.attach(function () {
                    me.displayAddressValidation('Home', null, null, null);
                    me.displayAddressValidation('Business', null, null, null);
                });
            }

            this.forceAddressValidation = function (type) {
                var address = getInterface(type).createAddress();
                if (me.isValidAddress(address))
                    me.displayAddressValidation(type, address, null, true);
            };

            this._application.ui.showAddressSuccess = showAddressSuccess;
            this._application.ui.showAddressFailure = showAddressFailure;
            this._application.ui.showAddressError = showAddressError;
            this._application.ui.overrideWeakCoordinates = overrideWeakCoordinates;
        },
        displayAddressValidation: function(type, address, mapUrl, hideMessage) {
            var me = this;
            if (address != null) {
                var mapLink = null;
                if (address.coordinates) {
                    initCoordinates = new google.maps.LatLng(address.coordinates.latitude, address.coordinates.longitude);
                    mapLink = $('<span class="fas fa-map-marker-alt vv-address-map-icon"></span>')
                        .click(function () {
                            me._application.coordinateRequest = {
                                type: type,
                                address: address
                            };
                            me.navigateSpecial('adjustCoordinates');
                        });
                }

                if (me.isValidAddress(address) && address.coordinates && address.coordinates.isWeakCoordinates) {
                    if (address.country === 'US') {
                        vvui.confirm({
                            text: this.translateLanguage('MapWeakMatchMessage'),
                            buttons: 'ViewMap|Cancel'
                        },
                        function() {
                            mapLink.trigger('click');
                        });
                        $('#addressValidation' + type).html('');
                    } else {
                        $('#addressValidation' + type).css('color', 'green');
                        $('#addressValidation' + type).html(this.translateLanguage('MapWeakMatchTitle'));
                    }
                } else if (!me.isValidAddress(address) && address.coordinates && address.coordinates.isWeakCoordinates) {
                    mapLink = null;
                    $('#addressValidation' + type).html('');
                } else if (!hideMessage) {
                    $('#addressValidation' + type).css('color', 'green');
                    $('#addressValidation' + type).html(this.translateLanguage('MessageAddressOkay'));
                }
                
                if (mapLink !== null) {
                    if ($('#addressValidation' + type).html() && $('#addressValidation' + type).html().length > 0)
                        mapLink.css('padding-left', '5px');

                    var icon = mapLink.find('img');
                    var iconSize = parseInt($('#addressValidation' + type).css('font-size'), 10);
                    icon.css('height', iconSize);
                    icon.css('width', iconSize);

                    $('#addressValidation' + type).append(mapLink);
                }
            } else {
                $('#addressValidation' + type).html(''); 
            }
        },
        isValidAddress: function(address) {
            return address.streetAddress
                && address.city
                && address.state
                && address.zipCode;
        }
    });
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
/*!
 * jQuery Cookie Plugin
 * https://github.com/carhartl/jquery-cookie
 *
 * Copyright 2011, Klaus Hartl
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.opensource.org/licenses/GPL-2.0
 */
(function($) {
    $.cookie = function(key, value, options) {

        // key and at least value given, set cookie...
        if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
            options = $.extend({}, options);

            if (value === null || value === undefined) {
                options.expires = -1;
            }

            if (typeof options.expires === 'number') {
                var days = options.expires, t = options.expires = new Date();
                t.setDate(t.getDate() + days);
            }

            value = String(value);

            return (document.cookie = [
                encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
                options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
                options.path    ? '; path=' + options.path : '',
                options.domain  ? '; domain=' + options.domain : '',
                options.secure ? '; secure' : '',
                // need to explicitly set third party cookies to samesite: None AND secure
                // https://www.chromestatus.com/feature/5088147346030592
                // https://www.chromestatus.com/feature/5633521622188032
                options.sameSite ? '; samesite=' + options.sameSite : ''
            ].join(''));
        }

        // key and possibly options given, get cookie...
        options = value || {};
        var decode = options.raw ? function(s) { return s; } : decodeURIComponent;

        var pairs = document.cookie.split('; ');
        for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
            if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
        }
        return null;
    };
})(jQuery);
;
(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    function renderLoginLink(selector, transition, getIdentity) {
        $(selector).hide();
        if (transition !== undefined) {
            var identity = getIdentity();
            if (identity.userToken) {
                var link = vvui.translateLanguage('Not') + ' ' + identity.givenNames + "?";
                $(selector).html('<a id="loginLink" onclick="vvui.navigateLogin();">' + link + '</a>');
                $(selector).show();
            }
        }
    }

    VoterVoiceUserInterface.prototype.extend({
        initializeUserDisplay: function (hideLoginLink) {
            var application = this._application;
            this.displayUpdaters.push(function (display, transitions, getIdentity) {
                var showRememberedBlurb = display.showRememberedBlurb;
                var showRememberedSummary = display.showRememberedSummary;
                var showUser = display.showUser;
                var showGreeting = false;
                if (application.showRememberedBlurb !== undefined) {
                    showRememberedBlurb = application.showRememberedBlurb();
                }

                if (showUser !== undefined && hideLoginLink !== true) {
                    renderLoginLink('#loginLinkContainer', transitions.login, getIdentity);
                } else {
                    if (showRememberedBlurb === true) {
                        //$('#vvRememberedUserSection').find('span').html(getIdentity().givenNames);
                        renderLoginLink('#loginLinkContainer', transitions.login, getIdentity);
                    }
                }

               // $('.vvNavigation').toggleClass('vvNavigationRight', display.showLogin === true || showUser === true);

                var showTempUserSections = showUser && (!showRememberedSummary || showUser === 'detailed');
                var identity = getIdentity();
                if (showUser !== undefined && showRememberedSummary === true) {
                    if (identity.userId === undefined) {
                        showRememberedSummary = false;
                        showTempUserSections = true;
                    } else {
                        if ($('#stepUserInformation3').attr('editing') === 'true') {
                            showTempUserSections = true;
                        }
                    }
                }

                if (identity !== undefined && identity !== null) {
                    var greeting = identity.greeting;
                    if (greeting !== undefined) {
                        showGreeting = true;
                        $('#vv-greeting').text(greeting);
                    } else {
                        $('#vv-greeting').text('');
                    }
                }

                //Only show section if the section was originally configured to show as well. 
                var showUserFieldHomeSection = showTempUserSections && $('.vv-home-user-field-section').attr('data-vv-hide-home-section') === 'false';
                var showUserFieldBusinessSection = showTempUserSections && $('.vv-business-user-field-section').attr('data-vv-hide-business-address') === 'false';
              
                return {
                    '#vv-greeting-container': showGreeting,
                    '.vv-remembered-profile': showRememberedSummary && !showTempUserSections,
                    //'#vvRememberedUserSection': showRememberedBlurb,
                    '#stepUserInformation3': showUser,
                    '.vv-temp-user-field-section': showTempUserSections,
                    '.vv-home-user-field-section': showUserFieldHomeSection,
                    '.vv-business-user-field-section': showUserFieldBusinessSection,
                    '#loginSection': display.showLogin
                };
            });
        },
        initializeUserEmail: function() {
            var me = this;
            var application = this._application;

            this.modalDialogs.emailVerificationDialog = function() {
                // create dialog
                var dialog = $('#emailVerificationDialogTemplate').clone(true).removeAttr("id");
                if (application._emailVerification.rateLimited === true) {
                    dialog.find('section > p').eq(0).html(me.translateLanguage('EmailVerificationMessage'));
                }
                dialog.find('.vv-verification-email-address').html(application._emailVerification.emailAddress);

                // using number as input type causes desktop browser to render increment / decrement arrow buttons.telephone keyboard is close enoug
                if (typeof vvuimobile !== 'undefined') {
                    dialog.find('.vv-verification-code-input').attr('type', 'number');
                }

                // keypress and keydown events do not fire in android chrome
                // https://therealmofcode.com/posts/2014/03/where-is-keypress-event-chrome-android.html
                dialog.find('.vv-verification-code-input').inputMask({ regex: '[0-9]', onsuccess: function() {
                    var verificationCode = $.trim(dialog.find('.vv-verification-code-input').val());
                    application.setEmailVerificationCode(verificationCode);

                    if (verificationCode.length === 4) {
                        application.verifyCode(function () {
                            dialog.find('.vvNavigation input').attr('disabled', 'disabled');
                            dialog.find('.vv-verification-error').hide();
                            dialog.find('.vv-verification-success').fadeIn();

                            setTimeout(function () {
                                dialog.dialog('close');
                                application.navigateByName('forward');
                            }, 2500);

                        }, function () { });
                    }
                } });

                var errorDisplay = dialog.find('aside');
                application.secretCodeRequired.attach(function() {
                    dialog.find('.vv-verification-code-input').addClass('vv-required');

                    setTimeout(function() {
                        dialog.find('.vv-verification-code-input').removeClass('vv-required');
                    }, 3000);
                });
                application.emailOwnershipProofFailed.attach(function (expired) {
                    dialog.find('.vv-verification-success').hide();
                    dialog.find('.vv-verification-error').show();
                    var errorMessage = expired === false
                        ? me.translateLanguage('EmailVerificationInvalidCode')
                        : me.translateLanguage('EmailVerificationFailure');
                    errorDisplay.html(errorMessage);
                    errorDisplay.css('color', 'red');
                    errorDisplay.show();
                });
                dialog.find('.vvNavigation input:first-child').click(function() {
                    errorDisplay.hide();
                    me.navigateForward();
                });
                dialog.find('.vvNavigation input:last-child').click(function() { me.navigateCancel(); });

                // start dialog
                me.openModalDialog(dialog, {
                    draggable: false,
                    title: me.translateLanguage('EmailVerificationDialogTitle'),
                    width: $('.vvBody').first().width() - 10 + 'px',
                    maxWidth: 800,
                    closeTransition: 'cancel'
                });
                return dialog;
            };
        },
        navigateLogin: function () {
            $('#stepUserInformation3').attr('editing', 'true');
            $('.jsReceivesMessagesText').text(vvui.translateLanguage('SubscriptionText'));
            this._application.navigateByName('login');
        }
    });
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    VoterVoiceUserInterface.prototype.extend({
        initializeUser: function(startNonRegisteredMatching, weaklyMatchedAddressTypes, promptEmailChange) {
            this.guardMessages.invalidEmail = this.translateLanguage('MessageInvalidEmail');
            this.guardMessages.saveUser = this.translateLanguage('MessageSavingUser');
            this.guardMessages.fetchIdentities = this.translateLanguage('MessageLoggingIn');

            // destroy "Remember me" if cookies are unavailable
            onReady(function() {

                $('#vv-edit-profile-link').click(function() {
                    var userProfile = $('#stepUserInformation3');
                    userProfile.attr('editing', 'true');
                    $('.vv-remembered-profile').slideUp();
                    $('.vv-temp-user-field-section').slideDown();
                    $('.vv-home-user-field-section[data-vv-hide-home-section="false"]').slideDown();
                    $('.vv-business-user-field-section[data-vv-hide-business-address="false"]').slideDown();

                    if ($('.vv-sidebar').css('float') === 'none' || $('.vv-content-container').height() < $('.vv-sidebar').height()) {
                        setTimeout(function() {
                                $('html, body').animate({
                                        scrollTop: userProfile.offset().top
                                    },
                                    2000);
                            },
                            500);
                    }
                });

                //if (typeof vvuimobile !== 'undefined') {
                //    $('#Membership_ReceivesTextMessages').on('change.bootstrapSwitch', function (e) {
                //        if (e.target.checked) {
                //            $('#Membership_MobileNumber').slideDown();
                //            $('#vvMobileSubscriptionMessage').slideDown();
                //        } else {
                //            $('#Membership_MobileNumber').slideUp();
                //            $('#vvMobileSubscriptionMessage').slideUp();
                //        }
                //    });
                //}

                var checkMobileNumberRequested = false;
                var checkingMobileNumber = false;
                function checkMobileNumber() {
                    function _checkMobileNumber() {
                        var receivesTextMessages = $('#Membership_ReceivesTextMessages')
                        var mobileNumber = $("#Membership_MobileNumber").val();
                        var userToken = application._userTracker.getIdentity().userToken;
                        checkingMobileNumber = true;
                        var association = application._userTracker._association;
                        application._userTracker._api.checkMobileNumber(
                            association,
                            userToken,
                            mobileNumber,
                            function (result) {
                                checkingMobileNumber = false;

                                if (receivesTextMessages && result.isNotMobileNumber) {
                                    $('#Membership_MobileNumber_IsNotMobileWarning').slideDown();
                                } else {
                                    $('#Membership_MobileNumber_IsNotMobileWarning').slideUp();
                                }
    
                                if (receivesTextMessages
                                    && result.requiresStartTextMessageToResubscribe
                                ) {
                                    $("#Membership_MobileNumber_ResubscribeWarning").slideDown();
                                } else {
                                    $("#Membership_MobileNumber_ResubscribeWarning").slideUp();
                                }

                                if (checkMobileNumberRequested) {
                                    checkMobileNumberRequested = false;
                                    _checkMobileNumber();
                                }
                            }
                        );
                    };

                    if (!checkingMobileNumber) {
                        _checkMobileNumber();
                    } else {
                        checkMobileNumberRequested = true;
                    }
                }

                $('#Membership_ReceivesTextMessages').change(function() {
                    if ($(this).is(':checked')) {
                        //$('#Membership_MobileNumber').slideDown();
                        //$('#vvMobileSubscriptionMessage').slideDown();
                        checkMobileNumber();
                    } else {
                        //$('#Membership_MobileNumber').slideUp();
                        //$('#vvMobileSubscriptionMessage').slideUp();
                        $('#Membership_MobileNumber_IsNotMobileWarning').slideUp();
                        $("#Membership_MobileNumber_ResubscribeWarning").slideUp();
                    }
                });

                $("#Membership_MobileNumber").on('keyup', function () {
                    if (debounce) {
                        clearTimeout(debounce);
                    }
                    // Debounce API calls by waiting 2 seconds
                    var debounce = setTimeout(checkMobileNumber, 2000);
                });

                function decorateSelectMultiple(jqControls) {
                    jqControls.filter('[multiple]').each(function(index, control) {

                        function setSelected(value, selected) {
                            $(control).find('option')
                                .filter(function() { return this.value === value; })
                                .prop('selected', selected);
                            control.rerenderValues();
                        }

                        $(control).hide();
                        var select = $('<select><option>-- Please select --</option></select>')
                            .insertBefore(control)
                            .change(function() { setSelected(this.value, true); });
                        var optionDisplays = [];
                        $(control).find('option').each(function(index2, option) {
                            var optionDisplay = $('<div><input type="checkbox" class="js-checkbox-template"/> <label></label></div>')
                                .insertBefore(control);
                            optionDisplay.find('input').prop('id', option.value)
                                .val(option.value)
                                .change(function() { setSelected(this.value, $(this).prop('checked')); });
                            optionDisplay.find('label').attr('for', option.value).html($(option).html());
                            optionDisplays.push(optionDisplay[0]);
                        });
                        control.rerenderValues = function() {
                            var jqControl = $(this);
                            select.find('option:not(:first-child)').remove();
                            select.append(jqControl.find('option:not(:selected)').clone());
                            select.val(null);
                            $(optionDisplays).find('input').prop('checked', function() {
                                var value = this.value;
                                var selected = jqControl.find('option')
                                    .filter(function() { return this.value === value; })
                                    .is(':selected') === true;
                                $(this).parent().toggle(selected);
                                return selected;
                            });
                        };
                        control.rerenderValues();
                    });
                }

                decorateSelectMultiple($('select.jsCustomField'));
                $('.jsCustomField.vvDate').datepicker();

                function canSetCookies() {
                    var cookieName = 'test_cookie';
                    var cookieValue = 'cookie_value';
                    $.cookie(cookieName, cookieValue, { path: '/', secure: true, sameSite: 'None' });
                    if ($.cookie(cookieName) === cookieValue) {
                        $.cookie(cookieName, null, { path: '/', secure: true, sameSite: 'None' });
                        return true;
                    } else {
                        // legacy browsers, i.e. ATs
                        // https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
                        $.cookie(cookieName, cookieValue, { path: '/' });
                        if ($.cookie(cookieName) === cookieValue) {
                            $.cookie(cookieName, null, { path: '/' });
                            return true;
                        }
                    }
                    return false;
                }

                if (canSetCookies() === false) {
                    $('#vvRememberMe').parents('.vv-remember-me-block').remove();
                }

                attachForwardNavigationOnEnterKey($('#vvEmailAddress'));
                attachForwardNavigationOnEnterKey($('#vvCountry'));
                attachForwardNavigationOnEnterKey($('#vvZipCode'));

  
                var homeAddressZipCodeLabel = $('label[for="UserModel_HomeAddress_ZipCode"]');
                var homeAddressDefaultZipCodeLabelText = homeAddressZipCodeLabel.text();
                $('#UserModel_HomeAddress_Country')
                    .change(function() {
                        setUserZipCodeLabel(homeAddressDefaultZipCodeLabelText, homeAddressZipCodeLabel, $(this).val());
                    });


                var zipCodeLabel = $('label[for="vvZipCode"]');
                var defaultUserZipCodeLabelText = zipCodeLabel.text();
                $('#vvCountry')
                    .change(function() {
                        setUserZipCodeLabel(defaultUserZipCodeLabelText, zipCodeLabel, $(this).val());
                    });

                function setUserZipCodeLabel(defaultZipCodeLabelText, zipCodeLabel, country) {
                    if (defaultZipCodeLabelText === 'ZIP Code') {
                        if (country !== 'US') {
                            zipCodeLabel.text('Postal Code');
                        } else {
                            zipCodeLabel.text(defaultZipCodeLabelText);
                        }
                    }
                }


                function attachForwardNavigationOnEnterKey(input) {
                    if (input !== undefined) {
                        // use keyup instead of keypress/keydown due to IE side-effect when pressing enter to close alerts
                        input.on('keyup', function(e) {
                            if (e.which == 13) {
                                // force loss of focus for FireFox
                                input.blur();
                                me._application.navigateByName('forward');
                            }
                        });
                    }
                }

                var honorificInput = $('#UserModel_Honorific');
                honorificInput.blur(function () {
                        honorificInput.val(honorificInput.val().trim());
                        var initialHonorific = honorificInput.val();
                        application.sanitizeHonorific(initialHonorific, function (data) {
                            if (data.honorific == undefined) return;
                            var currentHonorific = honorificInput.val();
                            if (initialHonorific === currentHonorific) {
                                honorificInput.val(data.honorific);
                            }
                        });
                });

                if ($("#divSuffix").css("display") !== 'none') {
                    $('#UserModel_Suffix')
                        .change(function () {
                            $("#UserModel_Suffix").val($("#UserModel_Suffix").val().trim());
                        });
                }
            });

            /*
            User Stuff
            */

            function CustomFieldControl(control) {
                if (!(this instanceof arguments.callee))
                    throw new Error("Constructor called as a function");

                this._control = control;
            }

            CustomFieldControl.prototype = {
                isRequired: function() {
                    var label;
                    if (this._control.type === 'radio') {
                        var labelId = $(this._control).attr('labelId');
                        label = $('label[for="' + labelId + '"]');
                    }
                    else {
                        label = $('label[for="' + this._control.id + '"]');    
                    }
                    
                    return label.parent().hasClass('vv_required');
                },
                getValue: function() {
                    if (this._control.type === 'radio') {
                        if ($('input[type=radio][name="'+this._control.name+'"]:checked').length > 0) {
                            return [$('input[type=radio][name="'+this._control.name+'"]:checked').val().toLowerCase()];
                        } else {
                            return [null];
                        }
                    } else if (this._control.type === 'checkbox') {
                        return [this._control.checked];
                    } else if (this._control.type === 'select-multiple') {
                        var allMultipleValues = $(this._control).val();
                        if (allMultipleValues == null) {
                            return [];
                        } else {
                            var valueArray = [];
                            for (var k = 0; k < allMultipleValues.length; k++) {
                                valueArray.push(allMultipleValues[k]);
                            }
                            return valueArray;
                        }
                    } else {
                        return [$(this._control).val()];
                    }
                },
                setValue: function(values) {
                    var value = values === null ? null : values[0];
                    if (this._control.type === 'radio') {
                        $('input[name="' + this._control.name + '"]').prop('checked', function() {
                            return value === this.value;
                        });
                    } else if (this._control.type === 'checkbox') {
                        if (value === null) {
                            var vvDefault = $(this._control).attr('vvDefault');
                            if (vvDefault === "True") {
                                value = true;
                            }
                        }
                        this._control.checked = value;
                    } else if (this._control.type === 'select-multiple') {
                        // set other controls?
                        value = values === null ? [] : values.splice(0);
                        $(this._control).val(value);
                        this._control.rerenderValues();
                    } else {
                        var control = $(this._control);

                        if (control.hasClass('vvDate')) {
                            control.datepicker();
                            control.placeholder();
                        }

                        control.val(value);
                    }
                }
            };

            function initializeAutoCompleteCustomField(control) {
                var options = eval($(control).attr('data-options'));
                $(control)
                    //.focus(function() { $(this).autocomplete("search", $(this).val()); }) 
                    .tooltip({ tooltipClass: "ui-state-highlight", position: { my: "top", at: "bottom" } })
                    .autocomplete({
                        minLength: 0,
                        delay: 0,
                        autoFocus: true,
                        source: function(request, response) {
                            var results = $.ui.autocomplete.filter(options, request.term);
                            response(results.slice(0, 20)); //Display 20 results in dropdown.
                        },
                        change: function(event, ui) {
                            if (!ui.item) {
                                var jControl = $(this);
                                var searchValue = jControl.val().trim();
                                //If field is whitepace, clear field
                                if (searchValue === "") {
                                    jControl.val("");
                                    return false;
                                }
                                //If it matches an option, replace it with that option  
                                var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(searchValue) + '$', 'i');
                                for (var i = 0; i < options.length; i++) {
                                    if (options[i].match(matcher)) {
                                        jControl.val(options[i]);
                                        return;
                                    }
                                }
                                //If not matched, then it's invalid; clear field
                                jControl.val("")
                                    .attr("title", "'" + searchValue + "'" + " didn't match any item")
                                    .tooltip("open");
                                setTimeout(function() {
                                    jControl.tooltip("close")
                                        .attr("title", "");
                                }, 3500);
                                return false;
                            }
                        }
                    });
            }

            function CustomFieldControlSet(selector) {
                if (!(this instanceof arguments.callee))
                    throw new Error("Constructor called as a function");

                this._set = {};
                var controls = $(selector);
                for (var i = 0; i < controls.length; i++) {
                    var control = controls[i];
                    var fieldId = control.id.split('__')[1];
                    if (control.type !== 'radio' || this._set[fieldId] === undefined) {
                        this._set[fieldId] = new CustomFieldControl(control);
                    }

                    if ($(control).attr("data-isautocomplete") === "True") {
                        initializeAutoCompleteCustomField(control);
                    }
                }
            }

            CustomFieldControlSet.prototype = {
                getRequiredTemplate: function() {
                    var result = [];
                    for (var fieldId in this._set) {
                        var v = this._set[fieldId].isRequired();
                        result.push({
                            id: fieldId,
                            values: v
                        });
                    }
                    return result;
                },
                getValues: function() {
                    var cfValues = new Array();
                    for (var fieldId in this._set) {
                        cfValues.push({
                            id: fieldId,
                            values: this._set[fieldId].getValue()
                        });
                    }
                    return cfValues;
                },
                setValues: function(values) {
                    for (var fieldId in this._set) {
                        var value = null;
                        if (values !== undefined && values !== null) {
                            for (var v = 0; v < values.length; v++) {
                                if (values[v].id == fieldId)
                                    value = values[v].values;
                            }
                        }
                        this._set[fieldId].setValue(value);
                    }
                }
            };

            var me = this;
            var application = this._application;

            function wireUserToUi() {
                application.userCleared.attach(function() {
                    $('#vvEmailAddress').val("");
                    $('#vvZipCode').val("");
                    //Clear the Actionbar Buttons of the previous user's actions. 
                    $('.vvBody .jsActionBarButton > .jsCompletedBadge').hide();
                    $('.vvBody .jsActionBarButton').attr('title', '');
                });
                application.identityChanged.attach(function(userToken, changedAddresses) {
                    var translationKey = userToken ? 'HeaderUserProfileExisting' : 'HeaderUserProfileNew';
                    $('.vvUserProfileHeader:not(.customized)').html(me.translateLanguage(translationKey));
                });

                $('#UserModel_GivenNames').change(function() { confirmNameChange(false); });
                $('#UserModel_Surname').change(function() { confirmNameChange(true); });
                $('#UserModel_EmailAddress').bind('keypress', confirmEmailChange);
                var identityConfirmed = false;
                var originalSignature, originalEmailAddress;

                function confirmNameChange(surnameChange) {
                    return confirmIdentity(
                        function(oldName, newName) {
                            // Since the original name (oldName) is producted by .getIdentity().signature,
                            // we should be able to use a leading/trailing space as an indication that
                            // the original given-name/surname (respectively) was empty too.
                            if (oldName === '') {
                                return false;
                            } if (!surnameChange && oldName[0] === ' ') {
                                return false; // changing given-names that was originally empty
                            } else if (surnameChange && oldName[oldName.length - 1] === ' ') {
                                return false; // changing surname that was originally empty
                            }
                            return oldName.toLowerCase() !== newName.toLowerCase();
                        },
                        function(oldName, newName) {
                            return {
                                text: me.translateLanguage('MessageNameChangeConfirmation')
                                    .replace('{0}', '<span style="font-weight: bold;">' + oldName + '</span>')
                                    .replace('{1}', '<span style="font-weight: bold;">' + newName + '</span>'),
                                buttons: 'Same|Different'
                            };
                        },
                        true);
                }

                function confirmEmailChange() {
                    return confirmIdentity(
                        function(oldName, newName) {
                            return originalEmailAddress !== '';
                        },
                        function(oldName, newName) {
                            return me.translateLanguage('MessageEmailChangeConfirmation') + " " + oldName + '.';
                        },
                        promptEmailChange);
                }

                function confirmIdentity(isRealChange, createPrompt, showPrompt) {
                    var identity = application._userTracker.getIdentity();
                    if (identity.userToken !== undefined && identity.userToken !== null) {
                        if (identityConfirmed !== true && isRealChange(originalSignature, identity.signature)) {
                            if (showPrompt === false) {
                                identityConfirmed = true;
                            } else {
                                me.confirm(createPrompt(originalSignature, identity.signature),
                               function () {
                                   identityConfirmed = true;
                               },
                               function () {
                                   me.navigateLogin();
                               });
                            }
                        }
                    }
                    return true;
                }

                application.baselineUser(function() {
                    var user = {
                        userId: $("#UserModel_UserId").val(),
                        commonHonorific: $("#UserModel_CommonHonorific").val(),
                        givenNames: $.trim($("#UserModel_GivenNames").val()),
                        surname: $.trim($("#UserModel_Surname").val()),
                        emailAddress: $.trim($("#UserModel_EmailAddress").val())
                        //isNonDeliverableEmail: $("#UserModel_IsNonDeliverableEmail").val(),
                    };
                    if ($("#divHonorific").css("display") !== 'none' &&
                    ($("#divHonorific").hasClass('vv_required') ||
                        //voter registration
                        !$("#divHonorific").hasClass('vv_required') && $('#UserModel_Honorific').val() !== '')) {
                        user.honorific = $("#UserModel_Honorific").val();
                    }
                    if ($("#divSuffix").css("display") !== 'none') {
                        //user.suffix = $("#UserModel_Suffix").val();
                    }
                    if ($("#divPhoneNumber").css("display") !== 'none') {
                        user.phoneNumber = $("#UserModel_PhoneNumber").val();
                    }

                    if ($("#divFaxNumber").css("display") !== 'none') {
                        user.faxNumber = $("#UserModel_FaxNumber").val();
                    }
                    var sectionHome = $('#divHomeInformationTitle').parent();
                                                                 //If remembered, the controls aren't visible, but the attr denotes if they would be shown in edit mode
                    if (sectionHome.css("display") !== 'none' || sectionHome.attr("data-vv-hide-home-section") === 'false') {
                        user.homeAddress = me.buildAddress('Home');
                    }
                    var sectionBusiness = $('#divBusinessInformationTitle').parent();
                                                                     //If remembered, the controls aren't visible, but the attr denotes if they would be shown in edit mode
                    if (sectionBusiness.css("display") !== 'none' || sectionBusiness.attr("data-vv-hide-business-address") === 'false') {
                        user.businessPositionName = $.trim($("#UserModel_BusinessPositionName").val());
                        user.businessCompanyName = $.trim($("#UserModel_BusinessCompanyName").val());
                        var businessAddress = $(sectionBusiness.find('.vv-address-information')[0]);
                        if (businessAddress.css("display") !== 'none')
                            user.businessAddress = me.buildAddress('Business');
                    }
                    user.customFieldValues = new CustomFieldControlSet('.jsCustomField').getValues();
                    return user;
                }, {
                    honorific: $('#UserModel_Honorific').parent().parent().hasClass('vv_required'),
                    givenNames: true,
                    surname: true,
                    emailAddress: true,
                    phoneNumber: $('#UserModel_PhoneNumber').parent().parent().hasClass('vv_required'),
                    homeAddress: $('#UserModel_HomeAddress_StreetAddress').parent().parent().hasClass('vv_required'),
                    businessPositionName: $('#divBusinessPositionName').hasClass('vv_required'),
                    businessCompanyName: $('#divBusinessCompanyName').hasClass('vv_required'),
                    businessAddress: $($('#divBusinessInformationTitle').parent().find('.vv-address-information')[0]).css("display") !== 'none' && $('#UserModel_BusinessAddress_StreetAddress').parent().parent().hasClass('vv_required'),
                    customFieldValues: new CustomFieldControlSet('.jsCustomField').getRequiredTemplate()
                });
                originalSignature = application._userTracker.getIdentity().signature;
                originalEmailAddress = application._userTracker.getEmailAddress();
                application.switchedUser.attach(function(user) {
                    $("#UserModel_UserId").val(user.userId);
                    $("#UserModel_Honorific").val(user.honorific);
                    $("#UserModel_CommonHonorific").val(user.commonHonorific);
                    $("#UserModel_GivenNames").val(user.givenNames);
                    $("#UserModel_Surname").val(user.surname);
                    $("#UserModel_Suffix").val(user.suffix);
                    $("#UserModel_EmailAddress").val(user.emailAddress);
                    // user POST will require a value
                    var isNonDeliverableEmail = !user.isNonDeliverableEmail // undefined or ''
                        ? false
                        : user.isNonDeliverableEmail;
                    $("#UserModel_IsNonDeliverableEmail").val(isNonDeliverableEmail);
                    $("#UserModel_PhoneNumber").val(user.phoneNumber);
                    $("#UserModel_FaxNumber").val(user.faxNumber);
                    $("#UserModel_BusinessPositionName").val(user.businessPositionName);
                    $("#UserModel_BusinessCompanyName").val(user.businessCompanyName);
                    me.setAddress('Home', user.homeAddress);
                    me.setAddress('Business', user.businessAddress);
                    new CustomFieldControlSet('.jsCustomField').setValues(user.customFieldValues);

                    if (user.membership !== undefined) {
                        $('#Membership_ReceivesMessages').prop('checked', user.membership.isSubscriber);
                        // mobile number currently not exposed through API
                    }
                });
                application.emailCleared.attach(function() {
                    $("#UserModel_EmailAddress").val('');
                });

                $('#divHomeInformationTitle').append('&nbsp;<span id="addressValidationHome"></span>');
                $('#divBusinessInformationTitle').append('&nbsp;<span id="addressValidationBusiness"></span>');

                // bind address changes
                me.bindAddressChanges('Home');
                me.bindAddressChanges('Business');

                me.forceAddressValidation('Home');
                me.forceAddressValidation('Business');

                if (startNonRegisteredMatching === true) {
                    // this will kick off matching for unregistered users
                    $();
                    if ($('#divHomeInformationTitle').parent().css("display") !== 'none') {
                        application.addressChanged('Home', me.buildAddress('Home'));
                    }
                    if ($('#divBusinessInformationTitle').parent().css("display") !== 'none') {
                        application.addressChanged('Business', me.buildAddress('Business'));
                    }
                } else if (weaklyMatchedAddressTypes !== undefined) {
                    $.each(weaklyMatchedAddressTypes, function(i, val) {
                        var addr = me.buildAddress(val.type);
                        addr.coordinates = val.coordinates;
                        addr.coordinates.isWeakCoordinates = true;
                        me.displayAddressValidation(val.type, addr);
                    });
                }
            }

            this._application.ui.readLoginInfo = readLoginInfo;
            this._application.ui.readReceivesMessages = readReceivesMessages;
            this._application.ui.readMembership = readMembership;
            this._application.ui.readRememberMe = readRememberMe;

            // do this before wiring displayChanged, so when redering calls getIdentity,
            // it will be able to call _fetchUser and use user names for display
            onReady(wireUserToUi);
        },
        navigateLogin: function () {
            $('#stepUserInformation3').attr('editing', 'true');
            $('#Membership_ReceivesTextMessages').prop('checked', true);
            $("#Membership_MobileNumber").val('');
            $("#Membership_MobileNumber").show();
            $('#vvMobileSubscriptionMessage').show();
            $('.jsReceivesMessagesText').text(vvui.translateLanguage('SubscriptionText'));
            this._application.navigateByName('login');
        }
    });

    function readLoginInfo() {
        var emailAddress = $.trim($('#vvEmailAddress').val());
        var country = $('#vvCountry').val();
        var zipCode = $('#vvZipCode').val();
        return {
            emailAddress: emailAddress,
            country: country,
            zipCode: zipCode
        };
    }

    function readReceivesMessages() {
        var checkbox = $('#Membership_ReceivesMessages');
        return checkbox.is(':hidden') || checkbox.is(':checked');
    }

    function readMembership() {
        var receivesMessagesCheckbox = $('#Membership_ReceivesMessages');
        var receivesTextMessagesCheckbox = $('#Membership_ReceivesTextMessages');
        var mobileNumber = null;
        if ($("#divMobileNumber").css("display") !== 'none') {
            mobileNumber = $("#Membership_MobileNumber").val();
        }

        return {
            receivesMessages: receivesMessagesCheckbox.is(':hidden') || receivesMessagesCheckbox.is(':checked'),
            mobileNumber: mobileNumber,
            receivesTextMessages: receivesTextMessagesCheckbox.is(':hidden') ? null : receivesTextMessagesCheckbox.is(':checked')
        };
    }

    function readRememberMe() {
        var checkbox = $('#vvRememberMe');
        return checkbox.length === 0
            ? undefined
            : checkbox.is(':checked');
    }
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
function ComposeUiFactory(composer) {
    this._composer = composer;
    this.messageUis = {};
    this.messageNumber = 0;
}

function CompositeComposeUiFactory(composers) {
    this._factories = [];
    for (var i = 0; i < composers.length; i++) {
        this._factories.push(new ComposeUiFactory(composers[i]));
    }
    // hack
    this.getCurrentMessageUiElements = function() {
        return this._factories[0].uiElements;
    };
}

(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    ComposeUiFactory.prototype = {
        initializeComposeUi: function(application) {
            this.parentSection = this._composer.getParentSection();
            this.parentSection.append($('#vvNoDeliveriesTemplate').clone(true).removeAttr('id').addClass('jsNoDeliveries'));
            this.parentSection.append($('#vvNoDeliverabilityTemplate').clone(true).removeAttr('id').addClass('jsNoDeliverability'));
            this._messageUis = {};
            application.initializeComposer(this._composer.deliveriesProperty);
        },
        startMessage: function(messages, tMessage, sample, respondUi) {
            var section = $('#targetedMessageTemplate').clone(true).removeAttr("id");
            this.messageUis[tMessage.id] = section.children();

            this.uiElements = {};
            var ui = section.children('header');
            this.messageNumber++;
            ui.filter(':not(.customized)').html(this._composer.composeMessageHeaderText(messages.length, this.messageNumber, sample, respondUi));

            this.parentSection.append(ui);
            this.uiElements.header = ui;

            ui = section.children('aside');
            this.parentSection.append(ui);
            this.uiElements.aside = ui;

            ui = section.children('section').eq(0);
            this.uiElements.recipients = ui;
        },
        createMessageRecipientUis: function(tm) {
            var recipientsSection = this.uiElements.recipients;

            this._messageUis[tm.id] = {
                groupsList: this.groupsList = recipientsSection.find('ul').first(),
                collapsedGroupsList: this.collapsedGroupsList = $('<ul class="vv-recipient-groupings-collapsed"></ul>'),
                expandedGroupsList: this.expandedGroupsList = $('<ul class="vv-recipient-groupings-expanded"></ul>'),
                groupsToggleLink: this.groupsToggleLink = $('<a class="vvMore vv-recipient-groupings-toggle" style="display: none" tabindex="0"></a>'),
                allRecipientsUi: this.allRecipientsUi = $('<ul></ul>'),
                collapsedRecipientsUi: this.collapsedRecipientsUi = $('<ul class="vv-recipients-collapsed"></ul>'),
                expandedRecipientsUi: this.expandedRecipientsUi = $('<ul class="vv-recipients-expanded"></ul>'),
                recipientsToggleLink: this.recipientsToggleLink = $('<a class="vvMore vv-recipient-toggle" style="display: none"></a>'),
                guidelinesUi: this.guidelinesUi = this._composer.createGuidelinesUi(),
                suggestedMessageUi: this.suggestedMessageUi = this._composer.createSuggestedMessageUi(),
                composeUi: this.composeUi = this._composer.createComposeUi()
            };
        },
        handleRecipientsDetermined: function(application, tm) {
            var groupsList = this.groupsList;
            var collapsedGroupsList = this.collapsedGroupsList;
            var expandedGroupsList = this.expandedGroupsList;
            var groupsToggleLink = this.groupsToggleLink;
            var allRecipientsUi = this.allRecipientsUi;
            var collapsedRecipientsUi = this.collapsedRecipientsUi;
            var expandedRecipientsUi = this.expandedRecipientsUi;
            var recipientsToggleLink = this.recipientsToggleLink;
            var composer = this._composer;

            application.recipientsDetermined.run(function(matchGroups, matchCriteria) {
                // initialize group/recipient lists' visibility
                var showGroupsList = initialGroupsListVisibility(application, matchCriteria, matchGroups, tm);
                var configuration = {
                    showGroupsList: showGroupsList,
                    groupsList: groupsList,
                    collapsedGroupsList: collapsedGroupsList,
                    expandedGroupsList: expandedGroupsList,
                    groupsToggleLink: groupsToggleLink,
                    allRecipientsUi: allRecipientsUi,
                    collapsedRecipientsUi: collapsedRecipientsUi,
                    expandedRecipientsUi: expandedRecipientsUi,
                    recipientsToggleLink: recipientsToggleLink
                };

                composer.toggleListVisibility(configuration);

                groupsToggleLink.html(vvui.translateLanguage('TextShowMore'));
                recipientsToggleLink.html(vvui.translateLanguage('TextShowMore'));

                // clear combined recipients before processing groups
                allRecipientsUi.empty();
                collapsedRecipientsUi.empty();
                expandedRecipientsUi.empty();
            });

            function initialGroupsListVisibility(application, matchCriteria, matchGroups, tm) {
                if (application.hideEmptyGroups === true) {
                    return false;
                }
                // by default don't show groups if we searched by something
                var noCriteria = !(matchCriteria.byUser || matchCriteria.byAddress);
                if (noCriteria) {
                    return true;
                }
                // still show if there were no results
                var recipientCount = 0;
                for (var tmg = 0; tmg < matchGroups.length; tmg++) {
                    var matchGroup = matchGroups[tmg];
                    if (matchGroup.messageId == tm.id) {
                        recipientCount += matchGroup.matches.length;
                    }
                }
                return recipientCount === 0;
            }
        },
        populateAndBindRecipientGroupUi: function (tm, application, messageLengthValidator, respondUi) {
            var recipientsSection = this.uiElements.recipients;
            var groupsList = this.groupsList;
            var collapsedGroupsList = this.collapsedGroupsList;
            var expandedGroupsList = this.expandedGroupsList;
            var groupsToggleLink = this.groupsToggleLink;
            var allRecipientsUi = this.allRecipientsUi;
            var collapsedRecipientsUi = this.collapsedRecipientsUi;
            var expandedRecipientsUi = this.expandedRecipientsUi;
            var recipientsToggleLink = this.recipientsToggleLink;
            var uiContainer = this._composer.buildGroupUiContainer(groupsList, respondUi);
            var recipientsBeforeMore = this._composer.recipientsBeforeMore;

            for (var t = 0; t < tm.targets.length; t++) {
                var groupUi = createTargetGroupingUi.call(this, tm.targets[t], application, tm, messageLengthValidator, allRecipientsUi, respondUi);
               
                if (recipientsBeforeMore === undefined) {
                    uiContainer.append(groupUi);
                } else {
                    if (collapsedGroupsList.children().length >= recipientsBeforeMore) {
                        expandedGroupsList.append(groupUi);
                    } else {
                        collapsedGroupsList.append(groupUi);
                    }
                }
            }
            //TODO: this, maybe, could replace the above for; can't check right now
            if (application.hideEmptyGroups) {

                if (recipientsBeforeMore === undefined) {
                    groupsList.html('<li>' + application.hideEmptyGroups + '</li>');
                } else {
                    if (collapsedGroupsList.children().length >= recipientsBeforeMore) {
                        expandedGroupsList.html('<li>' + application.hideEmptyGroups + '</li>');
                    } else {
                        collapsedGroupsList.html('<li>' + application.hideEmptyGroups + '</li>');
                    }
                }
            }

            this.buildToggleLink(application, collapsedGroupsList, expandedGroupsList, groupsToggleLink);
            setTimeout(function() {
                groupsToggleLink.toggle(collapsedGroupsList.is(':visible') && expandedGroupsList.children().length > 0);
            }, 100);
            
            function createTargetGroupingUi(target, application, tm, messageLengthValidator, allRecipientsUi, respondUi) {
                var me = this;
                var groupUi = this._composer.createGroupUi(target);
                var binder = this._composer.buildBinder(bindMatch, createRecipientItem);

                application.resetMaximumMessageLength.attach(function () {
                    resetMaxMessageLength.call(me, messageLengthValidator);
                });

                application.recipientsDetermined.run(
                    function (matchGroups, matchCriteria) {
                        function matchGroupNeedsMatching(matchGroup, composerName) {
                            if (matchGroup.assigned === undefined)
                                return true;

                            for (var c = 0; c < matchGroup.assigned.length; c++) {
                                var assigned = matchGroup.assigned[c];
                                if (composerName === assigned)
                                    return false;
                            }

                            return true;
                        }

                        // get message/group matches
                        me._composer.prepareGroupUiContainerForBinding(uiContainer, matchGroups);
                        var composerName = me._composer.composerName;
                        var myMatches = new Array();
                        for (var tmg = 0; tmg < matchGroups.length; tmg++) {
                            var matchGroup = matchGroups[tmg];
                            if (matchGroup.messageId == tm.id && matchGroup.groupId == target.groupId && matchGroupNeedsMatching(matchGroup, composerName)) {
                                if (matchGroup.assigned === undefined) {
                                    matchGroup.assigned = [];
                                }
                                matchGroup.assigned.push(composerName);
                                myMatches = matchGroup.matches;
                                break;
                            }
                        }

                        binder.call(me, application, groupUi, myMatches, tm, matchCriteria, messageLengthValidator, allRecipientsUi, respondUi);
                    });
                return groupUi;
            }

            function resetMaxMessageLength(messageLengthValidator) {
                messageLengthValidator.clearMaximumMessageLength(this._composer);
            }

            function bindMatch(application, ui, myMatches, targetedMessage, matchCriteria, messageLengthValidator, allRecipientsUi, respondUi) {
                // bind matches
                var noCriteria = !(matchCriteria.byUser || matchCriteria.byAddress);
                var skipGroupList = application.hideEmptyGroups === true;
                var ul = ui.find('ul');
                ul.empty();
                for (var m = 0; m < myMatches.length; m++) {
                    var li = createRecipientItem.call(this, myMatches[m], false, application, targetedMessage, messageLengthValidator, respondUi);
                    if (allRecipientsUi.children().length >= recipientsBeforeMore) {
                        li.toggleClass('vvRecipientMore');
                    }
                    if (noCriteria && !skipGroupList) {
                        ul.append(li);
                    } else {

                        if (recipientsBeforeMore === undefined) {
                            allRecipientsUi.append(li);
                        } else {
                            if (collapsedRecipientsUi.children().length >= recipientsBeforeMore) {
                                expandedRecipientsUi.append(li);
                            } else {
                                collapsedRecipientsUi.append(li);
                            }
                        }
                    }
                }

                recipientsToggleLink.toggle(expandedRecipientsUi.children().length > 0);

                // adjust visibility
                var link = ui.find('a');
                if (myMatches.length === 0) {
                    link.html("");
                } else {
                    link.html("(+)");
                }
                ul.hide();
            }

            function createRecipientItem(match, isPhone, application, targetedMessage, messageLengthValidator, respondUi) {
                var li = this._composer.createRecipientLi(match);
                var me = this;

                match.gotDeliveryOptions.run(
                    function(match, options, messageDeliverabilityContext) {
                        var deliveryOptions = [];
                        for (var i = 0; i < options.length; i++) {
                            if (me._composer.acceptsDeliveryOption(options[i])) {
                                deliveryOptions.push(options[i]);
                            }
                        }

                        bindDeliveryOptions.call(me, application, li, match, deliveryOptions, targetedMessage, respondUi);
                        messageLengthValidator.setMaximumMessageLength.call(me, deliveryOptions, me._composer, respondUi);

                        // grey messages we determine have undeliverable recipients
                        if (messageDeliverabilityContext.deliverabilityDetections[me._composer.deliverabilityKey] === undefined) {
                            messageDeliverabilityContext.deliverabilityDetections.push(messageDeliverabilityContext.deliverabilityDetections[me._composer.deliverabilityKey] = {});
                        }
                        if (messageDeliverabilityContext.deliverabilityDetections[me._composer.deliverabilityKey].isDeliverable === undefined) {
                            var hasDeliveryOptions = deliveryOptions.length > 0;
                            if (messageDeliverabilityContext.pendingOptionCount === 0 || hasDeliveryOptions) {
                                messageDeliverabilityContext.deliverabilityDetections[me._composer.deliverabilityKey].isDeliverable = hasDeliveryOptions;

                                me._deliverabilityDetected(messageDeliverabilityContext.messageId, true, hasDeliveryOptions, respondUi);
                            }
                        }
                    });

                return li;
            }
            
            function bindDeliveryOptions(application, li, match, options, targetedMessage, respondUi) {
                var option = null;
                for (var o = 0; o < options.length; o++) {
                    if (options[o].constituentOnly === false || match.addressIdentities.length !== 0) {
                        option = options[o];
                        break;
                    }
                }
                //Determine if any targets for a message_composer pair can be sent to, if not, toggleNoDeliverableTargetsMode. 
                targetedMessage[this._composer.targetedMessageDeliverableProperty].hasDeliveryOptions = option !== null || targetedMessage[this._composer.targetedMessageDeliverableProperty].hasDeliveryOptions || false;
                this.toggleNoDeliverableTargetsMode(targetedMessage.id, targetedMessage[this._composer.targetedMessageDeliverableProperty].hasDeliveryOptions);

                if (option === null) {
                    this._composer.buildUndeliverableTargetUi(application, li, respondUi);
                }
                else {
                    this._composer.buildDeliveryTargetUi(application, li, match, option, targetedMessage, respondUi);
                }
                application.setDeliveryOption(match.type, match.id, option, this._composer.deliveriesProperty);
            }
        },
        addFullRecipientList: function(application, vvui) {
            var recipientsSection = this.uiElements.recipients;
            var allRecipientsUi = this.allRecipientsUi;
            var collapsedRecipientsUi = this.collapsedRecipientsUi;
            var expandedRecipientsUi = this.expandedRecipientsUi;
            var recipientsToggleLink = this.recipientsToggleLink;

            allRecipientsUi.hide();
            recipientsSection.append(allRecipientsUi);
            this.buildToggleLink(application, collapsedRecipientsUi, expandedRecipientsUi, recipientsToggleLink);

            //var moreLink = $('<a class="vvMore" style="display: none"></a>');
            //application.recipientsDetermined.run(
            //    function(matchGroups, matchCriteria) {
            //        moreLink.toggle(allRecipientsUi.children('.vvRecipientMore').length !== 0);
            //    });
            //moreLink.click(function() {
            //    allRecipientsUi.toggleClass('vvRecipientsListLess');
            //    moreLink.html(allRecipientsUi.is('.vvRecipientsListLess') ? vvui.translateLanguage('TextShowMore') : vvui.translateLanguage('TextShowLess'));
            //});
            //moreLink.click();
            //moreLink.click();

            //expandedRecipientsUi.hide();
            //recipientsSection.append(allRecipientsUi);
            //recipientsSection.append(moreLink);
        },
        buildToggleLink: function (application, collapsedUi, expandedUi, toggleLink) {
            var recipientsSection = this.uiElements.recipients;
            //application.recipientsDetermined.run(
            //    function (matchGroups, matchCriteria) {
            //        toggleLink.toggle(expandedUi.children().length !== 0);
            //    });

            toggleLink.click(function () {
                if (expandedUi.is(':visible')) {
                    expandedUi.slideUp();
                    toggleLink.html(vvui.translateLanguage('TextShowMore'));
                } else {
                    expandedUi.slideDown();
                    toggleLink.html(vvui.translateLanguage('TextShowLess'));
                }
            });

            toggleLink.on('keydown', function(e){
                // enter or spacebar key 
                if (/^(13|32)$/.test(e.which)) {
                  e.preventDefault();
                  toggleLink.click();
                }
            });

            expandedUi.hide();
            recipientsSection.append(collapsedUi);
            recipientsSection.append(expandedUi);
            recipientsSection.append(toggleLink);
        },
        appendRecipients: function() {
            var parentSection = this.parentSection;
            var ui = this.uiElements.recipients;

            parentSection.append(ui);
        },
        bindAndAppendGuidelines: function (tMessage, sample, respondUi) {

            //TODO: Remove this hack. 
            if (this._composer.composerName === 'phoneComposer')
                return;
            var me = this;
            var messageUis = this.messageUis;
            var parentSection = this.parentSection;
            var uiElements = this.uiElements;

            var guidelinesUi = this._messageUis[tMessage.id].guidelinesUi;
            messageUis[tMessage.id] = messageUis[tMessage.id].add(guidelinesUi);
            bindGuidelines.call(respondUi, guidelinesUi, tMessage, sample);
            parentSection.append(guidelinesUi);
            uiElements.guidelines = guidelinesUi;

            vvui.emitContentLoadedEvent();

            function bindGuidelines(ui, tm, sm) {
                var respondUi = this;
                var targetedMessageComposerHasDeliveryOptions = tm[me._composer.targetedMessageDeliverableProperty].hasDeliveryOptions;
                if (!sm.guidelines || targetedMessageComposerHasDeliveryOptions === false)
                    ui.hide();
                else {
                    ui.show();
                    var summary = ui.find('summary');
                    var container = summary.find('span').html(sm.guidelines);
                    respondUi.defaultLinksToNewWindow(container);
                }
            }

            this.guidelinesUi = guidelinesUi;
        },
        appendSuggestedMessage: function(tMessage) {
            var messageUis = this.messageUis;
            var parentSection = this.parentSection;
            var uiElements = this.uiElements;
            var targetedMessageComposerHasDeliveryOptions = tMessage[this._composer.targetedMessageDeliverableProperty].hasDeliveryOptions;

            var suggestedMessageUi = this._messageUis[tMessage.id].suggestedMessageUi;
            messageUis[tMessage.id] = messageUis[tMessage.id].add(suggestedMessageUi);
            uiElements.suggestedMessageUi = suggestedMessageUi;

            var show = !(suggestedMessageUi.length === 0 || targetedMessageComposerHasDeliveryOptions === false);
            suggestedMessageUi.toggle(show);
            parentSection.append(suggestedMessageUi);
        },
        bindAndAppendCompose: function (tMessage, sample, messageLengthValidator, respondUi) {
            var me = this;
            var messageUis = this.messageUis;
            var parentSection = this.parentSection;
            var uiElements = this.uiElements;
            var guidelinesUi = this.guidelinesUi;

            var composeUi = this._messageUis[tMessage.id].composeUi;
            messageUis[tMessage.id] = messageUis[tMessage.id].add(composeUi);
            bindTargetedMessage.call(this, uiElements.instructions, guidelinesUi, composeUi, tMessage, sample, messageLengthValidator, respondUi);
            parentSection.append(composeUi);
            uiElements.compose = composeUi;

            composeUi.find('input').placeholder();

            respondUi._application.matchingStarted.run(function() {
                delete tMessage[me._composer.targetedMessageDeliverableProperty].hasDeliveryOptions;
            });

            respondUi._application.matchingCleared.run(function() {
                me.toggleNoDeliverableTargetsMode(tMessage.id, true);
            });

            function bindTargetedMessage(instructionsUi, guidelinesUi, composeUi, tm, sm, messageLengthValidator, respondUi) {
                var composeElements = this._composer.createComposeElements(uiElements.suggestedMessageUi, composeUi);

                var me = this;
                respondUi.messageContentChanged.attach(function() {
                    var messageCounter = composeElements.messageCounter;

                    var joinedMessageLength = me._composer.getJoinedMessageLength(composeElements);

                    var remainingCharacterCount = messageLengthValidator.getMaximumMessageLength(me._composer) - joinedMessageLength;
                    var messageCounterContainer = messageCounter.parent();
                    if (remainingCharacterCount < 0) {
                        messageCounter.addClass('remaining-message-count-error');
                        messageCounterContainer.siblings('.jsMessageError').css('display', 'block');
                    } else {
                        messageCounter.removeClass('remaining-message-count-error');
                        messageCounterContainer.siblings('.jsMessageError').css('display', 'none');
                    }

                    function commaSeparateNumber(x) {
                        return String(x).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                    }

                    messageCounter.html(commaSeparateNumber(remainingCharacterCount));

                    var composeFooter = messageCounter.closest('.vv-compose-footer');
                    if (remainingCharacterCount <= 50) {
                        messageCounterContainer.removeClass('vv-message-counter-container-hidden');
                        composeFooter.slideDown();
                    } else {
                        if (composeFooter.hasClass('vv-compose-carousel')) {
                            messageCounterContainer.addClass('vv-message-counter-container-hidden');
                        } else {
                            composeFooter.slideUp();    
                        }
                    }
                });

                this._composer.bindComposeElements(composeElements, tm, sm, composeUi, respondUi);

                var nextMessageInput = composeUi.find('#nextMessage');
                nextMessageInput.removeAttr("id");
                if (tm.messageDisplay === 'carousel' && tm.messages.length > 1 && !this._composer.hasParagraphs(tm)) {
                    nextMessageInput.click(function() {

                        function rotateNextMessage() {
                            var message = tm.nextSample();

                            me._composer.showNewSampleMessage(composeElements, tm, message, respondUi);

                            if (!message.guidelines)
                                guidelinesUi.hide();
                            else {
                                guidelinesUi.show();

                                var summary = guidelinesUi.find('summary');
                                summary.show();
                                summary.find('span').html(message.guidelines);
                            }

                            respondUi.messageContentChanged.dispatch();
                        }

                        if (me._composer.hasMessageBodyChanged(composeElements, tm)) {
                            respondUi.confirm(respondUi.translateLanguage('MessageChangedMessage'), rotateNextMessage);
                        } else if (me._composer.hasSubjectChanged(composeElements, tm)) {
                            respondUi.confirm(respondUi.translateLanguage('SubjectChangedMessage'), rotateNextMessage);
                        } else {
                            rotateNextMessage();
                        }
                    });

                    var composeFooter = nextMessageInput.closest('.vv-compose-footer');
                    composeFooter.addClass('vv-compose-carousel');
                } else {
                    nextMessageInput.hide();
                }
            }
        },
        appendMissingRecipientsDisclaimer: function(respondUi) {
        },
        initializeRecipients: function(tracker) {
            this._tracker = tracker;
            this._potentiallyDeliverableMessageCount = tracker.potentiallyDeliverableMessageCount;
        },
        toggleNoRecipientsMode: function (messageId, noRecipients, tracker) {
            var uis = this.messageUis[messageId];

            var visible = uis.first().is(':visible');

            //ignore cookie warning aside
            uis.not('aside').each(function () {
                //ignore empty guidelines
                if ($(this).find('summary').find('span').html() === '') {
                    return;
                }
                $(this).toggle(!noRecipients);
            });
            tracker.changedVisibility = tracker.changedVisibility || visible !== uis.first().is(':visible');
        },
        toggleNoDeliverableTargetsMode: function (messageId, hasDeliveryOptions) {
            this._messageUis[messageId].composeUi.children().not('header').toggle(hasDeliveryOptions);
            this._messageUis[messageId].composeUi.children('.jsUndeliverableMessage').toggle(!hasDeliveryOptions);

            var show = !(hasDeliveryOptions === false);

            //Toggle display if guidelines has already been bound (bindGuidelines). (Address change on loaded page)
            var showGuidelinesUi = show && this._messageUis[messageId].guidelinesUi.find('summary span:not(:empty)').length > 0;
            this._messageUis[messageId].guidelinesUi.toggle(showGuidelinesUi);

            //Toggle display if suggestedMessage has already been bound (bindComposeElements). (Address change on loaded page)
            var showSuggestedMessageUi = show && this._messageUis[messageId].suggestedMessageUi.find('summary span:not(:empty)').length > 0;
            this._messageUis[messageId].suggestedMessageUi.toggle(showSuggestedMessageUi);
        },
        toggleNoDeliveriesMessage: function(noRecipients, noDeliveryMethods, respondUi) {
            var noDeliveries = noRecipients;
            this.parentSection.children('.jsNoDeliveries').toggle(noDeliveries);
            this._composer.showingNoDeliveriesMessage = noDeliveries;
            if (this._composer.isActiveComposer === true) {
                respondUi.toggleNoDeliveriesButtons(this._composer.showingNoDeliveriesMessage);
            }
            this.parentSection.find('.jsNoMatches').toggle(noDeliveryMethods === false);
            this.parentSection.find('.jsNoDeliveryMethods').toggle(noDeliveryMethods === true);
        },
        recalculateMessageHeaderText: function(messages, respondUi) {
            var messageUis = this.messageUis;

            var visibleMessageCount = 0;

            for (var i = 0; i < messages.length; i++) {
                var messageId = messages[i].id;
                var messageUi = messageUis[messageId];

                if (messageUi.is(':visible')) {
                    visibleMessageCount++;
                    var headerText = this._composer.composeMessageHeaderText(messages.length, visibleMessageCount, messages[i].currentSample(), respondUi);
                    messageUi.filter('header:not(.customized)').html(headerText);
                }
            }
        },
        _deliverabilityDetected: function(messageId, hasRecipients, hasDeliveryOptions, respondUi) {
            // We check whether this._tracker is defined because Directory fires this BEFORE it is set.
            if (this._tracker && this._tracker.useNoDeliveriesMessage()) {
                if (hasRecipients && !hasDeliveryOptions) {
                    this._potentiallyDeliverableMessageCount = this._potentiallyDeliverableMessageCount - 1;
                }
                var hideMessage = this._tracker.reflectUndeliverableMessages === true && !hasRecipients;
                // using single-composer hack
                this.toggleNoRecipientsMode(messageId, hideMessage, this._tracker);

                if (this._tracker.messages.length > 1) {
                    this.recalculateMessageHeaderText(this._tracker.messages, respondUi);
                    this._tracker.tryShowingMessageSetChangedAlert();
                }
                if (this._potentiallyDeliverableMessageCount === 0) {
                    if (this._tracker._useNoDeliveriesMessage === true) {
                        this.toggleNoDeliveriesMessage(true, true, respondUi);
                        this.toggleNoRecipientsMode(messageId, true, this._tracker);
                    }
                }
            }
        }
    };

    // make all ComposeUiFactory.prototype methods available on CompositeComposeUiFactory
    (function(prototype) {
        for (var property in prototype) {
            if (typeof prototype[property] === 'function' && prototype.hasOwnProperty(property)) {
                (function(methodName) {
                    CompositeComposeUiFactory.prototype[methodName] = function() {
                        for (var i = 0; i < this._factories.length; i++) {
                            this._factories[i][methodName].apply(this._factories[i], arguments);
                        }
                    };
                })(property);
            }
        }
    })(ComposeUiFactory.prototype);
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
function AdvocacyMessageComposer(settings) {
    this.composerName = 'messageComposer';
    this.honorificRequired = settings.honorificRequired;
    this.parentSectionSelector = '.jsComposeMessage';
    this.deliveriesProperty = 'messageDeliveries';
    this.deliverabilityKey = 'messageDeliverability';
    this.maxMessageLengthDefault = 10000; //todo: tie these to config value MaxPossibleMessageLength
    this.targetedMessageDeliverableProperty = 'messageDeliverable';
    this.respondForwardButtonTextTranslationKey = 'ButtonTextSendMessage';
    this.recipientsBeforeMore = 3;
}

(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    AdvocacyMessageComposer.prototype = {
        getParentSection: function() {
            return $('#stepMessage3');
        },
        setRespondForwardButtonText: function (respondUi) {
            respondUi.translateButtonText('ButtonTextSendMessage', this.respondForwardButtonTextTranslationKey);
        },
        createRecipientLi: function(match) {
            var li = $('#targetMatchTemplate').clone(true).removeAttr("id");
            li.html(match.name);
            return li;
        },
        acceptsDeliveryOption: function(deliveryOption) {
            return deliveryOption.deliveryMethod !== 'twitter' &&
                deliveryOption.deliveryMethod !== 'facebook' &&
                deliveryOption.deliveryMethod !== 'phone';
        },
        composeMessageHeaderText: function(count, number, sample, respondUi) {
            var headerText = respondUi.translateLanguage(sample.readOnly == true
                ? 'HeaderReviewNthMessage'
                : 'HeaderComposeNthMessage');
            var messageNumberText = count == 1
                ? respondUi.translateLanguage('TextYour')
                : respondUi.convertOrdinalNumberToName(number);
            return headerText.replace('{0}', messageNumberText);
        },
        buildGroupUiContainer: function (groupsList) {
            return groupsList;
        },
        prepareGroupUiContainerForBinding: function (uiContainer, matchGroups) {
        },
        createGroupUi: function (target) {
            var groupUi = $('#targetGroupingTemplate').clone(true).removeAttr("id");
            groupUi.find('span').html(target.displayName);
            return groupUi;
        },
        buildBinder: function (defaultBinder) {
            return defaultBinder;
        },
        buildUndeliverableTargetUi: function (application, li, respondUi) {
            li.append($("<span class='vv-target-undeliverable-message'>&nbsp;(" + respondUi.translateLanguage('TargetUndeliverable') + ")</span>"));
            li.addClass("vv-target-undeliverable");
        },
        buildDeliveryTargetUi: function (application, li, match, option, targetedMessage, respondUi) {
        },
        createGuidelinesUi: function () {
            return $('#guidelinesTemplate').clone(true).removeAttr("id");
        },
        createSuggestedMessageUi: function() {
            return $();
        },
        createComposeUi: function() {
            return $('#composeTemplate').clone(true).removeAttr("id");
        },
        createComposeElements: function(suggestedMessageUi, composeUi) {
            return {
                composeUi: composeUi,
                subjectInput: composeUi.find('.subjectInput'),
                messageInput: composeUi.find('.messageInput'),
                messageCounter: composeUi.find('.messageCounter'),
                signatureInput: composeUi.find('.signatureInput')
            };
        },
        getJoinedMessageLength: function (composeElements) {
            var opening = composeElements.opening;
            var messageInput = composeElements.messageInput;
            var closing = composeElements.closing;

            var parts = new Array();
            if (opening !== undefined && opening != null) {
                if (opening.val() !== "")
                    parts.push(opening.val());
            }
            if (messageInput !== undefined && messageInput != null) {
                if (messageInput.val() !== "")
                    parts.push(messageInput.val());
            }
            if (closing !== undefined && closing != null) {
                if (closing.val() !== "")
                    parts.push(closing.val());
            }
            var joinedMessage = parts.join("\r\n\r\n");
            return joinedMessage.length;
        },
        bindComposeElements: function(composeElements, tm, sm, composeUi, respondUi) {
            var deliverable = tm[this.targetedMessageDeliverableProperty];
            var subjectInput = composeElements.subjectInput;
            var messageInput = composeElements.messageInput;
            var signatureInput = composeElements.signatureInput;

            deliverable.sampleMessageId = sm.sampleMessageId;
            deliverable.subject = sm.subject;
            deliverable.body = sm.message;
            deliverable.complimentaryClose = respondUi.translateLanguage('Sincerely');
            //deliverable.modified = false;

            // wire subject
            subjectInput.val(sm.subject);
            subjectInput.change(function() {
                deliverable.subject = this.value;
                //deliverable.modified = true;
            });
            // wire signature
            var application = respondUi._application;
            signatureInput.val($.trim(application._userTracker.getIdentity().signature));
            signatureInput.change(function() {
                application.customizeSignature($.trim(this.value));
            });

            var hasParagraphs = this.hasParagraphs(tm);

            messageInput.change(function() {
                deliverable.body = this.value;
                //deliverable.modified = true;
            });
            this._readonlyMessage(sm.readOnly === true && hasParagraphs === false, sm.showOpeningAndClosing, composeElements, respondUi);

            // respondUi.messageContentChanged binding previously done here to update "Remaining" character count
            if (sm.showOpeningAndClosing === true) {
                //opening = $('<label>' + respondUi.translateLanguage('OpeningComments') + '</label>').insertAfter(opening);
                //closing = $('<label>' + respondUi.translateLanguage('ClosingComments') + '</label>').insertAfter(closing);

                var opening = $('<textarea autocomplete="messageInputAux" class="messageInputAux" spellcheck="true" placeholder="' + respondUi.translateLanguage('AdvocacyOpeningPlaceholder') + '"></textarea>').placeholder().insertBefore(messageInput);
                opening.change(function() {
                    deliverable.opening = this.value;
                    //deliverable.modified = true;
                });

                //var closing = $('<textarea autocomplete="messageInputAux" class="messageInputAux" spellcheck="true" placeholder="' + respondUi.translateLanguage('AdvocacyClosingPlaceholder') + '"></textarea>').placeholder().insertAfter(messageInput);
                //closing.change(function() {
                //    deliverable.closing = this.value;
                //    deliverable.modified = true;
                //});

                deliverable.closing = '';

                composeElements.opening = opening;
                //composeElements.closing = closing;
                opening.keyup(function(eventObject) { respondUi.messageContentChanged.dispatch(); });
                //closing.keyup(function(eventObject) { respondUi.messageContentChanged.dispatch(); });
            }

            // wire body
            messageInput.keyup(function(eventObject) { respondUi.messageContentChanged.dispatch(); });

            if (hasParagraphs) {
                bindParagraph(composeUi, tm, deliverable);
            } else {
                // wire body
                messageInput.val(sm.message);
                respondUi.messageContentChanged.dispatch();
            }

            function bindParagraph(ui, targetedMessage, deliverable) {
                if (targetedMessage.messages.length < 1) return;

                var paragraphTemplate = $('#paragraphTemplate');
                var paragraphListContainer = $('#paragraphListContainerTemplate').clone();
                paragraphListContainer.removeAttr('id');
                var paragraphList = paragraphListContainer.find('ul');

                var sampleMessage = targetedMessage.messages[0];
                for (var i = 0; i < sampleMessage.paragraphs.length; i++) {
                    var content = sampleMessage.paragraphs[i].text;
                    var paragraph = paragraphTemplate.clone();
                    paragraph.removeAttr('id');
                    paragraph.find('div').html(content);
                    paragraphList.append(paragraph);
                }

                ui.append(paragraphListContainer);

                if (sampleMessage.readOnly === true) {
                    ui.find('.messageInput').attr('readonly', 'readonly');
                    ui.find('.subjectInput').attr('readonly', 'readonly');
                }

                paragraphListContainer.find('li > a').click(function() {
                    toggleItem($(this));
                });
                paragraphList.sortable({
                    placeholder: "vvParagraphMovePlaceholder"
                });
                paragraphList.disableSelection();

                ui.find('.messageInput').click(function() {
                    var messageInput = $(this);
                    messageInput.blur();

                    var dialog = paragraphListContainer;
                    dialog.addClass('vvParagraphListContainer');

                    dialog.find('.vvNotificationButton').unbind('click');
                    dialog.find('.vvParagraphOkayButton').click(function() {
                        var paragraphs = dialog.find('li > div');
                        var composedParagraphText = '';
                        for (var i = 0; i < paragraphs.length; i++) {
                            var paragraph = $(paragraphs[i]);
                            if (paragraph.attr('hiddenText') === undefined) {
                                composedParagraphText += paragraph.text();
                                composedParagraphText += '\r\n\r\n';
                            }
                        }
                        composedParagraphText = $.trim(composedParagraphText);
                        messageInput.val(composedParagraphText);

                        deliverable.sampleMessageId = sampleMessage.sampleMessageId;
                        deliverable.body = composedParagraphText;
                        //deliverable.modified = false;

                        if (sampleMessage.readOnly === false) {
                            messageInput.unbind("click");
                        }

                        messageInput.keyup();

                        dialog.dialog('close');
                    });
                    dialog.find('.vvParagraphCancelButton').click(function() {
                        dialog.dialog('close');
                    });

                    respondUi.openModalDialog(dialog,
                    {
                        draggable: true,
                        resizable: false,
                        width: $('.vvBody').first().width() - 10 + 'px',
                        showCloseX: false,
                        title: respondUi.translateLanguage('ComposeMessage'),
                        removeDialogContainer: false
                    });
                });

                function toggleItem(link) {
                    var parent = link.parent();
                    var div = parent.find('div').first();
                    var list = parent.parent();
                    if (link.text() === respondUi.translateLanguage('Hide')) {
                        link.text(respondUi.translateLanguage('Show'));

                        parent.remove();
                        parent.addClass('vvParagraphItemHidden');
                        list.append(parent);

                        div.attr('hiddenText', div.html());
                        div.html('&nbsp;');

                        link.click(function() {
                            toggleItem(link);
                        });
                    } else {
                        link.text(respondUi.translateLanguage('Hide'));
                        parent.removeClass('vvParagraphItemHidden');
                        div.html(div.attr('hiddenText'));
                        div.removeAttr('hiddenText');
                    }
                }
            }
        },
        hasParagraphs: function(tm) {
            var hasParagraphs = false;
            for (var i = 0; i < tm.messages.length; i++) {
                if (tm.messages[i].paragraphs !== undefined) {
                    hasParagraphs = true;
                    break;
                }
            }
            return hasParagraphs;
        },
        hasMessageBodyChanged: function(composeElements, tm) {
            var messageInput = composeElements.messageInput;
            var originalSampleMessageBody = tm.messages[tm.messages.currentIndex].message;

            return $.trim(messageInput.val().replace(/\r/g, '')) !== $.trim(originalSampleMessageBody.replace(/\r/g, ''));
        },
        hasSubjectChanged: function(composeElements, tm) {
            var subjectInput = composeElements.subjectInput;
            var originalSubject = tm.messages[tm.messages.currentIndex].subject;

            return $.trim(subjectInput.val()) !== $.trim(originalSubject);
        },
        showNewSampleMessage: function(composeElements, tm, message, respondUi) {
            var subjectInput = composeElements.subjectInput;
            var messageInput = composeElements.messageInput;
            var deliverable = tm[this.targetedMessageDeliverableProperty];

            subjectInput.val(message.subject);
            messageInput.val(message.message);
            deliverable.sampleMessageId = message.sampleMessageId;
            deliverable.subject = message.subject;
            deliverable.body = message.message;
            //deliverable.modified = false;

            this._readonlyMessage(message.readOnly === true, message.showOpeningAndClosing, composeElements, respondUi);
        },
        _readonlyMessage: function(readOnly, showOpeningAndClosing, composeElements, respondUi) {
            var subjectInput = composeElements.subjectInput;
            var messageInput = composeElements.messageInput;
            var messageCounter = composeElements.messageCounter;

            subjectInput.toggleClass('readonlyMessage', readOnly);
            messageInput.toggleClass('readonlyMessage', readOnly);
            if (readOnly) {
                messageInput.attr('readonly', "readonly");
                subjectInput.attr('readonly', "readonly");
                messageInput.css('height', '0');
                if ($('#stepMessage3').is(':visible') === true) {
                    respondUi.finishReadonlyMessageResize(messageInput);
                }
            } else {
                messageInput.removeAttr('readonly');
                subjectInput.removeAttr('readonly');
                messageInput.css('height', '');
            }
            //messageCounter.parent().toggle(!readOnly || showOpeningAndClosing === true);
        },
        toggleListVisibility: function (configuration) {
            configuration.groupsList.hide();
            configuration.collapsedGroupsList.toggle(configuration.showGroupsList);
            configuration.expandedGroupsList.hide();
            configuration.groupsToggleLink.toggle(configuration.showGroupsList && configuration.expandedGroupsList.children().length > 0);

            configuration.allRecipientsUi.hide();
            configuration.collapsedRecipientsUi.toggle(!configuration.showGroupsList);
            configuration.expandedRecipientsUi.hide();
            configuration.recipientsToggleLink.toggle(!configuration.showGroupsList && configuration.expandedRecipientsUi.children().length > 0);
        }
    };
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
(function(){if(typeof twttr==="undefined"||twttr===null){var twttr={}}twttr.txt={};twttr.txt.regexen={};var HTML_ENTITIES={"&":"&amp;",">":"&gt;","<":"&lt;",'"':"&quot;","'":"&#39;"};twttr.txt.htmlEscape=function(text){return text&&text.replace(/[&"'><]/g,function(character){return HTML_ENTITIES[character]})};function regexSupplant(regex,flags){flags=flags||"";if(typeof regex!=="string"){if(regex.global&&flags.indexOf("g")<0){flags+="g"}if(regex.ignoreCase&&flags.indexOf("i")<0){flags+="i"}if(regex.multiline&&flags.indexOf("m")<0){flags+="m"}regex=regex.source}return new RegExp(regex.replace(/#\{(\w+)\}/g,function(match,name){var newRegex=twttr.txt.regexen[name]||"";if(typeof newRegex!=="string"){newRegex=newRegex.source}return newRegex}),flags)}twttr.txt.regexSupplant=regexSupplant;function stringSupplant(str,values){return str.replace(/#\{(\w+)\}/g,function(match,name){return values[name]||""})}twttr.txt.stringSupplant=stringSupplant;function addCharsToCharClass(charClass,start,end){var s=String.fromCharCode(start);if(end!==start){s+="-"+String.fromCharCode(end)}charClass.push(s);return charClass}twttr.txt.addCharsToCharClass=addCharsToCharClass;var fromCode=String.fromCharCode;var UNICODE_SPACES=[fromCode(32),fromCode(133),fromCode(160),fromCode(5760),fromCode(6158),fromCode(8232),fromCode(8233),fromCode(8239),fromCode(8287),fromCode(12288)];addCharsToCharClass(UNICODE_SPACES,9,13);addCharsToCharClass(UNICODE_SPACES,8192,8202);var INVALID_CHARS=[fromCode(65534),fromCode(65279),fromCode(65535)];addCharsToCharClass(INVALID_CHARS,8234,8238);twttr.txt.regexen.spaces_group=regexSupplant(UNICODE_SPACES.join(""));twttr.txt.regexen.spaces=regexSupplant("["+UNICODE_SPACES.join("")+"]");twttr.txt.regexen.invalid_chars_group=regexSupplant(INVALID_CHARS.join(""));twttr.txt.regexen.punct=/\!'#%&'\(\)*\+,\\\-\.\/:;<=>\?@\[\]\^_{|}~\$/;twttr.txt.regexen.rtl_chars=/[\u0600-\u06FF]|[\u0750-\u077F]|[\u0590-\u05FF]|[\uFE70-\uFEFF]/gm;twttr.txt.regexen.non_bmp_code_pairs=/[\uD800-\uDBFF][\uDC00-\uDFFF]/gm;var nonLatinHashtagChars=[];addCharsToCharClass(nonLatinHashtagChars,1024,1279);addCharsToCharClass(nonLatinHashtagChars,1280,1319);addCharsToCharClass(nonLatinHashtagChars,11744,11775);addCharsToCharClass(nonLatinHashtagChars,42560,42655);addCharsToCharClass(nonLatinHashtagChars,1425,1471);addCharsToCharClass(nonLatinHashtagChars,1473,1474);addCharsToCharClass(nonLatinHashtagChars,1476,1477);addCharsToCharClass(nonLatinHashtagChars,1479,1479);addCharsToCharClass(nonLatinHashtagChars,1488,1514);addCharsToCharClass(nonLatinHashtagChars,1520,1524);addCharsToCharClass(nonLatinHashtagChars,64274,64296);addCharsToCharClass(nonLatinHashtagChars,64298,64310);addCharsToCharClass(nonLatinHashtagChars,64312,64316);addCharsToCharClass(nonLatinHashtagChars,64318,64318);addCharsToCharClass(nonLatinHashtagChars,64320,64321);addCharsToCharClass(nonLatinHashtagChars,64323,64324);addCharsToCharClass(nonLatinHashtagChars,64326,64335);addCharsToCharClass(nonLatinHashtagChars,1552,1562);addCharsToCharClass(nonLatinHashtagChars,1568,1631);addCharsToCharClass(nonLatinHashtagChars,1646,1747);addCharsToCharClass(nonLatinHashtagChars,1749,1756);addCharsToCharClass(nonLatinHashtagChars,1758,1768);addCharsToCharClass(nonLatinHashtagChars,1770,1775);addCharsToCharClass(nonLatinHashtagChars,1786,1788);addCharsToCharClass(nonLatinHashtagChars,1791,1791);addCharsToCharClass(nonLatinHashtagChars,1872,1919);addCharsToCharClass(nonLatinHashtagChars,2208,2208);addCharsToCharClass(nonLatinHashtagChars,2210,2220);addCharsToCharClass(nonLatinHashtagChars,2276,2302);addCharsToCharClass(nonLatinHashtagChars,64336,64433);addCharsToCharClass(nonLatinHashtagChars,64467,64829);addCharsToCharClass(nonLatinHashtagChars,64848,64911);addCharsToCharClass(nonLatinHashtagChars,64914,64967);addCharsToCharClass(nonLatinHashtagChars,65008,65019);addCharsToCharClass(nonLatinHashtagChars,65136,65140);addCharsToCharClass(nonLatinHashtagChars,65142,65276);addCharsToCharClass(nonLatinHashtagChars,8204,8204);addCharsToCharClass(nonLatinHashtagChars,3585,3642);addCharsToCharClass(nonLatinHashtagChars,3648,3662);addCharsToCharClass(nonLatinHashtagChars,4352,4607);addCharsToCharClass(nonLatinHashtagChars,12592,12677);addCharsToCharClass(nonLatinHashtagChars,43360,43391);addCharsToCharClass(nonLatinHashtagChars,44032,55215);addCharsToCharClass(nonLatinHashtagChars,55216,55295);addCharsToCharClass(nonLatinHashtagChars,65441,65500);addCharsToCharClass(nonLatinHashtagChars,12449,12538);addCharsToCharClass(nonLatinHashtagChars,12540,12542);addCharsToCharClass(nonLatinHashtagChars,65382,65439);addCharsToCharClass(nonLatinHashtagChars,65392,65392);addCharsToCharClass(nonLatinHashtagChars,65296,65305);addCharsToCharClass(nonLatinHashtagChars,65313,65338);addCharsToCharClass(nonLatinHashtagChars,65345,65370);addCharsToCharClass(nonLatinHashtagChars,12353,12438);addCharsToCharClass(nonLatinHashtagChars,12441,12446);addCharsToCharClass(nonLatinHashtagChars,13312,19903);addCharsToCharClass(nonLatinHashtagChars,19968,40959);addCharsToCharClass(nonLatinHashtagChars,173824,177983);addCharsToCharClass(nonLatinHashtagChars,177984,178207);addCharsToCharClass(nonLatinHashtagChars,194560,195103);addCharsToCharClass(nonLatinHashtagChars,12291,12291);addCharsToCharClass(nonLatinHashtagChars,12293,12293);addCharsToCharClass(nonLatinHashtagChars,12347,12347);twttr.txt.regexen.nonLatinHashtagChars=regexSupplant(nonLatinHashtagChars.join(""));var latinAccentChars=[];addCharsToCharClass(latinAccentChars,192,214);addCharsToCharClass(latinAccentChars,216,246);addCharsToCharClass(latinAccentChars,248,255);addCharsToCharClass(latinAccentChars,256,591);addCharsToCharClass(latinAccentChars,595,596);addCharsToCharClass(latinAccentChars,598,599);addCharsToCharClass(latinAccentChars,601,601);addCharsToCharClass(latinAccentChars,603,603);addCharsToCharClass(latinAccentChars,611,611);addCharsToCharClass(latinAccentChars,616,616);addCharsToCharClass(latinAccentChars,623,623);addCharsToCharClass(latinAccentChars,626,626);addCharsToCharClass(latinAccentChars,649,649);addCharsToCharClass(latinAccentChars,651,651);addCharsToCharClass(latinAccentChars,699,699);addCharsToCharClass(latinAccentChars,768,879);addCharsToCharClass(latinAccentChars,7680,7935);twttr.txt.regexen.latinAccentChars=regexSupplant(latinAccentChars.join(""));twttr.txt.regexen.hashSigns=/[#＃]/;twttr.txt.regexen.hashtagAlpha=regexSupplant(/[a-z_#{latinAccentChars}#{nonLatinHashtagChars}]/i);twttr.txt.regexen.hashtagAlphaNumeric=regexSupplant(/[a-z0-9_#{latinAccentChars}#{nonLatinHashtagChars}]/i);twttr.txt.regexen.endHashtagMatch=regexSupplant(/^(?:#{hashSigns}|:\/\/)/);twttr.txt.regexen.hashtagBoundary=regexSupplant(/(?:^|$|[^&a-z0-9_#{latinAccentChars}#{nonLatinHashtagChars}])/);twttr.txt.regexen.validHashtag=regexSupplant(/(#{hashtagBoundary})(#{hashSigns})(#{hashtagAlphaNumeric}*#{hashtagAlpha}#{hashtagAlphaNumeric}*)/gi);twttr.txt.regexen.validMentionPrecedingChars=/(?:^|[^a-zA-Z0-9_!#$%&*@＠]|(?:rt|RT|rT|Rt):?)/;twttr.txt.regexen.atSigns=/[@＠]/;twttr.txt.regexen.validMentionOrList=regexSupplant("(#{validMentionPrecedingChars})"+"(#{atSigns})"+"([a-zA-Z0-9_]{1,20})"+"(/[a-zA-Z][a-zA-Z0-9_-]{0,24})?","g");twttr.txt.regexen.validReply=regexSupplant(/^(?:#{spaces})*#{atSigns}([a-zA-Z0-9_]{1,20})/);twttr.txt.regexen.endMentionMatch=regexSupplant(/^(?:#{atSigns}|[#{latinAccentChars}]|:\/\/)/);twttr.txt.regexen.validUrlPrecedingChars=regexSupplant(/(?:[^A-Za-z0-9@＠$#＃#{invalid_chars_group}]|^)/);twttr.txt.regexen.invalidUrlWithoutProtocolPrecedingChars=/[-_.\/]$/;twttr.txt.regexen.invalidDomainChars=stringSupplant("#{punct}#{spaces_group}#{invalid_chars_group}",twttr.txt.regexen);twttr.txt.regexen.validDomainChars=regexSupplant(/[^#{invalidDomainChars}]/);twttr.txt.regexen.validSubdomain=regexSupplant(/(?:(?:#{validDomainChars}(?:[_-]|#{validDomainChars})*)?#{validDomainChars}\.)/);twttr.txt.regexen.validDomainName=regexSupplant(/(?:(?:#{validDomainChars}(?:-|#{validDomainChars})*)?#{validDomainChars}\.)/);twttr.txt.regexen.validGTLD=regexSupplant(/(?:(?:aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx)(?=[^0-9a-zA-Z@]|$))/);twttr.txt.regexen.validCCTLD=regexSupplant(RegExp("(?:(?:ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|"+"ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|"+"ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|"+"ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|"+"na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|"+"sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|"+"ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)(?=[^0-9a-zA-Z@]|$))"));twttr.txt.regexen.validPunycode=regexSupplant(/(?:xn--[0-9a-z]+)/);twttr.txt.regexen.validDomain=regexSupplant(/(?:#{validSubdomain}*#{validDomainName}(?:#{validGTLD}|#{validCCTLD}|#{validPunycode}))/);twttr.txt.regexen.validAsciiDomain=regexSupplant(/(?:(?:[\-a-z0-9#{latinAccentChars}]+)\.)+(?:#{validGTLD}|#{validCCTLD}|#{validPunycode})/gi);twttr.txt.regexen.invalidShortDomain=regexSupplant(/^#{validDomainName}#{validCCTLD}$/);twttr.txt.regexen.validPortNumber=regexSupplant(/[0-9]+/);twttr.txt.regexen.validGeneralUrlPathChars=regexSupplant(/[a-z0-9!\*';:=\+,\.\$\/%#\[\]\-_~@|&#{latinAccentChars}]/i);twttr.txt.regexen.validUrlBalancedParens=regexSupplant("\\("+"(?:"+"#{validGeneralUrlPathChars}+"+"|"+"(?:"+"#{validGeneralUrlPathChars}*"+"\\("+"#{validGeneralUrlPathChars}+"+"\\)"+"#{validGeneralUrlPathChars}*"+")"+")"+"\\)","i");twttr.txt.regexen.validUrlPathEndingChars=regexSupplant(/[\+\-a-z0-9=_#\/#{latinAccentChars}]|(?:#{validUrlBalancedParens})/i);twttr.txt.regexen.validUrlPath=regexSupplant("(?:"+"(?:"+"#{validGeneralUrlPathChars}*"+"(?:#{validUrlBalancedParens}#{validGeneralUrlPathChars}*)*"+"#{validUrlPathEndingChars}"+")|(?:@#{validGeneralUrlPathChars}+/)"+")","i");twttr.txt.regexen.validUrlQueryChars=/[a-z0-9!?\*'@\(\);:&=\+\$\/%#\[\]\-_\.,~|]/i;twttr.txt.regexen.validUrlQueryEndingChars=/[a-z0-9_&=#\/]/i;twttr.txt.regexen.extractUrl=regexSupplant("("+"(#{validUrlPrecedingChars})"+"("+"(https?:\\/\\/)?"+"(#{validDomain})"+"(?::(#{validPortNumber}))?"+"(\\/#{validUrlPath}*)?"+"(\\?#{validUrlQueryChars}*#{validUrlQueryEndingChars})?"+")"+")","gi");twttr.txt.regexen.validTcoUrl=/^https?:\/\/t\.co\/[a-z0-9]+/i;twttr.txt.regexen.urlHasProtocol=/^https?:\/\//i;twttr.txt.regexen.urlHasHttps=/^https:\/\//i;twttr.txt.regexen.cashtag=/[a-z]{1,6}(?:[._][a-z]{1,2})?/i;twttr.txt.regexen.validCashtag=regexSupplant("(^|#{spaces})(\\$)(#{cashtag})(?=$|\\s|[#{punct}])","gi");twttr.txt.regexen.validateUrlUnreserved=/[a-z0-9\-._~]/i;twttr.txt.regexen.validateUrlPctEncoded=/(?:%[0-9a-f]{2})/i;twttr.txt.regexen.validateUrlSubDelims=/[!$&'()*+,;=]/i;twttr.txt.regexen.validateUrlPchar=regexSupplant("(?:"+"#{validateUrlUnreserved}|"+"#{validateUrlPctEncoded}|"+"#{validateUrlSubDelims}|"+"[:|@]"+")","i");twttr.txt.regexen.validateUrlScheme=/(?:[a-z][a-z0-9+\-.]*)/i;twttr.txt.regexen.validateUrlUserinfo=regexSupplant("(?:"+"#{validateUrlUnreserved}|"+"#{validateUrlPctEncoded}|"+"#{validateUrlSubDelims}|"+":"+")*","i");twttr.txt.regexen.validateUrlDecOctet=/(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9]{2})|(?:2[0-4][0-9])|(?:25[0-5]))/i;twttr.txt.regexen.validateUrlIpv4=regexSupplant(/(?:#{validateUrlDecOctet}(?:\.#{validateUrlDecOctet}){3})/i);twttr.txt.regexen.validateUrlIpv6=/(?:\[[a-f0-9:\.]+\])/i;twttr.txt.regexen.validateUrlIp=regexSupplant("(?:"+"#{validateUrlIpv4}|"+"#{validateUrlIpv6}"+")","i");twttr.txt.regexen.validateUrlSubDomainSegment=/(?:[a-z0-9](?:[a-z0-9_\-]*[a-z0-9])?)/i;twttr.txt.regexen.validateUrlDomainSegment=/(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?)/i;twttr.txt.regexen.validateUrlDomainTld=/(?:[a-z](?:[a-z0-9\-]*[a-z0-9])?)/i;twttr.txt.regexen.validateUrlDomain=regexSupplant(/(?:(?:#{validateUrlSubDomainSegment]}\.)*(?:#{validateUrlDomainSegment]}\.)#{validateUrlDomainTld})/i);twttr.txt.regexen.validateUrlHost=regexSupplant("(?:"+"#{validateUrlIp}|"+"#{validateUrlDomain}"+")","i");twttr.txt.regexen.validateUrlUnicodeSubDomainSegment=/(?:(?:[a-z0-9]|[^\u0000-\u007f])(?:(?:[a-z0-9_\-]|[^\u0000-\u007f])*(?:[a-z0-9]|[^\u0000-\u007f]))?)/i;twttr.txt.regexen.validateUrlUnicodeDomainSegment=/(?:(?:[a-z0-9]|[^\u0000-\u007f])(?:(?:[a-z0-9\-]|[^\u0000-\u007f])*(?:[a-z0-9]|[^\u0000-\u007f]))?)/i;twttr.txt.regexen.validateUrlUnicodeDomainTld=/(?:(?:[a-z]|[^\u0000-\u007f])(?:(?:[a-z0-9\-]|[^\u0000-\u007f])*(?:[a-z0-9]|[^\u0000-\u007f]))?)/i;twttr.txt.regexen.validateUrlUnicodeDomain=regexSupplant(/(?:(?:#{validateUrlUnicodeSubDomainSegment}\.)*(?:#{validateUrlUnicodeDomainSegment}\.)#{validateUrlUnicodeDomainTld})/i);twttr.txt.regexen.validateUrlUnicodeHost=regexSupplant("(?:"+"#{validateUrlIp}|"+"#{validateUrlUnicodeDomain}"+")","i");twttr.txt.regexen.validateUrlPort=/[0-9]{1,5}/;twttr.txt.regexen.validateUrlUnicodeAuthority=regexSupplant("(?:(#{validateUrlUserinfo})@)?"+"(#{validateUrlUnicodeHost})"+"(?::(#{validateUrlPort}))?","i");twttr.txt.regexen.validateUrlAuthority=regexSupplant("(?:(#{validateUrlUserinfo})@)?"+"(#{validateUrlHost})"+"(?::(#{validateUrlPort}))?","i");twttr.txt.regexen.validateUrlPath=regexSupplant(/(\/#{validateUrlPchar}*)*/i);twttr.txt.regexen.validateUrlQuery=regexSupplant(/(#{validateUrlPchar}|\/|\?)*/i);twttr.txt.regexen.validateUrlFragment=regexSupplant(/(#{validateUrlPchar}|\/|\?)*/i);twttr.txt.regexen.validateUrlUnencoded=regexSupplant("^"+"(?:"+"([^:/?#]+):\\/\\/"+")?"+"([^/?#]*)"+"([^?#]*)"+"(?:"+"\\?([^#]*)"+")?"+"(?:"+"#(.*)"+")?$","i");var DEFAULT_LIST_CLASS="tweet-url list-slug";var DEFAULT_USERNAME_CLASS="tweet-url username";var DEFAULT_HASHTAG_CLASS="tweet-url hashtag";var DEFAULT_CASHTAG_CLASS="tweet-url cashtag";var OPTIONS_NOT_ATTRIBUTES={urlClass:true,listClass:true,usernameClass:true,hashtagClass:true,cashtagClass:true,usernameUrlBase:true,listUrlBase:true,hashtagUrlBase:true,cashtagUrlBase:true,usernameUrlBlock:true,listUrlBlock:true,hashtagUrlBlock:true,linkUrlBlock:true,usernameIncludeSymbol:true,suppressLists:true,suppressNoFollow:true,targetBlank:true,suppressDataScreenName:true,urlEntities:true,symbolTag:true,textWithSymbolTag:true,urlTarget:true,invisibleTagAttrs:true,linkAttributeBlock:true,linkTextBlock:true,htmlEscapeNonEntities:true};var BOOLEAN_ATTRIBUTES={disabled:true,readonly:true,multiple:true,checked:true};function clone(o){var r={};for(var k in o){if(o.hasOwnProperty(k)){r[k]=o[k]}}return r}twttr.txt.tagAttrs=function(attributes){var htmlAttrs="";for(var k in attributes){var v=attributes[k];if(BOOLEAN_ATTRIBUTES[k]){v=v?k:null}if(v==null)continue;htmlAttrs+=" "+twttr.txt.htmlEscape(k)+'="'+twttr.txt.htmlEscape(v.toString())+'"'}return htmlAttrs};twttr.txt.linkToText=function(entity,text,attributes,options){if(!options.suppressNoFollow){attributes.rel="nofollow"}if(options.linkAttributeBlock){options.linkAttributeBlock(entity,attributes)}if(options.linkTextBlock){text=options.linkTextBlock(entity,text)}var d={text:text,attr:twttr.txt.tagAttrs(attributes)};return stringSupplant("<a#{attr}>#{text}</a>",d)};twttr.txt.linkToTextWithSymbol=function(entity,symbol,text,attributes,options){var taggedSymbol=options.symbolTag?"<"+options.symbolTag+">"+symbol+"</"+options.symbolTag+">":symbol;text=twttr.txt.htmlEscape(text);var taggedText=options.textWithSymbolTag?"<"+options.textWithSymbolTag+">"+text+"</"+options.textWithSymbolTag+">":text;if(options.usernameIncludeSymbol||!symbol.match(twttr.txt.regexen.atSigns)){return twttr.txt.linkToText(entity,taggedSymbol+taggedText,attributes,options)}else{return taggedSymbol+twttr.txt.linkToText(entity,taggedText,attributes,options)}};twttr.txt.linkToHashtag=function(entity,text,options){var hash=text.substring(entity.indices[0],entity.indices[0]+1);var hashtag=twttr.txt.htmlEscape(entity.hashtag);var attrs=clone(options.htmlAttrs||{});attrs.href=options.hashtagUrlBase+hashtag;attrs.title="#"+hashtag;attrs["class"]=options.hashtagClass;if(hashtag.charAt(0).match(twttr.txt.regexen.rtl_chars)){attrs["class"]+=" rtl"}if(options.targetBlank){attrs.target="_blank"}return twttr.txt.linkToTextWithSymbol(entity,hash,hashtag,attrs,options)};twttr.txt.linkToCashtag=function(entity,text,options){var cashtag=twttr.txt.htmlEscape(entity.cashtag);var attrs=clone(options.htmlAttrs||{});attrs.href=options.cashtagUrlBase+cashtag;attrs.title="$"+cashtag;attrs["class"]=options.cashtagClass;if(options.targetBlank){attrs.target="_blank"}return twttr.txt.linkToTextWithSymbol(entity,"$",cashtag,attrs,options)};twttr.txt.linkToMentionAndList=function(entity,text,options){var at=text.substring(entity.indices[0],entity.indices[0]+1);var user=twttr.txt.htmlEscape(entity.screenName);var slashListname=twttr.txt.htmlEscape(entity.listSlug);var isList=entity.listSlug&&!options.suppressLists;var attrs=clone(options.htmlAttrs||{});attrs["class"]=isList?options.listClass:options.usernameClass;attrs.href=isList?options.listUrlBase+user+slashListname:options.usernameUrlBase+user;if(!isList&&!options.suppressDataScreenName){attrs["data-screen-name"]=user}if(options.targetBlank){attrs.target="_blank"}return twttr.txt.linkToTextWithSymbol(entity,at,isList?user+slashListname:user,attrs,options)};twttr.txt.linkToUrl=function(entity,text,options){var url=entity.url;var displayUrl=url;var linkText=twttr.txt.htmlEscape(displayUrl);var urlEntity=options.urlEntities&&options.urlEntities[url]||entity;if(urlEntity.display_url){linkText=twttr.txt.linkTextWithEntity(urlEntity,options)}var attrs=clone(options.htmlAttrs||{});if(!url.match(twttr.txt.regexen.urlHasProtocol)){url="http://"+url}attrs.href=url;if(options.targetBlank){attrs.target="_blank"}if(options.urlClass){attrs["class"]=options.urlClass}if(options.urlTarget){attrs.target=options.urlTarget}if(!options.title&&urlEntity.display_url){attrs.title=urlEntity.expanded_url}return twttr.txt.linkToText(entity,linkText,attrs,options)};twttr.txt.linkTextWithEntity=function(entity,options){var displayUrl=entity.display_url;var expandedUrl=entity.expanded_url;var displayUrlSansEllipses=displayUrl.replace(/…/g,"");if(expandedUrl.indexOf(displayUrlSansEllipses)!=-1){var displayUrlIndex=expandedUrl.indexOf(displayUrlSansEllipses);var v={displayUrlSansEllipses:displayUrlSansEllipses,beforeDisplayUrl:expandedUrl.substr(0,displayUrlIndex),afterDisplayUrl:expandedUrl.substr(displayUrlIndex+displayUrlSansEllipses.length),precedingEllipsis:displayUrl.match(/^…/)?"…":"",followingEllipsis:displayUrl.match(/…$/)?"…":""};for(var k in v){if(v.hasOwnProperty(k)){v[k]=twttr.txt.htmlEscape(v[k])}}v["invisible"]=options.invisibleTagAttrs;return stringSupplant("<span class='tco-ellipsis'>#{precedingEllipsis}<span #{invisible}>&nbsp;</span></span><span #{invisible}>#{beforeDisplayUrl}</span><span class='js-display-url'>#{displayUrlSansEllipses}</span><span #{invisible}>#{afterDisplayUrl}</span><span class='tco-ellipsis'><span #{invisible}>&nbsp;</span>#{followingEllipsis}</span>",v)}return displayUrl};twttr.txt.autoLinkEntities=function(text,entities,options){options=clone(options||{});options.hashtagClass=options.hashtagClass||DEFAULT_HASHTAG_CLASS;options.hashtagUrlBase=options.hashtagUrlBase||"https://twitter.com/#!/search?q=%23";options.cashtagClass=options.cashtagClass||DEFAULT_CASHTAG_CLASS;options.cashtagUrlBase=options.cashtagUrlBase||"https://twitter.com/#!/search?q=%24";options.listClass=options.listClass||DEFAULT_LIST_CLASS;options.usernameClass=options.usernameClass||DEFAULT_USERNAME_CLASS;options.usernameUrlBase=options.usernameUrlBase||"https://twitter.com/";options.listUrlBase=options.listUrlBase||"https://twitter.com/";options.htmlAttrs=twttr.txt.extractHtmlAttrsFromOptions(options);options.invisibleTagAttrs=options.invisibleTagAttrs||"style='position:absolute;left:-9999px;'";var urlEntities,i,len;if(options.urlEntities){urlEntities={};for(i=0,len=options.urlEntities.length;i<len;i++){urlEntities[options.urlEntities[i].url]=options.urlEntities[i]}options.urlEntities=urlEntities}var result="";var beginIndex=0;entities.sort(function(a,b){return a.indices[0]-b.indices[0]});var nonEntity=options.htmlEscapeNonEntities?twttr.txt.htmlEscape:function(text){return text};for(var i=0;i<entities.length;i++){var entity=entities[i];result+=nonEntity(text.substring(beginIndex,entity.indices[0]));if(entity.url){result+=twttr.txt.linkToUrl(entity,text,options)}else if(entity.hashtag){result+=twttr.txt.linkToHashtag(entity,text,options)}else if(entity.screenName){result+=twttr.txt.linkToMentionAndList(entity,text,options)}else if(entity.cashtag){result+=twttr.txt.linkToCashtag(entity,text,options)}beginIndex=entity.indices[1]}result+=nonEntity(text.substring(beginIndex,text.length));return result};twttr.txt.autoLinkWithJSON=function(text,json,options){var entities=[];for(var key in json){entities=entities.concat(json[key])}for(var i=0;i<entities.length;i++){entity=entities[i];if(entity.screen_name){entity.screenName=entity.screen_name}else if(entity.text){entity.hashtag=entity.text}}twttr.txt.modifyIndicesFromUnicodeToUTF16(text,entities);return twttr.txt.autoLinkEntities(text,entities,options)};twttr.txt.extractHtmlAttrsFromOptions=function(options){var htmlAttrs={};for(var k in options){var v=options[k];if(OPTIONS_NOT_ATTRIBUTES[k])continue;if(BOOLEAN_ATTRIBUTES[k]){v=v?k:null}if(v==null)continue;htmlAttrs[k]=v}return htmlAttrs};twttr.txt.autoLink=function(text,options){var entities=twttr.txt.extractEntitiesWithIndices(text,{extractUrlsWithoutProtocol:false});return twttr.txt.autoLinkEntities(text,entities,options)};twttr.txt.autoLinkUsernamesOrLists=function(text,options){var entities=twttr.txt.extractMentionsOrListsWithIndices(text);return twttr.txt.autoLinkEntities(text,entities,options)};twttr.txt.autoLinkHashtags=function(text,options){var entities=twttr.txt.extractHashtagsWithIndices(text);return twttr.txt.autoLinkEntities(text,entities,options)};twttr.txt.autoLinkCashtags=function(text,options){var entities=twttr.txt.extractCashtagsWithIndices(text);return twttr.txt.autoLinkEntities(text,entities,options)};twttr.txt.autoLinkUrlsCustom=function(text,options){var entities=twttr.txt.extractUrlsWithIndices(text,{extractUrlsWithoutProtocol:false});return twttr.txt.autoLinkEntities(text,entities,options)};twttr.txt.removeOverlappingEntities=function(entities){entities.sort(function(a,b){return a.indices[0]-b.indices[0]});var prev=entities[0];for(var i=1;i<entities.length;i++){if(prev.indices[1]>entities[i].indices[0]){entities.splice(i,1);i--}else{prev=entities[i]}}};twttr.txt.extractEntitiesWithIndices=function(text,options){var entities=twttr.txt.extractUrlsWithIndices(text,options).concat(twttr.txt.extractMentionsOrListsWithIndices(text)).concat(twttr.txt.extractHashtagsWithIndices(text,{checkUrlOverlap:false})).concat(twttr.txt.extractCashtagsWithIndices(text));if(entities.length==0){return[]}twttr.txt.removeOverlappingEntities(entities);return entities};twttr.txt.extractMentions=function(text){var screenNamesOnly=[],screenNamesWithIndices=twttr.txt.extractMentionsWithIndices(text);for(var i=0;i<screenNamesWithIndices.length;i++){var screenName=screenNamesWithIndices[i].screenName;screenNamesOnly.push(screenName)}return screenNamesOnly};twttr.txt.extractMentionsWithIndices=function(text){var mentions=[],mentionOrList,mentionsOrLists=twttr.txt.extractMentionsOrListsWithIndices(text);for(var i=0;i<mentionsOrLists.length;i++){mentionOrList=mentionsOrLists[i];if(mentionOrList.listSlug==""){mentions.push({screenName:mentionOrList.screenName,indices:mentionOrList.indices})}}return mentions};twttr.txt.extractMentionsOrListsWithIndices=function(text){if(!text||!text.match(twttr.txt.regexen.atSigns)){return[]}var possibleNames=[],slashListname;text.replace(twttr.txt.regexen.validMentionOrList,function(match,before,atSign,screenName,slashListname,offset,chunk){var after=chunk.slice(offset+match.length);if(!after.match(twttr.txt.regexen.endMentionMatch)){slashListname=slashListname||"";var startPosition=offset+before.length;var endPosition=startPosition+screenName.length+slashListname.length+1;possibleNames.push({screenName:screenName,listSlug:slashListname,indices:[startPosition,endPosition]})}});return possibleNames};twttr.txt.extractReplies=function(text){if(!text){return null}var possibleScreenName=text.match(twttr.txt.regexen.validReply);if(!possibleScreenName||RegExp.rightContext.match(twttr.txt.regexen.endMentionMatch)){return null}return possibleScreenName[1]};twttr.txt.extractUrls=function(text,options){var urlsOnly=[],urlsWithIndices=twttr.txt.extractUrlsWithIndices(text,options);for(var i=0;i<urlsWithIndices.length;i++){urlsOnly.push(urlsWithIndices[i].url)}return urlsOnly};twttr.txt.extractUrlsWithIndices=function(text,options){if(!options){options={extractUrlsWithoutProtocol:true}}if(!text||(options.extractUrlsWithoutProtocol?!text.match(/\./):!text.match(/:/))){return[]}var urls=[];while(twttr.txt.regexen.extractUrl.exec(text)){var before=RegExp.$2,url=RegExp.$3,protocol=RegExp.$4,domain=RegExp.$5,path=RegExp.$7;var endPosition=twttr.txt.regexen.extractUrl.lastIndex,startPosition=endPosition-url.length;if(!protocol){if(!options.extractUrlsWithoutProtocol||before.match(twttr.txt.regexen.invalidUrlWithoutProtocolPrecedingChars)){continue}var lastUrl=null,lastUrlInvalidMatch=false,asciiEndPosition=0;domain.replace(twttr.txt.regexen.validAsciiDomain,function(asciiDomain){var asciiStartPosition=domain.indexOf(asciiDomain,asciiEndPosition);asciiEndPosition=asciiStartPosition+asciiDomain.length;lastUrl={url:asciiDomain,indices:[startPosition+asciiStartPosition,startPosition+asciiEndPosition]};lastUrlInvalidMatch=asciiDomain.match(twttr.txt.regexen.invalidShortDomain);if(!lastUrlInvalidMatch){urls.push(lastUrl)}});if(lastUrl==null){continue}if(path){if(lastUrlInvalidMatch){urls.push(lastUrl)}lastUrl.url=url.replace(domain,lastUrl.url);lastUrl.indices[1]=endPosition}}else{if(url.match(twttr.txt.regexen.validTcoUrl)){url=RegExp.lastMatch;endPosition=startPosition+url.length}urls.push({url:url,indices:[startPosition,endPosition]})}}return urls};twttr.txt.extractHashtags=function(text){var hashtagsOnly=[],hashtagsWithIndices=twttr.txt.extractHashtagsWithIndices(text);for(var i=0;i<hashtagsWithIndices.length;i++){hashtagsOnly.push(hashtagsWithIndices[i].hashtag)}return hashtagsOnly};twttr.txt.extractHashtagsWithIndices=function(text,options){if(!options){options={checkUrlOverlap:true}}if(!text||!text.match(twttr.txt.regexen.hashSigns)){return[]}var tags=[];text.replace(twttr.txt.regexen.validHashtag,function(match,before,hash,hashText,offset,chunk){var after=chunk.slice(offset+match.length);if(after.match(twttr.txt.regexen.endHashtagMatch))return;var startPosition=offset+before.length;var endPosition=startPosition+hashText.length+1;tags.push({hashtag:hashText,indices:[startPosition,endPosition]})});if(options.checkUrlOverlap){var urls=twttr.txt.extractUrlsWithIndices(text);if(urls.length>0){var entities=tags.concat(urls);twttr.txt.removeOverlappingEntities(entities);tags=[];for(var i=0;i<entities.length;i++){if(entities[i].hashtag){tags.push(entities[i])}}}}return tags};twttr.txt.extractCashtags=function(text){var cashtagsOnly=[],cashtagsWithIndices=twttr.txt.extractCashtagsWithIndices(text);for(var i=0;i<cashtagsWithIndices.length;i++){cashtagsOnly.push(cashtagsWithIndices[i].cashtag)}return cashtagsOnly};twttr.txt.extractCashtagsWithIndices=function(text){if(!text||text.indexOf("$")==-1){return[]}var tags=[];text.replace(twttr.txt.regexen.validCashtag,function(match,before,dollar,cashtag,offset,chunk){var startPosition=offset+before.length;var endPosition=startPosition+cashtag.length+1;tags.push({cashtag:cashtag,indices:[startPosition,endPosition]})});return tags};twttr.txt.modifyIndicesFromUnicodeToUTF16=function(text,entities){twttr.txt.convertUnicodeIndices(text,entities,false)};twttr.txt.modifyIndicesFromUTF16ToUnicode=function(text,entities){twttr.txt.convertUnicodeIndices(text,entities,true)};twttr.txt.getUnicodeTextLength=function(text){return text.replace(twttr.txt.regexen.non_bmp_code_pairs," ").length};twttr.txt.convertUnicodeIndices=function(text,entities,indicesInUTF16){if(entities.length==0){return}var charIndex=0;var codePointIndex=0;entities.sort(function(a,b){return a.indices[0]-b.indices[0]});var entityIndex=0;var entity=entities[0];while(charIndex<text.length){if(entity.indices[0]==(indicesInUTF16?charIndex:codePointIndex)){var len=entity.indices[1]-entity.indices[0];entity.indices[0]=indicesInUTF16?codePointIndex:charIndex;entity.indices[1]=entity.indices[0]+len;entityIndex++;if(entityIndex==entities.length){break}entity=entities[entityIndex]}var c=text.charCodeAt(charIndex);if(55296<=c&&c<=56319&&charIndex<text.length-1){c=text.charCodeAt(charIndex+1);if(56320<=c&&c<=57343){charIndex++}}codePointIndex++;charIndex++}};twttr.txt.splitTags=function(text){var firstSplits=text.split("<"),secondSplits,allSplits=[],split;for(var i=0;i<firstSplits.length;i+=1){split=firstSplits[i];if(!split){allSplits.push("")}else{secondSplits=split.split(">");for(var j=0;j<secondSplits.length;j+=1){allSplits.push(secondSplits[j])}}}return allSplits};twttr.txt.hitHighlight=function(text,hits,options){var defaultHighlightTag="em";hits=hits||[];options=options||{};if(hits.length===0){return text}var tagName=options.tag||defaultHighlightTag,tags=["<"+tagName+">","</"+tagName+">"],chunks=twttr.txt.splitTags(text),i,j,result="",chunkIndex=0,chunk=chunks[0],prevChunksLen=0,chunkCursor=0,startInChunk=false,chunkChars=chunk,flatHits=[],index,hit,tag,placed,hitSpot;for(i=0;i<hits.length;i+=1){for(j=0;j<hits[i].length;j+=1){flatHits.push(hits[i][j])}}for(index=0;index<flatHits.length;index+=1){hit=flatHits[index];tag=tags[index%2];placed=false;while(chunk!=null&&hit>=prevChunksLen+chunk.length){result+=chunkChars.slice(chunkCursor);if(startInChunk&&hit===prevChunksLen+chunkChars.length){result+=tag;placed=true}if(chunks[chunkIndex+1]){result+="<"+chunks[chunkIndex+1]+">"}prevChunksLen+=chunkChars.length;chunkCursor=0;chunkIndex+=2;chunk=chunks[chunkIndex];chunkChars=chunk;startInChunk=false}if(!placed&&chunk!=null){hitSpot=hit-prevChunksLen;result+=chunkChars.slice(chunkCursor,hitSpot)+tag;chunkCursor=hitSpot;if(index%2===0){startInChunk=true}else{startInChunk=false}}else if(!placed){placed=true;result+=tag}}if(chunk!=null){if(chunkCursor<chunkChars.length){result+=chunkChars.slice(chunkCursor)}for(index=chunkIndex+1;index<chunks.length;index+=1){result+=index%2===0?chunks[index]:"<"+chunks[index]+">"}}return result};var MAX_LENGTH=140;var INVALID_CHARACTERS=[fromCode(65534),fromCode(65279),fromCode(65535),fromCode(8234),fromCode(8235),fromCode(8236),fromCode(8237),fromCode(8238)];twttr.txt.getTweetLength=function(text,options){if(!options){options={short_url_length:22,short_url_length_https:23}}var textLength=twttr.txt.getUnicodeTextLength(text),urlsWithIndices=twttr.txt.extractUrlsWithIndices(text);twttr.txt.modifyIndicesFromUTF16ToUnicode(text,urlsWithIndices);for(var i=0;i<urlsWithIndices.length;i++){textLength+=urlsWithIndices[i].indices[0]-urlsWithIndices[i].indices[1];if(urlsWithIndices[i].url.toLowerCase().match(twttr.txt.regexen.urlHasHttps)){textLength+=options.short_url_length_https}else{textLength+=options.short_url_length}}return textLength};twttr.txt.isInvalidTweet=function(text){if(!text){return"empty"}if(twttr.txt.getTweetLength(text)>MAX_LENGTH){return"too_long"}for(var i=0;i<INVALID_CHARACTERS.length;i++){if(text.indexOf(INVALID_CHARACTERS[i])>=0){return"invalid_characters"}}return false};twttr.txt.isValidTweetText=function(text){return!twttr.txt.isInvalidTweet(text)};twttr.txt.isValidUsername=function(username){if(!username){return false}var extracted=twttr.txt.extractMentions(username);
return extracted.length===1&&extracted[0]===username.slice(1)};var VALID_LIST_RE=regexSupplant(/^#{validMentionOrList}$/);twttr.txt.isValidList=function(usernameList){var match=usernameList.match(VALID_LIST_RE);return!!(match&&match[1]==""&&match[4])};twttr.txt.isValidHashtag=function(hashtag){if(!hashtag){return false}var extracted=twttr.txt.extractHashtags(hashtag);return extracted.length===1&&extracted[0]===hashtag.slice(1)};twttr.txt.isValidUrl=function(url,unicodeDomains,requireProtocol){if(unicodeDomains==null){unicodeDomains=true}if(requireProtocol==null){requireProtocol=true}if(!url){return false}var urlParts=url.match(twttr.txt.regexen.validateUrlUnencoded);if(!urlParts||urlParts[0]!==url){return false}var scheme=urlParts[1],authority=urlParts[2],path=urlParts[3],query=urlParts[4],fragment=urlParts[5];if(!((!requireProtocol||isValidMatch(scheme,twttr.txt.regexen.validateUrlScheme)&&scheme.match(/^https?$/i))&&isValidMatch(path,twttr.txt.regexen.validateUrlPath)&&isValidMatch(query,twttr.txt.regexen.validateUrlQuery,true)&&isValidMatch(fragment,twttr.txt.regexen.validateUrlFragment,true))){return false}return unicodeDomains&&isValidMatch(authority,twttr.txt.regexen.validateUrlUnicodeAuthority)||!unicodeDomains&&isValidMatch(authority,twttr.txt.regexen.validateUrlAuthority)};function isValidMatch(string,regex,optional){if(!optional){return typeof string==="string"&&string.match(regex)&&RegExp["$&"]===string}return!string||string.match(regex)&&RegExp["$&"]===string}if(typeof module!="undefined"&&module.exports){module.exports=twttr.txt}if(typeof window!="undefined"){if(window.twttr){for(var prop in twttr){window.twttr[prop]=twttr[prop]}}else{window.twttr=twttr}}})();;
function TwitterCharacterCount() {}

TwitterCharacterCount.prototype = {
    countCharacters: function(status) {
        var extractedUrls = twttr.txt.extractUrlsWithIndices(status);
        var urlsFound = extractedUrls.length;
        var tweetLength = status.length;
        if (urlsFound > 0) {
            for (var u = 0; u < urlsFound; u++) {
                var extractedUrl = extractedUrls[u].url;
                tweetLength = tweetLength - extractedUrl.length;
                if (extractedUrl.slice(0, 5) == "https") {
                    tweetLength = tweetLength + 23;
                } else if (extractedUrl.slice(0, 4) == "http") {
                    tweetLength = tweetLength + 22;
                }
            }
        }
        return tweetLength;
    }
};;
function SocialMediaComposer(deliveryMethod) {
    var config;
    switch (deliveryMethod) {
    case 'twitter':
        config = SocialMediaComposer.twitterConfiguration;
        break;
    case 'facebook':
        config = SocialMediaComposer.facebookConfiguration;
        break;
    default:
        throw "SocialMediaComposer cannot process a deliveryMethod of '" + deliveryMethod + "'";
    }

    this.composerName = config.composerName;
    this.parentSectionSelector = config.uiClasses.compose;
    this._deliveryMethod = deliveryMethod;
    this.deliveriesProperty = config.deliveriesProperty;
    this.deliverabilityKey = config.deliverabilityKey;
    this._suggestedMessageTemplateId = config.suggestedMessageTemplateId;
    this._composeTemplateId = config.composeTemplateId;
    this.maxMessageLengthDefault = config.maximumMessageLength;
    this._getMessageLength = config.characterCounter.countCharacters;
    this.targetedMessageDeliverableProperty = config.targetedMessageProperty;
    this._sampleMessageBodyProperty = config.sampleMessageProperty;
    this.respondForwardButtonTextTranslationKey = config.translateKeys.respondForwardButtonText;
    this.recipientsBeforeMore = 3;
};

(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    SocialMediaComposer.twitterConfiguration = {
        composerName: 'twitterComposer',
        deliveriesProperty: 'twitterDeliveries',
        deliverabilityKey: 'twitterDeliverability',
        suggestedMessageTemplateId: null,
        composeTemplateId: 'composeTwitterTemplate',
        maximumMessageLength: 280, //#TwitterLength
        targetedMessageProperty: 'twitterDeliverable',
        sampleMessageProperty: 'twitterMessage',
        characterCounter: new TwitterCharacterCount(),
        uiClasses: {
            compose: '.jsComposeTwitter'
        },
        translateKeys: {
            respondForwardButtonText: 'TwitterButtonTextRespondForward'
        }
    };
    SocialMediaComposer.facebookConfiguration = {
        composerName: 'facebookComposer',
        deliveriesProperty: 'facebookDeliveries',
        deliverabilityKey: 'facebookDeliverability',
        suggestedMessageTemplateId: 'suggestedMessageFacebookTemplate',
        composeTemplateId: 'composeFacebookTemplate',
        maximumMessageLength: 2000,
        targetedMessageProperty: 'facebookDeliverable',
        sampleMessageProperty: 'facebookMessage',
        characterCounter: {
            countCharacters: function(status) {
                if (!status) {
                    status = "";
                }
                return status.length;
            }
        },
        uiClasses: {
            compose: '.jsComposeFacebook'
        },
        translateKeys: {
            respondForwardButtonText: 'FacebookButtonTextRespondForward'
        }
    };

    SocialMediaComposer.prototype = {
        getParentSection: function() {
            return $(this.parentSectionSelector);
        },
        setRespondForwardButtonText: function(respondUi) {
            respondUi.translateButtonText('ButtonTextSendMessage', this.respondForwardButtonTextTranslationKey);
        },
        createRecipientLi: function(match) {
            var li = $('<li></li>');
            li.html(match.name);
            return li;
        },
        acceptsDeliveryOption: function(deliveryOption) {
            return deliveryOption.deliveryMethod === this._deliveryMethod;
        },
        composeMessageHeaderText: function (count, number, sample, respondUi) {
            var headerText = respondUi.translateLanguage(sample.readOnly == true ? 'HeaderReviewNthMessage' : 'HeaderComposeNthMessage');
            var messageNumberText = count == 1 ? respondUi.translateLanguage('TextYour') : respondUi.convertOrdinalNumberToName(number);
            return headerText.replace('{0}', messageNumberText);
        },
        buildGroupUiContainer: function (groupsList) {
            return groupsList;
        },
        prepareGroupUiContainerForBinding: function (uiContainer, matchGroups) {
        },
        createGroupUi: function (target) {
            var groupUi = $('#targetGroupingTemplate').clone(true).removeAttr("id");
            groupUi.find('span').html(target.displayName);
            return groupUi;
        },
        buildBinder: function (defaultBinder) {
            return defaultBinder;
        },
        buildUndeliverableTargetUi: function (application, li, respondUi) {
            li.append($("<span class='vv-target-undeliverable-message'>&nbsp;(" + respondUi.translateLanguage('TargetUndeliverable') + ")</span>"));
            li.addClass("vv-target-undeliverable");
        },
        buildDeliveryTargetUi: function (application, li, match, option, targetedMessage, respondUi) {
        },
        createGuidelinesUi: function () {
            return $('#guidelinesTemplate').clone(true).removeAttr("id");
        },
        createSuggestedMessageUi: function () {
            return $('#' + this._suggestedMessageTemplateId).clone(true).removeAttr("id");
        },
        createComposeUi: function() {
            return $('#' + this._composeTemplateId).clone(true).removeAttr("id");
        },
        createComposeElements: function(suggestedMessageUi, composeUi) {
            return {
                composeUi: composeUi,
                suggestedMessageUi: suggestedMessageUi,
                messageInput: composeUi.find('.messageInput'),
                messageCounter: composeUi.find('.messageCounter')
            };
        },
        getJoinedMessageLength: function(composeElements) {
            var messageInput = composeElements.messageInput;

            var parts = new Array();
            if (messageInput !== undefined && messageInput != null && messageInput.val() !== ""){
                    parts.push(messageInput.val());
            }
            var joinedMessage = parts.join("\r\n\r\n");
            return this._getMessageLength(joinedMessage);
        },
        bindComposeElements: function(composeElements, tm, sm, composeUi, respondUi) {
            var messageInput = composeElements.messageInput;
            var deliverable = tm[this.targetedMessageDeliverableProperty];

            var body;
            if (composeElements.suggestedMessageUi.length > 0) {
                if (!sm[this._sampleMessageBodyProperty])
                    composeElements.suggestedMessageUi.hide();
                else {
                    var smHtml = sm[this._sampleMessageBodyProperty].replace(/\n/g, "<br/>");
                    composeElements.suggestedMessageUi.find('summary span').html(smHtml);

                    //Toggle display if bindDeliveryOptions has already been run. (Loaded page when remembered)
                    if (deliverable.hasDeliveryOptions && sm[this._sampleMessageBodyProperty] != '') {
                        composeElements.suggestedMessageUi.show();
                    }
                }
                body = '';
            } else {
                body = sm[this._sampleMessageBodyProperty];
            }

            deliverable.sampleMessageId = sm.sampleMessageId;
            deliverable.body = body;
            //deliverable.modified = false;

            messageInput.change(function() {
                deliverable.body = this.value;
                //deliverable.modified = true;
            });

            // respondUi.messageContentChanged binding previously done here to update "Remaining" character count

            // wire body
            messageInput.keyup(function(eventObject) { respondUi.messageContentChanged.dispatch(); });
            messageInput.val(body);
            respondUi.messageContentChanged.dispatch();
        },
        hasParagraphs: function(tm) {
            return false;
        },
        hasMessageBodyChanged: function(composeElements, tm) {
            var messageInput = composeElements.messageInput;
            var originalSampleMessageBody;
            if (composeElements.suggestedMessageUi.length > 0) {
                originalSampleMessageBody = '';
            } else {
                originalSampleMessageBody = tm.messages[tm.messages.currentIndex][this._sampleMessageBodyProperty];
            }

            return $.trim(messageInput.val().replace(/\r/g, '')) !== $.trim(originalSampleMessageBody.replace(/\r/g, ''));
        },
        hasSubjectChanged: function(composeElements, tm) {
            return false;
        },
        showNewSampleMessage: function(composeElements, tm, message, respondUi) {
            var messageInput = composeElements.messageInput;
            var deliverable = tm[this.targetedMessageDeliverableProperty];

            var body;
            if (composeElements.suggestedMessageUi.length > 0) {
                if (!message[this._sampleMessageBodyProperty])
                    composeElements.suggestedMessageUi.hide();
                else {
                    composeElements.suggestedMessageUi.find('summary').find('span').html(message[this._sampleMessageBodyProperty]);
                }
                body = '';
            } else {
                body = message[this._sampleMessageBodyProperty];
            }

            messageInput.val(body);
            deliverable.sampleMessageId = message.sampleMessageId;
            deliverable.body = body;
            //deliverable.modified = false;
        },
        toggleListVisibility: function (configuration) {
            configuration.groupsList.hide();
            configuration.collapsedGroupsList.toggle(configuration.showGroupsList);
            configuration.expandedGroupsList.hide();
            configuration.groupsToggleLink.toggle(configuration.showGroupsList && configuration.expandedGroupsList.children().length > 0);

            configuration.allRecipientsUi.hide();
            configuration.collapsedRecipientsUi.toggle(!configuration.showGroupsList);
            configuration.expandedRecipientsUi.hide();
            configuration.recipientsToggleLink.toggle(!configuration.showGroupsList && configuration.expandedRecipientsUi.children().length > 0);
        }
    };
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
function PhoneCallComposer() {
    this._deliveryMethod = 'phone';

    this.composerName = 'phoneComposer';
    this.parentSectionSelector = '.jsComposePhone';
    this.deliveriesProperty = 'phoneDeliveries';
    this.deliverabilityKey = 'phoneDeliverability';
    this.targetedMessageDeliverableProperty = 'phoneDeliverable';
    this.respondForwardButtonTextTranslationKey = 'ButtonTextContinue';
    this.recipientsBeforeMore = undefined;
};

(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    PhoneCallComposer.prototype = {
        getParentSection: function() {
            return $(this.parentSectionSelector);
        },
        setRespondForwardButtonText: function(respondUi) {
            respondUi.translateButtonText('ButtonTextSendMessage', this.respondForwardButtonTextTranslationKey);
        },
        createRecipientLi: function(match) {
            var li = $('#targetMatchPhoneTemplate').clone(true).removeAttr("id");
            li.find('span').html(match.name);
            return li;
        },
        acceptsDeliveryOption: function(deliveryOption) {
            return deliveryOption.deliveryMethod === this._deliveryMethod;
        },
        composeMessageHeaderText: function (count, number, sample, respondUi) {
            return count == 1
                    ? respondUi.translateLanguage('PlacePhoneCall')
                    : respondUi.convertOrdinalNumberToName(number) + " " + respondUi.translateLanguage('PhoneScript');
        },
        buildGroupUiContainer: function (groupsList, respondUi) {
            var uiContainer = groupsList.parent();
            groupsList.remove();
            uiContainer.children('header').hide();
            //Build PhoneUserInformationNeededMessage
            var userInformationNeededMessage = $('<div class="jsUserInformationNeededMessage">' + respondUi.translateLanguage('PhoneUserInformationNeededMessage') + '</div>');
            userInformationNeededMessage.hide(); 
            uiContainer.append(userInformationNeededMessage);

            return uiContainer;
        },
        prepareGroupUiContainerForBinding: function (uiContainer, matchGroups) {
            if (matchGroups.length > 0) {
                uiContainer.children('header').slideDown();
                uiContainer.children('div.jsUserInformationNeededMessage').slideUp();
            } else {
                uiContainer.children('header').slideUp();
                uiContainer.children('div.jsUserInformationNeededMessage').slideDown();
            }
        },
        createGroupUi: function () {
            return $('<ul></ul>');
        },
        buildBinder: function (defaultBinder, createRecipientItem) {
            return function bindMatchPhone(application, ul, myMatches, targetedMessage, matchCriteria, messageLengthValidator, allRecipientsUi, respondUi) {
                messageLengthValidator.clearMaximumMessageLength(this._composer);
                ul.empty();
                for (var m = 0; m < myMatches.length; m++) {
                    var li = createRecipientItem.call(this, myMatches[m], true, application, targetedMessage, messageLengthValidator, respondUi);
                    ul.append(li);
                }
            };
        },
        buildUndeliverableTargetUi: function (application, li, respondUi) {
            li.append($("<span class='vv-target-undeliverable-message'>&nbsp;(" + respondUi.translateLanguage('TargetUndeliverable') + ")</span>"));
            li.addClass("vv-target-undeliverable");
            li.find('.vvActionButton').slideUp();
        },
        buildDeliveryTargetUi: function (application, li, match, option, targetedMessage, respondUi) {
            li.css('white-space', 'nowrap');

            var placeCallText = respondUi.translateLanguage('PlaceCall');
            var completeBadge = $('<span class="fas fa-check-circle vv-completed-phone-badge jsCompletedBadge" aria-hidden="true"></span>');
            var image = $('<span class="fas fa-phone-square vvActionLinkImage vv-phone-call-button" aria-hidden="true" alt="' + placeCallText + '"></span>');
            var button = $('<button class="vvActionButton vv-action-bar-button vv-button" style="white-space:nowrap;" title="' + placeCallText + '"></button>');

            completeBadge.hide();
            button.append(completeBadge);
            button.append(image);
            button.click(function() {
                application.activateCallee(match, option, targetedMessage, targetedMessage.randomSample());
                application.navigateByName('beginCall');
            });

            var aside = li.find('.vvPhoneRecipientAside');
            aside.empty();
            button.hide();
            aside.append(button);
            button.slideDown();

            function markUiCompleted() {
                completeBadge.slideDown();
                image.fadeTo('slow', 0.33);
                button.prop('disabled', true);
                button.prop('title', '');
            }

            function persistCallCompletion(newMatch) {
                if (!application._completedCalls) {
                    application._completedCalls = [];
                }
                for (var i = 0; i < application._completedCalls.length; i++) {
                    var completedCall = application._completedCalls[i];
                    if (completedCall.id === newMatch.id && completedCall.type === newMatch.type) {
                        markUiCompleted();
                        break;
                    }
                }
            }
            persistCallCompletion(match);

            match.phoneCallCompleted.attach(function () {
                match.wasPhoneCallCompleted = true;
                application._completedCalls.push(match);
                markUiCompleted();
            });
        },
        createGuidelinesUi: function() {
            return $();
        },
        createSuggestedMessageUi: function() {
            return $();
        },
        createComposeUi: function() {
            return $();
        },
        createComposeElements: function(suggestedMessageUi, composeUi) {
            return {
                composeUi: $(),
                suggestedMessageUi: $(),
                messageInput: $(),
                messageCounter: $()
            };
        },
        getJoinedMessageLength: function(composeElements) {
            return 0;
        },
        bindComposeElements: function(composeElements, tm, sm, composeUi, respondUi) {
            var deliverable = tm[this.targetedMessageDeliverableProperty];
            deliverable.sampleMessageId = sm.sampleMessageId;
            //deliverable.modified = false;
            respondUi.messageContentChanged.dispatch();
        },
        hasParagraphs: function(tm) {
            return false;
        },
        hasMessageBodyChanged: function(composeElements, tm) {
            return false;
        },
        hasSubjectChanged: function(composeElements, tm) {
            return false;
        },
        showNewSampleMessage: function(composeElements, tm, message, respondUi) {
        },
        toggleListVisibility: function(configuration) {
            configuration.groupsList.toggle(configuration.showGroupsList);
            configuration.allRecipientsUi.toggle(!configuration.showGroupsList);
        }
    };
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
function SocialMediaAuth(window, ui) {
    var angular = ui.angular;
    var jQuery = ui.jQuery;

    if ('BroadcastChannel' in self && navigator.userAgent.toLowerCase().indexOf("votervoice/") < 0) {
        //https://stackoverflow.com/a/2117523
        function uuidv4() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

        var broadcastChannelIdentifier = uuidv4();
        this.broadcastChannels = {};
        this.broadcastChannels['twitter'] = new BroadcastChannel('vv-twitter-' + broadcastChannelIdentifier);
    }

    this.facebookListener = { listening: [] };
    this.twitterListener = { listening: [] };

    function receiveMessage(event, callback) {
        callback(event.data);
    }

    this.listen = function (popup, callback, listener, socialMedia) {
        if (typeof popup === 'undefined' && navigator.userAgent.toLowerCase().indexOf("votervoice/") > -1) {
            // mobile app
            window.socialMediaAuth = function(code) {
                callback(code);
            };
        //} else if (!popup.opener) {
        //    // https://code.google.com/p/chromium/issues/detail?id=136610&q=window.opener&colspec=ID%20Pri%20Mstone%20ReleaseBlock%20OS%20Area%20Feature%20Status%20Owner%20Summary
        //    // "3. Don't profit."
        //    localStorage.removeItem(SocialMediaAuth.noOpenerKey);
        //    var timer = setInterval(function() {
        //        var done = popup == null || popup.closed;
        //        try {
        //            var code = localStorage.getItem(SocialMediaAuth.noOpenerKey);
        //            if (code) {
        //                done = true;
        //                code = JSON.parse(code);
        //                receiveMessage({
        //                        data: code,
        //                        origin: null,
        //                        source: popup
        //                    },
        //                    callback);
        //            }
        //        } finally {
        //            if (done) {
        //                clearInterval(timer);
        //            }
        //        }
        //    }, 500);
        } else if (socialMedia !== undefined && 'BroadcastChannel' in self) {
            this.broadcastChannels[socialMedia].onmessage = function (e) {
                popup.close();
                receiveMessage({
                        data: e.data,
                        origin: null,
                        source: popup
                    },
                    callback);
            }
        } else if (window.ActiveXObject && !window.MessageChannel) {
            //IE9-
            window.postMessage10 = function(data) {
                receiveMessage({
                        data: data,
                        origin: null,
                        source: popup
                    },
                    callback);
            };
        } else {

            function messageEventHandler(e) {
                for (var i = 0; i < listener.listening.length; i++) {
                    if (listener.listening[i] === e.originalEvent.source) {
                        receiveMessage(e.originalEvent, callback);
                    }
                }
            }

            listener.listening.push(popup);
            if (listener.listening.length === 1) {
                if (typeof (angular) !== 'undefined') {
                    angular.element(window).bind('message', messageEventHandler);
                } else {
                    jQuery(window).on("message", messageEventHandler);
                }
            }
        }
    };
}

SocialMediaAuth.noOpenerKey = 'iPhoneChromeOAuth';
SocialMediaAuth.prototype = {
    openFbDialog: function(authUri, callback) {
        var popup = this.openPopup(authUri, 'vvFbAuth');
        this.listen(popup, callback, this.facebookListener);
    },
    getBroadcastChannelIdentifier: function(socialMedia) {
        if (this.broadcastChannels !== undefined && this.broadcastChannels[socialMedia] !== undefined) {
            if (this.broadcastChannels[socialMedia].name !== undefined) {
                return this.broadcastChannels[socialMedia].name;
            } else if (this.broadcastChannels[socialMedia].channel !== undefined) {
                return this.broadcastChannels[socialMedia].channel;
            }
        }

        return null;
    },
    appendBroadcastChannelIdentifierQuerystring: function(url, socialMedia) {
        if ('BroadcastChannel' in self && navigator.userAgent.toLowerCase().indexOf("votervoice/") < 0) {
            var broadcastChannelIdentifier = this.getBroadcastChannelIdentifier(socialMedia);

            if (broadcastChannelIdentifier !== null && broadcastChannelIdentifier !== undefined && broadcastChannelIdentifier !== '') {
                if (url.indexOf('?') > -1) {
                    url += '&broadcastChannel=' + encodeURIComponent(broadcastChannelIdentifier);
                } else {
                    url += '?broadcastChannel=' + encodeURIComponent(broadcastChannelIdentifier);
                }
            }
        }

        return url;
    },
    openTwitterDialog: function(authUri, callback) {
        var popup = this.openPopup(authUri, 'vvTwitterAuth');
        this.listen(popup, callback, this.twitterListener, 'twitter');
    },
    openPopup: function(authUri, windowName, height) {
        var parentWidth = window.innerWidth;
        var parentHeight = window.innerHeight;
        var parentLeft = window.screenLeft;
        var parentTop = window.screenTop;
        var popupWidth = 690;
        var popupHeight = 630;
        if (height != undefined) {
            popupHeight = height;
        }

        var popupLeft = parentLeft + (parentWidth / 2.0) - (popupWidth / 2.0);
        var popupTop = parentTop + (parentHeight / 2.0) - (popupHeight / 2.0);
        
        return window.open(authUri, windowName,
            'toolbar=0,location=0,menubar=0' +
            ',width=' + popupWidth +
            ',height=' + popupHeight +
            ',left=' + popupLeft +
            ',top=' + popupTop);
    }
};

function completeSocialMediaOAuth(code, broadcastChannel) {
    // mobile app
    if (typeof popup === 'undefined' && navigator.userAgent.toLowerCase().indexOf("votervoice/") > -1) {
        location.href = 'votervoicewebview://socialmediaauth?code=' + JSON.stringify(code);
    //} else if (!window.opener) {
    //    localStorage.setItem(SocialMediaAuth.noOpenerKey, JSON.stringify(code));
    } else if (broadcastChannel !== null && broadcastChannel !== undefined) {
        broadcastChannel.postMessage(code);
    } else if (window.ActiveXObject && !window.MessageChannel) {
        //IE9-
        if (window.opener.postMessage10 != undefined) {
            window.opener.postMessage10(code);
        }
    } else {
        window.opener.postMessage(code, '*');
    }
};
function ActionBar(jquery) {
    var $ = jquery;
    var translator = new VoterVoiceTranslator();
    var actionBar;
    var twitterIcon = `
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 512 512" fill="currentcolor">
    <path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path>
</svg>
`;
    var ui = {
        messageButtonUi:  '<div class="vv-action-bar-button-container"><div id="actionbarMessage"  class="vv-action-bar-button jsActionBarButton jsActionBarButtonComposeMessage"  data-composer-name="messageComposer" ><span class="fas fa-check-circle vv-completed-badge jsCompletedBadge" aria-hidden="true"></span><div class="vv-action-bar-icon"><span class="fas fa-envelope-open" aria-hidden="true"></span></div><div class="vv-action-bar-text">' + translator.GetTextForCulture('ActionBarMessage')  + '</div></div><div>',
        twitterButtonUi:  `<div class="vv-action-bar-button-container"><div id="actionbarTwitter"  class="vv-action-bar-button jsActionBarButton jsActionBarButtonComposeTwitter"  data-composer-name="twitterComposer" ><span class="fas fa-check-circle vv-completed-badge jsCompletedBadge" aria-hidden="true"></span><div class="vv-action-bar-icon">${twitterIcon}</div><div class="vv-action-bar-text">` + translator.GetTextForCulture('ActionBarTwitter') + '</div></div><div>',
        facebookButtonUi: '<div class="vv-action-bar-button-container"><div id="actionbarFacebook" class="vv-action-bar-button jsActionBarButton jsActionBarButtonComposeFacebook" data-composer-name="facebookComposer"><span class="fas fa-check-circle vv-completed-badge jsCompletedBadge" aria-hidden="true"></span><div class="vv-action-bar-icon"><span class="fab fa-facebook-f"    aria-hidden="true"></span></div><div class="vv-action-bar-text">' + translator.GetTextForCulture('ActionBarFacebook') + '</div></div><div>',
        phoneButtonUi:    '<div class="vv-action-bar-button-container"><div id="actionbarPhone"    class="vv-action-bar-button jsActionBarButton jsActionBarButtonComposePhone"    data-composer-name="phoneComposer"   ><span class="fas fa-check-circle vv-completed-badge jsCompletedBadge" aria-hidden="true"></span><div class="vv-action-bar-icon"><span class="fas fa-phone"         aria-hidden="true"></span></div><div class="vv-action-bar-text">' + translator.GetTextForCulture('ActionBarPhone') + '</div></div><div>',
        petitionButtonUi: '<div class="vv-action-bar-button-container"><div id="actionbarPetition" class="vv-action-bar-button jsActionBarButton jsActionBarButtonPetition"        data-composer-name="petitionComposer"><span class="fas fa-check-circle vv-completed-badge jsCompletedBadge" aria-hidden="true"></span><div class="vv-action-bar-icon"><span class="fas fa-clipboard"     aria-hidden="true"></span></div><div class="vv-action-bar-text">' + translator.GetTextForCulture('ActionBarPetition') + '</div></div><div>',
        donateButtonUi:   '<div class="vv-action-bar-button-container"><div id="actionbarDonate"   class="vv-action-bar-button jsActionBarButton jsActionBarButtonDonate"          data-composer-name="donateComposer"  ><span class="fas fa-check-circle vv-completed-badge jsCompletedBadge" aria-hidden="true"></span><div class="vv-action-bar-icon"><span class="fas fa-credit-card"   aria-hidden="true"></span></div><div class="vv-action-bar-text">' + translator.GetTextForCulture('ActionBarDonate') + '</div></div><div>'
    };

    this.build = function (parentUi, messageConfig, twitterConfig, facebookConfig, phoneConfig) {
        var actionBarPlaceholder = parentUi;
        if (actionBarPlaceholder && actionBarPlaceholder.length) {
            actionBar = $('<div class="vv-action-bar"></div>');

            if (messageConfig.isShown) {
                actionBar.append(ui.messageButtonUi);
                actionBar.find("#actionbarMessage").click(messageConfig.clickHandler);
            }

            if (twitterConfig.isShown) {
                actionBar.append(ui.twitterButtonUi);
                actionBar.find("#actionbarTwitter").click(twitterConfig.clickHandler);
            }

            if (facebookConfig.isShown) {
                actionBar.append(ui.facebookButtonUi);
                actionBar.find("#actionbarFacebook").click(facebookConfig.clickHandler);
            }

            if (phoneConfig.isShown) {
                actionBar.append(ui.phoneButtonUi);
                actionBar.find("#actionbarPhone").click(phoneConfig.clickHandler);
            }

            var newSection = $('<section id="vv-action-bar-section" class="vv-form-scroll-target"></section>')
                .css('display', actionBarPlaceholder.css('display'))
                .append(actionBar);
            actionBarPlaceholder.replaceWith(newSection);
        }
    };

    this.setState = function (deliveredTargets) {
        if (!actionBar || !deliveredTargets) return;

        function addToTooltip(uiId, message, targets) {
            var ui = actionBar.find(uiId);
            if (targets.length > 0) {
                ui.find('.jsCompletedBadge').show();
                ui.attr('title', message);
                for (var i = 0; i < targets.length; i++) {
                    var existingToolTipValue = ui.attr('title');
                    if (existingToolTipValue.indexOf(targets[i].name) === -1) {
                        var toolTip = existingToolTipValue + (i > 0 ? "," : "") + "\n   " + targets[i].name;
                        ui.attr('title', toolTip);
                    }
                }
            } else {
                ui.find('.jsCompletedBadge').hide();
                ui.attr('title', translator.GetTextForCulture('ActionBarClickToSendMessage'));
            }
        }

        addToTooltip('#actionbarMessage',  translator.GetTextForCulture('ActionBarCommunicatedWithMessage')  + ":", deliveredTargets.message);
        addToTooltip('#actionbarTwitter',  translator.GetTextForCulture('ActionBarCommunicatedWithTwitter')  + ":", deliveredTargets.twitter);
        addToTooltip('#actionbarFacebook', translator.GetTextForCulture('ActionBarCommunicatedWithFacebook') + ":", deliveredTargets.facebook);
        addToTooltip('#actionbarPhone',    translator.GetTextForCulture('ActionBarCommunicatedWithPhone')    + ":", deliveredTargets.phone);
    };
};
(function(onReady, jQuery, undefined) {
    var $;
    onReady(function() { $ = jQuery(); });

    function TargetedMessageLengthValidator() {
        var me = this;
        var maxMessageLengths = []; //MaxLengths are unique to a TargetedMessage and Composer.
        this.clearMaximumMessageLength = function(composer) {
            delete maxMessageLengths[composer.composerName];
        };
        this.getMaximumMessageLength = function(composer) {
            return maxMessageLengths[composer.composerName] || composer.maxMessageLengthDefault;
        };
        this.setMaximumMessageLength = function(options, composer, respondUi) {
            for (var i = 0; i < options.length; i++) {
                if (options[i].maximumMessageLength == undefined) {
                    break;
                }

                var newMaxLength = options[i].maximumMessageLength;
                if (newMaxLength < me.getMaximumMessageLength(composer)) {
                    maxMessageLengths[composer.composerName] = newMaxLength;
                }
            }
            respondUi.messageContentChanged.dispatch();
        };
    }

    function populateQuestionnaire(application, additionalUserFields, questions) {
        var questionnaire = $("#stepAdditionalInfo3");
        questionnaire.find('section').remove();
        questionnaire.find('.jsPrefixInstructions').hide();

        var ui;
        for (var f = 0; f < additionalUserFields.length; f++) {
            ui = $('#questionTextTemplate').clone(true).removeAttr("id");
            bindUserField.call(this, application, ui, additionalUserFields[f]);
            questionnaire.append(ui);
        }
        for (var q = 0; q < questions.length; q++) {
            ui = $('#questionSelectTemplate').clone(true).removeAttr("id");
            bindQuestion(application, ui, questions[q].question, questions[q].heading);
            questionnaire.append(ui);
            if (questions[q].question.question === this.translateLanguage("HonorificName")) {
                questionnaire.find('.jsPrefixInstructions').show();
            }
        }
    }

    function bindUserField(application, ui, additionalUserField) {
        ui.find('header').html(additionalUserField.heading);
        var labelText = additionalUserField.fieldName === 'phone' 
								? this.translateLanguage('UserYourPhoneNumber') 
								: additionalUserField.fieldName;
        ui.find('label').html(labelText);
        var textbox = ui.find('input');
        textbox.change(function() {
            application.answerQuestion(additionalUserField, $(this).val());
        });

        if (additionalUserField.answer !== null) {
            textbox.val(additionalUserField.answer);
        }
    }

    function bindQuestion(application, ui, question, heading) {
        ui.find('header').html(heading);
        ui.find('label').html(question.question);
        var select = ui.find('select');
        for (var a = 0; a < question.validAnswers.length; a++) {
            select.append('<option>' + question.validAnswers[a] + '</option>');
        }
        select.change(function() {
            //CODE-928 previously: "$(this).val()" but 'val()' trims the value, which we must have EXACTLY as it was given. -PJ
            application.answerQuestion(question, $(this).children(':selected').text());
        });

        if (question.answer !== undefined) {
            //CODE-928 If the answer is encased in whitespace, and the option-html is also encased in whitespace 
            //it will trim the option-html but not the answer passed in the val(). So trim it to match. -PJ
            select.val($.trim(question.answer));
        }
    }

    function bindTargets(application, composeUiFactory, tm, messageLengthValidator) {
        composeUiFactory.createMessageRecipientUis(tm);
        composeUiFactory.handleRecipientsDetermined(application, tm);

        // create and bind recipient group UIs
        composeUiFactory.populateAndBindRecipientGroupUi(tm, application, messageLengthValidator, this);

        // add full recipient list
        composeUiFactory.addFullRecipientList(application, vvui);
    }

    function finishReadonlyMessageResize(messageInput) {
        setTimeout(function () {
            //var height = messageInput.prop('scrollHeight');
            //if (height < 25)
            //    height = 25;
            var height = 150;
            messageInput.css('height', '' + height + 'px');
        }, 0);
    }

    //TODO: Move to Phone Composer
    function createPhoneCallDialog(currentCallee, vvuiLocal) {
        var match = currentCallee.match;
        var option = currentCallee.option;
        var sampleMessage = currentCallee.sampleMessage;

        var dialog = $('#phoneCallDialogTemplate').clone(true).removeAttr("id");
        var link = dialog.find('a').first();
        link.attr('href', "tel:" + option.addressText);
        link.html(formatPhone(option.addressText));
        dialog.find('.vv-phone-script-text').html(sampleMessage.phoneGuidelines);
        dialog.find('#vvPhoneAnswerType').change(function() {
            dialog.find('.vvPhoneAnswer').toggle($(this).val() === 'person');
        });
        var select = dialog.find('#vvPhoneAnswerPerson');
        select.append($('<option value="">' + vvuiLocal.translateLanguage('DropdownItemChooseOne') + '</option>'));
        select.append($('<option></option>').attr("value", match.name).text(match.name));
        select.append($('<option value="other">' + vvuiLocal.translateLanguage('SomeoneElse') + '</option>'));
        select.change(function() {
            var selectedValue = select.val();
            dialog.find('.vvPhoneAnswer').find('input').toggle(selectedValue === 'other');
        });
        dialog.find('.vvPhoneAnswer').find('input').placeholder();

        return dialog;
    }

    //TODO: Move to Phone Composer
    function formatPhone(phonenum) {
        var regexObj = /^(?:\+?1[-. ]?)?(?:\(?([0-9]{3})\)?[-. ]?)?([0-9]{3})[-. ]?([0-9]{4})$/;
        if (regexObj.test(phonenum)) {
            var parts = phonenum.match(regexObj);
            var phone = "";
            if (parts[1]) {
                phone += "(" + parts[1] + ") ";
            }
            phone += parts[2] + "-" + parts[3];
            return phone;
        } else {
            //invalid phone number
            return phonenum;
        }
    }

    //TODO: Move to Phone Composer
    function buildPhoneResponse(dialog, vvuiLocal) {
        switch (dialog.find('#vvPhoneAnswerType').val()) {
        case 'person':
            var select = dialog.find('#vvPhoneAnswerPerson');
            var answerer = select.val() !== 'other'
                ? select.val()
                : $.trim(dialog.find('.vvPhoneAnswer').find('input').val());
            return {
                recipientName: answerer,
                recipientResponse: dialog.find('.messageInputAux').val()
            };
        case 'machine':
            return {
                recipientName: vvuiLocal.translateLanguage('AnsweringMachine')
            };
        case 'none':
            return null;
        default:
            return undefined;
        }
    }

    VoterVoiceUserInterface.prototype.extend({
        initializeAdvocacy: function (enableMessageSetChangedAlert, useNoDeliveriesMessage, shareUrl) {

            var me = this;
            this.targetedMessageRendered = new VoterVoiceApplication.Event();
            this.messageContentChanged = new VoterVoiceApplication.Event();

            this.displayUpdaters.push(function (display, transitions, getIdentity) {
                if (display.pageHash !== undefined) {
                    if (display.showCompose) {
                        $('#stepMessage3 textarea.messageInput.readonlyMessage').each(function () {
                            finishReadonlyMessageResize($(this));
                        });
                    }
                    if (display.signatureDisplay === 'first/last') {
                        if ($('.signatureInput-part').length === 0) {
                            var signatureInput = $('.signatureInput');
                            signatureInput.hide();
                            $('<div><input class="signatureInput-part" maxlength="45" placeholder="First Name" />' +
                                ' <input class="signatureInput-part" maxlength="45" placeholder="Last Name" /></div>')
                                .insertAfter(signatureInput)
                                .find('.signatureInput-part').change(function () {
                                    var parts = $(this).parent().find('.signatureInput-part');
                                    var givenNames = $.trim(parts[0].value);
                                    var surname = $.trim(parts[1].value);
                                    var signature = givenNames === '' && surname === ''
                                        ? null
                                        : surname + ", " + givenNames;
                                    application.customizeSignature($.trim(signature));
                                });
                        }
                    }
                    return {
                        '#alertSection': display.showAlert,
                        '#vv-action-bar-section': display.showCompose ? 'full' : false,
                        '.jsComposeForm': display.showCompose,
                        '#stepAdditionalInfo3': display.showQuestionnaire
                    };
                } else {
                    return undefined;
                }
            });
            this.guardMessages.matching = this.translateLanguage('StillMatchingToTargets');
            this.guardMessages.fetchingOptions = this.translateLanguage('StillFetchingDeliveryOptions');
            this.guardMessages.noRecipients = {
                toString: function () {
                    return $('#vvNoDeliveriesTemplate .jsNoMatches').html();
                }
            };
            this.guardMessages.noDeliveryOptions = this.translateLanguage('NoDeliveryOptionsMessage');
            this.guardMessages.noSubjectOrBody = this.translateLanguage('SubjectOrBodyMissing');
            this.guardMessages.noBody = this.translateLanguage('BodyMissing');
            this.guardMessages.noPhoneResponsesGiven = this.translateLanguage('LeaveWithoutPlacingAnyCalls');
            this.guardMessages.phoneCompletionUnanswered = this.translateLanguage('PhoneCompletionUnanswered');
            this.guardMessages.phoneRecipientRequired = this.translateLanguage('PhoneRecipientRequired');
            this.guardMessages.phoneResponseRequired = this.translateLanguage('PhoneResponseRequired');
            this.guardMessages.moreInformationNeeded = this.translateLanguage('ProvideRequestedInformation');
            this.guardMessages.sendResponse = null;

            this.displayComposeForm = function (control) {
                var supportsPopups = !window.navigator.userAgent.match(/Windows Phone/);
                var composer = this._composers[$(control).attr('data-composer-name')];

                if (composer.honorificRequired === true) {
                    $("#divHonorific").addClass('vv_required');
                    $("#UserModel_Honorific").attr('placeholder', me._writtenCommunicationHonorificPlaceholderText);

                    if ($("#UserModel_Honorific").val() === '') {
                        me.displayFullUserProfile();
                    }
                } else {
                    me.clearHonorificRequirement();
                }

                if (supportsPopups) {
                    this._application.switchComposer(composer);
                    $('.jsActionBarButton').removeClass('vv-action-bar-button-active');
                    $(control).addClass('vv-action-bar-button-active');
                } else {
                    this.alert(this.translateLanguage('DeviceDoesNotSupportSocialMedia'));
                }
            };
            this._application.composerChanged.attach(function (composer) {
                composer.setRespondForwardButtonText(me);
                var previousComposer;
                for (var i = 0; i < me._composers.length; i++) {
                    if (me._composers[i].isActiveComposer) {
                        previousComposer = me._composers[i];
                    }
                    me._composers[i].isActiveComposer = me._composers[i] === composer;
                }

                var composeFormSelector = composer.parentSectionSelector;
                var newActiveComposeUi = $(composeFormSelector);

                $('.jsComposeForm').removeClass('jsActiveComposeForm'); //Clear active marker
                newActiveComposeUi.addClass('jsActiveComposeForm');     //Mark current active
                if (previousComposer !== composer)
                    newActiveComposeUi.hide(); //Initialize the soon to be active composeUi to hidden for a smooth slideDown transition.

                //Hide all but active
                $('.jsComposeForm').not(composeFormSelector).slideUp({
                    complete: function () {
                        //The active may have changed since initiation of the slideUp()
                        //only mark 'hidden-compose-form' if not currently active
                        var composeForm = $(this);
                        if (!composeForm.hasClass('jsActiveComposeForm')) {
                            composeForm.addClass('hidden-compose-form');
                        } else {
                            composeForm.removeClass('hidden-compose-form');
                        }
                    }
                });
                newActiveComposeUi.removeClass('hidden-compose-form')
                    .slideDown();

                me.toggleNoDeliveriesButtons(composer.showingNoDeliveriesMessage);
            });
            this.toggleNoDeliveriesButtons = function (noDeliveries) {
                if (noDeliveries === true) {
                    this.overrideNavigation('respond', {
                        back: 'ButtonTextBack',
                        forward: null
                    });
                } else {
                    this.overrideNavigation('respond');
                }
            };

            if (this._application.goBack !== undefined) {
                this._application.goBack.attach(function () {
                    window.location = "../";
                });
            }

            if (this._application.share !== undefined) {
                this._application.share.attach(function () {
                    if (shareUrl !== undefined) {
                        window.location = shareUrl;
                    } else {
                        window.location = "forward";
                    }
                });
            }

            if (this._application.scrollToHeader !== undefined) {
                this._application.scrollToHeader.attach(function () {
                    var scrollToHeader = function () {
                        var headerMessage = me.translateLanguage('PlacePhoneCall');
                        var header = $(".atComposeYourMessageHeader:contains('" + headerMessage + "')");

                        $('html, body').animate({
                            scrollTop: header.offset().top
                        }, 1000);

                        $('#alertTemplate').off('dialogclose', scrollToHeader);
                    };

                    $('#alertTemplate').on('dialogclose', scrollToHeader);
                });
            }

            var application = this._application;

            //TODO: Move to Phone Composer
            this.modalDialogs.phoneCallDialog = function () {
                var dialog = createPhoneCallDialog(application._currentCallee, me);
                dialog.dialog({
                    autoOpen: false,
                    modal: true,
                    draggable: false,
                    title: me.translateLanguage('Call') + ' ' + application._currentCallee.match.name,
                    width: 'auto',
                    open: function () { $(".ui-dialog-titlebar-close", this.parentNode).hide(); },
                    close: function () {
                        if (dialog.closingForTransition !== true) {
                            application.navigateByName('closeDialog');
                        }
                        dialog.empty().remove();
                    }
                });
                var uiParent = dialog.parent('.ui-dialog');
                $('body').append(uiParent);
                dialog.attr('id', 'phoneCallDialog');
                dialog.dialog('open');
                dialog.addClass('vvBody');
                dialog.addClass('ck-content');
                uiParent.addClass('vvDialog');
                uiParent.find('.ui-dialog-titlebar').addClass('vvDialogHeader');

                vvui.emitContentLoadedEvent();

                return dialog;
            };

            this._application.ui.populateQuestionnaire = function (additionalUserFields, questions) {
                populateQuestionnaire.call(me, application, additionalUserFields, questions);
            };

            this._application.signatureChanged.attach(function (signature) {
                $('.signatureInput').val($.trim(signature));
            });
            this._application.hasMessages.run(showMessages);

            this.convertOrdinalNumberToName = function (num) {
                switch (num) {
                    case 1:
                        return me.translateLanguage('NumberFirst');
                    case 2:
                        return me.translateLanguage('NumberSecond');
                    case 3:
                        return me.translateLanguage('NumberThird');
                    case 4:
                        return me.translateLanguage('NumberFourth');
                    case 5:
                        return me.translateLanguage('NumberFifth');
                    case 6:
                        return me.translateLanguage('NumberSixth');
                    case 7:
                        return me.translateLanguage('NumberSeventh');
                    case 8:
                        return me.translateLanguage('NumberEighth');
                    case 9:
                        return me.translateLanguage('NumberNinth');
                    case 10:
                        return me.translateLanguage('NumberTenth');
                    default:
                        return me.translateLanguage('TextYour');
                }
            };

            function showMessages(messages) {
                onReady(function () {

                    var showMessage = false,
                        showTwitter = false,
                        showFacebook = false,
                        showPhone = false;
                    var actionBarPlaceholder = $('#vv-action-bar-section');
                    var responded = false;
                    for (var m = 0; m < messages.length; m++) {
                        for (var dm = 0; dm < messages[m].availableDeliveryMethods.length; dm++) {
                            switch (messages[m].availableDeliveryMethods[dm]) {
                                case 'twitter':
                                    showTwitter = true;
                                    break;
                                case 'facebook':
                                    showFacebook = true;
                                    break;
                                case 'phone':
                                    showPhone = true;
                                    break;
                                default:
                                    showMessage = true;
                                    break;
                            }
                        }
                    }

                    me.baselineHonorificState();
                    me._composers = [];
                    if (showMessage === true) me._composers.push(new AdvocacyMessageComposer({ honorificRequired: me._honorificRequiredForWrittenCommunication }));
                    if (showTwitter === true) me._composers.push(new SocialMediaComposer('twitter'));
                    if (showFacebook === true) me._composers.push(new SocialMediaComposer('facebook'));
                    if (showPhone === true) me._composers.push(new PhoneCallComposer());

                    for (var i = 0; i < me._composers.length; i++) {
                        me._composers[i].isActiveComposer = i === 0;
                        me._composers[me._composers[i].composerName] = me._composers[i];
                    }
                    var composeUiFactory = new CompositeComposeUiFactory(me._composers);
                    composeUiFactory.initializeComposeUi(me._application);

                    // Only show action bar if there is more than one composer
                    if (me._composers.length < 2) {
                        actionBarPlaceholder.hide();
                    } else {
                        // Add Action Bar after calls to AdvocacyResponseFactory.initializeComposer()
                        var actionBar = new ActionBar($);
                        actionBar.build(actionBarPlaceholder,
                            {
                                isShown: showMessage,
                                clickHandler: function () {
                                    vvui.displayComposeForm(this);
                                }
                            },
                            {
                                isShown: showTwitter,
                                clickHandler: function () {
                                    vvui.displayComposeForm(this);

                                }
                            },
                            {
                                isShown: showFacebook,
                                clickHandler: function () {
                                    vvui.displayComposeForm(this);
                                }
                            },
                            {
                                isShown: showPhone,
                                clickHandler: function () {
                                    vvui.displayComposeForm(this);
                                }
                            });
                        var deliveredTargets = me._application._responseFactory.getDeliveredTargetsFromLocalStorage(me._application._userTracker._userToken, me._application._advocacyContext.campaignId);
                        if (deliveredTargets === null) {
                            function getParameterByName(name) {
                                name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
                                var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search);
                                return results === null
                                    ? ""
                                    : decodeURIComponent(results[1].replace(/\+/g, " "));
                            }
                            var serialized = decodeURIComponent(getParameterByName('deliveredMethods'));
                            deliveredTargets = application._responseFactory.getDeliveredTargetsFromSerialized(me._application._userTracker._userToken, serialized);
                        }
                        actionBar.setState(deliveredTargets);

                        for (var prop in deliveredTargets) {
                            if (deliveredTargets[prop].length > 0) {
                                responded = true;
                                break;
                            }
                        }

                        // Make the Action Alert span both columns in a two column layout because the actionBar will be present. 
                        $('#alertSection').css('float', 'none')
                            .css('margin-left', '0')
                            .css('width', 'auto')
                            .css('padding-right', '0');
                    }

                    function setDefaultComposer(composerType, composerName) {
                        var composeUi = $('.jsCompose' + composerType);
                        var actionBarButton = $('.jsActionBarButtonCompose' + composerType);

                        composeUi.removeClass('hidden-compose-form');
                        actionBarButton.addClass('vv-action-bar-button-active');

                        var composer = me._composers[composerName];
                        me._application._responseFactory.switchComposer(composer.targetedMessageDeliverableProperty, composer.deliveriesProperty);

                        for (var i = 0; i < me._composers.length; i++) {
                            me._composers[i].isActiveComposer = me._composers[i] === composer;
                        }

                        if (composer.honorificRequired !== true) {
                            me.clearHonorificRequirement();
                        }
                        else if ($("#UserModel_Honorific").val() === '') {
                            me.displayFullUserProfile();
                        }

                        composer.setRespondForwardButtonText(me);
                    }

                    //Check if the url includes an override of the default composer to show
                    var composerOverride = false;
                    if ($.address) {
                        switch (($.address.parameter('composer') || '').toLowerCase()) {
                            case 'twitter':
                                if (showTwitter) {
                                    composerOverride = true;
                                    setDefaultComposer('Twitter', 'twitterComposer');
                                }
                                break;
                            case 'facebook':
                                if (showFacebook) {
                                    composerOverride = true;
                                    setDefaultComposer('Facebook', 'facebookComposer');
                                }
                                break;
                            case 'phone':
                                if (showPhone) {
                                    composerOverride = true;
                                    setDefaultComposer('Phone', 'phoneComposer');
                                }
                                break;
                        }
                    }
                    if (composerOverride === false) {
                        //Determine default composer to show
                        if (showMessage) {
                            setDefaultComposer('Message', 'messageComposer');
                        } else if (showTwitter) {
                            setDefaultComposer('Twitter', 'twitterComposer');
                        } else if (showFacebook) {
                            setDefaultComposer('Facebook', 'facebookComposer');
                        } else if (showPhone) {
                            setDefaultComposer('Phone', 'phoneComposer');
                        }
                    }
                    for (m = 0; m < messages.length; m++) {
                        var tMessage = messages[m];
                        var sample = tMessage.randomSample();

                        composeUiFactory.startMessage(messages, tMessage, sample, me);
                        var messageLengthValidator = new TargetedMessageLengthValidator();
                        bindTargets.call(me, application, composeUiFactory, tMessage, messageLengthValidator);

                        composeUiFactory.appendRecipients();

                        composeUiFactory.bindAndAppendGuidelines(tMessage, sample, me);
                        composeUiFactory.appendSuggestedMessage(tMessage);

                        me.finishReadonlyMessageResize = finishReadonlyMessageResize;
                        composeUiFactory.bindAndAppendCompose(tMessage, sample, messageLengthValidator, me);

                        // using single-composer hack
                        me.targetedMessageRendered.dispatch(composeUiFactory.getCurrentMessageUiElements(), tMessage);
                    }

                    if (typeof vvuimobile !== 'undefined' && responded) {
                        var visibleTextArea = $('textarea.messageInput:visible');
                        if (visibleTextArea) {
                            $('html, body').animate({ scrollTop: visibleTextArea.offset().top }, 2000);
                        }
                    }
                    composeUiFactory.appendMissingRecipientsDisclaimer(me);

                    // grey messages who don't even appear in the match results
                    var tracker;
                    application.recipientsDetermined.run(
                        function (matchGroups, matchCriteria) {
                            tracker = {
                                _useNoDeliveriesMessage: useNoDeliveriesMessage,
                                reflectUndeliverableMessages: matchCriteria.byUser || matchCriteria.byAddress,
                                showMessageSetChangedAlert: enableMessageSetChangedAlert && matchCriteria.addressChanged,
                                changedVisibility: false,
                                _messageSetChangedAlertDisplayed: false,
                                pendingMessageCount: 0,
                                potentiallyDeliverableMessageCount: 0,
                                initialize: function (messages, matchGroups) {
                                    this.messages = messages;
                                    var messagesWithoutRecipients = [];
                                    for (var m = 0; m < messages.length; m++) {
                                        var messageId = messages[m].id;
                                        for (var g = 0; g < matchGroups.length; g++) {
                                            if (matchGroups[g].messageId === messageId) {
                                                this.pendingMessageCount++;
                                                this.potentiallyDeliverableMessageCount++;
                                                messageId = null;
                                                break;
                                            }
                                        }
                                        if (messageId !== null) {
                                            messagesWithoutRecipients.push(messageId);
                                        }
                                    }
                                    return messagesWithoutRecipients;
                                },
                                useNoDeliveriesMessage: function () {
                                    return this.messages.length > 1 || this._useNoDeliveriesMessage === true;
                                },
                                tryShowingMessageSetChangedAlert: function () {
                                    if (this.changedVisibility === true && this.showMessageSetChangedAlert === true && application._initializationComplete === true) {
                                        if (this._messageSetChangedAlertDisplayed === false) {
                                            me.alert(me.translateLanguage('RecipientsChangedMessage'));
                                            this._messageSetChangedAlertDisplayed = true;
                                        }
                                    }
                                }
                            };

                            var messagesWithoutRecipients = tracker.initialize(messages, matchGroups);
                            composeUiFactory.initializeRecipients(tracker);
                            var someMessagesHaveRecipients = messagesWithoutRecipients.length !== messages.length;
                            if (tracker.useNoDeliveriesMessage()) {
                                for (var m = 0; m < messagesWithoutRecipients.length; m++) {
                                    var messageId = messagesWithoutRecipients[m];
                                    // NEVER hide all messages EXCEPT when displaying jsNoDeliveries
                                    var hideMessage = tracker.reflectUndeliverableMessages && (someMessagesHaveRecipients || useNoDeliveriesMessage);
                                    // update message visibility and set tracker.changedVisibility
                                    composeUiFactory.toggleNoRecipientsMode(messageId, hideMessage, tracker);
                                }
                                if (messages.length > 1) {
                                    composeUiFactory.recalculateMessageHeaderText(messages, me);
                                    tracker.tryShowingMessageSetChangedAlert();
                                }
                                if (useNoDeliveriesMessage === true) {
                                    composeUiFactory.toggleNoDeliveriesMessage(!someMessagesHaveRecipients, false, me);
                                }
                            }

                            if (tracker.pendingMessageCount === 0) {
                                application._initializationComplete = true;
                            }
                        });
                    for (m = 0; m < messages.length; m++) {
                        application.deliverabilityDetected.run(
                            function (messageId) {
                                // this if also keeps Directory (which fires this BEFORE tracker is set) from running this code
                                if (messages.length > 1 || useNoDeliveriesMessage === true) {
                                    tracker.pendingMessageCount = tracker.pendingMessageCount - 1;
                                    if (tracker.pendingMessageCount === 0) {
                                        application._initializationComplete = true;
                                    }
                                }
                            },
                            [messages[m].id]);
                    }
                });
            }
        },

        toggleTargets: function (link) {
            toggleDisplay($(link), "(-)", "(+)");
        },
        toggleSection: function (link) {
            toggleDisplay($(link), this.translateLanguage('HideDetails'), this.translateLanguage('ShowDetails'));
        },
        toggleAlert: function (link) {
            $(link).parent().parent().children().toggle();
        },
        finishPhoneCall: function () {
            var phoneResponse = buildPhoneResponse($('#phoneCallDialog'), this);
            this._application.setCalleeResult(phoneResponse);
            this._application.navigateByName('completeCall');
        },
        cancelPhoneCall: function () {
            this._application.navigateByName('closeDialog');
        },
        baselineHonorificState: function () {
            if (this._honorificRequiredForWrittenCommunication === undefined) {
                this._honorificRequiredForWrittenCommunication = $("#divHonorific").hasClass('vv_required');
            }

            if (this._writtenCommunicationHonorificPlaceholderText === undefined) {
                this._writtenCommunicationHonorificPlaceholderText = $("#UserModel_Honorific").attr('placeholder');
            }
        },
        clearHonorificRequirement: function () {
            $("#divHonorific").removeClass('vv_required');
            $("#UserModel_Honorific").attr('placeholder', this.translateLanguage('UserPrefix'));
        },
        displayFullUserProfile: function () {
            if ($('#vv-edit-profile-link').is(':visible')) {
                $('#vv-edit-profile-link').click();
            }
        }
    });

    function toggleDisplay(link, expanded, collapsed) {
        var ul = link.parent().next();
        if (link.html() === collapsed) {
            link.html(expanded);
            ul.show();
        } else if (link.html() === expanded) {
            link.html(collapsed);
            ul.hide();
        }
    }
})(VoterVoiceUserInterface._onReady, VoterVoiceUserInterface._jQuery);;
VoterVoiceStack = {};
(function(undefined) {
    VoterVoiceStack.startUi = function startUi(settings) {
        var vvui = (function createUserInterface() {
            var api = new VoterVoiceAPI(settings.api.url, settings.api.key, settings.api.language);
            try {
                var languageTranslator = new VoterVoiceTranslator();
                if (settings.translations !== undefined) {
                    for (var translationKey in settings.translations) {
                        if (settings.translations.hasOwnProperty(translationKey)) {
                            var titleCasedTranslationKey = translationKey.substring(0, 1).toUpperCase() + translationKey.substring(1);
                            languageTranslator.phrases[titleCasedTranslationKey] = settings.translations[translationKey];
                        }
                    }
                }

                if (settings.api.supportErrorUrl !== undefined && api.isSupported() !== true) {
                    window.location.href = settings.api.supportErrorUrl;
                }
                window.onbeforeunload = function() { api.requestError = null; };
                api.requestError = function(requestImplementation) {
                    setTimeout(function() {
                        alert(languageTranslator.GetTextForCulture('MessageNetworkError'));
                    }, 2000);
                };

                // create application
                var visitTracker;
                if (settings.referrer !== undefined) {
                    visitTracker = new VisitTracker(api, settings.referrer);
                    visitTracker.initializeVisit();
                }
                var userTracker;
                if (settings.user !== undefined) {
                    userTracker = new UserTracker(api, settings.user.rememberMeUrl);
                    if (settings.user.identity !== undefined) {
                        userTracker.initIdentity(settings.user.identity);
                    }
                    if (visitTracker !== undefined) {
                        userTracker.initializeVisit(visitTracker);
                    }
                    if (settings.user.setBusinessAddressFromLogin === true) {
                        userTracker.setBusinessAddressFromLogin();
                    }
                    if (settings.user.homeSectionVisible !== true) {
                        userTracker.skipHomeValidation();
                    }
                    if (settings.user.businessAddressRequired === false || settings.user.businessSectionVisible !== true) {
                        userTracker.skipBusinessValidation();
                    }
                }

                var application = new VoterVoiceApplication(settings.stateMachine);
                if (visitTracker !== undefined) {
                    application.enableVisitTracking();
                }
                if (settings.billTracker !== undefined) {
                    application.enableBillTracker(api);
                }
                if (settings.addresses !== undefined) {
                    if (userTracker === undefined) {
                        userTracker = new UserTracker(api);
                    }
                    application.enableAddresses(userTracker);
                    if (settings.addresses !== true) {
                        application.enableAdvocacyAddress();
                    }
                }
                if (settings.user !== undefined) {
                    application.enableUserProfile(new EmailAddressService());
                }
                if (settings.candidates != undefined) {
                    application.enableCandidates(api);
                }
                if (settings.forward !== undefined) {
                    application.enableForwardToFriend(api, userTracker, new EmailAddressService());
                }
                if (settings.relationships !== undefined) {
                    application.enableRelationships(api);
                }
                if (settings.scorecard !== undefined) {
                    application.enableScorecard(api);
                }
                if (settings.advocacy !== undefined) {
                    var responseFactory = new AdvocacyResponseFactory(api);
                    responseFactory.friendlyError = function(result, error) {
                        if (JSON.stringify(error).indexOf("500 ") !== -1)
                            alert(languageTranslator.GetTextForCulture('MessageErrorOccurred'));
                        else
                            alert(result);
                    };
                    if (settings.advocacy.viaFacebook === true) {
                        responseFactory.viaFacebook();
                    }
                    if (settings.advocacy.campaign !== undefined || settings.advocacy.directory !== undefined) {
                        application.enableAdvocacy(responseFactory);
                        if (settings.advocacy.campaign !== undefined) {
                            if (settings.advocacy.campaign.trackingId !== null) {
                                responseFactory.trackingId = settings.advocacy.campaign.trackingId;
                            }
                            application.enableCampaignResponse(responseFactory);
                        }
                        if (settings.advocacy.directory !== undefined) {
                            application.enableDirectory(api);
                        }
                    }
                    if (settings.advocacy.confirm !== undefined) {
                        application.enableAdvocacyConfirm(responseFactory);
                    }
                }
                if (settings.newsletters !== undefined) {
                    application.enableNewsletters(api);
                }
                if (settings.activities !== undefined) {
                    application.enableActivities(api);
                }
                if (settings.voterRegistration !== undefined) {
                    application.enableVoterRegistration(api);
                }
                if (settings.petition !== undefined) {
                    application.enablePetitionResponse(api);
                }
                if (settings.surveys !== undefined) {
                    application.enableSurveys(api, settings.surveys.sectionCount);
                }
                if (settings.subscriptions !== undefined) {
                    application.enableSubscriptions(api, userTracker);
                }
                if (settings.events !== undefined) {
                    application.enableEvents(api, userTracker);
                }
                if (settings.blogPost !== undefined) {
                    application.enableBlogPost(api, userTracker);
                }
                if (settings.meetings !== undefined) {
                    application.enableSurveys(api, userTracker);
                    application.enableMeetings(api, userTracker);
                }
                if (settings.scheduleMeeting !== undefined) {
                    application.enableScheduleMeeting(api, userTracker);
                }
                if (settings.mapInfo !== undefined) {
                    application.enableMapApplication(api);
                }

                application.enableWaitsAndDisplayChanged();

                // initialize application
                if (visitTracker !== undefined) {
                    application.initializeVisit(visitTracker);
                }
                if (settings.billTracker !== undefined) {
                    application.initializeBillTracker(settings.billTracker.association, settings.billTracker.defaultGovernmentName, settings.billTracker.defaultKeyLegislationGovernmentName);
                }
                if (settings.addresses !== undefined) {
                    application.initializeAddresses();
                    if (settings.addresses !== true && settings.addresses.disableAdvocacy !== true) {
                        application.initializeAdvocacyAddress(settings.addresses.associationIdName, settings.addresses.associationCountry, settings.lookUpDistricts, settings.addresses.addressQueryParameter, settings.addresses.finalRedirect, settings.bypassPostalCode);
                    }
                }
                if (settings.user !== undefined) {
                    application.initializeUser(settings.user.associationIdName, settings.user.siteNumber, settings.user.associationCountry, settings.user.skipLogin, settings.user.useInternationalUi, settings.customButtonText);
                }
                if (settings.candidates !== undefined) {
                    application.initializeCandidates(settings.candidates.associationIdName, settings.candidates.address.type, settings.candidates.user, settings.candidates.address.value);
                }
                if (settings.forward !== undefined) {
                    application.initializeForwardToFriend(
                        settings.forward.associationId,
                        settings.forward.associationIdName,
                        settings.forward.userToken,
                        settings.forward.referringNugget,
                        settings.forward.subject.applicationName,
                        settings.forward.subject.id,
                        settings.forward.subject.headlines,
                        settings.forward.subject.details,
                        settings.forward.shareUrl,
                        settings.forward.forwardConfirmationCallback
                    );
                }
                if (settings.relationships !== undefined) {
                    application.initializeRelationships(settings.relationships.associationIdName, settings.relationships.countryCodeISO3166_1);
                }
                if (settings.scorecard !== undefined) {
                    application.initializeScorecard(settings.scorecard.associationIdName);
                }
                if (settings.events !== undefined) {
                    application.initializeEvents(settings.events.id, settings.events.associationIdName, settings.events.confirmRegistrationRedirect, settings.customButtonText);
                }
                if (settings.blogPost !== undefined) {
                    application.initializeBlogPost(settings.blogPost.associationIdName);
                }
                if (settings.commentSubject !== undefined) {
                    application.initializeComments(settings.commentSubject.type, settings.commentSubject.id, settings.commentSubject.associationIdName, settings.commentSubject.maximumCharacterLength);
                }
                if (settings.meetings !== undefined) {
                    application.initializeMeetings({ associationIdName: settings.meetings.associationIdName, siteNumber: settings.meetings.siteNumber, countryCodeISO3166_1: settings.meetings.countryCodeISO3166_1, timeZones: settings.timeZones }, settings.meetings.meeting);
                }
                if (settings.scheduleMeeting !== undefined) {
                    application.initializeScheduleMeeting(settings.scheduleMeeting.associationIdName, settings.scheduleMeeting.siteNumber, settings.scheduleMeeting.countryCodeISO3166_1, settings.scheduleMeeting.confirmationUrl);
                }
                application.start();
                if (settings.advocacy !== undefined) {
                    var campaignId = settings.advocacy.campaign !== undefined
                        ? settings.advocacy.campaign.id
                        : null;
                    var confirmResponseUrl = settings.advocacy.campaign !== undefined
                        ? settings.advocacy.campaign.confirmResponseUrl
                        : null;
                    if (settings.advocacy.campaign !== undefined || settings.advocacy.directory !== undefined) {
                        application.initializeAdvocacy(campaignId, settings.advocacy.associationIdName, confirmResponseUrl, settings.customButtonText);
                        if (settings.advocacy.campaign !== undefined) {
                            application.initializeCampaignResponse(false);
                        }
                        if (settings.advocacy.directory !== undefined) {
                            if (settings.advocacy.directory.singleOfficial !== undefined) {
                                application.initializeDirectory(settings.advocacy.associationIdName, settings.advocacy.directory.singleOfficial);
                            } else if (settings.advocacy.directory.address !== undefined) {
                                application.initializeDirectory(settings.advocacy.associationIdName, settings.advocacy.directory.address.type, settings.advocacy.directory.address.value);
                            } else {
                                application.initializeDirectory(settings.advocacy.associationIdName);
                            }

                            application.setComposeSectionVisibility(settings.advocacy.directory.hideComposeMessage);
                        }
                    }
                    if (settings.advocacy.confirm !== undefined) {
                        application.initializeAdvocacyConfirm(settings.advocacy.confirm.campaignId,
                                                              settings.advocacy.confirm.userToken,
                                                              settings.advocacy.confirm.deliveredMessages,
                                                              settings.advocacy.confirm.transactionIds,
                                                              settings.association,
                                                              settings.advocacy.confirm.allowMessage,
                                                              settings.advocacy.confirm.allowTwitter,
                                                              settings.advocacy.confirm.allowFacebook,
                                                              settings.advocacy.confirm.allowPhone);
                    }
                }
                if (settings.newsletters !== undefined) {
                    if (settings.newsletters.associationIdName !== undefined) {
                        application.initializeNewsletters(settings.newsletters.associationIdName);
                    } else {
                        application.initializeNewsletters();
                    }
                }
                if (settings.activities !== undefined) {
                    application.initializeActivities(settings.activities.resource, settings.activities.associationIdName, settings.activities.associationDisplayName);
                }

                if (settings.voterRegistration !== undefined) {
                    application.initializeVoterRegistration(settings.voterRegistration.associationIdName);
                }

                if (settings.petition !== undefined) {
                    application.initializePetitionResponse(settings.petition.id, settings.petition.showBackButton,settings.petition.hideAlert, settings.customButtonText);
                }
                if (settings.surveys !== undefined) {
                    if (settings.surveys.meetingInformation !== undefined) {
                        application.initializeSurveys(settings.surveys.surveyId, settings.surveys.associationIdName, settings.surveys.siteNumber, settings.surveys.countryCodeISO3166_1, settings.surveys.meetingInformation, settings.surveys.confirmResponseUrl, settings.surveys.confirmResponseCallback,settings.surveys.hideAlert, settings.customButtonText);
                    } else {
                        application.initializeSurveys(settings.surveys.surveyId, settings.surveys.associationIdName, settings.surveys.siteNumber, settings.surveys.countryCodeISO3166_1, undefined, settings.surveys.confirmResponseUrl, settings.surveys.confirmResponseCallback,settings.surveys.hideAlert, settings.customButtonText);
                    }
                }

                if (settings.subscriptions !== undefined) {
                    if (settings.user !== undefined && settings.user.associationIdName !== undefined) {
                        application.initializeSubscriptions(settings.user.associationIdName, settings.subscriptions.unsubscribeCallback);
                    } else {
                        application.initializeSubscriptions(settings.subscriptions.unsubscribeCallback);
                    }
                }
                if (settings.mapInfo !== undefined) {
                    application.initializeMapApplication(settings.associationIdName, settings.mapInfo);
                }

                if (settings.finalRedirect !== undefined) {
                    application.finalRedirect = settings.finalRedirect;
                }
                return new VoterVoiceUserInterface(application, api, languageTranslator);
            } catch (e) {
                var logger = new VoterVoiceLogger(api);
                logger.logException(e);
                throw e;
            }
        })();

        // initialize UI
        if (settings.scorecard !== undefined) {
            vvui.initializeScorecard();
        }
        if (settings.relationships !== undefined) {
            vvui.initializeRelationships();
        }
        if (settings.forward !== undefined) {
            vvui.initializeForwardToFriend();
        }
        if (settings.advocacy !== undefined) {
            if (settings.advocacy.campaign !== undefined || settings.advocacy.directory !== undefined) {
                if (settings.advocacy.directory !== undefined) {
                    vvui.initializeDirectory(settings.advocacy.directory.hiddenDisplayCategories, settings.advocacy.associationIdName);

                    if (settings.advocacy.directory.hideComposeMessage)
                        vvui.hideOfficialCheckboxes();
                }
                var enableMessageSetChangedAlert = false, useNoDeliveriesMessage = false;
                var forwardUrl = undefined;
                if (settings.advocacy.campaign !== undefined) {
                    enableMessageSetChangedAlert = settings.advocacy.campaign.enableMessageSetChangedAlert;
                    useNoDeliveriesMessage = settings.advocacy.campaign.useNoDeliveriesMessage;

                    if (settings.advocacy.campaign.forwardUrl !== undefined) {
                        forwardUrl = settings.advocacy.campaign.forwardUrl;
                    }
                }

                vvui.initializeAdvocacy(enableMessageSetChangedAlert, useNoDeliveriesMessage, forwardUrl);
            }
            if (settings.advocacy.confirm !== undefined) {
                vvui.initializeAdvocacyConfirm();
            }
        }
        if (settings.newsletters !== undefined) {
            vvui.initializeNewsletters();
        }
        if (settings.activities !== undefined) {
            vvui.initializeActivities(settings.activities.resource, settings.activities.associationDisplayName);
        }
        if (settings.voterRegistration !== undefined) {
            vvui.initializeVoterRegistration();
        }
        if (settings.petition !== undefined) {
            vvui.initializePetitions();
        }
        if (settings.user !== undefined) {
            if (settings.user.readOnly !== true) {
                vvui.initializeUserEmail();
            }
            if (settings.addresses !== undefined) {
                var startNonRegisteredMatching = settings.advocacy !== undefined && settings.advocacy.campaign !== undefined
                    ? settings.advocacy.campaign.startNonRegisteredMatching
                    : settings.user.identity !== undefined ? settings.user.identity.id === 0
                    : false;
                var hideLoginLink = settings.user.hideLoginLink !== undefined
                    ? settings.user.hideLoginLink
                    : false;
                var confirmEmailChange = settings.user.confirmEmailChange !== undefined
                   ? settings.user.confirmEmailChange
                   : true;

                if (vvui.initializeUser !== undefined) {
                    vvui.initializeUser(startNonRegisteredMatching, settings.user.weaklyMatchedAddressTypes, confirmEmailChange);
                }

                if (vvui.initializeUserDisplay !== undefined) {
                    vvui.initializeUserDisplay(hideLoginLink);
                }
            }
        }
        if (settings.candidates !== undefined) {
            vvui.initializeCandidates(settings.candidates.hiddenDisplayCategories);
        }

        if (settings.addresses !== undefined) {
            var addressUi, defaultCountry;
            if (settings.addresses === true) {
                addressUi = { bindUserAddresses: true };
                if (settings.user !== undefined) {
                    // The user setting should be there when addresses === true, but currently it is not for Survey Results.
                    // It does not seem to be needed there, and addresses itself may not be needed either.
                    defaultCountry = settings.user.associationCountry;
                }
            } else {
                addressUi = settings.addresses.ui;
                defaultCountry = settings.addresses.associationCountry;
            }
            vvui.initializeAddresses(addressUi, defaultCountry);

            if (addressUi.postalCodeSelector !== undefined || addressUi.addressIdPrefix !== undefined) {
                vvui.initializeAdvocacyAddress(addressUi.postalCodeSelector);
            }
        }
        if (settings.billTracker !== undefined) {
            vvui.initializeBillTracker(settings.billTracker.initialSearch);
        }
        if (settings.surveys !== undefined) {
            if (settings.surveys.showPercentagesOnly !== undefined) {
                vvui.initializeSurveys({ countryCodeISO3166_1: settings.surveys.countryCodeISO3166_1, showPercentagesOnly: settings.surveys.showPercentagesOnly, renderTitle: settings.surveys.renderTitle, timeZones: settings.timeZones }, settings.surveys.meetingInformation);
            } else {
                vvui.initializeSurveys({ countryCodeISO3166_1: settings.surveys.countryCodeISO3166_1, showPercentagesOnly: undefined, renderTitle: settings.surveys.renderTitle, timeZones: settings.timeZones }, settings.surveys.meetingInformation);
            }
        }
        if (settings.subscriptions !== undefined) {
            vvui.initializeSubscriptions();
        }
        if (settings.events !== undefined) {
            vvui.initializeEvents(settings.events.country);
        }
        if (settings.blogPost !== undefined) {
            vvui.initializeBlogPost();
        }
        if (settings.commentSubject !== undefined) {
            vvui.initializeComments(settings.commentSubject.maximumCharacterLength, settings.commentSubject.commentsRequireApproval);
        }
        if (settings.meetings !== undefined) {
            vvui.initializeMeetings(settings.meetings.associationIdName, settings.meetings.siteNumber, settings.meetings.displaySettings, settings.meetings.detailsUrlTemplate, settings.meetings.meeting);
        }
        if (settings.scheduleMeeting !== undefined) {
            vvui.initializeScheduleMeeting(settings.scheduleMeeting.meetingId, settings.scheduleMeeting.countryCodeISO3166_1, settings.scheduleMeeting.isReadOnlyInvitation, settings.timeZones);
        }
        if (settings.mapInfo !== undefined) {
            vvui.initializeMapUi(settings.mapInfo);
        }
        vvui.initialize();

        return vvui;
    };
})();;
(function ($) {
    $.fn.fixedSidebar = function (options) {
        if (typeof $ == 'undefined') {
            $ = jQuery();
        }

        var me = this;
        var defaults = {
            anchor: '.vv-content-container',
            content: '.vvBody',
            fixedClass: 'vv-sidebar-fixed-content'
        };
        var settings = $.extend({}, defaults, options);

        // initialization block
        {
            function positionSidebar() {
                var bounds = $(settings.anchor);
                var floater = $(me);
                var content = floater.find(settings.content);

                var buffer = 0;
                var offset = 0;
                if (floater.css('float') !== 'none' && bounds.height() > floater.height()) {
                    var top = Math.min.apply(null, $.map(bounds, function (el) { return $(el).offset().top; }));
                    var bottom = Math.max.apply(null, $.map(bounds, function (el) { return $(el).offset().top + $(el).height(); }));
                    if (typeof floatBuffer === 'undefined') {
                        floatBuffer = top - floater.offset().top;
                    }

                    buffer = floatBuffer;
                    offset = 0;
                    if ($(window).scrollTop() > top) {
                        offset = $(window).scrollTop() - top;
                        if (offset + floater.height() > bottom - top) {
                            offset = bottom - top - floater.height();
                        }
                    }
                    
                    if ((buffer + offset) > 0) {
                        var marginBottom = parseInt(content.css('margin-bottom').replace(/px/i, ''));
                        var paddingBottom = parseInt(content.css('padding-bottom').replace(/px/i, ''));
                        var totalHeight = content.height() + marginBottom + paddingBottom;
                        if ((buffer + offset + totalHeight <= bounds.height())) {
                            content.addClass(settings.fixedClass);
                            content.css('top', '0');
                        } else {
                            
                            offset = bounds.height() - (buffer + offset + totalHeight);
                            content.css('top', offset);
                        }
                    } else {
                        content.removeClass(settings.fixedClass);
                        content.css('top', '0');
                    }
                } else if (content.hasClass(settings.fixedClass)) {
                    content.removeClass(settings.fixedClass);
                    content.css('top', '0');
                }
            }

            $(window).bind('load scroll', positionSidebar);
        }
    };
})(jQuery);;
