From dd3770bc17413310d5fcec5b7346eb443854ea30 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 5 Aug 2016 03:06:46 -0400 Subject: [PATCH] CRM-17663 - Load dashlets directly --- CRM/Contact/Page/DashBoard.php | 2 +- CRM/Contact/Page/Dashlet.php | 17 +-- CRM/Core/BAO/Dashboard.php | 89 +++++++++--- css/dashboard.css | 1 + js/jquery/jquery.dashboard.js | 217 +++++++++-------------------- templates/CRM/common/dashboard.tpl | 113 +-------------- 6 files changed, 150 insertions(+), 289 deletions(-) diff --git a/CRM/Contact/Page/DashBoard.php b/CRM/Contact/Page/DashBoard.php index 40019a3f0b..142b31514a 100644 --- a/CRM/Contact/Page/DashBoard.php +++ b/CRM/Contact/Page/DashBoard.php @@ -44,7 +44,7 @@ class CRM_Contact_Page_DashBoard extends CRM_Core_Page { $resources = CRM_Core_Resources::singleton(); $resources->addScriptFile('civicrm', 'js/jquery/jquery.dashboard.js', 0, 'html-header', FALSE); $resources->addStyleFile('civicrm', 'css/dashboard.css'); - $this->assign('contactDashlets', CRM_Core_BAO_Dashboard::getContactDashlets()); + $this->assign('contactDashlets', CRM_Core_BAO_Dashboard::getContactDashletsForJS()); $resetCache = CRM_Utils_Request::retrieve('resetCache', 'Positive', CRM_Core_DAO::$_nullObject); diff --git a/CRM/Contact/Page/Dashlet.php b/CRM/Contact/Page/Dashlet.php index fa90e1dabe..9fda6074ac 100644 --- a/CRM/Contact/Page/Dashlet.php +++ b/CRM/Contact/Page/Dashlet.php @@ -51,16 +51,13 @@ class CRM_Contact_Page_Dashlet extends CRM_Core_Page { $currentDashlets = CRM_Core_BAO_Dashboard::getContactDashlets(); $contactDashlets = $availableDashlets = array(); - foreach ($currentDashlets as $columnNo => $values) { - foreach ($values as $val => $isMinimized) { - list($weight, $dashletID) = explode('-', $val); - $key = "{$dashletID}-{$isMinimized}"; - $contactDashlets[$columnNo][$key] = array( - 'label' => $allDashlets[$dashletID]['label'], - 'is_reserved' => $allDashlets[$dashletID]['is_reserved'], - ); - unset($allDashlets[$dashletID]); - } + foreach ($currentDashlets as $item) { + $key = "{$item['dashboard_id']}-{$item['is_minimized']}"; + $contactDashlets[$item['column_no']][$key] = array( + 'label' => $item['label'], + 'is_reserved' => $allDashlets[$item['dashboard_id']]['is_reserved'], + ); + unset($allDashlets[$item['dashboard_id']]); } foreach ($allDashlets as $dashletID => $values) { diff --git a/CRM/Core/BAO/Dashboard.php b/CRM/Core/BAO/Dashboard.php index c08a980662..209b309874 100644 --- a/CRM/Core/BAO/Dashboard.php +++ b/CRM/Core/BAO/Dashboard.php @@ -104,33 +104,67 @@ class CRM_Core_BAO_Dashboard extends CRM_Core_DAO_Dashboard { $dashlets = array(); // Get contact dashboard dashlets. - $results = civicrm_api3('dashboard_contact', 'get', array( + $results = civicrm_api3('DashboardContact', 'get', array( 'contact_id' => $contactID, - 'options' => array('sort' => 'column_no asc, weight asc'), + 'is_active' => 1, + 'dashboard_id.is_active' => 1, + 'options' => array('sort' => 'weight'), + 'return' => array( + 'id', + 'weight', + 'column_no', + 'is_minimized', + 'dashboard_id', + 'dashboard_id.name', + 'dashboard_id.label', + 'dashboard_id.url', + 'dashboard_id.fullscreen_url', + 'dashboard_id.permission', + 'dashboard_id.permission_operator', + ), )); - // The available list will only include those which are valid for the domain. - $availableDashlets = self::getDashlets(); foreach ($results['values'] as $item) { - // When a dashlet is removed, it stays in the table with status disabled, - // so even if a user decides not to have any dashlets show, they will still - // have records in the table to indicate that we are not newly initializing. - if ((!empty($availableDashlets[$item['dashboard_id']]) && $availableDashlets[$item['dashboard_id']]['is_active'])) { - if ($item['is_active']) { - // append weight so that order is preserved. - $dashlets[$item['column_no']]["{$item['weight']}-{$item['dashboard_id']}"] = $item['is_minimized']; - } + if (self::checkPermission(CRM_Utils_Array::value('dashboard_id.permission', $item), CRM_Utils_Array::value('dashboard_id.permission_operator', $item))) { + $dashlets[$item['id']] = array( + 'dashboard_id' => $item['dashboard_id'], + 'weight' => $item['weight'], + 'column_no' => $item['column_no'], + 'is_minimized' => $item['is_minimized'], + 'name' => $item['dashboard_id.name'], + 'label' => $item['dashboard_id.label'], + 'url' => $item['dashboard_id.url'], + 'fullscreen_url' => $item['dashboard_id.fullscreen_url'], + ); } } - // If empty, then initialize contact dashboard for this user. + // If empty, then initialize default dashlets for this user. if (!$results['count']) { - $dashlets = self::initializeDashlets(); + // They may just have disabled all their dashlets. Check if any records exist for this contact. + if (!civicrm_api3('DashboardContact', 'getcount', array('contact_id' => $contactID))) { + $dashlets = self::initializeDashlets(); + } } return $dashlets; } + public static function getContactDashletsForJS() { + $data = array(array(), array()); + foreach (self::getContactDashlets() as $item) { + $data[$item['column_no']][] = array( + 'id' => (int) $item['dashboard_id'], + 'minimized' => (bool) $item['is_minimized'], + 'name' => $item['name'], + 'title' => $item['label'], + 'url' => self::parseUrl($item['url']), + 'fullscreenUrl' => self::parseUrl($item['fullscreen_url']), + ); + } + return $data; + } + /** * Setup default dashlets for new users. * @@ -165,21 +199,40 @@ class CRM_Core_BAO_Dashboard extends CRM_Core_DAO_Dashboard { if (is_array($defaultDashlets) && !empty($defaultDashlets)) { foreach ($defaultDashlets as $id => $defaultDashlet) { $dashboard_id = $defaultDashlet['dashboard_id']; - if (!self::checkPermission($getDashlets['values'][$dashboard_id]['permission'], - CRM_Utils_Array::value('permission_operator', $getDashlets['values'][$dashboard_id])) - ) { + $dashlet = $getDashlets['values'][$dashboard_id]; + if (!self::checkPermission(CRM_Utils_Array::value('permission', $dashlet), CRM_Utils_Array::value('permission_operator', $dashlet))) { continue; } else { $assignDashlets = civicrm_api3("dashboard_contact", "create", $defaultDashlet); $values = $assignDashlets['values'][$assignDashlets['id']]; - $dashlets[$values['column_no']][$values['weight'] - $values['dashboard_id']] = $values['is_minimized']; + $dashlets[$assignDashlets['id']] = array( + 'dashboard_id' => $values['dashboard_id'], + 'weight' => $values['weight'], + 'column_no' => $values['column_no'], + 'is_minimized' => $values['is_minimized'], + 'name' => $dashlet['name'], + 'label' => $dashlet['label'], + 'url' => $dashlet['url'], + 'fullscreen_url' => $dashlet['fullscreen_url'], + ); } } } return $dashlets; } + /** + * @param $url + * @return string + */ + public static function parseUrl($url) { + if (substr($url, 0, 4) != 'http') { + $urlParam = explode('?', $url); + $url = CRM_Utils_System::url($urlParam[0], $urlParam[1], FALSE, NULL, FALSE); + } + return $url; + } /** * Check dashlet permission for current user. diff --git a/css/dashboard.css b/css/dashboard.css index c2f63fb653..75e8ace435 100644 --- a/css/dashboard.css +++ b/css/dashboard.css @@ -70,6 +70,7 @@ background-color: #ffffff; padding:0.5em; overflow-x:auto; + min-height: 10em; } #crm-container .widget-content .crm-accordion-header { diff --git a/js/jquery/jquery.dashboard.js b/js/jquery/jquery.dashboard.js index c97dc32996..b26f88d96d 100644 --- a/js/jquery/jquery.dashboard.js +++ b/js/jquery/jquery.dashboard.js @@ -1,50 +1,15 @@ -/** - +--------------------------------------------------------------------+ - | CiviCRM version 4.7 | - +--------------------------------------------------------------------+ - | Copyright CiviCRM LLC (c) 2004-2016 | - +--------------------------------------------------------------------+ - | This file is a part of CiviCRM. | - | | - | CiviCRM is free software; you can copy, modify, and distribute it | - | under the terms of the GNU Affero General Public License | - | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | - | | - | CiviCRM is distributed in the hope that it will be useful, but | - | WITHOUT ANY WARRANTY; without even the implied warranty of | - | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | - | See the GNU Affero General Public License for more details. | - | | - | You should have received a copy of the GNU Affero General Public | - | License and the CiviCRM Licensing Exception along | - | with this program; if not, contact CiviCRM LLC | - | at info[AT]civicrm[DOT]org. If you have questions about the | - | GNU Affero General Public License or the licensing of CiviCRM, | - | see the CiviCRM license FAQ at http://civicrm.org/licensing | - +--------------------------------------------------------------------+ - * - * Copyright (C) 2009 Bevan Rudge - * Licensed to CiviCRM under the Academic Free License version 3.0. - * - * @file Defines the jQuery.dashboard() plugin. - * - * Uses jQuery 1.3, jQuery UI 1.6 and several jQuery UI extensions, most of all Sortable - * http://visualjquery.com/ - * http://docs.jquery.com/UI/Sortable - * http://ui.jquery.com/download - * Sortable - * Draggable - * UI Core - */ +// https://civicrm.org/licensing +/* global CRM, ts */ (function($) { + 'use strict'; // Constructor for dashboard object. $.fn.dashboard = function(options) { // Public properties of dashboard. var dashboard = {}; dashboard.element = this.empty(); dashboard.ready = false; - dashboard.columns = Array(); - dashboard.widgets = Array(); + dashboard.columns = []; + dashboard.widgets = []; // End of public properties of dashboard. /** @@ -101,47 +66,6 @@ CRM.status({}, post); } }; - - // Puts the dashboard into full screen mode, saving element for when the user exits full-screen mode. - // Does not add element to the DOM – this is the caller's responsibility. - // Does show and hide element though. - dashboard.enterFullscreen = function(element) { - // Hide the columns. - for (var c in dashboard.columns) { - if ( typeof dashboard.columns[c] == 'object' ) dashboard.columns[c].element.hide(); - } - - if (!dashboard.fullscreen) { - // Initialize. - var markup = '' + opts.fullscreenHeaderInner + ''; - dashboard.fullscreen = { - headerElement: $(markup).prependTo(dashboard.element).click(dashboard.exitFullscreen).hide() - }; - } - - dashboard.fullscreen.headerElement.slideDown(); - dashboard.fullscreen.currentElement = element.show(); - dashboard.fullscreen.displayed = true; - invokeCallback(opts.callbacks.enterFullscreen, dashboard, dashboard.fullscreen.currentElement); - }; - - // Takes the dashboard out of full screen mode, hiding the active fullscreen element. - dashboard.exitFullscreen = function() { - if (!dashboard.fullscreen.displayed) { - return; - } - - dashboard.fullscreen.headerElement.slideUp(); - dashboard.fullscreen.currentElement.hide(); - dashboard.fullscreen.displayed = false; - - // Show the columns. - for (var c in dashboard.columns) { - if ( typeof dashboard.columns[c] == 'object' ) dashboard.columns[c].element.show(); - } - - invokeCallback(opts.callbacks.exitFullscreen, dashboard, dashboard.fullscreen.currentElement); - }; // End of public methods of dashboard. /** @@ -176,7 +100,7 @@ for (var c = 0; c < opts.columns; c++) { // Save the column to both the public scope for external accessibility and the local scope for readability. var col = dashboard.columns[c] = { - initialWidgets: Array(), + initialWidgets: [], element: $('').appendTo(dashboard.element) }; @@ -184,23 +108,20 @@ col.emptyPlaceholder = $(markup).appendTo(col.element).hide(); // For each widget in this column. - for (var id in widgets[c]) { - var widgetID = id.split('-'); - // Build a new widget object and save it to various publicly accessible places. - col.initialWidgets[id] = dashboard.widgets[widgetID[1]] = widget({ - id: widgetID[1], - element: $('
  • ').appendTo(col.element), - initialColumn: col, - minimized: ( widgets[c][widgetID[1]] > 0 ? true : false ) - }); - - //set empty Dashboard to false - emptyDashboard = false; - } + $.each(widgets[c], function(num, item) { + var id = (num+1) + '-' + item.id; + col.initialWidgets[id] = dashboard.widgets[item.id] = widget($.extend({ + element: $('
  • ').appendTo(col.element), + initialColumn: col + }, item)); + emptyDashboard = false; + }); } - if ( emptyDashboard ) { - emptyDashboardCondition( ); + if (emptyDashboard) { + emptyDashboardCondition(); + } else { + completeInit(); } invokeCallback(opts.callbacks.init, dashboard); @@ -208,14 +129,14 @@ // function that is called when dashboard is empty function emptyDashboardCondition( ) { - cj(".show-refresh").hide( ); - cj("#empty-message").show( ); + $(".show-refresh").hide( ); + $("#empty-message").show( ); } // Contructors for each widget call this when initialization has finished so that dashboard can complete it's intitialization. function completeInit() { // Don't do anything if any widgets are waiting for ajax requests to complete in order to finish initialization. - if (asynchronousRequestCounter > 0) { + if (asynchronousRequestCounter > 0 || dashboard.ready) { return; } @@ -229,8 +150,9 @@ // The class of placeholder elements (the 'ghost' widget showing where the dragged item would land if released now.) placeholder: 'placeholder', activate: function(event, ui) { - var h= cj(ui.item).height(); - $('.placeholder').css('height', h +'px'); }, + var h= $(ui.item).height(); + $('.placeholder').css('height', h +'px'); + }, opacity: 0.2, @@ -303,15 +225,22 @@ }; widget.minimize = function() { $('.widget-content', widget.element).slideUp(opts.animationSpeed); - $(widget.controls.minimize.element).addClass( 'fa-caret-right' ); - $(widget.controls.minimize.element).removeClass( 'fa-caret-down' ); + $(widget.controls.minimize.element) + .addClass('fa-caret-right') + .removeClass('fa-caret-down') + .attr('title', ts('Expand')); widget.minimized = true; }; widget.maximize = function() { $('.widget-content', widget.element).slideDown(opts.animationSpeed); - $(widget.controls.minimize.element).removeClass( 'fa-caret-right' ); - $(widget.controls.minimize.element).addClass( 'fa-caret-down' ); + $(widget.controls.minimize.element) + .removeClass( 'fa-caret-right' ) + .addClass( 'fa-caret-down' ) + .attr('title', ts('Collapse')); widget.minimized = false; + if (!widget.contentLoaded) { + loadContent(); + } }; // Toggles whether the widget is in settings-display mode or not. @@ -405,16 +334,9 @@ widget.enterFullscreen = function() { // Make sure the widget actually supports full screen mode. - if (!widget.fullscreenUrl) { - return; + if (widget.fullscreenUrl) { + CRM.loadPage(widget.fullscreenUrl); } - CRM.loadPage(widget.fullscreenUrl); - }; - - // Exit fullscreen mode. - widget.exitFullscreen = function() { - // This is just a wrapper for dashboard.exitFullscreen() which does the heavy lifting. - dashboard.exitFullscreen(); }; // Adds controls to a widget. id is for internal use and image file name in images/dashboard/ (a .gif). @@ -457,9 +379,9 @@ icon: 'fa-wrench' }, minimize: { - description: ts('Collapse or expand'), + description: widget.minimized ? ts('Expand') : ts('Collapse'), callback: widget.toggleMinimize, - icon: 'fa-caret-down' + icon: widget.minimized ? 'fa-caret-right' : 'fa-caret-down' }, fullscreen: { description: ts('View fullscreen'), @@ -472,55 +394,51 @@ icon: 'fa-times' } }; - // End public properties of widget. - - /** - * Private properties of widget. - */ + widget.contentLoaded = false; - // We're gonna 'fork' execution again, so let's tell the user to hold with us till the AJAX callback gets invoked. - var throbber = $(opts.throbberMarkup).appendTo(widget.element); - var params = $.extend({}, opts.ajaxCallbacks.getWidget.data, {id: widget.id}); - $.getJSON(opts.ajaxCallbacks.getWidget.url, params, init); + // End public properties of widget. - // Help dashboard track whether we've got any outstanding requests on which initialization is pending. - asynchronousRequestCounter++; + init(); return widget; - // End of private properties of widget. /** * Private methods of widget. */ - // Ajax callback for widget initialization. - function init(data, status) { - asynchronousRequestCounter--; - $.extend(widget, data); + function loadContent() { + asynchronousRequestCounter++; + widget.contentLoaded = true; + CRM.loadPage(widget.url, {target: widget.contentElement}) + .one('crmLoad', function() { + asynchronousRequestCounter--; + invokeCallback(opts.widgetCallbacks.get, widget); + completeInit(); + }); + } + // Build widget & load content. + function init() { // Delete controls that don't apply to this widget. if (!widget.settings) { delete widget.controls.settings; + widget.settings = {}; } if (!widget.fullscreenUrl) { delete widget.controls.fullscreen; } - - widget.element.attr('id', 'widget-' + widget.id).addClass(widget.classes); - throbber.remove(); + var cssClass = 'widget-' + widget.name.replace('/', '-'); + widget.element.attr('id', 'widget-' + widget.id).addClass(cssClass); // Build and add the widget's DOM element. - $(widget.element).append(widgetHTML()).trigger('crmLoad'); + $(widget.element).append(widgetHTML()); // Save the content element so that external scripts can reload it easily. widget.contentElement = $('.widget-content', widget.element); $.each(widget.controls, widget.addControl); - // Switch the initial state so that it initializes to the correct state. - widget.minimized = !widget.minimized; - widget.toggleMinimize(); - getJavascript(widget.initScript); - invokeCallback(opts.widgetCallbacks.get, widget); - - // completeInit() is a private method of the dashboard. Let it complete initialization of the dashboard. - completeInit(); + if (widget.minimized) { + widget.contentElement.hide(); + } else { + loadContent(); + } } // Builds inner HTML for widgets. @@ -528,7 +446,7 @@ var html = ''; html += '
    '; html += '

    ' + widget.title + '

    '; - html += '
    ' + widget.content + '
    '; + html += '
    '; html += '
    '; return html; } @@ -605,8 +523,7 @@ $.fn.dashboard.defaults = { columns: 2, emptyPlaceholderInner: ts('There are no dashlets in this column of your dashboard.'), - fullscreenHeaderInner: ts('Back to dashboard mode'), - throbberMarkup: '
    ' + ts('Loading') + '...
    ', + throbberMarkup: '', animationSpeed: 200, callbacks: {}, widgetCallbacks: {} @@ -616,8 +533,8 @@ $.fn.dashboard.widget = { defaults: { minimized: false, - settings: false, - fullscreen: false + settings: false + // url, fullscreenUrl, content, title, name } }; })(jQuery); diff --git a/templates/CRM/common/dashboard.tpl b/templates/CRM/common/dashboard.tpl index 57ca82ed0c..0c682d2e53 100644 --- a/templates/CRM/common/dashboard.tpl +++ b/templates/CRM/common/dashboard.tpl @@ -43,42 +43,6 @@ CRM.$(function($) { // These define the urls and data objects used for all of the ajax requests to the server. ajaxCallbacks: { - // Given the widget ID, the server returns the widget object as an associative array. - // E.g. {content: '

    Widget content

    ', title: 'Widget Title', } - // - // Required properties: - // * title: Text string. Widget title - // * content: HTML string. Widget content - // - // Optional properties: - // * classes: String CSS classes that will be added to the widget's
  • - // * fullscreen: HTML string for the content of the widget's full screen display. - // * settings: Boolean. True if widget has settings pane/display and server-side - // callback. - // - // Server-side executable script callbacks are called and executed on certain - // events. They can use the widgets property of the dashboard object returned - // from jQuery.dashboard(). E.g. dashboard.widgets[widgetID]. They should be - // javascript files on the server. Set the property to the path of the js file: - // * initScript: Called when dashboard is initialising (but not finished). - // * fullscreenInitScript: Called when the full screen element is initialising - // (being created for the first time). - // * fullscreenScript: Called when switching into full screen mode. Executed - // every time the widget goes into full-screen mode. - // * reloadContentScript: Called when the widget's reloadContent() method is - // called. (The widget.reloadContent() method is not used internally so must - // have either the callback function or this server-side executable javascript - // file implemented for the method to do anything.) - // - // The 'id' property of data is reserved for the widget ID – a string. - getWidget: { - url: {/literal}'{crmURL p='civicrm/ajax/dashboard' h=0 }'{literal}, - data: { - // id: widgetID, - op: 'get_widget', key: {/literal}"{crmKey name='civicrm/ajax/dashboard'}"{literal} - } - }, - // jQuery.dashboard() POSTs the widget-to-column settings here. The server's // response is sent to console.log() (if it exists), but is not used. No checks // for errors have been implemented yet. @@ -119,81 +83,9 @@ CRM.$(function($) { op: 'widget_settings', key: {/literal}"{crmKey name='civicrm/ajax/dashboard'}"{literal} } } - }, - - // Optional javascript callbacks for dashboard events. - // All callbacks have the dashboard object available as the 'this' variable. - // This property and all of it's members are optional. - callbacks: { - // Called when dashboard is initializing. - init: function() { - var dashboard = this; - }, - - // Called when dashboard has finished initializing. - ready: function() { - var dashboard = this; - }, - - // Called when dashboard has saved columns to the server. - saveColumns: function() { - var dashboard = this; - }, - - // Called when a widget has gone into fullscreen mode. - // Takes one argument for the widget. - enterFullscreen: function(widget) { - var dashboard = this; - }, - - // Called when a widget has come out of fullscreen mode. - // Takes one argument for the widget. - exitFullscreen: function(widget) { - var dashboard = this; - } - }, - - // Optional javascript callbacks for widget events. - // All callbacks have the respective widget object available as the 'this' variable. - // This property and all of it's members are optional. - widgetCallbacks: { - // Called when a widget has been gotten from the server. - get: function() { - var widget = this; - }, - - // Called when an external script has invoked the widget.reloadContent() method. - // (The widget.reloadContent() method is not used internally so must have either - // this callback function or a server-side executable javascript file implemented - // for the method to do anything.) - reloadContent: function() { - var widget = this; - }, - - // Called when the widget has gone into settings-edit mode. - showSettings: function() { - var widget = this; - }, - - // Called when the widget's settings have been saved to the server. - saveSettings: function() { - var widget = this; - }, - - // Called when the widget has gone out of settings-edit mode. - hideSettings: function() { - var widget = this; - }, - - // Called when the widget has been removed from the dashboard. - remove: function() { - var widget = this; - } } - }; - // Initialize the dashboard using these options - $('#civicrm-dashboard').dashboard(options); + }; $('#civicrm-dashboard') .on('mouseover', '.widget-header', function() { @@ -201,7 +93,8 @@ CRM.$(function($) { }) .on('mouseout', '.widget-header', function() { $(this).closest('.widget-wrapper').removeClass('db-hover-handle'); - }); + }) + .dashboard(options); }); -- 2.25.1