Skip to content
Extraits de code Groupes Projets
broadcast.js 25,9 ko
Newer Older
  • Learn to ignore specific revisions
  • robocoder's avatar
    robocoder a validé
    /*!
    
     * Piwik - free/libre analytics platform
    
    robocoder's avatar
    robocoder a validé
     *
     * @link http://piwik.org
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
     */
    
    
    /**
     *  broadcast object is to help maintain a hash for link clicks and ajax calls
     *  so we can have back button and refresh button working.
    
     * @type {object}
    
    var broadcast = {
    
        /**
         * Initialisation state
         * @type {Boolean}
         */
        _isInit: false,
    
    
        /**
         * Last known hash url without popover parameter
         */
        currentHashUrl: false,
    
        /**
         * Last known popover parameter
         */
        currentPopoverParameter: false,
    
        /**
         * Callbacks for popover parameter change
         */
        popoverHandlers: [],
    
        /**
         * Force reload once
         */
        forceReload: false,
    
    BeezyT's avatar
    BeezyT a validé
    
    
        /**
         * Suppress content update on hash changing
         */
        updateHashOnly: false,
    
    
        /**
         * Initializes broadcast object
         * @return {void}
         */
    
        init: function (noLoadingMessage) {
    
    sgiehl's avatar
    sgiehl a validé
            broadcast._isInit = true;
    
            angular.element(document).injector().invoke(function (historyService) {
                historyService.init();
            });
    
            if(noLoadingMessage != true) {
                piwikHelper.showAjaxLoading();
            }
    
        },
    
        /**
         * ========== PageLoad function =================
         * This function is called when:
         * 1. after calling $.history.init();
         * 2. after calling $.history.load();  //look at broadcast.changeParameter();
         * 3. after pushing "Go Back" button of a browser
    
         * * Note: the method is manipulated in Overlay/javascripts/Piwik_Overlay.js - keep this in mind when making changes.
    
         *
         * @param {string}  hash to load page with
         * @return {void}
         */
    
        pageload: function (hash) {
            broadcast.init();
    
    
            // Unbind any previously attached resize handlers
            $(window).off('resize');
    
    
            // do not update content if it should be suppressed
            if (broadcast.updateHashOnly) {
                broadcast.updateHashOnly = false;
                return;
            }
    
    
            // hash doesn't contain the first # character.
    
            if (hash && 0 === (''+hash).indexOf('/')) {
                hash = (''+hash).substr(1);
            }
    
    
                if (/^popover=/.test(hash)) {
                    var hashParts = [
                        '',
                        hash.replace(/^popover=/, '')
                    ];
                } else {
                    var hashParts = hash.split('&popover=');
                }
    
                var hashUrl = hashParts[0];
                var popoverParam = '';
                if (hashParts.length > 1) {
                    popoverParam = hashParts[1];
                    // in case the $ was encoded (e.g. when using copy&paste on urls in some browsers)
                    popoverParam = decodeURIComponent(popoverParam);
                    // revert special encoding from broadcast.propagateNewPopoverParameter()
                    popoverParam = popoverParam.replace(/\$/g, '%');
                    popoverParam = decodeURIComponent(popoverParam);
                }
    
                var pageUrlUpdated = (popoverParam == '' ||
                    (broadcast.currentHashUrl !== false && broadcast.currentHashUrl != hashUrl));
    
                var popoverParamUpdated = (popoverParam != '' && hashUrl == broadcast.currentHashUrl);
    
                if (broadcast.currentHashUrl === false) {
                    // new page load
                    pageUrlUpdated = true;
                    popoverParamUpdated = (popoverParam != '');
                }
    
                if (pageUrlUpdated || broadcast.forceReload) {
                    Piwik_Popover.close();
    
                    if (hashUrl != broadcast.currentHashUrl || broadcast.forceReload) {
                        // restore ajax loaded state
                        broadcast.loadAjaxContent(hashUrl);
    
                        // make sure the "Widgets & Dashboard" is deleted on reload
    
                        $('.top_controls .dashboard-manager').hide();
    
                        $('#dashboardWidgetsArea').dashboard('destroy');
    
    
                        // remove unused controls
                        require('piwik/UI').UIControl.cleanupUnusedControls();
    
                    }
                }
    
                broadcast.forceReload = false;
                broadcast.currentHashUrl = hashUrl;
                broadcast.currentPopoverParameter = popoverParam;
    
                if (popoverParamUpdated && popoverParam == '') {
                    Piwik_Popover.close();
                } else if (popoverParamUpdated) {
                    var popoverParamParts = popoverParam.split(':');
                    var handlerName = popoverParamParts[0];
                    popoverParamParts.shift();
                    var param = popoverParamParts.join(':');
    
                    if (typeof broadcast.popoverHandlers[handlerName] != 'undefined' && !broadcast.isLoginPage()) {
    
                        broadcast.popoverHandlers[handlerName](param);
                    }
                }
    
            } else {
                // start page
    
                $('.pageWrap #content:not(.admin)').empty();
    
        /**
         * Returns if the current page is the login page
         * @return {boolean}
         */
        isLoginPage: function() {
            return !!$('body#loginPage').length;
        },
    
    
         * propagateAjax -- update hash values then make ajax calls.
         *    example :
    
         *       1) <a href="javascript:broadcast.propagateAjax('module=Referrers&action=getKeywords')">View keywords report</a>
    
         *       2) Main menu li also goes through this function.
         *
         * Will propagate your new value into the current hash string and make ajax calls.
         *
         * NOTE: this method will only make ajax call and replacing main content.
         *
         * @param {string} ajaxUrl  querystring with parameters to be updated
    
         * @param {boolean} [disableHistory]  the hash change won't be available in the browser history
    
         * @return {void}
         */
    
        propagateAjax: function (ajaxUrl, disableHistory) {
    
    sgiehl's avatar
    sgiehl a validé
            broadcast.init();
    
            // abort all existing ajax requests
    
            // available in global scope
    
    sgiehl's avatar
    sgiehl a validé
            var currentHashStr = broadcast.getHash();
    
            ajaxUrl = ajaxUrl.replace(/^\?|&#/, '');
    
    
            var params_vals = ajaxUrl.split("&");
    
            for (var i = 0; i < params_vals.length; i++) {
                currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
    
            // if the module is not 'Goals', we specifically unset the 'idGoal' parameter
            // this is to ensure that the URLs are clean (and that clicks on graphs work as expected - they are broken with the extra parameter)
    
    sgiehl's avatar
    sgiehl a validé
            var action = broadcast.getParamValue('action', currentHashStr);
    
            if (action != 'goalReport'
                && action != 'ecommerceReport'
                && action != 'products'
                && action != 'sales'
                && (''+ ajaxUrl).indexOf('&idGoal=') === -1) {
    
    sgiehl's avatar
    sgiehl a validé
                currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
    
            // unset idDashboard if use doesn't display a dashboard
    
    sgiehl's avatar
    sgiehl a validé
            var module = broadcast.getParamValue('module', currentHashStr);
    
    sgiehl's avatar
    sgiehl a validé
                currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
    
            if (module != 'CustomDimensions') {
                currentHashStr = broadcast.updateParamValue('idDimension=', currentHashStr);
            }
    
    
                var newLocation = window.location.href.split('#')[0] + '#?' + currentHashStr;
    
                // window.location.replace changes the current url without pushing it on the browser's history stack
                window.location.replace(newLocation);
            }
            else {
                // Let history know about this new Hash and load it.
                broadcast.forceReload = true;
    
                angular.element(document).injector().invoke(function (historyService) {
    
         * Returns the current hash with updated parameters that were provided in ajaxUrl
    
         * Parameters like idGoal and idDashboard will be automatically reset if the won't be relevant anymore
    
         * NOTE: this method does not issue any ajax call, but returns the hash instead
    
         *
         * @param {string} ajaxUrl  querystring with parameters to be updated
    
         * @return {string} current hash with updated parameters
    
        buildReportingUrl: function (ajaxUrl) {
    
    
            // available in global scope
            var currentHashStr = broadcast.getHash();
    
            ajaxUrl = ajaxUrl.replace(/^\?|&#/, '');
    
            var params_vals = ajaxUrl.split("&");
            for (var i = 0; i < params_vals.length; i++) {
                currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
            }
    
            // if the module is not 'Goals', we specifically unset the 'idGoal' parameter
            // this is to ensure that the URLs are clean (and that clicks on graphs work as expected - they are broken with the extra parameter)
            var action = broadcast.getParamValue('action', currentHashStr);
            if (action != 'goalReport' && action != 'ecommerceReport' && action != 'products' && action != 'sales') {
                currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
            }
            // unset idDashboard if use doesn't display a dashboard
            var module = broadcast.getParamValue('module', currentHashStr);
            if (module != 'Dashboard') {
                currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
            }
    
            return '#' + currentHashStr;
        },
    
    
        /**
         * propagateNewPage() -- update url value and load new page,
         * Example:
         *         1) We want to update idSite to both search query and hash then reload the page,
         *         2) update period to both search query and hash then reload page.
         *
         * ** If you'd like to make ajax call with new values then use propagateAjax ** *
         *
         * Expecting:
         *         str = "param1=newVal1&param2=newVal2";
         *
         * NOTE: This method will refresh the page with new values.
         *
         * @param {string} str  url with parameters to be updated
    
         * @param {boolean} [showAjaxLoading] whether to show the ajax loading gif or not.
    
         * @param {string} strHash additional parameters that should be updated on the hash
    
         * @return {void}
         */
    
        propagateNewPage: function (str, showAjaxLoading, strHash) {
    
            // abort all existing ajax requests
    
    
            if (typeof showAjaxLoading === 'undefined' || showAjaxLoading) {
                piwikHelper.showAjaxLoading();
    
            var params_vals = str.split("&");
    
            // available in global scope
            var currentSearchStr = window.location.search;
    
    sgiehl's avatar
    sgiehl a validé
            var currentHashStr = broadcast.getHashFromUrl();
    
            var oldUrl = currentSearchStr + currentHashStr;
    
            for (var i = 0; i < params_vals.length; i++) {
    
                // update both the current search query and hash string
    
                currentSearchStr = broadcast.updateParamValue(params_vals[i], currentSearchStr);
    
                if (currentHashStr.length != 0) {
                    currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
    
            var updatedUrl = new RegExp('&updated=([0-9]+)');
            var updatedCounter = updatedUrl.exec(currentSearchStr);
            if (!updatedCounter) {
                currentSearchStr += '&updated=1';
            } else {
                updatedCounter = 1 + parseInt(updatedCounter[1]);
                currentSearchStr = currentSearchStr.replace(new RegExp('(&updated=[0-9]+)'), '&updated=' + updatedCounter);
            }
    
    
            if (strHash && currentHashStr.length != 0) {
                var params_hash_vals = strHash.split("&");
                for (var i = 0; i < params_hash_vals.length; i++) {
                    currentHashStr = broadcast.updateParamValue(params_hash_vals[i], currentHashStr);
                }
            }
    
    
            // Now load the new page.
            var newUrl = currentSearchStr + currentHashStr;
    
            var $rootScope = piwikHelper.getAngularDependency('$rootScope');
            if ($rootScope) {
                $rootScope.$on('$locationChangeStart', function (event) {
                    if (event) {
                        event.preventDefault();
                    }
                })
            }
    
    
                window.location.reload();
            } else {
    
                window.location.href = newUrl;
            }
    
        },
    
        /*************************************************
         *
         *      Broadcast Supporter Methods:
         *
         *************************************************/
    
        /**
         * updateParamValue(newParamValue,urlStr) -- Helping propagate functions to update value to url string.
         * eg. I want to update date value to search query or hash query
         *
         * Expecting:
         *        urlStr : A Hash or search query string. e.g: module=whatever&action=index=date=yesterday
         *        newParamValue : A param value pair: e.g: date=2009-05-02
         *
         * Return module=whatever&action=index&date=2009-05-02
         *
         * @param {string} newParamValue   param to be updated
         * @param {string} urlStr          url to be updated
         * @return {string}  urlStr with updated param
         */
    
        updateParamValue: function (newParamValue, urlStr) {
    
            var p_v = newParamValue.split("=");
    
            var paramName = p_v[0];
    
            var valFromUrl = broadcast.getParamValue(paramName, urlStr);
    
            // if set 'idGoal=' then we remove the parameter from the URL automatically (rather than passing an empty value)
            var paramValue = p_v[1];
    
                newParamValue = '';
            }
    
    mattab's avatar
    mattab a validé
            var getQuotedRegex = function(str) {
                return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
            };
    
    
                // replacing current param=value to newParamValue;
    
    mattab's avatar
    mattab a validé
                valFromUrl = getQuotedRegex(valFromUrl);
    
                var regToBeReplace = new RegExp(paramName + '=' + valFromUrl, 'ig');
    
                    // if new value is empty remove leading &, as well
    
                    regToBeReplace = new RegExp('[\&]?' + paramName + '=' + valFromUrl, 'ig');
                }
    
                urlStr = urlStr.replace(regToBeReplace, newParamValue);
            } else if (newParamValue != '') {
    
                urlStr += (urlStr == '') ? newParamValue : '&' + newParamValue;
            }
    
            return urlStr;
        },
    
         * Loads a popover by adding a 'popover' query parameter to the current URL and
         * indirectly executing the popover handler.
    
         * This function should be called to open popovers that can be opened by URL alone.
         * That is, if you want users to be able to copy-paste the URL displayed when a popover
         * is open into a new browser window/tab and have the same popover open, you should
         * call this function.
    
         * In order for this function to open a popover, there must be a popover handler
         * associated with handlerName. To associate one, call broadcast.addPopoverHandler.
    
         * @param {String} handlerName The name of the popover handler.
    
         * @param {String} value The String value that should be passed to the popover
    
         */
        propagateNewPopoverParameter: function (handlerName, value) {
    
            // init broadcast if not already done (it is required to make popovers work in widgetize mode)
    
            var $location = angular.element(document).injector().get('$location');
    
            var popover = '';
    
                popover = handlerName + ':' + value;
    
    
                // between jquery.history and different browser bugs, it's impossible to ensure
                // that the parameter is en- and decoded the same number of times. in order to
                // make sure it doesn't change, we have to manipulate the url encoding a bit.
                popover = encodeURIComponent(popover);
                popover = popover.replace(/%/g, '\$');
    
            if ('' == value || 'undefined' == typeof value) {
    
                $location.search('popover', '');
    
                $location.search('popover', popover);
    
            setTimeout(function () {
                angular.element(document).injector().get('$rootScope').$apply();
            }, 1);
    
         * Adds a handler for the 'popover' query parameter.
    
         * @see broadcast#propagateNewPopoverParameter
    
         * @param {String} handlerName The handler name, eg, 'visitorProfile'. Should identify
         *                             the popover that the callback will open up.
         * @param {Function} callback This function should open the popover. It should take
         *                            one string parameter.
    
         */
        addPopoverHandler: function (handlerName, callback) {
    
            broadcast.popoverHandlers[handlerName] = callback;
    
        /**
         * Loads the given url with ajax and replaces the content
    
         * Note: the method is replaced in Overlay/javascripts/Piwik_Overlay.js - keep this in mind when making changes.
    
         *
         * @param {string} urlAjax  url to load
         * @return {Boolean}
         */
    
        loadAjaxContent: function (urlAjax) {
    
            if(broadcast.getParamValue('module', urlAjax) == 'API') {
                broadcast.lastUrlRequested = null;
                $('#content').html("Loading content from the API and displaying it within Piwik is not allowed.");
                piwikHelper.hideAjaxLoading();
                return false;
            }
    
    
            piwikHelper.hideAjaxError('loadingError');
            piwikHelper.showAjaxLoading();
    
            $("object").remove();
    
            urlAjax = urlAjax.match(/^\?/) ? urlAjax : "?" + urlAjax;
    
    sgiehl's avatar
    sgiehl a validé
            broadcast.lastUrlRequested = urlAjax;
    
    
            function sectionLoaded(content, status, request) {
                if (request) {
                    var responseHeader = request.getResponseHeader('Content-Type');
                    if (responseHeader && 0 <= responseHeader.toLowerCase().indexOf('json')) {
                        var message = 'JSON cannot be displayed for';
                        if (this.getParams && this.getParams['module']) {
                            message += ' module=' +  this.getParams['module'];
                        }
                        if (this.getParams && this.getParams['action']) {
                            message += ' action=' +  this.getParams['action'];
                        }
                        $('#content').text(message);
                        piwikHelper.hideAjaxLoading();
                        return;
                    }
                }
    
    
                // if content is whole HTML document, do not show it, otherwise recursive page load could occur
                var htmlDocType = '<!DOCTYPE';
                if (content.substring(0, htmlDocType.length) == htmlDocType) {
    
                    // if the content has an error message, display it
                    if ($(content).filter('title').text() == 'Piwik › Error') {
                        content = $(content).filter('#contentsimple');
                    } else {
                        return;
                    }
    
                }
    
                if (urlAjax == broadcast.lastUrlRequested) {
                    $('#content').html(content).show();
    
                    $(broadcast).trigger('locationChangeSuccess', {element: $('#content'), content: content});
    
                    piwikHelper.hideAjaxLoading();
    
    sgiehl's avatar
    sgiehl a validé
                    broadcast.lastUrlRequested = null;
    
    
                    piwikHelper.compileAngularComponents('#content');
    
            var ajax = new ajaxHelper();
            ajax.setUrl(urlAjax);
    
            ajax._getDefaultPostParams = function () {
                return {};
            };
    
            ajax.setErrorCallback(broadcast.customAjaxHandleError);
            ajax.setCallback(sectionLoaded);
            ajax.setFormat('html');
            ajax.send();
    
    
            return false;
        },
    
        /**
         * Method to handle ajax errors
         * @param {XMLHttpRequest} deferred
         * @param {string} status
         * @return {void}
         */
    
        customAjaxHandleError: function (deferred, status) {
    
    sgiehl's avatar
    sgiehl a validé
            broadcast.lastUrlRequested = null;
    
            // do not display error message if request was aborted
            if(status == 'abort') {
                return;
            }
    
            $('#loadingError').show();
    
        },
    
        /**
         * Return hash string if hash exists on address bar.
         * else return false;
         *
    
         * @return {string|boolean}  current hash or false if it is empty
    
    sgiehl's avatar
    sgiehl a validé
            var hashStr = broadcast.getHashFromUrl();
    
                return hashStr;
            } else {
                return false;
            }
        },
    
        /**
         * Get Hash from given url or from current location.
         * return empty string if no hash present.
         *
    
         * @param {string}  [url]  url to get hash from (defaults to current location)
    
         * @return {string} the hash part of the given url
         */
    
        getHashFromUrl: function (url) {
    
            var hashStr = "";
            // If url provided, give back the hash from url, else get hash from current address.
    
            if (url && url.match('#')) {
                hashStr = url.substring(url.indexOf("#"), url.length);
    
    mattab's avatar
    mattab a validé
                locationSplit = location.href.split('#');
                if(typeof locationSplit[1] != 'undefined') {
                    hashStr = '#' + locationSplit[1];
                }
    
            return hashStr;
        },
    
        /**
         * Get search query from given url or from current location.
         * return empty string if no search query present.
         *
         * @param {string} url
         * @return {string}  the query part of the given url
         */
    
        getSearchFromUrl: function (url) {
    
            var searchStr = "";
            // If url provided, give back the query string from url, else get query string from current address.
    
            if (url && url.match(/\?/)) {
                searchStr = url.substring(url.indexOf("?"), url.length);
    
            } else {
                searchStr = location.search;
            }
    
            return searchStr;
        },
    
    mattab's avatar
    mattab a validé
         * Extracts from a query strings, the request array
         * @param queryString
         * @returns {object}
    
    mattab's avatar
    mattab a validé
        extractKeyValuePairsFromQueryString: function (queryString) {
            var pairs = queryString.split('&');
    
            var result = {};
            for (var i = 0; i != pairs.length; ++i) {
    
    sgiehl's avatar
    sgiehl a validé
                // attn: split with regex has bugs in several browsers such as IE 8
                // so we need to split, use the first part as key and rejoin the rest
                var pair = pairs[i].split('=');
    
    mattab's avatar
    mattab a validé
                var key = pair.shift();
    
    sgiehl's avatar
    sgiehl a validé
                result[key] = pair.join('=');
    
    mattab's avatar
    mattab a validé
        /**
         * Returns all key-value pairs in query string of url.
         *
         * @param {string} url url to check. if undefined, null or empty, current url is used.
         * @return {object} key value pair describing query string parameters
         */
        getValuesFromUrl: function (url) {
            var searchString = this._removeHashFromUrl(url).split('?')[1] || '';
            return this.extractKeyValuePairsFromQueryString(searchString);
        },
    
    
        /**
         * help to get param value for any given url string with provided param name
         * if no url is provided, it will get param from current address.
         * return:
         *   Empty String if param is not found.
         *
         * @param {string} param   parameter to search for
    
         * @param {string} [url]     url to check, defaults to current location
    
         * @return {string} value of the given param within the given url
         */
    
        getValueFromUrl: function (param, url) {
    
            var searchString = this._removeHashFromUrl(url);
    
            return broadcast.getParamValue(param, searchString);
    
        },
    
        /**
         * NOTE: you should probably be using broadcast.getValueFromUrl instead!
         *
         * @param {string} param   parameter to search for
    
         * @param {string} [url]   url to check
    
         * @return {string} value of the given param within the hash part of the given url
         */
    
        getValueFromHash: function (param, url) {
    
    sgiehl's avatar
    sgiehl a validé
            var hashStr = broadcast.getHashFromUrl(url);
    
            if (hashStr.substr(0, 1) == '#') {
                hashStr = hashStr.substr(1);
            }
            hashStr = hashStr.split('#')[0];
    
    mattab's avatar
    mattab a validé
    
    
            return broadcast.getParamValue(param, hashStr);
    
        },
    
        /**
         * return value for the requested param, will return the first match.
         * out side of this class should use getValueFromHash() or getValueFromUrl() instead.
         * return:
         *   Empty String if param is not found.
         *
         * @param {string} param   parameter to search for
         * @param {string} url     url to check
         * @return {string} value of the given param within the given url
         */
    
        getParamValue: function (param, url) {
    
    mattab's avatar
    mattab a validé
            var lookFor = param + '=';
            var startStr = url.indexOf(lookFor);
    
                var endStr = url.indexOf("&", startStr);
    
                    endStr = url.length;
                }
    
                var value = url.substring(startStr + param.length + 1, endStr);
    
    mattab's avatar
    mattab a validé
                // we sanitize values to add a protection layer against XSS
                // &segment= value is not sanitized, since segments are designed to accept any user input
                if(param != 'segment') {
                    value = value.replace(/[^_%~\*\+\-\<\>!@\$\.()=,;0-9a-zA-Z]/gi, '');
                }
    
                return value;
            } else {
                return '';
    
        },
    
        /**
         * Returns the hash without the starting #
         * @return {string} hash part of the current url
         */
    
            return broadcast.getHashFromUrl().replace(/^#/, '').split('#')[0];
    
    
        /**
         * Removes the hash portion of a URL and returns the rest.
         *
         * @param {string} url
         * @return {string} url w/o hash
         */
        _removeHashFromUrl: function (url) {
    
                var urlParts = url.split('#');
                searchString = urlParts[0];
            } else {
                searchString = location.search;
            }
            return searchString;