update Piwik to version 2.16 (fixes #91)

This commit is contained in:
oliver 2016-04-10 18:55:57 +02:00
commit d885a4baa9
5833 changed files with 418860 additions and 226988 deletions

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -54,9 +54,9 @@ var broadcast = {
}
broadcast._isInit = true;
// Initialize history plugin.
// The callback is called at once by present location.hash
$.history.init(broadcast.pageload, {unescape: true});
angular.element(document).injector().invoke(function (historyService) {
historyService.init();
});
if(noLoadingMessage != true) {
piwikHelper.showAjaxLoading();
@ -151,7 +151,7 @@ var broadcast = {
var handlerName = popoverParamParts[0];
popoverParamParts.shift();
var param = popoverParamParts.join(':');
if (typeof broadcast.popoverHandlers[handlerName] != 'undefined') {
if (typeof broadcast.popoverHandlers[handlerName] != 'undefined' && !broadcast.isLoginPage()) {
broadcast.popoverHandlers[handlerName](param);
}
}
@ -164,6 +164,14 @@ var broadcast = {
}
},
/**
* Returns if the current page is the login page
* @return {boolean}
*/
isLoginPage: function() {
return !!$('body#loginPage').length;
},
/**
* propagateAjax -- update hash values then make ajax calls.
* example :
@ -197,7 +205,7 @@ var broadcast = {
// 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') {
if (action != 'goalReport' && action != 'ecommerceReport' && action != 'products' && action != 'sales') {
currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
}
// unset idDashboard if use doesn't display a dashboard
@ -206,15 +214,21 @@ var broadcast = {
currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
}
if (module != 'CustomDimensions') {
currentHashStr = broadcast.updateParamValue('idDimension=', currentHashStr);
}
if (disableHistory) {
var newLocation = window.location.href.split('#')[0] + '#' + currentHashStr;
var newLocation = window.location.href.split('#')[0] + '#?' + currentHashStr;
// window.location.replace changes the current url without pushing it on the browser's history stack
window.location.replace(newLocation);
}
else {
// Let history know about this new Hash and load it.
broadcast.forceReload = true;
$.history.load(currentHashStr);
angular.element(document).injector().invoke(function (historyService) {
historyService.load(currentHashStr);
});
}
},
@ -324,17 +338,17 @@ var broadcast = {
/**
* 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
* @param {String} value The String value that should be passed to the popover
* handler.
*/
propagateNewPopoverParameter: function (handlerName, value) {
@ -370,14 +384,16 @@ var broadcast = {
}
broadcast.forceReload = false;
$.history.load(newHash);
angular.element(document).injector().invoke(function (historyService) {
historyService.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
@ -397,11 +413,31 @@ var broadcast = {
*/
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)
);
// we have to use a $timeout since menu groups are displayed using an angular directive, and on initial
// page load, the dropdown will not be completely rendered at this point. using 2 $timeouts (to push
// the menu activation logic to the end of the event queue twice), seems to work.
angular.element(document).injector().invoke(function ($timeout) {
$timeout(function () {
$timeout(function () {
piwikMenu.activateMenu(
broadcast.getParamValue('module', urlAjax),
broadcast.getParamValue('action', urlAjax),
{
idGoal: broadcast.getParamValue('idGoal', urlAjax),
idDashboard: broadcast.getParamValue('idDashboard', urlAjax),
idDimension: broadcast.getParamValue('idDimension', urlAjax)
}
);
});
});
});
}
if(broadcast.getParamValue('module', urlAjax) == 'API') {
broadcast.lastUrlRequested = null;
$('#content').html("Loading content from the API and displaying it within Piwik is not allowed.");
piwikHelper.hideAjaxLoading();
return false;
}
piwikHelper.hideAjaxError('loadingError');
@ -411,7 +447,24 @@ var broadcast = {
urlAjax = urlAjax.match(/^\?/) ? urlAjax : "?" + urlAjax;
broadcast.lastUrlRequested = urlAjax;
function sectionLoaded(content) {
function sectionLoaded(content, status, request) {
if (request) {
var responseHeader = request.getResponseHeader('Content-Type');
if (responseHeader && 0 <= responseHeader.toLowerCase().indexOf('json')) {
var message = 'JSON cannot be displayed for';
if (this.getParams && this.getParams['module']) {
message += ' module=' + this.getParams['module'];
}
if (this.getParams && this.getParams['action']) {
message += ' action=' + this.getParams['action'];
}
$('#content').text(message);
piwikHelper.hideAjaxLoading();
return;
}
}
// if content is whole HTML document, do not show it, otherwise recursive page load could occur
var htmlDocType = '<!DOCTYPE';
if (content.substring(0, htmlDocType.length) == htmlDocType) {
@ -437,6 +490,9 @@ var broadcast = {
var ajax = new ajaxHelper();
ajax.setUrl(urlAjax);
ajax._getDefaultPostParams = function () {
return {};
};
ajax.setErrorCallback(broadcast.customAjaxHandleError);
ajax.setCallback(sectionLoaded);
ajax.setFormat('html');
@ -454,14 +510,14 @@ var broadcast = {
customAjaxHandleError: function (deferred, status) {
broadcast.lastUrlRequested = null;
piwikHelper.hideAjaxLoading();
// do not display error message if request was aborted
if(status == 'abort') {
return;
}
$('#loadingError').show();
setTimeout( function(){
$('#loadingError').fadeOut('slow');
}, 2000);
},
/**
@ -551,7 +607,6 @@ var broadcast = {
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.
@ -584,7 +639,6 @@ var broadcast = {
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.

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -131,55 +131,55 @@
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')],
_pk_translate('Intl_Day_Min_StandAlone_7'),
_pk_translate('Intl_Day_Min_StandAlone_1'),
_pk_translate('Intl_Day_Min_StandAlone_2'),
_pk_translate('Intl_Day_Min_StandAlone_3'),
_pk_translate('Intl_Day_Min_StandAlone_4'),
_pk_translate('Intl_Day_Min_StandAlone_5'),
_pk_translate('Intl_Day_Min_StandAlone_6')],
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')],
_pk_translate('Intl_Day_Short_StandAlone_7'), // start with sunday
_pk_translate('Intl_Day_Short_StandAlone_1'),
_pk_translate('Intl_Day_Short_StandAlone_2'),
_pk_translate('Intl_Day_Short_StandAlone_3'),
_pk_translate('Intl_Day_Short_StandAlone_4'),
_pk_translate('Intl_Day_Short_StandAlone_5'),
_pk_translate('Intl_Day_Short_StandAlone_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')],
_pk_translate('Intl_Day_Long_StandAlone_7'), // start with sunday
_pk_translate('Intl_Day_Long_StandAlone_1'),
_pk_translate('Intl_Day_Long_StandAlone_2'),
_pk_translate('Intl_Day_Long_StandAlone_3'),
_pk_translate('Intl_Day_Long_StandAlone_4'),
_pk_translate('Intl_Day_Long_StandAlone_5'),
_pk_translate('Intl_Day_Long_StandAlone_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')],
_pk_translate('Intl_Month_Short_StandAlone_1'),
_pk_translate('Intl_Month_Short_StandAlone_2'),
_pk_translate('Intl_Month_Short_StandAlone_3'),
_pk_translate('Intl_Month_Short_StandAlone_4'),
_pk_translate('Intl_Month_Short_StandAlone_5'),
_pk_translate('Intl_Month_Short_StandAlone_6'),
_pk_translate('Intl_Month_Short_StandAlone_7'),
_pk_translate('Intl_Month_Short_StandAlone_8'),
_pk_translate('Intl_Month_Short_StandAlone_9'),
_pk_translate('Intl_Month_Short_StandAlone_10'),
_pk_translate('Intl_Month_Short_StandAlone_11'),
_pk_translate('Intl_Month_Short_StandAlone_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')]
_pk_translate('Intl_Month_Long_StandAlone_1'),
_pk_translate('Intl_Month_Long_StandAlone_2'),
_pk_translate('Intl_Month_Long_StandAlone_3'),
_pk_translate('Intl_Month_Long_StandAlone_4'),
_pk_translate('Intl_Month_Long_StandAlone_5'),
_pk_translate('Intl_Month_Long_StandAlone_6'),
_pk_translate('Intl_Month_Long_StandAlone_7'),
_pk_translate('Intl_Month_Long_StandAlone_8'),
_pk_translate('Intl_Month_Long_StandAlone_9'),
_pk_translate('Intl_Month_Long_StandAlone_10'),
_pk_translate('Intl_Month_Long_StandAlone_11'),
_pk_translate('Intl_Month_Long_StandAlone_12')]
};
};
@ -294,7 +294,6 @@
var togglePeriodPickers = function (showSingle) {
$('#periodString').find('.period-date').toggle(showSingle);
$('#periodString').find('.period-range').toggle(!showSingle);
$('#calendarRangeApply').toggle(!showSingle);
};
//
@ -354,12 +353,25 @@
}
});
// 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) {
var changePeriodWithPageReload = function (periodInput) {
var url = periodInput.val(),
period = broadcast.getValueFromUrl('period', url);
// if clicking on the selected period, change the period but not the date
if (period != 'range' && !reloading) {
// only reload if current period is different from selected
reloading = true;
selectedPeriod = period;
updateDate(piwik.currentDateString);
return true;
}
return false;
};
var changePeriodOnClickIfPeriodChanged = function (periodInput) {
if (reloading) // if a click event resulted in reloading, don't reload again
{
return;
@ -371,29 +383,62 @@
// 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);
if (piwik.period != selectedPeriod) {
return changePeriodWithPageReload(periodInput);
}
return true;
}
return false;
};
$("#otherPeriods").find("label").on('click', function (e) {
$("#otherPeriods").find("label,input").on('dblclick', function (e) {
var id = $(e.target).attr('for');
changePeriodOnClick($('#' + id));
changePeriodOnClickIfPeriodChanged($('#' + id));
});
$("#otherPeriods").find("label,input").on('dblclick', function (e) {
var id = $(e.target).attr('for');
changePeriodOnClickIfPeriodChanged($('#' + id));
});
// Apply date range button will reload the page with the selected range
$('#calendarApply')
.on('click', function () {
var $selectedPeriod = $('#periodMore [name=period]:checked');
if (!$selectedPeriod.is('#period_id_range')) {
changePeriodWithPageReload($selectedPeriod);
return true;
}
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();
// 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))) {
if (changePeriodOnClickIfPeriodChanged($(e.target))) {
return true;
}
@ -444,21 +489,6 @@
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);
@ -488,32 +518,9 @@
$('#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) {
@ -526,7 +533,7 @@
}
$("#calendar" + fromOrTo).datepicker("setDate", newDate);
if (e.keyCode == 13) {
$('#calendarRangeApply').click();
$('#calendarApply').click();
}
});
return true;

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -29,28 +29,28 @@
* 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'.
@ -63,10 +63,10 @@
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.
@ -92,7 +92,7 @@
/**
* 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.
@ -125,7 +125,7 @@
/**
* 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.
*/
@ -151,7 +151,7 @@
/**
* 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.
*/
@ -190,7 +190,7 @@
}
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
@ -219,7 +219,7 @@
return this.transparentColor;
}
};
piwik.ColorManager = new ColorManager();
}(jQuery));

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -27,16 +27,23 @@
module: 'CoreHome',
action: 'checkForUpdates'
}, 'get');
var $titleElement = $(this);
$titleElement.addClass('activityIndicator');
ajaxRequest.setCallback(function (response) {
headerMessage.fadeOut('slow', function () {
response = $(response);
$titleElement.removeClass('activityIndicator');
var newVersionAvailable = response.hasClass('header_alert');
if (newVersionAvailable) {
headerMessage.replaceWith(response);
headerMessage.show();
}
else {
headerMessage.html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion')).show();
headerMessage.find('.title').html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion'));
headerMessage.show();
setTimeout(function () {
headerMessage.fadeOut('slow', function () {
headerMessage.replaceWith(response);
@ -54,7 +61,7 @@
// 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');
$(this).toggleClass('expanded');
}
});
@ -142,19 +149,48 @@
var widgetUniqueId = widgetParams.module + widgetParams.action;
currentWidgetLoading = widgetUniqueId;
widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParams, function (response) {
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(widgetParams, 'get');
ajaxRequest.setCallback(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');
report.css('display', 'inline-block').html($(response));
// scroll to report
piwikHelper.lazyScrollTo(report, 400);
});
ajaxRequest.setErrorCallback(function (deferred, status) {
if (status == 'abort' || !deferred || deferred.status < 400 || deferred.status >= 600) {
return;
}
loading.hide();
var errorMessage = _pk_translate('General_ErrorRequest', ['', '']);
if ($('#loadingError').html()) {
errorMessage = $('#loadingError').html();
}
report.css('display', 'inline-block').html('<div class="dimensionLoadingError">' + errorMessage + '</div>');
});
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
});
});
}(jQuery));
$( document ).ready(function() {
$('.accessibility-skip-to-content').click(function(e){
$('a[name="main"]').attr('tabindex', -1).focus();
$(window).scrollTo($('a[name="main"]'));
});
});

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -22,7 +22,7 @@ var exports = require('piwik/UI'),
* method, and this class instance is stored using the jQuery $.data function
* with the 'uiControlObject' key.
*
* To find a datatable element by report (ie, 'UserSettings.getBrowser'),
* To find a datatable element by report (ie, 'DevicesDetection.getBrowsers'),
* use piwik.DataTable.getDataTableByReport.
*
* To get the dataTable JS instance (an instance of this class) for a
@ -42,8 +42,11 @@ DataTable.initNewDataTables = function () {
$('div.dataTable').each(function () {
if (!$(this).attr('id')) {
var tableType = $(this).attr('data-table-type') || 'DataTable',
klass = require('piwik/UI')[tableType] || require(tableType),
table = new klass(this);
klass = require('piwik/UI')[tableType] || require(tableType);
if (klass && $.isFunction(klass)) {
var table = new klass(this);
}
}
});
};
@ -64,7 +67,7 @@ DataTable.registerFooterIconHandler = function (id, handler) {
/**
* Returns the first datatable div displaying a specific report.
*
* @param {string} report The report, eg, UserSettings.getWideScreen
* @param {string} report The report, eg, UserLanguage.getLanguage
* @return {Element} The datatable div displaying the report, or undefined if
* it cannot be found.
*/
@ -115,6 +118,14 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
self.param.filter_offset = 0;
self.param.filter_sort_column = newColumnToSort;
if (!self.isDashboard()) {
self.notifyWidgetParametersChange(domElem, {
filter_sort_column: newColumnToSort,
filter_sort_order: self.param.filter_sort_order
});
}
self.reloadAjaxDataTable();
},
@ -130,6 +141,10 @@ $.extend(DataTable.prototype, UIControl.prototype, {
return !!$('#dashboardWidgetsArea').length;
},
getReportMetadata: function () {
return JSON.parse(this.$element.attr('data-report-metadata') || '{}');
},
//Reset DataTable filters (used before a reload or view change)
resetAllFilters: function () {
var self = this;
@ -148,7 +163,9 @@ $.extend(DataTable.prototype, UIControl.prototype, {
'columns',
'flat',
'include_aggregate_rows',
'totalRows'
'totalRows',
'pivotBy',
'pivotByColumn'
];
for (var key = 0; key < filters.length; key++) {
@ -200,15 +217,16 @@ $.extend(DataTable.prototype, UIControl.prototype, {
$('#' + self.workingDivId + ' .loadingPiwik').last().css('display', 'block');
}
$('#loadingError').hide();
// when switching to display graphs, reset limit
if (self.param.viewDataTable && self.param.viewDataTable.indexOf('graph') === 0) {
if (self && self.param && self.param.viewDataTable && String(self.param.viewDataTable).indexOf('graph') === 0) {
delete self.param.filter_offset;
delete self.param.filter_limit;
}
var container = $('#' + self.workingDivId + ' .piwik-graph');
var params = {};
for (var key in self.param) {
if (typeof self.param[key] != "undefined" && self.param[key] != '')
@ -226,10 +244,17 @@ $.extend(DataTable.prototype, UIControl.prototype, {
callbackSuccess(response);
}
);
ajaxRequest.setErrorCallback(function (deferred, status) {
if (status == 'abort' || !deferred || deferred.status < 400 || deferred.status >= 600) {
return;
}
$('#' + self.workingDivId + ' .loadingPiwik').last().css('display', 'none');
$('#loadingError').show();
});
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
},
// Function called when the AJAX request is successful
@ -295,12 +320,19 @@ $.extend(DataTable.prototype, UIControl.prototype, {
self.handleColumnHighlighting(domElem);
self.handleExpandFooter(domElem);
self.setFixWidthToMakeEllipsisWork(domElem);
self.handleSummaryRow(domElem);
},
setFixWidthToMakeEllipsisWork: function (domElem) {
var self = this;
function getTableWidth(domElem) {
function isWidgetized()
{
return -1 !== location.search.indexOf('module=Widgetize');
}
function getTableWidth(domElem)
{
var totalWidth = $(domElem).width();
var totalWidthTable = $('table.dataTable', domElem).width(); // fixes tables in dbstats, referrers, ...
@ -315,6 +347,28 @@ $.extend(DataTable.prototype, UIControl.prototype, {
return parseInt(totalWidth, 10);
}
function setMaxTableWidthIfNeeded (domElem, maxTableWidth)
{
var tableWidth = getTableWidth(domElem);
if (tableWidth <= maxTableWidth) {
return;
}
if (isWidgetized() || self.isDashboard()) {
return;
}
$(domElem).width(maxTableWidth);
var parentDataTable = $(domElem).parent('.dataTable');
if (parentDataTable && parentDataTable.length) {
// makes sure dataTableWrapper and DataTable has same size => makes sure maxLabelWidth does not get
// applied in getLabelWidth() since they will have the same size.
parentDataTable.width(maxTableWidth);
}
}
function getLabelWidth(domElem, tableWidth, minLabelWidth, maxLabelWidth)
{
var labelWidth = minLabelWidth;
@ -332,10 +386,15 @@ $.extend(DataTable.prototype, UIControl.prototype, {
labelWidth = tableWidth * 0.5;
}
var isWidgetized = -1 !== location.search.indexOf('module=Widgetize');
var innerWidth = 0;
var innerWrapper = domElem.find('.dataTableWrapper');
if (innerWrapper && innerWrapper.length) {
innerWidth = innerWrapper.width();
}
if (labelWidth > maxLabelWidth
&& !isWidgetized
&& !isWidgetized()
&& innerWidth !== domElem.width()
&& !self.isDashboard()) {
labelWidth = maxLabelWidth; // prevent for instance table in Actions-Pages is not too wide
}
@ -365,16 +424,26 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
function removePaddingFromWidth(domElem, labelWidth) {
var maxPaddingLeft = 0;
var maxPaddingRight = 0;
var firstLabel = $('tbody tr:nth-child(1) td.label', domElem);
var paddingLeft = firstLabel.css('paddingLeft');
paddingLeft = paddingLeft ? parseInt(paddingLeft, 10) : 0;
$('tbody tr td.label', domElem).each(function (i, node) {
$node = $(node);
var paddingRight = firstLabel.css('paddingRight');
paddingRight = paddingRight ? parseInt(paddingRight, 10) : 0;
var paddingLeft = $node.css('paddingLeft');
paddingLeft = paddingLeft ? Math.round(parseFloat(paddingLeft)) : 0;
var paddingRight = $node.css('paddingRight');
paddingRight = paddingRight ? Math.round(parseFloat(paddingLeft)) : 0;
labelWidth = labelWidth - paddingLeft - paddingRight;
if (paddingLeft > maxPaddingLeft) {
maxPaddingLeft = paddingLeft;
}
if (paddingRight > maxPaddingRight) {
maxPaddingRight = paddingRight;
}
});
labelWidth = labelWidth - maxPaddingLeft - maxPaddingRight;
return labelWidth;
}
@ -382,7 +451,8 @@ $.extend(DataTable.prototype, UIControl.prototype, {
var minLabelWidth = 125;
var maxLabelWidth = 440;
var tableWidth = getTableWidth(domElem);
setMaxTableWidthIfNeeded(domElem, 1200);
var tableWidth = getTableWidth(domElem);
var labelColumnMinWidth = getLabelColumnMinWidth(domElem);
var labelColumnWidth = getLabelWidth(domElem, tableWidth, 125, 440);
@ -400,7 +470,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
},
handleLimit: function (domElem) {
var tableRowLimits = [5, 10, 25, 50, 100, 250, 500],
var tableRowLimits = piwik.config.datatable_row_limits,
evolutionLimits =
{
day: [30, 60, 90, 180, 365, 500],
@ -436,13 +506,20 @@ $.extend(DataTable.prototype, UIControl.prototype, {
};
}
function getFilterLimitAsString(limit) {
if (limit == '-1') {
return _pk_translate('General_All').toLowerCase();
}
return limit;
}
// setup limit control
$('.limitSelection', domElem).append('<div><span>' + self.param[limitParamName] + '</span></div><ul></ul>');
$('.limitSelection', domElem).append('<div><span value="'+ self.param[limitParamName] +'">' + getFilterLimitAsString(self.param[limitParamName]) + '</span></div><ul></ul>');
if (self.props.show_limit_control) {
$('.limitSelection ul', domElem).hide();
for (var i = 0; i < numbers.length; i++) {
$('.limitSelection ul', domElem).append('<li value="' + numbers[i] + '"><span>' + numbers[i] + '</span></li>');
$('.limitSelection ul', domElem).append('<li value="' + numbers[i] + '"><span>' + getFilterLimitAsString(numbers[i]) + '</span></li>');
}
$('.limitSelection ul li:last', domElem).addClass('last');
@ -465,12 +542,12 @@ $.extend(DataTable.prototype, UIControl.prototype, {
$('.limitSelection', domElem).is('.visible') ? hide() : show();
});
$('.limitSelection ul li', domElem).on('click', function (event) {
var limit = parseInt($(event.target).text());
var limit = parseInt($(event.target).closest('li').attr('value'));
hide();
if (limit != self.param[limitParamName]) {
setLimitValue(self.param, limit);
$('.limitSelection>div>span', domElem).text(limit);
$('.limitSelection>div>span', domElem).text( getFilterLimitAsString(limit)).attr('value', limit);
self.reloadAjaxDataTable();
var data = {};
@ -494,24 +571,6 @@ $.extend(DataTable.prototype, UIControl.prototype, {
handleSort: function (domElem) {
var self = this;
function getSortImageSrc() {
var imageSortSrc = false;
if (currentIsSubDataTable) {
if (self.param.filter_sort_order == 'asc') {
imageSortSrc = 'plugins/Zeitgeist/images/sort_subtable_asc.png';
} else {
imageSortSrc = 'plugins/Zeitgeist/images/sort_subtable_desc.png';
}
} else {
if (self.param.filter_sort_order == 'asc') {
imageSortSrc = 'plugins/Zeitgeist/images/sortasc.png';
} else {
imageSortSrc = 'plugins/Zeitgeist/images/sortdesc.png';
}
}
return imageSortSrc;
}
if (self.props.enable_sort) {
$('.sortable', domElem).off('click.dataTableSort').on('click.dataTableSort',
function () {
@ -524,18 +583,26 @@ $.extend(DataTable.prototype, UIControl.prototype, {
if (self.param.filter_sort_column) {
// are we in a subdatatable?
var currentIsSubDataTable = $(domElem).parent().hasClass('cellSubDataTable');
var imageSortSrc = getSortImageSrc();
var imageSortClassType = currentIsSubDataTable ? 'sortSubtable' : ''
var imageSortWidth = 16;
var imageSortHeight = 16;
var sortOrder = self.param.filter_sort_order || 'desc';
var ImageSortClass = sortOrder.charAt(0).toUpperCase() + sortOrder.substr(1);
// we change the style of the column currently used as sort column
// adding an image and the class columnSorted to the TD
$("th#" + self.param.filter_sort_column + ' #thDIV', domElem).parent()
.addClass('columnSorted')
.prepend('<div class="sortIconContainer sortIconContainer' + ImageSortClass + '"><img class="sortIcon" width="' + imageSortWidth + '" height="' + imageSortHeight + '" src="' + imageSortSrc + '" /></div>');
var head = $('th', domElem).filter(function () {
return $(this).attr('id') == self.param.filter_sort_column;
}).addClass('columnSorted');
var sortIconHtml = '<span class="sortIcon ' + sortOrder + ' ' + imageSortClassType +'" width="' + imageSortWidth + '" height="' + imageSortHeight + '" />';
var div = head.find('.thDIV');
if (head.hasClass('first') || head.attr('id') == 'label') {
div.append(sortIconHtml);
} else {
div.prepend(sortIconHtml);
}
}
},
@ -557,6 +624,14 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
currentPattern = piwikHelper.htmlDecode(currentPattern);
var patternsToReplace = [{from: '?', to: '\\?'}, {from: '+', to: '\\+'}, {from: '*', to: '\\*'}]
$.each(patternsToReplace, function (index, pattern) {
if (0 === currentPattern.indexOf(pattern.to)) {
currentPattern = pattern.from + currentPattern.substr(2);
}
});
$('.dataTableSearchPattern', domElem)
.css({display: 'block'})
.each(function () {
@ -577,6 +652,12 @@ $.extend(DataTable.prototype, UIControl.prototype, {
var keyword = $(this).siblings('.searchInput').val();
self.param.filter_offset = 0;
$.each(patternsToReplace, function (index, pattern) {
if (0 === keyword.indexOf(pattern.from)) {
keyword = pattern.to + keyword.substr(1);
}
});
if (self.param.search_recursive) {
self.param.filter_column_recursive = 'label';
self.param.filter_pattern_recursive = keyword;
@ -642,46 +723,47 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
);
var $next = $('.dataTableNext', domElem);
// Display the next link if the total Rows is greater than the current end row
$('.dataTableNext', domElem)
.each(function () {
var offsetEnd = Number(self.param.filter_offset)
+ Number(self.param.filter_limit);
var totalRows = Number(self.param.totalRows);
if (self.param.keep_summary_row == 1) --totalRows;
if (offsetEnd < totalRows) {
$(this).css('display', 'inline');
}
})
// bind the click event to trigger the ajax request with the new offset
.click(function () {
$(this).off('click');
self.param.filter_offset = Number(self.param.filter_offset) + Number(self.param.filter_limit);
self.reloadAjaxDataTable();
})
;
$next.each(function () {
var offsetEnd = Number(self.param.filter_offset)
+ Number(self.param.filter_limit);
var totalRows = Number(self.param.totalRows);
if (self.param.keep_summary_row == 1) --totalRows;
if (offsetEnd < totalRows) {
$(this).css('display', 'inline');
}
});
// bind the click event to trigger the ajax request with the new offset
$next.off('click');
$next.click(function () {
$(this).off('click');
self.param.filter_offset = Number(self.param.filter_offset) + Number(self.param.filter_limit);
self.reloadAjaxDataTable();
});
var $prev = $('.dataTablePrevious', domElem);
// Display the previous link if the current offset is not zero
$('.dataTablePrevious', domElem)
.each(function () {
var offset = 1 + Number(self.param.filter_offset);
if (offset != 1) {
$(this).css('display', 'inline');
}
$prev.each(function () {
var offset = 1 + Number(self.param.filter_offset);
if (offset != 1) {
$(this).css('display', 'inline');
}
)
// bind the click event to trigger the ajax request with the new offset
// take care of the negative offset, we setup 0
.click(
function () {
$(this).off('click');
var offset = Number(self.param.filter_offset) - Number(self.param.filter_limit);
if (offset < 0) { offset = 0; }
self.param.filter_offset = offset;
self.param.previous = 1;
self.reloadAjaxDataTable();
}
);
});
// bind the click event to trigger the ajax request with the new offset
// take care of the negative offset, we setup 0
$prev.off('click');
$prev.click(function () {
$(this).off('click');
var offset = Number(self.param.filter_offset) - Number(self.param.filter_limit);
if (offset < 0) { offset = 0; }
self.param.filter_offset = offset;
self.param.previous = 1;
self.reloadAjaxDataTable();
});
},
handleEvolutionAnnotations: function (domElem) {
@ -706,9 +788,6 @@ $.extend(DataTable.prototype, UIControl.prototype, {
;
var annotationsCss = {left: 6}; // padding-left of .jqplot-graph element (in _dataTableViz_jqplotGraph.tpl)
if (!self.isDashboard() && !self.isWithinDialog(domElem)) {
annotationsCss['top'] = -datatableFeatures.height() - annotationAxisHeight + noteSize / 2;
}
// set position of evolution annotation icons
annotations.css(annotationsCss);
@ -716,11 +795,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
piwik.annotations.placeEvolutionIcons(annotations, domElem);
// add new section under axis
if (self.isDashboard() || self.isWithinDialog(domElem)) {
annotations.insertAfter($('.datatableRelatedReports', domElem));
} else {
datatableFeatures.append(annotations);
}
annotations.insertAfter($('.datatableRelatedReports', domElem));
// reposition annotation icons every time the graph is resized
$('.piwik-graph', domElem).on('resizeGraph', function () {
@ -971,14 +1046,13 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
});
$('.exportToFormatItems a', domElem)
// prevent click jacking attacks by dynamically adding the token auth when the link is clicked
.click(function () {
$(this).attr('href', function () {
var url = $(this).attr('href') + '&token_auth=' + piwik.token_auth;
var limit = $('.limitSelection>div>span', domElem).text();
var limit = $('.limitSelection>div>span', domElem).attr('value');
var defaultLimit = $(this).attr('filter_limit');
if (!limit || 'undefined' === limit || defaultLimit == -1) {
limit = defaultLimit;
@ -991,9 +1065,18 @@ $.extend(DataTable.prototype, UIControl.prototype, {
.attr('href', function () {
var format = $(this).attr('format');
var method = $(this).attr('methodToCall');
var params = $(this).attr('requestParams');
if (params) {
params = JSON.parse(params)
} else {
params = {};
}
var segment = self.param.segment;
var label = self.param.label;
var idGoal = self.param.idGoal;
var idDimension = self.param.idDimension;
var param_date = self.param.date;
var date = $(this).attr('date');
if (typeof date != 'undefined') {
@ -1015,6 +1098,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
&& self.param.viewDataTable == "graphEvolution") {
period = 'day';
}
var str = 'index.php?module=API'
+ '&method=' + method
+ '&format=' + format
@ -1024,10 +1108,15 @@ $.extend(DataTable.prototype, UIControl.prototype, {
+ ( typeof self.param.filter_pattern != "undefined" ? '&filter_pattern=' + self.param.filter_pattern : '')
+ ( typeof self.param.filter_pattern_recursive != "undefined" ? '&filter_pattern_recursive=' + self.param.filter_pattern_recursive : '');
if ($.isPlainObject(params)) {
$.each(params, function (index, param) {
str += '&' + index + '=' + encodeURIComponent(param);
});
}
if (typeof self.param.flat != "undefined") {
str += '&flat=' + (self.param.flat == 0 ? '0' : '1');
if (typeof self.param.include_aggregate_rows != "undefined" && self.param.include_aggregate_rows) {
if (typeof self.param.include_aggregate_rows != "undefined" && self.param.include_aggregate_rows == '1') {
str += '&include_aggregate_rows=1';
}
if (!self.param.flat
@ -1039,6 +1128,12 @@ $.extend(DataTable.prototype, UIControl.prototype, {
} else {
str += '&expanded=1';
}
if (self.param.pivotBy) {
str += '&pivotBy=' + self.param.pivotBy + '&pivotByColumnLimit=20';
if (self.props.pivot_by_column) {
str += '&pivotByColumn=' + self.props.pivot_by_column;
}
}
if (format == 'CSV' || format == 'TSV' || format == 'RSS') {
str += '&translateColumnNames=1&language=' + piwik.language;
}
@ -1050,6 +1145,11 @@ $.extend(DataTable.prototype, UIControl.prototype, {
&& idGoal != '-1') {
str += '&idGoal=' + idGoal;
}
// Export Dimension specific reports
if (typeof idDimension != 'undefined'
&& idDimension != '-1') {
str += '&idDimension=' + idDimension;
}
if (label) {
label = label.split(',');
@ -1083,6 +1183,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
// no manipulation when loading subtables
return;
}
if ((typeof self.numberOfSubtables == 'undefined' || self.numberOfSubtables == 0)
&& (typeof self.param.flat == 'undefined' || self.param.flat != 1)) {
// if there are no subtables, remove the flatten action
@ -1120,28 +1221,36 @@ $.extend(DataTable.prototype, UIControl.prototype, {
};
$('div.tableConfiguration', domElem).hover(open, close);
var generateClickCallback = function (paramName, callbackAfterToggle) {
var generateClickCallback = function (paramName, callbackAfterToggle, setParamCallback) {
return function () {
close();
self.param[paramName] = 1 - self.param[paramName];
if (setParamCallback) {
var data = setParamCallback();
} else {
self.param[paramName] = (1 - self.param[paramName]) + '';
var data = {};
}
self.param.filter_offset = 0;
delete self.param.totalRows;
if (callbackAfterToggle) callbackAfterToggle();
self.reloadAjaxDataTable(true, callbackSuccess);
var data = {};
data[paramName] = self.param[paramName];
self.notifyWidgetParametersChange(domElem, data);
};
};
var getText = function (text, addDefault) {
text = _pk_translate(text);
if (text.indexOf('%s') > 0) {
text = text.replace('%s', '<br /><span class="action">&raquo; ');
var getText = function (text, addDefault, replacement) {
if (/(%(.\$)?s+)/g.test(_pk_translate(text))) {
var values = ['<br /><span class="action">&raquo; '];
if(replacement) {
values.push(replacement);
}
text = _pk_translate(text, values);
if (addDefault) text += ' (' + _pk_translate('CoreHome_Default') + ')';
text += '</span>';
return text;
}
return text;
return _pk_translate(text);
};
var setText = function (el, paramName, textA, textB) {
@ -1196,6 +1305,37 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
}));
// handle pivot by
$('.dataTablePivotBySubtable', domElem)
.each(function () {
if (self.param.pivotBy
&& self.param.pivotBy != '0'
) {
$(this).html(getText('CoreHome_UndoPivotBySubtable', true));
iconHighlighted = true;
} else {
var optionLabelText = getText('CoreHome_PivotBySubtable', false, self.props.pivot_dimension_name);
$(this).html(optionLabelText);
}
})
.click(generateClickCallback('pivotBy', null, function () {
if (self.param.pivotBy
&& self.param.pivotBy != '0'
) {
self.param.pivotBy = '0'; // set to '0' so it will be sent in the request and override the saved param
self.param.pivotByColumn = '0';
} else {
self.param.pivotBy = self.props.pivot_by_dimension;
if (self.props.pivot_by_column) {
self.param.pivotByColumn = self.props.pivot_by_column;
}
}
// remove sorting so it will default to first column in table
self.param.filter_sort_column = '';
return {filter_sort_column: ''};
}));
// handle highlighted icon
if (iconHighlighted) {
icon.addClass('highlighted');
@ -1217,7 +1357,10 @@ $.extend(DataTable.prototype, UIControl.prototype, {
var width = 0;
ul.find('li').each(function () {
width = Math.max(width, $(this).width());
}).width(width);
});
if (width > 0) {
ul.find('li').width(width);
}
close();
}, 400);
}
@ -1227,7 +1370,26 @@ $.extend(DataTable.prototype, UIControl.prototype, {
notifyWidgetParametersChange: function (domWidget, parameters) {
var widget = $(domWidget).closest('[widgetId]');
// trigger setParameters event on base element
widget.trigger('setParameters', parameters);
if (widget && widget.length) {
widget.trigger('setParameters', parameters);
} else {
var reportId = $(domWidget).closest('[data-report]').attr('data-report');
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
module: 'CoreHome',
action: 'saveViewDataTableParameters',
report_id: reportId
}, 'get');
ajaxRequest.addParams({
parameters: JSON.stringify(parameters)
}, 'post');
ajaxRequest.setCallback(function () {});
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
}
},
tooltip: function (domElement) {
@ -1269,29 +1431,35 @@ $.extend(DataTable.prototype, UIControl.prototype, {
// Add some styles on the cells even/odd
// label (first column of a data row) or not
$("th:first-child", domElem).addClass('label');
$("td:first-child:odd", domElem).addClass('label labeleven');
$("td:first-child:even", domElem).addClass('label labelodd');
$("tr:odd td", domElem).slice(1).addClass('column columnodd');
$("tr:even td", domElem).slice(1).addClass('column columneven');
$("td:first-child", domElem).addClass('label');
$("tr td", domElem).addClass('column');
},
handleExpandFooter: function (domElem) {
if (!this.isDashboard() && !this.isWithinDialog(domElem)) {
return;
}
var footerIcons = $('.dataTableFooterIcons', domElem);
if (!footerIcons.length) {
return;
}
if (this.isWithinDialog(domElem)) {
$('.dataTableFeatures', domElem).addClass('expanded');
}
var self = this;
function toggleFooter()
function toggleFooter(event)
{
if (self.isWithinDialog(domElem)) {
return;
}
var icons = $('.dataTableFooterIcons', domElem);
$('.dataTableFeatures', domElem).toggleClass('expanded');
if (event && event.doNotNotifyChange) {
return;
}
self.notifyWidgetParametersChange(domElem, {
isFooterExpandedInDashboard: icons.is(':visible')
});
@ -1306,9 +1474,10 @@ $.extend(DataTable.prototype, UIControl.prototype, {
$('.expandDataTableFooterDrawer', domElem).after(footerIcons);
var controls = $('.controls', domElem);
if (controls.length) {
$('.foldDataTableFooterDrawer', domElem).after(controls);
var controls = $('.controls', domElem);
var footerWrap = $('.dataTableFooterWrap', domElem);
if (controls.length && footerWrap.length) {
$('.dataTableFooterWrap', domElem).before(controls);
}
var loadingPiwikBelow = $('.loadingPiwikBelow', domElem);
@ -1317,10 +1486,12 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
if (this.param.isFooterExpandedInDashboard) {
toggleFooter();
toggleFooter({doNotNotifyChange: true});
}
$('.foldDataTableFooterDrawer, .expandDataTableFooterDrawer', domElem).on('click', toggleFooter);
var $nodes = $('.foldDataTableFooterDrawer, .expandDataTableFooterDrawer', domElem);
$nodes.off('click');
$nodes.on('click', toggleFooter);
},
handleColumnHighlighting: function (domElem) {
@ -1332,19 +1503,24 @@ $.extend(DataTable.prototype, UIControl.prototype, {
// higlight all columns on hover
$('td', domElem).hover(
function() {
if ($(this).hasClass('label')) {
return;
}
var table = $(this).closest('table');
var nthChild = $(this).parent('tr').children().index($(this)) + 1;
var rows = $('> tbody > tr', table);
if (!maxWidth[nthChild]) {
maxWidth[nthChild] = 0;
rows.find("td:nth-child(" + (nthChild) + ") .column .value").each(function (index, element) {
var width = $(element).width();
rows.find("td:nth-child(" + (nthChild) + ").column .value").each(function (index, element) {
var width = $(element).width();
if (width > maxWidth[nthChild]) {
maxWidth[nthChild] = width;
}
});
rows.find("td:nth-child(" + (nthChild) + ") .column .value").each(function (index, element) {
rows.find("td:nth-child(" + (nthChild) + ").column .value").each(function (index, element) {
$(element).css({width: maxWidth[nthChild], display: 'inline-block'});
});
}
@ -1399,7 +1575,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
'<tr>' +
'<td colspan="' + numberOfColumns + '" class="cellSubDataTable">' +
'<div id="' + divIdToReplaceWithSubTable + '">' +
'<span class="loadingPiwik" style="display:inline"><img src="plugins/Zeitgeist/images/loading-blue.gif" />' + _pk_translate('General_Loading') + '</span>' +
'<span class="loadingPiwik" style="display:inline"><img src="plugins/Morpheus/images/loading-blue.gif" />' + _pk_translate('General_Loading') + '</span>' +
'</div>' +
'</td>' +
'</tr>'
@ -1435,6 +1611,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
}
$(this).next().toggle();
$(this).toggleClass('expanded');
self.repositionRowActions($(this));
}
).size();
@ -1540,6 +1717,9 @@ $.extend(DataTable.prototype, UIControl.prototype, {
self.param[key] = decodeURIComponent(newParams[key]);
}
delete self.param.pivotBy;
delete self.param.pivotByColumn;
// do ajax request
self.reloadAjaxDataTable(true, function (newReport) {
var newDomElem = self.dataTableLoaded(newReport, self.workingDivId);
@ -1576,6 +1756,22 @@ $.extend(DataTable.prototype, UIControl.prototype, {
});
},
handleSummaryRow: function (domElem) {
var details = _pk_translate('General_LearnMore', [' (<a href="http://piwik.org/faq/how-to/faq_54/" rel="noreferrer" target="_blank">', '</a>)']);
domElem.find('tr.summaryRow').each(function () {
var labelSpan = $(this).find('.label .value');
var defaultLabel = labelSpan.text();
$(this).hover(function() {
labelSpan.html(defaultLabel + details);
},
function() {
labelSpan.text(defaultLabel);
});
});
},
// also used in action data table
doHandleRowActions: function (trs) {
var self = this;
@ -1743,11 +1939,26 @@ var switchToHtmlTable = function (dataTable, viewDataTable) {
dataTable.notifyWidgetParametersChange(dataTable.$element, {viewDataTable: viewDataTable});
};
var switchToEcommerceView = function (dataTable, viewDataTable) {
if (viewDataTable == 'ecommerceOrder') {
dataTable.param.abandonedCarts = '0';
} else {
dataTable.param.abandonedCarts = '1';
}
var viewDataTable = dataTable.param.viewDataTable;
if (viewDataTable == 'ecommerceOrder' || viewDataTable == 'ecommerceAbandonedCart') {
viewDataTable = 'table';
}
switchToHtmlTable(dataTable, viewDataTable);
};
DataTable.registerFooterIconHandler('table', switchToHtmlTable);
DataTable.registerFooterIconHandler('tableAllColumns', switchToHtmlTable);
DataTable.registerFooterIconHandler('tableGoals', switchToHtmlTable);
DataTable.registerFooterIconHandler('ecommerceOrder', switchToHtmlTable);
DataTable.registerFooterIconHandler('ecommerceAbandonedCart', switchToHtmlTable);
DataTable.registerFooterIconHandler('ecommerceOrder', switchToEcommerceView);
DataTable.registerFooterIconHandler('ecommerceAbandonedCart', switchToEcommerceView);
// generic function to handle switch to graph visualizations
DataTable.switchToGraph = function (dataTable, viewDataTable) {

View file

@ -59,8 +59,8 @@ DataTable_RowActions_Registry.register({
name: 'RowEvolution',
dataTableIcon: 'plugins/Zeitgeist/images/row_evolution.png',
dataTableIconHover: 'plugins/Zeitgeist/images/row_evolution_hover.png',
dataTableIcon: 'plugins/Morpheus/images/row_evolution.png',
dataTableIconHover: 'plugins/Morpheus/images/row_evolution_hover.png',
order: 50,
@ -110,7 +110,6 @@ DataTable_RowActions_Registry.register({
});
/**
* DataTable Row Actions
*
@ -125,7 +124,6 @@ DataTable_RowActions_Registry.register({
* The two template methods are performAction and doOpenPopover
*/
//
// BASE CLASS
//
@ -161,11 +159,10 @@ DataTable_RowAction.prototype.initTr = function (tr) {
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();
label += separator + subTableLabel;
}
// handle sub tables in nested reports: forward to parent
@ -188,7 +185,7 @@ DataTable_RowAction.prototype.trigger = function (tr, e, subTableLabel) {
var findLevel = 'level' + (level - 1);
var ptr = tr;
while ((ptr = ptr.prev()).size() > 0) {
if (!ptr.hasClass(findLevel)) {
if (!ptr.hasClass(findLevel) || ptr.hasClass('nodata')) {
continue;
}
ptr.trigger(this.trEventName, {
@ -214,8 +211,14 @@ DataTable_RowAction.prototype.getLabelFromTr = function (tr) {
value = label.text();
}
value = value.trim();
value = encodeURIComponent(value);
return encodeURIComponent(value);
// if tr is a terminal node, we use the @ operator to distinguish it from branch nodes w/ the same name
if (!tr.hasClass('subDataTable')) {
value = '@' + value;
}
return value;
};
/**
@ -244,7 +247,6 @@ DataTable_RowAction.prototype.performAction = function (label, tr, e) {
DataTable_RowAction.prototype.doOpenPopover = function (parameter) {
};
//
// ROW EVOLUTION
//
@ -272,16 +274,22 @@ DataTable_RowActions_RowEvolution.prototype.performAction = function (label, tr,
return;
}
// check whether we have rows marked for multi row evolution
var isMultiRowEvolution = '0';
this.addMultiEvolutionRow(label);
// check whether we have rows marked for multi row evolution
var extraParams = {};
if (this.multiEvolutionRows.length > 1) {
isMultiRowEvolution = '1';
extraParams.action = 'getMultiRowEvolutionPopover';
label = this.multiEvolutionRows.join(',');
}
// check if abandonedCarts is in the dataTable params and if so, propagate to row evolution request
if (this.dataTable.param.abandonedCarts !== undefined) {
extraParams['abandonedCarts'] = this.dataTable.param.abandonedCarts;
}
var apiMethod = this.dataTable.param.module + '.' + this.dataTable.param.action;
this.openPopover(apiMethod, isMultiRowEvolution, label);
this.openPopover(apiMethod, extraParams, label);
};
DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function (label) {
@ -290,27 +298,37 @@ DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function (lab
}
};
DataTable_RowActions_RowEvolution.prototype.openPopover = function (apiMethod, multiRowEvolutionParam, label) {
var urlParam = apiMethod + ':' + multiRowEvolutionParam + ':' + label;
DataTable_RowActions_RowEvolution.prototype.openPopover = function (apiMethod, extraParams, label) {
var urlParam = apiMethod + ':' + encodeURIComponent(JSON.stringify(extraParams)) + ':' + 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 apiMethod = urlParamParts.shift();
var multiRowEvolutionParam = urlParamParts[0];
urlParamParts.shift();
var extraParamsString = urlParamParts.shift(),
extraParams = {}; // 0/1 or "0"/"1"
try {
extraParams = JSON.parse(decodeURIComponent(extraParamsString));
} catch (e) {
// assume the parameter is an int/string describing whether to use multi row evolution
if (extraParamsString == '1') {
extraParams.action = 'getMultiRowEvolutionPopover';
} else if (extraParamsString != '0') {
extraParams.action = 'getMultiRowEvolutionPopover';
extraParams.column = extraParamsString;
}
}
var label = urlParamParts.join(':');
this.showRowEvolution(apiMethod, label, multiRowEvolutionParam);
this.showRowEvolution(apiMethod, label, extraParams);
};
/** Open the row evolution popover */
DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMethod, label, multiRowEvolutionParam) {
DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMethod, label, extraParams) {
var self = this;
@ -325,17 +343,6 @@ DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMeth
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);
@ -353,7 +360,7 @@ DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMeth
if (self.dataTable !== null) {
// remember label for multi row evolution
box.find('a.rowevolution-startmulti').click(function () {
box.find('.rowevolution-startmulti').click(function () {
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
Piwik_Popover.close();
return false;
@ -369,15 +376,29 @@ DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMeth
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);
var extraParams = {action: 'getMultiRowEvolutionPopover', column: metric};
self.openPopover(apiMethod, extraParams, label);
return true;
});
};
requestParams.module = 'CoreHome';
requestParams.action = action;
requestParams.action = 'getRowEvolutionPopover';
requestParams.colors = JSON.stringify(piwik.getSparklineColors());
var idDimension;
if (broadcast.getValueFromUrl('module') === 'Widgetize') {
idDimension = broadcast.getValueFromUrl('idDimension');
} else {
idDimension = broadcast.getValueFromHash('idDimension');
}
if (idDimension) {
requestParams.idDimension = parseInt(idDimension, 10);
}
$.extend(requestParams, extraParams);
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(requestParams, 'get');
ajaxRequest.setCallback(callback);

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -24,10 +24,10 @@
// 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'));
$('.slider-donate-amount', slider).text('$' + donateAmounts[pos] + '/' + _pk_translate('Intl_Year_Short'));
// set the right smiley face
$('.slider-smiley-face').attr('src', 'plugins/Zeitgeist/images/smileyprog_' + pos + '.png');
$('.slider-smiley-face').attr('src', 'plugins/Morpheus/images/smileyprog_' + pos + '.png');
// set the hidden option input for paypal
var option = Math.max(1, pos);

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -16,50 +16,78 @@ menu.prototype =
{
resetTimer: null,
adaptSubMenuHeight: function() {
var subNavHeight = $('.sfHover > ul').outerHeight();
$('.nav_sep').height(subNavHeight);
onItemClick: function (e) {
if (e.which === 2) {
return;
}
$('#secondNavBar').removeClass('open fadeInLeft');
var $link = $(this);
var href = $link.attr('href');
if (!$('#content.admin').size()) {
if (!href && $link.parent().is('.menuTab')) {
var $li = $link.parents('li').first();
if ($li.hasClass('sfActive')) {
$li.removeClass('sfActive');
} else {
$li.siblings().removeClass('sfActive');
$li.addClass('sfActive');
}
var $children = $li.find('ul li > .item');
if ($children.length === 1) {
$children.first().click();
}
} else if (href) {
$('#secondNavBar').trigger('piwikSwitchPage', this);
broadcast.propagateAjax(href.substr(1));
}
return false;
}
return !!href;
},
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;
isAdmin: function () {
return !!$('#content.admin').size();
},
init: function () {
this.menuNode = $('.Menu--dashboard');
this.menuNode.find("li:has(ul)").hover(this.overMainLI, this.outMainLI);
this.menuNode = $('#secondNavBar');
// 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 $this = $(this);
var link = $this.find('a');
var main_menu = $this.parent().hasClass('navbar') ? true : false;
if (!link) {
return;
}
var href = link.attr('href');
if (!href) {
return;
}
var url = 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});
$this.attr({id: module});
}
// if there's a idGoal or idDashboard, use this in the ID
else if (moduleId != '') {
@ -70,27 +98,65 @@ menu.prototype =
}
});
menu.prototype.adaptSubMenuHeight();
this.menuNode.find('a.item').click(this.onItemClick);
var self = this;
$('#header .toggle-second-menu').click(function () {
self.menuNode.toggleClass('open fadeInLeft');
});
},
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');
}
activateMenu: function (module, action, params) {
params = params || {};
params.module = module;
params.action = action;
mainLi.addClass('sfActive').addClass('sfHover');
this.menuNode.find('li').removeClass('sfActive');
$li.addClass('sfHover');
var isAdmin = this.isAdmin();
var $activeLink = this.menuNode.find('a').filter(function () {
var url = $(this).attr('href');
if (!url) {
return false;
}
var found = false;
for (var key in params) {
if (!params.hasOwnProperty(key)
|| !params[key]
) {
continue;
}
var actual;
if (isAdmin) {
actual = broadcast.getValueFromUrl(key, url);
} else {
actual = broadcast.getValueFromHash(key, url);
}
if (actual != params[key]) {
return false;
}
found = true;
// at least one param must match. Otherwise all menu items might be highlighted if params[key] = null;
}
return found;
});
$activeLink.closest('li').addClass('sfActive');
$activeLink.closest('li.menuTab').addClass('sfActive');
},
// 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')) {
if ((module == 'Goals' || module == 'Ecommerce') && 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') {
@ -104,7 +170,7 @@ menu.prototype =
loadFirstSection: function () {
if (broadcast.isHashExists() == false) {
$('li:first a:first', this.menuNode).click().addClass('sfHover').addClass('sfActive');
$('.navbar li:first ul a:first', this.menuNode).click().addClass('sfActive');
}
}
};
};

View file

@ -1,19 +1,40 @@
$(function () {
var isPageHasMenu = $('.Menu--dashboard').size();
var isPageHasMenu = $('#secondNavBar').size();
var isPageIsAdmin = $('#content.admin').size();
if (isPageHasMenu) {
piwikMenu = new menu();
piwikMenu.init();
piwikMenu.loadFirstSection();
if (isPageIsAdmin) {
piwikMenu.activateMenu(broadcast.getValueFromUrl('module'), broadcast.getValueFromUrl('action'), '');
} else {
piwikMenu.loadFirstSection();
}
} else if (!isPageIsAdmin) {
// eg multisites
initTopControls();
}
if(isPageIsAdmin) {
// don't use broadcast in admin pages
if (isPageIsAdmin) {
// don't use broadcast in admin page
initTopControls();
return;
}
if(isPageHasMenu) {
if (isPageHasMenu) {
broadcast.init();
} else {
broadcast.init(true);
}
$('.menuTab').keydown(function(e){
if(e.which==27){
var isFocusedInSubmenu = !$(':focus').parent().hasClass('menuTab')
if(isFocusedInSubmenu){
$(this).closest('.menuTab').attr('tabindex', -1).focus();
$('body').scrollTop(0);
}
$('.sfActive').removeClass('sfActive');
}
})
});

View file

@ -1,5 +1,5 @@
/**
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -50,26 +50,18 @@
options = {};
}
if ('persistent' == options.type) {
// otherwise it is never possible to dismiss the notification
options.noclear = false;
}
var template = generateNotificationHtmlMarkup(options, message);
this.$node = placeNotification(template, options);
};
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);
}
/**
* Removes a previously shown notification having the given notification id.
*
*
* @param {string} notificationId The id of a notification that was previously registered.
*/
Notification.prototype.remove = function (notificationId) {
$('[piwik-notification][notification-id=' + notificationId + ']').remove();
};
Notification.prototype.scrollToNotification = function () {
@ -80,69 +72,37 @@
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);
var attributeMapping = {
id: 'notification-id',
title: 'notification-title',
context: 'context',
type: 'type',
noclear: 'noclear'
},
html = '<div piwik-notification';
if (!options.noclear) {
template += buildClearButton();
for (var key in attributeMapping) {
if (attributeMapping.hasOwnProperty(key)
&& options[key]
) {
html += ' ' + attributeMapping[key] + '="' + options[key].toString().replace(/"/g, "&quot;") + '"';
}
}
if (options.title) {
template += buildTitle(options);
}
html += '>' + message + '</div>';
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">&times;</button>';
}
function buildTitle(options) {
return '<strong>' + options.title + '</strong> ';
return html;
}
function placeNotification(template, options) {
var $notificationNode = $(template);
// compile the template in angular
angular.element(document).injector().invoke(function ($compile, $rootScope) {
$compile($notificationNode)($rootScope.$new(true));
});
if (options.style) {
$notificationNode.css(options.style);
}
@ -159,40 +119,4 @@
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);

View file

@ -1,5 +1,5 @@
/**
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later

View file

@ -0,0 +1,143 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Number Formatter for formatting numbers, percent and currencies values
*
* @type {object}
*/
var NumberFormatter = (function () {
var minimumFractionDigits = 0;
var maximumFractionDigits = 2;
/**
* Formats the given numeric value with the given pattern
*
* @param value
* @param pattern
* @returns {string}
*/
function format(value, pattern) {
if (!$.isNumeric(value)) {
return value;
}
pattern = pattern || piwik.numbers.patternNumber;
var patterns = pattern.split(';');
if (patterns.length == 1) {
// No explicit negative pattern was provided, construct it.
patterns.push('-' + patterns[0])
}
// Ensure that the value is positive and has the right number of digits.
var negative = value < 0;
pattern = negative ? patterns[1] : patterns[0];
var usesGrouping = (pattern.indexOf(',') != -1);
// if pattern has number groups, parse them.
if (usesGrouping) {
var primaryGroupMatches = pattern.match(/#+0/);
var primaryGroupSize = primaryGroupMatches[0].length;
var secondaryGroupSize = primaryGroupMatches[0].length;
var numberGroups = pattern.split(',');
// check for distinct secondary group size.
if (numberGroups.length > 2) {
secondaryGroupSize = numberGroups[1].length;
}
}
var signMultiplier = negative ? '-1' : '1';
value = value * signMultiplier;
// Split the number into major and minor digits.
var valueParts = value.toString().split('.');
var majorDigits = valueParts[0];
// Account for maximumFractionDigits = 0, where the number won't
// have a decimal point, and $valueParts[1] won't be set.
minorDigits = valueParts[1] || '';
if (usesGrouping) {
// Reverse the major digits, since they are grouped from the right.
majorDigits = majorDigits.split('').reverse();
// Group the major digits.
var groups = [];
groups.push(majorDigits.splice(0, primaryGroupSize).reverse().join(''));
while (majorDigits.length) {
groups.push(majorDigits.splice(0, secondaryGroupSize).reverse().join(''));
}
// Reverse the groups and the digits inside of them.
groups = groups.reverse();
// Reconstruct the major digits.
majorDigits = groups.join(',');
}
if (minimumFractionDigits < maximumFractionDigits) {
// Strip any trailing zeroes.
var minorDigits = minorDigits.replace(/0+$/,'');
if (minorDigits.length < minimumFractionDigits) {
// Now there are too few digits, re-add trailing zeroes
// until the desired length is reached.
var neededZeroes = minimumFractionDigits - minorDigits.length;
minorDigits += (new Array(neededZeroes+1)).join('0');
}
}
// Assemble the final number and insert it into the pattern.
value = minorDigits ? majorDigits + '.' + minorDigits : majorDigits;
value = pattern.replace(/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/, value);
// Localize the number.
return replaceSymbols(value);
}
/**
* Replaces the placeholders with real symbols
*
* @param value
* @returns {string}
*/
function replaceSymbols(value) {
var replacements = {
'.': piwik.numbers.symbolDecimal,
',': piwik.numbers.symbolGroup,
'+': piwik.numbers.symbolPlus,
'-': piwik.numbers.symbolMinus,
'%': piwik.numbers.symbolPercent
};
var newValue = '';
var valueParts = value.split('');
$.each(valueParts, function(index, value) {
$.each(replacements, function(char, replacement) {
if (value.indexOf(char) != -1) {
value = value.replace(char, replacement);
return false;
}
});
newValue += value;
});
return newValue;
}
/**
* Public available methods
*/
return {
formatNumber: function (value) {
return format(value, piwik.numbers.patternNumber);
},
formatPercent: function (value) {
return format(value, piwik.numbers.patternPercent);
},
formatCurrency: function (value, currency) {
var formatted = format(value, piwik.numbers.patternCurrency);
return formatted.replace('¤', currency);
}
}
})();

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -20,11 +20,11 @@ var Piwik_Popover = (function () {
var openPopover = function (title, dialogClass) {
createContainer();
var options =
var options =
{
title: title,
modal: true,
width: '950px',
width: '1050px',
position: ['center', 'center'],
resizable: false,
autoOpen: true,
@ -142,7 +142,17 @@ var Piwik_Popover = (function () {
/** Set the title of the popover */
setTitle: function (titleHtml) {
var titleText = piwikHelper.htmlDecode(titleHtml);
if (titleText.length > 60) {
titleHtml = $('<span>').attr('class', 'tooltip').attr('title', titleText).html(titleHtml);
}
container.dialog('option', 'title', titleHtml);
try {
$('.tooltip', container.parentNode).tooltip('destroy');
} catch (e) {}
if (titleText.length > 60) {
$('.tooltip', container.parentNode).tooltip({track: true, items: '.tooltip'});
}
},
/** Set inner HTML of the popover */
@ -209,11 +219,11 @@ var Piwik_Popover = (function () {
/**
* 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

View file

@ -1,12 +0,0 @@
$(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();
});
});

View file

@ -1,5 +1,5 @@
/**
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* Module creation & inclusion for Piwik.
*
@ -13,9 +13,9 @@
/**
* 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 '\'.

View file

@ -1,5 +1,5 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -17,11 +17,11 @@ piwik.getSparklineColors = function () {
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);
@ -42,10 +42,14 @@ window.initializeSparklines = function () {
// 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 != "") {
$(this).addClass('linked');
var params = broadcast.getValuesFromUrl(sparklineUrl);
for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i) {
delete params[sparklineUrlParamsToIgnore[i]];
@ -60,7 +64,8 @@ window.initializeSparklines = function () {
}
// on click, reload the graph with the new url
$(this).click(function () {
$(this).off('click.sparkline');
$(this).on('click.sparkline', function () {
var reportId = graph.attr('data-graph-id'),
dataTable = $(require('piwik/UI').DataTable.getDataTableByReport(reportId));
@ -69,7 +74,11 @@ window.initializeSparklines = function () {
// 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');
if ($(this).closest('.widget').length) {
dataTable = $(this).closest('.widget').find('div.dataTableVizEvolution');
} else {
dataTable = $('div.dataTableVizEvolution');
}
}
// reload the datatable w/ a new column & scroll to the graph

View file

@ -1,13 +1,33 @@
/*!
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function initTopControls() {
function getOverlap(element1, element2)
{
if (!element1 || !element1.getBoundingClientRect || !element2 || !element2.getBoundingClientRect) {
return 0;
}
var rect1 = element1.getBoundingClientRect();
var rect2 = element2.getBoundingClientRect();
var doOverlap = !(rect1.right < rect2.left || rect1.left > rect2.right);
if (doOverlap) {
return rect1.left - rect2.right;
}
return 0;
}
var $topControlsContainer = $('.top_controls'),
left = 0;
var allRendered = true;
if ($topControlsContainer.length) {
$('.piwikTopControl').each(function () {
var $control = $(this);
@ -16,12 +36,79 @@ function initTopControls() {
}
$control.css('left', left);
var width = $control.outerWidth(true);
if (!$.contains($topControlsContainer[0], this)) {
$control.detach().appendTo($topControlsContainer);
var isControlFullyRendered = width >= 30;
if (!isControlFullyRendered) {
allRendered = false;
}
left += $control.outerWidth(true);
left += width;
});
if (allRendered) {
// we make top controls visible only after all selectors are rendered
$('.top_controls').css('visibility', 'visible');
$('.top_controls').css('opacity', '1');
}
var header = $('#header_message.isPiwikDemo');
if (header.length) {
// make sure isPiwikDemo message is always fully visible, move it to the right if needed
var lastSelector = $('.top_controls .piwikTopControl:last');
var overlap = getOverlap(header[0], lastSelector[0]);
if (header[0] !== lastSelector[0] && overlap !== 0) {
header.css('right', (Math.abs(overlap) + 18) * -1);
}
}
}
}
//Keyboard controls for Top Controls Calendar through tab and enter.
$( document ).ready(function() {
$('.periodSelector').keydown(function(e){
toggleCalendar(e);
})
blockPropegation();
$('.periodSelector .form-radio').keydown(function(e){
e.stopPropagation();
if(e.which==13){
selectPeriodRadioButton($(this));
}
})
});
function toggleCalendar(e){
var calendarOpen = $('.periodSelector').hasClass('expanded');
$('.periodSelector .ui-datepicker-month').attr('tabindex','4');
$('.periodSelector td a').attr('tabindex','4');
$('.periodSelector .ui-datepicker-year').attr('tabindex','4');
$('.periodSelector .form-radio').attr('tabindex','4');
if(e.which==13){
if(calendarOpen){
$('.periodSelector').removeClass('expanded');
}else{
$('.periodSelector').addClass('expanded');
}
}
}
function selectPeriodRadioButton(button){
$('.periodSelector .form-radio').removeClass('checked');
button.addClass('checked');
button.find('input').click();
blockPropegation();
}
function blockPropegation(){
$('.ui-datepicker-month, .ui-datepicker-year, .periodSelector td a').keydown(function(e){
e.stopPropagation();
})
}

View file

@ -1,5 +1,5 @@
/**
* Piwik - Web Analytics
* Piwik - free/libre analytics platform
*
* Visitor profile popup control.
*
@ -14,7 +14,7 @@
/**
* 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) {
@ -35,7 +35,7 @@
}
}
this.param = params;
this.props = JSON.parse($element.attr('data-props') || '{}');
};
@ -47,7 +47,7 @@
/**
* 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
*/
@ -95,8 +95,8 @@
/**
* Handle the widget resize event, if we're currently in a widget.
*
* TODO: should use proper resize detection (see
*
* 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)
*/