Selaa lähdekoodia

N°634: BrowseBrick Windows 8-style display (tiles). This is still a beta version.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4567 a333f486-631f-4898-b8df-5754b55c2be0
glajarige 8 vuotta sitten
vanhempi
commit
7aa3a217bf

+ 1 - 0
datamodels/2.x/itop-portal-base/cs.dict.itop-portal-base.php

@@ -87,6 +87,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
     'Brick:Portal:Browse:Name' => 'Procházet položky',
     'Brick:Portal:Browse:Mode:List' => 'Seznam',
     'Brick:Portal:Browse:Mode:Tree' => 'Strom',
+    'Brick:Portal:Browse:Mode:Grid' => 'Tiles~~',
     'Brick:Portal:Browse:Action:Drilldown' => 'Rozpad',
     'Brick:Portal:Browse:Action:View' => 'Podrobnosti',
     'Brick:Portal:Browse:Action:Edit' => 'Upravit',

+ 1 - 0
datamodels/2.x/itop-portal-base/de.dict.itop-portal-base.php

@@ -83,6 +83,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
 	'Brick:Portal:Browse:Name' => 'List durchgehen',
 	'Brick:Portal:Browse:Mode:List' => 'Liste',
 	'Brick:Portal:Browse:Mode:Tree' => 'Baum',
+    'Brick:Portal:Browse:Mode:Grid' => 'Tiles~~',
 	'Brick:Portal:Browse:Action:Drilldown' => 'Drilldown',
 	'Brick:Portal:Browse:Action:View' => 'Details',
 	'Brick:Portal:Browse:Action:Edit' => 'Editieren',

+ 1 - 0
datamodels/2.x/itop-portal-base/en.dict.itop-portal-base.php

@@ -83,6 +83,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'Brick:Portal:Browse:Name' => 'Browse throught items',
 	'Brick:Portal:Browse:Mode:List' => 'List',
 	'Brick:Portal:Browse:Mode:Tree' => 'Tree',
+    'Brick:Portal:Browse:Mode:Grid' => 'Tiles',
 	'Brick:Portal:Browse:Action:Drilldown' => 'Drilldown',
 	'Brick:Portal:Browse:Action:View' => 'Details',
 	'Brick:Portal:Browse:Action:Edit' => 'Edit',

+ 1 - 0
datamodels/2.x/itop-portal-base/es_cr.dict.itop-portal-base.php

@@ -83,6 +83,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
 	'Brick:Portal:Browse:Name' => 'Buscar en todos los elementos',
 	'Brick:Portal:Browse:Mode:List' => 'Lista',
 	'Brick:Portal:Browse:Mode:Tree' => 'Árbol',
+    'Brick:Portal:Browse:Mode:Grid' => 'Tiles~~',
 	'Brick:Portal:Browse:Action:Drilldown' => 'Desglose',
 	'Brick:Portal:Browse:Action:View' => 'Detalles',
 	'Brick:Portal:Browse:Action:Edit' => 'Editar',

+ 2 - 1
datamodels/2.x/itop-portal-base/fr.dict.itop-portal-base.php

@@ -82,7 +82,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
 Dict::Add('FR FR', 'French', 'Français', array(
 	'Brick:Portal:Browse:Name' => 'Navigation dans les éléments',
 	'Brick:Portal:Browse:Mode:List' => 'Liste',
-	'Brick:Portal:Browse:Mode:Tree' => 'Hiérarchie',
+    'Brick:Portal:Browse:Mode:Tree' => 'Hiérarchie',
+    'Brick:Portal:Browse:Mode:Grid' => 'Tuiles',
 	'Brick:Portal:Browse:Action:Drilldown' => 'Parcourir',
 	'Brick:Portal:Browse:Action:View' => 'Détails',
 	'Brick:Portal:Browse:Action:Edit' => 'Modifier',

+ 1 - 1
datamodels/2.x/itop-portal-base/module.itop-portal-base.php

@@ -2,7 +2,7 @@
 
 SetupWebPage::AddModule(
 	__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
-	'itop-portal-base/1.0.1', array(
+	'itop-portal-base/1.1.0', array(
 	// Identification
 	'label' => 'Portal Development Library',
 		'category' => 'Portal',

+ 64 - 13
datamodels/2.x/itop-portal-base/portal/src/controllers/browsebrickcontroller.class.inc.php

@@ -41,6 +41,7 @@ use \Combodo\iTop\Portal\Brick\BrowseBrick;
 class BrowseBrickController extends BrickController
 {
 	const LEVEL_SEPARATOR = '-';
+	public static $aOptionalAttributes = array('tooltip_att', 'description_att', 'image_att');
 
 	public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sBrowseMode = null, $sDataLoading = null)
 	{
@@ -78,7 +79,7 @@ class BrowseBrickController extends BrickController
 		// Building DBobjectSearch
 		$oQuery = null;
 		// ... In this case only we have to build a specific query for the current level only
-		if (($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_TREE) && ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY))
+		if (in_array($sBrowseMode, array(BrowseBrick::ENUM_BROWSE_MODE_TREE, BrowseBrick::ENUM_BROWSE_MODE_GRID)) && ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY))
 		{
 			// Will be handled later in the pagination part
 		}
@@ -231,6 +232,7 @@ class BrowseBrickController extends BrickController
 
 					break;
 				case BrowseBrick::ENUM_BROWSE_MODE_TREE:
+                case BrowseBrick::ENUM_BROWSE_MODE_GRID:
 					// Retrieving parameters
 					$sLevelAlias = $oRequest->get('sLevelAlias');
 					$sNodeId = $oRequest->get('sNodeId');
@@ -298,7 +300,7 @@ class BrowseBrickController extends BrickController
 				$aTmpLevelProperties = $aLevelsProperties[$sTmpClassAlias];
 				// Mandatory main attribute
 				$aTmpColumnAttrs = array($aTmpLevelProperties['name_att']);
-				// Optionnal attributes, only if in list mode
+				// Optional attributes, only if in list mode
 				if ($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_LIST)
 				{
 					foreach ($aTmpLevelProperties['fields'] as $aTmpField)
@@ -306,6 +308,15 @@ class BrowseBrickController extends BrickController
 						$aTmpColumnAttrs[] = $aTmpField['code'];
 					}
 				}
+                // Optional attributes
+                foreach(static::$aOptionalAttributes as $sOptionalAttribute)
+                {
+                    if($aTmpLevelProperties[$sOptionalAttribute] !== null)
+                    {
+                        $aTmpColumnAttrs[] = $aTmpLevelProperties[$sOptionalAttribute];
+                    }
+                }
+
 				$aColumnAttrs[$sTmpClassAlias] = $aTmpColumnAttrs;
 			}
 		}
@@ -321,6 +332,7 @@ class BrowseBrickController extends BrickController
 			switch ($sBrowseMode)
 			{
 				case BrowseBrick::ENUM_BROWSE_MODE_TREE:
+                case BrowseBrick::ENUM_BROWSE_MODE_GRID:
 					static::AddToTreeItems($aItems, $aCurrentRow, $aLevelsProperties);
 					break;
 
@@ -381,7 +393,7 @@ class BrowseBrickController extends BrickController
 	 *
 	 * Note : This is not in the BrowseBrick class because the classes should not rely on DBObjectSearch.
 	 *
-	 * @param Silex\Application $oApp
+	 * @param \Silex\Application $oApp
 	 * @param array $aLevels Levels from a BrowseBrick class
 	 * @param array $aLevelsProperties Reference to an array that will contain the flattened levels
 	 * @param string $sLevelAliasPrefix String that will be prefixed to the level ID as an unique path identifier
@@ -409,7 +421,9 @@ class BrowseBrickController extends BrickController
 					'title' => ($aLevel['title'] !== null) ? Dict::S($aLevel['title']) : MetaModel::GetName($oSearch->GetClass()),
 					'parent_att' => $aLevel['parent_att'],
 					'name_att' => $aLevel['name_att'],
-					'tooltip_att' => $aLevel['tooltip_att'],
+                    'tooltip_att' => $aLevel['tooltip_att'],
+                    'description_att' => $aLevel['description_att'],
+                    'image_att' => $aLevel['image_att'],
 					'search' => $oSearch,
 					'fields' => array(),
 					'actions' => array()
@@ -613,11 +627,29 @@ class BrowseBrickController extends BrickController
 				'action_rules_token' => static::PrepareActionRulesForItems($aItems, $key, $aLevelsProperties)
 			);
 
-			// Adding tooltip attribute if necessary
-			if ($aLevelsProperties[$key]['tooltip_att'] !== null)
-			{
-				$aRow[$key]['tooltip'] = $value->Get($aLevelsProperties[$key]['tooltip_att']);
-			}
+			// Adding optional attributes if necessary
+            foreach(static::$aOptionalAttributes as $sOptionalAttribute)
+            {
+                if ($aLevelsProperties[$key][$sOptionalAttribute] !== null)
+                {
+                    $sPropertyName = substr($sOptionalAttribute, 0, -4);
+
+                    $tmpAttValue = $value->Get($aLevelsProperties[$key][$sOptionalAttribute]);
+                    if($sOptionalAttribute === 'image_att')
+                    {
+                        if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty())
+                        {
+                            $tmpAttValue = $tmpAttValue->GetDisplayURL(get_class($value), $value->GetKey(), $aLevelsProperties[$key][$sOptionalAttribute]);
+                        }
+                        else
+                        {
+                            $tmpAttValue = MetaModel::GetAttributeDef(get_class($value), $aLevelsProperties[$key][$sOptionalAttribute])->Get('default_image');
+                        }
+                    }
+
+                    $aRow[$key][$sPropertyName] = $tmpAttValue;
+                }
+            }
 			// Adding fields attributes if necessary
 			if (!empty($aLevelsProperties[$key]['fields']))
 			{
@@ -687,10 +719,29 @@ class BrowseBrickController extends BrickController
 				'action_rules_token' => static::PrepareActionRulesForItems($aCurrentRowObjects, $aCurrentRowKeys[0], $aLevelsProperties)
 			);
 
-			if ($aLevelsProperties[$aCurrentRowKeys[0]]['tooltip_att'] !== null)
-			{
-				$aItems[$sCurrentIndex]['tooltip'] = $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['tooltip_att']);
-			}
+            // Adding optional attributes if necessary
+            foreach(static::$aOptionalAttributes as $sOptionalAttribute)
+            {
+                if ($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute] !== null)
+                {
+                    $sPropertyName = substr($sOptionalAttribute, 0, -4);
+
+                    $tmpAttValue = $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]);
+                    if($sOptionalAttribute === 'image_att')
+                    {
+                        if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty())
+                        {
+                            $tmpAttValue = $tmpAttValue->GetDisplayURL(get_class($aCurrentRowValues[0]), $aCurrentRowValues[0]->GetKey(), $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]);
+                        }
+                        else
+                        {
+                            $tmpAttValue = MetaModel::GetAttributeDef(get_class($aCurrentRowValues[0]), $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute])->Get('default_image');
+                        }
+                    }
+
+                    $aItems[$sCurrentIndex][$sPropertyName] = $tmpAttValue;
+                }
+            }
 		}
 
 		$aCurrentRowSliced = array_slice($aCurrentRow, 1);

+ 6 - 3
datamodels/2.x/itop-portal-base/portal/src/entities/browsebrick.class.inc.php

@@ -33,7 +33,8 @@ class BrowseBrick extends PortalBrick
 	const DEFAULT_HOME_ICON_CLASS = 'fa fa-map';
 	const DEFAULT_NAVIGATION_MENU_ICON_CLASS = 'fa fa-map fa-2x';
 	const ENUM_BROWSE_MODE_LIST = 'list';
-	const ENUM_BROWSE_MODE_TREE = 'tree';
+    const ENUM_BROWSE_MODE_TREE = 'tree';
+    const ENUM_BROWSE_MODE_GRID = 'grid';
 	const ENUM_ACTION_VIEW = 'view';
 	const ENUM_ACTION_EDIT = 'edit';
 	const ENUM_ACTION_DRILLDOWN = 'drilldown';
@@ -289,6 +290,8 @@ class BrowseBrick extends PortalBrick
 		$aLevel = array(
 			'parent_att' => null,
 			'tooltip_att' => null,
+            'description_att' => null,
+            'image_att' => null,
 			'title' => null,
 			'name_att' => static::DEFAULT_LEVEL_NAME_ATT,
 			'fields' => array(),
@@ -331,6 +334,8 @@ class BrowseBrick extends PortalBrick
 
 				case 'parent_att':
 				case 'tooltip_att':
+                case 'description_att':
+                case 'image_att':
 				case 'title':
 					$aLevel[$oLevelPropertyNode->nodeName] = $oLevelPropertyNode->GetText(null);
 					break;
@@ -458,5 +463,3 @@ class BrowseBrick extends PortalBrick
 	}
 
 }
-
-?>

+ 385 - 0
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/mode_grid.html.twig

@@ -0,0 +1,385 @@
+{# itop-portal-base/portal/src/views/bricks/browse/mode_grid.html.twig #}
+{# Browse brick grid mode layout #}
+{% extends 'itop-portal-base/portal/src/views/bricks/browse/layout.html.twig' %}
+
+{% block bBrowseMainContent %}
+	<div id="brick_content_grid">
+		{% block bBrowseGridContent %}
+			<div class="grid-group" data-level-id="L">
+			</div>
+		{% endblock %}
+	</div>
+
+	<div id="brick_content_empty" class="text-center">
+		{% block bBrowseGridEmpty %}
+			{{ 'Brick:Portal:Browse:Filter:NoData'|dict_s }}
+		{% endblock %}
+	</div>
+	<div id="brick_grid_overlay">
+		{% block bBrowseGridOverlay %}
+			<div class="overlay_content">
+				{% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
+			</div>
+		{% endblock %}
+	</div>
+{% endblock %}
+
+{% block pPageLiveScripts %}
+	{{ parent() }}
+	
+	<script type="text/javascript">
+		var sBrowseMode = '{{ sBrowseMode }}';
+		var oLevelsProperties = {{ aLevelsProperties|raw }};
+		var oRawDatas = {{ aItems|raw }};
+        var sGridEffectName = 'fade';
+        var oGridEffectOptions = {};
+        var iGridEffectDuration = 200;
+
+        // Show a loader over the grid
+		var showGridLoader = function()
+		{
+			$("#brick_content_grid").hide();
+			$('#brick_grid_overlay').show();
+		};
+		// Hide the loader over the tree
+		var hideGridLoader = function()
+		{
+			$('#brick_grid_overlay').hide();
+			$("#brick_content_grid").show();
+		}
+		// Registers the toggle listeners on the tree nodes. Used after every AJAX calls.
+		var registerToggleListeners = function()
+		{
+		    $('#brick_content_grid .grid-drilldown').off('click').on('click', function (oEvent) {
+				oEvent.preventDefault();
+
+				var me = this;
+				// Retrieving sublevel
+                var sublevelId = $(this).attr('data-level-alias') + '::' + $(this).attr('data-item-id');
+                var sublevelElem = $('#brick_content_grid .grid-group[data-level-id="'+sublevelId+'"]');
+
+				// Hidding current level
+                if(sublevelElem.length === 0)
+                {
+                    showGridLoader();
+                }
+                $(this).closest('.grid-group').hide(
+                    sGridEffectName,
+					oGridEffectOptions,
+					iGridEffectDuration,
+					function(){
+				        // Showing sublevel
+				        if(sublevelElem.length === 0)
+						{
+                            loadChildNodes($(me).attr('data-level-alias'), $(me).attr('data-item-id'));
+						}
+						else
+						{
+                            sublevelElem.show(sGridEffectName, oGridEffectOptions, iGridEffectDuration);
+						}
+					}
+				);
+			});
+
+            $('#brick_content_grid .grid-rollup').off('click').on('click', function (oEvent) {
+                oEvent.preventDefault();
+
+                // Retrieving upper level
+                var upperlevelId = $(this).attr('data-level-id');
+                var upperlevelElem = $('#brick_content_grid .grid-group[data-level-id="'+upperlevelId+'"]');
+
+                // Hidding current level
+                $(this).closest('.grid-group').hide(
+                    sGridEffectName,
+					oGridEffectOptions,
+					iGridEffectDuration,
+                    function(){
+                        // Showing upper level
+                        if(upperlevelElem.length === 0)
+                        {
+                            var upperlevelIdParts = upperlevelId.split('::');
+
+                            loadChildNodes(upperlevelIdParts[0], upperlevelIdParts[1]);
+                        }
+                        else
+                        {
+                            upperlevelElem.show(sGridEffectName, oGridEffectOptions, iGridEffectDuration);
+                        }
+                    }
+                );
+            });
+		};
+		// Registers the filter listeners on the tree.
+		var registerFilterListeners = function()
+		{
+			/*$('#brick_search_field').treeListFilter('#brick_content_grid', iSearchDelay, filterResultsHandler);*/
+		};
+		// Load current node childnodes throught AJAX
+		var loadChildNodes = function(sLevelAlias, sNodeId)
+		{
+			var sUrl = '{{ app.url_generator.generate('p_browse_brick_mode_tree', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseMode, 'sLevelAlias': '-sLevelAlias-', 'sNodeId': '-sNodeId-'})|raw }}';
+			sUrl = sUrl.replace(/-sLevelAlias-/, sLevelAlias).replace(/-sNodeId-/, sNodeId);
+
+            $.ajax(sUrl)
+			.done(function(data) {
+				// Building child nodes
+				for(index in data.data)
+				{
+					var sublevel = data.data[index];
+					var sublevelData = {};
+					sublevelData[sublevel.level_alias+"::"+sublevel.id] = sublevel;
+					buildGrid(sublevelData, sLevelAlias+"::"+sNodeId, false);
+				}
+				// Showing sublevel
+                $('#brick_content_grid .grid-group[data-level-id="'+sLevelAlias+"::"+sNodeId+'"]').show(sGridEffectName, oGridEffectOptions, iGridEffectDuration);
+
+				registerToggleListeners();
+			})
+			.fail(function() {
+				alert('{{ 'Error:XHR:Fail'|dict_s }}');
+			})
+			.always(function(){
+                hideGridLoader();
+			});
+		};
+		// Build tree nodes from data under the nodeId
+		var buildGrid = function(data, nodeId, isRootLevel)
+		{
+		    var iItemIndex;
+		    var oItemFlowLastOfLineIndex = {
+		        sm: 3,
+				md: 3,
+				lg: 4,
+			};
+
+			if(nodeId === undefined)
+			{
+				// We are on the root node
+				nodeId = 'L';
+			}
+			if(isRootLevel === undefined)
+			{
+				isRootLevel = true;
+			}
+
+			// Building node if necessary
+			if($('div[data-level-id="'+nodeId+'"]').length === 0)
+			{
+                $('#brick_content_grid').append( $('<div></div>').addClass('grid-group').attr('data-level-id', nodeId) );
+			}
+			// - Initializing item index
+			iItemIndex = $('div[data-level-id="'+nodeId+'"] .grid-item').length;
+
+			if(!isRootLevel)
+			{
+			    // Retrieving upper level id
+                var levelIdParts = nodeId.split('::');
+                var upperlevelId = $('.grid-item[data-level-alias="'+levelIdParts[0]+'"][data-item-id="'+levelIdParts[1]+'"]').closest('.grid-group').attr('data-level-id');
+
+                // Building back button
+                if( $('div[data-level-id="'+nodeId+'"] .grid-group-back').length === 0 ) {
+                    var backElem = $('<div></div>').addClass('grid-group-back');
+                    var aElem = $('<a></a>').addClass('grid-item').addClass('grid-rollup').attr('href', '#').attr('data-level-id', upperlevelId).html('<div class="grid-item-text"><span class="glyphicon glyphicon-arrow-left"></span></div>');
+
+                    iItemIndex++;
+
+                    backElem.append(aElem);
+                    $('div[data-level-id="' + nodeId + '"]').append(backElem);
+                }
+			}
+			else
+			{
+                $('div[data-level-id="'+nodeId+'"]').html('');
+			}
+			
+			$.each(data, function(i, item){
+				var levelId = item.level_alias+'::'+item.id;
+				var levelAltId = item.level_alias+'_'+item.id;
+				var levelActions = oLevelsProperties[item.level_alias].actions;
+				var levelActionsKeys = Object.keys(levelActions);
+				var levelPrimaryAction = levelActions[levelActionsKeys[0]];
+				var url = '';
+
+                // Building node
+                var itemElem  = $('<div></div>').addClass('grid-group-item');
+				var aElem   = $('<a></a>').addClass('grid-item').attr('data-item-id', item.id).attr('href', '#').attr('data-level-alias', item.level_alias);
+				var iItemFlags = 0;
+                iItemIndex++;
+                // - Adding stub div
+				var textElem = $('<div></div>').addClass('grid-item-text');
+                // - Adding image
+                if( (item.image !== undefined) && (item.image !== '') )
+                {
+                    iItemFlags += 4;
+                    aElem.append( $('<div></div>').addClass('grid-item-image').append( $('<img />').attr('src', item.image) ) );
+                }
+                // - Adding name
+                if( (item.name !== undefined) && (item.name !== '') )
+                {
+                    iItemFlags += 1;
+                    textElem.append( $('<div></div>').addClass('grid-item-name').text(item.name) );
+                }
+                // - Adding description
+                if( (item.description !== undefined) && (item.description !== '') )
+                {
+                    iItemFlags += 2;
+                    textElem.append( $('<div></div>').addClass('grid-item-description').text(item.description) );
+				}
+                aElem.append( textElem );
+				// - Adding CSS class to adjust the layout regarding which properties are available
+				aElem.addClass('grid-item-layout-'+iItemFlags);
+                // - Adding CSS class to adjust items flow regarding the screen size
+				for(var i in oItemFlowLastOfLineIndex)
+				{
+                    if(iItemIndex % oItemFlowLastOfLineIndex[i] === 0)
+                    {
+                        itemElem.addClass('ggi-last-'+i);
+                    }
+				}
+                // - Appending element
+				$('div[data-level-id="'+nodeId+'"]').append(itemElem);
+				itemElem.append(aElem);
+				
+				// Building tooltip for the node
+				if( (item.tooltip !== undefined) && (item.tooltip !== '') )
+				{
+					aElem.attr('title', item.tooltip).attr('data-toggle', 'tooltip').tooltip({html: true, trigger: 'hover', placement: 'top'});
+				}
+				
+				// Building actions for that node
+				switch(levelPrimaryAction.type)
+				{
+					case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}':
+						aElem.addClass('grid-drilldown');
+						break;
+					case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
+						url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
+						aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
+						break;
+					case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
+						url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
+						aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
+						break;
+					case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
+						url = levelPrimaryAction.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
+						url = AddParameterToUrl(url, 'ar_token', item.action_rules_token[levelPrimaryAction.type]);
+						aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
+						break;
+					default:
+						//console.log('Action "'+levelPrimaryAction.type+'" not implemented for primary action');
+						break;
+				}
+				
+				if(levelActionsKeys.length > 1)
+				{
+					// Retrieving secondary action
+					var actionsButtons = {};
+					for(j = 1; j < levelActionsKeys.length; j++)
+					{
+						actionsButtons[levelActionsKeys[j]] = levelActions[levelActionsKeys[j]];
+					}
+					
+					// Preparing secondary actions container
+					var actionsElem = $('<div></div>').addClass('grid-group-item-actions');
+					itemElem.append(actionsElem);
+					// Checking if a menu is necessary
+					var bHasSeveralSecondaryActions = (Object.keys(actionsButtons).length > 1);
+					// Preparing secondary actions menu
+					if(bHasSeveralSecondaryActions)
+					{
+						var actionsSSTogglerElem = $('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-'+levelAltId+'"></a>');
+						var actionsSSMenuElem = $('<div id="item-actions-menu-'+levelAltId+'" class="item-action-wrapper panel panel-default"></div>');
+						var actionsSSMenuContainerElem = $('<div class="panel-body"></div>');
+						actionsSSMenuElem.append(actionsSSMenuContainerElem);
+						actionsElem.append(actionsSSTogglerElem);
+						actionsElem.append(actionsSSMenuElem);
+					}
+					
+					// Adding secondary actions
+					for(j in actionsButtons)
+					{
+						var action = actionsButtons[j];
+						var actionElem = $('<a></a>');
+						var actionIconElem = $('<span></span>').appendTo(actionElem);
+						
+						switch(action.type)
+						{
+							case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
+								url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
+								actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
+								break;
+							case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
+								url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
+								actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
+								break;
+							case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
+								url = action.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
+								url = AddParameterToUrl(url, 'ar_token', item.action_rules_token[action.type]);
+								actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
+								break;
+							default:
+								//console.log('Action "'+action.type+'" not implemented for secondary action');
+								break;
+						}
+						
+						// Adding title if present
+						if(action.title !== undefined)
+						{
+							actionElem.attr('title', action.title);
+						}
+						// Adding icon class if present
+						if(action.icon_class !== undefined)
+						{
+							actionIconElem.addClass(action.icon_class);
+						}
+						
+						if(bHasSeveralSecondaryActions)
+						{
+							actionElem.append(action.title);
+							actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem) );
+						}
+						else
+						{
+							actionsElem.append(actionElem);
+						}
+					}
+				}
+				
+				// Building subnodes if necessary
+				if(item.subitems.length !== 0)
+				{
+                    var subitemsElem;
+				    if($('div[data-level-id="'+levelId+'"]').length > 0)
+					{
+                        subitemsElem = $('div[data-level-id="'+levelId+'"]');
+					}
+					else
+					{
+					    subitemsElem = $('<div></div>').addClass('grid-group').attr('data-level-id', levelId);
+                        $('div[data-level-id="'+nodeId+'"]').after(subitemsElem);
+					}
+					buildGrid(item.subitems, levelId, false);
+				}				
+			});
+			
+			// Update listeners
+			if(isRootLevel)
+			{
+				registerToggleListeners();
+			}
+		};
+		
+		$(document).ready(function(){
+			// Auto collapse item actions popup
+			$('body').click(function(){
+				$('#brick_content_grid .item-action-wrapper.collapse.in').collapse('hide');
+			});
+			
+			// Build the tree (collapsed)
+			showGridLoader();
+			buildGrid(oRawDatas);
+			hideGridLoader();
+			registerFilterListeners();
+		});
+	</script>
+{% endblock %}

+ 6 - 6
datamodels/2.x/itop-portal-base/portal/src/views/bricks/browse/mode_tree.html.twig

@@ -36,12 +36,7 @@
 	</div>
 	<div id="brick_tree_overlay">
 		<div class="overlay_content">
-			<div class="content_loader">
-				<div class="icon glyphicon glyphicon-refresh"></div>
-				<div class="message">
-					{{ 'Page:PleaseWait'|dict_s }}
-				</div>
-			</div>
+            {% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
 		</div>
 	</div>
 {% endblock %}
@@ -206,6 +201,11 @@
 				{
 					aElem.attr('title', item.tooltip).attr('data-toggle', 'tooltip').tooltip({html: true, trigger: 'hover', placement: 'right'});
 				}
+				// Building description for the node
+				if( (item.description !== undefined) && (item.description !== '') )
+				{
+				    liElem.append( $('<span class="list-group-item-description">'+item.description+'</span>') );
+				}
 				
 				// Building actions for that node
 				switch(levelPrimaryAction.type)

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

@@ -11,7 +11,7 @@
 	{% endif %}
 {% endblock %}
 
-{% block pPageBodyClass %}{{ parent() }} page_brick_of_id_{{ oBrick.GetId() }}{% endblock %}
+{% block pPageBodyClass %}{{ parent() }} {% if oBrick is defined and oBrick is not null %}page_brick_of_id_{{ oBrick.GetId() }}{% endif %}{% endblock %}
 
 {% block pMainHeader %}
 	<div class="col-xs-12">

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig

@@ -60,7 +60,7 @@
 	{% endblock %}
 	{% block pPageScripts %}
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery/jquery-1.11.3.min.js'|add_itop_version }}"></script>
-		<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery-ui-1.10.3.custom.min.js'|add_itop_version }}"></script>
+		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/jquery-ui/jquery-ui-1.11.4.min.js'|add_itop_version }}"></script>
 		<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.magnific-popup.min.js'|add_itop_version }}"></script>
 		<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.iframe-transport.js'|add_itop_version }}"></script>
 		<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.fileupload.js'|add_itop_version }}"></script>

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

@@ -553,10 +553,10 @@ footer {
   top: 10px;
   right: 10px;
 }
-.list-group-item:hover > .list-group-item-actions {
+.list-group-item:hover > .list-group-item-actions, .grid-group-item:hover > .grid-group-item-actions {
   display: block;
 }
-.list-group-item .list-group-item-actions a:not(:first-child) {
+.list-group-item .list-group-item-actions a:not(:first-child), .grid-group-item .grid-group-item-actions a:not(:first-child) {
   margin-left: 10px;
 }
 .list-group-item .keep-spinning {
@@ -565,15 +565,20 @@ footer {
   -moz-animation: spin 1s linear infinite;
   -ms-animation: spin 1s linear infinite;
 }
+.list-group.tree .list-group-item .list-group-item-description {
+  display: block;
+  margin-top: 3px;
+  font-size: 0.8em;
+}
 /* Secondary actions */
 table .group-actions {
   position: relative;
 }
-.list-group-item-actions a.glyphicon-menu-hamburger, table .group-actions a.glyphicon-menu-hamburger {
+.list-group-item-actions a.glyphicon-menu-hamburger, .grid-group-item-actions a.glyphicon-menu-hamburger, table .group-actions a.glyphicon-menu-hamburger {
   cursor: pointer;
   text-decoration: none;
 }
-.list-group-item-actions .item-action-wrapper, table .group-actions .item-action-wrapper {
+.list-group-item-actions .item-action-wrapper, .grid-group-item-actions .item-action-wrapper, table .group-actions .item-action-wrapper {
   display: none;
   position: absolute;
   z-index: 5;
@@ -583,16 +588,16 @@ table .group-actions {
   -moz-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.15);
   box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.15);
 }
-.list-group-item-actions .item-action-wrapper .glyphicon, table .group-actions .item-action-wrapper .glyphicon {
+.list-group-item-actions .item-action-wrapper .glyphicon, .grid-group-item-actions .item-action-wrapper .glyphicon, table .group-actions .item-action-wrapper .glyphicon {
   margin-right: 0.6em;
 }
-.list-group-item-actions .item-action-wrapper.collapse.in, table .group-actions .item-action-wrapper.collapse.in {
+.list-group-item-actions .item-action-wrapper.collapse.in, .grid-group-item-actions .item-action-wrapper.collapse.in, table .group-actions .item-action-wrapper.collapse.in {
   display: block;
 }
-.list-group-item-actions .item-action-wrapper .panel-body > p, table .group-actions .item-action-wrapper .panel-body > p {
+.list-group-item-actions .item-action-wrapper .panel-body > p, .grid-group-item-actions .item-action-wrapper .panel-body > p, table .group-actions .item-action-wrapper .panel-body > p {
   white-space: nowrap;
 }
-.list-group-item-actions .item-action-wrapper .panel-body > p:last-child, table .group-actions .item-action-wrapper .panel-body > p:last-child {
+.list-group-item-actions .item-action-wrapper .panel-body > p:last-child, .grid-group-item-actions .item-action-wrapper .panel-body > p:last-child, table .group-actions .item-action-wrapper .panel-body > p:last-child {
   margin-bottom: 0px;
 }
 #brick_content_empty {
@@ -602,13 +607,197 @@ table .group-actions {
   font-style: italic;
 }
 /* Loader */
-#brick_tree_overlay {
+#brick_tree_overlay, #brick_grid_overlay {
   display: none;
   padding: 8% 0px;
   border-radius: 0px 0px 4px 4px;
   font-size: 1em;
 }
 /****************/
+/* - Grid mode  */
+/* Note: Some of the CSS is factorised in the "Tree mode" part */
+/****************/
+#brick_content_grid {
+  position: relative;
+  padding: 10px 10px 1px 10px;
+}
+.grid-group {
+  display: none;
+}
+/* Only the first level is showed by default */
+.grid-group:first-child {
+  display: block;
+}
+.grid-group-back, .grid-group-item {
+  position: relative;
+  height: 55px;
+  margin-bottom: 10px;
+  text-align: center;
+  color: #fff;
+}
+.grid-group-back {
+  font-size: 25px;
+}
+.grid-item {
+  display: table;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  background-color: #585653;
+  transition: background-color linear 0.3s;
+}
+.grid-item, .grid-item:hover, .grid-item:active, .grid-item:focus, .grid-item:visited {
+  color: #fff;
+  text-decoration: none;
+}
+.grid-item:active {
+  background-color: #9e510f;
+}
+.grid-item-image, .grid-item-text {
+  display: table-cell;
+  text-align: center;
+  vertical-align: middle;
+}
+.grid-item-image > img {
+  max-width: 100%;
+}
+.grid-group-item > .grid-group-item-actions {
+  position: absolute;
+  top: 5px;
+  right: 5px;
+}
+.grid-group-item-actions > a {
+  color: #fff;
+  text-decoration: none;
+}
+.grid-group-item-actions > a:hover, .grid-group-item-actions > a:focus {
+  color: #eee;
+}
+.ggi-last-xs, .ggi-last-sm, .ggi-last-md, .ggi-last-lg {
+  margin-right: 0px;
+}
+@media (max-width: 768px) {
+  /* All layout */
+  /* Layout 2 */
+  /* Layout 5/7 */
+  .grid-group-item > .grid-group-item-actions {
+    top: 12px;
+    right: 6px;
+  }
+  .grid-group-item > .grid-group-item-actions > .glyphicon {
+    margin-top: 5px;
+  }
+  .grid-group-item-actions > a {
+    font-size: 20px;
+  }
+  .grid-item-image {
+    width: 55px;
+    padding: 10px;
+  }
+  .grid-item-image > img {
+    max-height: 30px;
+  }
+  .grid-item-name {
+    font-size: 14px;
+  }
+  .grid-item-description {
+    display: none;
+  }
+  .grid-item-layout-2 .grid-item-description {
+    display: block;
+  }
+  .grid-item-layout-5 .grid-item-name, .grid-item-layout-7 .grid-item-name {
+    padding-right: 40px;
+  }
+}
+@media (min-width: 768px) {
+  /* All layout */
+  /* Layout 1 */
+  /* Layout 7 */
+  .grid-group-back, .grid-group-item {
+    display: inline-block;
+    width: 32%;
+    height: 120px;
+    margin-right: 1.95%;
+    /* We don't put 2% to keep a margin in case of a bad browser rendering */
+  }
+  .grid-item {
+    padding: 10px;
+  }
+  .grid-item:hover, .grid-item:focus {
+    background-color: #ea7d1e;
+  }
+  .grid-item-name {
+    font-weight: 600;
+    font-size: 12px;
+  }
+  .grid-item-description {
+    overflow: hidden;
+  }
+  .grid-item-layout-1 .grid-item-name {
+    font-weight: inherit;
+    font-size: 14px;
+  }
+  .grid-item-layout-7 .grid-item-image {
+    display: none;
+  }
+  .grid-item-layout-3 .grid-item-description, .grid-item-layout-7 .grid-item-description {
+    margin-top: 10px;
+    max-height: 40px;
+    font-size: 10px;
+  }
+}
+@media (min-width: 992px) {
+  /* Layout 7 */
+  .grid-item {
+    padding: 10px 15px;
+  }
+  .grid-group-back {
+    font-size: 40px;
+  }
+  .grid-item-layout-7 .grid-item-image {
+    display: table-cell;
+    width: 105px;
+    padding-left: 5px;
+    padding-right: 18px;
+  }
+  .grid-item-layout-7 .grid-item-image > img {
+    max-width: 100%;
+  }
+  .grid-item-layout-7 .grid-item-name {
+    font-size: 12px;
+  }
+}
+@media (min-width: 1200px) {
+  /* Layout 7 */
+  .grid-group-back, .grid-group-item {
+    width: 24%;
+    height: 140px;
+    margin-right: 1.3%;
+  }
+}
+/* Helper classes to remove margin depending on the screen size */
+@media (max-width: 992px) {
+  .ggi-last-xs {
+    margin-right: 0px;
+  }
+}
+@media (min-width: 768px) and (max-width: 992px) {
+  .ggi-last-sm {
+    margin-right: 0px;
+  }
+}
+@media (min-width: 992px) and (max-width: 1200px) {
+  .ggi-last-md {
+    margin-right: 0px;
+  }
+}
+@media (min-width: 1200px) {
+  .ggi-last-lg {
+    margin-right: 0px;
+  }
+}
+/****************/
 /* - List mode  */
 /****************/
 /*********/

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

@@ -580,10 +580,12 @@ footer{
 	top: 10px;
 	right: 10px;
 }
-.list-group-item:hover > .list-group-item-actions{
+.list-group-item:hover > .list-group-item-actions,
+.grid-group-item:hover > .grid-group-item-actions{
 	display: block;
 }
-.list-group-item .list-group-item-actions a:not(:first-child){
+.list-group-item .list-group-item-actions a:not(:first-child),
+.grid-group-item .grid-group-item-actions a:not(:first-child){
 	margin-left: 10px;
 }
 .list-group-item .keep-spinning{
@@ -592,17 +594,24 @@ footer{
 	-moz-animation: spin 1s linear infinite;
 	-ms-animation: spin 1s linear infinite;
 }
+.list-group.tree .list-group-item .list-group-item-description{
+	display: block;
+	margin-top: 3px;
+	font-size: 0.8em;
+}
 
 /* Secondary actions */
 table .group-actions{
 	position: relative;
 }
 .list-group-item-actions a.glyphicon-menu-hamburger,
+.grid-group-item-actions a.glyphicon-menu-hamburger,
 table .group-actions a.glyphicon-menu-hamburger{
 	cursor: pointer;
 	text-decoration: none;
 }
 .list-group-item-actions .item-action-wrapper,
+.grid-group-item-actions .item-action-wrapper,
 table .group-actions .item-action-wrapper
 {
 	display: none;
@@ -615,18 +624,22 @@ table .group-actions .item-action-wrapper
 	box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
 }
 .list-group-item-actions .item-action-wrapper .glyphicon,
+.grid-group-item-actions .item-action-wrapper .glyphicon,
 table .group-actions .item-action-wrapper .glyphicon{
 	margin-right: 0.6em;
 }
 .list-group-item-actions .item-action-wrapper.collapse.in,
+.grid-group-item-actions .item-action-wrapper.collapse.in,
 table .group-actions .item-action-wrapper.collapse.in{
 	display: block;
 }
 .list-group-item-actions .item-action-wrapper .panel-body > p,
+.grid-group-item-actions .item-action-wrapper .panel-body > p,
 table .group-actions .item-action-wrapper .panel-body > p{
 	white-space: nowrap;
 }
 .list-group-item-actions .item-action-wrapper .panel-body > p:last-child,
+.grid-group-item-actions .item-action-wrapper .panel-body > p:last-child,
 table .group-actions .item-action-wrapper .panel-body > p:last-child{
 	margin-bottom: 0px;
 }
@@ -639,12 +652,220 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 }
 
 /* Loader */
-#brick_tree_overlay{
+#brick_tree_overlay,
+#brick_grid_overlay{
 	display: none;
 	padding: 8% 0px;
 	border-radius: 0px 0px 4px 4px;
 	font-size: 1em;
 }
+
+/****************/
+/* - Grid mode  */
+/* Note: Some of the CSS is factorised in the "Tree mode" part */
+/****************/
+#brick_content_grid{
+  	position: relative;
+	padding: 10px 10px 1px 10px;
+}
+.grid-group{
+	display: none;
+}
+/* Only the first level is showed by default */
+.grid-group:first-child{
+	display: block;
+}
+
+.grid-group-back,
+.grid-group-item{
+	position: relative;
+	height: 55px;
+	margin-bottom: 10px;
+	text-align: center;
+	color: $white;
+}
+.grid-group-back{
+	font-size: 25px;
+}
+.grid-item{
+	display: table;
+	width: 100%;
+	height: 100%;
+	overflow: hidden;
+	background-color: $combodo-dark-gray;
+	transition: background-color linear 0.3s;
+}
+.grid-item,
+.grid-item:hover,
+.grid-item:active,
+.grid-item:focus,
+.grid-item:visited{
+	color: $white;
+	text-decoration: none;
+}
+.grid-item:active{
+	background-color: $combodo-orange-darker;
+}
+.grid-item-image,
+.grid-item-text{
+	display: table-cell;
+	text-align: center;
+	vertical-align: middle;
+}
+.grid-item-image > img{
+	max-width: 100%;
+}
+.grid-group-item > .grid-group-item-actions{
+	position: absolute;
+	top: 5px;
+	right: 5px;
+}
+.grid-group-item-actions > a{
+  color: $white;
+  text-decoration: none;
+}
+.grid-group-item-actions > a:hover,
+.grid-group-item-actions > a:focus{
+  color: #EEEEEE;
+}
+
+.ggi-last-xs, .ggi-last-sm, .ggi-last-md, .ggi-last-lg{
+	margin-right: 0px;
+}
+
+@media (max-width: 768px) {
+	.grid-group-item > .grid-group-item-actions{
+		top: 12px;
+		right: 6px;
+	}
+	.grid-group-item > .grid-group-item-actions > .glyphicon{
+		margin-top: 5px;
+	}
+	.grid-group-item-actions > a{
+		font-size: 20px;
+	}
+	/* All layout */
+	.grid-item-image{
+		width: 55px;
+		padding: 10px;
+	}
+	.grid-item-image > img{
+		max-height: 30px;
+	}
+	.grid-item-name{
+		font-size: 14px;
+	}
+	.grid-item-description{
+		display: none;
+	}
+	/* Layout 2 */
+	.grid-item-layout-2 .grid-item-description{
+		display: block;
+	}
+	/* Layout 5/7 */
+	.grid-item-layout-5 .grid-item-name,
+	.grid-item-layout-7 .grid-item-name{
+		padding-right: 40px;
+	}
+}
+@media (min-width: 768px) {
+	.grid-group-back,
+	.grid-group-item{
+		display: inline-block;
+		width: 32%;
+		height:	120px;
+		margin-right: 1.95%; /* We don't put 2% to keep a margin in case of a bad browser rendering */
+	}
+	.grid-item{
+		padding: 10px;
+	}
+	.grid-item:hover,
+	.grid-item:focus{
+		background-color: $combodo-orange;
+	}
+
+	/* All layout */
+	.grid-item-name{
+		font-weight: 600;
+		font-size: 12px;
+	}
+	.grid-item-description{
+		overflow: hidden;
+	}
+	/* Layout 1 */
+	.grid-item-layout-1 .grid-item-name{
+		font-weight: inherit;
+		font-size: 14px;
+	}
+	/* Layout 7 */
+	.grid-item-layout-7 .grid-item-image{
+		display: none;
+	}
+	.grid-item-layout-3 .grid-item-description,
+	.grid-item-layout-7 .grid-item-description{
+		margin-top: 10px;
+		max-height: 40px;
+		font-size: 10px;
+	}
+}
+@media (min-width: 992px) {
+	.grid-item{
+		padding: 10px 15px;
+	}
+	.grid-group-back{
+		font-size: 40px;
+	}
+
+	/* Layout 7 */
+	.grid-item-layout-7 .grid-item-image{
+		display: table-cell;
+		width: 105px;
+		padding-left: 5px;
+		padding-right: 18px;
+	}
+	.grid-item-layout-7 .grid-item-image > img{
+		max-width: 100%;
+	}
+	.grid-item-layout-7 .grid-item-name{
+		font-size: 12px;
+	}
+}
+@media (min-width: 1200px) {
+	.grid-group-back,
+	.grid-group-item{
+		width: 24%;
+		height: 140px;
+		margin-right: 1.3%;
+	}
+	/* Layout 7 */
+	//.grid-item-layout-7 .grid-item-image{
+	//	width: 100px;
+	//	padding: 60px 15px;
+	//}
+}
+
+/* Helper classes to remove margin depending on the screen size */
+@media (max-width: 992px) {
+	.ggi-last-xs{
+		margin-right: 0px;
+	}
+}
+@media (min-width: 768px) and (max-width: 992px) {
+	.ggi-last-sm{
+		margin-right: 0px;
+	}
+}
+@media (min-width: 992px) and (max-width: 1200px) {
+	.ggi-last-md{
+		margin-right: 0px;
+	}
+}
+@media (min-width: 1200px) {
+	.ggi-last-lg{
+		margin-right: 0px;
+	}
+}
+
 /****************/
 /* - List mode  */
 /****************/

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 5 - 0
datamodels/2.x/itop-portal-base/portal/web/lib/jquery-ui/jquery-ui-1.11.4.min.js


+ 1 - 0
datamodels/2.x/itop-portal-base/ru.dict.itop-portal-base.php

@@ -65,6 +65,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
     'Brick:Portal:Browse:Name' => 'Просмотр элементов',
     'Brick:Portal:Browse:Mode:List' => 'Список',
     'Brick:Portal:Browse:Mode:Tree' => 'Дерево',
+    'Brick:Portal:Browse:Mode:Grid' => 'Tiles~~',
     'Brick:Portal:Browse:Action:Drilldown' => 'Детализация',
     'Brick:Portal:Browse:Action:View' => 'Подробно',
     'Brick:Portal:Browse:Action:Edit' => 'Изменить',

+ 10 - 3
datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml

@@ -1051,12 +1051,18 @@
 								<level id="1">
 									<!-- Can be either a class tag with the class name or an oql tag with the query -->
 									<class>Service</class>
-									<!-- Attribute code of the above class that point to the upper level class -->
+									<!-- Attribute code of the above class [from the OQL] that point to the upper level class -->
 									<parent_att>servicefamily_id</parent_att>
-									<!-- Attribute code to use to display the object name, default is 'name'. -->
+									<!-- Attribute code of the above class [from the OQL] used to display the object name, default is 'name'. -->
 									<name_att/>
-									<!-- Description text from attribute of above class [from the OQL] -->
+									<!-- Attribute code of the above class [from the OQL] used to display in a tooltip when mouse is over the object -->
 									<tooltip_att>description</tooltip_att>
+									<!-- Attribute code of the above class [from the OQL] used to display a small text beside the object's name -->
+									<!-- Note: This is not used in "list" mode -->
+									<description_att/>
+									<!-- Attribute code of the above class [from the OQL] used to display a image beside the object's name -->
+									<!-- Note: This is used in "grid" mode only for now -->
+									<!--<image_att/>-->
 									<!-- Title of the level, will be display in lists and others browse modes -->
 									<title>Class:Service</title>
 									<!-- Optional tag to add attributes to the table by their code, can be specified for each level -->
@@ -1101,6 +1107,7 @@
 						<availables>
 							<mode id="list"/>
 							<mode id="tree"/>
+							<mode id="grid"/>
 						</availables>
 						<default>list</default>
 					</browse_modes>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä