mirror of
https://github.com/zhigang1992/typeahead.js.git
synced 2026-05-22 04:27:39 +08:00
Implement highlighting. Closes #69.
This commit is contained in:
82
src/highlight.js
Normal file
82
src/highlight.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* typeahead.js
|
||||
* https://github.com/twitter/typeahead
|
||||
* Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
|
||||
*/
|
||||
|
||||
// inspired by https://github.com/jharding/bearhug
|
||||
|
||||
var highlight = (function(doc) {
|
||||
|
||||
var defaults = {
|
||||
node: null,
|
||||
pattern: null,
|
||||
tagName: 'strong',
|
||||
className: null,
|
||||
wordsOnly: false,
|
||||
caseSensitive: false
|
||||
};
|
||||
|
||||
return function hightlight(o) {
|
||||
var regex;
|
||||
|
||||
o = utils.mixin({}, defaults, o);
|
||||
|
||||
if (!o.node || !o.pattern) {
|
||||
throw new Error('both node and pattern must be set');
|
||||
}
|
||||
|
||||
// support wrapping multiple patterns
|
||||
o.pattern = utils.isArray(o.pattern) ? o.pattern : [o.pattern];
|
||||
|
||||
regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);
|
||||
traverse(o.node, hightlightTextNode);
|
||||
|
||||
function hightlightTextNode(textNode) {
|
||||
var match, patternNode;
|
||||
|
||||
if (match = regex.exec(textNode.data)) {
|
||||
wrapperNode = doc.createElement(o.tagName);
|
||||
o.className && (wrapperNode.className = o.className);
|
||||
|
||||
patternNode = textNode.splitText(match.index);
|
||||
patternNode.splitText(match[0].length);
|
||||
wrapperNode.appendChild(patternNode.cloneNode(true));
|
||||
|
||||
textNode.parentNode.replaceChild(wrapperNode, patternNode);
|
||||
}
|
||||
|
||||
return !!match;
|
||||
}
|
||||
|
||||
function traverse(el, hightlightTextNode) {
|
||||
var childNode, TEXT_NODE_TYPE = 3;
|
||||
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
childNode = el.childNodes[i];
|
||||
|
||||
if (childNode.nodeType === TEXT_NODE_TYPE) {
|
||||
i += hightlightTextNode(childNode) ? 1 : 0;
|
||||
}
|
||||
|
||||
else {
|
||||
traverse(childNode, hightlightTextNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getRegex(patterns, caseSensitive, wordsOnly) {
|
||||
var escapedPatterns = [], regexStr;
|
||||
|
||||
for (var i = 0; i < patterns.length; i++) {
|
||||
escapedPatterns.push(utils.escapeRegExChars(patterns[i]));
|
||||
}
|
||||
|
||||
regexStr = wordsOnly ?
|
||||
'\\b(' + escapedPatterns.join('|') + ')\\b' :
|
||||
'(' + escapedPatterns.join('|') + ')';
|
||||
|
||||
return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, 'i');
|
||||
}
|
||||
})(window.document);
|
||||
@@ -19,6 +19,7 @@ var SectionView = (function() {
|
||||
|
||||
// tracks the last query the section was updated for
|
||||
this.query = null;
|
||||
this.highlight = !!o.highlight;
|
||||
|
||||
this.dataset = o.dataset;
|
||||
this.templates = o.templates || {};
|
||||
@@ -51,6 +52,7 @@ var SectionView = (function() {
|
||||
|
||||
this.clear();
|
||||
this.$el.append($suggestions);
|
||||
this.highlight && highlight({ node: $suggestions[0], pattern: query });
|
||||
// TODO: render header and footer
|
||||
|
||||
this.trigger('rendered');
|
||||
|
||||
128
test/highlight_spec.js
Normal file
128
test/highlight_spec.js
Normal file
@@ -0,0 +1,128 @@
|
||||
describe('highlight', function() {
|
||||
|
||||
it('should throw an error if node is not set', function() {
|
||||
expect(init).toThrow();
|
||||
function init() { highlight({ pattern: 'abc' }); }
|
||||
});
|
||||
|
||||
it('should throw an error if pattern is not set', function() {
|
||||
expect(init).toThrow();
|
||||
function init() { highlight({ node: document.createElement('div') }); }
|
||||
});
|
||||
|
||||
it('should allow tagName to be specified', function() {
|
||||
var before = 'abcde',
|
||||
after = 'a<span>bcd</span>e',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'bcd', tagName: 'span' });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should allow className to be specified', function() {
|
||||
var before = 'abcde',
|
||||
after = 'a<strong class="one two">bcd</strong>e',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'bcd', className: 'one two' });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should be case insensitive by default', function() {
|
||||
var before = 'ABCDE',
|
||||
after = 'A<strong>BCD</strong>E',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'bcd' });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should support case sensitivity', function() {
|
||||
var before = 'ABCDE',
|
||||
after = 'ABCDE',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'bcd', caseSensitive: true });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should support words only matching', function() {
|
||||
var before = 'tone one phone',
|
||||
after = 'tone <strong>one</strong> phone',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'one', wordsOnly: true });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should support matching multiple patterns', function() {
|
||||
var before = 'tone one phone',
|
||||
after = '<strong>tone</strong> one <strong>phone</strong>',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: ['tone', 'phone'] });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should support regex chars in the pattern', function() {
|
||||
var before = '*.js when?',
|
||||
after = '<strong>*.</strong>js when<strong>?</strong>',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: ['*.', '?'] });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should work on complex html structures', function() {
|
||||
var before = [
|
||||
'<div>abcde',
|
||||
'<span>abcde</span>',
|
||||
'<div><p>abcde</p></div>',
|
||||
'</div>'
|
||||
].join(''),
|
||||
after = [
|
||||
'<div><strong>abc</strong>de',
|
||||
'<span><strong>abc</strong>de</span>',
|
||||
'<div><p><strong>abc</strong>de</p></div>',
|
||||
'</div>'
|
||||
].join(''),
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'abc' });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should ignore html tags and attributes', function() {
|
||||
var before = '<span class="class"></span>',
|
||||
after = '<span class="class"></span>',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: ['span', 'class'] });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should not match across tags', function() {
|
||||
var before = 'a<span>b</span>c',
|
||||
after = 'a<span>b</span>c',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'abc' });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
it('should ignore html comments', function() {
|
||||
var before = '<!-- abc -->',
|
||||
after = '<!-- abc -->',
|
||||
testNode = buildTestNode(before);
|
||||
|
||||
highlight({ node: testNode, pattern: 'abc' });
|
||||
expect(testNode.innerHTML).toEqual(after);
|
||||
});
|
||||
|
||||
function buildTestNode(content) {
|
||||
var node = document.createElement('div');
|
||||
node.innerHTML = content;
|
||||
|
||||
return node;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user