Skip to content

Commit

Permalink
[TASK] Migrate LiveSearch to TypeScript
Browse files Browse the repository at this point in the history
Resolves: #82592
Releases: master
Change-Id: I1d07106cf79d552b62b4e9dedd61d4d1f7de007f
Reviewed-on: https://review.typo3.org/55999
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Tested-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
  • Loading branch information
andreaskienast authored and bmack committed Mar 7, 2018
1 parent 1c22987 commit e10bee1
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 143 deletions.
3 changes: 3 additions & 0 deletions Build/types/TYPO3/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface Window {
};
rawurlencode: Function;
list_frame: Window;
jump: Function;
}

/**
Expand Down Expand Up @@ -99,4 +100,6 @@ interface JQuery {

// To be able to use twbs/bootstrap-slider we have to override the definition of jquerui
slider(options: { [key: string]: any }): any;
// To be able to use jquery/autocomplete-slider we have to override the definition of jquerui
autocomplete(options?: { [key: string]: any }): any;
}
201 changes: 201 additions & 0 deletions typo3/sysext/backend/Resources/Private/TypeScript/LiveSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

import * as $ from 'jquery';
import Viewport = require('./Viewport');
import Icons = require('./Icons');
import 'jquery/autocomplete';
import 'TYPO3/CMS/Backend/jquery.clearable';

enum Identifiers {
containerSelector = '#typo3-cms-backend-backend-toolbaritems-livesearchtoolbaritem',
toolbarItem = '.t3js-toolbar-item-search',
dropdownToggle = '.t3js-toolbar-search-dropdowntoggle',
searchFieldSelector = '.t3js-topbar-navigation-search-field',
formSelector = '.t3js-topbar-navigation-search'
}

interface ResultItem {
editLink: string;
iconHTML: string;
id: string;
pageId: number;
title: string;
typeLabel: string;
}

interface Suggestion {
data: ResultItem;
value: string;
}

/**
* Module: TYPO3/CMS/Backend/LiveSearch
* Global search to deal with everything in the backend that is search-related
* @exports TYPO3/CMS/Backend/LiveSearch
*/
class LiveSearch {
private url: string = TYPO3.settings.ajaxUrls.livesearch;

constructor() {
Viewport.Topbar.Toolbar.registerEvent((): void => {
this.registerAutocomplete();
this.registerEvents();

// Unset height, width and z-index
$(Identifiers.toolbarItem).removeAttr('style');

$(Identifiers.searchFieldSelector).clearable({
onClear: (): void => {
if ($(Identifiers.toolbarItem).hasClass('open')) {
$(Identifiers.dropdownToggle).dropdown('toggle');
}
}
});
});
}

private registerAutocomplete(): void {
$(Identifiers.searchFieldSelector).autocomplete({
// ajax options
serviceUrl: this.url,
paramName: 'q',
dataType: 'json',
minChars: 2,
width: '100%',
groupBy: 'typeLabel',
containerClass: Identifiers.toolbarItem.substr(1, Identifiers.toolbarItem.length),
appendTo: Identifiers.containerSelector + ' .dropdown-menu',
forceFixPosition: false,
preserveInput: true,
showNoSuggestionNotice: true,
triggerSelectOnValidInput: false,
preventBadQueries: false,
noSuggestionNotice: '<h3 class="dropdown-headline">' + TYPO3.lang.liveSearch_listEmptyText + '</h3>'
+ '<p>' + TYPO3.lang.liveSearch_helpTitle + '</p>'
+ '<hr>'
+ '<p>' + TYPO3.lang.liveSearch_helpDescription + '<br>' + TYPO3.lang.liveSearch_helpDescriptionPages + '</p>',
// put the AJAX results in the right format
transformResult: (response: Array<ResultItem>): { [key: string]: Array<Suggestion> } => {
return {
suggestions: $.map(response, (dataItem: ResultItem): Suggestion => {
return {value: dataItem.title, data: dataItem};
})
};
},
formatGroup: (suggestion: Suggestion, category: string, i: number): string => {
let html = '';
// add a divider if it's not the first group
if (i > 0) {
html = '<hr>';
}
return html + '<h3 class="dropdown-headline">' + category + '</h3>';
},
// Rendering of each item
formatResult: (suggestion: Suggestion): string => {
return ''
+ '<div class="dropdown-table">'
+ '<div class="dropdown-table-row">'
+ '<div class="dropdown-table-column dropdown-table-icon">' + suggestion.data.iconHTML + '</div>'
+ '<div class="dropdown-table-column dropdown-table-title">'
+ '<a class="dropdown-table-title-ellipsis dropdown-list-link"'
+ ' href="#" data-pageid="' + suggestion.data.pageId + '" data-target="' + suggestion.data.editLink + '">'
+ suggestion.data.title
+ '</a>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '';
},
onSearchStart: (): void => {
const $toolbarItem = $(Identifiers.toolbarItem);
if (!$toolbarItem.hasClass('loading')) {
$toolbarItem.addClass('loading');
Icons.getIcon(
'spinner-circle-light',
Icons.sizes.small,
'',
Icons.states.default,
Icons.markupIdentifiers.inline
).done((markup: string): void => {
$toolbarItem.find('.icon-apps-toolbar-menu-search').replaceWith(markup);
});
}
},
onSearchComplete: (): void => {
const $toolbarItem = $(Identifiers.toolbarItem);
const $searchField = $(Identifiers.searchFieldSelector);
if (!$toolbarItem.hasClass('open') && $searchField.val().length > 1) {
$(Identifiers.dropdownToggle).dropdown('toggle');
$searchField.focus();
}
if ($toolbarItem.hasClass('loading')) {
$toolbarItem.removeClass('loading');
Icons.getIcon(
'apps-toolbar-menu-search',
Icons.sizes.small,
'',
Icons.states.default,
Icons.markupIdentifiers.inline
).done((markup: string): void => {
$toolbarItem.find('.icon-spinner-circle-light').replaceWith(markup);
});
}
},
beforeRender: (container: JQuery): void => {
container.append('<hr><div>' +
'<a href="#" class="btn btn-primary pull-right t3js-live-search-show-all">' +
TYPO3.lang.liveSearch_showAllResults +
'</a>' +
'</div>');
if (!$(Identifiers.toolbarItem).hasClass('open')) {
$(Identifiers.dropdownToggle).dropdown('toggle');
$(Identifiers.searchFieldSelector).focus();
}
},
onHide: (): void => {
if ($(Identifiers.toolbarItem).hasClass('open')) {
$(Identifiers.dropdownToggle).dropdown('toggle');
}
}
});
}

private registerEvents(): void {
const $searchField = $(Identifiers.searchFieldSelector);
const $autocompleteContainer = $('.' + $searchField.autocomplete().options.containerClass);

$(Identifiers.containerSelector).on('click', '.t3js-live-search-show-all', (evt: JQueryEventObject): void => {
evt.preventDefault();

TYPO3.ModuleMenu.App.showModule('web_list', 'id=0&search_levels=-1&search_field=' + encodeURIComponent($searchField.val()));
$searchField.val('').trigger('change');
});
if ($searchField.length) {
$autocompleteContainer.on('click.autocomplete', '.dropdown-list-link', (evt: JQueryEventObject): void => {
evt.preventDefault();

const $me = $(evt.currentTarget);
top.jump($me.data('target'), 'web_list', 'web', $me.data('pageid'));
$searchField.val('').trigger('change');
});
}

// Prevent submitting the search form
$(Identifiers.formSelector).on('submit', (evt: JQueryEventObject): void => {
evt.preventDefault();
});
}
}

export = new LiveSearch();
144 changes: 1 addition & 143 deletions typo3/sysext/backend/Resources/Public/JavaScript/LiveSearch.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e10bee1

Please sign in to comment.