Skip to content

Commit d684ee5

Browse files
authored
Merge pull request MoveInc#10 from DavidMove/dh-search-highlighting
Added search highlighting
2 parents c12f1df + 260098c commit d684ee5

File tree

8 files changed

+666
-538
lines changed

8 files changed

+666
-538
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
### Enhancements & Features
66
- Add `defaultRowCount` property to set default row count from rowCount array
77
- Add `sort` event; triggered when column is sorted
8+
- Add `searchSettings.highlightResults` option to wrap matching substrings that match the query in result set
9+
- Add `highlightResults` css class to options
10+
- Add `highlightResults` template which wraps the matched substring
811

912
## 1.4.3
1013

demo/index.htm

Lines changed: 530 additions & 525 deletions
Large diffs are not rendered by default.

dist/jquery.bootgrid.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* jQuery Bootgrid v1.4.2 - 01/20/2017
2+
* jQuery Bootgrid v1.4.2 - 02/10/2017
33
* Copyright (c) 2014-2017 Rafael Staib (http://www.jquery-bootgrid.com)
44
* Licensed under MIT http://www.opensource.org/licenses/MIT
55
*/
@@ -145,3 +145,6 @@
145145
text-overflow: inherit !important;
146146
white-space: inherit !important;
147147
}
148+
.bootgrid-search-highlight {
149+
font-weight: bold;
150+
}

dist/jquery.bootgrid.fa.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* jQuery Bootgrid v1.4.2 - 01/20/2017
2+
* jQuery Bootgrid v1.4.2 - 02/10/2017
33
* Copyright (c) 2014-2017 Rafael Staib (http://www.jquery-bootgrid.com)
44
* Licensed under MIT http://www.opensource.org/licenses/MIT
55
*/

dist/jquery.bootgrid.js

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* jQuery Bootgrid v1.4.2 - 01/20/2017
2+
* jQuery Bootgrid v1.4.2 - 02/10/2017
33
* Copyright (c) 2014-2017 Rafael Staib (http://www.jquery-bootgrid.com)
44
* Licensed under MIT http://www.opensource.org/licenses/MIT
55
*/
@@ -87,6 +87,45 @@ function highlightAppendedRows(rows) {
8787
}
8888
}
8989

90+
// Replaces all occurrences of the word in the given html
91+
// Original source: http://stackoverflow.com/questions/8503121/replace-words-in-a-string-but-ignore-html
92+
function highlightResults(html) {
93+
var that = this,
94+
word = that.searchPhrase,
95+
tpl = this.options.templates,
96+
css = this.options.css,
97+
container = document.createElement("div"),
98+
regexFlag = that.options.caseSensitive ? 'g' : 'gi',
99+
regex = new RegExp('(' + word + ')', regexFlag);
100+
101+
container.innerHTML = html;
102+
103+
// Traverses the given element and apply the text replacement function with the given regex
104+
function traverseElement(el, regex, textReplacerFunc) {
105+
var child = el.lastChild;
106+
while (child) {
107+
if (child.nodeType === 1) {
108+
traverseElement(child, regex, textReplacerFunc);
109+
} else if (child.nodeType === 3) {
110+
textReplacerFunc(child, regex);
111+
}
112+
child = child.previousSibling;
113+
}
114+
}
115+
116+
traverseElement(container, regex, function(textNode, regex) {
117+
// need this as the rendered cell will encode any html tags which will not render
118+
textNode.data = textNode.data.replace(regex, "{{$1}}");
119+
});
120+
121+
var reg = new RegExp("(?:{{)(.*?)(?:}})", "g");
122+
return container.innerHTML.replace(reg, function(str, el){
123+
return tpl.highlightResults.resolve(getParams.call(that, {
124+
content: el
125+
}));
126+
});
127+
}
128+
90129
function isVisible(column) {
91130
return column.visible;
92131
}
@@ -162,7 +201,7 @@ function loadData() {
162201

163202
for (var i = 0; i < that.columns.length; i++) {
164203
column = that.columns[i];
165-
if (column.searchable && (column.visible || that.options.searchSettings.includeHidden ) &&
204+
if (column.searchable && (column.visible || that.options.searchSettings.includeHidden) &&
166205
column.converter.to(row[column.id], row, column, that).toString().search(searchPattern) > -1) {
167206
return true;
168207
}
@@ -609,6 +648,12 @@ function renderRows(rows) {
609648
column.formatter.call(that, column, row) :
610649
column.converter.to(row[column.id], row, column, that),
611650
cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : "";
651+
652+
// Highlight search phrase if available
653+
if (that.searchPhrase !== '' && that.options.searchSettings.highlightResults) {
654+
value = highlightResults.call(that, value);
655+
}
656+
612657
cells += tpl.cell.resolve(getParams.call(that, {
613658
content: (value == null || value === "") ? "&nbsp;" : value,
614659
css: ((column.align === "right") ? css.right : (column.align === "center") ?
@@ -1093,14 +1138,24 @@ Grid.defaults = {
10931138
characters: 1,
10941139

10951140
/**
1096-
* Option if search should ignore hidden columns
1141+
* Option if search should include hidden columns
10971142
*
10981143
* @property includeHidden
10991144
* @type Boolean
11001145
* @default false
11011146
* @for searchSettings
11021147
**/
1103-
includeHidden: false
1148+
includeHidden: false,
1149+
1150+
/**
1151+
* Option if search term in results should be highlighted
1152+
*
1153+
* @property highlightResults
1154+
* @type Boolean
1155+
* @default false
1156+
* @for searchSettings
1157+
**/
1158+
highlightResults: true
11041159
},
11051160

11061161
/**
@@ -1266,6 +1321,7 @@ Grid.defaults = {
12661321
dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown
12671322
footer: "bootgrid-footer container-fluid",
12681323
header: "bootgrid-header container-fluid",
1324+
highlightResults: "bootgrid-search-highlight",
12691325
icon: "icon glyphicon",
12701326
iconColumns: "glyphicon-th-list",
12711327
iconDown: "glyphicon-chevron-down",
@@ -1276,7 +1332,6 @@ Grid.defaults = {
12761332
left: "text-left",
12771333
pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer
12781334
paginationButton: "button", // must be a unique class name or constellation of class names within the pagination
1279-
12801335
/**
12811336
* CSS class to select the parent div which activates responsive mode.
12821337
*
@@ -1423,6 +1478,7 @@ Grid.defaults = {
14231478
footer: "<div id=\"{{ctx.id}}\" class=\"{{css.footer}}\"><div class=\"row\"><div class=\"col-sm-6\"><p class=\"{{css.pagination}}\"></p></div><div class=\"col-sm-6 infoBar\"><p class=\"{{css.infos}}\"></p></div></div></div>",
14241479
header: "<div id=\"{{ctx.id}}\" class=\"{{css.header}}\"><div class=\"row\"><div class=\"col-sm-12 actionBar\"><p class=\"{{css.search}}\"></p><p class=\"{{css.actions}}\"></p></div></div></div>",
14251480
headerCell: "<th data-column-id=\"{{ctx.column.id}}\" class=\"{{ctx.css}}\" style=\"{{ctx.style}}\"><a href=\"javascript:void(0);\" class=\"{{css.columnHeaderAnchor}} {{ctx.sortable}}\"><span class=\"{{css.columnHeaderText}}\">{{ctx.column.text}}</span>{{ctx.icon}}</a></th>",
1481+
highlightResults: "<span class=\"{{css.highlightResults}}\">{{ctx.content}}</span>",
14261482
icon: "<span class=\"{{css.icon}} {{ctx.iconCss}}\"></span>",
14271483
infos: "<div class=\"{{css.infos}}\">{{lbl.infos}}</div>",
14281484
loading: "<tr><td colspan=\"{{ctx.columns}}\" class=\"loading\">{{lbl.loading}}</td></tr>",

src/internal.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,45 @@ function highlightAppendedRows(rows) {
7777
}
7878
}
7979

80+
// Replaces all occurrences of the word in the given html
81+
// Original source: http://stackoverflow.com/questions/8503121/replace-words-in-a-string-but-ignore-html
82+
function highlightResults(html) {
83+
var that = this,
84+
word = that.searchPhrase,
85+
tpl = this.options.templates,
86+
css = this.options.css,
87+
container = document.createElement("div"),
88+
regexFlag = that.options.caseSensitive ? 'g' : 'gi',
89+
regex = new RegExp('(' + word + ')', regexFlag);
90+
91+
container.innerHTML = html;
92+
93+
// Traverses the given element and apply the text replacement function with the given regex
94+
function traverseElement(el, regex, textReplacerFunc) {
95+
var child = el.lastChild;
96+
while (child) {
97+
if (child.nodeType === 1) {
98+
traverseElement(child, regex, textReplacerFunc);
99+
} else if (child.nodeType === 3) {
100+
textReplacerFunc(child, regex);
101+
}
102+
child = child.previousSibling;
103+
}
104+
}
105+
106+
traverseElement(container, regex, function(textNode, regex) {
107+
// need this as the rendered cell will encode any html tags which will not render
108+
textNode.data = textNode.data.replace(regex, "{{$1}}");
109+
});
110+
111+
var reg = new RegExp("(?:{{)(.*?)(?:}})", "g");
112+
return container.innerHTML.replace(reg, function(str, el){
113+
return tpl.highlightResults.resolve(getParams.call(that, {
114+
content: el
115+
}));
116+
});
117+
}
118+
80119
function isVisible(column) {
81120
return column.visible;
82121
}
@@ -152,7 +191,7 @@ function loadData() {
152191

153192
for (var i = 0; i < that.columns.length; i++) {
154193
column = that.columns[i];
155-
if (column.searchable && (column.visible || that.options.searchSettings.includeHidden ) &&
194+
if (column.searchable && (column.visible || that.options.searchSettings.includeHidden) &&
156195
column.converter.to(row[column.id], row, column, that).toString().search(searchPattern) > -1) {
157196
return true;
158197
}
@@ -599,6 +638,12 @@ function renderRows(rows) {
599638
column.formatter.call(that, column, row) :
600639
column.converter.to(row[column.id], row, column, that),
601640
cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : "";
641+
642+
// Highlight search phrase if available
643+
if (that.searchPhrase !== '' && that.options.searchSettings.highlightResults) {
644+
value = highlightResults.call(that, value);
645+
}
646+
602647
cells += tpl.cell.resolve(getParams.call(that, {
603648
content: (value == null || value === "") ? "&nbsp;" : value,
604649
css: ((column.align === "right") ? css.right : (column.align === "center") ?

src/jquery.bootgrid.less

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
text-align: right;
5555

5656
.btn-group > .btn-group
57-
{
57+
{
5858
.dropdown-menu
5959
{
6060
text-align: left;
@@ -157,4 +157,9 @@
157157

158158
td { .noTurncate(); }
159159
}
160-
}
160+
}
161+
162+
.bootgrid-search-highlight
163+
{
164+
font-weight: bold;
165+
}

src/public.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,24 @@ Grid.defaults = {
155155
characters: 1,
156156

157157
/**
158-
* Option if search should ignore hidden columns
158+
* Option if search should include hidden columns
159159
*
160160
* @property includeHidden
161161
* @type Boolean
162162
* @default false
163163
* @for searchSettings
164164
**/
165-
includeHidden: false
165+
includeHidden: false,
166+
167+
/**
168+
* Option if search term in results should be highlighted
169+
*
170+
* @property highlightResults
171+
* @type Boolean
172+
* @default false
173+
* @for searchSettings
174+
**/
175+
highlightResults: true
166176
},
167177

168178
/**
@@ -328,6 +338,7 @@ Grid.defaults = {
328338
dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown
329339
footer: "bootgrid-footer container-fluid",
330340
header: "bootgrid-header container-fluid",
341+
highlightResults: "bootgrid-search-highlight",
331342
icon: "icon glyphicon",
332343
iconColumns: "glyphicon-th-list",
333344
iconDown: "glyphicon-chevron-down",
@@ -338,7 +349,6 @@ Grid.defaults = {
338349
left: "text-left",
339350
pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer
340351
paginationButton: "button", // must be a unique class name or constellation of class names within the pagination
341-
342352
/**
343353
* CSS class to select the parent div which activates responsive mode.
344354
*
@@ -485,6 +495,7 @@ Grid.defaults = {
485495
footer: "<div id=\"{{ctx.id}}\" class=\"{{css.footer}}\"><div class=\"row\"><div class=\"col-sm-6\"><p class=\"{{css.pagination}}\"></p></div><div class=\"col-sm-6 infoBar\"><p class=\"{{css.infos}}\"></p></div></div></div>",
486496
header: "<div id=\"{{ctx.id}}\" class=\"{{css.header}}\"><div class=\"row\"><div class=\"col-sm-12 actionBar\"><p class=\"{{css.search}}\"></p><p class=\"{{css.actions}}\"></p></div></div></div>",
487497
headerCell: "<th data-column-id=\"{{ctx.column.id}}\" class=\"{{ctx.css}}\" style=\"{{ctx.style}}\"><a href=\"javascript:void(0);\" class=\"{{css.columnHeaderAnchor}} {{ctx.sortable}}\"><span class=\"{{css.columnHeaderText}}\">{{ctx.column.text}}</span>{{ctx.icon}}</a></th>",
498+
highlightResults: "<span class=\"{{css.highlightResults}}\">{{ctx.content}}</span>",
488499
icon: "<span class=\"{{css.icon}} {{ctx.iconCss}}\"></span>",
489500
infos: "<div class=\"{{css.infos}}\">{{lbl.infos}}</div>",
490501
loading: "<tr><td colspan=\"{{ctx.columns}}\" class=\"loading\">{{lbl.loading}}</td></tr>",

0 commit comments

Comments
 (0)