Commit 90fa470e authored by Benni Mack's avatar Benni Mack Committed by Georg Ringer

[TASK] Optimize search box for record lists

The search box is currently available in
* Page module (docheader)
* List module (docheader)
* Record Selector / Element Browser

All implementations are different but use the same code base:
* The outer wrapper functionality is only needed for the doc header
* The page module is not using the Fluid-based snippet

Additionally, styling is broken due to Bootstrap 5 upgrade.

The change reduces CSS and JS initialization by consolidating
the rendering of the search box and removing the buggy showLimit
functionality since there is no pagination the placeholder is not used.

Ideally, this code would be separated into a separate class
as it is only related to a fragment of DatabaseRecordList.
This is to be handled with a separate patch.

Resolves: #93849
Releases: master
Change-Id: I20e406f0ed5bf74503fb45c9ad93cf0e6a91f153
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68667Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
parent fe3deeb5
......@@ -167,14 +167,6 @@ $module-body-padding: $module-body-padding-vertical $module-body-padding-horizon
}
}
.module-docheader-bar-search {
margin-bottom: 0;
.form-control-clearable {
height: auto;
}
}
.module-docheader-bar-column-left {
float: left;
}
......
......@@ -31,10 +31,3 @@ div.typo3-synchronizationLink img {
div.typo3-listOptions {
margin: 0 0 24px;
}
.db_list-searchbox-toolbar {
.form-inline {
margin-top: -0.5em;
margin-bottom: 0;
}
}
......@@ -12,7 +12,6 @@
*/
import $ from 'jquery';
import 'TYPO3/CMS/Backend/Input/Clearable';
/**
* Module: TYPO3/CMS/Backend/ToggleSearchToolbox
......@@ -34,20 +33,6 @@ class ToggleSearchToolbox {
$('#search_field').focus();
}
});
let searchField: HTMLInputElement;
if ((searchField = document.getElementById('search_field') as HTMLInputElement) !== null) {
const searchResultShown = ('' !== searchField.value);
// make search field clearable
searchField.clearable({
onClear: (input: HTMLInputElement): void => {
if (searchResultShown) {
input.closest('form').trigger('submit');
}
},
});
}
}
}
......
......@@ -32,11 +32,6 @@ class BrowseDatabase {
parseInt(targetEl.dataset.close || '0', 10) === 1,
);
}).delegateTo(document, '[data-close]');
// adjust searchbox layout
const searchbox: HTMLElement = document.getElementById('db_list-searchbox-toolbar');
searchbox.style.display = 'block';
searchbox.style.position = 'relative';
}
}
......
......@@ -26,11 +26,6 @@ class RecordLinkHandler {
this.currentLink = document.body.dataset.currentLink;
this.identifier = document.body.dataset.identifier;
// adjust searchbox layout
const searchbox: HTMLElement = document.getElementById('db_list-searchbox-toolbar');
searchbox.style.display = 'block';
searchbox.style.position = 'relative';
new RegularEvent('click', (evt: MouseEvent, targetEl: HTMLElement): void => {
evt.preventDefault();
const data = targetEl.closest('span').dataset;
......
......@@ -44,6 +44,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Versioning\VersionState;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
/**
* Script Class for Web > Layout module
......@@ -1044,64 +1045,12 @@ class PageLayoutController
if (!$this->getBackendUser()->check('modules', 'web_list')) {
return '';
}
$lang = $this->getLanguageService();
$listModule = $this->uriBuilder->buildUriFromRoute('web_list', ['id' => $this->id]);
// Make level selector:
$opt = [];
// "New" generation of search levels ... based on TS config
$config = BackendUtility::getPagesTSconfig($this->id);
$searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
$searchLevelItems = [];
// get translated labels for search levels from pagets
foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
$label = $lang->sL('LLL:' . $labelConfigured);
if ($label === '') {
$label = $labelConfigured;
}
$searchLevelItems[$keySearchLevel] = $label;
}
foreach ($searchLevelItems as $kv => $label) {
$opt[] = '<option value="' . $kv . '"' . ($kv === 0 ? ' selected="selected"' : '') . '>'
. htmlspecialchars($label)
. '</option>';
}
$searchLevelLabel = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels');
$searchStringLabel = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.searchString');
$lMenu = '<select class="form-select" name="search_levels" title="' . htmlspecialchars($searchLevelLabel) . '" id="search_levels">' . implode('', $opt) . '</select>';
return '<div class="db_list-searchbox-form db_list-searchbox-toolbar module-docheader-bar module-docheader-bar-search t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: none;">
<form action="' . htmlspecialchars((string)$listModule) . '" method="post">
<div id="typo3-dblist-search">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="form-group col-12">
<label for="search_field">' . htmlspecialchars($searchStringLabel) . ': </label>
<input class="form-control" type="search" placeholder="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')) . '" name="search_field" id="search_field" value="" />
</div>
<div class="form-group col-12 col-sm-6">
<label for="search_levels">' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')) . ': </label>
' . $lMenu . '
</div>
<div class="form-group col-12 col-sm-6">
<label for="showLimit">' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.limit')) . ': </label>
<input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.limit')) . '" name="showLimit" id="showLimit" value="" />
</div>
<div class="form-group col-12">
<div class="form-control-wrap">
<button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search')) . '">
' . $this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.search')) . '
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>';
$dbList = GeneralUtility::makeInstance(DatabaseRecordList::class);
$dbList->start($this->id, '', '');
$formUrl = $this->uriBuilder->buildUriFromRoute('web_list', ['id' => $this->id]);
return '<div class="module-docheader-bar t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: none;"><div class="panel panel-default"><div class="p-2 ps-4">' . $dbList->getSearchBox((string)$formUrl) . '</div></div></div>';
}
/**
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/Input/Clearable"],(function(e,t,l){"use strict";l=__importDefault(l);return new class{constructor(){l.default(()=>{this.initialize()})}initialize(){const e=l.default("#db_list-searchbox-toolbar");let t;if(l.default(".t3js-toggle-search-toolbox").on("click",()=>{e.toggle(),e.is(":visible")&&l.default("#search_field").focus()}),null!==(t=document.getElementById("search_field"))){const e=""!==t.value;t.clearable({onClear:t=>{e&&t.closest("form").trigger("submit")}})}}}}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery"],(function(t,e,i){"use strict";i=__importDefault(i);return new class{constructor(){i.default(()=>{this.initialize()})}initialize(){const t=i.default("#db_list-searchbox-toolbar");i.default(".t3js-toggle-search-toolbox").on("click",()=>{t.toggle(),t.is(":visible")&&i.default("#search_field").focus()})}}}));
\ No newline at end of file
......@@ -152,20 +152,6 @@ class ElementsGroupCest
$I->switchToWindow();
$I->switchToIFrame('modal_frame');
$I->amGoingTo("search record '' and limit 1 in DB-Browser");
$I->fillField('#showLimit', 1);
$I->click('button[name="search"]');
$I->waitForElement('.recordlist');
$I->seeNumberOfElements('.recordlist #recordlist-be_groups table tbody tr', 1);
$I->amGoingTo('search record style and limit 1 in DB-Browser');
$I->fillField('#search_field', 'style');
$I->click('button[name="search"]');
$I->waitForElement('.recordlist');
$I->seeNumberOfElements('.recordlist #recordlist-be_groups table tbody tr', 1);
$I->amGoingTo('reset limit');
$I->fillField('#showLimit', '');
$I->amGoingTo('search record foo in DB-Browser');
$I->fillField('#search_field', 'foo');
$I->click('button[name="search"]');
......
......@@ -221,14 +221,13 @@ class DatabaseBrowser extends AbstractElementBrowser implements ElementBrowserIn
GeneralUtility::_GP('table'),
MathUtility::forceIntegerInRange(GeneralUtility::_GP('pointer'), 0, 100000),
GeneralUtility::_GP('search_field'),
GeneralUtility::_GP('search_levels'),
GeneralUtility::_GP('showLimit')
GeneralUtility::_GP('search_levels')
);
$dbList->setDispFields();
$tableList = $dbList->generateList();
$out .= $dbList->getSearchBox();
$out .= '<div class="p-2 pb-3 border">' . $dbList->getSearchBox() . '</div>';
// Add the HTML for the record list to output variable:
$out .= $tableList;
......
......@@ -112,7 +112,6 @@ class RecordListController
$table = (string)($parsedBody['table'] ?? $queryParams['table'] ?? '');
$search_field = (string)($parsedBody['search_field'] ?? $queryParams['search_field'] ?? '');
$search_levels = (int)($parsedBody['search_levels'] ?? $queryParams['search_levels'] ?? 0);
$showLimit = (int)($parsedBody['showLimit'] ?? $queryParams['showLimit'] ?? 0);
$returnUrl = GeneralUtility::sanitizeLocalUrl((string)($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? ''));
$cmd = (string)($parsedBody['cmd'] ?? $queryParams['cmd'] ?? '');
$cmd_table = (string)($parsedBody['cmd_table'] ?? $queryParams['cmd_table'] ?? '');
......@@ -231,7 +230,7 @@ class RecordListController
}
}
// Initialize the listing object, dblist, for rendering the list:
$dblist->start($id, $table, $pointer, $search_field, $search_levels, $showLimit);
$dblist->start($id, $table, $pointer, $search_field, $search_levels);
$dblist->setDispFields();
// Render the list of tables:
$tableOutput = $dblist->generateList();
......@@ -372,7 +371,9 @@ class RecordListController
// search box toolbar
$content = '';
if (!($modTSconfig['properties']['disableSearchBox'] ?? false) && ($tableOutput || !empty($dblist->searchString))) {
$content .= $dblist->getSearchBox();
$searchBoxVisible = !empty($dblist->searchString);
$searchBox = $dblist->getSearchBox();
$content .= '<div class="module-docheader-bar mb-0 t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="' . ($searchBoxVisible ? 'display: block;' : 'display: none;') . '"><div class="panel panel-default"><div class="p-2 ps-4">' . $searchBox . '</div></div></div>';
$this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
$searchButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeLinkButton();
......
......@@ -600,7 +600,6 @@ class DatabaseRecordList
'table',
'search_field',
'search_levels',
'showLimit',
'sortField',
'sortRev'
];
......@@ -779,8 +778,8 @@ class DatabaseRecordList
$itemsPerPage = $this->table ? $itemsLimitSingleTable : $itemsLimitPerTable;
// Set limit from search
if ($this->showLimit) {
$itemsPerPage = $this->showLimit;
if ($this->searchString) {
$itemsPerPage = $totalItems;
}
// Init
......@@ -814,8 +813,8 @@ class DatabaseRecordList
$this->showLimit = $totalItems;
$itemsPerPage = $totalItems;
$dbCount = $totalItems;
} elseif ($firstElement + $this->showLimit <= $totalItems) {
$dbCount = $this->showLimit + 2;
} elseif ($firstElement + $itemsPerPage <= $totalItems) {
$dbCount = $itemsPerPage + 2;
} else {
$dbCount = $totalItems - $firstElement + 2;
}
......@@ -2479,7 +2478,6 @@ class DatabaseRecordList
$this->page = MathUtility::forceIntegerInRange((int)$pointer, 1, 1000);
$this->searchString = trim($search);
$this->searchLevels = (int)$levels;
$this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
// Setting GPvars:
$this->csvOutput = (bool)GeneralUtility::_GP('csv');
$this->sortField = GeneralUtility::_GP('sortField');
......@@ -2589,15 +2587,14 @@ class DatabaseRecordList
*
* @return string HTML for the search box
*/
public function getSearchBox(): string
public function getSearchBox(string $formUrl = null): string
{
return $this->getFluidTemplateObject('Search.html')
->assignMultiple([
'formUrl' => $this->listURL('', '-1', 'pointer,search_field'),
'formUrl' => $formUrl ?? $this->listURL('', '-1', 'pointer,search_field'),
'searchLevelsFromTSconfig' => (array)(BackendUtility::getPagesTSconfig($this->id)['mod.']['web_list.']['searchLevel.']['items.'] ?? []),
'selectedSearchLevel' => $this->searchLevels,
'searchString' => $this->searchString,
'showLimit' => $this->showLimit
'searchString' => $this->searchString
])
->render();
}
......@@ -3022,7 +3019,7 @@ class DatabaseRecordList
/**
* Creates the URL to this script, including all relevant GPvars
* Fixed GPvars are id, table, returnUrl, search_field, search_levels and showLimit
* Fixed GPvars are id, table, returnUrl, search_field, and search_levels
* The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
*
* @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
......@@ -3052,9 +3049,6 @@ class DatabaseRecordList
if ($this->searchLevels) {
$urlParameters['search_levels'] = $this->searchLevels;
}
if ($this->showLimit) {
$urlParameters['showLimit'] = $this->showLimit;
}
if ((!$exclList || !GeneralUtility::inList($exclList, 'pointer')) && $this->page) {
$urlParameters['pointer'] = $this->page;
}
......
<div class="db_list-searchbox-form db_list-searchbox-toolbar module-docheader-bar module-docheader-bar-search t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: {f:if(condition: searchString, then: 'block;', else: 'none;')}">
<form action="{formUrl}" method="post">
<div id="typo3-dblist-search">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-sm-6 col-12">
<label for="search_field">
<f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.searchString"/>
</label>
<input class="form-control" type="search" placeholder="{f:translate(id:'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')}" title="{f:translate(id: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')}" name="search_field" id="search_field" value="{searchString}" />
</div>
<div class="col-12 col-sm-3">
<label for="search_levels"><f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels" /></label>
<select class="form-select" name="search_levels" title="{f:translate(id: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels')}" id="search_levels">
<f:for each="{searchLevelsFromTSconfig}" as="searchLevelFromTsConfig" key="level">
<option {f:if(condition: '{level} == {selectedSearchLevel}', then: ' selected="selected"')} value="{level}">
<f:translate id="LLL:{searchLevelFromTsConfig}">{searchLevelFromTsConfig}</f:translate>
</option>
</f:for>
</select>
</div>
<div class="col-12 col-sm-3">
<label for="showLimit"><f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.limit" /></label>
<input class="form-control" type="number" min="0" max="10000" placeholder="10" title="{f:translate(id:'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.limit')}" name="showLimit" id="showLimit" value="{f:if(condition: showLimit, then: '{showLimit}', else: '')}" />
</div>
<div class="col-12">
<div class="form-control-wrap">
<button type="submit" class="btn btn-default" name="search" title="{f:translate(id:'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search')}">
<core:icon identifier="actions-search" />
<f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.search"/>
</button>
</div>
</div>
</div>
</div>
<form action="{formUrl}" method="post" id="typo3-dblist-search">
<div class="container-fluid p-0">
<div class="row align-items-end">
<div class="col-sm-6 col-12">
<label for="search_field">
<f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.searchString"/>
</label>
<input class="form-control" type="search" placeholder="{f:translate(id:'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')}" title="{f:translate(id: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')}" name="search_field" id="search_field" value="{searchString}" />
</div>
<div class="col-12 col-sm-3">
<label for="search_levels"><f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels" /></label>
<select class="form-select" name="search_levels" title="{f:translate(id: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels')}" id="search_levels">
<f:for each="{searchLevelsFromTSconfig}" as="searchLevelFromTsConfig" key="level">
<option {f:if(condition: '{level} == {selectedSearchLevel}', then: ' selected="selected"')} value="{level}">
<f:translate id="LLL:{searchLevelFromTsConfig}">{searchLevelFromTsConfig}</f:translate>
</option>
</f:for>
</select>
</div>
<div class="col-12 col-sm-3">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-default" name="search" title="{f:translate(id:'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search')}">
<core:icon identifier="actions-search" />
<f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.search"/>
</button>
</div>
</div>
</form>
</div>
</div>
</form>
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","./ElementBrowser","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,s,l){"use strict";l=__importDefault(l);return new class{constructor(){new l.default("click",(e,t)=>{e.preventDefault();const l=t.closest("span").dataset;s.insertElement(l.table,l.uid,l.title,"",1===parseInt(t.dataset.close||"0",10))}).delegateTo(document,"[data-close]");const e=document.getElementById("db_list-searchbox-toolbar");e.style.display="block",e.style.position="relative"}}}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","./ElementBrowser","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,r,n){"use strict";n=__importDefault(n);return new class{constructor(){new n.default("click",(e,t)=>{e.preventDefault();const n=t.closest("span").dataset;r.insertElement(n.table,n.uid,n.title,"",1===parseInt(t.dataset.close||"0",10))}).delegateTo(document,"[data-close]")}}}));
\ No newline at end of file
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","./LinkBrowser","TYPO3/CMS/Core/Event/RegularEvent"],(function(t,e,i,n){"use strict";n=__importDefault(n);return new class{constructor(){this.currentLink="",this.identifier="",this.currentLink=document.body.dataset.currentLink,this.identifier=document.body.dataset.identifier;const t=document.getElementById("db_list-searchbox-toolbar");t.style.display="block",t.style.position="relative",new n.default("click",(t,e)=>{t.preventDefault();const n=e.closest("span").dataset;i.finalizeFunction(this.identifier+n.uid)}).delegateTo(document,"[data-close]"),new n.default("click",(t,e)=>{t.preventDefault(),i.finalizeFunction(this.currentLink)}).delegateTo(document,"input.t3js-linkCurrent")}}}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","./LinkBrowser","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,i,n){"use strict";n=__importDefault(n);return new class{constructor(){this.currentLink="",this.identifier="",this.currentLink=document.body.dataset.currentLink,this.identifier=document.body.dataset.identifier,new n.default("click",(e,t)=>{e.preventDefault();const n=t.closest("span").dataset;i.finalizeFunction(this.identifier+n.uid)}).delegateTo(document,"[data-close]"),new n.default("click",(e,t)=>{e.preventDefault(),i.finalizeFunction(this.currentLink)}).delegateTo(document,"input.t3js-linkCurrent")}}}));
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment