src/doc/api: add links to renderer code
[nit.git] / share / nitdoc / js / plugins / quicksearch.js
1 /* This file is part of NIT ( http://www.nitlanguage.org ).
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14
15 Documentation generator for the nit language.
16 Generate API documentation in HTML format from nit source code.
17 */
18
19 /*
20 * Nitdoc QuickSearch widget
21 */
22 $.widget("nitdoc.quicksearch", {
23
24 options: {
25 list: {}, // List of raw results generated by nitdoc tool
26 fieldAttrs: {
27 autocomplete: "off",
28 },
29 tableID: "nitdoc-qs-table",
30 tableCSS: {
31 "position": "absolute"
32 },
33 rowClass: "nitdoc-qs-row",
34 rowCatClass: "nitdoc-qs-cat",
35 rowSubClass: "nitdoc-qs-sub",
36 rowActiveClass: "nitdoc-qs-active",
37 rowOverflowClass: "nitdoc-qs-overflow",
38 rowOverflowActive: "nitdoc-qs-overflow-active",
39 rowNoResultClass: "nitdoc-qs-noresult",
40 overflowUpHtml: "▲",
41 overflowDownHtml: "▼",
42 noresultText: "Sorry, there is no match, best results are:",
43 infoClass: "nitdoc-qs-info",
44 gotoPage: "search.html",
45 maxSize: 10
46 },
47
48 _create: function() {
49 // set widget options
50 this.element.attr(this.options.fieldAttrs);
51 // event dispatch
52 this._on(this.element, {
53 "keydown": this._doKeyDown,
54 "keyup": this._doKeyUp,
55 "input": this._doInput
56 });
57 // add result table element once
58 this._table = $("<table/>")
59 .attr("id", this.options.tableID)
60 .css(this.options.tableCSS)
61 .css("min-width", this.element.outerWidth());
62 $("body").append(this._table);
63 // make table disappear when a click occurs outside
64 $(document).click($.proxy(this.closeTable, this));
65 },
66
67 /* events */
68
69 _doKeyDown: function(event) {
70 switch(event.keyCode) {
71 case 38: // Up
72 this._selectPrev();
73 return false;
74 case 40: // Down
75 this._selectNext();
76 return false;
77 default:
78 return true;
79 }
80 },
81
82 _doKeyUp: function(event) {
83 switch(event.keyCode) {
84 case 38: // Up
85 case 40: // Down
86 break;
87 case 13: // Enter
88 this._loadResult();
89 return false;
90 case 27: // Escape
91 this.element.blur();
92 this.closeTable();
93 return true;
94 default: // Other keys
95 return true;
96 }
97 },
98
99 _doInput: function(event) {
100 Utils.delayEvent($.proxy(this.search, this));
101 },
102
103 /* Result lookup */
104
105 _getResults: function(query) {
106 var results = {};
107 results.matches = [];
108 for(var entry in this.options.list) {
109 if(!entry.startsWith(query, true)) {
110 continue;
111 }
112 var cat = {
113 name: entry,
114 entries: this.options.list[entry]
115 };
116 results.matches[results.matches.length] = cat;
117
118 if(entry == query) {
119 cat.rank = 3;
120 } else if(entry.toUpperCase() == query.toUpperCase()) {
121 cat.rank = 2;
122 } else {
123 cat.rank = 1 + query.dice(entry);
124 }
125 }
126 results.matches.sort(this._rankSorter);
127 results.partials = new Array();
128 if(results.matches.length == 0) {
129 for(var entry in this.options.list) {
130 var cat = {
131 name: entry,
132 entries: this.options.list[entry]
133 }
134 cat.rank = query.dice(entry);
135 if(cat.rank > 0) {
136 results.partials[results.partials.length] = cat;
137 }
138 }
139 results.partials.sort(this._rankSorter);
140 }
141 return results;
142 },
143
144 _rankSorter: function(a, b){
145 if(a.rank < b.rank) {
146 return 1;
147 } else if(a.rank > b.rank) {
148 return -1;
149 }
150 return 0;
151 },
152
153 /* Results table */
154
155 search: function() {
156 var query = this.element.val();
157 if(query) {
158 var results = this._getResults(query);
159 this.openTable(query, results);
160 }
161 },
162
163 openTable: function(query, results) {
164 this._table.empty();
165 this._rows = [];
166 this._index = -1;
167
168 var resultSet = results.matches;
169 if(resultSet.length == 0) {
170 resultSet = results.partials
171 }
172
173 for(var i in resultSet) {
174 var cat = resultSet[i];
175 var result = cat.entries[0];
176 this.addRow(cat.name, result.txt, result.url, this.options.rowCatClass)
177 for(var j = 1; j < cat.entries.length; j++) {
178 var result = cat.entries[j];
179 this.addRow(cat.name, result.txt, result.url, this.options.rowSubClass)
180 }
181 }
182
183 if(this._rows.length >= this.options.maxSize) {
184 this.addOverflowUp();
185 this.addOverflowDown();
186 }
187 if(results.matches.length == 0) {
188 this.addNoResultRow();
189 }
190
191 if(resultSet.length > 0) {
192 this._setIndex(0);
193 }
194 this._table.show();
195 this._autosizeTable();
196 },
197
198 closeTable: function(target) {
199 if(target != this.element && target != this._table) {
200 this._table.hide();
201 }
202 },
203
204 addRow: function(name, txt, url, cls) {
205 var row = $("<tr/>")
206 .addClass(this.options.rowClass)
207 .data("searchDetails", {name: name, url: url})
208 .data("index", this._rows.length)
209 .append(
210 $("<td/>")
211 .html(name)
212 .addClass(cls)
213 )
214 .append(
215 $("<td/>")
216 .html(txt + "&nbsp;&raquo;")
217 .addClass(this.options.infoClass)
218 )
219 .mouseover($.proxy(this._mouseOverRow, this))
220 .click($.proxy(this._clickRow, this))
221 this._rows.push(row);
222 if(this._rows.length >= this.options.maxSize) {
223 row.hide();
224 }
225 this._table.append(row);
226 },
227
228 addOverflowUp: function() {
229 this._table.prepend(
230 $("<tr/>")
231 .addClass(this.options.rowOverflowClass)
232 .append(
233 $("<td/>")
234 .attr("colspan", 2)
235 .html(this.options.overflowUpHtml)
236 )
237 .click($.proxy(this._clickPrev, this))
238 );
239 },
240
241 addOverflowDown: function() {
242 this._table.append(
243 $("<tr/>")
244 .addClass(this.options.rowOverflowClass)
245 .addClass(this.options.rowOverflowActive)
246 .append(
247 $("<td/>")
248 .attr("colspan", 2)
249 .html(this.options.overflowDownHtml)
250 )
251 .click($.proxy(this._clickNext, this))
252 );
253 },
254
255 addNoResultRow: function() {
256 this._table.prepend(
257 $("<tr/>")
258 .addClass(this.options.rowNoResultClass)
259 .append(
260 $("<td/>")
261 .attr("colspan", "2")
262 .text(this.options.noresultText)
263 )
264 );
265 },
266
267 _autosizeTable: function() {
268 this._table.position({
269 my: "right top",
270 at: "right bottom",
271 of: this.element
272 });
273 },
274
275 _hasIndex: function(index) {
276 return index >= 0 && index < this._rows.length;
277 },
278
279 _hasPrev: function(index) {
280 return index - 1 >= 0;
281 },
282
283 _hasNext: function(index) {
284 return index + 1 < this._rows.length;
285 },
286
287 _setIndex: function(index) {
288 if(this._hasIndex(this._index)) {
289 this._rows[this._index].removeClass(this.options.rowActiveClass);
290 }
291 this._index = index;
292 if(this._hasIndex(this._index)) {
293 this._rows[this._index].addClass(this.options.rowActiveClass);
294 }
295 },
296
297 _selectPrev: function() {
298 if(this._hasPrev(this._index)) {
299 this._setIndex(this._index - 1);
300 if(!this._rows[this._index].is(":visible")) {
301 this._table.find("tr." + this.options.rowClass + ":visible").last().hide();
302 this._table.find("tr." + this.options.rowOverflowClass).addClass(this.options.rowOverflowActive);
303 this._rows[this._index].show();
304 if(!this._hasPrev(this._index)) {
305 this._table.find("tr." + this.options.rowOverflowClass).removeClass(this.options.rowOverflowActive);
306 }
307 this._autosizeTable();
308 }
309 }
310 },
311
312 _selectNext: function() {
313 if(this._hasNext(this._index)) {
314 this._setIndex(this._index + 1);
315 if(!this._rows[this._index].is(":visible")) {
316 this._table.find("tr." + this.options.rowClass + ":visible").first().hide();
317 this._table.find("tr." + this.options.rowOverflowClass).addClass(this.options.rowOverflowActive);
318 this._rows[this._index].show();
319 if(!this._hasNext(this._index)) {
320 this._table.find("tr." + this.options.rowOverflowClass).removeClass(this.options.rowOverflowActive);
321 }
322 this._autosizeTable();
323 }
324 }
325 },
326
327 // Load selected search result page
328 _loadResult: function() {
329 if(this._index > -1) {
330 window.location = this._rows[this._index].data("searchDetails").url;
331 return;
332 }
333 if(this.element.val().length == 0) { return; }
334
335 window.location = this.options.gotoPage + "#q=" + this.element.val();
336 if(window.location.href.indexOf(this.options.gotoPage) > -1) {
337 location.reload();
338 }
339 },
340
341 /* table events */
342
343 _clickNext: function(event) {
344 event.stopPropagation();
345 this._selectNext();
346 },
347
348 _clickPrev: function(event) {
349 event.stopPropagation();
350 this._selectPrev();
351 },
352
353 _clickRow: function(event) {
354 window.location = $(event.currentTarget).data("searchDetails")["url"];
355 },
356
357 _mouseOverRow: function(event) {
358 this._setIndex($(event.currentTarget).data("index"));
359 }
360 });
361
362 var searchField = $("<input/>")
363 .addClass("form-control input-sm")
364 .attr({
365 id: "nitdoc-qs-field",
366 type: "text",
367 placeholder: "Search..."
368 })
369
370 $("#topmenu-collapse").append(
371 $("<div>")
372 .addClass("navbar-form navbar-right")
373 .append(
374 $("<div>")
375 .addClass("form-group")
376 .append(searchField)
377 )
378 );
379
380 searchField.quicksearch({
381 list: this.nitdocQuickSearchRawList
382 });