Chris@76
|
1 // This file contains javascript associated with a autosuggest control
|
Chris@76
|
2 function smc_AutoSuggest(oOptions)
|
Chris@76
|
3 {
|
Chris@76
|
4 this.opt = oOptions;
|
Chris@76
|
5
|
Chris@76
|
6 // Store the handle to the text box.
|
Chris@76
|
7 this.oTextHandle = document.getElementById(this.opt.sControlId);
|
Chris@76
|
8 this.oRealTextHandle = null;
|
Chris@76
|
9
|
Chris@76
|
10 this.oSuggestDivHandle = null;
|
Chris@76
|
11 this.sLastSearch = '';
|
Chris@76
|
12 this.sLastDirtySearch = '';
|
Chris@76
|
13 this.oSelectedDiv = null;
|
Chris@76
|
14 this.aCache = [];
|
Chris@76
|
15 this.aDisplayData = [];
|
Chris@76
|
16
|
Chris@76
|
17 this.sRetrieveURL = 'sRetrieveURL' in this.opt ? this.opt.sRetrieveURL : '%scripturl%action=suggest;suggest_type=%suggest_type%;search=%search%;%sessionVar%=%sessionID%;xml;time=%time%';
|
Chris@76
|
18
|
Chris@76
|
19 // How many objects can we show at once?
|
Chris@76
|
20 this.iMaxDisplayQuantity = 'iMaxDisplayQuantity' in this.opt ? this.opt.iMaxDisplayQuantity : 15;
|
Chris@76
|
21
|
Chris@76
|
22 // How many characters shall we start searching on?
|
Chris@76
|
23 this.iMinimumSearchChars = 'iMinimumSearchChars' in this.opt ? this.opt.iMinimumSearchChars : 3;
|
Chris@76
|
24
|
Chris@76
|
25 // Should selected items be added to a list?
|
Chris@76
|
26 this.bItemList = 'bItemList' in this.opt ? this.opt.bItemList : false;
|
Chris@76
|
27
|
Chris@76
|
28 // Are there any items that should be added in advance?
|
Chris@76
|
29 this.aListItems = 'aListItems' in this.opt ? this.opt.aListItems : [];
|
Chris@76
|
30
|
Chris@76
|
31 this.sItemTemplate = 'sItemTemplate' in this.opt ? this.opt.sItemTemplate : '<input type="hidden" name="%post_name%[]" value="%item_id%" /><a href="%item_href%" class="extern" onclick="window.open(this.href, \'_blank\'); return false;">%item_name%</a> <img src="%images_url%/pm_recipient_delete.gif" alt="%delete_text%" title="%delete_text%" onclick="return %self%.deleteAddedItem(%item_id%);" />';
|
Chris@76
|
32
|
Chris@76
|
33 this.sTextDeleteItem = 'sTextDeleteItem' in this.opt ? this.opt.sTextDeleteItem : '';
|
Chris@76
|
34
|
Chris@76
|
35 this.oCallback = {};
|
Chris@76
|
36 this.bDoAutoAdd = false;
|
Chris@76
|
37 this.iItemCount = 0;
|
Chris@76
|
38
|
Chris@76
|
39 this.oHideTimer = null;
|
Chris@76
|
40 this.bPositionComplete = false;
|
Chris@76
|
41
|
Chris@76
|
42 this.oXmlRequestHandle = null;
|
Chris@76
|
43
|
Chris@76
|
44 // Just make sure the page is loaded before calling the init.
|
Chris@76
|
45 addLoadEvent(this.opt.sSelf + '.init();');
|
Chris@76
|
46 }
|
Chris@76
|
47
|
Chris@76
|
48 smc_AutoSuggest.prototype.init = function()
|
Chris@76
|
49 {
|
Chris@76
|
50 if (!window.XMLHttpRequest)
|
Chris@76
|
51 return false;
|
Chris@76
|
52
|
Chris@76
|
53 // Create a div that'll contain the results later on.
|
Chris@76
|
54 this.oSuggestDivHandle = document.createElement('div');
|
Chris@76
|
55 this.oSuggestDivHandle.className = 'auto_suggest_div';
|
Chris@76
|
56 document.body.appendChild(this.oSuggestDivHandle);
|
Chris@76
|
57
|
Chris@76
|
58 // Create a backup text input.
|
Chris@76
|
59 this.oRealTextHandle = document.createElement('input');
|
Chris@76
|
60 this.oRealTextHandle.type = 'hidden';
|
Chris@76
|
61 this.oRealTextHandle.name = this.oTextHandle.name;
|
Chris@76
|
62 this.oRealTextHandle.value = this.oTextHandle.value;
|
Chris@76
|
63 this.oTextHandle.form.appendChild(this.oRealTextHandle);
|
Chris@76
|
64
|
Chris@76
|
65 // Disable autocomplete in any browser by obfuscating the name.
|
Chris@76
|
66 this.oTextHandle.name = 'dummy_' + Math.floor(Math.random() * 1000000);
|
Chris@76
|
67 this.oTextHandle.autocomplete = 'off';
|
Chris@76
|
68
|
Chris@76
|
69 this.oTextHandle.instanceRef = this;
|
Chris@76
|
70
|
Chris@76
|
71 var fOnKeyDown = function (oEvent) {
|
Chris@76
|
72 return this.instanceRef.handleKey(oEvent);
|
Chris@76
|
73 };
|
Chris@76
|
74 is_opera ? this.oTextHandle.onkeypress = fOnKeyDown : this.oTextHandle.onkeydown = fOnKeyDown;
|
Chris@76
|
75
|
Chris@76
|
76 this.oTextHandle.onkeyup = function (oEvent) {
|
Chris@76
|
77 return this.instanceRef.autoSuggestUpdate(oEvent);
|
Chris@76
|
78 };
|
Chris@76
|
79
|
Chris@76
|
80 this.oTextHandle.onchange = function (oEvent) {
|
Chris@76
|
81 return this.instanceRef.autoSuggestUpdate(oEvent);
|
Chris@76
|
82 };
|
Chris@76
|
83
|
Chris@76
|
84 this.oTextHandle.onblur = function (oEvent) {
|
Chris@76
|
85 return this.instanceRef.autoSuggestHide(oEvent);
|
Chris@76
|
86 };
|
Chris@76
|
87
|
Chris@76
|
88 this.oTextHandle.onfocus = function (oEvent) {
|
Chris@76
|
89 return this.instanceRef.autoSuggestUpdate(oEvent);
|
Chris@76
|
90 };
|
Chris@76
|
91
|
Chris@76
|
92 if (this.bItemList)
|
Chris@76
|
93 {
|
Chris@76
|
94 if ('sItemListContainerId' in this.opt)
|
Chris@76
|
95 this.oItemList = document.getElementById(this.opt.sItemListContainerId);
|
Chris@76
|
96 else
|
Chris@76
|
97 {
|
Chris@76
|
98 this.oItemList = document.createElement('div');
|
Chris@76
|
99 this.oTextHandle.parentNode.insertBefore(this.oItemList, this.oTextHandle.nextSibling);
|
Chris@76
|
100 }
|
Chris@76
|
101 }
|
Chris@76
|
102
|
Chris@76
|
103 if (this.aListItems.length > 0)
|
Chris@76
|
104 for (var i = 0, n = this.aListItems.length; i < n; i++)
|
Chris@76
|
105 this.addItemLink(this.aListItems[i].sItemId, this.aListItems[i].sItemName);
|
Chris@76
|
106
|
Chris@76
|
107 return true;
|
Chris@76
|
108 }
|
Chris@76
|
109
|
Chris@76
|
110 // Was it an enter key - if so assume they are trying to select something.
|
Chris@76
|
111 smc_AutoSuggest.prototype.handleKey = function(oEvent)
|
Chris@76
|
112 {
|
Chris@76
|
113 // Grab the event object, one way or the other
|
Chris@76
|
114 if (!oEvent)
|
Chris@76
|
115 oEvent = window.event;
|
Chris@76
|
116
|
Chris@76
|
117 // Get the keycode of the key that was pressed.
|
Chris@76
|
118 var iKeyPress = 0;
|
Chris@76
|
119 if ('keyCode' in oEvent)
|
Chris@76
|
120 iKeyPress = oEvent.keyCode;
|
Chris@76
|
121 else if ('which' in oEvent)
|
Chris@76
|
122 iKeyPress = oEvent.which;
|
Chris@76
|
123
|
Chris@76
|
124 switch (iKeyPress)
|
Chris@76
|
125 {
|
Chris@76
|
126 // Tab.
|
Chris@76
|
127 case 9:
|
Chris@76
|
128 if (this.aDisplayData.length > 0)
|
Chris@76
|
129 {
|
Chris@76
|
130 if (this.oSelectedDiv != null)
|
Chris@76
|
131 this.itemClicked(this.oSelectedDiv);
|
Chris@76
|
132 else
|
Chris@76
|
133 this.handleSubmit();
|
Chris@76
|
134 }
|
Chris@76
|
135
|
Chris@76
|
136 // Continue to the next control.
|
Chris@76
|
137 return true;
|
Chris@76
|
138 break;
|
Chris@76
|
139
|
Chris@76
|
140 // Enter.
|
Chris@76
|
141 case 13:
|
Chris@76
|
142 if (this.aDisplayData.length > 0 && this.oSelectedDiv != null)
|
Chris@76
|
143 {
|
Chris@76
|
144 this.itemClicked(this.oSelectedDiv);
|
Chris@76
|
145
|
Chris@76
|
146 // Do our best to stop it submitting the form!
|
Chris@76
|
147 return false;
|
Chris@76
|
148 }
|
Chris@76
|
149 else
|
Chris@76
|
150 return true;
|
Chris@76
|
151
|
Chris@76
|
152 break;
|
Chris@76
|
153
|
Chris@76
|
154 // Up/Down arrow?
|
Chris@76
|
155 case 38:
|
Chris@76
|
156 case 40:
|
Chris@76
|
157 if (this.aDisplayData.length && this.oSuggestDivHandle.style.visibility != 'hidden')
|
Chris@76
|
158 {
|
Chris@76
|
159 // Loop through the display data trying to find our entry.
|
Chris@76
|
160 var bPrevHandle = false;
|
Chris@76
|
161 var oToHighlight = null;
|
Chris@76
|
162 for (var i = 0; i < this.aDisplayData.length; i++)
|
Chris@76
|
163 {
|
Chris@76
|
164 // If we're going up and yet the top one was already selected don't go around.
|
Chris@76
|
165 if (this.oSelectedDiv != null && this.oSelectedDiv == this.aDisplayData[i] && i == 0 && iKeyPress == 38)
|
Chris@76
|
166 {
|
Chris@76
|
167 oToHighlight = this.oSelectedDiv;
|
Chris@76
|
168 break;
|
Chris@76
|
169 }
|
Chris@76
|
170 // If nothing is selected and we are going down then we select the first one.
|
Chris@76
|
171 if (this.oSelectedDiv == null && iKeyPress == 40)
|
Chris@76
|
172 {
|
Chris@76
|
173 oToHighlight = this.aDisplayData[i];
|
Chris@76
|
174 break;
|
Chris@76
|
175 }
|
Chris@76
|
176
|
Chris@76
|
177 // If the previous handle was the actual previously selected one and we're hitting down then this is the one we want.
|
Chris@76
|
178 if (bPrevHandle != false && bPrevHandle == this.oSelectedDiv && iKeyPress == 40)
|
Chris@76
|
179 {
|
Chris@76
|
180 oToHighlight = this.aDisplayData[i];
|
Chris@76
|
181 break;
|
Chris@76
|
182 }
|
Chris@76
|
183 // If we're going up and this is the previously selected one then we want the one before, if there was one.
|
Chris@76
|
184 if (bPrevHandle != false && this.aDisplayData[i] == this.oSelectedDiv && iKeyPress == 38)
|
Chris@76
|
185 {
|
Chris@76
|
186 oToHighlight = bPrevHandle;
|
Chris@76
|
187 break;
|
Chris@76
|
188 }
|
Chris@76
|
189 // Make the previous handle this!
|
Chris@76
|
190 bPrevHandle = this.aDisplayData[i];
|
Chris@76
|
191 }
|
Chris@76
|
192
|
Chris@76
|
193 // If we don't have one to highlight by now then it must be the last one that we're after.
|
Chris@76
|
194 if (oToHighlight == null)
|
Chris@76
|
195 oToHighlight = bPrevHandle;
|
Chris@76
|
196
|
Chris@76
|
197 // Remove any old highlighting.
|
Chris@76
|
198 if (this.oSelectedDiv != null)
|
Chris@76
|
199 this.itemMouseOut(this.oSelectedDiv);
|
Chris@76
|
200 // Mark what the selected div now is.
|
Chris@76
|
201 this.oSelectedDiv = oToHighlight;
|
Chris@76
|
202 this.itemMouseOver(this.oSelectedDiv);
|
Chris@76
|
203 }
|
Chris@76
|
204 break;
|
Chris@76
|
205 }
|
Chris@76
|
206 return true;
|
Chris@76
|
207 }
|
Chris@76
|
208
|
Chris@76
|
209 // Functions for integration.
|
Chris@76
|
210 smc_AutoSuggest.prototype.registerCallback = function(sCallbackType, sCallback)
|
Chris@76
|
211 {
|
Chris@76
|
212 switch (sCallbackType)
|
Chris@76
|
213 {
|
Chris@76
|
214 case 'onBeforeAddItem':
|
Chris@76
|
215 this.oCallback.onBeforeAddItem = sCallback;
|
Chris@76
|
216 break;
|
Chris@76
|
217
|
Chris@76
|
218 case 'onAfterAddItem':
|
Chris@76
|
219 this.oCallback.onAfterAddItem = sCallback;
|
Chris@76
|
220 break;
|
Chris@76
|
221
|
Chris@76
|
222 case 'onAfterDeleteItem':
|
Chris@76
|
223 this.oCallback.onAfterDeleteItem = sCallback;
|
Chris@76
|
224 break;
|
Chris@76
|
225
|
Chris@76
|
226 case 'onBeforeUpdate':
|
Chris@76
|
227 this.oCallback.onBeforeUpdate = sCallback;
|
Chris@76
|
228 break;
|
Chris@76
|
229 }
|
Chris@76
|
230 }
|
Chris@76
|
231
|
Chris@76
|
232 // User hit submit?
|
Chris@76
|
233 smc_AutoSuggest.prototype.handleSubmit = function()
|
Chris@76
|
234 {
|
Chris@76
|
235 var bReturnValue = true;
|
Chris@76
|
236 var oFoundEntry = null;
|
Chris@76
|
237
|
Chris@76
|
238 // Do we have something that matches the current text?
|
Chris@76
|
239 for (var i = 0; i < this.aCache.length; i++)
|
Chris@76
|
240 {
|
Chris@76
|
241 if (this.sLastSearch.toLowerCase() == this.aCache[i].sItemName.toLowerCase().substr(0, this.sLastSearch.length))
|
Chris@76
|
242 {
|
Chris@76
|
243 // Exact match?
|
Chris@76
|
244 if (this.sLastSearch.length == this.aCache[i].sItemName.length)
|
Chris@76
|
245 {
|
Chris@76
|
246 // This is the one!
|
Chris@76
|
247 oFoundEntry = {
|
Chris@76
|
248 sItemId: this.aCache[i].sItemId,
|
Chris@76
|
249 sItemName: this.aCache[i].sItemName
|
Chris@76
|
250 };
|
Chris@76
|
251 break;
|
Chris@76
|
252 }
|
Chris@76
|
253
|
Chris@76
|
254 // Not an exact match, but it'll do for now.
|
Chris@76
|
255 else
|
Chris@76
|
256 {
|
Chris@76
|
257 // If we have two matches don't find anything.
|
Chris@76
|
258 if (oFoundEntry != null)
|
Chris@76
|
259 bReturnValue = false;
|
Chris@76
|
260 else
|
Chris@76
|
261 oFoundEntry = {
|
Chris@76
|
262 sItemId: this.aCache[i].sItemId,
|
Chris@76
|
263 sItemName: this.aCache[i].sItemName
|
Chris@76
|
264 };
|
Chris@76
|
265 }
|
Chris@76
|
266 }
|
Chris@76
|
267 }
|
Chris@76
|
268
|
Chris@76
|
269 if (oFoundEntry == null || bReturnValue == false)
|
Chris@76
|
270 return bReturnValue;
|
Chris@76
|
271 else
|
Chris@76
|
272 {
|
Chris@76
|
273 this.addItemLink(oFoundEntry.sItemId, oFoundEntry.sItemName, true);
|
Chris@76
|
274 return false;
|
Chris@76
|
275 }
|
Chris@76
|
276 }
|
Chris@76
|
277
|
Chris@76
|
278 // Positions the box correctly on the window.
|
Chris@76
|
279 smc_AutoSuggest.prototype.positionDiv = function()
|
Chris@76
|
280 {
|
Chris@76
|
281 // Only do it once.
|
Chris@76
|
282 if (this.bPositionComplete)
|
Chris@76
|
283 return true;
|
Chris@76
|
284
|
Chris@76
|
285 this.bPositionComplete = true;
|
Chris@76
|
286
|
Chris@76
|
287 // Put the div under the text box.
|
Chris@76
|
288 var aParentPos = smf_itemPos(this.oTextHandle);
|
Chris@76
|
289
|
Chris@76
|
290 this.oSuggestDivHandle.style.left = aParentPos[0] + 'px';
|
Chris@76
|
291 this.oSuggestDivHandle.style.top = (aParentPos[1] + this.oTextHandle.offsetHeight) + 'px';
|
Chris@76
|
292 this.oSuggestDivHandle.style.width = this.oTextHandle.style.width;
|
Chris@76
|
293
|
Chris@76
|
294 return true;
|
Chris@76
|
295 }
|
Chris@76
|
296
|
Chris@76
|
297 // Do something after clicking an item.
|
Chris@76
|
298 smc_AutoSuggest.prototype.itemClicked = function(oCurElement)
|
Chris@76
|
299 {
|
Chris@76
|
300 // Is there a div that we are populating?
|
Chris@76
|
301 if (this.bItemList)
|
Chris@76
|
302 this.addItemLink(oCurElement.sItemId, oCurElement.innerHTML);
|
Chris@76
|
303
|
Chris@76
|
304 // Otherwise clear things down.
|
Chris@76
|
305 else
|
Chris@76
|
306 this.oTextHandle.value = oCurElement.innerHTML.php_unhtmlspecialchars();
|
Chris@76
|
307
|
Chris@76
|
308 this.oRealTextHandle.value = this.oTextHandle.value;
|
Chris@76
|
309 this.autoSuggestActualHide();
|
Chris@76
|
310 this.oSelectedDiv = null;
|
Chris@76
|
311 }
|
Chris@76
|
312
|
Chris@76
|
313 // Remove the last searched for name from the search box.
|
Chris@76
|
314 smc_AutoSuggest.prototype.removeLastSearchString = function ()
|
Chris@76
|
315 {
|
Chris@76
|
316 // Remove the text we searched for from the div.
|
Chris@76
|
317 var sTempText = this.oTextHandle.value.toLowerCase();
|
Chris@76
|
318 var iStartString = sTempText.indexOf(this.sLastSearch.toLowerCase());
|
Chris@76
|
319 // Just attempt to remove the bits we just searched for.
|
Chris@76
|
320 if (iStartString != -1)
|
Chris@76
|
321 {
|
Chris@76
|
322 while (iStartString > 0)
|
Chris@76
|
323 {
|
Chris@76
|
324 if (sTempText.charAt(iStartString - 1) == '"' || sTempText.charAt(iStartString - 1) == ',' || sTempText.charAt(iStartString - 1) == ' ')
|
Chris@76
|
325 {
|
Chris@76
|
326 iStartString--;
|
Chris@76
|
327 if (sTempText.charAt(iStartString - 1) == ',')
|
Chris@76
|
328 break;
|
Chris@76
|
329 }
|
Chris@76
|
330 else
|
Chris@76
|
331 break;
|
Chris@76
|
332 }
|
Chris@76
|
333
|
Chris@76
|
334 // Now remove anything from iStartString upwards.
|
Chris@76
|
335 this.oTextHandle.value = this.oTextHandle.value.substr(0, iStartString);
|
Chris@76
|
336 }
|
Chris@76
|
337 // Just take it all.
|
Chris@76
|
338 else
|
Chris@76
|
339 this.oTextHandle.value = '';
|
Chris@76
|
340 }
|
Chris@76
|
341
|
Chris@76
|
342 // Add a result if not already done.
|
Chris@76
|
343 smc_AutoSuggest.prototype.addItemLink = function (sItemId, sItemName, bFromSubmit)
|
Chris@76
|
344 {
|
Chris@76
|
345 // Increase the internal item count.
|
Chris@76
|
346 this.iItemCount ++;
|
Chris@76
|
347
|
Chris@76
|
348 // If there's a callback then call it.
|
Chris@76
|
349 if ('oCallback' in this && 'onBeforeAddItem' in this.oCallback && typeof(this.oCallback.onBeforeAddItem) == 'string')
|
Chris@76
|
350 {
|
Chris@76
|
351 // If it returns false the item must not be added.
|
Chris@76
|
352 if (!eval(this.oCallback.onBeforeAddItem + '(' + this.opt.sSelf + ', \'' + sItemId + '\');'))
|
Chris@76
|
353 return;
|
Chris@76
|
354 }
|
Chris@76
|
355
|
Chris@76
|
356 var oNewDiv = document.createElement('div');
|
Chris@76
|
357 oNewDiv.id = 'suggest_' + this.opt.sSuggestId + '_' + sItemId;
|
Chris@76
|
358 setInnerHTML(oNewDiv, this.sItemTemplate.replace(/%post_name%/g, this.opt.sPostName).replace(/%item_id%/g, sItemId).replace(/%item_href%/g, smf_prepareScriptUrl(smf_scripturl) + this.opt.sURLMask.replace(/%item_id%/g, sItemId)).replace(/%item_name%/g, sItemName).replace(/%images_url%/g, smf_images_url).replace(/%self%/g, this.opt.sSelf).replace(/%delete_text%/g, this.sTextDeleteItem));
|
Chris@76
|
359 this.oItemList.appendChild(oNewDiv);
|
Chris@76
|
360
|
Chris@76
|
361 // If there's a registered callback, call it.
|
Chris@76
|
362 if ('oCallback' in this && 'onAfterAddItem' in this.oCallback && typeof(this.oCallback.onAfterAddItem) == 'string')
|
Chris@76
|
363 eval(this.oCallback.onAfterAddItem + '(' + this.opt.sSelf + ', \'' + oNewDiv.id + '\', ' + this.iItemCount + ');');
|
Chris@76
|
364
|
Chris@76
|
365 // Clear the div a bit.
|
Chris@76
|
366 this.removeLastSearchString();
|
Chris@76
|
367
|
Chris@76
|
368 // If we came from a submit, and there's still more to go, turn on auto add for all the other things.
|
Chris@76
|
369 this.bDoAutoAdd = this.oTextHandle.value != '' && bFromSubmit;
|
Chris@76
|
370
|
Chris@76
|
371 // Update the fellow..
|
Chris@76
|
372 this.autoSuggestUpdate();
|
Chris@76
|
373 }
|
Chris@76
|
374
|
Chris@76
|
375 // Delete an item that has been added, if at all?
|
Chris@76
|
376 smc_AutoSuggest.prototype.deleteAddedItem = function (sItemId)
|
Chris@76
|
377 {
|
Chris@76
|
378 var oDiv = document.getElementById('suggest_' + this.opt.sSuggestId + '_' + sItemId);
|
Chris@76
|
379
|
Chris@76
|
380 // Remove the div if it exists.
|
Chris@76
|
381 if (typeof(oDiv) == 'object' && oDiv != null)
|
Chris@76
|
382 {
|
Chris@76
|
383 oDiv.parentNode.removeChild(document.getElementById('suggest_' + this.opt.sSuggestId + '_' + sItemId));
|
Chris@76
|
384
|
Chris@76
|
385 // Decrease the internal item count.
|
Chris@76
|
386 this.iItemCount --;
|
Chris@76
|
387
|
Chris@76
|
388 // If there's a registered callback, call it.
|
Chris@76
|
389 if ('oCallback' in this && 'onAfterDeleteItem' in this.oCallback && typeof(this.oCallback.onAfterDeleteItem) == 'string')
|
Chris@76
|
390 eval(this.oCallback.onAfterDeleteItem + '(' + this.opt.sSelf + ', ' + this.iItemCount + ');');
|
Chris@76
|
391 }
|
Chris@76
|
392
|
Chris@76
|
393 return false;
|
Chris@76
|
394 }
|
Chris@76
|
395
|
Chris@76
|
396 // Hide the box.
|
Chris@76
|
397 smc_AutoSuggest.prototype.autoSuggestHide = function ()
|
Chris@76
|
398 {
|
Chris@76
|
399 // Delay to allow events to propogate through....
|
Chris@76
|
400 this.oHideTimer = setTimeout(this.opt.sSelf + '.autoSuggestActualHide();', 250);
|
Chris@76
|
401 }
|
Chris@76
|
402
|
Chris@76
|
403 // Do the actual hiding after a timeout.
|
Chris@76
|
404 smc_AutoSuggest.prototype.autoSuggestActualHide = function()
|
Chris@76
|
405 {
|
Chris@76
|
406 this.oSuggestDivHandle.style.display = 'none';
|
Chris@76
|
407 this.oSuggestDivHandle.style.visibility = 'hidden';
|
Chris@76
|
408 this.oSelectedDiv = null;
|
Chris@76
|
409 }
|
Chris@76
|
410
|
Chris@76
|
411 // Show the box.
|
Chris@76
|
412 smc_AutoSuggest.prototype.autoSuggestShow = function()
|
Chris@76
|
413 {
|
Chris@76
|
414 if (this.oHideTimer)
|
Chris@76
|
415 {
|
Chris@76
|
416 clearTimeout(this.oHideTimer);
|
Chris@76
|
417 this.oHideTimer = false;
|
Chris@76
|
418 }
|
Chris@76
|
419
|
Chris@76
|
420 this.positionDiv();
|
Chris@76
|
421
|
Chris@76
|
422 this.oSuggestDivHandle.style.visibility = 'visible';
|
Chris@76
|
423 this.oSuggestDivHandle.style.display = '';
|
Chris@76
|
424 }
|
Chris@76
|
425
|
Chris@76
|
426 // Populate the actual div.
|
Chris@76
|
427 smc_AutoSuggest.prototype.populateDiv = function(aResults)
|
Chris@76
|
428 {
|
Chris@76
|
429 // Cannot have any children yet.
|
Chris@76
|
430 while (this.oSuggestDivHandle.childNodes.length > 0)
|
Chris@76
|
431 {
|
Chris@76
|
432 // Tidy up the events etc too.
|
Chris@76
|
433 this.oSuggestDivHandle.childNodes[0].onmouseover = null;
|
Chris@76
|
434 this.oSuggestDivHandle.childNodes[0].onmouseout = null;
|
Chris@76
|
435 this.oSuggestDivHandle.childNodes[0].onclick = null;
|
Chris@76
|
436
|
Chris@76
|
437 this.oSuggestDivHandle.removeChild(this.oSuggestDivHandle.childNodes[0]);
|
Chris@76
|
438 }
|
Chris@76
|
439
|
Chris@76
|
440 // Something to display?
|
Chris@76
|
441 if (typeof(aResults) == 'undefined')
|
Chris@76
|
442 {
|
Chris@76
|
443 this.aDisplayData = [];
|
Chris@76
|
444 return false;
|
Chris@76
|
445 }
|
Chris@76
|
446
|
Chris@76
|
447 var aNewDisplayData = [];
|
Chris@76
|
448 for (var i = 0; i < (aResults.length > this.iMaxDisplayQuantity ? this.iMaxDisplayQuantity : aResults.length); i++)
|
Chris@76
|
449 {
|
Chris@76
|
450 // Create the sub element
|
Chris@76
|
451 var oNewDivHandle = document.createElement('div');
|
Chris@76
|
452 oNewDivHandle.sItemId = aResults[i].sItemId;
|
Chris@76
|
453 oNewDivHandle.className = 'auto_suggest_item';
|
Chris@76
|
454 oNewDivHandle.innerHTML = aResults[i].sItemName;
|
Chris@76
|
455 //oNewDivHandle.style.width = this.oTextHandle.style.width;
|
Chris@76
|
456
|
Chris@76
|
457 this.oSuggestDivHandle.appendChild(oNewDivHandle);
|
Chris@76
|
458
|
Chris@76
|
459 // Attach some events to it so we can do stuff.
|
Chris@76
|
460 oNewDivHandle.instanceRef = this;
|
Chris@76
|
461 oNewDivHandle.onmouseover = function (oEvent)
|
Chris@76
|
462 {
|
Chris@76
|
463 this.instanceRef.itemMouseOver(this);
|
Chris@76
|
464 }
|
Chris@76
|
465 oNewDivHandle.onmouseout = function (oEvent)
|
Chris@76
|
466 {
|
Chris@76
|
467 this.instanceRef.itemMouseOut(this);
|
Chris@76
|
468 }
|
Chris@76
|
469 oNewDivHandle.onclick = function (oEvent)
|
Chris@76
|
470 {
|
Chris@76
|
471 this.instanceRef.itemClicked(this);
|
Chris@76
|
472 }
|
Chris@76
|
473
|
Chris@76
|
474
|
Chris@76
|
475 aNewDisplayData[i] = oNewDivHandle;
|
Chris@76
|
476 }
|
Chris@76
|
477
|
Chris@76
|
478 this.aDisplayData = aNewDisplayData;
|
Chris@76
|
479
|
Chris@76
|
480 return true;
|
Chris@76
|
481 }
|
Chris@76
|
482
|
Chris@76
|
483 // Refocus the element.
|
Chris@76
|
484 smc_AutoSuggest.prototype.itemMouseOver = function (oCurElement)
|
Chris@76
|
485 {
|
Chris@76
|
486 this.oSelectedDiv = oCurElement;
|
Chris@76
|
487 oCurElement.className = 'auto_suggest_item_hover';
|
Chris@76
|
488 }
|
Chris@76
|
489
|
Chris@76
|
490 // Onfocus the element
|
Chris@76
|
491 smc_AutoSuggest.prototype.itemMouseOut = function (oCurElement)
|
Chris@76
|
492 {
|
Chris@76
|
493 oCurElement.className = 'auto_suggest_item';
|
Chris@76
|
494 }
|
Chris@76
|
495
|
Chris@76
|
496 smc_AutoSuggest.prototype.onSuggestionReceived = function (oXMLDoc)
|
Chris@76
|
497 {
|
Chris@76
|
498 var sQuoteText = '';
|
Chris@76
|
499 var aItems = oXMLDoc.getElementsByTagName('item');
|
Chris@76
|
500 this.aCache = [];
|
Chris@76
|
501 for (var i = 0; i < aItems.length; i++)
|
Chris@76
|
502 {
|
Chris@76
|
503 this.aCache[i] = {
|
Chris@76
|
504 sItemId: aItems[i].getAttribute('id'),
|
Chris@76
|
505 sItemName: aItems[i].childNodes[0].nodeValue
|
Chris@76
|
506 };
|
Chris@76
|
507
|
Chris@76
|
508 // If we're doing auto add and we find the exact person, then add them!
|
Chris@76
|
509 if (this.bDoAutoAdd && this.sLastSearch == this.aCache[i].sItemName)
|
Chris@76
|
510 {
|
Chris@76
|
511 var oReturnValue = {
|
Chris@76
|
512 sItemId: this.aCache[i].sItemId,
|
Chris@76
|
513 sItemName: this.aCache[i].sItemName
|
Chris@76
|
514 };
|
Chris@76
|
515 this.aCache = [];
|
Chris@76
|
516 return this.addItemLink(oReturnValue.sItemId, oReturnValue.sItemName, true);
|
Chris@76
|
517 }
|
Chris@76
|
518 }
|
Chris@76
|
519
|
Chris@76
|
520 // Check we don't try to keep auto updating!
|
Chris@76
|
521 this.bDoAutoAdd = false;
|
Chris@76
|
522
|
Chris@76
|
523 // Populate the div.
|
Chris@76
|
524 this.populateDiv(this.aCache);
|
Chris@76
|
525
|
Chris@76
|
526 // Make sure we can see it - if we can.
|
Chris@76
|
527 if (aItems.length == 0)
|
Chris@76
|
528 this.autoSuggestHide();
|
Chris@76
|
529 else
|
Chris@76
|
530 this.autoSuggestShow();
|
Chris@76
|
531
|
Chris@76
|
532 return true;
|
Chris@76
|
533 }
|
Chris@76
|
534
|
Chris@76
|
535 // Get a new suggestion.
|
Chris@76
|
536 smc_AutoSuggest.prototype.autoSuggestUpdate = function ()
|
Chris@76
|
537 {
|
Chris@76
|
538 // If there's a callback then call it.
|
Chris@76
|
539 if ('onBeforeUpdate' in this.oCallback && typeof(this.oCallback.onBeforeUpdate) == 'string')
|
Chris@76
|
540 {
|
Chris@76
|
541 // If it returns false the item must not be added.
|
Chris@76
|
542 if (!eval(this.oCallback.onBeforeUpdate + '(' + this.opt.sSelf + ');'))
|
Chris@76
|
543 return false;
|
Chris@76
|
544 }
|
Chris@76
|
545
|
Chris@76
|
546 this.oRealTextHandle.value = this.oTextHandle.value;
|
Chris@76
|
547
|
Chris@76
|
548 if (isEmptyText(this.oTextHandle))
|
Chris@76
|
549 {
|
Chris@76
|
550 this.aCache = [];
|
Chris@76
|
551
|
Chris@76
|
552 this.populateDiv();
|
Chris@76
|
553
|
Chris@76
|
554 this.autoSuggestHide();
|
Chris@76
|
555
|
Chris@76
|
556 return true;
|
Chris@76
|
557 }
|
Chris@76
|
558
|
Chris@76
|
559 // Nothing changed?
|
Chris@76
|
560 if (this.oTextHandle.value == this.sLastDirtySearch)
|
Chris@76
|
561 return true;
|
Chris@76
|
562 this.sLastDirtySearch = this.oTextHandle.value;
|
Chris@76
|
563
|
Chris@76
|
564 // We're only actually interested in the last string.
|
Chris@76
|
565 var sSearchString = this.oTextHandle.value.replace(/^("[^"]+",[ ]*)+/, '').replace(/^([^,]+,[ ]*)+/, '');
|
Chris@76
|
566 if (sSearchString.substr(0, 1) == '"')
|
Chris@76
|
567 sSearchString = sSearchString.substr(1);
|
Chris@76
|
568
|
Chris@76
|
569 // Stop replication ASAP.
|
Chris@76
|
570 var sRealLastSearch = this.sLastSearch;
|
Chris@76
|
571 this.sLastSearch = sSearchString;
|
Chris@76
|
572
|
Chris@76
|
573 // Either nothing or we've completed a sentance.
|
Chris@76
|
574 if (sSearchString == '' || sSearchString.substr(sSearchString.length - 1) == '"')
|
Chris@76
|
575 {
|
Chris@76
|
576 this.populateDiv();
|
Chris@76
|
577 return true;
|
Chris@76
|
578 }
|
Chris@76
|
579
|
Chris@76
|
580 // Nothing?
|
Chris@76
|
581 if (sRealLastSearch == sSearchString)
|
Chris@76
|
582 return true;
|
Chris@76
|
583
|
Chris@76
|
584 // Too small?
|
Chris@76
|
585 else if (sSearchString.length < this.iMinimumSearchChars)
|
Chris@76
|
586 {
|
Chris@76
|
587 this.aCache = [];
|
Chris@76
|
588 this.autoSuggestHide();
|
Chris@76
|
589 return true;
|
Chris@76
|
590 }
|
Chris@76
|
591 else if (sSearchString.substr(0, sRealLastSearch.length) == sRealLastSearch)
|
Chris@76
|
592 {
|
Chris@76
|
593 // Instead of hitting the server again, just narrow down the results...
|
Chris@76
|
594 var aNewCache = [];
|
Chris@76
|
595 var j = 0;
|
Chris@76
|
596 var sLowercaseSearch = sSearchString.toLowerCase();
|
Chris@76
|
597 for (var k = 0; k < this.aCache.length; k++)
|
Chris@76
|
598 {
|
Chris@76
|
599 if (this.aCache[k].sItemName.substr(0, sSearchString.length).toLowerCase() == sLowercaseSearch)
|
Chris@76
|
600 aNewCache[j++] = this.aCache[k];
|
Chris@76
|
601 }
|
Chris@76
|
602
|
Chris@76
|
603 this.aCache = [];
|
Chris@76
|
604 if (aNewCache.length != 0)
|
Chris@76
|
605 {
|
Chris@76
|
606 this.aCache = aNewCache;
|
Chris@76
|
607 // Repopulate.
|
Chris@76
|
608 this.populateDiv(this.aCache);
|
Chris@76
|
609
|
Chris@76
|
610 // Check it can be seen.
|
Chris@76
|
611 this.autoSuggestShow();
|
Chris@76
|
612
|
Chris@76
|
613 return true;
|
Chris@76
|
614 }
|
Chris@76
|
615 }
|
Chris@76
|
616
|
Chris@76
|
617 // In progress means destroy!
|
Chris@76
|
618 if (typeof(this.oXmlRequestHandle) == 'object' && this.oXmlRequestHandle != null)
|
Chris@76
|
619 this.oXmlRequestHandle.abort();
|
Chris@76
|
620
|
Chris@76
|
621 // Clean the text handle.
|
Chris@76
|
622 sSearchString = sSearchString.php_to8bit().php_urlencode();
|
Chris@76
|
623
|
Chris@76
|
624 // Get the document.
|
Chris@76
|
625 this.tmpMethod = getXMLDocument;
|
Chris@76
|
626 this.oXmlRequestHandle = this.tmpMethod(this.sRetrieveURL.replace(/%scripturl%/g, smf_prepareScriptUrl(smf_scripturl)).replace(/%suggest_type%/g, this.opt.sSearchType).replace(/%search%/g, sSearchString).replace(/%sessionVar%/g, this.opt.sSessionVar).replace(/%sessionID%/g, this.opt.sSessionId).replace(/%time%/g, new Date().getTime()), this.onSuggestionReceived);
|
Chris@76
|
627 delete this.tmpMethod;
|
Chris@76
|
628
|
Chris@76
|
629 return true;
|
Chris@76
|
630 } |