/*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ function widgetsHelper() { } /** * Returns the available widgets fetched via AJAX (if not already done) * * @return {object} object containing available widgets */ widgetsHelper.getAvailableWidgets = function (callback) { function mergeCategoriesAndSubCategories(availableWidgets) { var categorized = {}; $.each(availableWidgets, function (index, widget) { var category = widget.category.name; if (!categorized[category]) { categorized[category] = {'-': []}; } var subcategory = '-'; if (widget.subcategory && widget.subcategory.name) { subcategory = widget.subcategory.name; } if (!categorized[category][subcategory]) { categorized[category][subcategory] = []; } categorized[category][subcategory].push(widget); }); var moved = {}; $.each(categorized, function (category, widgets) { $.each(widgets, function (subcategory, subwidgets) { var categoryToUse = category; if (subwidgets.length >= 3 && subcategory !== '-') { categoryToUse = category + ' - ' + subcategory; } if (!moved[categoryToUse]) { moved[categoryToUse] = []; } $.each(subwidgets, function (index, widget) { moved[categoryToUse].push(widget); }); }); }); return moved; } if (!widgetsHelper.availableWidgets) { var ajaxRequest = new ajaxHelper(); ajaxRequest._mixinDefaultGetParams = function (params) { return params; }; ajaxRequest.addParams({ module: 'API', method: 'API.getWidgetMetadata', filter_limit: '-1', format: 'JSON', deep: '1', idSite: piwik.idSite || broadcast.getValueFromUrl('idSite') }, 'get'); ajaxRequest.setCallback( function (data) { widgetsHelper.availableWidgets = mergeCategoriesAndSubCategories(data); if (callback) { callback(widgetsHelper.availableWidgets); } } ); ajaxRequest.setErrorCallback(function (deferred, status) { if (status == 'abort' || !deferred || deferred.status < 400 || deferred.status >= 600) { return; } $('#loadingError').show(); }); ajaxRequest.send(); return; } if (callback) { callback(widgetsHelper.availableWidgets); } }; /** * Determines the complete widget object by its unique id and sends it to callback method * * @param {string} uniqueId * @param {function} callback */ widgetsHelper.getWidgetObjectFromUniqueId = function (uniqueId, callback) { widgetsHelper.getAvailableWidgets(function(widgets){ for (var widgetCategory in widgets) { var widgetInCategory = widgets[widgetCategory]; for (var i in widgetInCategory) { if (widgetInCategory[i]["uniqueId"] == uniqueId) { callback(widgetInCategory[i]); return; } } } callback(false); }); }; /** * Determines the name of a widget by its unique id and sends it to the callback * * @param {string} uniqueId unique id of the widget * @param {function} callback * @return {string} */ widgetsHelper.getWidgetNameFromUniqueId = function (uniqueId, callback) { this.getWidgetObjectFromUniqueId(uniqueId, function(widget) { if (widget == false) { callback(false); } callback(widget["name"]); }); }; /** * Sends and ajax request to query for the widgets html * * @param {string} widgetUniqueId unique id of the widget * @param {object} widgetParameters parameters to be used for loading the widget * @param {function} onWidgetLoadedCallback callback to be executed after widget is loaded * @return {object} */ widgetsHelper.loadWidgetAjax = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback, onWidgetErrorCallback) { var disableLink = broadcast.getValueFromUrl('disableLink'); widgetParameters['disableLink'] = disableLink.length || $('body#standalone').length; widgetParameters['widget'] = 1; var ajaxRequest = new ajaxHelper(); ajaxRequest.addParams(widgetParameters, 'get'); ajaxRequest.setCallback(onWidgetLoadedCallback); if (onWidgetErrorCallback) { ajaxRequest.setErrorCallback(onWidgetErrorCallback); } ajaxRequest.setFormat('html'); ajaxRequest.send(); return ajaxRequest; }; (function ($, require) { var exports = require('piwik/UI/Dashboard'); /** * Singleton instance that creates widget elements. Normally not needed even * when embedding/re-using dashboard widgets, but it can be useful when creating * elements with the same look and feel as dashboard widgets, but different * behavior (such as the widget preview in the dashboard manager control). * * @constructor */ var WidgetFactory = function () { // empty }; /** * Creates an HTML element for displaying a widget. * * @param {string} uniqueId unique id of the widget * @param {string} widgetName name of the widget * @return {Element} the empty widget */ WidgetFactory.prototype.make = function (uniqueId, widgetName) { var $result = this.getWidgetTemplate().clone(); $result.attr('id', uniqueId).find('.widgetName').append(widgetName); return $result; }; /** * Returns the base widget template element. The template is stored in the * element with id == 'widgetTemplate'. * * @return {Element} the widget template */ WidgetFactory.prototype.getWidgetTemplate = function () { if (!this.widgetTemplate) { this.widgetTemplate = $('#widgetTemplate').find('>.widget').detach(); } return this.widgetTemplate; }; exports.WidgetFactory = new WidgetFactory(); })(jQuery, require); /** * widgetPreview jQuery Extension * * Converts an dom element to a widget preview * Widget preview contains an categorylist, widgetlist and a preview */ (function ($) { $.extend({ widgetPreview: new function () { /** * Default settings for widgetPreview * @type {object} */ var defaultSettings = { /** * handler called after a widget preview is loaded in preview element * @type {function} */ onPreviewLoaded: function () {}, /** * handler called on click on element in widgetlist or widget header * @type {function} */ onSelect: function () {}, /** * callback used to determine if a widget is available or not * unavailable widgets aren't chooseable in widgetlist * @type {function} */ isWidgetAvailable: function (widgetUniqueId) { return true; }, /** * should the lists and preview be reset on widget selection? * @type {boolean} */ resetOnSelect: false, /** * css classes for various elements * @type {string} */ baseClass: 'widgetpreview-base', categorylistClass: 'widgetpreview-categorylist', widgetlistClass: 'widgetpreview-widgetlist', widgetpreviewClass: 'widgetpreview-preview', choosenClass: 'widgetpreview-choosen', unavailableClass: 'widgetpreview-unavailable' }; /** * Returns the div to show category list in * - if element doesn't exist it will be created and added * - if element already exist it's content will be removed * * @return {$} category list element */ function createWidgetCategoryList(widgetPreview, availableWidgets) { var settings = widgetPreview.settings; if (!$('.' + settings.categorylistClass, widgetPreview).length) { $(widgetPreview).append(''); } else { $('.' + settings.categorylistClass, widgetPreview).empty(); } for (var widgetCategory in availableWidgets) { $('.' + settings.categorylistClass, widgetPreview).append($('
  • ').text(widgetCategory)); } return $('.' + settings.categorylistClass, widgetPreview); } /** * Returns the div to show widget list in * - if element doesn't exist it will be created and added * - if element already exist it's content will be removed * * @return {$} widget list element */ function createWidgetList(widgetPreview) { var settings = widgetPreview.settings; if (!$('.' + settings.widgetlistClass, widgetPreview).length) { $(widgetPreview).append(''); } else { $('.' + settings.widgetlistClass + ' li', widgetPreview).off('mouseover'); $('.' + settings.widgetlistClass + ' li', widgetPreview).off('click'); $('.' + settings.widgetlistClass, widgetPreview).empty(); } if ($('.' + settings.categorylistClass + ' .' + settings.choosenClass, widgetPreview).length) { var position = $('.' + settings.categorylistClass + ' .' + settings.choosenClass, widgetPreview).position().top - $('.' + settings.categorylistClass, widgetPreview).position().top + $('.dashboard-manager .addWidget').outerHeight(); if (!$('#content.admin').length) { position += 5; // + padding defined in dashboard view } $('.' + settings.widgetlistClass, widgetPreview).css('top', position); $('.' + settings.widgetlistClass, widgetPreview).css('marginBottom', position); } return $('.' + settings.widgetlistClass, widgetPreview); } /** * Display the given widgets in a widget list * * @param {object} widgets widgets to be displayed * @return {void} */ function showWidgetList(widgets, widgetPreview) { var settings = widgetPreview.settings; var widgetList = createWidgetList(widgetPreview), widgetPreviewTimer; for (var j = 0; j < widgets.length; j++) { var widgetName = widgets[j]["name"]; var widgetUniqueId = widgets[j]["uniqueId"]; var widgetCategoryId = widgets[j].category ? widgets[j].category.id : null; var widgetClass = ''; if (!settings.isWidgetAvailable(widgetUniqueId) && widgetCategoryId !== 'General_KpiMetric') { widgetClass += ' ' + settings.unavailableClass; } widgetName = piwikHelper.escape(piwikHelper.htmlEntities(widgetName)); widgetList.append('
  • ' + widgetName + '
  • '); } // delay widget preview a few millisconds $('li', widgetList).on('mouseenter', function () { var that = this, widgetUniqueId = $(this).attr('uniqueid'); clearTimeout(widgetPreview); widgetPreviewTimer = setTimeout(function () { $('li', widgetList).removeClass(settings.choosenClass); $(that).addClass(settings.choosenClass); showPreview(widgetUniqueId, widgetPreview); }, 400); }); // clear timeout after mouse has left $('li:not(.' + settings.unavailableClass + ')', widgetList).on('mouseleave', function () { clearTimeout(widgetPreview); }); $('li', widgetList).on('click', function () { if (!$('.widgetLoading', widgetPreview).length) { settings.onSelect($(this).attr('uniqueid')); $(widgetPreview).closest('.dashboard-manager').removeClass('expanded'); if (settings.resetOnSelect) { resetWidgetPreview(widgetPreview); } } return false; }); } /** * Returns the div to show widget preview in * - if element doesn't exist it will be created and added * - if element already exist it's content will be removed * * @return {$} preview element */ function createPreviewElement(widgetPreview) { var settings = widgetPreview.settings; if (!$('.' + settings.widgetpreviewClass, widgetPreview).length) { $(widgetPreview).append('
    '); } else { $('.' + settings.widgetpreviewClass + ' .widgetTop', widgetPreview).off('click'); $('.' + settings.widgetpreviewClass, widgetPreview).empty(); } return $('.' + settings.widgetpreviewClass, widgetPreview); } /** * Show widget with the given uniqueId in preview * * @param {string} widgetUniqueId unique id of widget to display * @param widgetPreview * @return {void} */ function showPreview(widgetUniqueId, widgetPreview) { // do not reload id widget already displayed if ($('[id="' + widgetUniqueId + '"]', widgetPreview).length) return; var settings = widgetPreview.settings; var previewElement = createPreviewElement(widgetPreview); widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId, function(widget) { var widgetParameters = widget['parameters']; var emptyWidgetHtml = require('piwik/UI/Dashboard').WidgetFactory.make( widgetUniqueId, $('') .attr('title', _pk_translate("Dashboard_AddPreviewedWidget")) .text(_pk_translate('Dashboard_WidgetPreview')) ); previewElement.html(emptyWidgetHtml); var onWidgetLoadedCallback = function (response) { var widgetElement = $(document.getElementById(widgetUniqueId)); // document.getElementById needed for widgets with uniqueid like widgetOpens+Contact+Form $('.widgetContent', widgetElement).html($(response)); piwikHelper.compileAngularComponents($('.widgetContent', widgetElement), { forceNewScope: true }); $('.widgetContent', widgetElement).trigger('widget:create'); settings.onPreviewLoaded(widgetUniqueId, widgetElement); $('.' + settings.widgetpreviewClass + ' .widgetTop', widgetPreview).on('click', function () { settings.onSelect(widgetUniqueId); $(widgetPreview).closest('.dashboard-manager').removeClass('expanded'); if (settings.resetOnSelect) { resetWidgetPreview(widgetPreview); } return false; }); }; // abort previous sent request if (widgetPreview.widgetAjaxRequest) { widgetPreview.widgetAjaxRequest.abort(); } widgetPreview.widgetAjaxRequest = widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParameters, onWidgetLoadedCallback); }); } /** * Reset function * * @return {void} */ function resetWidgetPreview(widgetPreview) { var settings = widgetPreview.settings; $('.' + settings.categorylistClass + ' li', widgetPreview).removeClass(settings.choosenClass); createWidgetList(widgetPreview); createPreviewElement(widgetPreview); } /** * Constructor * * @param {object} userSettings Settings to be used * @return {void} */ this.construct = function (userSettings) { if (userSettings == 'reset') { resetWidgetPreview(this); return; } this.widgetAjaxRequest = null; $(this).addClass('widgetpreview-base'); this.settings = jQuery.extend({}, defaultSettings, userSettings); // set onSelect callback if (typeof this.settings.onSelect == 'function') { this.onSelect = this.settings.onSelect; } // set onPreviewLoaded callback if (typeof this.settings.onPreviewLoaded == 'function') { this.onPreviewLoaded = this.settings.onPreviewLoaded; } var self = this; widgetsHelper.getAvailableWidgets(function (availableWidgets) { var categoryList = createWidgetCategoryList(self, availableWidgets); $('li', categoryList).on('mouseover', function () { var category = $(this).text(); var widgets = availableWidgets[category]; $('li', categoryList).removeClass(self.settings.choosenClass); $(this).addClass(self.settings.choosenClass); showWidgetList(widgets, self); createPreviewElement(self); // empty preview }); }); }; } }); /** * Makes widgetPreview available with $().widgetPreview() */ $.fn.extend({ widgetPreview: $.widgetPreview.construct }) })(jQuery);