added swap files, tests results and Eclipse project file to git ignore
[nit.git] / share / ni_nitdoc / scripts / Nitdoc.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 var Nitdoc = Nitdoc || {};
20
21 /*
22 * Nitdoc QuickSearch module
23 *
24 */
25
26 Nitdoc.QuickSearch = function() {
27 var rawList = nitdocQuickSearchRawList; // List of raw resulsts generated by nitdoc tool
28 var searchField = null; // <input:text> search field
29 var currentTable = null; // current search results <table>
30 var currentIndex = -1; // current cursor position into search results table
31
32 // Enable QuickSearch plugin
33 var enableQuickSearch = function(containerSelector) {
34 searchField = $(document.createElement("input"))
35 .attr({
36 id: "nitdoc-qs-field",
37 type: "text",
38 autocomplete: "off",
39 value: "quick search..."
40 })
41 .addClass("nitdoc-qs-field-notused")
42 .keyup(function(event) {
43 Nitdoc.QuickSearch.doKeyAction(event.keyCode);
44 })
45 .focusout(function() {
46 if($(this).val() == "") {
47 $(this).addClass("nitdoc-qs-field-notused");
48 $(this).val("quick search...");
49 }
50 })
51 .focusin(function() {
52 if($(this).val() == "quick search...") {
53 $(this).removeClass("nitdoc-qs-field-notused");
54 $(this).val("");
55 }
56 });
57
58 $(containerSelector).append(
59 $(document.createElement("li"))
60 .attr("id", "nitdoc-qs-li")
61 .append(searchField)
62 );
63
64 // Close quicksearch list on click
65 $(document).click(function(e) {
66 Nitdoc.QuickSearch.closeResultsTable();
67 });
68 }
69
70 // Respond to key event
71 var doKeyAction = function(key) {
72 switch(key) {
73 case 38: // Up
74 selectPrevResult();
75 break;
76
77 case 40: // Down
78 selectNextResult();
79 break;
80
81 case 13: // Enter
82 goToResult();
83 return false;
84 break;
85
86 case 27: // Escape
87 $(this).blur();
88 closeResultsTable();
89 break;
90
91 default: // Other keys
92 var query = searchField.val();
93 if(!query) {
94 return false;
95 }
96 var results = rankResults(query);
97 results.sort(resultsSort);
98 displayResultsTable(query, results);
99 break;
100 }
101 }
102
103 // Rank raw list entries corresponding to query
104 var rankResults = function(query) {
105 var results = new Array();
106 for(var entry in rawList) {
107 for(var i in rawList[entry]) {
108 var result = rawList[entry][i];
109 result.entry = entry;
110 result.distance = query.dice(entry);
111 results[results.length] = result;
112 }
113 }
114 return results;
115 }
116
117 // Sort an array of results
118 var resultsSort = function(a, b){
119 if(a.distance < b.distance) {
120 return 1;
121 } else if(a.distance > b.distance) {
122 return -1;
123 }
124 return 0;
125 }
126
127 // Display results in a popup table
128 var displayResultsTable = function(query, results) {
129 // Clear results table
130 if(currentTable) currentTable.remove();
131
132 // Build results table
133 currentIndex = -1;
134 currentTable = $(document.createElement("table"));
135
136 for(var i in results) {
137 if(i > 10) {
138 break;
139 }
140 var result = results[i];
141 currentTable.append(
142 $(document.createElement("tr"))
143 .data("searchDetails", {name: result.entry, url: result.url})
144 .data("index", i)
145 .append($(document.createElement("td")).html(result.entry))
146 .append(
147 $(document.createElement("td"))
148 .addClass("nitdoc-qs-info")
149 .html(result.txt + "&nbsp;&raquo;")
150 )
151 .mouseover( function() {
152 $(currentTable.find("tr")[currentIndex]).removeClass("nitdoc-qs-active");
153 $(this).addClass("nitdoc-qs-active");
154 currentIndex = $(this).data("index");
155 })
156 .mouseout( function() {
157 $(this).removeClass("nitdoc-qs-active");
158 })
159 .click( function() {
160 window.location = $(this).data("searchDetails")["url"];
161 })
162 );
163 }
164 currentTable.append(
165 $("<tr class='nitdoc-qs-overflow'>")
166 .append(
167 $("<td colspan='2'>")
168 .html("Best results for '" + query + "'")
169 )
170 );
171
172 // Initialize table properties
173 currentTable.attr("id", "nitdoc-qs-table");
174 currentTable.css("position", "absolute");
175 currentTable.width(searchField.outerWidth());
176 $("body").append(currentTable);
177 currentTable.offset({left: searchField.offset().left + (searchField.outerWidth() - currentTable.outerWidth()), top: searchField.offset().top + searchField.outerHeight()});
178 // Preselect first entry
179 if(currentTable.find("tr").length > 0) {
180 currentIndex = 0;
181 $(currentTable.find("tr")[currentIndex]).addClass("nitdoc-qs-active");
182 searchField.focus();
183 }
184 }
185
186 // Select the previous result on current table
187 var selectPrevResult = function() {
188 // If already on first result, focus search input
189 if(currentIndex == 0) {
190 searchField.val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);
191 searchField.focus();
192 // Else select previous result
193 } else if(currentIndex > 0) {
194 $(currentTable.find("tr")[currentIndex]).removeClass("nitdoc-qs-active");
195 currentIndex--;
196 $(currentTable.find("tr")[currentIndex]).addClass("nitdoc-qs-active");
197 searchField.val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);
198 searchField.focus();
199 }
200 }
201
202 // Select the next result on current table
203 var selectNextResult = function() {
204 if(currentIndex < currentTable.find("tr").length - 1) {
205 if($(currentTable.find("tr")[currentIndex + 1]).hasClass("nitdoc-qs-overflow")) {
206 return;
207 }
208 $(currentTable.find("tr")[currentIndex]).removeClass("nitdoc-qs-active");
209 currentIndex++;
210 $(currentTable.find("tr")[currentIndex]).addClass("nitdoc-qs-active");
211 searchField.val($(currentTable.find("tr")[currentIndex]).data("searchDetails").name);
212 searchField.focus();
213 }
214 }
215
216 // Load selected search result page
217 var goToResult = function() {
218 if(currentIndex > -1) {
219 window.location = $(currentTable.find("tr")[currentIndex]).data("searchDetails").url;
220 return;
221 }
222
223 if(searchField.val().length == 0) { return; }
224
225 window.location = "search.html#q=" + searchField.val();
226 if(window.location.href.indexOf("search.html") > -1) {
227 location.reload();
228 }
229 }
230
231 // Close the results table
232 closeResultsTable = function(target) {
233 if(target != searchField && target != currentTable) {
234 if(currentTable != null) {
235 currentTable.remove();
236 currentTable = null;
237 }
238 }
239 }
240
241 // Public interface
242 var quicksearch = {
243 enableQuickSearch: enableQuickSearch,
244 doKeyAction: doKeyAction,
245 closeResultsTable: closeResultsTable
246 };
247
248 return quicksearch;
249 }();
250
251 $(document).ready(function() {
252 Nitdoc.QuickSearch.enableQuickSearch("nav.main ul");
253 });
254
255 /*
256 * Utils
257 */
258
259 // Calculate levenshtein distance beetween two strings
260 // see: http://en.wikipedia.org/wiki/Levenshtein_distance
261 String.prototype.levenshtein = function(other) {
262 var matrix = new Array();
263
264 for(var i = 0; i <= this.length; i++) {
265 matrix[i] = new Array();
266 matrix[i][0] = i;
267 }
268 for(var j = 0; j <= other.length; j++) {
269 matrix[0][j] = j;
270 }
271 var cost = 0;
272 for(var i = 1; i <= this.length; i++) {
273 for(var j = 1; j <= other.length; j++) {
274 if(this.charAt(i - 1) == other.charAt(j - 1)) {
275 cost = 0;
276 } else if(this.charAt(i - 1).toLowerCase() == other.charAt(j - 1).toLowerCase()) {
277 cost = 0.5;
278 } else {
279 cost = 1;
280 }
281 matrix[i][j] = Math.min(
282 matrix[i - 1][j] + 1, // deletion
283 matrix[i][j - 1] + 1, // insertion
284 matrix[i - 1][j - 1] + cost // substitution
285 );
286 }
287 }
288 return matrix[this.length][other.length]
289 }
290
291 // Compare two strings using Sorensen-Dice Coefficient
292 // see: http://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient
293 String.prototype.dice = function(other) {
294 var length1 = this.length - 1;
295 var length2 = other.length - 1;
296 if(length1 < 1 || length2 < 1) return 0;
297
298 var bigrams2 = [];
299 for(var i = 0; i < length2; i++) {
300 bigrams2.push(other.substr(i, 2));
301 }
302
303 var intersection = 0;
304 for(var i = 0; i < length1; i++) {
305 var bigram1 = this.substr(i, 2);
306 for(var j = 0; j < length2; j++) {
307 if(bigram1 == bigrams2[j]) {
308 intersection += 2;
309 bigrams2[j] = null;
310 break;
311 } else if (bigram1 && bigrams2[j] && bigram1.toLowerCase() == bigrams2[j].toLowerCase()) {
312 intersection += 1;
313 bigrams2[j] = null;
314 break;
315 }
316 }
317 }
318 return (2.0 * intersection) / (length1 + length2);
319 }
320