From 95e04178eaa9cebef145586fee95de633f3ec577 Mon Sep 17 00:00:00 2001 From: Guillaume Lajarige Date: Fri, 17 Mar 2017 13:03:09 +0000 Subject: [PATCH] =?UTF-8?q?N=C2=B0636=20Portal:=20Action=20buttons=20can?= =?UTF-8?q?=20now=20be=20added=20to=20object=20details=20page=20through=20?= =?UTF-8?q?the=20iPopupMenuItemExtension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:trunk[4600] --- application/applicationextension.inc.php | 98 ++++++++++++++++++- application/webpage.class.inc.php | 2 +- .../objectcontroller.class.inc.php | 39 ++++++-- .../views/bricks/object/mode_create.html.twig | 9 ++ .../views/bricks/object/mode_view.html.twig | 12 +-- .../bricks/object/plugins_buttons.html.twig | 61 ++++++++++++ .../portal/web/css/portal.css | 6 ++ .../portal/web/css/portal.scss | 6 ++ 8 files changed, 214 insertions(+), 19 deletions(-) create mode 100644 datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 63655d79a..87eace0ae 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -308,6 +308,37 @@ interface iPopupMenuExtension * $param is null */ const MENU_USER_ACTIONS = 5; + /** + * Insert an item into the Action menu on an object details page in the portal + * + * $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed) + */ + const PORTAL_OBJDETAILS_ACTIONS = 7; + + /** + * Insert an item into the Actions menu of a list in the portal + * Note: This is not implemented yet ! + * + * $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects + * @todo + */ + const PORTAL_OBJLIST_ACTIONS = 6; + /** + * Insert an item into the user menu of the portal + * Note: This is not implemented yet ! + * + * $param is the portal id + * @todo + */ + const PORTAL_USER_ACTIONS = 8; + /** + * Insert an item into the navigation menu of the portal + * Note: This is not implemented yet ! + * + * $param is the portal id + * @todo + */ + const PORTAL_MENU_ACTIONS = 9; /** * Get the list of items to be added to a menu. @@ -334,17 +365,21 @@ abstract class ApplicationPopupMenuItem protected $sUID; /** @ignore */ protected $sLabel; + /** @ignore */ + protected $aCssClasses; /** * Constructor * * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough - * @param string $sLabel The display label of the menu (must be localized) - */ + * @param string $sLabel The display label of the menu (must be localized) + * @param array $aCssClasses The CSS classes to add to the menu + */ public function __construct($sUID, $sLabel) { $this->sUID = $sUID; $this->sLabel = $sLabel; + $this->aCssClasses = array(); } /** @@ -368,6 +403,35 @@ abstract class ApplicationPopupMenuItem { return $this->sLabel; } + + /** + * Get the CSS classes + * + * @return array + * @ignore + */ + public function GetCssClasses() + { + return $this->aCssClasses; + } + + /** + * @param $aCssClasses + */ + public function SetCssClasses($aCssClasses) + { + $this->aCssClasses = $aCssClasses; + } + + /** + * Adds a CSS class to the CSS classes that will be put on the menu item + * + * @param $sCssClass + */ + public function AddCssClass($sCssClass) + { + $this->aCssClasses[] = $sCssClass; + } /** * Returns the components to create a popup menu item in HTML @@ -415,7 +479,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem /** @ignore */ public function GetMenuItem() { - return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget); + return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses); } } @@ -451,7 +515,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem public function GetMenuItem() { // Note: the semicolumn is a must here! - return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#'); + return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#', 'css_classes' => $this->aCssClasses); } /** @ignore */ @@ -483,10 +547,34 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem /** @ignore */ public function GetMenuItem() { - return array ('label' => '', 'url' => ''); + return array ('label' => '', 'url' => '', 'css_classes' => $this->aCssClasses); } } +/** + * Class for adding an item as a button that browses to the given URL + * + * @package Extensibility + * @api + * @since 2.0 + */ +class URLButtonItem extends URLPopupMenuItem +{ + +} + +/** + * Class for adding an item as a button that runs some JS code + * + * @package Extensibility + * @api + * @since 2.0 + */ +class JSButtonItem extends JSPopupMenuItem +{ + +} + /** * Implement this interface to add content to any iTopWebPage * diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index cd85fe705..149cb3277 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -734,7 +734,7 @@ class WebPage implements Page { foreach ($aActions as $aAction) { - $sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : ""; + $sClass = isset($aAction['css_classes']) ? ' class="'.implode(' ', $aAction['css_classes']).'"' : ''; $sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : ''; $sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : ""; if (empty($aAction['url'])) diff --git a/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php index b0dccf400..a42c45a3c 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php @@ -43,6 +43,9 @@ use \cmdbAbstractObject; use \AttributeEnum; use \AttributeFinalClass; use \UserRights; +use \iPopupMenuExtension; +use \URLButtonItem; +use \JSButtonItem; use \Combodo\iTop\Portal\Helper\ApplicationHelper; use \Combodo\iTop\Portal\Helper\SecurityHelper; use \Combodo\iTop\Portal\Helper\ContextManipulatorHelper; @@ -101,10 +104,13 @@ class ObjectController extends AbstractController // Add an edit button if user is allowed if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId)) { - $aData['form']['buttons']['links'][] = array( - 'label' => Dict::S('UI:Menu:Modify'), - 'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)) - ); + $oModifyButton = new URLButtonItem( + 'modify_object', + Dict::S('UI:Menu:Modify'), + $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)) + ); + // Putting this one first + $aData['form']['buttons']['links'][] = $oModifyButton->GetMenuItem(); } // Preparing response @@ -467,7 +473,9 @@ class ObjectController extends AbstractController // Preparing transitions only if we are currently going through one $aFormData['buttons'] = array( - 'transitions' => array() + 'transitions' => array(), + 'actions' => array(), + 'links' => array(), ); if ($sMode !== 'apply_stimulus') { @@ -482,6 +490,25 @@ class ObjectController extends AbstractController $aFormData['buttons']['transitions'][$sStimulusCode] = $aStimuli[$sStimulusCode]->GetLabel(); } } + + // Add plugins buttons + foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) + { + foreach($oExtensionInstance->EnumItems(iPopupMenuExtension::PORTAL_OBJDETAILS_ACTIONS, array('portal_id' => $oApp['combodo.portal.instance.id'], 'object' => $oObject)) as $oMenuItem) + { + if (is_object($oMenuItem)) + { + if($oMenuItem instanceof JSButtonItem) + { + $aFormData['buttons']['actions'][] = $oMenuItem->GetMenuItem() + array('js_files' => $oMenuItem->GetLinkedScripts()); + } + elseif($oMenuItem instanceof URLButtonItem) + { + $aFormData['buttons']['links'][] = $oMenuItem->GetMenuItem(); + } + } + } + } } // Preparing callback urls $aCallbackUrls = $oApp['context_manipulator']->GetCallbackUrls($oApp, $aActionRules, $oObject, $bModal); @@ -1499,5 +1526,3 @@ class ObjectController extends AbstractController } } - -?> \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig b/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig index 5f2875802..608da60f4 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig @@ -21,6 +21,13 @@
{% block pFormButtons %} + {# Misc. buttons #} + {% if form.buttons is defined and (form.buttons.actions is defined or form.buttons.links is defined) %} +
+ {% include 'itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %} +
+ {% endif %} + {# Transition buttons #} {% if form.buttons is defined and form.buttons.transitions is defined and form.buttons.transitions|length > 0 %}
{% for sStimulusCode, sStimulusLabel in form.buttons.transitions %} @@ -29,6 +36,7 @@
{% endif %}
+ {# If form has editable fields, we display cancel / submit buttons #} {% if form.editable_fields_count is defined and form.editable_fields_count > 0 %} {% else %} + {# Modal mode #} {% if tIsModal %} {% endif %} diff --git a/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig b/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig index ef157e823..d005a82e9 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig @@ -5,16 +5,16 @@ {# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #} {% block pFormButtons %} - {% if form.buttons is defined and form.buttons.links is defined %} -
- {% for aLink in form.buttons.links %} - {{ aLink.label }} - {% endfor %} + {# Misc. buttons #} + {% if form.buttons is defined and (form.buttons.actions is defined or form.buttons.links is defined) %} +
+ {% include 'itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
{% endif %} + {% if tIsModal is defined and tIsModal == true %}
- +
{% endif %} {% endblock %} \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig b/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig new file mode 100644 index 000000000..40eaefaf0 --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig @@ -0,0 +1,61 @@ +{% set iLinkButtonsCount = (aButtons.links is defined) ? aButtons.links|length : 0 %} +{% set iActionButtonsCount = (aButtons.actions is defined) ? aButtons.actions|length : 0 %} +{% set iButtonsCount = iLinkButtonsCount + iActionButtonsCount %} +{% set iButtonsGroupThreshold = 2 %} +{% set bGroupButtons = (iButtonsCount > iButtonsGroupThreshold) ? true : false %} + +{% set sButtonCssClasses = (bGroupButtons) ? '' : 'btn btn-default' %} +{% set aJSFiles = [] %} + +{% if bGroupButtons == true %} +
+ + +
+{% endif %} + +{# Loading JS files #} +{% if aJSFiles|length > 0 %} + +{% endif %} + diff --git a/datamodels/2.x/itop-portal-base/portal/web/css/portal.css b/datamodels/2.x/itop-portal-base/portal/web/css/portal.css index 67d2a2d13..5809e3fde 100644 --- a/datamodels/2.x/itop-portal-base/portal/web/css/portal.css +++ b/datamodels/2.x/itop-portal-base/portal/web/css/portal.css @@ -1056,6 +1056,9 @@ table .group-actions { padding-top: 20px; text-align: center; } +.form_buttons .form_btn_misc { + margin-bottom: 20px; +} .form_buttons .form_btn_transitions { margin-bottom: 20px; } @@ -1067,6 +1070,9 @@ table .group-actions { } @media (min-width: 768px) { /* Making regular button sticky */ + .form_buttons .form_btn_misc { + float: left !important; + } .form_buttons .form_btn_transitions { float: right !important; margin-left: 3px; diff --git a/datamodels/2.x/itop-portal-base/portal/web/css/portal.scss b/datamodels/2.x/itop-portal-base/portal/web/css/portal.scss index 56e9bc38d..28e736d4d 100644 --- a/datamodels/2.x/itop-portal-base/portal/web/css/portal.scss +++ b/datamodels/2.x/itop-portal-base/portal/web/css/portal.scss @@ -1126,6 +1126,9 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{ padding-top: 20px; text-align: center; } +.form_buttons .form_btn_misc { + margin-bottom: 20px; +} .form_buttons .form_btn_transitions{ margin-bottom: 20px; } @@ -1136,6 +1139,9 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{ display: none; } @media (min-width: 768px){ + .form_buttons .form_btn_misc{ + float: left !important; + } .form_buttons .form_btn_transitions{ float: right !important; margin-left: 3px;