SmsStatus-1.11.0.js

/**
 * SMS Status embedding library v1.11.0
 * http://sms.webmoney.ru/SmsStatus/HelpV1/Manual
 */
/* jshint browser: true */
/* globals $, jQuery, ActiveXObject */
(function() {
    'use strict';

    /**
     *  Represents the result of getting current SMS status.
     *  @typedef {Object} SmsStatus~SmsReportQueryResult
     *  @global
     *
     *  @property {string} SecureId
     *  The secure ID of the message.
     *
     *  @property {string} Locale
     *  The locale of the response.
     *
     *  @property {string} UserData
     *  An arbitrary string associated with the request by the request sender.
     *
     *  @property {SmsStatus~SmsReport} Report
     *  The user report on an outgoing SMS.
     */

    /**
     *  Represents a report on an outgoing message.
     *  @typedef {Object} SmsStatus~SmsReport
     *  @global
     *
     *  @property {Date} Created
     *  The date and time the message was created.
     *
     *  @property {string} CreatedStr
     *  The string representation of the {@link Created} property.
     *
     *  @property {Date} StatusUpdated
     *  The date and time the message status was last updated.
     *
     *  @property {string} StatusUpdatedStr
     *  The string representation of the {@link StatusUpdated} property.
     *
     *  @property {number} SendingRetries
     *  The number of sending retries.
     *
     *  @property {number} ValidityPeriodMinutes
     *  The amount of time in minutes since the message was created before the message will be
     *  discarded if not delivered to the destination.
     *
     *  @property {string} ValidityPeriodStr
     *  The string representation of the {@link ValidityPeriodMinutes} property.
     *
     *  @property {Date} ValidUntil
     *  The date and time when the message will be (or was) discarded if not delivered to the
     *  destination.
     *
     *  @property {string} ValidUntilStr
     *  The string representation of the {@link ValidUntil} property.
     *
     *  @property {string} LastTransportTypeStr
     *  The name of the message transport last used to send the message.
     *
     *  @property {number} LastTransportType
     *  The identifier of the message transport last used to send the message.
     *
     *  @property {string} Status
     *  The name of the current message status.
     *
     *  @property {number} StatusId
     *  The identifier of current message status.
     *
     *  @property {string} [StatusDetails]
     *  The detailed description of current message status.
     *
     *  @property {string} [StatusInstruction]
     *  The user instruction describing the reasons of current message status delivery failure and
     *  possibly instruction on how to fix it.
     *
     *  @property {boolean} Resendable
     *  true if the message is resendable; otherwise, false.
     *
     *  @property {number} [UntilResendableSeconds]
     *  The time in seconds until the message can be resent by a user request or null if the message
     *  can not be resent or the time when the message becomes resendable is currently unknown.
     *
     *  @property {string} [UntilResendableStr]
     *  The string representation of the {@link UntilResendableStr} property.
     *
     *  @property {string} [UntilResendableStrV2]
     *  The string representation of the {@link UntilResendableStr} property according to CLDR.
     *
     *  @property {boolean} [IsFinalState]
     *  true if the message report can change on further queries; otherwise, false.
     *
     *  @property {Array<SmsStatus~SmsSendingReport>} [SendingAtempts]
     *  The reports on attempts to send the message.
     */

    /**
     *  Represents a report on a message sending attempt.
     *  @typedef {Object} SmsStatus~SmsSendingReport
     *  @global
     *
     *  @property {number} [AttemptNumber]
     *  The number of sending attempt.
     *
     *  @property {Date} Created
     *  The date and time the sending attempt was made.
     *
     *  @property {string} CreatedStr
     *  The string representation of the {@link Created} property.
     *
     *  @property {Date} Updated
     *  The date and time the sending attempt status was last updated.
     *
     *  @property {string} UpdatedStr
     *  The string representation of the {@link Updated} property.
     *
     *  @property {string} Status
     *  The sending attempt status.
     *
     *  @property {number} StatusId
     *  The sending attempt status identifier.
     *
     *  @property {string} TransportTypeStr
     *  The name of the delivery transport type used in this attempt.
     *
     *  @property {number} TransportType
     *  The identifier of the delivery transport type used in this attempt.
     */

    /**
     *  Represents the response to a command to resend a message.
     *  @typedef {Object} SmsStatus~SmsResendCommandResult
     *  @global
     *
     *  @property {string} SecureId
     *  The secure ID of the message.
     *
     *  @property {string} Locale
     *  The locale of the response.
     *
     *  @property {string} UserData
     *  An arbitrary string associated with the request by the request sender.
     *
     *  @property {string} Status
     *  The resending status code.
     */

    /**
     *  Represents loading indicator display configuration for a loading event.
     *  @typedef {Object} SmsStatus~LoadingEventSettings
     *  @global
     *
     *  @property {boolean} [showIndicator=true]
     *  A flag indicating whether the loading indicator should be displayed.
     *
     *  @property {number} [minVisibleTime=1000]
     *  The minimum amount of time in milliseconds the loading indicator should be visible.
     *
     *  @property {number} [showDelay=0]
     *  The amount of time in milliseconds after the event occurs before the loading indicator should
     *  be displayed.
     */

    /**
     *  Represents {@link SmsStatus} object setting.
     *  @typedef {Object} SmsStatus~Settings
     *  @global
     *
     *  @property {boolean} [showIndicator=true]
     *  A flag indicating whether the loading indicator should be displayed.
     *
     *  @property {String} [dtf] Date and time values format.
     *
     *  @property {Number} [pollTimeout=5000] SMS status update frequency in milliseconds.
     *  @property {String} [pollUrlBase='https://sms.webmoney.ru/SmsStatus/Sms/'||'https://sms.web.money/SmsStatus/Sms/'] The base part of SMS status poll URL.
     *  @property {String} [resendUrlBase='https://sms.webmoney.ru/SmsStatus/Resend/'||'https://sms.web.money/SmsStatus/Resend/'] The base part of SMS resend URL.
     *  @property {String} [timezone]
     *  The desired timezone. If not specified, resolves to the current timezone of the user. If current
     *  timezone can not be resolved in the current browser, defaults to 'MSK'.
     *  @property {Boolean} [setButtonHandlers=true] Boolean flag indicating if the button click handlers should be set.
     *  @property {Boolean} [untilResendableCountdown=true]
     *  Boolean flag indicating if {@link SmsStatus~SmsReport.UntilResendable} and related strings
     *  should be updated automatically between receiving updates from the server. Note that the
     *  countdown availability depends on browser support.
     *
     *  @property {String} [loadingIndicator='#sms-loading-indicator']
     *  Selector matching SMS loading indicator.
     *
     *  @property {Object<string,SmsStatus~LoadingEventSettings>} [loadingEvents={'poll': {showDelay: 1000}, '': {showIndicator: true, minVisibleTime: 1000, showDelay: 0}}]
     *  An object specifying loading indicator parameters for various loading events. Use empty key
     *  to specify the default parameters.
     *
     *  @property {Object} [errorBlock={}]
     *  The parameters of the block containing the error messages.
     *
     *  @property {String} [reportBlock.root='#sms-error']
     *  Selector matching SMS status errors root element.
     *
     *  @property {String} [errorBlock.reportError='#sms-error-report']
     *  Selector matching element displaying error message if server response does not contain SMS status report. This can
     *  happen if SMS with the specified ID does not exist or was sent long time ago.
     *
     *  @property {Object} [reportBlock={}]
     *  The parameters of the block containing the message status report.
     *
     *  @property {String} [reportBlock.root='#sms-report']
     *  Selector matching SMS status report root element.
     *
     *  @property {String} [reportBlock.created='#sms-report-created']
     *  Selector matching element displaying date and time the SMS was created.
     *
     *  @property {String} [reportBlock.updated='#sms-report-updated']
     *  Selector matching element displaying date and time of the latest SMS status update.
     *
     *  @property {String} [reportBlock.retries='#sms-report-retries']
     *  Selector matching element displaying the number of attempts to send the SMS.
     *
     *  @property {String} [reportBlock.validity='#sms-report-validity']
     *  Selector matching element displaying the message validity period (time before attempts to deliver the SMS are abandoned).
     *
     *  @property {String} [reportBlock.validUntil='#sms-report-valid-until',']
     *  Selector matching element displaying the date and time message validity expires (date and time when further attempts to deliver the SMS are abandoned).
     *
     *  @property {String} [reportBlock.lastTransportType='#sms-report-last-transport-type']
     *  Selector matching element displaying the message transport last used to send the message.
     *
     *  @property {String} [reportBlock.status='#sms-report-status']
     *  Selector matching element displaying current SMS status.
     *
     *  @property {String} [reportBlock.refresh='#sms-report-refresh']
     *  Selector matching element used as a button to refresh message status immediately.
     *
     *  @property {String} [reportBlock.instruction='#sms-report-instruction']
     *  Selector matching container element of detailed description of current SMS status and short instruction on how to
     *  resolve delivery problems (e.g. turn on the cell phone). This element is used to hide instruction block if the instruction
     *  is not present in SMS status report.
     *
     *  @property {String} [reportBlock.instructionText='#sms-report-instruction-text']
     *  Selector matching element displaying detailed description of current SMS status and short instruction on how to
     *  resolve delivery problems (e.g. turn on the cell phone). This element should be a child element of the element matching
     *  {@link reportBlock.instruction} selector.
     *
     *  @property {String} [reportBlock.supportLinks='#sms-report-support-links']
     *  Selector matching a block containing links to notify WebMoney if the message was not delivered.
     *
     *  @property {String} [reportBlock.untilResendable='#sms-report-until-resendable']
     *  Selector matching a container element for the amount of time remaining until the message can be resent on a user request.
     *  This element is used to hide the block if the information on the reaming time until the message can be resent on a user
     *  request is not present in SMS status report.
     *
     *  @property {String} [reportBlock.untilResendableText='#sms-report-until-resendable-text']
     *  Selector matching a element displaying the amount of time remaining until the message can be resent on a user request.
     *  This element should be a child element of the element matching {@link reportBlock.untilResendable} selector.
     *
     *  @property {Number} [reportBlock.untilResendableVersion=2]
     *  The version of the string representation of {@link SmsStatus~SmsReport.UntilResendableSeconds} to use.
     *  Set to 1 to use {@link SmsStatus~SmsReport.UntilResendableStr} or set to 2 to use {@link SmsStatus~SmsReport.UntilResendableStrV2}.
     *  If value is not set or is not recognized, version 2 will be used.
     *
     *  @property {Object} [reportBlock.sendingsList={}]
     *  The parameters of the block displaying attempts to send the SMS.
     *
     *  @property {String} [reportBlock.sendingsList.root='#sms-report-attempts']
     *  Selector matching container element for elements displaying attempts.
     *
     *  @property {String} [reportBlock.sendingsList.itemTemplate='#sms-report-attempt-template']
     *  Selector matching element serving as a template for elements displaying sending attempts.
     *
     *  @property {String} [reportBlock.sendingsList.doNotClear='.sms-report-attempts-do-not-clear']
     *  Selector matching child elements of the sending attempts container element that must not be removed
     *  when the attempts list is cleared.
     *
     *  @property {String} [reportBlock.sendingsList.attemptNumber='#sms-report-attempt-number']
     *  Selector matching element within the template displaying attempt number.
     *
     *  @property {String} [reportBlock.sendingsList.created='#sms-report-attempt-created']
     *  Selector matching element within the template displaying the date and time attempt was made.
     *
     *  @property {String} [reportBlock.sendingsList.updated='#sms-report-attempt-updated']
     *  Selector matching element within the template displaying the date and time the attempt status was last updated.
     *
     *  @property {String} [reportBlock.sendingsList.status='#sms-report-attempt-status']
     *  Selector matching element within the template displaying the attempt status.
     *
     *  @property {String} [reportBlock.sendingsList.transportType='#sms-report-attempt-transport-type']
     *  Selector matching element within the template displaying delivery transport type used in the
     *  attempt.
     *
     *  @property {function} [reportBlock.sendingsList.update=undefined]
     *  Deprecated since version 1.3.0. Override {@link sendingsUpdate} method instead.
     *  Function processing all sending attempts reports. If undefined, clears sendings using
     *  {@link reportBlock.sendingsList.clear} and adds the reports by one using
     *  {@link reportBlock.sendingsList.addItem}.
     *
     *  @property {function} [reportBlock.sendingsList.addItem=undefined]
     *  Deprecated since version 1.3.0. Override {@link sendingsAddItem} method instead.
     *  Function adding a sending attempt report. The template is used if this parameter is undefined.
     *
     *  @property {function} [reportBlock.sendingsList.clear=undefined]
     *  Deprecated since version 1.3.0. Override {@link sendingsClear} method instead.
     *  Function clearing all elements displaying sending attempt reports. If undefined, clears all
     *  children of the element matching {@link reportBlock.sendingsList.root} except any
     *  elements matching {@link reportBlock.sendingsList.itemTemplate} and
     *  {@link reportBlock.sendingsList.doNotClear}.
     *
     *  @property {Number} [reportBlock.sendingsList.displayMinCount=2]
     *  The minimum number of displayed sending attempts or a negative number to turn off sending
     *  attempts details display.
     *
     *  @property  {Object} [resendBlock={}]
     *  The parameters of the block providing button to resend the SMS.
     *
     *  @property {String} [resendBlock.root='#sms-resend']
     *  Selector matching SMS resend block root element.
     *
     *  @property {String} [resendBlock.button='#sms-resend-button']
     *  Selector matching SMS resend button.
     *
     *  @property {String} [resendBlock.status='#sms-resend-status']
     *  Selector matching SMS resend attempt status.
     */

    /**
     *  Initializes a new SMS status watcher instance.
     *  @constructor
     *  @global
     *
     *  @param {String} id SMS secure identifier.
     *  @param {String} culture The output culture (en, en-US, ru, ru-RU and so on).
     *  @param {SmsStatus~Settings} [settings={}] Optional SMS poller settings.
     *
     *  @example
     *  // Initializes settings with default values.
     *  // Can be used as a template for custom settings object.
     *
     *  var settings = {
     *      // Use default date and time format
     *      dtf: encodeURIComponent(''),
     *
     *      // Polling configuration
     *      pollTimeout: 5000,
     *      pollUrlBase: 'https://sms.webmoney.ru/SmsStatus/Sms/',
     *      resendUrlBase: 'https://sms.webmoney.ru/SmsStatus/Resend/',
     *      timezone: 'MSK',
     *
     *      setButtonHandlers: true,
     *      untilResendableCountdown: true,
     *
     *      // Loading indicator configuration: show indicator immediately when message data loads for
     *      // the first time and when user clicks a button, show indicator for automatic polling only
     *      // if the server takes more than 1 second to respond.
     *      loadingIndicator: '#sms-loading-indicator',
     *      loadingEvents: {
     *          'poll': {
     *              showDelay: 1000
     *          }
     *      },
     *
     *      // Error display elements selectors
     *      errorBlock: {
     *          root: '#sms-error',
     *          reportError: '#sms-error-report'
     *      },
     *
     *      // Message status display elements selectors
     *      reportBlock: {
     *          root: '#sms-report',
     *          created: '#sms-report-created',
     *          updated: '#sms-report-updated',
     *          retries: '#sms-report-retries',
     *          validity: '#sms-report-validity',
     *          validUntil: '#sms-report-valid-until',
     *          lastTransportType: '#sms-report-last-transport-type',
     *          status: '#sms-report-status',
     *          refresh: '#sms-report-refresh',
     *          instruction: '#sms-report-instruction',
     *          instructionText: '#sms-report-instruction-text',
     *          supportLinks: '#sms-report-support-links',
     *          untilResendable: '#sms-report-until-resendable',
     *          untilResendableText: '#sms-report-until-resendable-text',
     *          untilResendableVersion: 2,
     *
     *          sendingsList : {
     *              root: '#sms-report-attempts',
     *              itemTemplate: '#sms-report-attempt-template',
     *              attemptNumber: '#sms-report-attempt-number',
     *              created: '#sms-report-attempt-created',
     *              updated: '#sms-report-attempt-updated',
     *              status: '#sms-report-attempt-status',
                    transportType: '#sms-report-attempt-transport-type',
     *              update: undefined,
     *              addItem: undefined,
     *              clear: undefined,
     *              displayMinCount : 2
     *          }
     *      },
     *      // Resend controls block selectors
     *      resendBlock: {
     *          root: '#sms-resend',
     *          button: '#sms-resend-button',
     *          status: '#sms-resend-status'
     *      }
     *  };
     *  var id = 'SecurID_1_12345678901234567890123456';
     *  var culture = 'ru-RU';
     *  var smsStatus = new SmsStatus (id, culture, settings);
     *  smsStatus.startPolling();
     */
    function SmsStatus(id, culture, settings) {
        /**
         *  The instance settings.
         *
         *  @see {@link SmsStatus} 'settings' parameter for description.
         */
        this.settings = this._setDefaultSettings(settings, SmsStatus.prototype.settings);

        this._id = id;
        this._culture = culture;

        /**@private*/
        this.pollId = undefined;
        var timezone = encodeURIComponent(this.settings.timezone);
        var dtf = encodeURIComponent(this.settings.dtf);
        /**@private*/
        this._pollUrl = this.settings.pollUrlBase + this._culture + '/' + timezone + '/Json/' + this._id +
            '?dtf=' + dtf;
        /**@private*/
        this._resendUrl = this.settings.resendUrlBase + this._culture + '/' + timezone + '/Json/' + this._id +
            '?dtf=' + dtf;

        /**
         *  Contains the last received SMS Status check response.
         */
        this.lastStatusResponse = null;

        /**
         *  Contains the last received SMS resend response.
         */
        this.lastResendResponse = null;

        /**
         *  @deprecated Deprecated since version 1.3.0. Use {@link SmsStatus.prototype.sendingsUpdate}.
         *
         *  The default function processing sending attempt reports.
         *
         *  @param {Object} e The event arguments.
         *  @param {SmsStatus} e.sender Current SMS status poller object.
         *  @param {SmsStatus~SmsReportQueryResult} e.response The SMS status service response.
         *
         *  @return {undefined}
         */
        this.defaultUpdateSendings = function(e) {
            SmsStatus.prototype.sendingsUpdate.call(this, e);
        };

        /**
         *  @deprecated Deprecated since version 1.3.0. Use {@link SmsStatus.prototype.sendingsAddItem}.
         *
         *  The default function adding a sending attempt report. Uses the template.
         *
         *  @param {SmsStatus~SmsSendingReport} sending The sending attempt data to add.
         *
         *  @return {undefined}
         */
        this.defaultAddSending = function(sending) {
            SmsStatus.prototype.sendingsAddItem.call(this, sending);
        };

        /**
         *  @deprecated Deprecated since version 1.3.0. Use {@link SmsStatus.prototype.sendingsClear}.
         *
         *  The default function clearing all elements displaying sending attempt reports.
         *  Clears all children of the element matching {@link settings.reportBlock.sendingsList.root} except any
         *  elements matching {@link settings.reportBlock.sendingsList.itemTemplate} and
         *  {@link settings.reportBlock.sendingsList.doNotClear}.
         */
        this.defaultClearSendings = function() {
            SmsStatus.prototype.sendingsClear.call(this);
        };

        if (this.settings.reportBlock.sendingsList.update) {
            this.sendingsUpdate = this.settings.reportBlock.sendingsList.update;
        }

        if (this.settings.reportBlock.sendingsList.clear) {
            this.sendingsClear = this.settings.reportBlock.sendingsList.clear;
        }

        if (this.settings.reportBlock.sendingsList.addItem) {
            this.sendingsAddItem = this.settings.reportBlock.sendingsList.addItem;
        }

        if (this.settings.setButtonHandlers) {
            $(this.settings.resendBlock.button).click(this, function(e) {
                e.data.resend();
                return false;
            });
            $(this.settings.reportBlock.refresh).click(this, function(e) {
                e.data.pollNow();
                return false;
            });
        }

        /**
         *  The index of this instance in the {@link SmsStatus.instances} array.
         */
        this.instanceIndex = SmsStatus.instances.push(this) - 1;
        var untilResendableCountdown =
            this.settings.untilResendableCountdown &&
            typeof Intl !== 'undefined' &&
            typeof Intl.RelativeTimeFormat !== 'undefined' &&
            // performance.now is far more widely available than RelativeTimeFormat hence polyfill
            // here is redundant.
            typeof performance !== 'undefined' &&
            typeof performance.now === 'function';
        if (untilResendableCountdown) {
            /**@private*/
            this._untilResendableCountdownHandler = (function() {
                function relativeTimeFormat(locale) {
                    return new Intl.RelativeTimeFormat(locale, {
                        style: 'long',
                        numeric: 'always',
                        numberingSystem: 'latn'
                    });
                }
                function unitFormat(locale, unit) {
                    return new Intl.NumberFormat(locale, {
                        style: 'unit',
                        unitDisplay: 'short',
                        unit: unit,
                        numberingSystem: 'latn'
                    });
                }

                function getFormatter(handler, locale) {
                    if (handler._locale !== locale) {
                        var rtf = relativeTimeFormat(locale);
                        var formatRelativeTime = function(seconds) {
                            var value;
                            var unit;
                            if (seconds > 60 * 60) {
                                value = Math.ceil(seconds / (60 * 60));
                                unit = 'hour';
                            } else if (seconds > 60) {
                                value = Math.ceil(seconds / 60);
                                unit = 'minute';
                            } else {
                                value = seconds;
                                unit = 'second';
                            }
                            return rtf.format(value, unit);
                        };

                        var formatDuration;
                        try {
                            var sf = unitFormat(locale, 'second').format;
                            var mf = unitFormat(locale, 'minute').format;
                            var hf = unitFormat(locale, 'hour').format;
                            var df = unitFormat(locale, 'day').format;
                            formatDuration = function(seconds) {
                                if (seconds <= 60) {
                                    return sf(seconds);
                                }

                                var fullTotalMinutes = Math.ceil(seconds / 60);
                                var normalizedTotalHours = fullTotalMinutes / 60;
                                var normalizedTotalDays = normalizedTotalHours / 24;

                                var result = '';

                                var hasDays = normalizedTotalDays >= 1;
                                if (hasDays) {
                                    var days = Math.floor(normalizedTotalDays);
                                    result += df(days);
                                }

                                var hasHours = normalizedTotalHours >= 1;
                                if (hasHours) {
                                    var hours = Math.floor(normalizedTotalHours) % 24;
                                    if (result) {
                                        result += ' ';
                                    }
                                    result += hf(hours);
                                }

                                var minutes = fullTotalMinutes % 60;
                                if (result) {
                                    result += ' ';
                                }
                                result += mf(minutes);

                                return result;
                            };
                        } catch (e) {
                            formatDuration = function() {
                                return null;
                            };
                        }
                        handler._formatter = {
                            formatRelativeTime: formatRelativeTime,
                            formatDuration: formatDuration
                        };
                        handler._locale = locale;
                    }

                    return handler._formatter;
                }

                function updateReport(target) {
                    var response = target.lastStatusResponse;
                    var report = response && response.Report;
                    if (report) {
                        var locale = response.Locale;
                        var formatter = getFormatter(target._untilResendableCountdownHandler, locale);

                        var untilResendableStr = formatter.formatDuration(report.UntilResendableSeconds) ||
                            report.UntilResendableStr;
                        report.UntilResendableStr = untilResendableStr;
                        var untilResendableStrV2 = formatter.formatRelativeTime(report.UntilResendableSeconds) ||
                            report.UntilResendableStrV2;
                        report.UntilResendableStrV2 = untilResendableStrV2;

                        var newInstruction = document.createElement('div');
                        newInstruction.innerHTML = report.StatusInstruction;
                        var untiResendablePlaceholders = newInstruction.querySelectorAll('[data-sms-status-instruction="until-resendable"]');
                        for (var i = 0, length = untiResendablePlaceholders.length; i < length; i++) {
                            untiResendablePlaceholders[i].innerHTML = untilResendableStrV2;
                        }

                        if (report.UntilResendableSeconds <= 0) {
                            report.UntilResendableSeconds = null;
                            report.Resendable = true;
                        }

                        if (report.Resendable || !report.UntilResendableSeconds) {
                            var untilResendableNotes = newInstruction.querySelectorAll('[data-sms-status-instruction="resendable-later"]');
                            for (i = 0, length = untilResendableNotes.length; i < length; i++) {
                                var note = untilResendableNotes[i];
                                note.parentElement.removeChild(note);
                            }
                        }

                        report.StatusInstruction = newInstruction.innerHTML;
                    }
                }

                var scheduleDraw;
                var cancelDraw;
                if (typeof requestAnimationFrame === 'function' && typeof cancelAnimationFrame === 'function') {
                    scheduleDraw = requestAnimationFrame;
                    cancelDraw = cancelAnimationFrame;
                } else {
                    scheduleDraw = function(callback) {
                        return setTimeout(callback, 1000);
                    };
                    cancelDraw = clearTimeout;
                }

                function callback(target) {
                    var countdown = target._untilResendableCountdownHandler;
                    var updateTimeout = countdown._updateTimeout;
                    if (updateTimeout) {
                        cancelDraw(updateTimeout);
                    }

                    countdown._initialTimeMs = null;
                    countdown._initialUntilResendableSeconds = null;
                }
                function handleStatus(target) {
                    var response = target.lastStatusResponse;
                    var report = response && response.Report;
                    if (report && report.UntilResendableSeconds > 0) {
                        var countdown = target._untilResendableCountdownHandler;

                        if (countdown._initialTimeMs === null) {
                            countdown._initialTimeMs = performance.now();
                            countdown._initialUntilResendableSeconds = report.UntilResendableSeconds;
                        }
                        updateReport(target);

                        var updateTimeout = countdown._updateTimeout;
                        if (updateTimeout) {
                            cancelDraw(updateTimeout);
                        }
                        target._untilResendableCountdownHandler._updateTimeout = scheduleDraw(function() {
                            if (target.lastStatusResponse !== response) {
                                return;
                            }

                            var countdown = target._untilResendableCountdownHandler;

                            var currentTimeMs = performance.now();
                            var elapsedSeconds = Math.floor((currentTimeMs - countdown._initialTimeMs) / 1000);
                            var newUntilResendableSeconds = countdown._initialUntilResendableSeconds - elapsedSeconds;

                            if (report.UntilResendableSeconds !== newUntilResendableSeconds) {
                                report.UntilResendableSeconds = newUntilResendableSeconds;
                                updateReport(target);
                            }

                            target._handleStatus(response);
                        });
                    }
                }
                return {
                    callback: callback,
                    handleStatus: handleStatus,
                    _untilResendableCountdownHandler: null,
                    _initialUntilResendableSeconds: null
                };
            })();
        } else {
            /**@private*/
            this._untilResendableCountdownHandler = {
                callback: function() { },
                handleStatus: function() { }
            };
        }
    }

    /**
     * An informational string to send to server in X-Requested-With.
     * @private
     */
    SmsStatus.prototype._clientName = 'SmsStatus-1.11.0';

    /**
     * A polyfill for Array.isArray.
     *
     * @param {*} x The value to be checked.
     * @return {boolean} true if the value is an Array; otherwise, false.
     * @private
     */
    SmsStatus.prototype._isArray = Array.isArray || function(x) {
        return Object.prototype.toString.call(x) === '[object Array]';
    };

    if (typeof jQuery !== 'undefined') {
        /**
         * Merges a settings object with default settings.
         *
         * @param {*} settings The user-provided settings.
         * @param {*} defaults The default settings.
         * @return {Object}
         * An object with settings from the {@link settings} with missing settings set to the default
         * value.
         * @private
         */
        SmsStatus.prototype._setDefaultSettings = function(settings, defaults) {
            return jQuery.extend(true, {}, defaults, settings);
        };
    } else {
        /**
         * Merges a settings object with default settings.
         *
         * @param {*} settings The user-provided settings.
         * @param {*} defaults The default settings.
         * @return {Object}
         * An object with settings from the {@link settings} with missing settings set to the default
         * value.
         * @private
         */
        SmsStatus.prototype._setDefaultSettings = function(settings, defaults) {
            function isPrimitiveType(value) {
                return (typeof value === 'string') ||
                    (typeof value === 'boolean') ||
                    (typeof value === 'number');
            }

            if (typeof settings === 'undefined') {
                if (isPrimitiveType(defaults) || this._isArray(defaults)) {
                    return defaults;
                } else {
                    return this._setDefaultSettings({}, defaults);
                }
            }

            if (isPrimitiveType(settings)) {
                return settings;
            }

            if (this._isArray(settings)) {
                // Arrays are not currently used in SmsStatus settings.
                return settings;
            }

            var result = {};
            var name;
            for (name in settings) {
                if (settings.hasOwnProperty(name)) {
                    result[name] = settings[name];
                }
            }

            for (name in defaults) {
                if (defaults.hasOwnProperty(name)) {
                    var currentValue = settings[name];
                    var defaultValue = defaults[name];

                    if (settings === defaultValue) {
                        continue;
                    }

                    result[name] = this._setDefaultSettings(currentValue, defaultValue);
                }
            }

            return result;
        };
    }

    (function() {
        function getByLocation(location, options) {
            var result;
            if (typeof options !== 'object') {
                result = options;
            } else {
                var hostname = location.hostname || '';
                /* Fail-safe against infinite loop */
                var prevHostname;
                do {
                    result = options[hostname];
                    prevHostname = hostname;
                    hostname = hostname.replace(/[^\.]*\.|(^[^\.]+$)/, '');
                } while (!result && hostname.length > 0 && prevHostname !== hostname);
                result = result || options[''];
            }

            return result;
        }

        var defaultTimezoneFallback = 'MSK';
        var defaultTimezone;
        if (typeof Intl !== 'undefined' &&
            typeof Intl.DateTimeFormat !== 'undefined' &&
            Intl.DateTimeFormat.prototype &&
            Intl.DateTimeFormat.prototype.resolvedOptions) {
            var dateTimeFormat = new Intl.DateTimeFormat().resolvedOptions();
            defaultTimezone = dateTimeFormat && dateTimeFormat.timeZone || defaultTimezoneFallback;
        } else {
            defaultTimezone = defaultTimezoneFallback;
        }
        /**
         *  The default {@link SmsStatus} settings.
         */
        SmsStatus.prototype.settings = {
            dtf: '',

            pollTimeout: 5000,
            pollUrlBase: getByLocation(window.location, {
                '': 'https://sms.web.money/SmsStatus/Sms/',
                'webmoney.ru': 'https://sms.webmoney.ru/SmsStatus/Sms/'
            }),
            resendUrlBase: getByLocation(window.location, {
                '': 'https://sms.web.money/SmsStatus/Resend/',
                'webmoney.ru': 'https://sms.webmoney.ru/SmsStatus/Resend/'
            }),
            timezone: defaultTimezone,

            setButtonHandlers: true,
            untilResendableCountdown: true,

            loadingIndicator: '#sms-loading-indicator',
            loadingEvents: {
                'poll': {
                    showDelay: 1000
                },
                '': {
                    showIndicator: true,
                    minVisibleTime: 1000,
                    showDelay: 0
                }
            },

            errorBlock: {
                root: '#sms-error',
                reportError: '#sms-error-report'
            },

            reportBlock: {
                root: '#sms-report',
                created: '#sms-report-created',
                updated: '#sms-report-updated',
                retries: '#sms-report-retries',
                validity: '#sms-report-validity',
                validUntil: '#sms-report-valid-until',
                lastTransportType: '#sms-report-last-transport-type',
                status: '#sms-report-status',
                refresh: '#sms-report-refresh',
                instruction: '#sms-report-instruction',
                instructionText: '#sms-report-instruction-text',
                supportLinks: '#sms-report-support-links',
                untilResendable: '#sms-report-until-resendable',
                untilResendableText: '#sms-report-until-resendable-text',
                untilResendableVersion: 2,

                sendingsList: {
                    root: '#sms-report-attempts',
                    itemTemplate: '#sms-report-attempt-template',
                    doNotClear: '.sms-report-attempts-do-not-clear',
                    attemptNumber: '#sms-report-attempt-number',
                    created: '#sms-report-attempt-created',
                    updated: '#sms-report-attempt-updated',
                    status: '#sms-report-attempt-status',
                    transportType: '#sms-report-attempt-transport-type',
                    displayMinCount: 2
                }
            },
            resendBlock: {
                root: '#sms-resend',
                button: '#sms-resend-button',
                status: '#sms-resend-status'
            }
        };
    })();

    /**
     *  A global SmsStatus instance array so that SmsStatus instances can be accessed from the
     *  template-generated markup.
     */
    SmsStatus.instances = [];

    /**
     * The function called when a server response is received.
     * @param {*} response The response object received from the server.
     * @param {function} handler The handler for the valid responses.
     * @private
     */
    SmsStatus.prototype._callback = function(response, handler) {
        this._onLoadingEvent(false, response && response.UserData);

        if (typeof response !== 'object') {
            this.stopPolling();
        } else if (response && response.SecureId === this._id) {
            this._untilResendableCountdownHandler.callback(this);
            handler.call(this, response);
        }
    };

    /**
     *  The {@link toggleLoadingIndicator} loadingEventId parameter value when SMS status is first
     *  loaded. Not that this may occur multiple times e.g. if SMS status fails to load.
     */
    SmsStatus.prototype.EVENT_ID_LOAD = 'load';

    /**
     *  The {@link toggleLoadingIndicator} loadingEventId parameter value when user initiates an update
     *  of SMS status.
     */
    SmsStatus.prototype.EVENT_ID_POLL_NOW = 'poll-now';

    /**
     *  The {@link toggleLoadingIndicator} loadingEventId parameter value when SMS status is about to
     *  be polled automatically.
     */
    SmsStatus.prototype.EVENT_ID_POLL = 'poll';

    /**
     *  The {@link toggleLoadingIndicator} loadingEventId parameter value when SMS is about to be
     *  resent.
     */
    SmsStatus.prototype.EVENT_ID_RESEND = 'resend';

    /**
     * Reloads a script at the specified URL.
     * @param {string} url The script URL.
     * @param {boolean} post true to send a POST request, false to send a GET request.
     * @param {string} loadingEventId
     * The value to pass to the {@link toggleLoadingIndicator} loadingEventId parameter when the
     * response is received.
     * @param {function} callback The function to call when a response is received.
     * @param {object} callbackData An arbitrary data to pass to callback as the second argument.
     * @private
     */
    SmsStatus.prototype._sendRequest = function(url, post, loadingEventId, callback, callbackData) {
        this._onLoadingEvent(true, loadingEventId);

        var u = url + '&userData=' + encodeURIComponent(loadingEventId);

        var xmlHttpFactories = [
            function() { return new XMLHttpRequest(); },
            function() { return new ActiveXObject('Microsoft.XMLHTTP'); }
        ];
        var done = 4;

        function createXmlHttpObject() {
            var result = false;
            for (var i = 0; i < xmlHttpFactories.length; i++) {
                try {
                    result = xmlHttpFactories[i]();
                } catch (e) {
                    continue;
                }
                break;
            }
            return result;
        }

        var req = createXmlHttpObject();
        if (!req) {
            throw new Error();
        }

        var method = post ? 'POST' : 'GET';
        req.open(method, u, true);
        req.setRequestHeader('X-Requested-With', SmsStatus.prototype._clientName);
        if (post) {
            req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        }
        req.onreadystatechange = function() {
            if (req.readyState !== done) {
                return;
            }
            if (req.status !== 200 && req.status !== 304) {
                return;
            }
            var response = JSON.parse(req.response);
            callback(response, callbackData);
        };
        if (req.readyState === done) return;
        req.send(null);
    };

    /**
     * Reloads the script providing up-to-data message status.
     * @param {boolean} isUserInitiated
     * true if the refresh was initiated by the user interaction, false if the refresh was initiated by
     * the periodic poll.
     * @private
     */
    SmsStatus.prototype._sendPollRequest = function(isUserInitiated) {
        var pollUrl = this._pollUrl;

        var loadingEventId = isUserInitiated ? this.EVENT_ID_POLL_NOW
                           : this.lastStatusResponse === null ? this.EVENT_ID_LOAD
                           : this.EVENT_ID_POLL;
        this._sendRequest(pollUrl, false, loadingEventId, function(r, t) {
            t._callback(r, t._handleStatus);
        }, this);
    };

    /**
     * Reloads the script resending the message.
     * @private
     */
    SmsStatus.prototype._sendResendRequest = function() {
        var resendUrl = this._resendUrl;

        var loadingEventId = this.EVENT_ID_RESEND;
        this._sendRequest(resendUrl, true, loadingEventId, function(r, t) {
            t._callback(r, t._handleResend);
        }, this);
    };

    /**
     * The function called when a server returns an up-to-date message status.
     * @param {*} response The response object received from the server.
     * @private
     */
    SmsStatus.prototype._handleStatus = function(response) {
        var e = { 'sender': this, 'response': response };
        var hasReport = typeof response.Report === 'object' && response.Report;

        this.lastStatusResponse = response;

        if (!hasReport) {
            if (!this.onStatusError(e)) {
                this.stopPolling();
            }
            return;
        }

        if (response.Report.IsFinalState) {
            this.stopPolling();
        }

        this._untilResendableCountdownHandler.handleStatus(this);

        if (typeof this.onStatus === 'function') {
            this.onStatus(e);
        }
    };

    /**
     * The function called when a server returns response after resending a message.
     * @param {*} response The response object received from the server.
     * @private
     */
    SmsStatus.prototype._handleResend = function(response) {
        this.lastResendResponse = response;

        if (typeof this.onResent === 'function') {
            var e = { 'sender': this, 'response': response };
            this.onResent(e);
        }
    };

    /**@private*/
    SmsStatus.prototype._onPoll = function() {
        this._sendPollRequest(false);

        this.pollId = setTimeout(function(target) {
            target._onPoll();
        }, this.settings.pollTimeout, this);
    };

    /**
     *  Starts polling SMS status.
     *  Stops any previously started polling.
     */
    SmsStatus.prototype.startPolling = function() {
        this.stopPolling();
        this.pollId = setTimeout(function(sender) { sender._onPoll(); }, 1, this);
    };

    /**
     *  Stops previously started SMS status polling.
     */
    SmsStatus.prototype.stopPolling = function() {
        if (this.pollId !== undefined) {
            clearTimeout(this.pollId);
            this.pollId = undefined;
        }
    };

    /**
     *  Immediately updates SMS status.
     */
    SmsStatus.prototype.pollNow = function() {
        setTimeout(function(target) {
            target._sendPollRequest(true);
        }, 1, this);
    };

    /**
     *  Requests SMS Status service to resend the SMS.
     */
    SmsStatus.prototype.resend = function() {
        this.stopPolling();
        setTimeout(function(target) {
            target._sendResendRequest();
        }, 1, this);
    };

    /**
     *  Checks if SMS sending attempts should be displayed.
     *
     *  @param {Object} e The event arguments.
     *  @param {SmsStatus} e.sender Current SMS status poller object.
     *  @param {SmsStatus~SmsReportQueryResult} e.response The SMS status service response.
     *
     *  @return {boolean} true if sendings should be displayed; otherwise, false.
     */
    SmsStatus.prototype.displaySendings = function(e) {
        var sender = e.sender;
        var response = e.response;
        var report = response.Report;

        var sendingsList = sender.settings.reportBlock.sendingsList;
        var displayMinCount = sendingsList.displayMinCount;

        return report.SendingAtempts && displayMinCount >= 0 && report.SendingAtempts.length >= displayMinCount;
    };

    /**
     *  The default function processing sending attempt reports.
     *
     *  @param {Object} e The event arguments.
     *  @param {SmsStatus} e.sender Current SMS status poller object.
     *  @param {SmsStatus~SmsReportQueryResult} e.response The SMS status service response.
     *
     *  @return {undefined}
     */
    SmsStatus.prototype.sendingsUpdate = function(e) {
        var sender = e.sender;

        sender.sendingsClear();
        if (sender.displaySendings(e)) {
            var response = e.response;
            var report = response.Report;
            var sendingsList = sender.settings.reportBlock.sendingsList;

            $(sendingsList.root).show();
            var sendingAttempts = report.SendingAtempts;
            for (var i in sendingAttempts) {
                if (sendingAttempts.hasOwnProperty(i)) {
                    var attempt = sendingAttempts[i];
                    sender.sendingsAddItem(attempt);
                }
            }
        }
    };

    /**
     *  The default function adding a sending attempt report. Uses the template.
     *
     *  @param {SmsStatus~SmsSendingReport} sending The sending attempt data to add.
     *
     *  @return {undefined}
     */
    SmsStatus.prototype.sendingsAddItem = function(sending) {
        var sendingsList = this.settings.reportBlock.sendingsList;

        var template = $(sendingsList.itemTemplate);
        var root = $(sendingsList.root);

        if (root && template) {
            var newItem = template.clone();
            newItem.removeAttr('id');

            var attemptNumber = newItem.find(sendingsList.attemptNumber);
            attemptNumber.text(sending.AttemptNumber);
            attemptNumber.removeAttr('id');

            var created = newItem.find(sendingsList.created);
            created.text(sending.CreatedStr);
            created.removeAttr('id');

            var updated = newItem.find(sendingsList.updated);
            updated.text(sending.UpdatedStr);
            updated.removeAttr('id');

            var status = newItem.find(sendingsList.status);
            status.text(sending.Status);
            status.removeAttr('id');

            var transportType = newItem.find(sendingsList.transportType);
            transportType.text(sending.TransportTypeStr);
            transportType.removeAttr('id');

            root.append(newItem);
            newItem.show();
        }
    };

    /**
     *  The default function clearing all elements displaying sending attempt reports.
     *  Clears all children of the element matching {@link settings.reportBlock.sendingsList.root} except any
     *  elements matching {@link settings.reportBlock.sendingsList.itemTemplate} and
     *  {@link settings.reportBlock.sendingsList.doNotClear}.
     */
    SmsStatus.prototype.sendingsClear = function() {
        $(this.settings.reportBlock.sendingsList.root)
            .children()
            .not(this.settings.reportBlock.sendingsList.itemTemplate)
            .not(this.settings.reportBlock.sendingsList.doNotClear)
            .remove();
    };

    /**
     *  Handles SMS resend attempt result.
     *
     *  @param {Object} e The event arguments.
     *  @param {SmsStatus} e.sender Current SMS status poller object.
     *  @param {SmsStatus~SmsResendCommandResult} e.response The SMS status report.
     *
     *  @return {undefined}
     */
    SmsStatus.prototype.onResent = function(e) {
        var sender = e.sender;
        var response = e.response;

        var resendBlock = sender.settings.resendBlock;

        $(resendBlock.root).show();
        $(resendBlock.button).hide();
        $(resendBlock.status).text(response.Status);
        $(resendBlock.status).show();

        var hasReport = typeof response.Report === 'object' && response.Report;
        sender.startPolling();

        if (hasReport && typeof sender.onStatus === 'function') {
            sender.onStatus(e);
        }
    };

    /**
     *  Toggles loading indicator visibility.
     *
     *  @param {Boolean} show
     *  The flag indicating whether the indicator must be displayed or hidden. true if the indicator
     *  must be displayed, false if the indicator must be hidden.
     *
     *  @param {String} loadingEventId
     *  The identifier of the event being loaded.
     *
     *  @return {undefined}
     *
     *  @see {@link EVENT_ID_LOAD}
     *  @see {@link EVENT_ID_POLL_NOW}
     *  @see {@link EVENT_ID_POLL}
     *  @see {@link EVENT_ID_RESEND}
     */
    SmsStatus.prototype.toggleLoadingIndicator = function(show, loadingEventId) {
        $(this.settings.loadingIndicator).toggle(show);
    };

    /**
     *  Toggles loading indicator visibility.
     *
     *  @param {Boolean} show
     *  The flag indicating whether the indicator must be displayed or hidden. true if the indicator
     *  must be displayed, false if the indicator must be hidden.
     *
     *  @param {String} loadingEventId
     *  The identifier of the event being loaded.
     *
     *  @return {undefined}
     *
     *  @see {@link EVENT_ID_LOAD}
     *  @see {@link EVENT_ID_POLL_NOW}
     *  @see {@link EVENT_ID_POLL}
     *  @see {@link EVENT_ID_RESEND}
     */
    SmsStatus.prototype._onLoadingEvent = function(show, loadingEventId) {
        var loadingEvents = this.settings.loadingEvents;
        var eventSettings = loadingEvents[loadingEventId];
        var defaultEventSettings = loadingEvents[''];
        var effectiveEventSettings = this._setDefaultSettings(eventSettings || {}, defaultEventSettings);

        if (effectiveEventSettings.showIndicator) {
            this._loadingState = show;

            var self = this;
            if (show) {
                var showDelay = effectiveEventSettings.showDelay;
                if (showDelay > 0) {
                    setTimeout(function() {
                        if (self._loadingState) {
                            self.toggleLoadingIndicator(true, loadingEventId);
                            self._loadingIndicatorShownSince = new Date();
                        }
                    }, showDelay);
                } else {
                    self.toggleLoadingIndicator(true, loadingEventId);
                    self._loadingIndicatorShownSince = new Date();
                }
            } else {
                var minVisibleTime = effectiveEventSettings.minVisibleTime;
                var loadingIndicatorShownSince = self._loadingIndicatorShownSince || new Date(0);
                var now = new Date();
                var indicatorShownTime = now.valueOf() - loadingIndicatorShownSince.valueOf();
                var remainingShowTime = minVisibleTime - indicatorShownTime;

                clearTimeout(self._loadingIndicatorHideTimout);
                if (remainingShowTime > 0) {
                    self._loadingIndicatorHideTimout = setTimeout(function() {
                        if (!self._loadingState) {
                            self.toggleLoadingIndicator(false, loadingEventId);
                        }
                    }, remainingShowTime);
                } else {
                    self.toggleLoadingIndicator(false, loadingEventId);
                }
            }
        }
    };

    /**
     *  Handles a new SMS status report. Override this method to customize SMS status report handling.
     *
     *  @param {Object} e The event arguments.
     *  @param {SmsStatus} e.sender Current SMS status poller object.
     *  @param {SmsStatus~SmsReportQueryResult} e.response The SMS status service response.
     *
     *  @return {undefined}
     */
    SmsStatus.prototype.onStatus = function(e) {
        var sender = e.sender;
        var response = e.response;
        var report = response.Report;

        var reportBlock = sender.settings.reportBlock;

        $(sender.settings.errorBlock.root).hide();

        $(reportBlock.root).show();
        $(reportBlock.created).text(report.CreatedStr);
        $(reportBlock.updated).text(report.StatusUpdatedStr);

        $(reportBlock.retries).text(report.SendingRetries);
        $(reportBlock.validity).text(report.ValidityPeriodStr);
        $(reportBlock.validUntil).text(report.ValidUntilStr);

        $(reportBlock.lastTransportType).text(report.LastTransportTypeStr);

        $(reportBlock.status).text(report.Status);
        $(reportBlock.status).attr('title', report.StatusDetails);

        sender.sendingsUpdate(e);

        if (report.StatusInstruction) {
            $(reportBlock.instructionText).html(report.StatusInstruction);
            $(reportBlock.instruction).show();
        } else {
            $(reportBlock.instruction).hide();
            $(reportBlock.instructionText).html('');
        }

        var resendBlock = sender.settings.resendBlock;

        if (report.Resendable) {
            $(reportBlock.supportLinks).hide();

            $(resendBlock.root).show();
            $(resendBlock.button).show();
            $(resendBlock.status).hide();
        } else {
            $(reportBlock.supportLinks).show();

            $(resendBlock.root).hide();
            $(resendBlock.button).hide();
        }

        if (report.UntilResendableSeconds && report.UntilResendableSeconds > 0) {
            var untilResendableVersion = reportBlock.untilResendableVersion;
            var untilResendableStr = untilResendableVersion === 1
                ? report.UntilResendableStr
                : report['UntilResendableStrV' + untilResendableVersion] || report.UntilResendableStrV2;
            $(reportBlock.untilResendableText).text(untilResendableStr);
            $(reportBlock.untilResendable).show();
        } else {
            $(reportBlock.untilResendable).hide();
            $(reportBlock.untilResendableText).text('');
        }
    };

    /**
     *  Handles failure to get SMS status report. Override this method to customize error handling.
     *
     *  @param {Object} e The event arguments.
     *  @param {SmsStatus} e.sender Current SMS status poller object.
     *  @param {SmsStatus~SmsReportQueryResult} e.response The SMS status service response.
     *
     *  @returns {boolean} Boolean value indicating whether status polling should continue.
     */
    SmsStatus.prototype.onStatusError = function(e) {
        var sender = e.sender;

        var errorBlock = sender.settings.errorBlock;
        var reportBlock = sender.settings.reportBlock;

        $(errorBlock.root).show();
        $(errorBlock.reportError).show();

        $(reportBlock.root).hide();

        return false;
    };

    (function() {
        function plateUnavailable() {
            throw 'plate.js template script is required to use this template.';
        }

        function simpleUnavailable() {
            throw 'simple.js template script is required to use this template.';
        }

        /**
         *  Contains markup templates for automatic report rendering.
         */
        SmsStatus.prototype.templates = {
            /**
             *  Contains translation strings passed to the template.
             *
             *  Currently supported languages: English (default), Russian.
             *  Portuguese, Spanish and Turkish translations are missing support link, error and loading
             *  indicator translation.
             *  Vietnamese translation is missing support link, error, loading indicator and sending
             *  attempts translation.
             */
            SR: {
                /**
                 *  Gets resource set for the specified culture.
                 *
                 *  @param {String} culture The resources culture.
                 *
                 *  @return {Object} The set of strings for the specified culture.
                 */
                getResourceSet: function(culture) {
                    var lang = (culture && culture.indexOf('-') >= 0)
                        ? culture.substring(0, culture.indexOf('-'))
                        : culture;

                    if (typeof (this[lang]) === 'undefined') {
                        lang = '';
                    }
                    return this[lang];
                },

            '': {
                SmsStatus_Attempts: 'Sending attempts',
                SmsStatus_Created: 'Created',
                SmsStatus_Description: 'Description of status',
                SmsStatus_Error: 'The message with the specified identifier was not found.',
                SmsStatus_Loading: 'Loading...',
                SmsStatus_Refresh: 'Refresh',
                SmsStatus_Resend: 'Resend SMS',
                SmsStatus_Sending_AttemptIn: 'was made',
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'Attempt',
                SmsStatus_Sending_Status: 'Status',
                SmsStatus_Sending_Updated: 'Response received',
                SmsStatus_Status: 'Status',
                SmsStatus_SupportLink: 'notify support',
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'If you did not receive the message you can',
                SmsStatus_Total: 'Total',
                SmsStatus_Updated: 'Status updated',
                SmsStatus_Validity: 'Lifetime'
            },

            // ReSharper disable StringLiteralTypo
            // ReSharper disable CommentTypo
            // The following strings are in languages not recognized by ReSharper.
            'ar': {
                SmsStatus_Attempts: '\u0645\u062D\u0627\u0648\u0644\u0627\u062A \u0627\u0644\u0625\u0631\u0633\u0627\u0644' /* محاولات الإرسال */,
                SmsStatus_Created: '\u0627\u0646\u0634\u0623 \u0645\u0646 \u0642\u0628\u0644' /* انشأ من قبل */,
                SmsStatus_Description: '\u0648\u0635\u0641 \u0627\u0644\u062D\u0627\u0644\u0629' /* وصف الحالة */,
                SmsStatus_Error: '\u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0630\u0627\u062A \u0627\u0644\u0645\u0639\u0631\u0641 \u0627\u0644\u0645\u062D\u062F\u062F.' /* لم يتم العثور على الرسالة ذات المعرف المحدد. */,
                SmsStatus_Loading: '\u0641\u064A \u0627\u0646\u062A\u0638\u0627\u0631 \u0627\u0644\u0631\u062F ...' /* في انتظار الرد ... */,
                SmsStatus_Refresh: '\u064A\u0646\u0639\u0634' /* ينعش */,
                SmsStatus_Resend: '\u0627\u0639\u0627\u062F\u0629 \u0627\u0644\u0631\u0633\u0627\u0644 \u0627\u0644\u0631\u0633\u0627\u0644\u0629' /* اعادة الرسال الرسالة */,
                SmsStatus_Sending_AttemptIn: '\u0645\u0646\u062C\u0632' /* منجز */,
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: '\u0645\u062D\u0627\u0648\u0644\u0629' /* محاولة */,
                SmsStatus_Sending_Status: '\u062D\u0627\u0644\u0629' /* حالة */,
                SmsStatus_Sending_Updated: '\u062A\u0645 \u0627\u0633\u062A\u0644\u0627\u0645 \u0627\u0644\u0631\u062F' /* تم استلام الرد */,
                SmsStatus_Status: '\u062D\u0627\u0644\u0629' /* حالة */,
                SmsStatus_SupportLink: '\u0627\u0644\u0627\u062A\u0635\u0627\u0644 \u0628\u0627\u0644\u062F\u0639\u0645' /* الاتصال بالدعم */,
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: '\u0630\u0627 \u0644\u0645 \u064A\u062A\u0645 \u062A\u0633\u0644\u064A\u0645 \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u060C \u0641\u064A\u0645\u0643\u0646\u0643' /* ذا لم يتم تسليم الرسالة ، فيمكنك */,
                SmsStatus_Total: '\u0627\u0644\u0645\u062C\u0645\u0648\u0639' /* المجموع */,
                SmsStatus_Updated: '\u062A\u0645 \u062A\u062D\u062F\u064A\u062B \u0627\u0644\u062D\u0627\u0644\u0629' /* تم تحديث الحالة */,
                SmsStatus_Validity: '\u0645\u062F\u0629 \u0627\u0644\u062D\u064A\u0627\u0629' /* مدة الحياة */
            },

            'es': {
                SmsStatus_Attempts: 'Intentos de envio',
                SmsStatus_Created: 'Creado',
                SmsStatus_Description: 'Descripcion del estatus',
                SmsStatus_Error: 'El mensaje con el id espicificado no fue encontrado.',
                SmsStatus_Loading: 'Cargando...',
                SmsStatus_Refresh: 'Actualizar',
                SmsStatus_Resend: 'Reenviar SMS',
                SmsStatus_Sending_AttemptIn: 'se realizo',
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'El intento',
                SmsStatus_Sending_Status: 'Estatus',
                SmsStatus_Sending_Updated: 'Respuesta recibida',
                SmsStatus_Status: 'Estatus de entrega',
                SmsStatus_SupportLink: 'notificar a soporte',
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'Si no recibes el mensaje, tu puedes',
                SmsStatus_Total: 'Total',
                SmsStatus_Updated: 'El estatus ha sido actualizado',
                SmsStatus_Validity: 'Tiempo de vida'
            },

            'fr': {
                SmsStatus_Attempts: 'Tentatives d\'envoi',
                SmsStatus_Created: 'Cr\u00E9\u00E9 par' /* Créé par */,
                SmsStatus_Description: 'Statut Description',
                SmsStatus_Error: 'Le message avec l\'ID sp\u00E9cifi\u00E9 est introuvable.' /* Le message avec l'ID spécifié est introuvable. */,
                SmsStatus_Loading: 'En attente d\'une r\u00E9ponse...' /* En attente d'une réponse... */,
                SmsStatus_Refresh: 'Rafra\u00EEchir' /* Rafraîchir */,
                SmsStatus_Resend: 'Renvoyer SMS',
                SmsStatus_Sending_AttemptIn: 'realis\u00E9' /* realisé */,
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'Tentative',
                SmsStatus_Sending_Status: 'Statut',
                SmsStatus_Sending_Updated: 'R\u00E9ponse re\u00E7ue' /* Réponse reçue */,
                SmsStatus_Status: 'Statut',
                SmsStatus_SupportLink: 'Contactez le support',
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'Si le message n\'a pas \u00E9t\u00E9 remis, vous pouvez' /* Si le message n'a pas été remis, vous pouvez */,
                SmsStatus_Total: 'Total',
                SmsStatus_Updated: 'Statut mis \u00E0 jour' /* Statut mis à jour */,
                SmsStatus_Validity: 'Dur\u00E9e de vie' /* Durée de vie */
            },

            'pt': {
                SmsStatus_Attempts: 'Enviando tentativas',
                SmsStatus_Created: 'Criado',
                SmsStatus_Description: 'Descricao do status',
                SmsStatus_Error: 'A mensagem com o ID especificado n\u00E3o foi encontrado.' /* A mensagem com o ID especificado não foi encontrado. */,
                SmsStatus_Loading: 'Carregando...',
                SmsStatus_Refresh: 'Atualizar',
                SmsStatus_Resend: 'Reenviar SMS',
                SmsStatus_Sending_AttemptIn: 'foi feita',
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'Tentativa',
                SmsStatus_Sending_Status: 'Status',
                SmsStatus_Sending_Updated: 'Resposta recebida',
                SmsStatus_Status: 'Status',
                SmsStatus_SupportLink: 'notificar suporte',
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'Se voc\u00EA n\u00E3o receber a mensagem, voc\u00EA pode' /* Se você não receber a mensagem, você pode */,
                SmsStatus_Total: 'Total',
                SmsStatus_Updated: 'Status atualizado',
                SmsStatus_Validity: 'Tempo de vida'
            },

            'ro': {
                SmsStatus_Attempts: 'Incercari de trimitere',
                SmsStatus_Created: 'Creata',
                SmsStatus_Description: 'Descriere statut',
                SmsStatus_Error: 'Mesaj cu identificatorul dat nu a fost gasit',
                SmsStatus_Loading: 'Se asteapta raspuns\u2026' /* Se asteapta raspuns… */,
                SmsStatus_Refresh: 'Actualizeaza',
                SmsStatus_Resend: 'Retrimite SMS',
                SmsStatus_Sending_AttemptIn: 'Efectuat',
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'Incercare',
                SmsStatus_Sending_Status: 'Statut',
                SmsStatus_Sending_Updated: 'Raspuns primit',
                SmsStatus_Status: 'Statut',
                SmsStatus_SupportLink: 'Suport',
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'Daca mesajul nu a fost expediat, Dvs. puteti',
                SmsStatus_Total: 'Total',
                SmsStatus_Updated: 'Statu reinnoit',
                SmsStatus_Validity: 'Timpul vietii'
            },
            // ReSharper restore CommentTypo
            // ReSharper restore StringLiteralTypo

            'ru': {
                SmsStatus_Attempts: '\u041F\u043E\u043F\u044B\u0442\u043A\u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0438' /* Попытки отправки */,
                SmsStatus_Created: '\u0421\u043E\u0437\u0434\u0430\u043D\u0430' /* Создана */,
                SmsStatus_Description: '\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430' /* Описание статуса */,
                SmsStatus_Error: '\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0441 \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u043C \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.' /* Сообщение с указанным идентификатором не найдено. */,
                SmsStatus_Loading: '\u041E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F \u043E\u0442\u0432\u0435\u0442...' /* Ожидается ответ... */,
                SmsStatus_Refresh: '\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C' /* Обновить */,
                SmsStatus_Resend: '\u041F\u0435\u0440\u0435\u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C SMS' /* Переотправить SMS */,
                SmsStatus_Sending_AttemptIn: '\u0441\u0434\u0435\u043B\u0430\u043D\u0430' /* сделана */,
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: '\u041F\u043E\u043F\u044B\u0442\u043A\u0430' /* Попытка */,
                SmsStatus_Sending_Status: '\u0421\u0442\u0430\u0442\u0443\u0441' /* Статус */,
                SmsStatus_Sending_Updated: '\u041E\u0442\u0432\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0435\u043D' /* Ответ получен */,
                SmsStatus_Status: '\u0421\u0442\u0430\u0442\u0443\u0441' /* Статус */,
                SmsStatus_SupportLink: '\u043E\u0431\u0440\u0430\u0442\u0438\u0442\u044C\u0441\u044F \u0432 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443' /* обратиться в поддержку */,
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: '\u0415\u0441\u043B\u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0431\u044B\u043B\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E, \u0432\u044B \u043C\u043E\u0436\u0435\u0442\u0435' /* Если сообщение не было доставлено, вы можете */,
                SmsStatus_Total: '\u0412\u0441\u0435\u0433\u043E' /* Всего */,
                SmsStatus_Updated: '\u0421\u0442\u0430\u0442\u0443\u0441 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D' /* Статус обновлён */,
                SmsStatus_Validity: '\u0412\u0440\u0435\u043C\u044F \u0436\u0438\u0437\u043D\u0438' /* Время жизни */
            },

            // ReSharper disable StringLiteralTypo
            // ReSharper disable CommentTypo
            // The following strings are in languages not recognized by ReSharper.
            'tr': {
                SmsStatus_Attempts: 'Gonderme girisimleri',
                SmsStatus_Created: 'Olusturuldu',
                SmsStatus_Description: 'Durum tan\u0131m\u0131' /* Durum tanımı */,
                SmsStatus_Error: 'Belirlenen tan\u0131mlay\u0131c\u0131 ile birmesaj bulunamad\u0131.' /* Belirlenen tanımlayıcı ile birmesaj bulunamadı. */,
                SmsStatus_Loading: 'Y\u00FCkleniyor...' /* Yükleniyor... */,
                SmsStatus_Refresh: 'Yenile',
                SmsStatus_Resend: 'SMS\'i tekrar g\u00F6nder' /* SMS'i tekrar gönder */,
                SmsStatus_Sending_AttemptIn: 'ger\u00E7ekle\u015Ftirildi' /* gerçekleştirildi */,
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'Deneme',
                SmsStatus_Sending_Status: 'Durum',
                SmsStatus_Sending_Updated: 'Cevap al\u0131nd\u0131' /* Cevap alındı */,
                SmsStatus_Status: 'Durum',
                SmsStatus_SupportLink: 'destek birimine haber verin',
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'E\u011Fer mesaj taraf\u0131n\u0131za gelmediyse' /* Eğer mesaj tarafınıza gelmediyse */,
                SmsStatus_Total: 'Toplam',
                SmsStatus_Updated: 'Durum g\u00FCncellendi' /* Durum güncellendi */,
                SmsStatus_Validity: 'Ge\u00E7erlilik s\u00FCresi' /* Geçerlilik süresi */
            },

            'vi': {
                SmsStatus_Attempts: '\u0110ang g\u1EEDi' /* Đang gửi */,
                SmsStatus_Created: 'T\u1EA1o' /* Tạo */,
                SmsStatus_Description: 'M\u00F4 t\u1EA3 tr\u1EA1ng th\u00E1i' /* Mô tả trạng thái */,
                SmsStatus_Error: 'Kh\u00F4ng t\u00ECm th\u1EA5y tin nh\u1EAFn v\u1EDBi th\u00F4ng tin \u0111\u1ECBnh danh tr\u00EAn.' /* Không tìm thấy tin nhắn với thông tin định danh trên. */,
                SmsStatus_Loading: '\u0110ang \u0111\u1EE3i tr\u1EA3 l\u1EDDi' /* Đang đợi trả lời */,
                SmsStatus_Refresh: 'L\u00E0m m\u1EDBi' /* Làm mới */,
                SmsStatus_Resend: 'G\u1EEDi l\u1EA1i SMS' /* Gửi lại SMS */,
                SmsStatus_Sending_AttemptIn: '\u0110\u00E3 th\u1EF1c hi\u1EC7n' /* Đã thực hiện */,
                SmsStatus_Sending_AttemptPost: '',
                SmsStatus_Sending_AttemptPre: 'Th\u1EED l\u1EA1i' /* Thử lại */,
                SmsStatus_Sending_Status: 'Tr\u1EA1ng th\u00E1i' /* Trạng thái */,
                SmsStatus_Sending_Updated: 'Tr\u1EA3 l\u1EDDi nh\u1EADn \u0111\u01B0\u1EE3c' /* Trả lời nhận được */,
                SmsStatus_Status: 'Tr\u1EA1ng th\u00E1i g\u1EEDi' /* Trạng thái gửi */,
                SmsStatus_SupportLink: 'Li\u00EAn h\u1EC7 h\u1ED7 tr\u1EE3' /* Liên hệ hỗ trợ */,
                SmsStatus_SupportPost: '',
                SmsStatus_SupportPre: 'N\u1EBFu tin nh\u1EAFn kh\u00F4ng g\u1EEDi \u0111\u01B0\u1EE3c, b\u1EA1n c\u00F3 th\u1EC3' /* Nếu tin nhắn không gửi được, bạn có thể */,
                SmsStatus_Total: 'T\u1EA5t c\u1EA3' /* Tất cả */,
                SmsStatus_Updated: 'Tr\u1EA1ng th\u00E1i \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt' /* Trạng thái được cập nhật */,
                SmsStatus_Validity: 'Th\u1EDDi gian t\u1ED3n t\u1EA1i' /* Thời gian tồn tại */
            }
            // ReSharper restore CommentTypo
            // ReSharper restore StringLiteralTypo
            },

            plate_main: plateUnavailable,
            plate_sending: plateUnavailable,
            plate_loading: plateUnavailable,
            plate_error: plateUnavailable,

            simple_main: simpleUnavailable,
            simple_sending: simpleUnavailable,
            simple_loading: simpleUnavailable,
            simple_error: simpleUnavailable
        };

        /**
         * Returns a boolean value indicating whether "plate" well-known template is loaded.
         *
         * @return {boolean} true if "plate" well-known template is loaded; otherwise, false.
         *
         * @see SmsStatus.renderTemplate
         */
        SmsStatus.templatePlateAvailable = function() {
            return SmsStatus.prototype.templates.plate_main !== plateUnavailable &&
                SmsStatus.prototype.templates.plate_sending !== plateUnavailable &&
                SmsStatus.prototype.templates.plate_loading !== plateUnavailable &&
                SmsStatus.prototype.templates.plate_error !== plateUnavailable;
        };

        /**
         * Returns a boolean value indicating whether "simple" well-known template is loaded.
         *
         * @return {boolean} true if "simple" well-known template is loaded; otherwise, false.
         *
         * @see SmsStatus.renderTemplate
         */
        SmsStatus.templateSimpleAvailable = function() {
            return SmsStatus.prototype.templates.simple_main !== simpleUnavailable &&
                SmsStatus.prototype.templates.simple_sending !== simpleUnavailable &&
                SmsStatus.prototype.templates.simple_loading !== simpleUnavailable &&
                SmsStatus.prototype.templates.simple_error !== simpleUnavailable;
        };
    })();

    /**
     *  Renders automatically updated SMS Status display markup at the specified marker element.
     *
     *  The {@link markerElement} must have the following attributes:
     *  * data-sms-status-id: The secure identifier of the SMS to display.
     *
     *  The {@link markerElement} may have the following attributes:
     *  * data-sms-status-culture: The output culture (en, en-US, ru, ru-RU and so on).
     *  * data-sms-status-template: The template name base. Default to 'plate'.
     *    The name of the templates to use are determined by appending '_main', '_sending', '_loading'
     *    and '_error' to the base name. Template names may be defined individually using
     *    data-sms-status-template-main, data-sms-status-template-sending,
     *    data-sms-status-template-loading and data-sms-status-template-error attributes.
     *  * data-sms-status-settings-*. Defined the settings object. Settings object fields are defined
     *    by attribute name with '.' replaced with '-'.
     *
     *    @example
     *    <div data-sms-status-id="123456789012345678901234567890123456"
     *         data-sms-status-culture="en-US"
     *         data-sms-status-settings-reportBlock-sendingsList-displayMinCount="2" />
     *    <!--
     *      Will create the following SmsStatus object:
     *
     *      var id = '123456789012345678901234567890123456';
     *      var culture = 'en-US';
     *      var settings = {
     *          reportBlock: {
     *              sendingsList: {
     *                  displayMinCount: '2'
     *              }
     *          }
     *      }
     *      var status = new SmsStatus(id, culture, settings);
     *    -->
     *
     *  If {@link markerElement} is a string and jQuery is defined then jQuery will be used to find the
     *  node or nodes to replace. If jQuery is not defined then document.querySelectorAll will be used
     *  instead.
     *
     *  @method renderTemplate
     *  @memberof SmsStatus
     *  @static
     *  @global
     *
     *  @param {(HTMLElement|HTMLElement[]|NodeList|jQuery|string)} markerElement
     *  The DOM node, nodes or selector string matching nodes to replace with template markup.
     *
     *  @param {boolean} doNotStart
     *  If set to true, prevents {@link startPolling} method from being called after initialization.
     *  Setting this parameter to true allows to apply additional customization of the poller object.
     *
     *  @return {(SmsStatus|SmsStatus[])}
     *  If markerElement is a single node, the {@link SmsStatus} object polling and handling resend
     *  requests for the message specified in the marker element. If markerElement is a collection of
     *  nodes or a selector (including selectors matching a single node), an array of the
     *  {@link SmsStatus} objects.
     */
    /**
     *  Renders automatically updated SMS Status display markup at the specified marker element.
     *
     *  SMS secure identifier must be passed either via {@link options} parameter or via
     *  data-sms-status-id attribute on the {@link markerElement}.
     *
     *  The {@link markerElement} may have the following attributes:
     *  * data-sms-status-id: The secure identifier of the SMS to display.
     *  * data-sms-status-culture: The output culture (en, en-US, ru, ru-RU and so on).
     *  * data-sms-status-template: The template name base. Default to 'plate'.
     *    The name of the templates to use are determined by appending '_main', '_sending', '_loading'
     *    and '_error' to the base name. Template names may be defined individually using
     *    data-sms-status-template-main, data-sms-status-template-sending,
     *    data-sms-status-template-loading and data-sms-status-template-error attributes.
     *  * data-sms-status-settings-*. Defined the settings object. Settings object fields are defined
     *    by attribute name with '.' replaced with '-'.
     *
     *    @example
     *    <div data-sms-status-id="123456789012345678901234567890123456"
     *         data-sms-status-culture="en-US"
     *         data-sms-status-settings-reportBlock-sendingsList-displayMinCount="2" />
     *    <!--
     *      Will create the following SmsStatus object:
     *
     *      var id = '123456789012345678901234567890123456';
     *      var culture = 'en-US';
     *      var settings = {
     *          reportBlock: {
     *              sendingsList: {
     *                  displayMinCount: '2'
     *              }
     *          }
     *      }
     *      var status = new SmsStatus(id, culture, settings);
     *    -->
     *
     *  If {@link markerElement} is a string and jQuery is defined then jQuery will be used to find the
     *  node or nodes to replace. If jQuery is not defined then document.querySelectorAll will be used
     *  instead.
     *
     *  @method renderTemplate
     *  @memberof SmsStatus
     *  @static
     *  @global
     *
     *  @param {(HTMLElement|HTMLElement[]|NodeList|jQuery|string)} markerElement
     *  The DOM node, nodes or selector string matching nodes to replace with template markup.
     *
     *  @param {Object} [options={}] Optional template parameters.
     * 
     *  @param {boolean} [options.doNotStart=false]
     *  If set to true, prevents {@link startPolling} method from being called after initialization.
     *  Setting this parameter to true allows to apply additional customization of the poller object.
     *
     *  @param {String} [options.id] SMS secure identifier.
     *
     *  @param {String} [options.culture] The output culture (en, en-US, ru, ru-RU and so on).
     *
     *  @param {SmsStatus~Settings} [options.settings={}] Optional SMS poller settings.
     *
     *  @return {(SmsStatus|SmsStatus[])}
     *  If markerElement is a single node, the {@link SmsStatus} object polling and handling resend
     *  requests for the message specified in the marker element. If markerElement is a collection of
     *  nodes or a selector (including selectors matching a single node), an array of the
     *  {@link SmsStatus} objects.
     */
    SmsStatus.renderTemplate = function (markerElement, options) {
        if (SmsStatus.prototype._isArray(markerElement) || (typeof NodeList !== 'undefined' && markerElement instanceof NodeList)) {
            var result = [];
            for (var i = 0, length = markerElement.length; i < length; i++) {
                var item = SmsStatus.renderTemplate(markerElement[i], options);
                if (SmsStatus.prototype._isArray(item)) {
                    Array.prototype.push.apply(result, item);
                } else {
                    result.push(item);
                }
            }
            return result;
        }

        if (typeof jQuery !== 'undefined' && markerElement instanceof jQuery) {
            return SmsStatus.renderTemplate(markerElement.get(), options);
        }

        if (typeof markerElement === 'string') {
            var match = typeof jQuery !== 'undefined'
                ? jQuery(markerElement)
                : document.querySelectorAll(markerElement);
            return SmsStatus.renderTemplate(match, options);
        }

        // ReSharper disable DuplicatingLocalDeclaration

        /**
         *  Updates child node of the specified container element.
         *
         *  @param {HTMLElement} container
         *  The DOM element containing the nodes to be added and updated.
         *
         *  @param {(HTMLElement|HTMLElement[])} [initialNodes]
         *  The initially present nodes that will be replaced with new the new markup.
         *
         *  @private
         */
        function DomUpdater(container, initialNodes) {
            var isArray = SmsStatus.prototype._isArray;

            if (typeof initialNodes === 'undefined') {
                this._oldNodes = [];
            } else if (isArray(initialNodes)) {
                this._oldNodes = initialNodes;
            } else {
                this._oldNodes = [initialNodes];
            }

            this._container = container;
        }

        // ReSharper restore DuplicatingLocalDeclaration

        /**
         *  Replaces the nodes with the new HTML markup.
         *
         *  @param {String} newHtml - The new HTML markup.
         *
         *  @private
         */
        DomUpdater.prototype.update = function(newHtml) {
            var wrapper = document.createElement('div');
            wrapper.innerHTML = newHtml;

            var oldNodes = this._oldNodes;
            var newNodes = wrapper.childNodes;
            var newNodesInserted = [];

            var newIdx = newNodes.length - 1;
            var oldIdx = oldNodes.length - 1;
            var previousNode = null;

            for (; newIdx >= 0; --newIdx, --oldIdx) {
                var currentNode = newNodes[newIdx];

                if (oldIdx >= 0) {
                    this._container.replaceChild(currentNode, oldNodes[oldIdx]);
                } else if (previousNode) {
                    this._container.insertBefore(currentNode, previousNode);
                } else {
                    this._container.appendChild(currentNode);
                }

                previousNode = currentNode;
                newNodesInserted.push(currentNode);
            }
            for (; oldIdx >= 0; --oldIdx) {
                this._container.removeChild(oldNodes[oldIdx]);
            }

            newNodesInserted.reverse();

            this._oldNodes = newNodesInserted;
        };

        function readSettingsFromTag(sample, element, prefix) {
            var result = {};

            for (var option in sample) {
                if (sample.hasOwnProperty(option)) {
                    var sampleValue = sample[option];

                    if (typeof sampleValue === 'object') {
                        result[option] = readSettingsFromTag(sampleValue, element, prefix + option + '-');
                    } else {
                        var value = element.getAttribute(prefix + option);
                        if (value) {
                            result[option] = value;
                        }
                    }
                }
            }

            return result;
        }

        if (typeof options === "boolean") {
            options = {
                doNotStart: options
            }
        }

        options = SmsStatus.prototype._setDefaultSettings(options, {
            doNotStart: false
        });

        var smsId = markerElement.getAttribute('data-sms-status-id');
        if (smsId === null || smsId === '' || typeof smsId === 'undefined') {
            smsId = options.id;
        }
        if (smsId === null || typeof smsId === 'undefined') {
            smsId = '';
        }

        var culture = markerElement.getAttribute('data-sms-status-culture') || options.culture || '';
        var settingsFromTag = readSettingsFromTag(SmsStatus.prototype.settings, markerElement, 'data-sms-status-settings-');
        var settings = SmsStatus.prototype._setDefaultSettings(settingsFromTag, options.settings);
        settings.setButtonHandlers = false;

        var status = new SmsStatus(smsId, culture, settings);

        var domUpdater = new DomUpdater(markerElement.parentNode, markerElement);

        var templateBaseName = markerElement.getAttribute('data-sms-status-template') || 'plate';
        var templateMainName = markerElement.getAttribute('data-sms-status-template-main') || (templateBaseName + '_main');
        var templateSendingName = markerElement.getAttribute('data-sms-status-template-sending') || (templateBaseName + '_sending');
        var templateLoadingName = markerElement.getAttribute('data-sms-status-template-loading') || (templateBaseName + '_loading');
        var templateErrorName = markerElement.getAttribute('data-sms-status-template-error') || (templateBaseName + '_error');

        status.onStatus = function(e) {
            var sender = e.sender;
            var response = e.response;
            var report = response.Report;
            var loadingEventId = sender._lastLoadingEventId;

            var templateMain = sender.templates[templateMainName];
            var templateSending = sender.templates[templateSendingName];

            var it = {
                report: report,
                culture: culture,
                SR: sender.templates.SR.getResourceSet(culture),
                formatSending: templateSending,
                displaySendings: sender.displaySendings(e),
                instanceIndex: status.instanceIndex,
                loadingEventId: loadingEventId
            };

            var newHtml = templateMain(it);
            domUpdater.update(newHtml);
            //container.innerHTML = newHtml;
        };
        status.onStatusError = function(e) {
            var sender = e.sender;

            var templateError = sender.templates[templateErrorName];
            var it = {
                culture: culture,
                SR: sender.templates.SR.getResourceSet(culture)
            };

            var newHtml = templateError(it);
            domUpdater.update(newHtml);

            return false;
        };
        status.onResent = function(e) {
            var sender = e.sender;

            var lastStatusResponse = sender.lastStatusResponse;
            if (lastStatusResponse && lastStatusResponse.Report) {
                var temp = lastStatusResponse.Report.Resendable;
                lastStatusResponse.Report.Resendable = false;

                sender.onStatus({ sender: sender, response: lastStatusResponse });

                lastStatusResponse.Report.Resendable = temp;
            }

            sender.startPolling();
        };
        status.toggleLoadingIndicator = function(show, loadingEventId) {
            var showLoaderOnly = loadingEventId === this.EVENT_ID_LOAD ||
                !this.lastStatusResponse;
            if (show && showLoaderOnly) {
                var it = {
                    culture: culture,
                    SR: this.templates.SR.getResourceSet(culture)
                };
                var templateLoading = this.templates[templateLoadingName];

                var newHtml = templateLoading(it);
                domUpdater.update(newHtml);
            } else if (this.lastStatusResponse) {
                this._lastLoadingEventId = show ? loadingEventId : null;
                this.onStatus({
                    sender: this,
                    response: this.lastStatusResponse
                });
            }
        };

        if (!options.doNotStart) {
            status.startPolling();
        }

        return status;
    };

    window.SmsStatus = SmsStatus;
})();