nitdoc: Preselect first result of quicksearch list and easy close on click
[nit.git] / share / nitdoc / scripts / js-facilities.js
1 /*
2 * JQuery Case Insensitive :icontains selector
3 */
4 $.expr[':'].icontains = function(obj, index, meta, stack){
5 return (obj.textContent.replace(/\[[0-9]+\]/g, "") || obj.innerText.replace(/\[[0-9]+\]/g, "") || jQuery(obj).text().replace(/\[[0-9]+\]/g, "") || '').toLowerCase().indexOf(meta[3].toLowerCase()) >= 0;
6 };
7
8 /*
9 * Quick Search global vars
10 */
11
12 // Current search results preview table
13 var currentTable = null;
14
15 //Hightlighted index in search result preview table
16 var currentIndex = -1;
17
18
19 /*
20 * Add folding and filtering facilities to class description page.
21 */
22 $(document).ready(function() {
23
24 /*
25 * Highlight the spoted element
26 */
27 highlightBlock(currentAnchor());
28
29 /*
30 * Nav block folding
31 */
32
33 // Menu nav folding
34 $(".menu nav h3")
35 .prepend(
36 $(document.createElement("a"))
37 .html("-")
38 .addClass("fold")
39 )
40 .css("cursor", "pointer")
41 .click( function() {
42 if($(this).find("a.fold").html() == "+") {
43 $(this).find("a.fold").html("-");
44 } else {
45 $(this).find("a.fold").html("+");
46 }
47 $(this).nextAll().toggle();
48 })
49
50 // Insert search field
51 $("nav.main ul")
52 .append(
53 $(document.createElement("li"))
54 .append(
55 $(document.createElement("form"))
56 .append(
57 $(document.createElement("input"))
58 .attr({
59 id: "search",
60 type: "text",
61 autocomplete: "off",
62 value: "quick search..."
63 })
64 .addClass("notUsed")
65
66 // Key management
67 .keyup(function(e) {
68 switch(e.keyCode) {
69
70 // Select previous result on "Up"
71 case 38:
72 // If already on first result, focus search input
73 if(currentIndex == 0) {
74 $("#search").val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);
75 $("#search").focus();
76 // Else select previous result
77 } else if(currentIndex > 0) {
78 $(currentTable.find("tr")[currentIndex]).removeClass("activeSearchResult");
79 currentIndex--;
80 $(currentTable.find("tr")[currentIndex]).addClass("activeSearchResult");
81 $("#search").val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);
82 $("#search").focus();
83 }
84 break;
85
86 // Select next result on "Down"
87 case 40:
88 if(currentIndex < currentTable.find("tr").length - 1) {
89 $(currentTable.find("tr")[currentIndex]).removeClass("activeSearchResult");
90 currentIndex++;
91 $(currentTable.find("tr")[currentIndex]).addClass("activeSearchResult");
92 $("#search").val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);
93 $("#search").focus();
94 }
95 break;
96 // Go to url on "Enter"
97 case 13:
98 if(currentIndex > -1) {
99 window.location = $(currentTable.find("tr")[currentIndex]).data("searchDetails").url;
100 return false;
101 }
102 if($("#search").val().length == 0)
103 return false
104
105 window.location = "full-index.html#q=" + $("#search").val();
106 if(window.location.href.indexOf("full-index.html") > -1) {
107 location.reload();
108 }
109 return false;
110 break;
111
112 // Hide results preview on "Escape"
113 case 27:
114 $(this).blur();
115 if(currentTable != null) {
116 currentTable.remove();
117 currentTable = null;
118 }
119 break;
120
121 default:
122 if($("#search").val().length == 0) {
123 return false;
124 }
125
126 // Remove previous table
127 if(currentTable != null) {
128 currentTable.remove();
129 }
130
131 // Build results table
132 currentIndex = -1;
133 currentTable = $(document.createElement("table"));
134
135 // Escape regexp related characters in query
136 var query = $("#search").val();
137 query = query.replace(/\[/gi, "\\[");
138 query = query.replace(/\|/gi, "\\|");
139 query = query.replace(/\*/gi, "\\*");
140 query = query.replace(/\+/gi, "\\+");
141 query = query.replace(/\\/gi, "\\\\");
142 query = query.replace(/\?/gi, "\\?");
143 query = query.replace(/\(/gi, "\\(");
144 query = query.replace(/\)/gi, "\\)");
145
146 var index = 0;
147 var regexp = new RegExp("^" + query, "i");
148 for(var entry in entries) {
149 if(index > 10) {
150 break;
151 }
152 var result = entry.match(regexp);
153 if(result != null && result.toString().toUpperCase() == $("#search").val().toUpperCase()) {
154 for(var i = 0; i < entries[entry].length; i++) {
155 if(index > 10) {
156 break;
157 }
158 currentTable.append(
159 $(document.createElement("tr"))
160 .data("searchDetails", {name: entry, url: entries[entry][i]["url"]})
161 .data("index", index)
162 .append($(document.createElement("td")).html(entry))
163 .append(
164 $(document.createElement("td"))
165 .addClass("entryInfo")
166 .html(entries[entry][i]["txt"] + "&nbsp;&raquo;"))
167 .mouseover( function() {
168 $(currentTable.find("tr")[currentIndex]).removeClass("activeSearchResult");
169 $(this).addClass("activeSearchResult");
170 currentIndex = $(this).data("index");
171 })
172 .mouseout( function() {
173 $(this).removeClass("activeSearchResult");
174 })
175 .click( function() {
176 window.location = $(this).data("searchDetails")["url"];
177 })
178 );
179 index++;
180 }
181 }
182 }
183
184 // Initialize table properties
185 currentTable.attr("id", "searchTable");
186 currentTable.css("position", "absolute");
187 currentTable.width($("#search").outerWidth());
188 $("header").append(currentTable);
189 currentTable.offset({left: $("#search").offset().left + ($("#search").outerWidth() - currentTable.outerWidth()), top: $("#search").offset().top + $("#search").outerHeight()});
190
191 // Preselect first entry
192 if(currentTable.find("tr").length > 0) {
193 currentIndex = 0;
194 $(currentTable.find("tr")[currentIndex]).addClass("activeSearchResult");
195 $("#search").focus();
196 }
197 break;
198 }
199 })
200 .focusout(function() {
201 if($(this).val() == "") {
202 $(this).addClass("notUsed");
203 $(this).val("quick search...");
204 }
205 })
206 .focusin(function() {
207 if($(this).val() == "quick search...") {
208 $(this).removeClass("notUsed");
209 $(this).val("");
210 }
211 })
212 )
213 .submit( function() {
214 return false;
215 })
216 )
217 );
218
219 // Close quicksearch list on click
220 $(document).click(function(e) {
221 if(e.target != $("#search")[0] && e.target != $("#searchTable")[0]) {
222 if(currentTable != null) {
223 currentTable.remove();
224 currentTable = null;
225 }
226 }
227 });
228
229 /*
230 * Anchors jumps
231 */
232 $("a[href^='#']").click( function() {
233 var a = $(this).attr("href").replace(/#/, "");
234 highlightBlock(a);
235 });
236
237 // Insert filter field
238 $("article.filterable h2, nav.filterable h3")
239 .after(
240 $(document.createElement("div"))
241 .addClass("filter")
242 .append(
243 $(document.createElement("input"))
244 .attr({
245 type: "text",
246 value: "filter..."
247 })
248 .addClass("notUsed")
249 .keyup(function() {
250 $(this).parent().parent().find("ul li:not(:icontains('" + $(this).val() + "'))").addClass("hide");
251 $(this).parent().parent().find("ul li:icontains('" + $(this).val() + "')").removeClass("hide");
252 })
253 .focusout(function() {
254 if($(this).val() == "") {
255 $(this).addClass("notUsed");
256 $(this).val("filter...");
257 }
258 })
259 .focusin(function() {
260 if($(this).val() == "filter...") {
261 $(this).removeClass("notUsed");
262 $(this).val("");
263 }
264 })
265 )
266 );
267
268 // Filter toggle between H I R in nav porperties list
269 $("nav.properties.filterable .filter")
270 .append(
271 $(document.createElement("a"))
272 .html("H")
273 .attr({
274 title: "hide inherited properties"
275 })
276 .click( function() {
277 if($(this).hasClass("hidden")) {
278 $(this).parent().parent().find("li.inherit").show();
279 } else {
280 $(this).parent().parent().find("li.inherit").hide();
281 }
282
283 $(this).toggleClass("hidden");
284 })
285 )
286 .append(
287 $(document.createElement("a"))
288 .html("R")
289 .attr({
290 title: "hide redefined properties"
291 })
292 .click( function() {
293 if($(this).hasClass("hidden")) {
294 $(this).parent().parent().find("li.redef").show();
295 } else {
296 $(this).parent().parent().find("li.redef").hide();
297 }
298
299 $(this).toggleClass("hidden");
300 })
301 )
302 .append(
303 $(document.createElement("a"))
304 .html("I")
305 .attr({
306 title: "hide introduced properties"
307 })
308 .click( function() {
309 if($(this).hasClass("hidden")) {
310 $(this).parent().parent().find("li.intro").show();
311 } else {
312 $(this).parent().parent().find("li.intro").hide();
313 }
314
315 $(this).toggleClass("hidden");
316 })
317 );
318
319 // Filter toggle between I R in
320 $("article.properties.filterable .filter, article.classes.filterable .filter")
321 .append(
322 $(document.createElement("a"))
323 .html("I")
324 .attr({
325 title: "hide introduced properties"
326 })
327 .click( function() {
328 if($(this).hasClass("hidden")) {
329 $(this).parent().parent().find("li.intro").show();
330 } else {
331 $(this).parent().parent().find("li.intro").hide();
332 }
333
334 $(this).toggleClass("hidden");
335 })
336 )
337 .append(
338 $(document.createElement("a"))
339 .html("R")
340 .attr({
341 title: "hide redefined properties"
342 })
343 .click( function() {
344 if($(this).hasClass("hidden")) {
345 $(this).parent().parent().find("li.redef").show();
346 } else {
347 $(this).parent().parent().find("li.redef").hide();
348 }
349
350 $(this).toggleClass("hidden");
351 })
352 );
353
354 //Preload filter fields with query string
355 preloadFilters();
356 });
357
358 /* Parse current URL and return anchor name */
359 function currentAnchor() {
360 var index = document.location.hash.indexOf("#");
361 if (index >= 0) {
362 return document.location.hash.substring(index + 1);
363 }
364 return null;
365 }
366
367 /* Prealod filters field using search query */
368 function preloadFilters() {
369 // Parse URL and get query string
370 var search = currentAnchor();
371
372 if(search == null || search.indexOf("q=") == -1)
373 return;
374
375 search = search.substring(2, search.length);
376
377 if(search == "" || search == "undefined")
378 return;
379
380 $(":text").val(search);
381 $(".filter :text")
382 .removeClass("notUsed")
383 .trigger("keyup");
384
385 }
386
387 /* Hightlight the spoted block */
388 function highlightBlock(a) {
389 if(a == undefined) {
390 return;
391 }
392
393 $(".highlighted").removeClass("highlighted");
394
395 var target = $("#" + a);
396
397 if(target.is("article")) {
398 target.parent().addClass("highlighted");
399 }
400
401 target.addClass("highlighted");
402 target.show();
403 }