2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
6 * http://www.apache.org/licenses/LICENSE-2.0
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
15 /* Nitdoc QuickSearch widget */
17 $
.widget("nitdoc.quicksearch", {
20 list
: {}, // List of raw results generated by nitdoc tool
29 this.element
.attr(this.options
.fieldAttrs
);
31 this._on(this.element
, {
32 "keydown": this._doKeyDown
,
33 "keyup": this._doKeyUp
,
34 "input": this._doInput
36 // add result table element once
37 this._popup
= $
("<div/>")
38 .attr("id", "nitdoc-qs-popup")
39 .css("position", "absolute")
40 .css("z-index", 10000)
42 $
("body").append(this._popup
);
43 // make table disappear when a click occurs outside
44 $
(document
).click($
.proxy(this.close
, this));
45 this._autosizeTable();
50 _doKeyDown
: function(event
) {
51 switch(event
.keyCode
) {
63 _doKeyUp
: function(event
) {
64 switch(event
.keyCode
) {
75 default: // Other keys
80 _doInput
: function(event
) {
81 Utils
.delayEvent($
.proxy(this.search
, this));
86 _getResults
: function(query
) {
90 var prefix_matches
= [];
91 for(var entry
in this.options
.list
) {
92 if(!entry
.startsWith(query
, true)) {
97 entries
: this.options
.list
[entry]
99 prefix_matches
.push(cat
);
103 } else if(entry
.toUpperCase() == query
.toUpperCase()) {
105 } else if(entry
[0] == query
[0]) {
106 cat
.rank
= 1.1 + query
.dice(entry
);
108 cat
.rank
= 1 + query
.dice(entry
);
111 if(prefix_matches
.length
> 0) {
112 prefix_matches
.sort(this._rankSorter
);
113 for(var i
in prefix_matches
) {
114 var cat
= prefix_matches
[i];
115 for(var j
in cat
.entries
) {
116 var entry
= cat
.entries
[j];
117 entry
.name
= cat
.name
;
125 var partial_matches
= [];
126 for(var entry
in this.options
.list
) {
129 entries
: this.options
.list
[entry]
131 cat
.rank
= query
.dice(entry
);
133 partial_matches
.push(cat
);
136 if(partial_matches
.length
> 0) {
137 partial_matches
.sort(this._rankSorter
);
138 for(var i
in partial_matches
) {
139 var cat
= partial_matches
[i];
140 for(var j
in cat
.entries
) {
141 var entry
= cat
.entries
[j];
142 entry
.name
= cat
.name
;
151 _rankSorter
: function(a
, b
){
152 if(a
.rank
< b
.rank
) {
154 } else if(a
.rank
> b
.rank
) {
163 var query
= this.element
.val();
165 var results
= this._getResults(query
);
166 this.open(query
, results
);
170 open
: function(query
, results
) {
175 if(results
.length
== 0) {
176 this.addNoResultCard();
179 if(results
.length
>= this.options
.maxSize
) {
180 this.addOverflowUp(false);
183 for(var i
in results
) {
184 var result
= results
[i];
185 this.addCard(result
.name
, result
.txt
, result
.url
, this.options
.rowCatClass
)
188 if(results
.length
>= this.options
.maxSize
) {
189 this.addOverflowDown(true);
192 if(results
.length
> 0) {
197 this._autosizeTable();
200 close
: function(target
) {
201 if(target
!= this.element
&& target
!= this._popup
) {
206 addCard
: function(name
, txt
, url
, cls
) {
207 var card
= $
("<div/>")
209 .addClass("qs-result")
210 .data("searchDetails", {name
: name
, url
: url
})
211 .data("index", this._cards
.length
)
222 .mouseover($
.proxy(this._mouseOverRow
, this))
223 .click($
.proxy(this._clickRow
, this))
224 this._cards
.push(card
);
225 if(this._cards
.length
>= this.options
.maxSize
) {
228 this._popup
.append(card
);
231 addOverflowUp
: function(active
) {
234 .addClass("qs-overflow")
235 .addClass("qs-overflow-up")
236 .addClass(active ?
"qs-overflow-active": "")
238 .click($
.proxy(this._clickPrev
, this))
242 addOverflowDown
: function(active
) {
245 .addClass("qs-overflow")
246 .addClass("qs-overflow-down")
247 .addClass(active ?
"qs-overflow-active": "")
249 .click($
.proxy(this._clickNext
, this))
253 addNoResultCard
: function() {
254 var card
= $
("<div/>")
255 .addClass("qs-card qs-noresult")
256 .html("Sorry, there is no match...");
257 this._popup
.append(card
);
260 _autosizeTable
: function() {
261 this._popup
.position({
267 .css("min-width", this.element
.outerWidth())
268 .css("max-width", this.element
.outerWidth());
271 _hasIndex
: function(index
) {
272 return index
>= 0 && index
< this._cards
.length
;
275 _hasPrev
: function(index
) {
276 return index
- 1 >= 0;
279 _hasNext
: function(index
) {
280 return index
+ 1 < this._cards
.length
;
283 _setIndex
: function(index
) {
284 if(this._hasIndex(this._index
)) {
285 this._cards
[this._index
].removeClass("qs-active");
288 if(this._hasIndex(this._index
)) {
289 this._cards
[this._index
].addClass("qs-active");
293 _selectPrev
: function() {
294 if(this._hasPrev(this._index
)) {
295 this._setIndex(this._index
- 1);
296 if(!this._cards
[this._index
].is(":visible")) {
297 this._popup
.find(".qs-result:visible").last().hide();
298 this._popup
.find(".qs-overflow-down").addClass("qs-overflow-active");
299 this._cards
[this._index
].show();
300 if(!this._hasPrev(this._index
)) {
301 this._popup
.find(".qs-overflow-up").removeClass("qs-overflow-active");
308 _selectNext
: function() {
309 if(this._hasNext(this._index
)) {
310 this._setIndex(this._index
+ 1);
311 if(!this._cards
[this._index
].is(":visible")) {
312 this._popup
.find(".qs-result:visible").first().hide();
313 this._popup
.find(".qs-overflow-up").addClass("qs-overflow-active");
314 this._cards
[this._index
].show();
315 if(!this._hasNext(this._index
)) {
316 this._popup
.find(".qs-overflow-down").removeClass("qs-overflow-active");
322 // Load selected search result page
323 _loadResult
: function() {
324 if(this._index
> -1) {
325 window
.location
= this._cards
[this._index
].data("searchDetails").url
;
328 if(this.element
.val().length
== 0) { return; }
330 window
.location
= this.options
.gotoPage
+ "#q=" + this.element
.val();
331 if(window
.location
.href
.indexOf(this.options
.gotoPage
) > -1) {
338 _clickNext
: function(event
) {
339 event
.stopPropagation();
343 _clickPrev
: function(event
) {
344 event
.stopPropagation();
348 _clickRow
: function(event
) {
349 window
.location
= $
(event
.currentTarget
).data("searchDetails")["url"];
352 _mouseOverRow
: function(event
) {
353 this._setIndex($
(event
.currentTarget
).data("index"));
357 var searchField
= $
("<input/>")
358 .addClass("form-control search-input")
360 id
: "nitdoc-qs-field",
362 placeholder
: "Search..."
365 $
("#search-placeholder").append(
367 .addClass("navbar-form navbar-right")
368 .on("submit", function() { return false; })
369 .css("margin-bottom", 0)
370 .css("margin-top", 0)
373 .addClass("form-group has-icon")
377 .addClass("glyphicon glyphicon-search form-control-icon text-muted")
382 searchField
.quicksearch({
383 list
: this.nitdocQuickSearchRawList