Browse Source

N°635 Portal: Forms now have 2 differents layout (display_mode), see online documentation (when released) for more information.

Note: This is a first step, some refactoring could be done soon.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4735 a333f486-631f-4898-b8df-5754b55c2be0
glajarige 8 năm trước cách đây
mục cha
commit
d770d803e3

+ 2 - 1
datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php

@@ -1,6 +1,6 @@
 <?php
 
-// Copyright (C) 2010-2015 Combodo SARL
+// Copyright (C) 2010-2017 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -661,6 +661,7 @@ class ObjectController extends AbstractController
 		$aFormData['renderer'] = $oFormManager->GetRenderer();
 		$aFormData['object_name'] = $oFormManager->GetObject()->GetName();
 		$aFormData['fieldset'] = $aFieldSetData;
+        $aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
 
 		return $aFormData;
 	}

+ 34 - 19
datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php

@@ -1,10 +1,10 @@
 <?php
 
-// Copyright (C) 2010-2016 Combodo SARL
+// Copyright (C) 2010-2017 Combodo SARL
 //
 //   This file is part of iTop.
 //
-//   iTop is free software; you can redistribute it and/or modify	
+//   iTop is free software; you can redistribute it and/or modify
 //   it under the terms of the GNU Affero General Public License as published by
 //   the Free Software Foundation, either version 3 of the License, or
 //   (at your option) any later version.
@@ -19,6 +19,7 @@
 
 namespace Combodo\iTop\Portal\Form;
 
+use Combodo\iTop\Form\Field\Field;
 use \Exception;
 use \Silex\Application;
 use \utils;
@@ -136,7 +137,7 @@ class ObjectFormManager extends FormManager
 	}
 
 	/**
-	 * 
+	 *
 	 * @return \Silex\Application
 	 */
 	public function GetApplication()
@@ -277,7 +278,7 @@ class ObjectFormManager extends FormManager
 		$aJson['formmode'] = $this->sMode;
 		$aJson['formactionrulestoken'] = $this->sActionRulesToken;
 		$aJson['formproperties'] = $this->aFormProperties;
-		
+
 		return $aJson;
 	}
 
@@ -384,12 +385,20 @@ class ObjectFormManager extends FormManager
 				{
 					$oFieldNode->setAttribute('data-form-path', $oForm->GetId());
 				}
-
-				// Checking if field should be displayed opened (For linked set)
+                // Checking if field should be displayed opened (For linked set)
                 if($oFieldNode->hasAttribute('data-field-opened') && ($oFieldNode->getAttribute('data-field-opened') === 'true') )
                 {
                     $aFieldsExtraData[$sFieldId]['opened'] = true;
                 }
+                // Checking field display mode
+                if($oFieldNode->hasAttribute('data-field-display-mode') && $oFieldNode->getAttribute('data-field-display-mode') !== '')
+                {
+                    $aFieldsExtraData[$sFieldId]['display_mode'] = $oFieldNode->getAttribute('data-field-display-mode');
+                }
+                else
+                {
+                    $aFieldsExtraData[$sFieldId]['display_mode'] = $this->aFormProperties['properties']['display_mode'];
+                }
 
 				// Settings field flags from the data-field-flags attribute
 				foreach (explode(' ', $sFieldFlags) as $sFieldFlag)
@@ -436,7 +445,7 @@ class ObjectFormManager extends FormManager
 				{
 					$iFieldFlags = $this->oObject->GetAttributeFlags($sAttCode);
 				}
-				
+
 				// Merging flags with those from the form definition
 				// - only if the field if it's in fields list
 				if (array_key_exists($sAttCode, $aFieldsAtts))
@@ -459,12 +468,12 @@ class ObjectFormManager extends FormManager
 		foreach ($aFieldsAtts as $sAttCode => $iFieldFlags)
 		{
 			$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sAttCode);
-			
+
 			// Failsafe for AttributeType that would not have MakeFormField and therefore could not be used in a form
 			if (is_callable(get_class($oAttDef) . '::MakeFormField'))
 			{
 				$oField = $oAttDef->MakeFormField($this->oObject);
-				
+
 				if ($this->sMode !== static::ENUM_MODE_VIEW)
 				{
 					// Field dependencies
@@ -510,7 +519,7 @@ class ObjectFormManager extends FormManager
 					{
 						// Normal field
 					}
-					
+
 					// Specific operation on field
 					// - Field that require a transaction id
 					if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\TextAreaField', 'Combodo\\iTop\\Form\\Field\\CaseLogField')))
@@ -546,7 +555,7 @@ class ObjectFormManager extends FormManager
 						if ($this->oApp !== null)
 						{
 							$oScopeOriginal = ($oField->GetSearch() !== null) ? $oField->GetSearch() : DBSearch::FromOQL($oAttDef->GetValuesDef()->GetFilterExpression());
-							
+
 							$oScopeSearch = $this->oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oScopeOriginal->GetClass(), UR_ACTION_READ);
 							if ($oScopeSearch === null)
 							{
@@ -638,8 +647,6 @@ class ObjectFormManager extends FormManager
                         $oField->SetDisplayOpened(true);
                     }
 				}
-
-				$oForm->AddField($oField);
 			}
 			else
 			{
@@ -648,10 +655,18 @@ class ObjectFormManager extends FormManager
 					->SetHidden(false)
 					->SetCurrentValue(get_class($oAttDef) . ' : Sorry, that AttributeType is not implemented yet.')
 					->SetLabel($oAttDef->GetLabel());
-				$oForm->AddField($oField);
 			}
+
+			// Setting field display mode
+            if(array_key_exists($sAttCode, $aFieldsExtraData) && array_key_exists('display_mode', $aFieldsExtraData[$sAttCode]))
+            {
+                $sFieldDisplayMode = ($aFieldsExtraData[$sAttCode]['display_mode'] === ApplicationHelper::FORM_ENUM_DISPLAY_MODE_COSY) ? Field::ENUM_DISPLAY_MODE_VERTICAL : Field::ENUM_DISPLAY_MODE_HORIZONTAL;
+                $oField->SetDisplayMode($sFieldDisplayMode);
+            }
+
+            $oForm->AddField($oField);
 		}
-		
+
 		// Checking dependencies to ensure that all needed fields are in the form
 		// (This is kind of a garbage collector for dependancies)
 		foreach ($oForm->GetDependencies() as $sImpactedFieldId => $aDependancies)
@@ -663,7 +678,7 @@ class ObjectFormManager extends FormManager
 					$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sDependancyFieldId);
 					$oField = $oAttDef->MakeFormField($this->oObject);
 					$oField->SetHidden(true);
-					
+
 					$oForm->AddField($oField);
 				}
 			}
@@ -683,7 +698,7 @@ class ObjectFormManager extends FormManager
 					break;
 				}
 			}
-			
+
 			// Adding attachment field
 			if ($bClassAllowed)
 			{
@@ -916,7 +931,7 @@ class ObjectFormManager extends FormManager
 			$aData['valid'] = false;
 			$aData['messages']['error'] += $this->oForm->GetErrorMessages();
 		}
-		
+
 		return $aData;
 	}
 
@@ -1036,7 +1051,7 @@ class ObjectFormManager extends FormManager
 				}
 				$this->oObject->DoComputeValues();
 			}
-			
+
 			// Then we retrieve properties of the form to build
 			if (isset($aArgs['formProperties']))
 			{

+ 10 - 2
datamodels/2.x/itop-portal-base/portal/src/helpers/applicationhelper.class.inc.php

@@ -1,6 +1,6 @@
 <?php
 
-// Copyright (C) 2010-2015 Combodo SARL
+// Copyright (C) 2010-2017 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -46,6 +46,10 @@ use \Combodo\iTop\Portal\Brick\AbstractBrick;
  */
 class ApplicationHelper
 {
+    const FORM_ENUM_DISPLAY_MODE_COSY = 'cosy';
+    const FORM_ENUM_DISPLAY_MODE_COMPACT = 'compact';
+    const FORM_DEFAULT_DISPLAY_MODE = self::FORM_ENUM_DISPLAY_MODE_COSY;
+    const FORM_DEFAULT_ALWAYS_SHOW_SUBMIT = false;
 
 	/**
 	 * Loads classes from the base portal
@@ -853,7 +857,8 @@ class ApplicationHelper
 
                     // Parsing properties
                     $aFormProperties = array(
-                        'always_show_submit' => false,
+                        'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE,
+                        'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT,
                     );
                     if($oFormNode->GetOptionalElement('properties') !== null)
                     {
@@ -861,6 +866,9 @@ class ApplicationHelper
                         {
                             switch($oPropertyNode->nodeName)
                             {
+                                case 'display_mode':
+                                    $aFormProperties['display_mode'] = $oPropertyNode->GetText(static::FORM_DEFAULT_DISPLAY_MODE);
+                                    break;
                                 case 'always_show_submit':
                                     $aFormProperties['always_show_submit'] = ($oPropertyNode->GetText('false') === 'true') ? true : false;
                                     break;

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

@@ -2,10 +2,11 @@
 {# Object brick create layout #}
 
 {% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_form' %}
+{% set sFormClass = (form.display_mode is defined and form.display_mode is not null) ? 'form_' ~ form.display_mode : '' %}
 {% set sFormIdSanitized = sFormId|replace({'-': ''}) %}
 {% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
 
-<form id="{{ sFormId }}" method="POST" action="{{ form.renderer.GetEndpoint()|raw }}">
+<form id="{{ sFormId }}" class="{{ sFormClass }}" method="POST" action="{{ form.renderer.GetEndpoint()|raw }}">
 	<input type="hidden" name="transaction_id" value="{{ form.transaction_id }}" />
 	<div class="form_alerts">
 		{% block pFormAlerts %}

+ 49 - 19
datamodels/2.x/itop-portal-base/portal/web/css/bootstrap-theme-combodo.css

@@ -66,7 +66,7 @@ img {
   height: auto;
 }
 .img-rounded {
-  border-radius: 6px;
+  border-radius: 0px;
 }
 .img-thumbnail {
   padding: 4px;
@@ -375,7 +375,7 @@ kbd {
   font-size: 90%;
   color: #fff;
   background-color: #333;
-  border-radius: 3px;
+  border-radius: 0px;
   -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
   box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
 }
@@ -1388,7 +1388,7 @@ input[type="radio"][disabled], input[type="checkbox"][disabled], input[type="rad
   padding: 5px 10px;
   font-size: 12px;
   line-height: 1.5;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 select.input-sm {
   height: 30px;
@@ -1402,7 +1402,7 @@ textarea.input-sm, select[multiple].input-sm {
   padding: 5px 10px;
   font-size: 12px;
   line-height: 1.5;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 .form-group-sm select.form-control {
   height: 30px;
@@ -1423,7 +1423,7 @@ textarea.input-sm, select[multiple].input-sm {
   padding: 14px 16px;
   font-size: 17px;
   line-height: 1.33333;
-  border-radius: 6px;
+  border-radius: 0px;
 }
 select.input-lg {
   height: 53px;
@@ -1437,7 +1437,7 @@ textarea.input-lg, select[multiple].input-lg {
   padding: 14px 16px;
   font-size: 17px;
   line-height: 1.33333;
-  border-radius: 6px;
+  border-radius: 0px;
 }
 .form-group-lg select.form-control {
   height: 53px;
@@ -1906,19 +1906,19 @@ a.btn.disabled, fieldset[disabled] a.btn {
   padding: 14px 16px;
   font-size: 17px;
   line-height: 1.33333;
-  border-radius: 6px;
+  border-radius: 0px;
 }
 .btn-sm, .btn-group-sm > .btn {
   padding: 5px 10px;
   font-size: 12px;
   line-height: 1.5;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 .btn-xs, .btn-group-xs > .btn {
   padding: 1px 5px;
   font-size: 12px;
   line-height: 1.5;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 .btn-block {
   display: block;
@@ -2271,7 +2271,7 @@ tbody.collapse.in {
   padding: 14px 16px;
   font-size: 17px;
   line-height: 1.33333;
-  border-radius: 6px;
+  border-radius: 0px;
 }
 select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn {
   height: 53px;
@@ -2285,7 +2285,7 @@ textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-
   padding: 5px 10px;
   font-size: 12px;
   line-height: 1.5;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn {
   height: 30px;
@@ -2313,18 +2313,18 @@ textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-
   color: #777;
   text-align: center;
   background-color: #ddd;
-  border: 1px solid #ddd;
+  border: 1px solid #c7c7c7;
   border-radius: 0px;
 }
 .input-group-addon.input-sm {
   padding: 5px 10px;
   font-size: 12px;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 .input-group-addon.input-lg {
   padding: 14px 16px;
   font-size: 17px;
-  border-radius: 6px;
+  border-radius: 0px;
 }
 .input-group-addon input[type="radio"], .input-group-addon input[type="checkbox"] {
   margin-top: 0;
@@ -3334,7 +3334,7 @@ a.badge:hover, a.badge:focus {
   border-top-color: #dbdbdb;
 }
 .container .jumbotron, .container-fluid .jumbotron {
-  border-radius: 6px;
+  border-radius: 0px;
 }
 .jumbotron .container {
   max-width: 100%;
@@ -4022,11 +4022,11 @@ a.list-group-item-danger.active, button.list-group-item-danger.active, a.list-gr
 }
 .well-lg {
   padding: 24px;
-  border-radius: 6px;
+  border-radius: 0px;
 }
 .well-sm {
   padding: 9px;
-  border-radius: 3px;
+  border-radius: 0px;
 }
 .close {
   float: right;
@@ -4324,7 +4324,7 @@ button.close {
   background-clip: padding-box;
   border: 1px solid #ccc;
   border: 1px solid rgba(0, 0, 0, 0.2);
-  border-radius: 6px;
+  border-radius: 0px;
   -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
   box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
 }
@@ -4929,7 +4929,37 @@ label {
   box-shadow: -1px 1px 2px rgba(0, 0, 0, 0.4);
 }
 /* Help blocks in forms */
-.form_fields .form-group > .help-block {
+.form_fields .form-group .help-block {
   margin-top: 0px;
   margin-bottom: 0px;
 }
+/* Overriding Twitter Typeahead */
+.input-group span.twitter-typeahead {
+  height: initial;
+}
+.input-group span.twitter-typeahead:first-child .form-control, .input-group span.twitter-typeahead:last-child .form-control, .input-group span.twitter-typeahead .form-control {
+  border-radius: 0px;
+}
+.input-group span.twitter-typeahead .tt-suggestion.tt-cursor, .input-group span.twitter-typeahead .tt-suggestion:hover, .input-group span.twitter-typeahead .tt-suggestion:focus {
+  color: #fff;
+  background-color: #ea7d1e;
+}
+/* Compact form display */
+.form_compact .form-group.form_group_small {
+  margin-bottom: 10px;
+}
+.form_compact .form-group.form_group_small .control-label {
+  margin-right: 0.5em;
+}
+.form_compact .form-group.form_group_small .form-control-static {
+  display: inline;
+}
+.form_compact .form-group.form_group_small .form-control {
+  height: 28px;
+  padding: 4px 5px;
+  font-size: 12px;
+}
+.form_compact .form-group.form_group_small .input-group-addon {
+  padding: 4px 10px;
+  font-size: 12px;
+}

+ 37 - 2
datamodels/2.x/itop-portal-base/portal/web/css/bootstrap-theme-combodo.scss

@@ -2995,7 +2995,7 @@ select[multiple].input-group-sm>.input-group-btn>.btn {
     color: $gray;
     text-align: center;
     background-color: #dddddd;
-    border: 1px solid #dddddd;
+    border: 1px solid #c7c7c7;
     border-radius: $border-radius-base
 }
 .input-group-addon.input-sm {
@@ -6012,7 +6012,42 @@ label {
 	box-shadow: -1px 1px 2px rgba(0, 0, 0, 0.4);
 }
 /* Help blocks in forms */
-.form_fields .form-group > .help-block{
+.form_fields .form-group .help-block{
     margin-top: 0px;
     margin-bottom: 0px;
+}
+/* Overriding Twitter Typeahead */
+.input-group span.twitter-typeahead{
+    height: initial;
+}
+.input-group span.twitter-typeahead:first-child .form-control,
+.input-group span.twitter-typeahead:last-child .form-control,
+.input-group span.twitter-typeahead .form-control{
+    border-radius: $border-radius-base;
+}
+.input-group span.twitter-typeahead .tt-suggestion.tt-cursor,
+.input-group span.twitter-typeahead .tt-suggestion:hover,
+.input-group span.twitter-typeahead .tt-suggestion:focus{
+    color: $btn-primary-color;
+    background-color: $btn-primary-bg;
+}
+/* Compact form display */
+.form_compact .form-group.form_group_small{
+    margin-bottom: 10px;
+
+    .control-label{
+        margin-right: 0.5em;
+    }
+    .form-control-static{
+        display: inline;
+    }
+    .form-control{
+        height: 28px;
+        padding: 4px 5px;
+        font-size: 12px;
+    }
+    .input-group-addon {
+        padding: 4px 10px;
+        font-size: 12px;
+    }
 }

+ 4 - 4
datamodels/2.x/itop-portal-base/portal/web/css/portal.css

@@ -258,10 +258,6 @@ footer {
   padding-right: 0px;
   /* To align all actions on the right without indent */
 }
-/* Custom "glyphicons" */
-.glyphicon-ext-hierarchy:before {
-  content: url('../img/icons/hierarchy-white-13px.png');
-}
 /******************/
 /* Modal settings */
 /******************/
@@ -853,6 +849,10 @@ table .group-actions {
   color: #ea7d1e;
   font-size: 0.9em;
 }
+/* ExternalKey */
+.selectobject .input-group-addon {
+  cursor: pointer;
+}
 /* InlineImage */
 .inline-image {
   cursor: pointer;

+ 5 - 6
datamodels/2.x/itop-portal-base/portal/web/css/portal.scss

@@ -272,11 +272,6 @@ footer{
 	padding-right: 0px; /* To align all actions on the right without indent */
 }
 
-/* Custom "glyphicons" */
-.glyphicon-ext-hierarchy:before {
-    content: url('../img/icons/hierarchy-white-13px.png');
-}
-
 /******************/
 /* Modal settings */
 /******************/
@@ -920,6 +915,10 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 	color: $brand-primary;
 	font-size: 0.9em;
 }
+/* ExternalKey */
+.selectobject .input-group-addon{
+	cursor: pointer;
+}
 /* InlineImage */
 .inline-image{
 	cursor: pointer;
@@ -1242,4 +1241,4 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 /* Wiki text (hyperlinks) */
 .wiki_broken_link {
 	text-decoration: line-through;
-}
+}

+ 2 - 2
datamodels/2.x/itop-portal-base/portal/web/css/variables.scss

@@ -130,8 +130,8 @@ $line-height-large:         1.3333333 !default; // extra decimals for Win 8.1 Ch
 $line-height-small:         1.5 !default;
 
 $border-radius-base:        0px !default;
-$border-radius-large:       6px !default;
-$border-radius-small:       3px !default;
+$border-radius-large:       $border-radius-base; // Default: 6px !default;
+$border-radius-small:       $border-radius-base; // Default: 3px !default;
 
 //** Global color for active items (e.g., navs or dropdowns).
 $component-active-color:    #fff !default;

+ 16 - 8
js/field_set.js

@@ -279,14 +279,22 @@ $(function()
 					this._loadJsFile(oField.js_files[i]);
 				}
 			}
-			// CSS files
-			if( (oField.css_files !== undefined) && (oField.css_files.length > 0) )
-			{
-				for(var i in oField.css_files)
-				{
-					this._loadCssFile(oField.css_files[i]);
-				}
-			}
+            // CSS files
+            if( (oField.css_files !== undefined) && (oField.css_files.length > 0) )
+            {
+                for(var i in oField.css_files)
+                {
+                    this._loadCssFile(oField.css_files[i]);
+                }
+            }
+            // CSS classes
+            if( (oField.css_classes !== undefined) && (oField.css_classes.length > 0) )
+            {
+                for(var i in oField.css_classes)
+                {
+                    oFieldContainer.addClass(oField.css_classes[i]);
+                }
+            }
 			// JS inline
 			if( (oField.js_inline !== undefined) && (oField.js_inline !== '') )
 			{

+ 37 - 1
sources/form/field/field.class.inc.php

@@ -1,6 +1,6 @@
 <?php
 
-// Copyright (C) 2010-2016 Combodo SARL
+// Copyright (C) 2010-2017 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -30,10 +30,14 @@ use \Combodo\iTop\Form\Validator\MandatoryValidator;
  */
 abstract class Field
 {
+    const ENUM_DISPLAY_MODE_VERTICAL = 'vertical';      // Label and value side by side
+    const ENUM_DISPLAY_MODE_HORIZONTAL = 'horizontal';  // Label above value
+
 	const DEFAULT_LABEL = '';
 	const DEFAULT_HIDDEN = false;
 	const DEFAULT_READ_ONLY = false;
 	const DEFAULT_MANDATORY = false;
+    const DEFAULT_DISPLAY_MODE = self::ENUM_DISPLAY_MODE_HORIZONTAL;
 	const DEFAULT_VALID = true;
 
 	protected $sId;
@@ -43,6 +47,7 @@ abstract class Field
 	protected $bHidden;
 	protected $bReadOnly;
 	protected $bMandatory;
+	protected $sDisplayMode;
 	protected $aValidators;
 	protected $bValid;
 	protected $aErrorMessages;
@@ -64,6 +69,7 @@ abstract class Field
 		$this->bHidden = static::DEFAULT_HIDDEN;
 		$this->bReadOnly = static::DEFAULT_READ_ONLY;
 		$this->bMandatory = static::DEFAULT_MANDATORY;
+		$this->sDisplayMode = static::DEFAULT_DISPLAY_MODE;
 		$this->aValidators = array();
 		$this->bValid = static::DEFAULT_VALID;
 		$this->aErrorMessages = array();
@@ -136,6 +142,15 @@ abstract class Field
 		return $this->bMandatory;
 	}
 
+    /**
+     *
+     * @return string
+     */
+	public function GetDisplayMode()
+    {
+        return $this->sDisplayMode;
+    }
+
 	/**
 	 *
 	 * @return array
@@ -256,6 +271,17 @@ abstract class Field
 		return $this;
 	}
 
+    /**
+     *
+     * @param $sDisplayMode
+     * @return $this
+     */
+	public function SetDisplayMode($sDisplayMode)
+    {
+        $this->sDisplayMode = $sDisplayMode;
+        return $this;
+    }
+
 	/**
 	 *
 	 * @param array $aValidators
@@ -364,6 +390,16 @@ abstract class Field
 		return $this;
 	}
 
+    /**
+     * Returns if the field should be displayed horizontally (label and value side by side)
+     *
+     * @return bool
+     */
+	public function IsHorizontalDisplayMode()
+    {
+        return $this->sDisplayMode === static::ENUM_DISPLAY_MODE_HORIZONTAL;
+    }
+
 	/**
 	 * Returns if the field is editable. Meaning that it is not editable nor hidden.
 	 * 

+ 45 - 27
sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php

@@ -50,6 +50,7 @@ class BsSelectObjectFieldRenderer extends FieldRenderer
 		$oOutput = new RenderingOutput();
 		$sFieldValueClass = $this->oField->GetSearch()->GetClass();
 		$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
+        $sFieldContainerClass = ($this->oField->IsHorizontalDisplayMode() && !$this->oField->GetHidden()) ? 'row' : '';
 		$iFieldControlType = $this->oField->GetControlType();
 
 		// TODO : Remove this when hierarchical search supported
@@ -59,11 +60,19 @@ class BsSelectObjectFieldRenderer extends FieldRenderer
 		if (!$this->oField->GetReadOnly() && !$this->oField->GetHidden())
 		{
 			// Rendering field
-			$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
+            // - Opening container
+			$oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldMandatoryClass . ' ' . $sFieldContainerClass . '">');
+
+			// Label
+            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-3">'); }
 			if ($this->oField->GetLabel() !== '')
 			{
 				$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
 			}
+            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
+
+            // Value
+            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-9">'); }
 			$oOutput->AddHtml('<div class="help-block"></div>');
 			// - As a select
 			// TODO : This should be changed when we do the radio button display. For now we display everything with select
@@ -77,7 +86,7 @@ class BsSelectObjectFieldRenderer extends FieldRenderer
 				// Note : Autocomplete/Search is disabled for template fields as they are not external keys, thus they will just be displayed as regular select.
 				$bRegularSelect = ( ($iSetCount <= $this->oField->GetMaximumComboLength()) || ($this->oField->GetSearchEndpoint() === null) || ($this->oField->GetSearchEndpoint() === '') );
 				unset($oCountSet);
-
+$bRegularSelect=false;
 				// - For regular select
 				if ($bRegularSelect)
 				{
@@ -158,24 +167,18 @@ EOF
 					}
 
 					// HTML for autocomplete part
-					// - Opening row
-					$oOutput->AddHtml('<div class="row">');
-					// - Rendering autocomplete search
-					$oOutput->AddHtml('<div class="col-xs-' . ( $this->oField->GetHierarchical() ? 9 : 10 ) . ' col-sm-' . ( $this->oField->GetHierarchical() ? 8 : 9 ) . ' col-md-' . ( $this->oField->GetHierarchical() ? 8 : 10 ) . ' col-lg-' . ( $this->oField->GetHierarchical() ? 9 : 10 ) . '">');
-					$oOutput->AddHtml('<input type="text" id="' . $sAutocompleteFieldId . '" name="' . $sAutocompleteFieldId . '" value="')->AddHtml($sFieldValue)->AddHtml('" data-target-id="' . $this->oField->GetGlobalId() . ' "class="form-control" />');
-					$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $this->oField->GetCurrentValue() . '" />');
-					$oOutput->AddHtml('</div>');
-					// - Rendering buttons
-					$oOutput->AddHtml('<div class="col-xs-' . ( $this->oField->GetHierarchical() ? 3 : 2 ) . ' col-sm-' . ( $this->oField->GetHierarchical() ? 4 : 3 ) . ' col-md-' . ( $this->oField->GetHierarchical() ? 4 : 2 ) . ' col-lg-' . ( $this->oField->GetHierarchical() ? 3 : 2 ) . ' text-right">');
-					$oOutput->AddHtml('<div class="btn-group" role="group">');
-					//   - Rendering hierarchy button
-					$this->RenderHierarchicalSearch($oOutput);
-					//   - Rendering regular search
-					$this->RenderRegularSearch($oOutput);
-					$oOutput->AddHtml('</div>');
-					$oOutput->AddHtml('</div>');
-					// - Closing row
-					$oOutput->AddHtml('</div>');
+                    // - Opening input group
+                    $oOutput->AddHtml('<div class="input-group selectobject">');
+                    // - Rendering autocomplete search
+                    $oOutput->AddHtml('<input type="text" id="' . $sAutocompleteFieldId . '" name="' . $sAutocompleteFieldId . '" value="')->AddHtml($sFieldValue)->AddHtml('" data-target-id="' . $this->oField->GetGlobalId() . ' "class="form-control" />');
+                    $oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $this->oField->GetCurrentValue() . '" />');
+                    // - Rendering buttons
+                    //   - Rendering hierarchy button
+                    $this->RenderHierarchicalSearch($oOutput);
+                    //   - Rendering regular search
+                    $this->RenderRegularSearch($oOutput);
+                    // - Closing input group
+                    $oOutput->AddHtml('</div>');
 
 					// JS FieldChange trigger (:input are not always at the same depth)
 					// Note : Not used for that field type
@@ -296,6 +299,9 @@ EOF
 					);
 				}
 			}
+            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
+
+            // - Closing container
 			$oOutput->AddHtml('</div>');
 		}
 		// ... and in read-only mode (or hidden)
@@ -313,19 +319,31 @@ EOF
 				$sFieldValue = Dict::S('UI:UndefinedObject');
 			}
 
-			$oOutput->AddHtml('<div class="form-group">');
+			// Opening container
+			$oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldContainerClass . '">');
+
 			// Showing label / value only if read-only but not hidden
 			if (!$this->oField->GetHidden())
 			{
+			    // Label
+                if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-3">'); }
 				if ($this->oField->GetLabel() !== '')
 				{
 					$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
 				}
+                if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
+
+                // Value
+                if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-9">'); }
 				$oOutput->AddHtml('<div class="form-control-static">' . $sFieldValue . '</div>');
+                if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
 			}
+
+			// Adding hidden value
 			$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $this->oField->GetCurrentValue() . '" class="form-control" />');
-			$oOutput->AddHtml('</div>');
 
+			// Closing container
+			$oOutput->AddHtml('</div>');
 
 			// JS FieldChange trigger (:input are not always at the same depth)
 			$oOutput->AddJs(
@@ -362,12 +380,12 @@ EOF
 	 */
 	protected function RenderHierarchicalSearch(RenderingOutput &$oOutput)
 	{
-		if ($this->oField->GetHierarchical())
-		{
+        if ($this->oField->GetHierarchical())
+        {
 			$sHierarchicalButtonId = 's_hi_' . $this->oField->GetGlobalId();
 			$sEndpoint = str_replace('-sMode-', 'hierarchy', $this->oField->GetSearchEndpoint());
 
-			$oOutput->AddHtml('<button type="button" class="btn btn-default" id="' . $sHierarchicalButtonId . '"><span class="glyphicon glyphicon-ext-hierarchy"></span></button>');
+			$oOutput->AddHtml('<div class="input-group-addon" id="' . $sHierarchicalButtonId . '"><span class="fa fa-sitemap"></span></div>');
 
 			$oOutput->AddJs(
 <<<EOF
@@ -411,8 +429,8 @@ EOF
 		$sSearchButtonId = 's_rg_' . $this->oField->GetGlobalId();
 		$sEndpoint = str_replace('-sMode-', 'from-attribute', $this->oField->GetSearchEndpoint());
 
-		$oOutput->AddHtml('<button type="button" class="btn btn-default" id="' . $sSearchButtonId . '"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></button>');
-		
+        $oOutput->AddHtml('<div class="input-group-addon" id="' . $sSearchButtonId . '"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></div>');
+
 		$oOutput->AddJs(
 <<<EOF
 			$('#{$sSearchButtonId}').off('click').on('click', function(){

+ 107 - 107
sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php

@@ -1,6 +1,6 @@
 <?php
 
-// Copyright (C) 2010-2016 Combodo SARL
+// Copyright (C) 2010-2017 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -47,58 +47,81 @@ class BsSimpleFieldRenderer extends FieldRenderer
 		$oOutput = new RenderingOutput();
 		$sFieldClass = get_class($this->oField);
 		$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
+		$sFieldContainerClass = ($this->oField->IsHorizontalDisplayMode() && !$this->oField->GetHidden()) ? 'row' : '';
 		
 		// Rendering field in edition mode
 		if (!$this->oField->GetReadOnly() && !$this->oField->GetHidden())
 		{
 			switch ($sFieldClass)
 			{
-				case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
-					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
-					if ($this->oField->GetLabel() !== '')
-					{
-						$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
-					}
-					$oOutput->AddHtml('<div class="help-block"></div>');
-					$oOutput->AddHtml('<div class="input-group date" id="datepicker_' . $this->oField->GetGlobalId() . '">');
-					$oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
-					$oOutput->AddHtml('<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>');
-					$oOutput->AddHtml('</div>');
-					$oOutput->AddHtml('</div>');
-					$sJSFormat = json_encode($this->oField->GetJSDateTimeFormat());
-					$oOutput->AddJs(
-<<<EOF
-					$('#datepicker_{$this->oField->GetGlobalId()}').datetimepicker({format: $sJSFormat});
+                case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
+                case 'Combodo\\iTop\\Form\\Field\\PasswordField':
+                case 'Combodo\\iTop\\Form\\Field\\StringField':
+                case 'Combodo\\iTop\\Form\\Field\\UrlField':
+                case 'Combodo\\iTop\\Form\\Field\\SelectField':
+                case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
+                    // Opening container
+                    $oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldMandatoryClass . ' ' . $sFieldContainerClass . '">');
+
+                    // Label
+                    if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-3">'); }
+                    if ($this->oField->GetLabel() !== '')
+                    {
+                        $oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
+                    }
+                    if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
+
+                    // Value
+                    if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-9">'); }
+                    // - Help block
+                    $oOutput->AddHtml('<div class="help-block"></div>');
+                    // - Value regarding the field type
+                    switch($sFieldClass)
+                    {
+                        case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
+                            $oOutput->AddHtml('<div class="input-group date" id="datepicker_' . $this->oField->GetGlobalId() . '">');
+                            $oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
+                            $oOutput->AddHtml('<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>');
+                            $oOutput->AddHtml('</div>');
+                            $sJSFormat = json_encode($this->oField->GetJSDateTimeFormat());
+                            $oOutput->AddJs(
+                                <<<EOF
+                                					$('#datepicker_{$this->oField->GetGlobalId()}').datetimepicker({format: $sJSFormat});
 EOF
-					);
-					break;
-				case 'Combodo\\iTop\\Form\\Field\\PasswordField':
-					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
-					if ($this->oField->GetLabel() !== '')
-					{
-						$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
-					}
-					$oOutput->AddHtml('<div class="help-block"></div>');
-					$oOutput->AddHtml('<input type="password" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" autocomplete="off" />');
-					$oOutput->AddHtml('</div>');
-					break;
+                            );
+                            break;
+
+                        case 'Combodo\\iTop\\Form\\Field\\PasswordField':
+                            $oOutput->AddHtml('<input type="password" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" autocomplete="off" />');
+                            break;
+
+                        case 'Combodo\\iTop\\Form\\Field\\StringField':
+                        case 'Combodo\\iTop\\Form\\Field\\UrlField':
+                            $oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
+                            break;
+
+                        case 'Combodo\\iTop\\Form\\Field\\SelectField':
+                        case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
+                            $oOutput->AddHtml('<select id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" ' . ( ($this->oField->GetMultipleValuesEnabled()) ? 'multiple' : '' ) . ' class="form-control">');
+                            foreach ($this->oField->GetChoices() as $sChoice => $sLabel)
+                            {
+                                // Note : The test is a double equal on purpose as the type of the value received from the XHR is not always the same as the type of the allowed values. (eg : string vs int)
+                                $sSelectedAtt = ($this->oField->GetCurrentValue() == $sChoice) ? 'selected' : '';
+                                $oOutput->AddHtml('<option value="' . $sChoice . '" ' . $sSelectedAtt . ' >')->AddHtml($sLabel)->AddHtml('</option>');
+                            }
+                            $oOutput->AddHtml('</select>');
+                            break;
+                    }
+                    if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
 
-				case 'Combodo\\iTop\\Form\\Field\\StringField':
-                case 'Combodo\\iTop\\Form\\Field\\UrlField':
-					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
-					if ($this->oField->GetLabel() !== '')
-					{
-						$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
-					}
-					$oOutput->AddHtml('<div class="help-block"></div>');
-					$oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
-					$oOutput->AddHtml('</div>');
-					break;
+                    // Closing container
+                    $oOutput->AddHtml('</div>');
+                    break;
 
 				case 'Combodo\\iTop\\Form\\Field\\TextAreaField':
 				case 'Combodo\\iTop\\Form\\Field\\CaseLogField':
 					$bRichEditor = ($this->oField->GetFormat() === TextAreaField::ENUM_FORMAT_HTML);
-					
+
 					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
 					if ($this->oField->GetLabel() !== '')
 					{
@@ -133,25 +156,6 @@ EOF
 					}
 					break;
 
-				case 'Combodo\\iTop\\Form\\Field\\SelectField':
-				case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
-					$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
-					if ($this->oField->GetLabel() !== '')
-					{
-						$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
-					}
-					$oOutput->AddHtml('<div class="help-block"></div>');
-					$oOutput->AddHtml('<select id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" ' . ( ($this->oField->GetMultipleValuesEnabled()) ? 'multiple' : '' ) . ' class="form-control">');
-					foreach ($this->oField->GetChoices() as $sChoice => $sLabel)
-					{
-						// Note : The test is a double equal on purpose as the type of the value received from the XHR is not always the same as the type of the allowed values. (eg : string vs int)
-						$sSelectedAtt = ($this->oField->GetCurrentValue() == $sChoice) ? 'selected' : '';
-						$oOutput->AddHtml('<option value="' . $sChoice . '" ' . $sSelectedAtt . ' >')->AddHtml($sLabel)->AddHtml('</option>');
-					}
-					$oOutput->AddHtml('</select>');
-					$oOutput->AddHtml('</div>');
-					break;
-
 				case 'Combodo\\iTop\\Form\\Field\\RadioField':
 				case 'Combodo\\iTop\\Form\\Field\\CheckboxField':
 					$sFieldType = ($sFieldClass === 'Combodo\\iTop\\Form\\Field\\RadioField') ? 'radio' : 'checkbox';
@@ -198,21 +202,42 @@ EOF
 				switch ($sFieldClass)
 				{
 					case 'Combodo\\iTop\\Form\\Field\\LabelField':
-						$oOutput->AddHtml('<div class="form-group">');
+                    case 'Combodo\\iTop\\Form\\Field\\StringField':
+                    case 'Combodo\\iTop\\Form\\Field\\UrlField':
+                    case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
+                    case 'Combodo\\iTop\\Form\\Field\\DurationField':
+                        // Opening container
+						$oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldContainerClass . '">');
+
 						// Showing label / value only if read-only but not hidden
 						if (!$this->oField->GetHidden())
 						{
+						    // Label
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-3">'); }
 							if ($this->oField->GetLabel() !== '')
 							{
 								$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
 							}
-							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('</div>');
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
+
+                            // Value
+                            $bEncodeHtmlEntities = ($sFieldClass === 'Combodo\\iTop\\Form\\Field\\UrlField') ? false : true;
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-9">'); }
+							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($this->oField->GetDisplayValue(), $bEncodeHtmlEntities)->AddHtml('</div>');
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
 						}
+
+						// Adding hidden input if not a label
+                        if($sFieldClass !== 'Combodo\\iTop\\Form\\Field\\LabelField')
+                        {
+                            $sValueForInput = ($sFieldClass === 'Combodo\\iTop\\Form\\Field\\DateTimeField') ? $this->oField->GetDisplayValue() : $this->oField->GetCurrentValue();
+                            $oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($sValueForInput, true)->AddHtml('" class="form-control" />');
+                        }
+
+                        // Closing container
 						$oOutput->AddHtml('</div>');
 						break;
 
-                    case 'Combodo\\iTop\\Form\\Field\\StringField':
-                    case 'Combodo\\iTop\\Form\\Field\\UrlField':
 					case 'Combodo\\iTop\\Form\\Field\\TextAreaField':
 						$oOutput->AddHtml('<div class="form-group">');
 						// Showing label / value only if read-only but not hidden
@@ -223,18 +248,10 @@ EOF
 								$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
 							}
 
-							if($sFieldClass === 'Combodo\\iTop\\Form\\Field\\UrlField' || $sFieldClass === 'Combodo\\iTop\\Form\\Field\\TextAreaField')
-							{
-								$bEncodeHtmlEntities = false;
-								$sDisplayValue = $this->oField->GetDisplayValue();
-							}
-							else
-							{
-								$bEncodeHtmlEntities = true;
-								$sDisplayValue = $this->oField->GetCurrentValue();
-							}
-							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($sDisplayValue, $bEncodeHtmlEntities)->AddHtml('</div>');
+
+							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($this->oField->GetDisplayValue(), false)->AddHtml('</div>');
 						}
+						// Adding hidden input
 						$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" />');
 						$oOutput->AddHtml('</div>');
 						break;
@@ -250,37 +267,7 @@ EOF
 						$oOutput->AddHtml('</div>');
 						break;
 
-					case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
-						$oOutput->AddHtml('<div class="form-group">');
-						// Showing label / value only if read-only but not hidden
-						if (!$this->oField->GetHidden())
-						{
-							if ($this->oField->GetLabel() !== '')
-							{
-								$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
-							}
-							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('</div>');
-						}
-						$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('" class="form-control" />');
-						$oOutput->AddHtml('</div>');
-						break;
-
-					case 'Combodo\\iTop\\Form\\Field\\DurationField':
-						$oOutput->AddHtml('<div class="form-group">');
-						// Showing label / value only if read-only but not hidden
-						if (!$this->oField->GetHidden())
-						{
-							if ($this->oField->GetLabel() !== '')
-							{
-								$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
-							}
-							$oOutput->AddHtml('<div class="form-control-static">')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('</div>');
-						}
-						$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" />');
-						$oOutput->AddHtml('</div>');
-						break;
-
-                    case 'Combodo\\iTop\\Form\\Field\\BlobField':
+					case 'Combodo\\iTop\\Form\\Field\\BlobField':
                     case 'Combodo\\iTop\\Form\\Field\\ImageField':
 						$oOutput->AddHtml('<div class="form-group">');
 						// Showing label / value only if read-only but not hidden
@@ -311,17 +298,30 @@ EOF
 						$aFieldChoices = $this->oField->GetChoices();
 						$sFieldValue = (isset($aFieldChoices[$this->oField->GetCurrentValue()])) ? $aFieldChoices[$this->oField->GetCurrentValue()] : Dict::S('UI:UndefinedObject');
 
-						$oOutput->AddHtml('<div class="form-group">');
+						// Opening container
+						$oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldContainerClass . '">');
+
 						// Showing label / value only if read-only but not hidden
 						if (!$this->oField->GetHidden())
 						{
+						    // Label
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-3">'); }
 							if ($this->oField->GetLabel() !== '')
 							{
 								$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
 							}
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
+
+                            // Value
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('<div class="col-sm-9">'); }
 							$oOutput->AddHtml('<div class="form-control-static">' . $sFieldValue . '</div>');
+                            if($this->oField->IsHorizontalDisplayMode()){ $oOutput->AddHtml('</div>'); }
 						}
+
+						// Adding hidden value
 						$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $this->oField->GetCurrentValue() . '" class="form-control" />');
+
+						// Closing container
 						$oOutput->AddHtml('</div>');
 						break;
 

+ 8 - 1
sources/renderer/formrenderer.class.inc.php

@@ -223,11 +223,13 @@ abstract class FormRenderer
 			'js_inline' => '',
 			'css_inline' => '',
 			'js_files' => array(),
-			'css_files' => array()
+			'css_files' => array(),
+            'css_classes' => array(),
 		);
 
 		$sFieldRendererClass = $this->GetFieldRendererClass($oField);
 
+		/** @var FieldRenderer $oFieldRenderer */
 		$oFieldRenderer = new $sFieldRendererClass($oField);
 		$oFieldRenderer->SetEndpoint($this->GetEndpoint());
 
@@ -301,6 +303,11 @@ abstract class FormRenderer
 				$output['html'] .= '<style>' . $oRenderingOutput->GetCss() . '</style>';
 			}
 		}
+        // CSS classes
+        if ($oRenderingOutput->GetHtml() !== '')
+        {
+            $output['css_classes'] = $oRenderingOutput->GetCssClasses();
+        }
 
 		return $output;
 	}

+ 66 - 27
sources/renderer/renderingoutput.class.inc.php

@@ -31,6 +31,7 @@ class RenderingOutput
 	protected $aJsFiles;
 	protected $sCssInline;
 	protected $aCssFiles;
+	protected $aCssClasses;
 
 	public function __construct()
 	{
@@ -39,6 +40,7 @@ class RenderingOutput
 		$this->aJsFiles = array();
 		$this->sCssInline = '';
 		$this->aCssFiles = array();
+		$this->aCssClasses = array();
 	}
 
 	/**
@@ -86,6 +88,15 @@ class RenderingOutput
 		return $this->aCssFiles;
 	}
 
+    /**
+     *
+     * @return array
+     */
+	public function GetCssClasses()
+    {
+        return $this->aCssClasses;
+    }
+
 	/**
 	 *
 	 * @param string $sHtml
@@ -147,32 +158,60 @@ class RenderingOutput
 		return $this;
 	}
 
-	/**
-	 *
-	 * @param string $sFile
-	 * @return \Combodo\iTop\Renderer\RenderingOutput
-	 */
-	public function AddCssFile($sFile)
-	{
-		if (!in_array($sFile, $this->aCssFiles))
-		{
-			$this->aCssFiles[] = $sFile;
-		}
-		return $this;
-	}
-
-	/**
-	 *
-	 * @param string $sFile
-	 * @return \Combodo\iTop\Renderer\RenderingOutput
-	 */
-	public function RemoveCssFile($sFile)
-	{
-		if (in_array($sFile, $this->aCssFiles))
-		{
-			unset($this->aCssFiles[$sFile]);
-		}
-		return $this;
-	}
+    /**
+     *
+     * @param string $sFile
+     * @return \Combodo\iTop\Renderer\RenderingOutput
+     */
+    public function AddCssFile($sFile)
+    {
+        if (!in_array($sFile, $this->aCssFiles))
+        {
+            $this->aCssFiles[] = $sFile;
+        }
+        return $this;
+    }
+
+    /**
+     *
+     * @param string $sFile
+     * @return \Combodo\iTop\Renderer\RenderingOutput
+     */
+    public function RemoveCssFile($sFile)
+    {
+        if (in_array($sFile, $this->aCssFiles))
+        {
+            unset($this->aCssFiles[$sFile]);
+        }
+        return $this;
+    }
+
+    /**
+     *
+     * @param string $sClass
+     * @return \Combodo\iTop\Renderer\RenderingOutput
+     */
+    public function AddCssClass($sClass)
+    {
+        if (!in_array($sClass, $this->aCssClasses))
+        {
+            $this->aCssClasses[] = $sClass;
+        }
+        return $this;
+    }
+
+    /**
+     *
+     * @param string $sClass
+     * @return \Combodo\iTop\Renderer\RenderingOutput
+     */
+    public function RemoveCssClass($sClass)
+    {
+        if (in_array($sClass, $this->aCssClasses))
+        {
+            unset($this->aCssClasses[$sClass]);
+        }
+        return $this;
+    }
 
 }