nitdoc: Add quick search results preview.
[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 break;
191 }
192 })
193 .focusout(function() {
194 if($(this).val() == "") {
195 $(this).addClass("notUsed");
196 $(this).val("quick search...");
197 }
198 })
199 .focusin(function() {
200 if($(this).val() == "quick search...") {
201 $(this).removeClass("notUsed");
202 $(this).val("");
203 }
204 })
205 )
206 .submit( function() {
207 return false;
208 })
209 )
210 );
211
212 /*
213 * Anchors jumps
214 */
215 $("a[href^='#']").click( function() {
216 var a = $(this).attr("href").replace(/#/, "");
217 highlightBlock(a);
218 });
219
220 // Insert filter field
221 $("article.filterable h2, nav.filterable h3")
222 .after(
223 $(document.createElement("div"))
224 .addClass("filter")
225 .append(
226 $(document.createElement("input"))
227 .attr({
228 type: "text",
229 value: "filter..."
230 })
231 .addClass("notUsed")
232 .keyup(function() {
233 $(this).parent().parent().find("ul li:not(:icontains('" + $(this).val() + "'))").addClass("hide");
234 $(this).parent().parent().find("ul li:icontains('" + $(this).val() + "')").removeClass("hide");
235 })
236 .focusout(function() {
237 if($(this).val() == "") {
238 $(this).addClass("notUsed");
239 $(this).val("filter...");
240 }
241 })
242 .focusin(function() {
243 if($(this).val() == "filter...") {
244 $(this).removeClass("notUsed");
245 $(this).val("");
246 }
247 })
248 )
249 );
250
251 // Filter toggle between H I R in nav porperties list
252 $("nav.properties.filterable .filter")
253 .append(
254 $(document.createElement("a"))
255 .html("H")
256 .attr({
257 title: "hide inherited properties"
258 })
259 .click( function() {
260 if($(this).hasClass("hidden")) {
261 $(this).parent().parent().find("li.inherit").show();
262 } else {
263 $(this).parent().parent().find("li.inherit").hide();
264 }
265
266 $(this).toggleClass("hidden");
267 })
268 )
269 .append(
270 $(document.createElement("a"))
271 .html("R")
272 .attr({
273 title: "hide redefined properties"
274 })
275 .click( function() {
276 if($(this).hasClass("hidden")) {
277 $(this).parent().parent().find("li.redef").show();
278 } else {
279 $(this).parent().parent().find("li.redef").hide();
280 }
281
282 $(this).toggleClass("hidden");
283 })
284 )
285 .append(
286 $(document.createElement("a"))
287 .html("I")
288 .attr({
289 title: "hide introduced properties"
290 })
291 .click( function() {
292 if($(this).hasClass("hidden")) {
293 $(this).parent().parent().find("li.intro").show();
294 } else {
295 $(this).parent().parent().find("li.intro").hide();
296 }
297
298 $(this).toggleClass("hidden");
299 })
300 );
301
302 // Filter toggle between I R in
303 $("article.properties.filterable .filter, article.classes.filterable .filter")
304 .append(
305 $(document.createElement("a"))
306 .html("I")
307 .attr({
308 title: "hide introduced properties"
309 })
310 .click( function() {
311 if($(this).hasClass("hidden")) {
312 $(this).parent().parent().find("li.intro").show();
313 } else {
314 $(this).parent().parent().find("li.intro").hide();
315 }
316
317 $(this).toggleClass("hidden");
318 })
319 )
320 .append(
321 $(document.createElement("a"))
322 .html("R")
323 .attr({
324 title: "hide redefined properties"
325 })
326 .click( function() {
327 if($(this).hasClass("hidden")) {
328 $(this).parent().parent().find("li.redef").show();
329 } else {
330 $(this).parent().parent().find("li.redef").hide();
331 }
332
333 $(this).toggleClass("hidden");
334 })
335 );
336
337 //Preload filter fields with query string
338 preloadFilters();
339 });
340
341 /* Parse current URL and return anchor name */
342 function currentAnchor() {
343 var index = document.location.hash.indexOf("#");
344 if (index >= 0) {
345 return document.location.hash.substring(index + 1);
346 }
347 return null;
348 }
349
350 /* Prealod filters field using search query */
351 function preloadFilters() {
352 // Parse URL and get query string
353 var search = currentAnchor();
354
355 if(search == null || search.indexOf("q=") == -1)
356 return;
357
358 search = search.substring(2, search.length);
359
360 if(search == "" || search == "undefined")
361 return;
362
363 $(":text").val(search);
364 $(".filter :text")
365 .removeClass("notUsed")
366 .trigger("keyup");
367
368 }
369
370 /* Hightlight the spoted block */
371 function highlightBlock(a) {
372 if(a == undefined) {
373 return;
374 }
375
376 $(".highlighted").removeClass("highlighted");
377
378 var target = $("#" + a);
379
380 if(target.is("article")) {
381 target.parent().addClass("highlighted");
382 }
383
384 target.addClass("highlighted");
385 target.show();
386 }