add icons for Character groups
This commit is contained in:
commit
2d9a41a5fe
3461 changed files with 594457 additions and 0 deletions
644
www/analytics/plugins/CoreHome/javascripts/broadcast.js
Normal file
644
www/analytics/plugins/CoreHome/javascripts/broadcast.js
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @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,
|
||||
|
||||
/**
|
||||
* Suppress content update on hash changing
|
||||
*/
|
||||
updateHashOnly: false,
|
||||
|
||||
/**
|
||||
* Initializes broadcast object
|
||||
* @return {void}
|
||||
*/
|
||||
init: function (noLoadingMessage) {
|
||||
if (broadcast._isInit) {
|
||||
return;
|
||||
}
|
||||
broadcast._isInit = true;
|
||||
|
||||
// Initialize history plugin.
|
||||
// The callback is called at once by present location.hash
|
||||
$.history.init(broadcast.pageload, {unescape: true});
|
||||
|
||||
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 (hash) {
|
||||
|
||||
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.popoverHandlers[handlerName](param);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// start page
|
||||
Piwik_Popover.close();
|
||||
|
||||
$('.pageWrap #content:not(.admin)').empty();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
broadcast.init();
|
||||
|
||||
// abort all existing ajax requests
|
||||
globalAjaxQueue.abort();
|
||||
|
||||
// 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') {
|
||||
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);
|
||||
}
|
||||
|
||||
if (disableHistory) {
|
||||
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;
|
||||
$.history.load(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¶m2=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.
|
||||
* @return {void}
|
||||
*/
|
||||
propagateNewPage: function (str, showAjaxLoading) {
|
||||
// abort all existing ajax requests
|
||||
globalAjaxQueue.abort();
|
||||
|
||||
if (typeof showAjaxLoading === 'undefined' || showAjaxLoading) {
|
||||
piwikHelper.showAjaxLoading();
|
||||
}
|
||||
|
||||
var params_vals = str.split("&");
|
||||
|
||||
// available in global scope
|
||||
var currentSearchStr = window.location.search;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Now load the new page.
|
||||
var newUrl = currentSearchStr + currentHashStr;
|
||||
|
||||
if (oldUrl == newUrl) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
this.forceReload = true;
|
||||
window.location.href = newUrl;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/*************************************************
|
||||
*
|
||||
* 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];
|
||||
if (paramValue == '') {
|
||||
newParamValue = '';
|
||||
}
|
||||
var getQuotedRegex = function(str) {
|
||||
return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
|
||||
};
|
||||
|
||||
if (valFromUrl != '') {
|
||||
// replacing current param=value to newParamValue;
|
||||
valFromUrl = getQuotedRegex(valFromUrl);
|
||||
var regToBeReplace = new RegExp(paramName + '=' + valFromUrl, 'ig');
|
||||
if (newParamValue == '') {
|
||||
// if new value is empty remove leading &, aswell
|
||||
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
|
||||
* handler.
|
||||
*/
|
||||
propagateNewPopoverParameter: function (handlerName, value) {
|
||||
// init broadcast if not already done (it is required to make popovers work in widgetize mode)
|
||||
broadcast.init(true);
|
||||
|
||||
var hash = broadcast.getHashFromUrl(window.location.href);
|
||||
|
||||
var popover = '';
|
||||
if (handlerName) {
|
||||
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) {
|
||||
var newHash = hash.replace(/(&?popover=.*)/, '');
|
||||
} else if (broadcast.getParamValue('popover', hash)) {
|
||||
var newHash = broadcast.updateParamValue('popover='+popover, hash);
|
||||
} else if (hash && hash != '#') {
|
||||
var newHash = hash + '&popover=' + popover
|
||||
} else {
|
||||
var newHash = '#popover='+popover;
|
||||
}
|
||||
|
||||
// never use an empty hash, as that might reload the page
|
||||
if ('' == newHash) {
|
||||
newHash = '#';
|
||||
}
|
||||
|
||||
broadcast.forceReload = false;
|
||||
$.history.load(newHash);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 (typeof piwikMenu !== 'undefined') {
|
||||
piwikMenu.activateMenu(
|
||||
broadcast.getParamValue('module', urlAjax),
|
||||
broadcast.getParamValue('action', urlAjax),
|
||||
broadcast.getParamValue('idGoal', urlAjax) || broadcast.getParamValue('idDashboard', urlAjax)
|
||||
);
|
||||
}
|
||||
|
||||
piwikHelper.hideAjaxError('loadingError');
|
||||
piwikHelper.showAjaxLoading();
|
||||
$('#content').empty();
|
||||
$("object").remove();
|
||||
|
||||
urlAjax = urlAjax.match(/^\?/) ? urlAjax : "?" + urlAjax;
|
||||
broadcast.lastUrlRequested = urlAjax;
|
||||
function sectionLoaded(content) {
|
||||
// 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();
|
||||
broadcast.lastUrlRequested = null;
|
||||
|
||||
piwikHelper.compileAngularComponents('#content');
|
||||
}
|
||||
|
||||
initTopControls();
|
||||
}
|
||||
|
||||
var ajax = new ajaxHelper();
|
||||
ajax.setUrl(urlAjax);
|
||||
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) {
|
||||
broadcast.lastUrlRequested = null;
|
||||
|
||||
// do not display error message if request was aborted
|
||||
if(status == 'abort') {
|
||||
return;
|
||||
}
|
||||
$('#loadingError').show();
|
||||
setTimeout( function(){
|
||||
$('#loadingError').fadeOut('slow');
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return hash string if hash exists on address bar.
|
||||
* else return false;
|
||||
*
|
||||
* @return {string|boolean} current hash or false if it is empty
|
||||
*/
|
||||
isHashExists: function () {
|
||||
var hashStr = broadcast.getHashFromUrl();
|
||||
|
||||
if (hashStr != "") {
|
||||
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);
|
||||
}
|
||||
else {
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extracts from a query strings, the request array
|
||||
* @param queryString
|
||||
* @returns {object}
|
||||
*/
|
||||
extractKeyValuePairsFromQueryString: function (queryString) {
|
||||
var pairs = queryString.split('&');
|
||||
var result = {};
|
||||
for (var i = 0; i != pairs.length; ++i) {
|
||||
// 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('=');
|
||||
var key = pair.shift();
|
||||
result[key] = pair.join('=');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var hashStr = broadcast.getHashFromUrl(url);
|
||||
if (hashStr.substr(0, 1) == '#') {
|
||||
hashStr = hashStr.substr(1);
|
||||
}
|
||||
hashStr = hashStr.split('#')[0];
|
||||
|
||||
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) {
|
||||
var lookFor = param + '=';
|
||||
var startStr = url.indexOf(lookFor);
|
||||
|
||||
if (startStr >= 0) {
|
||||
var endStr = url.indexOf("&", startStr);
|
||||
if (endStr == -1) {
|
||||
endStr = url.length;
|
||||
}
|
||||
var value = url.substring(startStr + param.length + 1, endStr);
|
||||
|
||||
// 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
|
||||
*/
|
||||
getHash: function () {
|
||||
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 searchString = '';
|
||||
if (url) {
|
||||
var urlParts = url.split('#');
|
||||
searchString = urlParts[0];
|
||||
} else {
|
||||
searchString = location.search;
|
||||
}
|
||||
return searchString;
|
||||
}
|
||||
};
|
||||
545
www/analytics/plugins/CoreHome/javascripts/calendar.js
Normal file
545
www/analytics/plugins/CoreHome/javascripts/calendar.js
Normal file
|
|
@ -0,0 +1,545 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
Date.prototype.getWeek = function () {
|
||||
var onejan = new Date(this.getFullYear(), 0, 1), // needed for getDay()
|
||||
|
||||
// use UTC times since getTime() can differ based on user's timezone
|
||||
onejan_utc = Date.UTC(this.getFullYear(), 0, 1),
|
||||
this_utc = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()),
|
||||
|
||||
daysSinceYearStart = (this_utc - onejan_utc) / 86400000; // constant is millisecs in one day
|
||||
|
||||
return Math.ceil((daysSinceYearStart + onejan.getDay()) / 7);
|
||||
};
|
||||
|
||||
var currentYear, currentMonth, currentDay, currentDate, currentWeek;
|
||||
|
||||
function setCurrentDate(dateStr) {
|
||||
var splitDate = dateStr.split("-");
|
||||
currentYear = splitDate[0];
|
||||
currentMonth = splitDate[1] - 1;
|
||||
currentDay = splitDate[2];
|
||||
currentDate = new Date(currentYear, currentMonth, currentDay);
|
||||
currentWeek = currentDate.getWeek();
|
||||
}
|
||||
|
||||
if(!piwik.currentDateString) {
|
||||
// eg. Login form
|
||||
return;
|
||||
}
|
||||
setCurrentDate(piwik.currentDateString);
|
||||
|
||||
var todayDate = new Date;
|
||||
var todayMonth = todayDate.getMonth();
|
||||
var todayYear = todayDate.getFullYear();
|
||||
var todayDay = todayDate.getDate();
|
||||
|
||||
// min/max date for picker
|
||||
var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
|
||||
piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
|
||||
|
||||
// we start w/ the current period
|
||||
var selectedPeriod = piwik.period;
|
||||
|
||||
function isDateInCurrentPeriod(date) {
|
||||
// if the selected period isn't the current period, don't highlight any dates
|
||||
if (selectedPeriod != piwik.period) {
|
||||
return [true, ''];
|
||||
}
|
||||
|
||||
var valid = false;
|
||||
|
||||
var dateMonth = date.getMonth();
|
||||
var dateYear = date.getFullYear();
|
||||
var dateDay = date.getDate();
|
||||
|
||||
// we don't color dates in the future
|
||||
if (dateMonth == todayMonth
|
||||
&& dateYear == todayYear
|
||||
&& dateDay > todayDay
|
||||
) {
|
||||
return [true, ''];
|
||||
}
|
||||
|
||||
// we don't color dates before the minimum date
|
||||
if (dateYear < piwik.minDateYear
|
||||
|| ( dateYear == piwik.minDateYear
|
||||
&&
|
||||
(
|
||||
(dateMonth == piwik.minDateMonth - 1
|
||||
&& dateDay < piwik.minDateDay)
|
||||
|| (dateMonth < piwik.minDateMonth - 1)
|
||||
)
|
||||
)
|
||||
) {
|
||||
return [true, ''];
|
||||
}
|
||||
|
||||
// we color all day of the month for the same year for the month period
|
||||
if (piwik.period == "month"
|
||||
&& dateMonth == currentMonth
|
||||
&& dateYear == currentYear
|
||||
) {
|
||||
valid = true;
|
||||
}
|
||||
// we color all day of the year for the year period
|
||||
else if (piwik.period == "year"
|
||||
&& dateYear == currentYear
|
||||
) {
|
||||
valid = true;
|
||||
}
|
||||
else if (piwik.period == "week"
|
||||
&& date.getWeek() == currentWeek
|
||||
&& dateYear == currentYear
|
||||
) {
|
||||
valid = true;
|
||||
}
|
||||
else if (piwik.period == "day"
|
||||
&& dateDay == currentDay
|
||||
&& dateMonth == currentMonth
|
||||
&& dateYear == currentYear
|
||||
) {
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
return [true, 'ui-datepicker-current-period'];
|
||||
}
|
||||
|
||||
return [true, ''];
|
||||
}
|
||||
|
||||
piwik.getBaseDatePickerOptions = function (defaultDate) {
|
||||
return {
|
||||
showOtherMonths: false,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
firstDay: 1,
|
||||
minDate: piwikMinDate,
|
||||
maxDate: piwikMaxDate,
|
||||
prevText: "",
|
||||
nextText: "",
|
||||
currentText: "",
|
||||
defaultDate: defaultDate,
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
stepMonths: 1,
|
||||
// jquery-ui-i18n 1.7.2 lacks some translations, so we use our own
|
||||
dayNamesMin: [
|
||||
_pk_translate('General_DaySu'),
|
||||
_pk_translate('General_DayMo'),
|
||||
_pk_translate('General_DayTu'),
|
||||
_pk_translate('General_DayWe'),
|
||||
_pk_translate('General_DayTh'),
|
||||
_pk_translate('General_DayFr'),
|
||||
_pk_translate('General_DaySa')],
|
||||
dayNamesShort: [
|
||||
_pk_translate('General_ShortDay_7'), // start with sunday
|
||||
_pk_translate('General_ShortDay_1'),
|
||||
_pk_translate('General_ShortDay_2'),
|
||||
_pk_translate('General_ShortDay_3'),
|
||||
_pk_translate('General_ShortDay_4'),
|
||||
_pk_translate('General_ShortDay_5'),
|
||||
_pk_translate('General_ShortDay_6')],
|
||||
dayNames: [
|
||||
_pk_translate('General_LongDay_7'), // start with sunday
|
||||
_pk_translate('General_LongDay_1'),
|
||||
_pk_translate('General_LongDay_2'),
|
||||
_pk_translate('General_LongDay_3'),
|
||||
_pk_translate('General_LongDay_4'),
|
||||
_pk_translate('General_LongDay_5'),
|
||||
_pk_translate('General_LongDay_6')],
|
||||
monthNamesShort: [
|
||||
_pk_translate('General_ShortMonth_1'),
|
||||
_pk_translate('General_ShortMonth_2'),
|
||||
_pk_translate('General_ShortMonth_3'),
|
||||
_pk_translate('General_ShortMonth_4'),
|
||||
_pk_translate('General_ShortMonth_5'),
|
||||
_pk_translate('General_ShortMonth_6'),
|
||||
_pk_translate('General_ShortMonth_7'),
|
||||
_pk_translate('General_ShortMonth_8'),
|
||||
_pk_translate('General_ShortMonth_9'),
|
||||
_pk_translate('General_ShortMonth_10'),
|
||||
_pk_translate('General_ShortMonth_11'),
|
||||
_pk_translate('General_ShortMonth_12')],
|
||||
monthNames: [
|
||||
_pk_translate('General_LongMonth_1'),
|
||||
_pk_translate('General_LongMonth_2'),
|
||||
_pk_translate('General_LongMonth_3'),
|
||||
_pk_translate('General_LongMonth_4'),
|
||||
_pk_translate('General_LongMonth_5'),
|
||||
_pk_translate('General_LongMonth_6'),
|
||||
_pk_translate('General_LongMonth_7'),
|
||||
_pk_translate('General_LongMonth_8'),
|
||||
_pk_translate('General_LongMonth_9'),
|
||||
_pk_translate('General_LongMonth_10'),
|
||||
_pk_translate('General_LongMonth_11'),
|
||||
_pk_translate('General_LongMonth_12')]
|
||||
};
|
||||
};
|
||||
|
||||
var updateDate;
|
||||
|
||||
function getDatePickerOptions() {
|
||||
var result = piwik.getBaseDatePickerOptions(currentDate);
|
||||
result.beforeShowDay = isDateInCurrentPeriod;
|
||||
result.stepMonths = selectedPeriod == 'year' ? 12 : 1;
|
||||
result.onSelect = function () { updateDate.apply(this, arguments); };
|
||||
return result;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
|
||||
var datepickerElem = $('#datepicker').datepicker(getDatePickerOptions()),
|
||||
periodLabels = $('#periodString').find('.period-type label'),
|
||||
periodTooltip = $('#periodString').find('.period-click-tooltip').html();
|
||||
|
||||
var toggleWhitespaceHighlighting = function (klass, toggleTop, toggleBottom) {
|
||||
var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
|
||||
viewedMonth = +$('.ui-datepicker-month', datepickerElem).val(), // convert to int w/ '+'
|
||||
firstOfViewedMonth = new Date(viewedYear, viewedMonth, 1),
|
||||
lastOfViewedMonth = new Date(viewedYear, viewedMonth + 1, 0);
|
||||
|
||||
// only highlight dates between piwik.minDate... & piwik.maxDate...
|
||||
// we select the cells to highlight by checking whether the first & last of the
|
||||
// currently viewed month are within the min/max dates.
|
||||
if (firstOfViewedMonth >= piwikMinDate) {
|
||||
$('tbody>tr:first-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleTop);
|
||||
}
|
||||
if (lastOfViewedMonth < piwikMaxDate) {
|
||||
$('tbody>tr:last-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleBottom);
|
||||
}
|
||||
};
|
||||
|
||||
// 'this' is the table cell
|
||||
var highlightCurrentPeriod = function () {
|
||||
switch (selectedPeriod) {
|
||||
case 'day':
|
||||
// highlight this link
|
||||
$('a', $(this)).addClass('ui-state-hover');
|
||||
break;
|
||||
case 'week':
|
||||
var row = $(this).parent();
|
||||
|
||||
// highlight parent row (the week)
|
||||
$('a', row).addClass('ui-state-hover');
|
||||
|
||||
// toggle whitespace if week goes into previous or next month. we check if week is on
|
||||
// top or bottom row.
|
||||
var toggleTop = row.is(':first-child'),
|
||||
toggleBottom = row.is(':last-child');
|
||||
toggleWhitespaceHighlighting('ui-state-hover', toggleTop, toggleBottom);
|
||||
break;
|
||||
case 'month':
|
||||
// highlight all parent rows (the month)
|
||||
$('a', $(this).parent().parent()).addClass('ui-state-hover');
|
||||
break;
|
||||
case 'year':
|
||||
// highlight table (month + whitespace)
|
||||
$('a', $(this).parent().parent()).addClass('ui-state-hover');
|
||||
toggleWhitespaceHighlighting('ui-state-hover', true, true);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var unhighlightAllDates = function () {
|
||||
// make sure nothing is highlighted
|
||||
$('.ui-state-active,.ui-state-hover', datepickerElem).removeClass('ui-state-active ui-state-hover');
|
||||
|
||||
// color whitespace
|
||||
if (piwik.period == 'year') {
|
||||
var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
|
||||
toggle = selectedPeriod == 'year' && currentYear == viewedYear;
|
||||
toggleWhitespaceHighlighting('ui-datepicker-current-period', toggle, toggle);
|
||||
}
|
||||
else if (piwik.period == 'week') {
|
||||
var toggleTop = $('tr:first-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period'),
|
||||
toggleBottom = $('tr:last-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period');
|
||||
toggleWhitespaceHighlighting('ui-datepicker-current-period', toggleTop, toggleBottom);
|
||||
}
|
||||
};
|
||||
|
||||
updateDate = function (dateText) {
|
||||
piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
|
||||
|
||||
// select new dates in calendar
|
||||
setCurrentDate(dateText);
|
||||
piwik.period = selectedPeriod;
|
||||
|
||||
// make sure it's called after jquery-ui is done, otherwise everything we do will
|
||||
// be undone.
|
||||
setTimeout(unhighlightAllDates, 1);
|
||||
|
||||
datepickerElem.datepicker('refresh');
|
||||
|
||||
// Let broadcast do its job:
|
||||
// It will replace date value to both search query and hash and load the new page.
|
||||
broadcast.propagateNewPage('date=' + dateText + '&period=' + selectedPeriod);
|
||||
};
|
||||
|
||||
var toggleMonthDropdown = function (disable) {
|
||||
if (typeof disable === 'undefined') {
|
||||
disable = selectedPeriod == 'year';
|
||||
}
|
||||
|
||||
// enable/disable month dropdown based on period == year
|
||||
$('.ui-datepicker-month', datepickerElem).attr('disabled', disable);
|
||||
};
|
||||
|
||||
var togglePeriodPickers = function (showSingle) {
|
||||
$('#periodString').find('.period-date').toggle(showSingle);
|
||||
$('#periodString').find('.period-range').toggle(!showSingle);
|
||||
$('#calendarRangeApply').toggle(!showSingle);
|
||||
};
|
||||
|
||||
//
|
||||
// setup datepicker
|
||||
//
|
||||
|
||||
unhighlightAllDates();
|
||||
|
||||
//
|
||||
// hook up event slots
|
||||
//
|
||||
|
||||
// highlight current period when mouse enters date
|
||||
datepickerElem.on('mouseenter', 'tbody td', function () {
|
||||
if ($(this).hasClass('ui-state-hover')) // if already highlighted, do nothing
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// unhighlight if cell is disabled/blank, unless the period is year
|
||||
if ($(this).hasClass('ui-state-disabled') && selectedPeriod != 'year') {
|
||||
unhighlightAllDates();
|
||||
|
||||
// if period is week, then highlight the current week
|
||||
if (selectedPeriod == 'week') {
|
||||
highlightCurrentPeriod.call(this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
highlightCurrentPeriod.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
// make sure cell stays highlighted when mouse leaves cell (overrides jquery-ui behavior)
|
||||
datepickerElem.on('mouseleave', 'tbody td', function () {
|
||||
$('a', this).addClass('ui-state-hover');
|
||||
});
|
||||
|
||||
// unhighlight everything when mouse leaves table body (can't do event on tbody, for some reason
|
||||
// that fails, so we do two events, one on the table & one on thead)
|
||||
datepickerElem.on('mouseleave', 'table', unhighlightAllDates)
|
||||
.on('mouseenter', 'thead', unhighlightAllDates);
|
||||
|
||||
// make sure whitespace is clickable when the period makes it appropriate
|
||||
datepickerElem.on('click', 'tbody td.ui-datepicker-other-month', function () {
|
||||
if ($(this).hasClass('ui-state-hover')) {
|
||||
var row = $(this).parent(), tbody = row.parent();
|
||||
|
||||
if (row.is(':first-child')) {
|
||||
// click on first of the month
|
||||
$('a', tbody).first().click();
|
||||
}
|
||||
else {
|
||||
// click on last of month
|
||||
$('a', tbody).last().click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hack to get around firefox bug. When double clicking a label in firefox, the 'click'
|
||||
// event of its associated input will not be fired twice. We want to change the period
|
||||
// if clicking the select period's label OR input, so we catch the click event on the
|
||||
// label & the input.
|
||||
var reloading = false;
|
||||
var changePeriodOnClick = function (periodInput) {
|
||||
if (reloading) // if a click event resulted in reloading, don't reload again
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var url = periodInput.val(),
|
||||
period = broadcast.getValueFromUrl('period', url);
|
||||
|
||||
// if clicking on the selected period, change the period but not the date
|
||||
if (selectedPeriod == period && selectedPeriod != 'range') {
|
||||
// only reload if current period is different from selected
|
||||
if (piwik.period != selectedPeriod && !reloading) {
|
||||
reloading = true;
|
||||
selectedPeriod = period;
|
||||
updateDate(piwik.currentDateString);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$("#otherPeriods").find("label").on('click', function (e) {
|
||||
var id = $(e.target).attr('for');
|
||||
changePeriodOnClick($('#' + id));
|
||||
});
|
||||
|
||||
// when non-range period is clicked, change the period & refresh the date picker
|
||||
$("#otherPeriods").find("input").on('click', function (e) {
|
||||
var request_URL = $(e.target).val(),
|
||||
period = broadcast.getValueFromUrl('period', request_URL),
|
||||
lastPeriod = selectedPeriod;
|
||||
|
||||
if (changePeriodOnClick($(e.target))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// switch the selected period
|
||||
selectedPeriod = period;
|
||||
|
||||
// remove tooltips from the period inputs
|
||||
periodLabels.each(function () { $(this).attr('title', '').removeClass('selected-period-label'); });
|
||||
|
||||
// range periods are handled in an event handler below
|
||||
if (period == 'range') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// set the tooltip of the current period
|
||||
if (period != piwik.period) // don't add tooltip for current period
|
||||
{
|
||||
$(this).parent().find('label[for=period_id_' + period + ']')
|
||||
.attr('title', periodTooltip).addClass('selected-period-label');
|
||||
}
|
||||
|
||||
// toggle the right selector controls (show period selector datepicker & hide 'apply range' button)
|
||||
togglePeriodPickers(true);
|
||||
|
||||
// set months step to 12 for year period (or set back to 1 if leaving year period)
|
||||
if (selectedPeriod == 'year' || lastPeriod == 'year') {
|
||||
// setting stepMonths will change the month in view back to the selected date. to avoid
|
||||
// we set the selected date to the month in view.
|
||||
var currentMonth = $('.ui-datepicker-month', datepickerElem).val(),
|
||||
currentYear = $('.ui-datepicker-year', datepickerElem).val();
|
||||
|
||||
datepickerElem
|
||||
.datepicker('option', 'stepMonths', selectedPeriod == 'year' ? 12 : 1)
|
||||
.datepicker('setDate', new Date(currentYear, currentMonth));
|
||||
}
|
||||
|
||||
datepickerElem.datepicker('refresh'); // must be last datepicker call, otherwise cells get highlighted
|
||||
|
||||
unhighlightAllDates();
|
||||
toggleMonthDropdown();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// clicking left/right re-enables the month dropdown, so we disable it again
|
||||
$(datepickerElem).on('click', '.ui-datepicker-next,.ui-datepicker-prev', function () {
|
||||
unhighlightAllDates(); // make sure today's date isn't highlighted & toggle extra year highlighting
|
||||
toggleMonthDropdown(selectedPeriod == 'year');
|
||||
});
|
||||
|
||||
// reset date/period when opening calendar
|
||||
$("#periodString").on('click', "#date,.calendar-icon", function () {
|
||||
var periodMore = $("#periodMore").toggle();
|
||||
if (periodMore.is(":visible")) {
|
||||
periodMore.find(".ui-state-highlight").removeClass('ui-state-highlight');
|
||||
}
|
||||
});
|
||||
|
||||
$('body').on('click', function(e) {
|
||||
var target = $(e.target);
|
||||
if (target.closest('html').length && !target.closest('#periodString').length && !target.is('option') && $("#periodMore").is(":visible")) {
|
||||
$("#periodMore").hide();
|
||||
}
|
||||
});
|
||||
|
||||
function onDateRangeSelect(dateText, inst) {
|
||||
var toOrFrom = inst.id == 'calendarFrom' ? 'From' : 'To';
|
||||
$('#inputCalendar' + toOrFrom).val(dateText);
|
||||
}
|
||||
|
||||
// this will trigger to change only the period value on search query and hash string.
|
||||
$("#period_id_range").on('click', function (e) {
|
||||
togglePeriodPickers(false);
|
||||
|
||||
var options = getDatePickerOptions();
|
||||
|
||||
// Custom Date range callback
|
||||
options.onSelect = onDateRangeSelect;
|
||||
// Do not highlight the period
|
||||
options.beforeShowDay = '';
|
||||
// Create both calendars
|
||||
options.defaultDate = piwik.startDateString;
|
||||
$('#calendarFrom').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.startDateString));
|
||||
|
||||
// Technically we should trigger the onSelect event on the calendar, but I couldn't find how to do that
|
||||
// So calling the onSelect bind function manually...
|
||||
//$('#calendarFrom').trigger('dateSelected'); // or onSelect
|
||||
onDateRangeSelect(piwik.startDateString, { "id": "calendarFrom" });
|
||||
|
||||
// Same code for the other calendar
|
||||
options.defaultDate = piwik.endDateString;
|
||||
$('#calendarTo').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.endDateString));
|
||||
onDateRangeSelect(piwik.endDateString, { "id": "calendarTo" });
|
||||
|
||||
|
||||
// If not called, the first date appears light brown instead of dark brown
|
||||
$('.ui-state-hover').removeClass('ui-state-hover');
|
||||
|
||||
// Apply date range button will reload the page with the selected range
|
||||
$('#calendarRangeApply')
|
||||
.on('click', function () {
|
||||
var request_URL = $(e.target).val();
|
||||
var dateFrom = $('#inputCalendarFrom').val(),
|
||||
dateTo = $('#inputCalendarTo').val(),
|
||||
oDateFrom = $.datepicker.parseDate('yy-mm-dd', dateFrom),
|
||||
oDateTo = $.datepicker.parseDate('yy-mm-dd', dateTo);
|
||||
|
||||
if (!isValidDate(oDateFrom)
|
||||
|| !isValidDate(oDateTo)
|
||||
|| oDateFrom > oDateTo) {
|
||||
$('#alert').find('h2').text(_pk_translate('General_InvalidDateRange'));
|
||||
piwikHelper.modalConfirm('#alert', {});
|
||||
return false;
|
||||
}
|
||||
piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
|
||||
broadcast.propagateNewPage('period=range&date=' + dateFrom + ',' + dateTo);
|
||||
})
|
||||
.show();
|
||||
|
||||
|
||||
// Bind the input fields to update the calendar's date when date is manually changed
|
||||
$('#inputCalendarFrom, #inputCalendarTo')
|
||||
.keyup(function (e) {
|
||||
var fromOrTo = this.id == 'inputCalendarFrom' ? 'From' : 'To';
|
||||
var dateInput = $(this).val();
|
||||
try {
|
||||
var newDate = $.datepicker.parseDate('yy-mm-dd', dateInput);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
$("#calendar" + fromOrTo).datepicker("setDate", newDate);
|
||||
if (e.keyCode == 13) {
|
||||
$('#calendarRangeApply').click();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
function isValidDate(d) {
|
||||
if (Object.prototype.toString.call(d) !== "[object Date]")
|
||||
return false;
|
||||
return !isNaN(d.getTime());
|
||||
}
|
||||
|
||||
if (piwik.period == 'range') {
|
||||
$("#period_id_range").click();
|
||||
}
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
225
www/analytics/plugins/CoreHome/javascripts/color_manager.js
Normal file
225
www/analytics/plugins/CoreHome/javascripts/color_manager.js
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
var colorNames = {"aliceblue":"#f0f8ff","antiquewhite":"#faebd7","aqua":"#00ffff","aquamarine":"#7fffd4","azure":"#f0ffff",
|
||||
"beige":"#f5f5dc","bisque":"#ffe4c4","black":"#000000","blanchedalmond":"#ffebcd","blue":"#0000ff","blueviolet":"#8a2be2","brown":"#a52a2a","burlywood":"#deb887",
|
||||
"cadetblue":"#5f9ea0","chartreuse":"#7fff00","chocolate":"#d2691e","coral":"#ff7f50","cornflowerblue":"#6495ed","cornsilk":"#fff8dc","crimson":"#dc143c","cyan":"#00ffff",
|
||||
"darkblue":"#00008b","darkcyan":"#008b8b","darkgoldenrod":"#b8860b","darkgray":"#a9a9a9","darkgreen":"#006400","darkkhaki":"#bdb76b","darkmagenta":"#8b008b","darkolivegreen":"#556b2f",
|
||||
"darkorange":"#ff8c00","darkorchid":"#9932cc","darkred":"#8b0000","darksalmon":"#e9967a","darkseagreen":"#8fbc8f","darkslateblue":"#483d8b","darkslategray":"#2f4f4f","darkturquoise":"#00ced1",
|
||||
"darkviolet":"#9400d3","deeppink":"#ff1493","deepskyblue":"#00bfff","dimgray":"#696969","dodgerblue":"#1e90ff",
|
||||
"firebrick":"#b22222","floralwhite":"#fffaf0","forestgreen":"#228b22","fuchsia":"#ff00ff","gainsboro":"#dcdcdc","ghostwhite":"#f8f8ff","gold":"#ffd700","goldenrod":"#daa520","gray":"#808080","green":"#008000","greenyellow":"#adff2f",
|
||||
"honeydew":"#f0fff0","hotpink":"#ff69b4","indianred ":"#cd5c5c","indigo ":"#4b0082","ivory":"#fffff0","khaki":"#f0e68c",
|
||||
"lavender":"#e6e6fa","lavenderblush":"#fff0f5","lawngreen":"#7cfc00","lemonchiffon":"#fffacd","lightblue":"#add8e6","lightcoral":"#f08080","lightcyan":"#e0ffff","lightgoldenrodyellow":"#fafad2",
|
||||
"lightgrey":"#d3d3d3","lightgreen":"#90ee90","lightpink":"#ffb6c1","lightsalmon":"#ffa07a","lightseagreen":"#20b2aa","lightskyblue":"#87cefa","lightslategray":"#778899","lightsteelblue":"#b0c4de",
|
||||
"lightyellow":"#ffffe0","lime":"#00ff00","limegreen":"#32cd32","linen":"#faf0e6","magenta":"#ff00ff","maroon":"#800000","mediumaquamarine":"#66cdaa","mediumblue":"#0000cd","mediumorchid":"#ba55d3","mediumpurple":"#9370d8","mediumseagreen":"#3cb371","mediumslateblue":"#7b68ee",
|
||||
"mediumspringgreen":"#00fa9a","mediumturquoise":"#48d1cc","mediumvioletred":"#c71585","midnightblue":"#191970","mintcream":"#f5fffa","mistyrose":"#ffe4e1","moccasin":"#ffe4b5",
|
||||
"navajowhite":"#ffdead","navy":"#000080","oldlace":"#fdf5e6","olive":"#808000","olivedrab":"#6b8e23","orange":"#ffa500","orangered":"#ff4500","orchid":"#da70d6",
|
||||
"palegoldenrod":"#eee8aa","palegreen":"#98fb98","paleturquoise":"#afeeee","palevioletred":"#d87093","papayawhip":"#ffefd5","peachpuff":"#ffdab9","peru":"#cd853f","pink":"#ffc0cb","plum":"#dda0dd","powderblue":"#b0e0e6","purple":"#800080",
|
||||
"red":"#ff0000","rosybrown":"#bc8f8f","royalblue":"#4169e1","saddlebrown":"#8b4513","salmon":"#fa8072","sandybrown":"#f4a460","seagreen":"#2e8b57","seashell":"#fff5ee","sienna":"#a0522d","silver":"#c0c0c0","skyblue":"#87ceeb","slateblue":"#6a5acd","slategray":"#708090","snow":"#fffafa","springgreen":"#00ff7f","steelblue":"#4682b4",
|
||||
"tan":"#d2b48c","teal":"#008080","thistle":"#d8bfd8","tomato":"#ff6347","turquoise":"#40e0d0","violet":"#ee82ee","wheat":"#f5deb3","white":"#ffffff","whitesmoke":"#f5f5f5","yellow":"#ffff00","yellowgreen":"#9acd32"};
|
||||
|
||||
/**
|
||||
* The ColorManager class allows JS code to grab colors defined in CSS for
|
||||
* components that don't manage HTML (like jqPlot or sparklines). Such components
|
||||
* can't use CSS colors directly since the colors are used to generate images
|
||||
* or by <canvas> elements.
|
||||
*
|
||||
* Colors obtained via ColorManager are defined in CSS like this:
|
||||
*
|
||||
* .my-color-namespace[data-name=color-name] {
|
||||
* color: #fff
|
||||
* }
|
||||
*
|
||||
* and can be accessed in JavaScript like this:
|
||||
*
|
||||
* piwik.ColorManager.getColor("my-color-namespace", "color-name");
|
||||
*
|
||||
* The singleton instance of this class can be accessed via piwik.ColorManager.
|
||||
*/
|
||||
var ColorManager = function () {
|
||||
// empty
|
||||
};
|
||||
|
||||
ColorManager.prototype = {
|
||||
|
||||
/**
|
||||
* Returns the color for a namespace and name.
|
||||
*
|
||||
* @param {String} namespace The string identifier that groups related colors
|
||||
* together. For example, 'sparkline-colors'.
|
||||
* @param {String} name The name of the color to retrieve. For example, 'lineColor'.
|
||||
* @return {String} A hex color, eg, '#fff'.
|
||||
*/
|
||||
getColor: function (namespace, name) {
|
||||
var element = this._getElement();
|
||||
|
||||
element.attr('class', 'color-manager ' + namespace).attr('data-name', name);
|
||||
|
||||
return this._normalizeColor(element.css('color'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the colors for a namespace and a list of names.
|
||||
*
|
||||
* @param {String} namespace The string identifier that groups related colors
|
||||
* together. For example, 'sparkline-colors'.
|
||||
* @param {Array} names An array of color names to retrieve.
|
||||
* @param {Boolean} asArray Whether the result should be an array or an object.
|
||||
* @return {Object|Array} An object mapping color names with color values or an
|
||||
* array of colors.
|
||||
*/
|
||||
getColors: function (namespace, names, asArray) {
|
||||
var colors = asArray ? [] : {};
|
||||
for (var i = 0; i != names.length; ++i) {
|
||||
var name = names[i],
|
||||
color = this.getColor(namespace, name);
|
||||
if (color) {
|
||||
if (asArray) {
|
||||
colors.push(color);
|
||||
} else {
|
||||
colors[name] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a color that is N % between two other colors.
|
||||
*
|
||||
* @param {String|Array} spectrumStart The start color. If percentFromStart is 0, this color will
|
||||
* be returned. Can be either a hex color or RGB array.
|
||||
* It will be converted to an RGB array if a hex color is supplied.
|
||||
* @param {String|Array} spectrumEnd The end color. If percentFromStart is 1, this color will be
|
||||
* returned. Can be either a hex color or RGB array. It will be
|
||||
* converted to an RGB array if a hex color is supplied.
|
||||
* @param {Number} percentFromStart The percent from spectrumStart and twoard spectrumEnd that the
|
||||
* result color should be. Must be a value between 0.0 & 1.0.
|
||||
* @return {String} A hex color.
|
||||
*/
|
||||
getSingleColorFromGradient: function (spectrumStart, spectrumEnd, percentFromStart) {
|
||||
if (!(spectrumStart instanceof Array)) {
|
||||
spectrumStart = this.getRgb(spectrumStart);
|
||||
}
|
||||
|
||||
if (!(spectrumEnd instanceof Array)) {
|
||||
spectrumEnd = this.getRgb(spectrumEnd);
|
||||
}
|
||||
|
||||
var result = [];
|
||||
for (var channel = 0; channel != spectrumStart.length; ++channel) {
|
||||
var delta = (spectrumEnd[channel] - spectrumStart[channel]) * percentFromStart;
|
||||
|
||||
result[channel] = Math.floor(spectrumStart[channel] + delta);
|
||||
}
|
||||
|
||||
return this.getHexColor(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function that converts a hex color (ie, #fff or #1a1a1a) to an array of
|
||||
* RGB values.
|
||||
*
|
||||
* @param {String} hexColor The color to convert.
|
||||
* @return {Array} An array with three integers between 0 and 255.
|
||||
*/
|
||||
getRgb: function (hexColor) {
|
||||
if (hexColor[0] == '#') {
|
||||
hexColor = hexColor.substring(1);
|
||||
}
|
||||
|
||||
if (hexColor.length == 3) {
|
||||
return [
|
||||
parseInt(hexColor[0], 16),
|
||||
parseInt(hexColor[1], 16),
|
||||
parseInt(hexColor[2], 16)
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
parseInt(hexColor.substring(0,2), 16),
|
||||
parseInt(hexColor.substring(2,4), 16),
|
||||
parseInt(hexColor.substring(4,6), 16)
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function that converts an RGB array to a hex color.
|
||||
*
|
||||
* @param {Array} rgbColor An array with three integers between 0 and 255.
|
||||
* @return {String} The hex color, eg, #1a1a1a.
|
||||
*/
|
||||
getHexColor: function (rgbColor) {
|
||||
// convert channels to hex with one leading 0
|
||||
for (var i = 0; i != rgbColor.length; ++i) {
|
||||
rgbColor[i] = ("00" + rgbColor[i].toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
// create hex string
|
||||
return '#' + rgbColor.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Turns a color string that might be an rgb value rgb(12, 34, 56) into
|
||||
* a hex color string.
|
||||
*/
|
||||
_normalizeColor: function (color) {
|
||||
if (color == this._getTransparentColor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (color && colorNames[color]) {
|
||||
return colorNames[color];
|
||||
}
|
||||
|
||||
if (color
|
||||
&& color[0] != '#'
|
||||
) {
|
||||
// parse rgb(#, #, #) and get rgb numbers
|
||||
var parts = color.split(/[()rgb,\s]+/);
|
||||
parts = [+parts[1], +parts[2], +parts[3]];
|
||||
|
||||
// convert to hex
|
||||
color = this.getHexColor(parts);
|
||||
}
|
||||
return color;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the manufactured <div> element used to obtain color data. When
|
||||
* getting color data the class and data-name attribute of this element are
|
||||
* changed.
|
||||
*/
|
||||
_getElement: function () {
|
||||
if (!this.$element) {
|
||||
$('body').append('<div id="color-manager"></div>');
|
||||
this.$element = $('#color-manager');
|
||||
}
|
||||
|
||||
return this.$element;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns this browser's representation of the 'transparent' color. Used to
|
||||
* compare against colors obtained in getColor. If a color is 'transparent'
|
||||
* it means there's no color for that namespace/name combination.
|
||||
*/
|
||||
_getTransparentColor: function () {
|
||||
if (!this.transparentColor) {
|
||||
this.transparentColor =
|
||||
$('<div style="color:transparent;display:none;"></div>').appendTo($('body')).css('color');
|
||||
}
|
||||
|
||||
return this.transparentColor;
|
||||
}
|
||||
};
|
||||
|
||||
piwik.ColorManager = new ColorManager();
|
||||
|
||||
}(jQuery));
|
||||
160
www/analytics/plugins/CoreHome/javascripts/corehome.js
Executable file
160
www/analytics/plugins/CoreHome/javascripts/corehome.js
Executable file
|
|
@ -0,0 +1,160 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
$(function () {
|
||||
|
||||
//
|
||||
// 'check for updates' behavior
|
||||
//
|
||||
|
||||
var headerMessageParent = $('#header_message').parent();
|
||||
|
||||
// when 'check for updates...' link is clicked, force a check & display the result
|
||||
headerMessageParent.on('click', '#updateCheckLinkContainer', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var headerMessage = $(this).closest('#header_message');
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.setLoadingElement('#header_message .loadingPiwik');
|
||||
ajaxRequest.addParams({
|
||||
module: 'CoreHome',
|
||||
action: 'checkForUpdates'
|
||||
}, 'get');
|
||||
ajaxRequest.setCallback(function (response) {
|
||||
headerMessage.fadeOut('slow', function () {
|
||||
response = $(response);
|
||||
|
||||
var newVersionAvailable = response.hasClass('header_alert');
|
||||
if (newVersionAvailable) {
|
||||
headerMessage.replaceWith(response);
|
||||
}
|
||||
else {
|
||||
headerMessage.html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion')).show();
|
||||
setTimeout(function () {
|
||||
headerMessage.fadeOut('slow', function () {
|
||||
headerMessage.replaceWith(response);
|
||||
});
|
||||
}, 4000);
|
||||
}
|
||||
});
|
||||
});
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// when clicking the header message, show the long message w/o needing to hover
|
||||
headerMessageParent.on('click', '#header_message', function (e) {
|
||||
if (e.target.tagName.toLowerCase() != 'a') {
|
||||
$(this).toggleClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// section toggler behavior
|
||||
//
|
||||
|
||||
var handleSectionToggle = function (self, showType, doHide) {
|
||||
var sectionId = $(self).attr('data-section-id'),
|
||||
section = $('#' + sectionId),
|
||||
showText = _pk_translate('General_Show'),
|
||||
hideText = _pk_translate('General_Hide');
|
||||
|
||||
if (typeof(doHide) == 'undefined') {
|
||||
doHide = section.is(':visible');
|
||||
}
|
||||
|
||||
if (doHide) {
|
||||
var newText = $(self).text().replace(hideText, showText),
|
||||
afterHide = function () { $(self).text(newText); };
|
||||
|
||||
if (showType == 'slide') {
|
||||
section.slideUp(afterHide);
|
||||
}
|
||||
else if (showType == 'inline') {
|
||||
section.hide();
|
||||
afterHide();
|
||||
}
|
||||
else {
|
||||
section.hide(afterHide);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var newText = $(self).text().replace(showText, hideText);
|
||||
$(self).text(newText);
|
||||
|
||||
if (showType == 'slide') {
|
||||
section.slideDown();
|
||||
}
|
||||
else if (showType == 'inline') {
|
||||
section.css('display', 'inline-block');
|
||||
}
|
||||
else {
|
||||
section.show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// when click section toggler link, toggle the visibility of the associated section
|
||||
$('body').on('click', 'a.section-toggler-link', function (e) {
|
||||
e.preventDefault();
|
||||
handleSectionToggle(this, 'slide');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('change', 'input.section-toggler-link', function (e) {
|
||||
handleSectionToggle(this, 'inline', !$(this).is(':checked'));
|
||||
});
|
||||
|
||||
//
|
||||
// reports by dimension list behavior
|
||||
//
|
||||
|
||||
// when a report dimension is clicked, load the appropriate report
|
||||
var currentWidgetLoading = null;
|
||||
$('body').on('click', '.reportDimension', function (e) {
|
||||
var view = $(this).closest('.reportsByDimensionView'),
|
||||
report = $('.dimensionReport', view),
|
||||
loading = $('.loadingPiwik', view);
|
||||
|
||||
// make this dimension the active one
|
||||
$('.activeDimension', view).removeClass('activeDimension');
|
||||
$(this).addClass('activeDimension');
|
||||
|
||||
// hide the visible report & show the loading elem
|
||||
report.hide();
|
||||
loading.show();
|
||||
|
||||
// load the report using the data-url attribute (which holds the URL to the report)
|
||||
var widgetParams = broadcast.getValuesFromUrl($(this).attr('data-url'));
|
||||
for (var key in widgetParams) {
|
||||
widgetParams[key] = decodeURIComponent(widgetParams[key]);
|
||||
}
|
||||
|
||||
var widgetUniqueId = widgetParams.module + widgetParams.action;
|
||||
currentWidgetLoading = widgetUniqueId;
|
||||
|
||||
widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParams, function (response) {
|
||||
// if the widget that was loaded was not for the latest clicked link, do nothing w/ the response
|
||||
if (widgetUniqueId != currentWidgetLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.hide();
|
||||
report.html($(response)).css('display', 'inline-block');
|
||||
|
||||
// scroll to report
|
||||
piwikHelper.lazyScrollTo(report, 400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
1767
www/analytics/plugins/CoreHome/javascripts/dataTable.js
Normal file
1767
www/analytics/plugins/CoreHome/javascripts/dataTable.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,386 @@
|
|||
/**
|
||||
* Registry for row actions
|
||||
*
|
||||
* Plugins can call DataTable_RowActions_Registry.register() from their JS
|
||||
* files in order to add new actions to arbitrary data tables. The register()
|
||||
* method takes an object containing:
|
||||
* - name: string identifying the action. must be short, no spaces.
|
||||
* - dataTableIcon: path to the icon for the action
|
||||
* - createInstance: a factory method to create an instance of the appropriate
|
||||
* subclass of DataTable_RowAction
|
||||
* - isAvailable: a method to determine whether the action is available in a
|
||||
* given row of a data table
|
||||
*/
|
||||
var DataTable_RowActions_Registry = {
|
||||
|
||||
registry: [],
|
||||
|
||||
register: function (action) {
|
||||
var createInstance = action.createInstance;
|
||||
action.createInstance = function (dataTable, param) {
|
||||
var instance = createInstance(dataTable, param);
|
||||
instance.actionName = action.name;
|
||||
return instance;
|
||||
};
|
||||
|
||||
this.registry.push(action);
|
||||
},
|
||||
|
||||
getAvailableActionsForReport: function (dataTableParams, tr) {
|
||||
if (dataTableParams.disable_row_actions == '1') {
|
||||
return [];
|
||||
}
|
||||
|
||||
var available = [];
|
||||
for (var i = 0; i < this.registry.length; i++) {
|
||||
if (this.registry[i].isAvailableOnReport(dataTableParams, tr)) {
|
||||
available.push(this.registry[i]);
|
||||
}
|
||||
}
|
||||
available.sort(function (a, b) {
|
||||
return b.order - a.order;
|
||||
});
|
||||
return available;
|
||||
},
|
||||
|
||||
getActionByName: function (name) {
|
||||
for (var i = 0; i < this.registry.length; i++) {
|
||||
if (this.registry[i].name == name) {
|
||||
return this.registry[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Register Row Evolution (also servers as example)
|
||||
DataTable_RowActions_Registry.register({
|
||||
|
||||
name: 'RowEvolution',
|
||||
|
||||
dataTableIcon: 'plugins/Zeitgeist/images/row_evolution.png',
|
||||
dataTableIconHover: 'plugins/Zeitgeist/images/row_evolution_hover.png',
|
||||
|
||||
order: 50,
|
||||
|
||||
dataTableIconTooltip: [
|
||||
_pk_translate('General_RowEvolutionRowActionTooltipTitle'),
|
||||
_pk_translate('General_RowEvolutionRowActionTooltip')
|
||||
],
|
||||
|
||||
createInstance: function (dataTable, param) {
|
||||
if (dataTable !== null && typeof dataTable.rowEvolutionActionInstance != 'undefined') {
|
||||
return dataTable.rowEvolutionActionInstance;
|
||||
}
|
||||
|
||||
if (dataTable === null && param) {
|
||||
// when row evolution is triggered from the url (not a click on the data table)
|
||||
// we look for the data table instance in the dom
|
||||
var report = param.split(':')[0];
|
||||
var div = $(require('piwik/UI').DataTable.getDataTableByReport(report));
|
||||
if (div.size() > 0 && div.data('uiControlObject')) {
|
||||
dataTable = div.data('uiControlObject');
|
||||
if (typeof dataTable.rowEvolutionActionInstance != 'undefined') {
|
||||
return dataTable.rowEvolutionActionInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var instance = new DataTable_RowActions_RowEvolution(dataTable);
|
||||
if (dataTable !== null) {
|
||||
dataTable.rowEvolutionActionInstance = instance;
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
|
||||
isAvailableOnReport: function (dataTableParams) {
|
||||
return (
|
||||
typeof dataTableParams.disable_row_evolution == 'undefined'
|
||||
|| dataTableParams.disable_row_evolution == "0"
|
||||
) && (
|
||||
typeof dataTableParams.flat == 'undefined'
|
||||
|| dataTableParams.flat == "0"
|
||||
);
|
||||
},
|
||||
|
||||
isAvailableOnRow: function (dataTableParams, tr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* DataTable Row Actions
|
||||
*
|
||||
* The lifecycle of an action is as follows:
|
||||
* - for each data table, a new instance of the action is created using the factory
|
||||
* - when the table is loaded, initTr is called for each tr
|
||||
* - when the action icon is clicked, trigger is called
|
||||
* - the label is put together and performAction is called
|
||||
* - performAction must call openPopover on the base class
|
||||
* - openPopover calls back doOpenPopover after doing general stuff
|
||||
*
|
||||
* The two template methods are performAction and doOpenPopover
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
// BASE CLASS
|
||||
//
|
||||
|
||||
function DataTable_RowAction(dataTable) {
|
||||
this.dataTable = dataTable;
|
||||
|
||||
// has to be overridden in subclasses
|
||||
this.trEventName = 'piwikTriggerRowAction';
|
||||
|
||||
// set in registry
|
||||
this.actionName = 'RowAction';
|
||||
}
|
||||
|
||||
/** Initialize a row when the table is loaded */
|
||||
DataTable_RowAction.prototype.initTr = function (tr) {
|
||||
var self = this;
|
||||
|
||||
// For subtables, we need to make sure that the actions are always triggered on the
|
||||
// action instance connected to the root table. Otherwise sharing data (e.g. for
|
||||
// for multi-row evolution) wouldn't be possible. Also, sub-tables might have different
|
||||
// API actions. For the label filter to work, we need to use the parent action.
|
||||
// We use jQuery events to let subtables access their parents.
|
||||
tr.bind(self.trEventName, function (e, params) {
|
||||
self.trigger($(this), params.originalEvent, params.label);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called from the click event and the tr event (see this.trEventName).
|
||||
* It derives the label and calls performAction.
|
||||
*/
|
||||
DataTable_RowAction.prototype.trigger = function (tr, e, subTableLabel) {
|
||||
var label = this.getLabelFromTr(tr);
|
||||
|
||||
label = label.trim();
|
||||
// if we have received the event from the sub table, add the label
|
||||
if (subTableLabel) {
|
||||
var separator = ' > '; // LabelFilter::SEPARATOR_RECURSIVE_LABEL
|
||||
label += separator + subTableLabel.trim();
|
||||
}
|
||||
|
||||
// handle sub tables in nested reports: forward to parent
|
||||
var subtable = tr.closest('table');
|
||||
if (subtable.is('.subDataTable')) {
|
||||
subtable.closest('tr').prev().trigger(this.trEventName, {
|
||||
label: label,
|
||||
originalEvent: e
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// ascend in action reports
|
||||
if (subtable.closest('div.dataTableActions').length) {
|
||||
var allClasses = tr.attr('class');
|
||||
var matches = allClasses.match(/level[0-9]+/);
|
||||
var level = parseInt(matches[0].substring(5, matches[0].length), 10);
|
||||
if (level > 0) {
|
||||
// .prev(.levelX) does not work for some reason => do it "by hand"
|
||||
var findLevel = 'level' + (level - 1);
|
||||
var ptr = tr;
|
||||
while ((ptr = ptr.prev()).size() > 0) {
|
||||
if (!ptr.hasClass(findLevel)) {
|
||||
continue;
|
||||
}
|
||||
ptr.trigger(this.trEventName, {
|
||||
label: label,
|
||||
originalEvent: e
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.performAction(label, tr, e);
|
||||
};
|
||||
|
||||
/** Get the label string from a tr dom element */
|
||||
DataTable_RowAction.prototype.getLabelFromTr = function (tr) {
|
||||
var label = tr.find('span.label');
|
||||
|
||||
// handle truncation
|
||||
var value = label.data('originalText');
|
||||
|
||||
if (!value) {
|
||||
value = label.text();
|
||||
}
|
||||
value = value.trim();
|
||||
|
||||
return encodeURIComponent(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Base method for opening popovers.
|
||||
* This method will remember the parameter in the url and call doOpenPopover().
|
||||
*/
|
||||
DataTable_RowAction.prototype.openPopover = function (parameter) {
|
||||
broadcast.propagateNewPopoverParameter('RowAction', this.actionName + ':' + parameter);
|
||||
};
|
||||
|
||||
broadcast.addPopoverHandler('RowAction', function (param) {
|
||||
var paramParts = param.split(':');
|
||||
var rowActionName = paramParts[0];
|
||||
paramParts.shift();
|
||||
param = paramParts.join(':');
|
||||
|
||||
var rowAction = DataTable_RowActions_Registry.getActionByName(rowActionName);
|
||||
if (rowAction) {
|
||||
rowAction.createInstance(null, param).doOpenPopover(param);
|
||||
}
|
||||
});
|
||||
|
||||
/** To be overridden */
|
||||
DataTable_RowAction.prototype.performAction = function (label, tr, e) {
|
||||
};
|
||||
DataTable_RowAction.prototype.doOpenPopover = function (parameter) {
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// ROW EVOLUTION
|
||||
//
|
||||
|
||||
function DataTable_RowActions_RowEvolution(dataTable) {
|
||||
this.dataTable = dataTable;
|
||||
this.trEventName = 'piwikTriggerRowEvolution';
|
||||
|
||||
/** The rows to be compared in multi row evolution */
|
||||
this.multiEvolutionRows = [];
|
||||
}
|
||||
|
||||
/** Static helper method to launch row evolution from anywhere */
|
||||
DataTable_RowActions_RowEvolution.launch = function (apiMethod, label) {
|
||||
var param = 'RowEvolution:' + apiMethod + ':0:' + label;
|
||||
broadcast.propagateNewPopoverParameter('RowAction', param);
|
||||
};
|
||||
|
||||
DataTable_RowActions_RowEvolution.prototype = new DataTable_RowAction;
|
||||
|
||||
DataTable_RowActions_RowEvolution.prototype.performAction = function (label, tr, e) {
|
||||
if (e.shiftKey) {
|
||||
// only mark for multi row evolution if shift key is pressed
|
||||
this.addMultiEvolutionRow(label);
|
||||
return;
|
||||
}
|
||||
|
||||
// check whether we have rows marked for multi row evolution
|
||||
var isMultiRowEvolution = '0';
|
||||
this.addMultiEvolutionRow(label);
|
||||
if (this.multiEvolutionRows.length > 1) {
|
||||
isMultiRowEvolution = '1';
|
||||
label = this.multiEvolutionRows.join(',');
|
||||
}
|
||||
|
||||
var apiMethod = this.dataTable.param.module + '.' + this.dataTable.param.action;
|
||||
this.openPopover(apiMethod, isMultiRowEvolution, label);
|
||||
};
|
||||
|
||||
DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function (label) {
|
||||
if ($.inArray(label, this.multiEvolutionRows) == -1) {
|
||||
this.multiEvolutionRows.push(label);
|
||||
}
|
||||
};
|
||||
|
||||
DataTable_RowActions_RowEvolution.prototype.openPopover = function (apiMethod, multiRowEvolutionParam, label) {
|
||||
var urlParam = apiMethod + ':' + multiRowEvolutionParam + ':' + label;
|
||||
DataTable_RowAction.prototype.openPopover.apply(this, [urlParam]);
|
||||
};
|
||||
|
||||
DataTable_RowActions_RowEvolution.prototype.doOpenPopover = function (urlParam) {
|
||||
var urlParamParts = urlParam.split(':');
|
||||
|
||||
var apiMethod = urlParamParts[0];
|
||||
urlParamParts.shift();
|
||||
|
||||
var multiRowEvolutionParam = urlParamParts[0];
|
||||
urlParamParts.shift();
|
||||
|
||||
var label = urlParamParts.join(':');
|
||||
|
||||
this.showRowEvolution(apiMethod, label, multiRowEvolutionParam);
|
||||
};
|
||||
|
||||
/** Open the row evolution popover */
|
||||
DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMethod, label, multiRowEvolutionParam) {
|
||||
|
||||
var self = this;
|
||||
|
||||
// open the popover
|
||||
var box = Piwik_Popover.showLoading('Row Evolution');
|
||||
box.addClass('rowEvolutionPopover');
|
||||
|
||||
// prepare loading the popover contents
|
||||
var requestParams = {
|
||||
apiMethod: apiMethod,
|
||||
label: label,
|
||||
disableLink: 1
|
||||
};
|
||||
|
||||
// derive api action and requested column from multiRowEvolutionParam
|
||||
var action;
|
||||
if (multiRowEvolutionParam == '0') {
|
||||
action = 'getRowEvolutionPopover';
|
||||
} else if (multiRowEvolutionParam == '1') {
|
||||
action = 'getMultiRowEvolutionPopover';
|
||||
} else {
|
||||
action = 'getMultiRowEvolutionPopover';
|
||||
requestParams.column = multiRowEvolutionParam;
|
||||
}
|
||||
|
||||
var callback = function (html) {
|
||||
Piwik_Popover.setContent(html);
|
||||
|
||||
// use the popover title returned from the server
|
||||
var title = box.find('div.popover-title');
|
||||
if (title.size() > 0) {
|
||||
Piwik_Popover.setTitle(title.html());
|
||||
title.remove();
|
||||
}
|
||||
|
||||
Piwik_Popover.onClose(function () {
|
||||
// reset rows marked for multi row evolution on close
|
||||
self.multiEvolutionRows = [];
|
||||
});
|
||||
|
||||
if (self.dataTable !== null) {
|
||||
// remember label for multi row evolution
|
||||
box.find('a.rowevolution-startmulti').click(function () {
|
||||
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
|
||||
Piwik_Popover.close();
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
// when the popover is launched by copy&pasting a url, we don't have the data table.
|
||||
// in this case, we can't remember the row marked for multi row evolution so
|
||||
// we disable the picker.
|
||||
box.find('.compare-container, .rowevolution-startmulti').remove();
|
||||
}
|
||||
|
||||
// switch metric in multi row evolution
|
||||
box.find('select.multirowevoltion-metric').change(function () {
|
||||
var metric = $(this).val();
|
||||
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
|
||||
self.openPopover(apiMethod, metric, label);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
requestParams.module = 'CoreHome';
|
||||
requestParams.action = action;
|
||||
requestParams.colors = JSON.stringify(piwik.getSparklineColors());
|
||||
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(requestParams, 'get');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
};
|
||||
142
www/analytics/plugins/CoreHome/javascripts/donate.js
Executable file
142
www/analytics/plugins/CoreHome/javascripts/donate.js
Executable file
|
|
@ -0,0 +1,142 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
var donateAmounts = [0, 30, 60, 90, 120];
|
||||
|
||||
// returns the space between each donation amount in the donation slider
|
||||
var getTickWidth = function (slider) {
|
||||
var effectiveSliderWidth = $('.slider-range', slider).width() - $('.slider-position', slider).width();
|
||||
return effectiveSliderWidth / (donateAmounts.length - 1);
|
||||
};
|
||||
|
||||
// returns the position index on a slider based on a x coordinate value
|
||||
var getPositionFromPageCoord = function (slider, pageX) {
|
||||
return Math.round((pageX - $('.slider-range', slider).offset().left) / getTickWidth(slider));
|
||||
};
|
||||
|
||||
// set's the correct amount text & smiley face image based on the position of the slider
|
||||
var setSmileyFaceAndAmount = function (slider, pos) {
|
||||
// set text yearly amount
|
||||
$('.slider-donate-amount', slider).text('$' + donateAmounts[pos] + '/' + _pk_translate('General_YearShort'));
|
||||
|
||||
// set the right smiley face
|
||||
$('.slider-smiley-face').attr('src', 'plugins/Zeitgeist/images/smileyprog_' + pos + '.png');
|
||||
|
||||
// set the hidden option input for paypal
|
||||
var option = Math.max(1, pos);
|
||||
$('.piwik-donate-call input[name=os0]').val("Option " + option);
|
||||
};
|
||||
|
||||
// move's a slider's position to a specific spot
|
||||
var moveSliderPosition = function (slider, to) {
|
||||
// make sure 'to' is valid
|
||||
if (to < 0) {
|
||||
to = 0;
|
||||
}
|
||||
else if (to >= donateAmounts.length) {
|
||||
to = donateAmounts.length - 1;
|
||||
}
|
||||
|
||||
// set the slider position
|
||||
var left = to * getTickWidth(slider);
|
||||
if (left == 0) {
|
||||
left = -1; // at position 0 we move one pixel left to cover up some of slider bar
|
||||
}
|
||||
|
||||
$('.slider-position', slider).css({
|
||||
left: left + 'px'
|
||||
});
|
||||
|
||||
// reset the smiley face & amount based on the new position
|
||||
setSmileyFaceAndAmount(slider, to);
|
||||
};
|
||||
|
||||
// when a slider is clicked, set the amount & smiley face appropriately
|
||||
$('body').on('click', '.piwik-donate-slider>.slider-range', function (e) {
|
||||
var slider = $(this).parent(),
|
||||
currentPageX = $('.slider-position', this).offset().left,
|
||||
currentPos = getPositionFromPageCoord(slider, currentPageX),
|
||||
pos = getPositionFromPageCoord(slider, e.pageX);
|
||||
|
||||
// if the closest position is the current one, use the other position since
|
||||
// the user obviously wants to move the slider.
|
||||
if (currentPos == pos) {
|
||||
// if click is to right, go forward one, else backwards one
|
||||
if (e.pageX > currentPageX) {
|
||||
++pos;
|
||||
}
|
||||
else {
|
||||
--pos;
|
||||
}
|
||||
}
|
||||
|
||||
moveSliderPosition(slider, pos);
|
||||
});
|
||||
|
||||
// when the smiley icon is clicked, move the position up one to demonstrate how to use the slider
|
||||
$('body').on('click', '.piwik-donate-slider .slider-smiley-face,.piwik-donate-slider .slider-donate-amount',
|
||||
function (e) {
|
||||
var slider = $(this).closest('.piwik-donate-slider'),
|
||||
currentPageX = $('.slider-position', slider).offset().left,
|
||||
currentPos = getPositionFromPageCoord(slider, currentPageX);
|
||||
|
||||
moveSliderPosition(slider, currentPos + 1);
|
||||
}
|
||||
);
|
||||
|
||||
// stores the current slider being dragged
|
||||
var draggingSlider = false;
|
||||
|
||||
// start dragging on mousedown for a slider's position bar
|
||||
$('body').on('mousedown', '.piwik-donate-slider .slider-position', function () {
|
||||
draggingSlider = $(this).parent().parent();
|
||||
});
|
||||
|
||||
// move the slider position if currently dragging when the mouse moves anywhere over the entire page
|
||||
$('body').on('mousemove', function (e) {
|
||||
if (draggingSlider) {
|
||||
var slider = draggingSlider.find('.slider-range'),
|
||||
sliderPos = slider.find('.slider-position'),
|
||||
left = e.pageX - slider.offset().left;
|
||||
|
||||
// only move slider if the mouse x-coord is still on the slider (w/ some padding for borders)
|
||||
if (left <= slider.width() - sliderPos.width() + 2
|
||||
&& left >= -2) {
|
||||
sliderPos.css({left: left + 'px'});
|
||||
|
||||
var closestPos = Math.round(left / getTickWidth(draggingSlider));
|
||||
setSmileyFaceAndAmount(draggingSlider, closestPos);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// stop dragging and normalize a slider's position on mouseup over the entire page
|
||||
$('body').on('mouseup', function () {
|
||||
if (draggingSlider) {
|
||||
var sliderPos = $('.slider-position', draggingSlider),
|
||||
slider = sliderPos.parent();
|
||||
|
||||
if (sliderPos.length) {
|
||||
// move the slider to the nearest donation amount position
|
||||
var pos = getPositionFromPageCoord(draggingSlider, sliderPos.offset().left);
|
||||
moveSliderPosition(draggingSlider, pos);
|
||||
}
|
||||
|
||||
draggingSlider = false; // stop dragging
|
||||
}
|
||||
});
|
||||
|
||||
// event for programatically changing the position
|
||||
$('body').on('piwik:changePosition', '.piwik-donate-slider', function (e, data) {
|
||||
moveSliderPosition(this, data.position);
|
||||
});
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
110
www/analytics/plugins/CoreHome/javascripts/menu.js
Normal file
110
www/analytics/plugins/CoreHome/javascripts/menu.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function menu() {
|
||||
this.param = {};
|
||||
}
|
||||
|
||||
menu.prototype =
|
||||
{
|
||||
resetTimer: null,
|
||||
|
||||
adaptSubMenuHeight: function() {
|
||||
var subNavHeight = $('.sfHover > ul').outerHeight();
|
||||
$('.nav_sep').height(subNavHeight);
|
||||
},
|
||||
|
||||
overMainLI: function () {
|
||||
var $this = $(this);
|
||||
$this.siblings().removeClass('sfHover');
|
||||
$this.addClass('sfHover');
|
||||
menu.prototype.adaptSubMenuHeight();
|
||||
clearTimeout(menu.prototype.resetTimer);
|
||||
},
|
||||
|
||||
outMainLI: function () {
|
||||
clearTimeout(menu.prototype.resetTimer);
|
||||
menu.prototype.resetTimer = setTimeout(function() {
|
||||
$('.Menu-tabList > .sfHover', this.menuNode).removeClass('sfHover');
|
||||
$('.Menu-tabList > .sfActive', this.menuNode).addClass('sfHover');
|
||||
menu.prototype.adaptSubMenuHeight();
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
onItemClick: function (item) {
|
||||
$('.Menu--dashboard').trigger('piwikSwitchPage', item);
|
||||
broadcast.propagateAjax( $(item).attr('href').substr(1) );
|
||||
return false;
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.menuNode = $('.Menu--dashboard');
|
||||
|
||||
this.menuNode.find("li:has(ul)").hover(this.overMainLI, this.outMainLI);
|
||||
|
||||
// add id to all li menu to support menu identification.
|
||||
// for all sub menu we want to have a unique id based on their module and action
|
||||
// for main menu we want to add just the module as its id.
|
||||
this.menuNode.find('li').each(function () {
|
||||
var url = $(this).find('a').attr('href').substr(1);
|
||||
var module = broadcast.getValueFromUrl("module", url);
|
||||
var action = broadcast.getValueFromUrl("action", url);
|
||||
var moduleId = broadcast.getValueFromUrl("idGoal", url) || broadcast.getValueFromUrl("idDashboard", url);
|
||||
var main_menu = $(this).parent().hasClass('Menu-tabList') ? true : false;
|
||||
if (main_menu) {
|
||||
$(this).attr({id: module});
|
||||
}
|
||||
// if there's a idGoal or idDashboard, use this in the ID
|
||||
else if (moduleId != '') {
|
||||
$(this).attr({id: module + '_' + action + '_' + moduleId});
|
||||
}
|
||||
else {
|
||||
$(this).attr({id: module + '_' + action});
|
||||
}
|
||||
});
|
||||
|
||||
menu.prototype.adaptSubMenuHeight();
|
||||
},
|
||||
|
||||
activateMenu: function (module, action, id) {
|
||||
this.menuNode.find('li').removeClass('sfHover').removeClass('sfActive');
|
||||
var $li = this.getSubmenuID(module, id, action);
|
||||
var mainLi = $("#" + module);
|
||||
if (!mainLi.length) {
|
||||
mainLi = $li.parents('li');
|
||||
}
|
||||
|
||||
mainLi.addClass('sfActive').addClass('sfHover');
|
||||
|
||||
$li.addClass('sfHover');
|
||||
},
|
||||
|
||||
// getting the right li is a little tricky since goals uses idGoal, and overview is index.
|
||||
getSubmenuID: function (module, id, action) {
|
||||
var $li = '';
|
||||
// So, if module is Goals, id is present, and action is not Index, must be one of the goals
|
||||
if (module == 'Goals' && id != '' && (action != 'index')) {
|
||||
$li = $("#" + module + "_" + action + "_" + id);
|
||||
// if module is Dashboard and id is present, must be one of the dashboards
|
||||
} else if (module == 'Dashboard') {
|
||||
if (!id) id = 1;
|
||||
$li = $("#" + module + "_" + action + "_" + id);
|
||||
} else {
|
||||
$li = $("#" + module + "_" + action);
|
||||
}
|
||||
return $li;
|
||||
},
|
||||
|
||||
loadFirstSection: function () {
|
||||
if (broadcast.isHashExists() == false) {
|
||||
$('li:first a:first', this.menuNode).click().addClass('sfHover').addClass('sfActive');
|
||||
}
|
||||
}
|
||||
};
|
||||
19
www/analytics/plugins/CoreHome/javascripts/menu_init.js
Normal file
19
www/analytics/plugins/CoreHome/javascripts/menu_init.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
$(function () {
|
||||
var isPageHasMenu = $('.Menu--dashboard').size();
|
||||
var isPageIsAdmin = $('#content.admin').size();
|
||||
if (isPageHasMenu) {
|
||||
piwikMenu = new menu();
|
||||
piwikMenu.init();
|
||||
piwikMenu.loadFirstSection();
|
||||
}
|
||||
|
||||
if(isPageIsAdmin) {
|
||||
// don't use broadcast in admin pages
|
||||
return;
|
||||
}
|
||||
if(isPageHasMenu) {
|
||||
broadcast.init();
|
||||
} else {
|
||||
broadcast.init(true);
|
||||
}
|
||||
});
|
||||
198
www/analytics/plugins/CoreHome/javascripts/notification.js
Normal file
198
www/analytics/plugins/CoreHome/javascripts/notification.js
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
var exports = require('piwik/UI');
|
||||
|
||||
/**
|
||||
* Creates a new notifications.
|
||||
*
|
||||
* Example:
|
||||
* var UI = require('piwik/UI');
|
||||
* var notification = new UI.Notification();
|
||||
* notification.show('My Notification Message', {title: 'Low space', context: 'warning'});
|
||||
*/
|
||||
var Notification = function () {
|
||||
this.$node = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes the notification visible.
|
||||
*
|
||||
* @param {string} message The actual message that will be displayed. Must be set.
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.id] Only needed for persistent notifications. The id will be sent to the
|
||||
* frontend once the user closes the notifications. The notification has to
|
||||
* be registered/notified under this name
|
||||
* @param {string} [options.title] The title of the notification. For instance the plugin name.
|
||||
* @param {bool} [options.animate=true] If enabled, the notification will be faded in.
|
||||
* @param {string} [options.context=warning] Context of the notification: 'info', 'warning', 'success' or
|
||||
* 'error'
|
||||
* @param {string} [options.type=transient] The type of the notification: Either 'toast' or 'transitent'
|
||||
* @param {bool} [options.noclear=false] If set, the close icon is not displayed.
|
||||
* @param {object} [options.style] Optional style/css dictionary. For instance {'display': 'inline-block'}
|
||||
* @param {string} [options.placeat] By default, the notification will be displayed in the "stats bar".
|
||||
* You can specify any other CSS selector to place the notifications
|
||||
* whereever you want.
|
||||
*/
|
||||
Notification.prototype.show = function (message, options) {
|
||||
if (!message) {
|
||||
throw new Error('No message given, cannot display notification');
|
||||
}
|
||||
if (options && !$.isPlainObject(options)) {
|
||||
throw new Error('Options has the wrong format, cannot display notification');
|
||||
} else if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if ('persistent' == options.type) {
|
||||
// otherwise it is never possible to dismiss the notification
|
||||
options.noclear = false;
|
||||
}
|
||||
|
||||
closeExistingNotificationHavingSameIdIfNeeded(options);
|
||||
|
||||
var template = generateNotificationHtmlMarkup(options, message);
|
||||
var $notificationNode = placeNotification(template, options);
|
||||
this.$node = $notificationNode;
|
||||
|
||||
if ('persistent' == options.type) {
|
||||
addPersistentEvent($notificationNode);
|
||||
} else if ('toast' == options.type) {
|
||||
addToastEvent($notificationNode);
|
||||
}
|
||||
|
||||
if (!options.noclear) {
|
||||
addCloseEvent($notificationNode);
|
||||
}
|
||||
};
|
||||
|
||||
Notification.prototype.scrollToNotification = function () {
|
||||
if (this.$node) {
|
||||
piwikHelper.lazyScrollTo(this.$node, 250);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Notification = Notification;
|
||||
|
||||
function closeExistingNotificationHavingSameIdIfNeeded(options)
|
||||
{
|
||||
if (!options.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $existingNode = $('.system.notification[data-id=' + options.id + ']');
|
||||
if ($existingNode && $existingNode.length) {
|
||||
$existingNode.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function generateNotificationHtmlMarkup(options, message) {
|
||||
var template = buildNotificationStart(options);
|
||||
|
||||
if (!options.noclear) {
|
||||
template += buildClearButton();
|
||||
}
|
||||
|
||||
if (options.title) {
|
||||
template += buildTitle(options);
|
||||
}
|
||||
|
||||
template += message;
|
||||
template += buildNotificationEnd();
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function buildNotificationStart(options) {
|
||||
var template = '<div class="notification system';
|
||||
|
||||
if (options.context) {
|
||||
template += ' notification-' + options.context;
|
||||
}
|
||||
|
||||
template += '"';
|
||||
|
||||
if (options.id) {
|
||||
template += ' data-id="' + options.id + '"';
|
||||
}
|
||||
|
||||
template += '>';
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function buildNotificationEnd() {
|
||||
return '</div>';
|
||||
}
|
||||
|
||||
function buildClearButton() {
|
||||
return '<button type="button" class="close" data-dismiss="alert">×</button>';
|
||||
}
|
||||
|
||||
function buildTitle(options) {
|
||||
return '<strong>' + options.title + '</strong> ';
|
||||
}
|
||||
|
||||
function placeNotification(template, options) {
|
||||
|
||||
var $notificationNode = $(template);
|
||||
|
||||
if (options.style) {
|
||||
$notificationNode.css(options.style);
|
||||
}
|
||||
|
||||
$notificationNode = $notificationNode.hide();
|
||||
$(options.placeat || '#notificationContainer').append($notificationNode);
|
||||
|
||||
if (false === options.animate) {
|
||||
$notificationNode.show();
|
||||
} else {
|
||||
$notificationNode.fadeIn(1000);
|
||||
}
|
||||
|
||||
return $notificationNode;
|
||||
}
|
||||
|
||||
function addToastEvent($notificationNode)
|
||||
{
|
||||
setTimeout(function () {
|
||||
$notificationNode.fadeOut( 'slow', function() {
|
||||
$notificationNode.remove();
|
||||
$notificationNode = null;
|
||||
});
|
||||
}, 12 * 1000);
|
||||
}
|
||||
|
||||
function addCloseEvent($notificationNode) {
|
||||
$notificationNode.on('click', '.close', function (event) {
|
||||
if (event && event.delegateTarget) {
|
||||
$(event.delegateTarget).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function addPersistentEvent($notificationNode) {
|
||||
var notificationId = $notificationNode.data('id');
|
||||
|
||||
if (!notificationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notificationNode.on('click', '.close', function (event) {
|
||||
var ajaxHandler = new ajaxHelper();
|
||||
ajaxHandler.addParams({
|
||||
module: 'CoreHome',
|
||||
action: 'markNotificationAsRead'
|
||||
}, 'GET');
|
||||
ajaxHandler.addParams({notificationId: notificationId}, 'POST');
|
||||
ajaxHandler.send(true);
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery, require);
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
$(document).ready((function ($, require) {
|
||||
return function () {
|
||||
|
||||
var UI = require('piwik/UI');
|
||||
|
||||
var $notificationNodes = $('[data-role="notification"]');
|
||||
|
||||
$notificationNodes.each(function (index, notificationNode) {
|
||||
$notificationNode = $(notificationNode);
|
||||
var attributes = $notificationNode.data();
|
||||
var message = $notificationNode.html();
|
||||
|
||||
if (message) {
|
||||
var notification = new UI.Notification();
|
||||
attributes.animate = false;
|
||||
notification.show(message, attributes);
|
||||
}
|
||||
|
||||
$notificationNodes.remove();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
})(jQuery, require));
|
||||
254
www/analytics/plugins/CoreHome/javascripts/popover.js
Normal file
254
www/analytics/plugins/CoreHome/javascripts/popover.js
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
var Piwik_Popover = (function () {
|
||||
|
||||
var container = false;
|
||||
var isOpen = false;
|
||||
var closeCallback = false;
|
||||
|
||||
var createContainer = function () {
|
||||
if (container === false) {
|
||||
container = $(document.createElement('div')).attr('id', 'Piwik_Popover');
|
||||
}
|
||||
};
|
||||
|
||||
var openPopover = function (title, dialogClass) {
|
||||
createContainer();
|
||||
|
||||
var options =
|
||||
{
|
||||
title: title,
|
||||
modal: true,
|
||||
width: '950px',
|
||||
position: ['center', 'center'],
|
||||
resizable: false,
|
||||
autoOpen: true,
|
||||
open: function (event, ui) {
|
||||
if (dialogClass) {
|
||||
$(this).parent().addClass(dialogClass).attr('style', '');
|
||||
}
|
||||
|
||||
$('.ui-widget-overlay').on('click.popover', function () {
|
||||
container.dialog('close');
|
||||
});
|
||||
},
|
||||
close: function (event, ui) {
|
||||
container.find('div.jqplot-target').trigger('piwikDestroyPlot');
|
||||
container[0].innerHTML = ''; // IE8 fix
|
||||
container.dialog('destroy').remove();
|
||||
globalAjaxQueue.abort();
|
||||
$('.ui-widget-overlay').off('click.popover');
|
||||
isOpen = false;
|
||||
broadcast.propagateNewPopoverParameter(false);
|
||||
require('piwik/UI').UIControl.cleanupUnusedControls();
|
||||
if (typeof closeCallback == 'function') {
|
||||
closeCallback();
|
||||
closeCallback = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
container.dialog(options);
|
||||
|
||||
// override the undocumented _title function to ensure that the title attribute is not escaped (according to jQueryUI bug #6016)
|
||||
container.data( "uiDialog" )._title = function(title) {
|
||||
title.html( this.options.title );
|
||||
};
|
||||
|
||||
isOpen = true;
|
||||
};
|
||||
|
||||
var centerPopover = function () {
|
||||
if (container !== false) {
|
||||
container.dialog({position: ['center', 'center']});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Open the popover with a loading message
|
||||
*
|
||||
* @param {string} popoverName name of the popover
|
||||
* @param {string} [popoverSubject] subject of the popover (e.g. url, optional)
|
||||
* @param {int} [height] height of the popover in px (optional)
|
||||
* @param {string} [dialogClass] css class to add to dialog
|
||||
*/
|
||||
showLoading: function (popoverName, popoverSubject, height, dialogClass) {
|
||||
var loading = $(document.createElement('div')).addClass('Piwik_Popover_Loading');
|
||||
|
||||
var loadingMessage = popoverSubject ? translations.General_LoadingPopoverFor :
|
||||
translations.General_LoadingPopover;
|
||||
|
||||
loadingMessage = loadingMessage.replace(/%s/, popoverName);
|
||||
|
||||
var p1 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Name');
|
||||
loading.append(p1.text(loadingMessage));
|
||||
|
||||
var p2;
|
||||
if (popoverSubject) {
|
||||
popoverSubject = piwikHelper.addBreakpointsToUrl(popoverSubject);
|
||||
p1.addClass('Piwik_Popover_Loading_NameWithSubject');
|
||||
p2 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Subject');
|
||||
loading.append(p2.html(popoverSubject));
|
||||
}
|
||||
|
||||
if (height) {
|
||||
loading.height(height);
|
||||
}
|
||||
|
||||
if (!isOpen) {
|
||||
openPopover(null, dialogClass);
|
||||
}
|
||||
|
||||
this.setContent(loading);
|
||||
this.setTitle('');
|
||||
|
||||
if (height) {
|
||||
var offset = loading.height() - p1.outerHeight();
|
||||
if (popoverSubject) {
|
||||
offset -= p2.outerHeight();
|
||||
}
|
||||
var spacingEl = $(document.createElement('div'));
|
||||
spacingEl.height(Math.round(offset / 2));
|
||||
loading.prepend(spacingEl);
|
||||
}
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a help button to the current popover
|
||||
*
|
||||
* @param {string} helpUrl
|
||||
*/
|
||||
addHelpButton: function (helpUrl) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
var titlebar = container.parent().find('.ui-dialog-titlebar');
|
||||
|
||||
var button = $(document.createElement('a')).addClass('ui-dialog-titlebar-help');
|
||||
button.attr({href: helpUrl, target: '_blank'});
|
||||
|
||||
titlebar.append(button);
|
||||
},
|
||||
|
||||
/** Set the title of the popover */
|
||||
setTitle: function (titleHtml) {
|
||||
container.dialog('option', 'title', titleHtml);
|
||||
},
|
||||
|
||||
/** Set inner HTML of the popover */
|
||||
setContent: function (html) {
|
||||
if (typeof closeCallback == 'function') {
|
||||
closeCallback();
|
||||
closeCallback = false;
|
||||
}
|
||||
|
||||
container[0].innerHTML = ''; // IE8 fix
|
||||
container.html(html);
|
||||
centerPopover();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show an error message. All params are HTML!
|
||||
*
|
||||
* @param {string} title
|
||||
* @param {string} [message]
|
||||
* @param {string} [backLabel]
|
||||
*/
|
||||
showError: function (title, message, backLabel) {
|
||||
var error = $(document.createElement('div')).addClass('Piwik_Popover_Error');
|
||||
|
||||
var p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Title');
|
||||
error.append(p.html(title));
|
||||
|
||||
if (message) {
|
||||
p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Message');
|
||||
error.append(p.html(message));
|
||||
}
|
||||
|
||||
if (backLabel) {
|
||||
var back = $(document.createElement('a')).addClass('Piwik_Popover_Error_Back');
|
||||
back.attr('href', '#').click(function () {
|
||||
history.back();
|
||||
return false;
|
||||
});
|
||||
error.append(back.html(backLabel));
|
||||
}
|
||||
|
||||
if (!isOpen) {
|
||||
openPopover();
|
||||
}
|
||||
|
||||
this.setContent(error);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a callback for the next time the popover is closed or the content changes
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
onClose: function (callback) {
|
||||
closeCallback = callback;
|
||||
},
|
||||
|
||||
/** Close the popover */
|
||||
close: function () {
|
||||
if (isOpen) {
|
||||
container.dialog('close');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a Popover and load the specified URL in it.
|
||||
*
|
||||
* Note: If you want the popover to be persisted in the URL (so if the URL is copy/pasted
|
||||
* to a new window/tab it will be opened there), use broadcast.propagateNewPopoverParameter
|
||||
* with a popover handler function that calls this one.
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {string} loadingName
|
||||
* @param {string} [dialogClass] css class to add to dialog
|
||||
*/
|
||||
createPopupAndLoadUrl: function (url, loadingName, dialogClass) {
|
||||
// make sure the minimum top position of the popover is 15px
|
||||
var ensureMinimumTop = function () {
|
||||
var popoverContainer = $('#Piwik_Popover').parent();
|
||||
if (popoverContainer.position().top < 106) {
|
||||
popoverContainer.css('top', '15px');
|
||||
}
|
||||
};
|
||||
|
||||
// open the popover
|
||||
var box = Piwik_Popover.showLoading(loadingName, null, null, dialogClass);
|
||||
ensureMinimumTop();
|
||||
|
||||
var callback = function (html) {
|
||||
function setPopoverTitleIfOneFoundInContainer() {
|
||||
var title = $('h1,h2', container);
|
||||
if (title.length == 1) {
|
||||
Piwik_Popover.setTitle(title.text());
|
||||
$(title).hide();
|
||||
}
|
||||
}
|
||||
|
||||
Piwik_Popover.setContent(html);
|
||||
setPopoverTitleIfOneFoundInContainer();
|
||||
ensureMinimumTop();
|
||||
};
|
||||
var ajaxRequest = new ajaxHelper();
|
||||
ajaxRequest.addParams(piwikHelper.getArrayFromQueryString(url), 'get');
|
||||
ajaxRequest.setCallback(callback);
|
||||
ajaxRequest.setFormat('html');
|
||||
ajaxRequest.send(false);
|
||||
}
|
||||
};
|
||||
})();
|
||||
12
www/analytics/plugins/CoreHome/javascripts/promo.js
Normal file
12
www/analytics/plugins/CoreHome/javascripts/promo.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
$(function () {
|
||||
$('#piwik-promo-thumbnail').click(function () {
|
||||
var promoEmbed = $('#piwik-promo-embed'),
|
||||
widgetWidth = $(this).closest('.widgetContent').width(),
|
||||
height = (266 * widgetWidth) / 421,
|
||||
embedHtml = '<iframe width="100%" height="' + height + '" src="http://www.youtube.com/embed/OslfF_EH81g?autoplay=1&vq=hd720&wmode=transparent" frameborder="0" wmode="Opaque"></iframe>';
|
||||
|
||||
$(this).hide();
|
||||
promoEmbed.height(height).html(embedHtml);
|
||||
promoEmbed.show();
|
||||
});
|
||||
});
|
||||
39
www/analytics/plugins/CoreHome/javascripts/require.js
Normal file
39
www/analytics/plugins/CoreHome/javascripts/require.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* Module creation & inclusion for Piwik.
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function (window) {
|
||||
|
||||
var MODULE_SPLIT_REGEX = /[\/.\\]/;
|
||||
|
||||
/**
|
||||
* Returns a module for its ID. Empty modules are created if they does not exist.
|
||||
*
|
||||
* Modules are currently stored in the window object.
|
||||
*
|
||||
* @param {String} moduleId e.g. 'piwik/UserCountryMap' or 'myPlugin/Widgets/FancySchmancyThing'.
|
||||
* The following characters can be used to separate individual modules:
|
||||
* '/', '.' or '\'.
|
||||
* @return {Object} The module object.
|
||||
*/
|
||||
window.require = function (moduleId) {
|
||||
var parts = moduleId.split(MODULE_SPLIT_REGEX);
|
||||
|
||||
// TODO: we use window objects for backwards compatibility. when rest of Piwik is rewritten to use
|
||||
// require, we can switch simply holding the modules in a private variable.
|
||||
var currentModule = window;
|
||||
for (var i = 0; i != parts.length; ++i) {
|
||||
var part = parts[i];
|
||||
|
||||
currentModule[part] = currentModule[part] || {};
|
||||
currentModule = currentModule[part];
|
||||
}
|
||||
return currentModule;
|
||||
};
|
||||
|
||||
})(window);
|
||||
83
www/analytics/plugins/CoreHome/javascripts/sparkline.js
Normal file
83
www/analytics/plugins/CoreHome/javascripts/sparkline.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
var sparklineColorNames = ['backgroundColor', 'lineColor', 'minPointColor', 'maxPointColor', 'lastPointColor'];
|
||||
|
||||
piwik.getSparklineColors = function () {
|
||||
return piwik.ColorManager.getColors('sparkline-colors', sparklineColorNames);
|
||||
};
|
||||
|
||||
// initializes each sparkline so they use colors defined in CSS
|
||||
piwik.initSparklines = function() {
|
||||
$('.sparkline > img').each(function () {
|
||||
var $self = $(this);
|
||||
|
||||
if ($self.attr('src')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var colors = JSON.stringify(piwik.getSparklineColors());
|
||||
var appendToSparklineUrl = '&colors=' + encodeURIComponent(colors);
|
||||
|
||||
// Append the token_auth to the URL if it was set (eg. embed dashboard)
|
||||
var token_auth = broadcast.getValueFromUrl('token_auth');
|
||||
if (token_auth.length) {
|
||||
appendToSparklineUrl += '&token_auth=' + token_auth;
|
||||
}
|
||||
$self.attr('src', $self.attr('data-src') + appendToSparklineUrl);
|
||||
});
|
||||
};
|
||||
|
||||
window.initializeSparklines = function () {
|
||||
var sparklineUrlParamsToIgnore = ['module', 'action', 'idSite', 'period', 'date', 'viewDataTable'];
|
||||
|
||||
$("[data-graph-id]").each(function () {
|
||||
var graph = $(this);
|
||||
|
||||
// try to find sparklines and add them clickable behaviour
|
||||
graph.parent().find('div.sparkline').each(function () {
|
||||
// find the sparkline and get it's src attribute
|
||||
var sparklineUrl = $('img', this).attr('data-src');
|
||||
|
||||
if (sparklineUrl != "") {
|
||||
var params = broadcast.getValuesFromUrl(sparklineUrl);
|
||||
for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i) {
|
||||
delete params[sparklineUrlParamsToIgnore[i]];
|
||||
}
|
||||
for (var key in params) {
|
||||
if (typeof params[key] == 'undefined') {
|
||||
// this happens for example with an empty segment parameter
|
||||
delete params[key];
|
||||
} else {
|
||||
params[key] = decodeURIComponent(params[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// on click, reload the graph with the new url
|
||||
$(this).click(function () {
|
||||
var reportId = graph.attr('data-graph-id'),
|
||||
dataTable = $(require('piwik/UI').DataTable.getDataTableByReport(reportId));
|
||||
|
||||
// when the metrics picker is used, the id of the data table might be updated (which is correct behavior).
|
||||
// for example, in goal reports it might change from GoalsgetEvolutionGraph to GoalsgetEvolutionGraph1.
|
||||
// if this happens, we can't find the graph using $('#'+idDataTable+"Chart");
|
||||
// instead, we just use the first evolution graph we can find.
|
||||
if (dataTable.length == 0) {
|
||||
dataTable = $('div.dataTableVizEvolution');
|
||||
}
|
||||
|
||||
// reload the datatable w/ a new column & scroll to the graph
|
||||
dataTable.trigger('reload', params);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
27
www/analytics/plugins/CoreHome/javascripts/top_controls.js
Normal file
27
www/analytics/plugins/CoreHome/javascripts/top_controls.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
function initTopControls() {
|
||||
var $topControlsContainer = $('.top_controls'),
|
||||
left = 0;
|
||||
|
||||
if ($topControlsContainer.length) {
|
||||
$('.piwikTopControl').each(function () {
|
||||
var $control = $(this);
|
||||
if ($control.css('display') == 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
$control.css('left', left);
|
||||
|
||||
if (!$.contains($topControlsContainer[0], this)) {
|
||||
$control.detach().appendTo($topControlsContainer);
|
||||
}
|
||||
|
||||
left += $control.outerWidth(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
113
www/analytics/plugins/CoreHome/javascripts/uiControl.js
Normal file
113
www/analytics/plugins/CoreHome/javascripts/uiControl.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* Visitor profile popup control.
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function ($, require) {
|
||||
|
||||
var exports = require('piwik/UI');
|
||||
|
||||
/**
|
||||
* Base type for Piwik UI controls. Provides functionality that all controls need (such as
|
||||
* cleanup on destruction).
|
||||
*
|
||||
* @param {Element} element The root element of the control.
|
||||
*/
|
||||
var UIControl = function (element) {
|
||||
if (!element) {
|
||||
throw new Error("no element passed to UIControl constructor");
|
||||
}
|
||||
|
||||
this._controlId = UIControl._controls.length;
|
||||
UIControl._controls.push(this);
|
||||
|
||||
var $element = this.$element = $(element);
|
||||
$element.data('uiControlObject', this);
|
||||
|
||||
var params = JSON.parse($element.attr('data-params') || '{}');
|
||||
for (var key in params) { // convert values in params that are arrays to comma separated string lists
|
||||
if (params[key] instanceof Array) {
|
||||
params[key] = params[key].join(',');
|
||||
}
|
||||
}
|
||||
this.param = params;
|
||||
|
||||
this.props = JSON.parse($element.attr('data-props') || '{}');
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains all active control instances.
|
||||
*/
|
||||
UIControl._controls = [];
|
||||
|
||||
/**
|
||||
* Utility method that will clean up all piwik UI controls whose elements are not attached
|
||||
* to the DOM.
|
||||
*
|
||||
* TODO: instead of having other pieces of the UI manually calling cleanupUnusedControls,
|
||||
* MutationObservers should be used
|
||||
*/
|
||||
UIControl.cleanupUnusedControls = function () {
|
||||
var controls = UIControl._controls;
|
||||
|
||||
for (var i = 0; i != controls.length; ++i) {
|
||||
var control = controls[i];
|
||||
if (control
|
||||
&& control.$element
|
||||
&& !$.contains(document.documentElement, control.$element[0])
|
||||
) {
|
||||
controls[i] = null;
|
||||
control._destroy();
|
||||
|
||||
if (!control._baseDestroyCalled) {
|
||||
throw new Error("Error: " + control.constructor.name + "'s destroy method does not call " +
|
||||
"UIControl.destroy. You may have a memory leak.");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UIControl.initElements = function (klass, selector) {
|
||||
$(selector).each(function () {
|
||||
if (!$(this).attr('data-inited')) {
|
||||
var control = new klass(this);
|
||||
$(this).attr('data-inited', 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
UIControl.prototype = {
|
||||
|
||||
/**
|
||||
* Perform cleanup. Called when the control has been removed from the DOM. Derived
|
||||
* classes should overload this function to perform their own cleanup.
|
||||
*/
|
||||
_destroy: function () {
|
||||
this.$element.removeData('uiControlObject');
|
||||
delete this.$element;
|
||||
|
||||
this._baseDestroyCalled = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the widget resize event, if we're currently in a widget.
|
||||
*
|
||||
* TODO: should use proper resize detection (see
|
||||
* http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ )
|
||||
* with timeouts (since resizing widgets can be expensive)
|
||||
*/
|
||||
onWidgetResize: function (handler) {
|
||||
var $widget = this.$element.closest('.widgetContent');
|
||||
$widget.on('widget:maximise', handler)
|
||||
.on('widget:minimise', handler)
|
||||
.on('widget:resize', handler);
|
||||
}
|
||||
};
|
||||
|
||||
exports.UIControl = UIControl;
|
||||
|
||||
})(jQuery, require);
|
||||
Loading…
Add table
Add a link
Reference in a new issue