Source: TechClicks/techclicks.js

/**
 * @class
 * @classdesc Plugin used to manage technical side of clicks : redirections, forms submit and mailto actions.
 * @name TechClicks
 * @memberof ATInternet.Tracker.Plugins
 * @type {function}
 * @param tag {object} Instance of the Tag used
 * @public
 */
ATInternet.Tracker.Plugins.TechClicks = function (tag) {

    'use strict';

    var _this = this;
    var _triggers = ['Tracker:Hit:Sent:Ok', 'Tracker:Hit:Sent:Error', 'Tracker:Hit:Sent:NoTrack'];

    // Plugin configuration.
    var _clicksAutoManagementEnabled,
        _clicksAutoManagementTimeout;

    var _redirectionInProgress = false;

    tag.configPlugin('TechClicks', dfltPluginCfg || {}, function (newConf) {
        _clicksAutoManagementEnabled = newConf['clicksAutoManagementEnabled'];
        _clicksAutoManagementTimeout = newConf['clicksAutoManagementTimeout'];
    });

    //*************************************************************************************************
    // PART FOR MANAGING REDIRECTION/SUBMIT/MAILTO ACTIONS AND LISTENER
    //*************************************************************************************************
    /**
     * Do a redirection with a given url & target.
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param contextObj {object} Redirection's url and target (properties url & target)
     * @private
     */
    var _doRedirection = function (contextObj) {
        if (!_redirectionInProgress) {
            _redirectionInProgress = true;
            switch (contextObj.target) {
                case '_top':
                    window.top.location.href = contextObj.url;
                    break;
                case '_parent':
                    window.parent.location.href = contextObj.url;
                    break;
                default:
                    window.location.href = contextObj.url;
                    break;
            }
        }
    };

    /**
     * Do a setTimeout with the action concerned with the current process (redirection/submit/mailto).
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param contextObject {object} Context object containing data for the case it must be done (redirection/submit/mailto) Properties timeout, mailto, form, url, target
     * @private
     */
    var _addTimeout = function (contextObject) {
        if (contextObject.mailto) {
            _this.timeout = setTimeout(function () {
                window.location.href = contextObject.mailto;
            }, contextObject.timeout);
        } else if (contextObject.form) {
            _this.timeout = setTimeout(function () {
                contextObject.form.submit();
            }, contextObject.timeout);
        } else if (contextObject.url) {
            _this.timeout = setTimeout(function () {
                _doRedirection({url: contextObject.url, target: contextObject.target});
            }, contextObject.timeout);
        }
    };

    /**
     * Allow to know if this is an action case (nothing should happen).
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param DOMElement {object} The clicked element
     * @return {boolean}
     * @private
     */
    var _isAction = function (DOMElement) {
        // Check 3 attributes ('href', 'target', 'atclickmanagement') :
        // First we check the 'target' and 'data-atclickmanagement' because they have priority over the 'href'
        var element = DOMElement;
        while (element) {
            if (typeof element.getAttribute === 'function') {
                if (element.getAttribute('target') === '_blank') {
                    return true;
                }
                if (element.getAttribute('data-atclickmanagement') === 'no') {
                    return true;
                }
            }
            element = element.parentNode;
        }
        // Then we check 'href'
        element = DOMElement;
        var currentUrl = window.location.href,
            newUrl;
        while (element) {
            // do not use getAttribute here, it gets the value exactly as it is and not the URL complete (so you will get '#anchor' instead of 'http....#anchor'
            newUrl = element.href;
            if (newUrl &&
                newUrl.indexOf('#') >= 0 &&
                currentUrl.substring(0, (currentUrl.indexOf('#') >= 0 ? currentUrl.indexOf('#') : currentUrl.length)) ===
                newUrl.substring(0, newUrl.indexOf('#'))) {
                // same base URL until anchor (#), if no anchor in the new url, it will do a navigation
                return true;
            }
            element = element.parentNode;
        }
        return false;
    };

    /**
     * Execute callback when a hit event is triggered
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param callback {function} Callback to execute
     * @private
     */
    var _reactToTriggers = function (callback) {
        for (var i = 0; i < _triggers.length; i++) {
            tag.onTrigger(_triggers[i], function (_, data) {
                callback && callback(data);
            });
        }
    };

    /**
     * Get redirection data and wait for the hit to be sent to trigger the redirection (a timeout will trigger it if it's too long).
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param DOMElement {object} The clicked element
     * @private
     */
    var _manageRedirection = function (DOMElement) {
        var url, target = '_self';
        var element = DOMElement;
        while (element) {
            if (element.href && (element.href.indexOf('http') === 0)) {
                url = element.href.split('"').join('\\"');
                target = (element.target ? element.target : target);
                break;
            }
            element = element.parentNode;
        }
        if (url) {
            var callback = function (data) {
                // If isMultiHit then we wait for the end of the 500ms delay.
                if (!data.details.isMultiHit && data.details.elementType === ATInternet.Utils.CLICKS_REDIRECTION) {
                    _this.timeout && clearTimeout(_this.timeout);
                    _doRedirection({url: url, target: target});
                }
            };
            _reactToTriggers(callback);
            _addTimeout({url: url, target: target, timeout: _clicksAutoManagementTimeout});
        }
    };

    /**
     * Get form data and wait for the hit to be sent to trigger the submit (a timeout will trigger it if it's too long).
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param DOMElement {object} The clicked element
     * @private
     */
    var _manageFormSubmit = function (DOMElement) {
        var element = DOMElement;
        while (element) {
            if (element.nodeName === 'FORM') {
                break;
            }
            element = element.parentNode;
        }
        if (element) {
            var callback = function (data) {
                // If isMultiHit then we wait for the end of the 500ms delay.
                if (!data.details.isMultiHit && data.details.elementType === ATInternet.Utils.CLICKS_FORM) {
                    _this.timeout && clearTimeout(_this.timeout);
                    element.submit();
                }
            };
            _reactToTriggers(callback);
            _addTimeout({form: element, timeout: _clicksAutoManagementTimeout});
        }
    };

    /**
     * Get mailto data and wait for the hit to be sent to trigger the mailto (a timeout will trigger it if it's too long).
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @private
     * @param DOMElement {object} The clicked element
     */
    var _manageMailto = function (DOMElement) {
        var element = DOMElement;
        while (element) {
            if (element.href && element.href.indexOf('mailto:') >= 0) {
                break;
            }
            element = element.parentNode;
        }
        if (element) {
            var callback = function (data) {
                // If isMultiHit then we wait for the end of the 500ms delay.
                if (!data.details.isMultiHit && data.details.elementType === ATInternet.Utils.CLICKS_MAILTO) {
                    _this.timeout && clearTimeout(_this.timeout);
                    window.location.href = element.href;
                }
            };
            _reactToTriggers(callback);
            _addTimeout({mailto: element.href, timeout: _clicksAutoManagementTimeout});
        }
    };

    /**
     * Check if the DOM element should be submitted
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param DOMElement {object} The clicked element
     * @return {boolean}
     * @private
     */
    var _toSubmit = function (DOMElement) {
        var toSubmit = false;
        if (DOMElement) {
            var tagName = DOMElement.tagName || '';
            tagName = tagName.toLowerCase();
            if (tagName === 'form') {
                toSubmit = true;
            } else {
                var tagType = DOMElement.getAttribute('type') || '';
                tagType = tagType.toLowerCase();
                if (tagName === 'button') {
                    // submit: The button submits the form data to the server.
                    // This is the default if the attribute is not specified,
                    // or if the attribute is dynamically changed to an empty or invalid value.
                    if ((tagType !== 'reset') && (tagType !== 'button')) {
                        toSubmit = true;
                    }
                } else if (tagName === 'input') {
                    // If this attribute is not specified, the default type adopted is text
                    if (tagType === 'submit') {
                        toSubmit = true;
                    }
                }
            }
        }
        return toSubmit;
    };

    /**
     * Return the case we should consider ('mailto', 'form' or 'redirection'). Return false if we don't find the minimum requirement
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param DOMElement {object} The clicked element
     * @return {string|false}
     * @private
     */
    var _getCurrentCase = function (DOMElement) {
        var element = DOMElement;
        while (element) {
            if (element.href) {
                if (element.href.indexOf('mailto:') >= 0) {
                    return ATInternet.Utils.CLICKS_MAILTO;
                } else if (element.href.indexOf('http') === 0) {
                    return ATInternet.Utils.CLICKS_REDIRECTION;
                }
            } else if (element.nodeName === 'FORM') {
                if (_toSubmit(DOMElement)) {
                    return ATInternet.Utils.CLICKS_FORM;
                } else {
                    return '';
                }
            }
            element = element.parentNode;
        }
        return '';
    };

    /**
     * Check if it is a form submission action.
     * @name isFormSubmit
     * @memberof ATInternet.Tracker.Tag
     * @type {boolean}
     * @param DOMElement {object} DOM element clicked
     * @public
     */
    _this.isFormSubmit = function (DOMElement) {
        var element = DOMElement;
        while (element) {
            if (element.nodeName === 'FORM') {
                return true;
            }
            element = element.parentNode;
        }
        return false;
    };

    /**
     * [Object added by plugin {@link ATInternet.Tracker.Plugins.techClicks Clicks}] Tags for clicks.
     * @name techClicks
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @type {object}
     * @property {function} manageClick Method for managing redirection after a click, see details here {@link ATInternet.Tracker.Tag#techClicks.manageClick}
     * @property {function} deactivateAutoManagement Method for managing redirection after a click, see details here {@link ATInternet.Tracker.Tag#techClicks.deactivateAutoManagement}
     * @public
     */
    tag.techClicks = {};

    /**
     * Take a DOM element (possibly with an event), check if this is a case that should be managed, return false if this is the case
     * (or preventdefault for the event) and call the manager corresponding to the current case (redirection/mailto/submit).
     * Return true if nothing will be done.
     * @name manageClick
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @param DOMElement {object} The clicked element
     * @param eventObj {object} The event object provided when triggered
     * @return {object}
     * @public
     */
    tag.techClicks.manageClick = _this.manageClick = function (DOMElement, eventObj) {
        var preservePropagation = true;
        var elementType = '';
        if (_clicksAutoManagementEnabled && DOMElement) { // process activated and element provided
            var action = _isAction(DOMElement);
            elementType = _getCurrentCase(DOMElement);
            if (!action && elementType) { // priority is to know if this is an action, if not, we check mailto then submit then redirection
                switch (elementType) {
                    case ATInternet.Utils.CLICKS_MAILTO:
                        _manageMailto(DOMElement);
                        preservePropagation = false;
                        break;
                    case ATInternet.Utils.CLICKS_FORM:
                        _manageFormSubmit(DOMElement);
                        preservePropagation = false;
                        break;
                    case ATInternet.Utils.CLICKS_REDIRECTION:
                        _manageRedirection(DOMElement);
                        preservePropagation = false;
                        break;
                    default:
                        // We couldn't find what we needed to manage a case
                        break;
                }
                // Code to deactivate the event (click/submit)
                if (eventObj) {
                    var defaultPrevented = eventObj.defaultPrevented;
                    if (typeof eventObj.isDefaultPrevented === 'function') {
                        defaultPrevented = eventObj.isDefaultPrevented();
                    }
                    if (!defaultPrevented) {
                        eventObj.preventDefault && eventObj.preventDefault();
                    }
                }
            }
        }
        return {"preservePropagation": preservePropagation, "elementType": elementType};
    };

    /**
     * Deactivate the automatic management (redirection/submit/mailto) for clicks on the current page.
     * @name deactivateAutoManagement
     * @memberof ATInternet.Tracker.Plugins.TechClicks#
     * @function
     * @public
     */
    tag.techClicks.deactivateAutoManagement = function () {
        _clicksAutoManagementEnabled = false;
    };

    // For unit tests on private elements !!!
    /* @if test */
    _this._doRedirection = _doRedirection;
    _this._addTimeout = _addTimeout;
    _this._isAction = _isAction;
    _this._reactToTriggers = _reactToTriggers;
    _this._manageRedirection = _manageRedirection;
    _this._manageFormSubmit = _manageFormSubmit;
    _this._manageMailto = _manageMailto;
    _this._toSubmit = _toSubmit;
    _this._getCurrentCase = _getCurrentCase;
    _this._clicksAutoManagementEnabled = _clicksAutoManagementEnabled;
    _this._clicksAutoManagementTimeout = _clicksAutoManagementTimeout;
    _this.getThemAll = function () {
        _this._clicksAutoManagementEnabled = _clicksAutoManagementEnabled;
        _this._clicksAutoManagementTimeout = _clicksAutoManagementTimeout;
    };
    _this.setInternalVar = function (name, val) {
        if (name === '_clicksAutoManagementEnabled') _clicksAutoManagementEnabled = val;
        if (name === '_clicksAutoManagementTimeout') _clicksAutoManagementTimeout = val;
    };
    /* @endif */
};
ATInternet.Tracker.addPlugin('TechClicks');