[BUGFIX] Prevent loading jsfunc.inline.js twice
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Private / TypeScript / LiveSearch.ts
1 /*
2  * This file is part of the TYPO3 CMS project.
3  *
4  * It is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License, either version 2
6  * of the License, or any later version.
7  *
8  * For the full copyright and license information, please read the
9  * LICENSE.txt file that was distributed with this source code.
10  *
11  * The TYPO3 project - inspiring people to share!
12  */
13
14 import * as $ from 'jquery';
15 import Viewport = require('./Viewport');
16 import Icons = require('./Icons');
17 import 'jquery/autocomplete';
18 import 'TYPO3/CMS/Backend/jquery.clearable';
19
20 enum Identifiers {
21   containerSelector = '#typo3-cms-backend-backend-toolbaritems-livesearchtoolbaritem',
22   toolbarItem = '.t3js-toolbar-item-search',
23   dropdownToggle = '.t3js-toolbar-search-dropdowntoggle',
24   searchFieldSelector = '.t3js-topbar-navigation-search-field',
25   formSelector = '.t3js-topbar-navigation-search'
26 }
27
28 interface ResultItem {
29   editLink: string;
30   iconHTML: string;
31   id: string;
32   pageId: number;
33   title: string;
34   typeLabel: string;
35 }
36
37 interface Suggestion {
38   data: ResultItem;
39   value: string;
40 }
41
42 /**
43  * Module: TYPO3/CMS/Backend/LiveSearch
44  * Global search to deal with everything in the backend that is search-related
45  * @exports TYPO3/CMS/Backend/LiveSearch
46  */
47 class LiveSearch {
48   private url: string = TYPO3.settings.ajaxUrls.livesearch;
49
50   constructor() {
51     Viewport.Topbar.Toolbar.registerEvent((): void => {
52       this.registerAutocomplete();
53       this.registerEvents();
54
55       // Unset height, width and z-index
56       $(Identifiers.toolbarItem).removeAttr('style');
57
58       $(Identifiers.searchFieldSelector).clearable({
59         onClear: (): void => {
60           if ($(Identifiers.toolbarItem).hasClass('open')) {
61             $(Identifiers.dropdownToggle).dropdown('toggle');
62           }
63         }
64       });
65     });
66   }
67
68   private registerAutocomplete(): void {
69     $(Identifiers.searchFieldSelector).autocomplete({
70       // ajax options
71       serviceUrl: this.url,
72       paramName: 'q',
73       dataType: 'json',
74       minChars: 2,
75       width: '100%',
76       groupBy: 'typeLabel',
77       containerClass: Identifiers.toolbarItem.substr(1, Identifiers.toolbarItem.length),
78       appendTo: Identifiers.containerSelector + ' .dropdown-menu',
79       forceFixPosition: false,
80       preserveInput: true,
81       showNoSuggestionNotice: true,
82       triggerSelectOnValidInput: false,
83       preventBadQueries: false,
84       noSuggestionNotice: '<h3 class="dropdown-headline">' + TYPO3.lang.liveSearch_listEmptyText + '</h3>'
85       + '<p>' + TYPO3.lang.liveSearch_helpTitle + '</p>'
86       + '<hr>'
87       + '<p>' + TYPO3.lang.liveSearch_helpDescription + '<br>' + TYPO3.lang.liveSearch_helpDescriptionPages + '</p>',
88       // put the AJAX results in the right format
89       transformResult: (response: Array<ResultItem>): { [key: string]: Array<Suggestion> } => {
90         return {
91           suggestions: $.map(response, (dataItem: ResultItem): Suggestion => {
92             return {value: dataItem.title, data: dataItem};
93           })
94         };
95       },
96       formatGroup: (suggestion: Suggestion, category: string, i: number): string => {
97         let html = '';
98         // add a divider if it's not the first group
99         if (i > 0) {
100           html = '<hr>';
101         }
102         return html + '<h3 class="dropdown-headline">' + category + '</h3>';
103       },
104       // Rendering of each item
105       formatResult: (suggestion: Suggestion): string => {
106         return ''
107           + '<div class="dropdown-table">'
108           + '<div class="dropdown-table-row">'
109           + '<div class="dropdown-table-column dropdown-table-icon">' + suggestion.data.iconHTML + '</div>'
110           + '<div class="dropdown-table-column dropdown-table-title">'
111           + '<a class="dropdown-table-title-ellipsis dropdown-list-link"'
112           + ' href="#" data-pageid="' + suggestion.data.pageId + '" data-target="' + suggestion.data.editLink + '">'
113           + suggestion.data.title
114           + '</a>'
115           + '</div>'
116           + '</div>'
117           + '</div>'
118           + '';
119       },
120       onSearchStart: (): void => {
121         const $toolbarItem = $(Identifiers.toolbarItem);
122         if (!$toolbarItem.hasClass('loading')) {
123           $toolbarItem.addClass('loading');
124           Icons.getIcon(
125             'spinner-circle-light',
126             Icons.sizes.small,
127             '',
128             Icons.states.default,
129             Icons.markupIdentifiers.inline
130           ).done((markup: string): void => {
131             $toolbarItem.find('.icon-apps-toolbar-menu-search').replaceWith(markup);
132           });
133         }
134       },
135       onSearchComplete: (): void => {
136         const $toolbarItem = $(Identifiers.toolbarItem);
137         const $searchField = $(Identifiers.searchFieldSelector);
138         if (!$toolbarItem.hasClass('open') && $searchField.val().length > 1) {
139           $(Identifiers.dropdownToggle).dropdown('toggle');
140           $searchField.focus();
141         }
142         if ($toolbarItem.hasClass('loading')) {
143           $toolbarItem.removeClass('loading');
144           Icons.getIcon(
145             'apps-toolbar-menu-search',
146             Icons.sizes.small,
147             '',
148             Icons.states.default,
149             Icons.markupIdentifiers.inline
150           ).done((markup: string): void => {
151             $toolbarItem.find('.icon-spinner-circle-light').replaceWith(markup);
152           });
153         }
154       },
155       beforeRender: (container: JQuery): void => {
156         container.append('<hr><div>' +
157           '<a href="#" class="btn btn-primary pull-right t3js-live-search-show-all">' +
158           TYPO3.lang.liveSearch_showAllResults +
159           '</a>' +
160           '</div>');
161         if (!$(Identifiers.toolbarItem).hasClass('open')) {
162           $(Identifiers.dropdownToggle).dropdown('toggle');
163           $(Identifiers.searchFieldSelector).focus();
164         }
165       },
166       onHide: (): void => {
167         if ($(Identifiers.toolbarItem).hasClass('open')) {
168           $(Identifiers.dropdownToggle).dropdown('toggle');
169         }
170       }
171     });
172   }
173
174   private registerEvents(): void {
175     const $searchField = $(Identifiers.searchFieldSelector);
176
177     $(Identifiers.containerSelector).on('click', '.t3js-live-search-show-all', (evt: JQueryEventObject): void => {
178       evt.preventDefault();
179
180       TYPO3.ModuleMenu.App.showModule('web_list', 'id=0&search_levels=-1&search_field=' + encodeURIComponent($searchField.val()));
181       $searchField.val('').trigger('change');
182     });
183     if ($searchField.length) {
184       const $autocompleteContainer = $('.' + $searchField.autocomplete().options.containerClass);
185       $autocompleteContainer.on('click.autocomplete', '.dropdown-list-link', (evt: JQueryEventObject): void => {
186         evt.preventDefault();
187
188         const $me = $(evt.currentTarget);
189         top.jump($me.data('target'), 'web_list', 'web', $me.data('pageid'));
190         $searchField.val('').trigger('change');
191       });
192     }
193
194     // Prevent submitting the search form
195     $(Identifiers.formSelector).on('submit', (evt: JQueryEventObject): void => {
196       evt.preventDefault();
197     });
198   }
199 }
200
201 export = new LiveSearch();