nitdoc: move css files to share/css/ dir
[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 define([
23 "jquery",
24 "jQueryUI",
25 "utils",
26 "quicksearchList",
27 ], function($, ui, utils) {
28 $.widget("nitdoc.quicksearch", {
29
30 options: {
31 list: {}, // List of raw results generated by nitdoc tool
32 fieldNotUsedClass: "nitdoc-qs-field-notused",
33 fieldAttrs: {
34 autocomplete: "off",
35 value: "quick search..."
36 },
37 tableID: "nitdoc-qs-table",
38 tableCSS: {
39 "position": "absolute"
40 },
41 rowClass: "nitdoc-qs-row",
42 rowCatClass: "nitdoc-qs-cat",
43 rowSubClass: "nitdoc-qs-sub",
44 rowActiveClass: "nitdoc-qs-active",
45 rowOverflowClass: "nitdoc-qs-overflow",
46 rowOverflowActive: "nitdoc-qs-overflow-active",
47 rowNoResultClass: "nitdoc-qs-noresult",
48 overflowUpHtml: "▲",
49 overflowDownHtml: "▼",
50 noresultText: "Sorry, there is no match, best results are:",
51 infoClass: "nitdoc-qs-info",
52 gotoPage: "search.html",
53 maxSize: 10
54 },
55
56 _create: function() {
57 this.element
58 .attr(this.options.fieldAttrs)
59 .addClass(this.options.fieldNotUsedClass)
60 .keydown($.proxy(this._doKeyDown, this))
61 .keyup($.proxy(this._doKeyUp, this))
62 .focusout($.proxy(this._doFocusOut, this))
63 .focusin($.proxy(this._doFocusIn, this));
64
65 this._table = $("<table/>")
66 .attr("id", this.options.tableID)
67 .css(this.options.tableCSS)
68 .css("min-width", this.element.outerWidth());
69 $("body").append(this._table);
70
71 $(document).click($.proxy(this.closeTable, this));
72 },
73
74 /* events */
75
76 _doKeyDown: function(event) {
77 switch(event.keyCode) {
78 case 38: // Up
79 this._selectPrev();
80 return false;
81 case 40: // Down
82 this._selectNext();
83 return false;
84 default:
85 return true;
86 }
87 },
88
89 _doKeyUp: function(event) {
90 switch(event.keyCode) {
91 case 38: // Up
92 case 40: // Down
93 break;
94 case 13: // Enter
95 this._loadResult();
96 break;
97 case 27: // Escape
98 this.element.blur();
99 this.closeTable();
100 break;
101 default: // Other keys
102 utils.delayEvent($.proxy(this.search, this));
103 break;
104 }
105 },
106
107 _doFocusOut: function() {
108 if(this.element.val() == "") {
109 this.element.addClass(this.options.fieldNotUsedClass);
110 this.element.val(this.options.fieldAttrs.value);
111 }
112 },
113
114 _doFocusIn: function() {
115 if(this.element.val() == this.options.fieldAttrs.value) {
116 this.element.removeClass(this.options.fieldNotUsedClass);
117 this.element.val("");
118 }
119 },
120
121 /* Result lookup */
122
123 _getResults: function(query) {
124 var results = {};
125 results.matches = [];
126 for(var entry in this.options.list) {
127 if(!entry.startsWith(query, true)) {
128 continue;
129 }
130 var cat = {
131 name: entry,
132 entries: this.options.list[entry]
133 };
134 results.matches[results.matches.length] = cat;
135
136 if(entry == query) {
137 cat.rank = 3;
138 } else if(entry.toUpperCase() == query.toUpperCase()) {
139 cat.rank = 2;
140 } else {
141 cat.rank = 1 + query.dice(entry);
142 }
143 }
144 results.matches.sort(this._rankSorter);
145 results.partials = new Array();
146 if(results.matches.length == 0) {
147 for(var entry in this.options.list) {
148 var cat = {
149 name: entry,
150 entries: this.options.list[entry]
151 }
152 cat.rank = query.dice(entry);
153 if(cat.rank > 0) {
154 results.partials[results.partials.length] = cat;
155 }
156 }
157 results.partials.sort(this._rankSorter);
158 }
159 return results;
160 },
161
162 _rankSorter: function(a, b){
163 if(a.rank < b.rank) {
164 return 1;
165 } else if(a.rank > b.rank) {
166 return -1;
167 }
168 return 0;
169 },
170
171 /* Results table */
172
173 search: function() {
174 var query = this.element.val();
175 if(query) {
176 var results = this._getResults(query);
177 this.openTable(query, results);
178 }
179 },
180
181 openTable: function(query, results) {
182 this._table.empty();
183 this._rows = [];
184 this._index = -1;
185
186 var resultSet = results.matches;
187 if(resultSet.length == 0) {
188 resultSet = results.partials
189 }
190
191 for(var i in resultSet) {
192 var cat = resultSet[i];
193 var result = cat.entries[0];
194 this.addRow(cat.name, result.txt, result.url, this.options.rowCatClass)
195 for(var j = 1; j < cat.entries.length; j++) {
196 var result = cat.entries[j];
197 this.addRow(cat.name, result.txt, result.url, this.options.rowSubClass)
198 }
199 }
200
201 if(this._rows.length >= this.options.maxSize) {
202 this.addOverflowUp();
203 this.addOverflowDown();
204 }
205 if(results.matches.length == 0) {
206 this.addNoResultRow();
207 }
208
209 if(resultSet.length > 0) {
210 this._setIndex(0);
211 }
212 this._table.show();
213 this._autosizeTable();
214 },
215
216 closeTable: function(target) {
217 if(target != this.element && target != this._table) {
218 this._table.hide();
219 }
220 },
221
222 addRow: function(name, txt, url, cls) {
223 var row = $("<tr/>")
224 .addClass(this.options.rowClass)
225 .data("searchDetails", {name: name, url: url})
226 .data("index", this._rows.length)
227 .append(
228 $("<td/>")
229 .html(name)
230 .addClass(cls)
231 )
232 .append(
233 $("<td/>")
234 .html(txt + "&nbsp;&raquo;")
235 .addClass(this.options.infoClass)
236 )
237 .mouseover($.proxy(this._mouseOverRow, this))
238 .click($.proxy(this._clickRow, this))
239 this._rows.push(row);
240 if(this._rows.length >= this.options.maxSize) {
241 row.hide();
242 }
243 this._table.append(row);
244 },
245
246 addOverflowUp: function() {
247 this._table.prepend(
248 $("<tr/>")
249 .addClass(this.options.rowOverflowClass)
250 .append(
251 $("<td/>")
252 .attr("colspan", 2)
253 .html(this.options.overflowUpHtml)
254 )
255 .click($.proxy(this._clickPrev, this))
256 );
257 },
258
259 addOverflowDown: function() {
260 this._table.append(
261 $("<tr/>")
262 .addClass(this.options.rowOverflowClass)
263 .addClass(this.options.rowOverflowActive)
264 .append(
265 $("<td/>")
266 .attr("colspan", 2)
267 .html(this.options.overflowDownHtml)
268 )
269 .click($.proxy(this._clickNext, this))
270 );
271 },
272
273 addNoResultRow: function() {
274 this._table.prepend(
275 $("<tr/>")
276 .addClass(this.options.rowNoResultClass)
277 .append(
278 $("<td/>")
279 .attr("colspan", "2")
280 .text(this.options.noresultText)
281 )
282 );
283 },
284
285 _autosizeTable: function() {
286 this._table.position({
287 my: "right top",
288 at: "right bottom",
289 of: this.element
290 });
291 },
292
293 _hasIndex: function(index) {
294 return index >= 0 && index < this._rows.length;
295 },
296
297 _hasPrev: function(index) {
298 return index - 1 >= 0;
299 },
300
301 _hasNext: function(index) {
302 return index + 1 < this._rows.length;
303 },
304
305 _setIndex: function(index) {
306 if(this._hasIndex(this._index)) {
307 this._rows[this._index].removeClass(this.options.rowActiveClass);
308 }
309 this._index = index;
310 if(this._hasIndex(this._index)) {
311 this._rows[this._index].addClass(this.options.rowActiveClass);
312 }
313 },
314
315 _selectPrev: function() {
316 if(this._hasPrev(this._index)) {
317 this._setIndex(this._index - 1);
318 if(!this._rows[this._index].is(":visible")) {
319 this._table.find("tr." + this.options.rowClass + ":visible").last().hide();
320 this._table.find("tr." + this.options.rowOverflowClass).addClass(this.options.rowOverflowActive);
321 this._rows[this._index].show();
322 if(!this._hasPrev(this._index)) {
323 this._table.find("tr." + this.options.rowOverflowClass).removeClass(this.options.rowOverflowActive);
324 }
325 this._autosizeTable();
326 }
327 }
328 },
329
330 _selectNext: function() {
331 if(this._hasNext(this._index)) {
332 this._setIndex(this._index + 1);
333 if(!this._rows[this._index].is(":visible")) {
334 this._table.find("tr." + this.options.rowClass + ":visible").first().hide();
335 this._table.find("tr." + this.options.rowOverflowClass).addClass(this.options.rowOverflowActive);
336 this._rows[this._index].show();
337 if(!this._hasNext(this._index)) {
338 this._table.find("tr." + this.options.rowOverflowClass).removeClass(this.options.rowOverflowActive);
339 }
340 this._autosizeTable();
341 }
342 }
343 },
344
345 // Load selected search result page
346 _loadResult: function() {
347 if(this._index > -1) {
348 window.location = this._rows[this._index].data("searchDetails").url;
349 return;
350 }
351 if(this.element.val().length == 0) { return; }
352
353 window.location = this.options.gotoPage + "#q=" + this.element.val();
354 if(window.location.href.indexOf(this.options.gotoPage) > -1) {
355 location.reload();
356 }
357 },
358
359 /* table events */
360
361 _clickNext: function(event) {
362 event.stopPropagation();
363 this._selectNext();
364 },
365
366 _clickPrev: function(event) {
367 event.stopPropagation();
368 this._selectPrev();
369 },
370
371 _clickRow: function(event) {
372 window.location = $(event.currentTarget).data("searchDetails")["url"];
373 },
374
375 _mouseOverRow: function(event) {
376 this._setIndex($(event.currentTarget).data("index"));
377 }
378 });
379
380 var searchField = $("<input/>")
381 .addClass("nitdoc-qs-field-notused")
382 .attr({
383 id: "nitdoc-qs-field",
384 type: "text",
385 })
386
387 $("nav.main ul").append(
388 $("<li/>")
389 .attr("id", "nitdoc-qs-li")
390 .append(searchField)
391 );
392
393 searchField.quicksearch({
394 list: this.nitdocQuickSearchRawList
395 });
396 });