/*!
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
var layoutColumnSelector = '#dashboardWidgetsArea > .col';
/**
* Current dashboard column layout
* @type {object}
*/
var dashboardLayout = {};
/**
* Id of current dashboard
* @type {int}
*/
var dashboardId = 1;
/**
* Name of current dashboard
* @type {string}
*/
var dashboardName = '';
/**
* Holds a reference to the dashboard element
* @type {object}
*/
var dashboardElement = null;
/**
* Boolean indicating weather the layout config has been changed or not
* @type {boolean}
*/
var dashboardChanged = false;
/**
* public methods of dashboard plugin
* all methods defined here are accessible with $(selector).dashboard('method', param, param, ...)
*/
var methods = {
/**
* creates a dashboard object
*
* @param {object} options
*/
init: function (options) {
dashboardElement = this;
if (options.idDashboard) {
dashboardId = options.idDashboard;
}
if (options.name) {
dashboardName = options.name;
}
if (options.layout) {
generateLayout(options.layout);
}
return this;
},
/**
* Destroys the dashboard object and all its childrens
*
* @return void
*/
destroy: function () {
$(dashboardElement).remove();
dashboardElement = null;
destroyWidgets();
},
destroyWidgets: destroyWidgets,
/**
* Load dashboard with the given id
*
* @param {int} dashboardIdToLoad
*/
loadDashboard: function (dashboardIdToLoad, forceReload) {
$(dashboardElement).empty();
dashboardName = '';
dashboardLayout = null;
dashboardId = dashboardIdToLoad;
if (!forceReload && piwikHelper.isAngularRenderingThePage()) {
angular.element(document).injector().invoke(function ($location) {
$location.search('subcategory', '' + dashboardIdToLoad);
});
} else {
var element = $('[piwik-dashboard]');
var scope = angular.element(element).scope();
scope.fetchDashboard(dashboardIdToLoad);
}
return this;
},
/**
* Change current column layout to the given one
*
* @param {String} newLayout
*/
setColumnLayout: function (newLayout) {
adjustDashboardColumns(newLayout);
},
/**
* Returns the current column layout
*
* @return {String}
*/
getColumnLayout: function () {
return dashboardLayout.config.layout;
},
/**
* Return the current dashboard name
*
* @return {String}
*/
getDashboardName: function () {
return dashboardName;
},
/**
* Return the current dashboard id
*
* @return {int}
*/
getDashboardId: function () {
return dashboardId;
},
/**
* Sets a new name for the current dashboard
*
* @param {String} newName
*/
setDashboardName: function (newName) {
dashboardName = newName;
dashboardChanged = true;
saveLayout();
},
/**
* Adds a new widget to the dashboard
*
* @param {String} uniqueId
* @param {int} columnNumber
* @param {object} widgetParameters
* @param {boolean} addWidgetOnTop
* @param {boolean} isHidden
*/
addWidget: function (uniqueId, columnNumber, widgetParameters, addWidgetOnTop, isHidden) {
addWidgetTemplate(uniqueId, columnNumber, widgetParameters, addWidgetOnTop, isHidden);
saveLayout();
},
/**
* Resets the current layout to the defaults
*/
resetLayout: function () {
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
module: 'Dashboard',
action: 'resetLayout',
idDashboard: dashboardId
}, 'get');
ajaxRequest.withTokenInUrl();
ajaxRequest.setCallback(
function () {
methods.loadDashboard.apply(this, [dashboardId, true])
}
);
ajaxRequest.setLoadingElement();
ajaxRequest.setFormat('html');
ajaxRequest.send();
},
rebuildMenu: rebuildMenu,
/**
* Removes the current dashboard
*/
removeDashboard: function () {
if (dashboardId == 1) {
return; // dashboard with id 1 should never be deleted, as it is the default
}
var ajaxRequest = new ajaxHelper();
ajaxRequest.setLoadingElement();
ajaxRequest.addParams({
module: 'API',
method: 'Dashboard.removeDashboard',
idDashboard: dashboardId,
login: piwik.userLogin,
format: 'json'
}, 'get');
ajaxRequest.setCallback(
function () {
methods.loadDashboard.apply(this, [1]);
rebuildMenu();
}
);
ajaxRequest.withTokenInUrl();
ajaxRequest.setFormat('html');
ajaxRequest.send();
},
/**
* Saves the current layout aus new default widget layout
*/
saveLayoutAsDefaultWidgetLayout: function () {
saveLayout('saveLayoutAsDefault');
},
/**
* Returns if the current loaded dashboard is the default dashboard
*/
isDefaultDashboard: function () {
return (dashboardId == 1);
}
};
function destroyWidgets()
{
var widgets = $('[widgetId]');
for (var i = 0; i < widgets.length; i++) {
$(widgets[i]).dashboardWidget('destroy');
}
}
function removeNonExistingWidgets(availableWidgets, layout)
{
var existingModuleAction = {};
$.each(availableWidgets, function (category, widgets) {
$.each(widgets, function (index, widget) {
existingModuleAction[widget.module + '.' + widget.action] = true;
});
});
var columns = [];
$.each(layout.columns, function (i, column) {
var widgets = [];
$.each(column, function (j, widget) {
if (!widget.parameters || !widget.parameters.module) {
return;
}
var method = widget.parameters.module + '.' + widget.parameters.action
if (existingModuleAction[method]) {
widgets.push(widget);
}
});
columns[i] = widgets;
});
layout.columns = columns;
return layout;
}
/**
* Generates the dashboard out of the given layout
*
* @param {object|string} layout
*/
function generateLayout(layout) {
dashboardLayout = parseLayout(layout);
widgetsHelper.getAvailableWidgets(function (availableWidgets) {
dashboardLayout = removeNonExistingWidgets(availableWidgets, dashboardLayout);
piwikHelper.hideAjaxLoading();
adjustDashboardColumns(dashboardLayout.config.layout);
var dashboardContainsWidgets = false;
for (var column = 0; column < dashboardLayout.columns.length; column++) {
for (var i in dashboardLayout.columns[column]) {
if (typeof dashboardLayout.columns[column][i] != 'object') {
// Fix IE8 bug: the "i in" loop contains i="indexOf", which would yield type function.
// If we would continue with i="indexOf", an invalid widget would be created.
continue;
}
var widget = dashboardLayout.columns[column][i];
dashboardContainsWidgets = true;
addWidgetTemplate(widget.uniqueId, column + 1, widget.parameters, false, widget.isHidden)
}
}
if (!dashboardContainsWidgets) {
$(dashboardElement).trigger('dashboardempty');
}
makeWidgetsSortable();
});
}
/**
* Adjust the dashboard columns to fit the new layout
* removes or adds new columns if needed and sets the column sizes.
*
* @param {String} layout new layout in format xx-xx-xx
* @return {void}
*/
function adjustDashboardColumns(layout) {
var columnWidth = layout.split('-');
var columnCount = columnWidth.length;
var currentCount = $('> .col', dashboardElement).length;
if (currentCount < columnCount) {
$('.menuClear', dashboardElement).remove();
for (var i = currentCount; i < columnCount; i++) {
if (dashboardLayout.columns.length < i) {
dashboardLayout.columns.push({});
}
$(dashboardElement).append('
');
}
$(dashboardElement).append('');
} else if (currentCount > columnCount) {
for (var i = columnCount; i < currentCount; i++) {
if (dashboardLayout.columns.length >= i) {
dashboardLayout.columns.pop();
}
// move widgets to other columns depending on columns height
$('[widgetId]', $(layoutColumnSelector + ':last')).each(function (id, elem) {
var cols = $(layoutColumnSelector).slice(0, columnCount);
var smallestColumn = $(cols[0]);
var smallestColumnHeight = null;
cols.each(function (colId, col) {
if (smallestColumnHeight == null || smallestColumnHeight > $(col).height()) {
smallestColumnHeight = $(col).height();
smallestColumn = $(col);
}
});
$(elem).appendTo(smallestColumn);
});
$(layoutColumnSelector + ':last').remove();
}
}
var $dashboardElement = $(' > .col', dashboardElement);
if (!$dashboardElement.length) {
return;
}
switch (layout) {
case '100':
$dashboardElement.removeClass().addClass('col s12');
break;
case '50-50':
$dashboardElement.removeClass().addClass('col s12 m6');
break;
case '67-33':
$dashboardElement[0].className = 'col s12 m8';
$dashboardElement[1].className = 'col s12 m4';
break;
case '33-67':
$dashboardElement[0].className = 'col s12 m4';
$dashboardElement[1].className = 'col s12 m8';
break;
case '33-33-33':
$dashboardElement[0].className = 'col s12 m4';
$dashboardElement[1].className = 'col s12 m4';
$dashboardElement[2].className = 'col s12 m4';
break;
case '40-30-30':
$dashboardElement[0].className = 'col s12 m6';
$dashboardElement[1].className = 'col s12 m3';
$dashboardElement[2].className = 'col s12 m3';
break;
case '30-40-30':
$dashboardElement[0].className = 'col s12 m3';
$dashboardElement[1].className = 'col s12 m6';
$dashboardElement[2].className = 'col s12 m3';
break;
case '30-30-40':
$dashboardElement[0].className = 'col s12 m3';
$dashboardElement[1].className = 'col s12 m3';
$dashboardElement[2].className = 'col s12 m6';
break;
case '25-25-25-25':
$dashboardElement[0].className = 'col s12 m3';
$dashboardElement[1].className = 'col s12 m3';
$dashboardElement[2].className = 'col s12 m3';
$dashboardElement[3].className = 'col s12 m3';
break;
}
makeWidgetsSortable();
// if dashboard column count is changed (not on initial load)
if (currentCount > 0 && dashboardLayout.config.layout != layout) {
dashboardChanged = true;
dashboardLayout.config.layout = layout;
saveLayout();
}
// trigger resize event on all widgets
$('.widgetContent').each(function () {
$(this).trigger('widget:resize');
});
}
/**
* Returns the given layout as an layout object
* Used to parse old layout format into the new syntax
*
* @param {object} layout layout object or string
* @return {object}
*/
function parseLayout(layout) {
// Handle layout array used in piwik before 1.7
// column count was always 3, so use layout 33-33-33 as default
if ($.isArray(layout)) {
layout = {
config: {layout: '33-33-33'},
columns: layout
};
}
if (!layout.config.layout) {
layout.config.layout = '33-33-33';
}
return layout;
}
/**
* Reloads the widget with the given uniqueId
*
* @param {String|jQuery} $widget
*/
function reloadWidget($widget) {
if (typeof $widget === 'string') {
$widget = $('[widgetid="' + $widget + '"]', dashboardElement);
}
$widget.dashboardWidget('reload', false, true);
}
/**
* Adds an empty widget template to the dashboard in the given column
* @param {String} uniqueId
* @param {int} columnNumber
* @param {object} widgetParameters
* @param {boolean} addWidgetOnTop
* @param {boolean} isHidden
*/
function addWidgetTemplate(uniqueId, columnNumber, widgetParameters, addWidgetOnTop, isHidden) {
if (!columnNumber) {
columnNumber = 1;
}
// do not try to add widget if given column number is to high
if (columnNumber > $('> .col', dashboardElement).length) {
return;
}
var $widgetContent = $('');
if (addWidgetOnTop) {
$('> .col:nth-child(' + columnNumber + ')', dashboardElement).prepend($widgetContent);
} else {
$('> .col:nth-child(' + columnNumber + ')', dashboardElement).append($widgetContent);
}
return $widgetContent.dashboardWidget({
uniqueId: uniqueId,
widgetParameters: widgetParameters,
onChange: function () {
saveLayout();
},
isHidden: isHidden
});
}
/**
* Make all widgets on the dashboard sortable
*/
function makeWidgetsSortable() {
function onStart(event, ui) {
if (!jQuery.support.noCloneEvent) {
$('object', this).hide();
}
}
function onStop(event, ui) {
$('object', this).show();
$('.widgetHover', this).removeClass('widgetHover');
$('.widgetTopHover', this).removeClass('widgetTopHover');
if ($('.widget:has(".piwik-graph")', ui.item).length) {
reloadWidget($('.widget', ui.item).attr('id'));
}
saveLayout();
}
//launch 'sortable' property on every dashboard widgets
$( layoutColumnSelector + ":data('ui-sortable')", dashboardElement ).sortable('destroy');
$('> .col', dashboardElement)
.sortable({
items: 'div.sortable',
opacity: 0.6,
forceHelperSize: true,
forcePlaceholderSize: true,
placeholder: 'hover',
handle: '.widgetTop',
helper: 'clone',
start: onStart,
stop: onStop,
connectWith: layoutColumnSelector
});
}
/**
* Handle clicks for menu items for choosing between available dashboards
*/
function rebuildMenu() {
if (piwikHelper.isAngularRenderingThePage()) {
// dashboard in reporting page (regular Piwik UI)
angular.element(document).injector().invoke(function (reportingMenuModel) {
reportingMenuModel.reloadMenuItems();
});
return;
}
var _self = this;
// widgetized
var success = function (dashboards) {
var dashboardMenuList = $('#Dashboard_embeddedIndex_1').closest('ul');
var dashboardMenuListItems = dashboardMenuList.find('>li');
dashboardMenuListItems.filter(function () {
return $(this).attr('id').indexOf('Dashboard_embeddedIndex') == 0;
}).remove();
if (dashboards.length === 0) {
dashboards = [{iddashboard: 1, name: _pk_translate('Dashboard_Dashboard')}];
}
if (dashboards.length > 1
|| dashboardMenuListItems.length >= 1
) {
var items = [];
for (var i = 0; i < dashboards.length; i++) {
var $link = $('').attr('data-iddashboard', dashboards[i].iddashboard).text(dashboards[i].name).addClass('item');
var $li = $('').attr('id', 'Dashboard_embeddedIndex_' + dashboards[i].iddashboard).addClass('dashboardMenuItem').attr('role', 'menuitem').append($link);
items.push($li);
if (dashboards[i].iddashboard == dashboardId) {
dashboardName = dashboards[i].name;
$li.addClass('active');
}
}
dashboardMenuList.prepend(items);
}
dashboardMenuList.find('a[data-iddashboard]').click(function (e) {
e.preventDefault();
var idDashboard = $(this).attr('data-iddashboard');
$('#Dashboard ul li').removeClass('active');
methods.loadDashboard.apply(_self, [idDashboard]);
$(this).closest('li').addClass('active');
});
};
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
module: 'Dashboard',
action: 'getAllDashboards',
filter_limit: '-1'
}, 'get');
ajaxRequest.withTokenInUrl();
ajaxRequest.setCallback(success);
ajaxRequest.send();
}
/**
* Save the current layout in database if it has changed
* @param {string} [action] action to perform (defaults to saveLayout)
*/
function saveLayout(action) {
var columns = [];
var columnNumber = 0;
$(layoutColumnSelector).each(function () {
columns[columnNumber] = [];
var items = $('[widgetId]', this);
for (var j = 0; j < items.length; j++) {
columns[columnNumber][j] = $(items[j]).dashboardWidget('getWidgetObject');
// Do not store segment in the dashboard layout
delete columns[columnNumber][j].parameters.segment;
}
columnNumber++;
});
if (JSON.stringify(dashboardLayout.columns) != JSON.stringify(columns) || dashboardChanged || action) {
dashboardLayout.columns = JSON.parse(JSON.stringify(columns));
columns = null;
if (!action) {
action = 'saveLayout';
}
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
module: 'Dashboard',
action: action,
idDashboard: dashboardId
}, 'get');
ajaxRequest.addParams({
layout: JSON.stringify(dashboardLayout),
name: dashboardName
}, 'post');
ajaxRequest.setCallback(
function () {
if (dashboardChanged) {
dashboardChanged = false;
rebuildMenu();
}
}
);
ajaxRequest.withTokenInUrl();
ajaxRequest.setFormat('html');
ajaxRequest.send();
}
}
/**
* Make plugin methods available
*/
$.fn.dashboard = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.dashboard');
}
}
})(jQuery);