Pārlūkot izejas kodu

N°636 Portal: Action buttons can now be added to object details page through the iPopupMenuItemExtension

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4600 a333f486-631f-4898-b8df-5754b55c2be0
glajarige 8 gadi atpakaļ
vecāks
revīzija
9466c2f7f1

+ 93 - 5
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,11 +547,35 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
 	/** @ignore */
 	public function GetMenuItem()
 	{
-		return array ('label' => '<hr class="menu-separator">', 'url' => '');
+		return array ('label' => '<hr class="menu-separator">', '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
  * 
  * There are 3 places where content can be added:

+ 1 - 1
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']))

+ 32 - 7
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
 	}
 
 }
-
-?>

+ 9 - 0
datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig

@@ -21,6 +21,13 @@
 	</div>
 	<div class="form_buttons">
 		{% block pFormButtons %}
+            {# Misc. buttons #}
+            {% if form.buttons is defined and (form.buttons.actions is defined or form.buttons.links is defined) %}
+				<div class="form_btn_misc">
+                    {% include 'itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
+				</div>
+            {% endif %}
+			{# Transition buttons #}
 			{% if form.buttons is defined and form.buttons.transitions is defined and form.buttons.transitions|length > 0 %}
 				<div class="form_btn_transitions">
 				{% for sStimulusCode, sStimulusLabel in form.buttons.transitions %}
@@ -29,6 +36,7 @@
 				</div>
 			{% endif %}
 			<div class="form_btn_regular">
+				{# If form has editable fields, we display cancel / submit buttons #}
 				{% if form.editable_fields_count is defined and form.editable_fields_count > 0 %}
 					<button class="btn btn-default form_btn_cancel" type="button" value="cancel" title="{{ 'Portal:Button:Cancel'|dict_s }}" data-dismiss="modal">
 						<span class="glyphicon glyphicon-remove"></span>
@@ -39,6 +47,7 @@
 						{{ 'Portal:Button:Submit'|dict_s }}
 					</button>
 				{% else %}
+					{# Modal mode #}
 					{% if tIsModal %}
 						<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal">
 					{% endif %}

+ 6 - 6
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 %}
-		<div class="form_btn_transitions">
-			{% for aLink in form.buttons.links %}
-				<a class="btn btn-primary" href="{{ aLink.url }}">{{ aLink.label }}</a>
-			{% endfor %}
+	{# Misc. buttons #}
+	{% if form.buttons is defined and (form.buttons.actions is defined or form.buttons.links is defined) %}
+		<div class="form_btn_misc">
+            {% include 'itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
 		</div>
 	{% endif %}
+
 	{% if tIsModal is defined and tIsModal == true %}
 		<div class="form_btn_regular">
-			<input class="btn btn-primary form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal">
+			<input class="btn btn-primary form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal" />
 		</div>
 	{% endif %}
 {% endblock %}

+ 61 - 0
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 %}
+	<div class="dropup">
+		<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+			{{ 'UI:Menu:OtherActions'|dict_s }}
+			<span class="caret"></span>
+		</button>
+		<ul class="dropdown-menu">
+{% endif %}
+
+	{# JSs buttons #}
+	{% if aButtons.actions is defined %}
+		{% for aButton in aButtons.actions %}
+			{# Adding button #}
+			{% if bGroupButtons == true %}
+				<li>
+			{% endif %}
+			<a class="{{ sButtonCssClasses }} {{ aButton.css_classes|join(' ') }}" href="{{ aButton.url }}" onclick="{{ aButton.onclick }}">{{ aButton.label }}</a>
+			{% if bGroupButtons == true %}
+				</li>
+			{% endif %}
+			{# Preparing JS files to load #}
+			{% set aJSFiles = aJSFiles|merge(aButton.js_files) %}
+		{% endfor %}
+	{%  endif %}
+	{# URLs buttons #}
+	{% if aButtons.links is defined %}
+		{% for aButton in aButtons.links %}
+            {% if bGroupButtons == true %}
+				<li>
+			{% endif %}
+			<a class="{{ sButtonCssClasses }} {{ aButton.css_classes|join(' ') }}" href="{{ aButton.url }}" target="{{ aButton.target }}">{{ aButton.label }}</a>
+            {% if bGroupButtons == true %}
+				</li>
+            {% endif %}
+		{% endfor %}
+	{%  endif %}
+
+{% if bGroupButtons == true %}
+		</ul>
+	</div>
+{% endif %}
+
+{# Loading JS files #}
+{% if aJSFiles|length > 0 %}
+	<script type="text/javascript">
+		{% for sJSFile in aJSFiles %}
+		console.log('loading {{ sJSFile }}');
+			$.getScript('{{ sJSFile }}');
+		{% endfor %}
+	</script>
+{% endif %}
+

+ 6 - 0
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;

+ 6 - 0
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;