cead0e18fe2e2fc13e8b6af91aa2f0e13cc39f72
[Packages/TYPO3.CMS.git] / typo3 / js / livesearch.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2009-2010 Michael Klapper <michael.klapper@aoemedia.de>
5 * (c) 2010 Jeff Segars <jeff@webempoweredchurch.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 Ext.namespace('TYPO3');
29
30 TYPO3.BackendLiveSearch = Ext.extend(Ext.form.ComboBox, {
31 autoSelect: false,
32 ctCls: 'live-search-results',
33 dataProvider: null,
34 searchResultsPid : 0,
35 displayField: 'title',
36 emptyText: null,
37 enableKeyEvents: true,
38 helpTitle: null,
39 hideTrigger: true,
40 itemSelector: 'div.search-item-title',
41 listAlign : 'tr-br',
42 listClass: 'live-search-list',
43 listEmptyText: null,
44 listWidth: 315,
45 loadingText: null,
46 minChars: 2,
47 resizable: false,
48 title: null,
49 width: 205,
50 hasIframeListeners: false,
51
52 triggerClass : 'x-form-clear-trigger',
53 triggerConfig: '<span tag="a" class="t3-icon t3-icon-actions t3-icon-actions-input t3-icon-input-clear t3-tceforms-input-clearer">&nbsp;</span>',
54 onTriggerClick: function() {
55 // Empty the form field, give it focus, and collapse the results
56 this.reset(this);
57 this.focus();
58 this.collapse();
59 },
60 tpl: new Ext.XTemplate(
61 '<table border="0" cellspacing="0">',
62 '<tpl for=".">',
63 '<tr class="search-item">',
64 '<td class="search-item-type" width="105" align="right">{recordTitle}</td>',
65 '<td class="search-item-content" width="195">',
66 '<div class="search-item-title">{iconHTML} {title}</div>',
67 '</td>',
68 '</tr>',
69 '</tpl>',
70 '</table>'
71 ),
72
73 dataReader : new Ext.data.JsonReader({
74 idProperty : 'type',
75 root : 'searchItems',
76 fields : [
77 {name: 'recordTitle'},
78 {name: 'id'},
79 {name: 'iconHTML'},
80 {name: 'title'},
81 {name: 'editLink'}
82 ]
83 }),
84 listeners: {
85 select : {
86 scope: this,
87 fn: function (combo, record, index) {
88 jump(record.data.editLink, 'web_list', 'web');
89 }
90 },
91 focus : {
92 fn: function() {
93 if (this.getValue() == this.emptyText) {
94 this.reset(this);
95 }
96 }
97 },
98 specialkey : function (field, e) {
99 if (e.getKey() == e.RETURN || e.getKey() == e.ENTER) {
100 if (this.dataReader.jsonData.pageJump != '') {
101 jump(this.dataReader.jsonData.pageJump, 'web_list', 'web');
102 } else {
103 TYPO3.ModuleMenu.App.showModule('web_list', this.getSearchResultsUrl(this.getValue()));
104 }
105 }
106 },
107 keyup : function() {
108 if ((this.getValue() == this.emptyText) || (this.getValue() == '')) {
109 this.setHideTrigger(true);
110 } else {
111 this.setHideTrigger(false);
112 }
113 }
114 },
115
116 /**
117 * Initializes the component.
118 */
119 initComponent: function() {
120 this.store = new Ext.data.DirectStore({
121 directFn: this.dataProvider.find,
122 reader: this.dataReader
123 });
124 TYPO3.BackendLiveSearch.superclass.initComponent.apply(this, arguments);
125 },
126
127 restrictHeight : function(){
128 this.innerList.dom.style.height = '';
129 var inner = this.innerList.dom;
130 var pad = this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight + 30; // @todo Remove hardcoded 30
131 var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
132 var ha = this.getPosition()[1]-Ext.getBody().getScroll().top;
133 var hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height;
134 var space = Math.max(ha, hb, this.minHeight || 0)-pad-2;
135 /** BUG FIX **/
136 if (this.shadow === true) { space-=this.list.shadow.offset; }
137
138 h = Math.min(h, space, this.maxHeight);
139
140 /**
141 * @internal The calcated height of "h" in the line before seems not working as expected.
142 * If i define a min height, the box shold at least use this height also if only one entry is in there
143 */
144 //h = this.maxHeight;
145
146 this.innerList.setHeight(h);
147 this.list.beginUpdate();
148 this.list.setHeight(h+pad);
149 this.list.alignTo(this.el, this.listAlign);
150 this.list.endUpdate();
151 },
152
153 initList : function () {
154 TYPO3.BackendLiveSearch.superclass.initList.apply(this, arguments);
155
156 var cls = 'x-combo-list';
157
158 /**
159 * Create bottom Toolbar to the result layer
160 */
161 this.footer = this.list.createChild({cls:cls+'-ft'});
162
163 this.pageTb = new Ext.Toolbar({
164 renderTo:this.footer,
165 height: 30,
166 items: [{
167 xtype: 'tbfill',
168 autoWidth : true
169 },{
170 xtype: 'button',
171 text: TYPO3.LLL.liveSearch.showAllResults,
172 arrowAlign : 'right',
173 shadow: false,
174 icon : '../typo3/sysext/t3skin/icons/module_web_list.gif',
175 listeners : {
176 scope : this,
177 click : function () {
178 // go to db_list.php and search for given search value
179 // @todo the current selected page ID from the page tree is required, also we need the
180 // values of $BE_USER->returnWebmounts() to search only during the allowed pages
181 TYPO3.ModuleMenu.App.showModule('web_list', this.getSearchResultsUrl(this.getValue()));
182 this.collapse();
183 }
184 }
185 }]
186 });
187 this.assetHeight += this.footer.getHeight();
188 },
189
190 initQuery : function(){
191 TYPO3.BackendLiveSearch.superclass.initQuery.apply(this, arguments);
192 this.removeHelp();
193 },
194 initHelp : function () {
195 if(!this.helpList){
196 var cls = 'search-list-help';
197
198 this.helpList = new Ext.Layer({
199 parentEl: this.getListParent(),
200 shadow: this.shadow,
201 cls: [cls, this.listClass].join(' '),
202 constrain:false
203 });
204
205 var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
206 this.helpList.setSize(lw);
207 this.helpList.swallowEvent('mousewheel');
208 if(this.syncFont !== false){
209 this.helpList.setStyle('font-size', this.el.getStyle('font-size'));
210 }
211
212 this.innerHelpList = this.helpList.createChild({cls:cls+'-inner'});
213 this.mon(this.innerHelpList, 'mouseover', this.onViewOver, this);
214 this.mon(this.innerHelpList, 'mousemove', this.onViewMove, this);
215 this.innerHelpList.setWidth(lw - this.helpList.getFrameWidth('lr'));
216
217 if(!this.helpTpl){
218 this.helpTpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
219 }
220
221 /**
222 * The {@link Ext.DataView DataView} used to display the ComboBox's options.
223 * @type Ext.DataView
224 */
225 this.helpView = new Ext.DataView({
226 applyTo: this.innerHelpList,
227 tpl: this.helpTpl,
228 singleSelect: true,
229 selectedClass: this.selectedClass,
230 itemSelector: this.itemSelector || '.' + cls + '-item',
231 emptyText: this.listEmptyText
232 });
233
234 this.helpList.createChild({
235 cls: cls + '-content',
236 // @todo Can we grab this content via ExtDirect?
237 html: '<strong>' + this.helpTitle + '</strong><p>' + TYPO3.LLL.liveSearch.helpDescription + '<br /> ' + TYPO3.LLL.liveSearch.helpDescriptionPages + '</p>'
238 });
239
240 this.helpList.alignTo(this.wrap, this.listAlign);
241 this.helpList.show();
242 }
243 },
244
245 removeHelp : function() {
246 if (this.helpList) {
247 this.helpList.destroy();
248 }
249 },
250
251 onFocus : function() {
252 TYPO3.BackendLiveSearch.superclass.onFocus.apply(this, arguments);
253
254 if (!this.hasIframeListeners) {
255 this.addIframeListeners();
256 }
257
258 // If search is blank, show the help on focus. Otherwise, show last results
259 if (this.getValue() == '') {
260 this.initHelp();
261 } else {
262 this.expand();
263 }
264 },
265
266 postBlur : function() {
267 TYPO3.BackendLiveSearch.superclass.postBlur.apply(this, arguments);
268 this.removeHelp();
269 },
270
271 getTriggerWidth : function() {
272 // Trigger is inset, so width used in calculations is 0
273 return 0;
274 },
275
276 reset : function() {
277 this.originalValue = this.emptyText;
278 this.setHideTrigger(true);
279 TYPO3.BackendLiveSearch.superclass.reset.apply(this, arguments);
280 },
281
282 getSearchResultsUrl : function(searchTerm) {
283 return 'id=' + this.searchResultsPid + '&search_levels=4&search_field=' + searchTerm;
284 },
285
286 addIframeListeners : function () {
287 // Add an event handler to each iframe, closing the search window when there's a click inside the iframe
288 // @todo Is there a cleaner way to handle this?
289 var iframes = Ext.query('iframe');
290 Ext.each(iframes, function(item, index, allItems) {
291 item.contentWindow.document.body.onclick = function() {
292 if (parent.TYPO3LiveSearch && parent.TYPO3LiveSearch.hasFocus) {
293 if (parent.TYPO3LiveSearch.isExpanded()) {
294 parent.TYPO3LiveSearch.collapse();
295 }
296
297 if (parent.TYPO3LiveSearch.getRawValue() == '') {
298 parent.TYPO3LiveSearch.originalValue = parent.TYPO3LiveSearch.emptyText;
299 parent.TYPO3LiveSearch.reset(this);
300 }
301
302 if (parent.TYPO3LiveSearch.helpList.isVisible()) {
303 parent.TYPO3LiveSearch.helpList.remove();
304 }
305 }
306 };
307 this.hasIframeListeners = true;
308 }, this);
309
310 }
311 });
312
313 var TYPO3LiveSearch;
314
315 Ext.onReady(function() {
316 TYPO3LiveSearch = new TYPO3.BackendLiveSearch({
317 dataProvider: TYPO3.LiveSearchActions.ExtDirect,
318 title: TYPO3.LLL.liveSearch.title,
319 helpTitle: TYPO3.LLL.liveSearch.helpTitle,
320 emptyText: TYPO3.LLL.liveSearch.emptyText,
321 loadingText: TYPO3.LLL.liveSearch.loadingText,
322 listEmptyText: TYPO3.LLL.liveSearch.listEmptyText,
323 searchResultsPid: TYPO3.configuration.firstWebmountPid
324 });
325
326 TYPO3LiveSearch.applyToMarkup(Ext.get('live-search-box'));
327 });