comparison chrome/content/incsearch.js @ 0:c47ec96326ad

initial import
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Fri, 15 Aug 2008 01:57:59 +0900
parents
children ac5648afee47
comparison
equal deleted inserted replaced
-1:000000000000 0:c47ec96326ad
1 var IncSearch = function() {
2 this.initialize.apply(this, arguments);
3 };
4
5 /*-- Utils --------------------------------------------*/
6 IncSearch._copyProperties = function(dest, src) {
7 for (var property in src) {
8 dest[property] = src[property];
9 }
10 return dest;
11 };
12
13 IncSearch._copyProperties = function(dest, src) {
14 for (var property in src) {
15 dest[property] = src[property];
16 }
17 return dest;
18 };
19
20 IncSearch._getElement = function(element) {
21 return (typeof element == 'string') ? document.getElementById(element) : element;
22 };
23
24 IncSearch._addEvent = function(element, type, func) {
25 element.addEventListener(type, func, false);
26 };
27
28 IncSearch._stopEvent = function(event) {
29 event.preventDefault();
30 event.stopPropagation();
31 };
32
33 /*-----------------------------------------------------*/
34 IncSearch.prototype = {
35 initialize: function(input, viewArea) {
36 this.input = IncSearch._getElement(input);
37 this.viewArea = IncSearch._getElement(viewArea);
38
39 this.checkLoopTimer = null;
40 this.setOptions(arguments[2] || {});
41
42 this.reset();
43
44 // check loop start
45 this.checkLoop();
46 },
47
48 reset: function() {
49 this.oldInput = null;
50 this.results = null;
51 this.resultCount = null;
52
53 this.nowPage = 0;
54 this.nowRow = 0;
55
56 this.resetTotalCount();
57 },
58
59 // options
60 interval: 500,
61 delay: 0,
62 dispMax: 10,
63 initDispNon: false,
64 ignoreCase: true,
65 highlight: true,
66 highClassName: 'high',
67 highClassNum: 4,
68 delim: ' ',
69 escape: true,
70 pagePrevName: 'prev',
71 pageNextName: 'next',
72 useHotkey: true,
73 urlTarget: '_blank',
74 editTarget: '_blank',
75
76 startElementText: '<table><tr><th></th><th width="60%">Description</th><th width="20%">Tags</th><th width="20%">Time</th><th></th>',
77
78 setOptions: function(options) {
79
80 IncSearch._copyProperties(this, options);
81
82 if (this.useHotkey) {
83 IncSearch._addEvent(document, 'keydown', this._bindEvent(this.hotkey));
84 }
85 },
86
87 checkLoop: function() {
88 var input = this.getInput();
89 if (this.isChange(input)) {
90 this.oldInput = input;
91 if (this.delay == 0) {
92 this.startSearch(input);
93 } else {
94 if (this.startSearchTimer) clearTimeout(this.startSearchTimer);
95 this.startSearchTimer = setTimeout(this._bind(this.startSearch, input), this.delay);
96 }
97 }
98 if (this.checkLoopTimer) clearTimeout(this.checkLoopTimer);
99 this.checkLoopTimer = setTimeout(this._bind(this.checkLoop), this.interval);
100 },
101
102 isChange: function(input) {
103 return (!this.oldInput || (input.join(this.delim) != this.oldInput.join(this.delim)));
104 },
105
106 startSearch: function(input) {
107 // init
108 this.clearViewArea();
109 if (!this.initDispNon || input.length != 0) {
110 if (this.searchBefore) this.searchBefore();
111 this.count(input);
112 this.search(input, 1);
113 this.createViewArea(input);
114 this.nowPage = 1;
115 this.changeRow(1);
116 this.createPageLink(1, this.pageLinkTop);
117 this.createPageLink(1, this.pageLinkBottom);
118 if (this.searchAfter) this.searchAfter();
119 }
120 },
121 changePage: function(pageNo) {
122
123 var start = (pageNo - 1) * this.dispMax + 1;
124
125 if (start > this.resultCount) return false;
126
127 if (this.changePageBefore) this.changePageBefore(pageNo);
128 this.search(this.oldInput, start);
129 this.createViewArea(this.oldInput);
130 this.nowPage = pageNo;
131 this.nowRow = 0;
132 this.changeRow(1);
133 this.createPageLink(pageNo, this.pageLinkTop);
134 this.createPageLink(pageNo, this.pageLinkBottom);
135 if (this.changePageAfter) this.changePageAfter(pageNo);
136 return true;
137 },
138 changeRow: function(rowNo) {
139
140 if (this.results.length == 0) {
141 return;
142 }
143
144 if (rowNo < 1) {
145 if (this.nowPage > 1) {
146 this.changePage(this.nowPage - 1);
147 this.changeRow(this.results.length);
148 }
149 return;
150 }
151
152 if (rowNo > this.results.length) {
153 if (this.nowPage < this.getPageCount()) {
154 this.changePage(this.nowPage + 1);
155 }
156 return;
157 }
158
159 var table = this.viewArea.getElementsByTagName('table')[0];
160
161 if (this.nowRow != 0 && table.rows[this.nowRow]) {
162 table.rows[this.nowRow].className = '';
163 }
164 var row = table.rows[rowNo];
165 row.className = 'focus';
166
167 var margin = 0;
168 var topPos = (this.viewArea.offsetTop + row.offsetTop) - (this.viewArea.offsetTop + table.rows[1].offsetTop);
169 var bottomPos = (this.viewArea.offsetTop + row.offsetTop + row.offsetHeight) + 5;
170
171 if (topPos < document.documentElement.scrollTop) {
172 window.scrollTo(0, topPos);
173 } else if (bottomPos > document.documentElement.clientHeight + document.documentElement.scrollTop) {
174 window.scrollTo(0, bottomPos - document.documentElement.clientHeight);
175 }
176
177 this.nowRow = rowNo;
178 },
179 openUrl: function(rowNo) {
180
181 if (this.results.length == 0) {
182 return;
183 }
184
185 window.open(this.results[rowNo - 1].url, this.urlTarget);
186 },
187 openEditWindow: function(rowNo) {
188
189 if (this.results.length == 0) {
190 return;
191 }
192
193 window.open(this.createEditUrl(this.results[rowNo - 1]), this.editTarget);
194 },
195
196 countSql: 'SELECT COUNT(*) count FROM bookmark',
197 resetTotalCount: function() {
198
199 var handler = new ResultArrayHandler(this.database, this.countSql);
200 handler.execute();
201 this.totalCount = handler.result[0].count;
202 },
203
204 count: function(patternList) {
205 var where = this.createWhere(patternList);
206 var sql = this.countSql + where.where;
207
208 try{
209 var handler = new ResultArrayHandler(this.database, sql);
210 handler.execute(where.params);
211 this.resultCount = handler.result[0].count;
212 } catch(e) {
213 alert(e.message || e);
214 throw e;
215 }
216 },
217
218 searchSql: 'SELECT url, title, info, tags, time FROM bookmark',
219 search: function(patternList, start) {
220
221 var where = this.createWhere(patternList);
222 var sql = [
223 this.searchSql, where.where,
224 ' ORDER BY id',
225 ' LIMIT ', this.dispMax,
226 ' OFFSET ', (start - 1)].join('');
227
228 try {
229 var handler = new ResultArrayHandler(this.database, sql);
230 handler.execute(where.params);
231 this.results = handler.result;
232 } catch(e) {
233 alert(e.message || e);
234 throw e;
235 }
236 },
237 createWhere: function(patternList) {
238
239 var where = [];
240 var params = {};
241
242 if (patternList.length != 0) {
243 for (var i = 0, len = patternList.length; i < len; i++) {
244 var temp = this.createCondOne(patternList[i], params, 'param' + i);
245 if (temp != '') {
246 if (where.length != 0) where.push(' AND');
247 where.push(temp);
248 }
249 }
250 }
251
252 var whereString = where.join('');
253 if (whereString.length > 0) whereString = ' WHERE' + whereString;
254
255 return {
256 where: whereString,
257 params: params};
258 },
259 createCondOne: function(pattern, params, paramName) {
260
261 var where = [];
262 if (pattern.indexOf('|') > -1) {
263 var patterns = this.getSplitPatterns(pattern, '|');
264 if (patterns.length != 0) {
265 for (var i = 0, len = patterns.length; i < len; i++) {
266 var temp = this.createCondOne(patterns[i], params, paramName + '_' + i);
267 if (temp != '') {
268 if (where.length != 0) where.push(' OR');
269 where.push(temp);
270 }
271 }
272 if (where.length != 0) {
273 where.unshift(' (');
274 where.push(')');
275 }
276 }
277 } else if (pattern.indexOf('!') == 0) {
278 if (pattern.length != 1) {
279 where.push(this._createCondOne(pattern.substr(1), params, paramName, true));
280 }
281 } else {
282 where.push(this._createCondOne(pattern, params, paramName));
283 }
284 return where.join('');
285 },
286 _createCondOne: function(pattern, params, paramName, not) {
287 params[paramName] = ['%', pattern.toUpperCase().replace(/\\/g, '\\\\').replace(/\%/g, '\\%').replace(/\_/g, '\\_'), '%'].join('');
288 return [" search_text ", (not ? "NOT " : ""), "LIKE :", paramName, " ESCAPE '\\'"].join('');
289 },
290
291 getSplitPatterns: function(pattern, separator) {
292 var temp = pattern.split(separator);
293 var patterns = [];
294 for (var i = 0, len = temp.length; i < len; i++) {
295 if (temp[i] != '') patterns.push(temp[i]);
296 }
297 return patterns;
298 },
299
300 createPageLink: function(pageNo, pageLinkElm) {
301
302 pageLinkElm = IncSearch._getElement(pageLinkElm);
303
304 var pageCount = this.getPageCount();
305
306 var prev_page = false;
307 var next_page = false;
308
309 if (pageCount > 1) {
310
311 if (pageNo == 1) {
312 next_page = true;
313 } else if (pageNo == pageCount) {
314 prev_page = true;
315 } else {
316 next_page = true;
317 prev_page = true;
318 }
319 }
320
321 pageLinkElm.innerHTML = '';
322
323 if (prev_page) {
324 this.createPageAnchor(pageLinkElm, this.pagePrevName, pageNo - 1);
325 }
326 if (next_page) {
327 if (prev_page) {
328 pageLinkElm.appendChild(document.createTextNode(' | '));
329 }
330
331 this.createPageAnchor(pageLinkElm, this.pageNextName, pageNo + 1);
332 }
333 },
334
335 createPageAnchor: function(parent, text, page) {
336
337 var a = parent.appendChild(document.createElement('a'));
338 a.setAttribute('href', 'javascript:void(0)');
339 a.appendChild(document.createTextNode(text));
340
341 IncSearch._addEvent(a, 'click', this._bind(this.changePage, page));
342 },
343
344 getPageCount: function() {
345 var pageCount = 0;
346
347 if (this.resultCount && this.resultCount != 0) {
348 if (this.dispMax == 0) {
349 pageCount = 1;
350 } else {
351 pageCount = Math.floor((this.resultCount + this.dispMax - 1) / this.dispMax);
352 }
353 }
354 return pageCount;
355 },
356
357 createInfo: function() {
358 var displayInfo = '';
359
360 if (this.resultCount != 0) {
361 var start = (this.nowPage - 1) * this.dispMax + 1;
362 var end = start + this.dispMax - 1;
363
364 if (this.dispMax == 0 || end > this.resultCount) {
365 end = this.resultCount;
366 }
367 displayInfo = ['(display :', start, '-', end, ')'].join('');
368 }
369 this.status.innerHTML = [this.resultCount.toString(), ' hits ',
370 displayInfo, ' / total : ', this.totalCount].join('');
371 },
372 searchAfter: function() {
373 this.createInfo();
374 window.scrollTo(0, 0);
375 },
376 searchBefore: function() {
377 this.status.innerHTML = 'Search...';
378 },
379 changePageAfter: function(pageNo) {
380 this.createInfo();
381 window.scrollTo(0, 0);
382 },
383
384 hotkey: function(event) {
385 if (event.ctrlKey) {
386 switch(event.keyCode) {
387 case 13: // Enter
388 case 77: // m (Enter Max OS X)
389 this.openUrl(this.nowRow);
390 IncSearch._stopEvent(event);
391 break;
392 case 37: // Left
393 if (this.nowPage > 1) {
394 this.changePage(this.nowPage - 1);
395 }
396 IncSearch._stopEvent(event);
397 break;
398 case 38: // Up
399 this.changeRow(this.nowRow - 1);
400 IncSearch._stopEvent(event);
401 break;
402 case 39: // Right
403 if (this.nowPage < this.getPageCount()) {
404 this.changePage(this.nowPage + 1);
405 }
406 IncSearch._stopEvent(event);
407 break;
408 case 40: // Down
409 this.changeRow(this.nowRow + 1);
410 IncSearch._stopEvent(event);
411 break;
412 case 69: // e
413 this.openEditWindow(this.nowRow);
414 IncSearch._stopEvent(event);
415 break;
416 default:
417 break;
418 }
419 }
420 },
421
422 createViewArea: function(patternList) {
423 var elementText = [];
424
425 patternList = this.getHighlightPatterns(patternList);
426
427 for (var i = 0, len = this.results.length; i < len; i++) {
428 elementText.push(this.createLineElement(this.results[i], patternList));
429 }
430
431 if (elementText.length > 0) {
432 if (this.startElementText) elementText.unshift(this.startElementText);
433 if (this.endElementText) elementText.push(this.endElementText);
434 this.viewArea.innerHTML = elementText.join('');
435 }
436
437 if (this.afterHookCreateView) {
438 this.afterHookCreateView(patternList);
439 }
440 },
441
442 getHighlightPatterns: function(patternList) {
443 var highlightPatterns = [];
444
445 for (var i = 0, len = patternList.length; i < len; i++) {
446 var pattern = patternList[i];
447 if (pattern.indexOf('|') > -1) {
448 var patterns = this.getSplitPatterns(pattern, '|');
449 highlightPatterns = highlightPatterns.concat(this.getHighlightPatterns(patterns));
450 } else if (pattern.indexOf('!') != 0) {
451 highlightPatterns.push(pattern);
452 }
453 }
454 return highlightPatterns;
455 },
456
457 clearViewArea: function() {
458 this.viewArea.innerHTML = '';
459 this.results = null;
460 this.resultCount = null;
461 this.nowPage = 0;
462 this.nowRow = 0;
463 },
464
465 createLineElement: function(bookmark, patternList) {
466
467 var text = ['<tr><td></td><td>'];
468
469 // url, title
470 text.push(this.createTitleElement(bookmark, patternList));
471
472 // info
473 if (bookmark.info) {
474 text.push(this.createElement(bookmark.info, patternList, 'p'));
475 }
476 text.push('</td>');
477
478 // tags
479 text.push(this.createElement(this.tagsString(bookmark.tags), patternList, 'td'));
480
481 // time
482 text.push(this.createElement(bookmark.time, patternList, 'td', false));
483
484 // edit
485 text.push(this.createEditElement(bookmark, patternList));
486 text.push('</tr>');
487
488 return text.join('');
489 },
490
491 createElement: function(value, patternList, tagName, highlight) {
492
493 return ['<', tagName, '>',
494 this.createText(value, patternList, highlight),
495 '</', tagName, '>'].join('');
496 },
497
498 createText: function(value, patternList, highlight) {
499
500 var textList = [];
501
502 if (highlight == null) highlight = this.highlight;
503
504 if (highlight) {
505
506 var first = this.getFirstMatch(value, patternList);
507
508 while (first.listIndex != -1) {
509 textList.push(this._escapeHTML(value.substr(0, first.matchIndex)));
510 textList.push('<strong class="');
511 textList.push(this.highClassName);
512 textList.push((first.listIndex % this.highClassNum) + 1);
513 textList.push('">');
514 textList.push(this._escapeHTML(value.substr(first.matchIndex, patternList[first.listIndex].length)));
515 textList.push('</strong>');
516
517 value = value.substr(first.matchIndex + patternList[first.listIndex].length);
518 first = this.getFirstMatch(value, patternList);
519 }
520 }
521
522 textList.push(this._escapeHTML(value));
523
524 return textList.join('');
525 },
526
527 tagsString: function(tags, sep) {
528
529 if (typeof(tags) == 'string') return tags;
530
531 sep = sep || ' ';
532 if (this.tagBracket && tags.length != 0) {
533 return ['[', tags.join(']' + sep + '['), ']'].join('');
534 } else {
535 return tags.join(sep);
536 }
537 },
538
539 createTitleElement: function(bookmark, patternList) {
540 var text = ['<a href="', bookmark.url, '"'];
541 if (this.urlTarget) {
542 text.push(' target="', this.urlTarget, '" ');
543 }
544 text.push('>');
545 text.push(this.createText(bookmark.title, patternList));
546 text.push('</a>');
547
548 if (this.addTitleText) {
549 text.push(this.addTitleText(bookmark, patternList));
550 }
551
552 text.push('<br />');
553 return text.join('');
554 },
555
556 createEditElement: function(bookmark, patternList) {
557
558 var text = ['<td><a href="', this.createEditUrl(bookmark), '"'];
559 if (this.editTarget) {
560 text.push(' target="', this.editTarget, '" ');
561 }
562
563 text.push('>edit</a></td>');
564
565 return text.join('');
566 },
567
568 matchIndex: function(value, pattern) {
569
570 if (this.ignoreCase) {
571 return value.toLowerCase().indexOf(pattern.toLowerCase());
572 } else {
573 return value.indexOf(pattern);
574 }
575 },
576
577 getFirstMatch: function(value, patternList) {
578
579 var first = {};
580 first.listIndex = -1;
581 first.matchIndex = value.length;
582
583 for (var i = 0, len = patternList.length; i < len; i++) {
584 var index = this.matchIndex(value, patternList[i]);
585 if (index != -1 && index < first.matchIndex) {
586 first.listIndex = i;
587 first.matchIndex = index;
588 }
589 }
590
591 return first;
592 },
593
594 getInput: function() {
595
596 var value = this.input.value;
597
598 if (!value) {
599 return [];
600 } else if (this.delim) {
601 var list = value.split(this.delim);
602 var inputs = [];
603 for (var i = 0, len = list.length; i < len; i++) {
604 if (list[i]) inputs.push(list[i]);
605 }
606 return inputs;
607 } else {
608 return [value];
609 }
610 },
611
612 // Utils
613 _bind: function(func) {
614 var self = this;
615 var args = Array.prototype.slice.call(arguments, 1);
616 return function(){ func.apply(self, args); };
617 },
618 _bindEvent: function(func) {
619 var self = this;
620 var args = Array.prototype.slice.call(arguments, 1);
621 return function(event){ event = event || window.event; func.apply(self, [event].concat(args)); };
622 },
623 _escapeHTML: function(value) {
624 if (this.escape) {
625 return value.replace(/\&/g, '&amp;').replace( /</g, '&lt;').replace(/>/g, '&gt;')
626 .replace(/\"/g, '&quot;').replace(/\'/g, '&#39;').replace(/\n|\r\n/g, '<br />');
627 } else {
628 return value;
629 }
630 }
631 }