0
|
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, '&').replace( /</g, '<').replace(/>/g, '>')
|
|
626 .replace(/\"/g, '"').replace(/\'/g, ''').replace(/\n|\r\n/g, '<br />');
|
|
627 } else {
|
|
628 return value;
|
|
629 }
|
|
630 }
|
|
631 }
|