diff --git a/js/layouts/tab-container/tab-container.js b/js/layouts/tab-container/tab-container.js index 36e0019af..73d6c3092 100644 --- a/js/layouts/tab-container/tab-container.js +++ b/js/layouts/tab-container/tab-container.js @@ -154,17 +154,25 @@ $(function() // - Update URL hash when tab is activated _onTabActivated: function(oUI) { - let oState = {}; + let oState = {}; - // Get the id of this tab widget. - const sId = this.element.attr( 'id' ); + // Get the id of this tab widget. + const sId = this.element.attr('id'); - // Get the index of this tab. - const iIdx = $(oUI.newTab).prevAll().length; + //Datatable are not displayed correctly when hidden + $(oUI.newPanel).find('.dataTables_scrollBody > .ibo-datatable').each(function () { + if ($('#'+this.id).find('thead').is(':visible')) { + $('#'+this.id).DataTable().columns.adjust().draw(); + $('#'+this.id).find('thead').hide(); + } + }); - // Set the state! - oState[ sId ] = iIdx; - $.bbq.pushState( oState ); + // Get the index of this tab. + const iIdx = $(oUI.newTab).prevAll().length; + + // Set the state! + oState[sId] = iIdx; + $.bbq.pushState(oState); }, // - Change current tab as necessary when URL hash changes _onHashChange: function() diff --git a/pages/UI.php b/pages/UI.php index 59b945357..c02d1c165 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -285,12 +285,13 @@ function DisplayMultipleSelectionForm(WebPage $oP, DBSearch $oFilter, string $sN } $oForm->AddSubBlock($oAppContext->GetForFormBlock()); $oDisplayBlock = new DisplayBlock($oFilter, 'list', false); + //by default all the elements are selected + $aExtraParams['selectionMode'] = 'negative'; $oForm->AddSubBlock($oDisplayBlock->GetDisplay($oP, 1, $aExtraParams)); $oForm->AddSubBlock(ButtonUIBlockFactory::MakeNeutral(Dict::S('UI:Button:Cancel'), 'cancel')->SetOnClickJsCode('window.history.back()')); $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Next'), 'next', Dict::S('UI:Button:Next'), true)); $oP->AddUiBlock($oForm); - $oP->add_ready_script("$('#1 table.listResults').trigger('check_all');"); } function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) diff --git a/sources/Controller/AjaxRenderController.php b/sources/Controller/AjaxRenderController.php index f80aeea24..5b559e4d6 100644 --- a/sources/Controller/AjaxRenderController.php +++ b/sources/Controller/AjaxRenderController.php @@ -11,6 +11,8 @@ use ApplicationMenu; use AttributeLinkedSet; use BulkExport; use BulkExportException; +use CMDBObjectSet; +use CMDBSource; use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings; use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityEntry\ActivityEntryFactory; @@ -451,4 +453,158 @@ class AjaxRenderController return $aResults; } + + /** + * @param string $sEncoding + * @param string $sFilter + * + * @return array + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DictExceptionMissingString + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public static function RefreshDashletList(string $sStyle, string $sFilter): array + { + $aExtraParams = utils::ReadParam('extra_params', '', false, 'raw_data'); + $oFilter = DBObjectSearch::FromOQL($sFilter); + + if (isset($aExtraParams['group_by'])) { + + $sAlias = $oFilter->GetClassAlias(); + if (isset($aExtraParams['group_by_label'])) { + $oGroupByExp = Expression::FromOQL($aExtraParams['group_by']); + $sGroupByLabel = $aExtraParams['group_by_label']; + } else { + // Backward compatibility: group_by is simply a field id + $oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias); + $sGroupByLabel = MetaModel::GetLabel($oFilter->GetClass(), $aExtraParams['group_by']); + } + + // Security filtering + $aFields = $oGroupByExp->ListRequiredFields(); + foreach ($aFields as $sFieldAlias) { + $aMatches = array(); + if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches)) { + $sFieldClass = $oFilter->GetClassName($aMatches[1]); + $oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]); + if ($oAttDef instanceof AttributeOneWayPassword) { + throw new Exception('Grouping on password fields is not supported.'); + } + } + } + + $aGroupBy = array(); + $aGroupBy['grouped_by_1'] = $oGroupByExp; + $aQueryParams = array(); + if (isset($aExtraParams['query_params'])) { + $aQueryParams = $aExtraParams['query_params']; + } + $aFunctions = array(); + $sAggregationFunction = 'count'; + $sFctVar = '_itop_count_'; + $sAggregationAttr = ''; + if (isset($aExtraParams['aggregation_function']) && !empty($aExtraParams['aggregation_attribute'])) { + $sAggregationFunction = $aExtraParams['aggregation_function']; + $sAggregationAttr = $aExtraParams['aggregation_attribute']; + $oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAggregationAttr.'`'); + $oFctExpr = new FunctionExpression(strtoupper($sAggregationFunction), array($oAttrExpr)); + $sFctVar = '_itop_'.$sAggregationFunction.'_'; + $aFunctions = array($sFctVar => $oFctExpr); + } + + if (!empty($sAggregationAttr)) { + $sClass = $oFilter->GetClass(); + $sAggregationAttr = MetaModel::GetLabel($sClass, $sAggregationAttr); + } + $iLimit = 0; + if (isset($aExtraParams['limit'])) { + $iLimit = intval($aExtraParams['limit']); + } + $aOrderBy = array(); + if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by'])) { + switch ($aExtraParams['order_by']) { + case 'attribute': + $aOrderBy = array('grouped_by_1' => ($aExtraParams['order_direction'] === 'asc')); + break; + case 'function': + $aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc')); + break; + } + } + + $sSql = $oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true, $aFunctions, $aOrderBy, $iLimit); + + $aRes = CMDBSource::QueryToArray($sSql); + + $aGroupBy = array(); + $aLabels = array(); + $aValues = array(); + $iTotalCount = 0; + foreach ($aRes as $iRow => $aRow) { + $sValue = $aRow['grouped_by_1']; + $aValues[$iRow] = $sValue; + $sHtmlValue = $oGroupByExp->MakeValueLabel($oFilter, $sValue, $sValue); + $aLabels[$iRow] = $sHtmlValue; + $aGroupBy[$iRow] = (int)$aRow[$sFctVar]; + $iTotalCount += $aRow['_itop_count_']; + } + + $aResult = array(); + $oAppContext = new ApplicationContext(); + $sParams = $oAppContext->GetForLink(); + foreach ($aGroupBy as $iRow => $iCount) { + // Build the search for this subset + $oSubsetSearch = $oFilter->DeepClone(); + $oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($aValues[$iRow])); + $oSubsetSearch->AddConditionExpression($oCondition); + if (isset($aExtraParams['query_params'])) { + $aQueryParams = $aExtraParams['query_params']; + } else { + $aQueryParams = array(); + } + $sFilter = rawurlencode($oSubsetSearch->serialize(false, $aQueryParams)); + + $aResult[] = array( + 'group' => $aLabels[$iRow], + 'value' => "$iCount", + ); // TO DO: add the context information + } + + } else { + // Simply count the number of elements in the set + $aOrderBy = []; + if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by'])) { + $aOrderBy = ['order_by' => $aExtraParams['order_by'], 'order_direction' => $aExtraParams['order_direction']]; + } + + $oSet = new CMDBObjectSet($oFilter, $aOrderBy, $aExtraParams); + $iCount = $oSet->Count(); + $sFormat = 'UI:CountOfObjects'; + if (isset($aExtraParams['format'])) { + $sFormat = $aExtraParams['format']; + } + $aResult = ['result' => Dict::Format($sFormat, $iCount)]; + } + + return $aResult; + } + + public static function RefreshCount(string $sFilter): array + { + $aExtraParams = utils::ReadParam('extra_params', '', false, 'raw_data'); + $oFilter = DBObjectSearch::FromOQL($sFilter); + + $oSet = new CMDBObjectSet($oFilter, [], $aExtraParams); + $iCount = $oSet->Count(); + $aResult = ['count' => $iCount]; + + return $aResult; + } + + } diff --git a/sources/application/UI/Base/Component/DataTable/DataTableSettings.php b/sources/application/UI/Base/Component/DataTable/DataTableSettings.php index 3abdd49bc..9f224b059 100644 --- a/sources/application/UI/Base/Component/DataTable/DataTableSettings.php +++ b/sources/application/UI/Base/Component/DataTable/DataTableSettings.php @@ -214,7 +214,6 @@ class DataTableSettings implements Serializable return null; } } - $oSettings->unserialize($pref); return $oSettings; } diff --git a/sources/application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php b/sources/application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php index 0c6f06f65..298823709 100644 --- a/sources/application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php +++ b/sources/application/UI/Base/Component/DataTable/DataTableUIBlockFactory.php @@ -115,7 +115,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory } if (!isset($aExtraParams['surround_with_panel']) || $aExtraParams['surround_with_panel']) { - $oContainer = PanelUIBlockFactory::MakeForClass($oSet->GetClass(), "Result")->AddCSSClass('ibo-datatable-panel'); + $oContainer = PanelUIBlockFactory::MakeForClass($oSet->GetClass(), "")->AddCSSClass('ibo-datatable-panel'); $oContainer->AddToolbarBlock($oBlockMenu); $oContainer->AddMainBlock($oDataTable); } else { @@ -147,13 +147,6 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory public static function MakeForRendering(string $sListId, DBObjectSet $oSet, $aExtraParams = array()) { $oDataTable = new DataTable('datatable_'.$sListId); - /////////////////////////////////////////////////// - /*TODO 3.0.0 PrintableVersion - if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) - { - return self::GetDisplaySetForPrinting($oPage, $oSet, $aExtraParams); - } - */ // Initialize and check the parameters $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; @@ -352,6 +345,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory $aOptions['select_mode'] = "single"; } } + $aOptions['selectionMode'] = $aExtraParams['selectionMode']?? 'positive'; if (isset($aExtraParams['cssCount'])) { $aOptions['sCountSelector'] = $aExtraParams['cssCount']; @@ -577,6 +571,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory $aOptions['select_mode'] = "single"; } } + $aOptions['selectionMode'] = $aExtraParams['selectionMode']?? 'positive'; $aOptions['sort'] = $aSortDatable; diff --git a/templates/base/components/datatable/layout.html.twig b/templates/base/components/datatable/layout.html.twig index 7fce811ad..a77093020 100644 --- a/templates/base/components/datatable/layout.html.twig +++ b/templates/base/components/datatable/layout.html.twig @@ -1,7 +1,7 @@ {% for oSubBlock in oUIBlock.GetSubBlocks() %}{{ render_block(oSubBlock, {aPage: aPage}) }}{% endfor %} {% if oUIBlock.GetOptions("select_mode") is not empty %} - +
{% if oUIBlock.GetAjaxData("extra_params") is not empty %} diff --git a/templates/base/components/datatable/layout.ready.js.twig b/templates/base/components/datatable/layout.ready.js.twig index ecf7f249e..d03ffd49d 100644 --- a/templates/base/components/datatable/layout.ready.js.twig +++ b/templates/base/components/datatable/layout.ready.js.twig @@ -4,7 +4,7 @@ {% if oUIBlock.GetOption("iPageSize") is not empty %} {% set iPageSize = oUIBlock.GetOption("iPageSize") %} {% else %} - {% set iPageSize = 10 %} +{% set iPageSize = 10 %} {% endif %} $('#{{ oUIBlock.GetId() }}').closest("[role=dialog]").on("dialogbeforeclose", function () { @@ -13,10 +13,36 @@ $('#{{ oUIBlock.GetId() }}').closest("[role=dialog]").on("dialogbeforeclose", fu $('#{{ sListId }}').data('target', 'ibo-datatables--outer'); -if ($.fn.dataTable.isDataTable('#{{ oUIBlock.GetId() }}')) { +if ($('#{{ oUIBlock.GetId() }}') != 'undefined' && $.fn.dataTable.isDataTable('#{{ oUIBlock.GetId() }}')) +{ $('#{{ oUIBlock.GetId() }}').DataTable().destroy(false); } - +//define maxHeight for Datatable +var maxHeight{{ sListId }} = 300; +if ($('#{{ oUIBlock.GetId() }}').closest('.ui-dialog').length > 0) +{ + //we are in dialogbox + maxHeight{{ sListId }} = $('#{{ oUIBlock.GetId() }}').closest('.ui-dialog').height(); + a +} +else +{ + maxHeight{{ sListId }} = $(window).height()-$('#ibo-top-container').outerHeight()+$('#ibo-main-content').height()-$('#ibo-main-content').outerHeight(); +} +if ($('#{{ oUIBlock.GetId() }}').closest('[data-target=search_results]').parent().find('.ibo-search-form-panel').length > 0) +{ + //we are in dialogbox + maxHeight{{ sListId }} = maxHeight{{ sListId }}-$('#{{ oUIBlock.GetId() }}').closest('[data-target=search_results]').parent().find('.ibo-search-form-panel').height(); +} +if (maxHeight{{ sListId }} < 300) +{ + maxHeight{{ sListId }} = 250; +} +else +{ + maxHeight{{ sListId }} = maxHeight{{ sListId }} -50; +} +console.warn('#{{ oUIBlock.GetId() }}'); var oTable{{ sListId }} = $('#{{ oUIBlock.GetId() }}').DataTable({ language: { processing: "{{ 'UI:Datatables:Language:Processing'|dict_s }}", @@ -38,6 +64,9 @@ var oTable{{ sListId }} = $('#{{ oUIBlock.GetId() }}').DataTable({ sortDescending: ": {{ 'UI:Datatables:Language:Sort:Descending'|dict_s }}" } }, + scrollY: maxHeight{{ sListId }}, + scrollX: true, + scrollCollapse: true, lengthMenu: [[ {{ iPageSize }}, {{ iPageSize*2 }}, {{ iPageSize*3 }}, {{ iPageSize*4 }}, -1], [ {{ iPageSize }}, {{ iPageSize*2 }}, {{ iPageSize*3 }}, {{ iPageSize*4 }}, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]], dom: "<'ibo-datatable-toolbar'pil>t<'ibo-datatable-toolbar'pil>", {% if( oUIBlock.GetOptions("sort")[0] is defined ) %} @@ -51,23 +80,30 @@ var oTable{{ sListId }} = $('#{{ oUIBlock.GetId() }}').DataTable({ style: "{{ oUIBlock.GetOption("select_mode") }}" }, rowCallback: function (oRow, oData) { - if ($(this).closest('.ibo-panel--body').find('[name=selectionMode]').val() === "negative") { + if ($(this).closest('.ibo-panel--body').find('[name=selectionMode]').val() === "negative") + { if (oSelectedItems{{ sListId }}.indexOf(oData.id) === -1) { $(oRow).select(); $(oRow).find('td:first-child input').prop('checked', true); } } else { - if (oSelectedItems{{ sListId }}.indexOf(oData.id) > -1) { + if (oSelectedItems{{ sListId }}.indexOf(oData.id) > -1) + { $(oRow).select(); $(oRow).find('td:first-child input').prop('checked', true); } } }, drawCallback: function () { - if ($(this).closest('.ibo-panel--body').find('[name=selectionMode]').val() === "negative") { - $(this).find('[name=selectAll]').prop('checked', true); + console.warn('la'); + console.warn($(this).closest('.ibo-panel--body').find('[name=selectionMode]').val()); + if ($(this).closest('.ibo-panel--body').find('[name=selectionMode]').val() === "negative") + { + $(this).closest('.dataTables_wrapper').find('.checkAll')[0].checked = true; $(this).DataTable().rows({page: 'current'}).select(); - } else { + } + else + { $(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show(); } }, @@ -84,11 +120,12 @@ var oTable{{ sListId }} = $('#{{ oUIBlock.GetId() }}').DataTable({ width: "20px", searchable: false, sortable: false, + orderable: false, title: - {% if oUIBlock.GetOption("select_mode") != "single" %} - '' + {% if oUIBlock.GetOption("select_mode") != "single" %} + '' {% else %} - '' + '' {% endif %}, type: "html", data: "", @@ -110,7 +147,8 @@ var oTable{{ sListId }} = $('#{{ oUIBlock.GetId() }}').DataTable({ {% endif %} {% for aColumn in oUIBlock.GetDisplayColumns() %} { - width: "auto", + // width: 100, + autoWidth: true, searchable: false, sortable: true, title: "{{ aColumn["attribute_label"] }}", @@ -137,7 +175,9 @@ var oTable{{ sListId }} = $('#{{ oUIBlock.GetId() }}').DataTable({ pages: 5 // number of pages to cache }), initComplete: function () { - if (this.api().page.info().pages < 2) { + this.api().columns.adjust().draw(); + if (this.api().page.info().pages < 2) + { this.parent().find('.dataTables_paginate').hide(); this.parent().find('.dataTables_length').hide(); } diff --git a/templates/base/components/datatable/static/formtable/layout.html.twig b/templates/base/components/datatable/static/formtable/layout.html.twig index 7f6ea4ea7..a6c60c225 100644 --- a/templates/base/components/datatable/static/formtable/layout.html.twig +++ b/templates/base/components/datatable/static/formtable/layout.html.twig @@ -4,7 +4,7 @@ {% set columns = oUIBlock.GetColumns() %} - +
{% for column in columns %} diff --git a/templates/base/components/datatable/static/formtable/layout.ready.js.twig b/templates/base/components/datatable/static/formtable/layout.ready.js.twig index 4ba7e0782..ac20022ec 100644 --- a/templates/base/components/datatable/static/formtable/layout.ready.js.twig +++ b/templates/base/components/datatable/static/formtable/layout.ready.js.twig @@ -1,9 +1,15 @@ -$('#{{ oUIBlock.GetId() }}').DataTable({ +var table{{ oUIBlock.GetId()|sanitize_identifier }}= $('#{{ oUIBlock.GetId() }}').DataTable({ language: { emptyTable: "{{ 'UI:Message:EmptyList:UseAdd'|dict_s }}" }, + scrollX: true, + scrollCollapse: true, paging: false, filter: false, search: false, - dom: "t" -}); \ No newline at end of file + dom: "t", +}); + + +//table{{ oUIBlock.GetId()|sanitize_identifier }}.columns.adjust().draw(); +//$(".dataTables_scrollBody thead").hide(); \ No newline at end of file diff --git a/templates/base/components/datatable/static/layout.html.twig b/templates/base/components/datatable/static/layout.html.twig index 74b942bae..43e8a0d4e 100644 --- a/templates/base/components/datatable/static/layout.html.twig +++ b/templates/base/components/datatable/static/layout.html.twig @@ -2,7 +2,7 @@ {# @license http://opensource.org/licenses/AGPL-3.0 #} {% set columns = oUIBlock.GetColumns() %} -
+
{% for column in columns %} diff --git a/templates/base/components/datatable/static/layout.ready.js.twig b/templates/base/components/datatable/static/layout.ready.js.twig index 8cafad35f..3d6c0742f 100644 --- a/templates/base/components/datatable/static/layout.ready.js.twig +++ b/templates/base/components/datatable/static/layout.ready.js.twig @@ -28,6 +28,8 @@ $('#{{ oUIBlock.GetId() }}').DataTable({ sortDescending: ": {{ 'UI:Datatables:Language:Sort:Descending'|dict_s }}" } }, + scrollX: true, + scrollCollapse: true, order: [], rowId: "id", filter: false,