Skip to content

Commit 961a8ad

Browse files
committed
Merge PR #617
2 parents 75effa0 + 593a2f2 commit 961a8ad

File tree

5 files changed

+91
-19
lines changed

5 files changed

+91
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* Functions in option `render` can now return a DOM node in addition to
2+
text. (#617)

docs/usage.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,9 @@ $(function() {
365365
<tr>
366366
<td valign="top"><code>render</code></td>
367367
<td valign="top">
368-
Custom rendering functions. Each function should accept two arguments: "data" and "escape" and return HTML (string) with a single root element.
368+
Custom rendering functions. Each function should accept two
369+
arguments: "data" and "escape" and return HTML (string or
370+
DOM element) with a single root element.
369371
The "escape" argument is a function that takes a string and escapes all special HTML characters.
370372
This is very important to use to prevent XSS vulnerabilities.
371373
<table width="100%">

src/selectize.js

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,10 +1060,10 @@ $.extend(Selectize.prototype, {
10601060
optgroup = '';
10611061
}
10621062
if (!groups.hasOwnProperty(optgroup)) {
1063-
groups[optgroup] = [];
1063+
groups[optgroup] = document.createDocumentFragment();
10641064
groups_order.push(optgroup);
10651065
}
1066-
groups[optgroup].push(option_html);
1066+
groups[optgroup].appendChild(option_html);
10671067
}
10681068
}
10691069

@@ -1077,23 +1077,26 @@ $.extend(Selectize.prototype, {
10771077
}
10781078

10791079
// render optgroup headers & join groups
1080-
html = [];
1080+
html = document.createDocumentFragment();
10811081
for (i = 0, n = groups_order.length; i < n; i++) {
10821082
optgroup = groups_order[i];
1083-
if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
1083+
if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) {
10841084
// render the optgroup header and options within it,
10851085
// then pass it to the wrapper template
1086-
html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
1087-
html_children += groups[optgroup].join('');
1088-
html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
1089-
html: html_children
1086+
html_children = document.createDocumentFragment();
1087+
html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup]));
1088+
html_children.appendChild(groups[optgroup]);
1089+
1090+
html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
1091+
html: domToString(html_children),
1092+
dom: html_children
10901093
})));
10911094
} else {
1092-
html.push(groups[optgroup].join(''));
1095+
html.appendChild(groups[optgroup]);
10931096
}
10941097
}
10951098

1096-
$dropdown_content.html(html.join(''));
1099+
$dropdown_content.html(html);
10971100

10981101
// highlight matching terms inline
10991102
if (self.settings.highlight && results.query.length && results.tokens.length) {
@@ -2044,26 +2047,26 @@ $.extend(Selectize.prototype, {
20442047
}
20452048

20462049
// render markup
2047-
html = self.settings.render[templateName].apply(this, [data, escape_html]);
2050+
html = $(self.settings.render[templateName].apply(this, [data, escape_html]));
20482051

20492052
// add mandatory attributes
20502053
if (templateName === 'option' || templateName === 'option_create') {
2051-
html = html.replace(regex_tag, '<$1 data-selectable');
2054+
html.attr('data-selectable', '');
20522055
}
2053-
if (templateName === 'optgroup') {
2056+
else if (templateName === 'optgroup') {
20542057
id = data[self.settings.optgroupValueField] || '';
2055-
html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
2058+
html.attr('data-group', id);
20562059
}
20572060
if (templateName === 'option' || templateName === 'item') {
2058-
html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
2061+
html.attr('data-value', value || '');
20592062
}
20602063

20612064
// update cache
20622065
if (cache) {
2063-
self.renderCache[templateName][value] = html;
2066+
self.renderCache[templateName][value] = html[0];
20642067
}
20652068

2066-
return html;
2069+
return html[0];
20672070
},
20682071

20692072
/**

src/utils.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,4 +322,12 @@ var autoGrow = function($input) {
322322

323323
$input.on('keydown keyup update blur', update);
324324
update();
325-
};
325+
};
326+
327+
var domToString = function(d) {
328+
var tmp = document.createElement('div');
329+
330+
tmp.appendChild(d.cloneNode(true));
331+
332+
return tmp.innerHTML;
333+
};

test/setup.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,63 @@
278278
});
279279
});
280280

281+
describe('<select> (custom string render)', function() {
282+
var test;
283+
284+
beforeEach(function() {
285+
test = setup_test('<select>' +
286+
'<option value="">Select an option...</option>' +
287+
'<option value="a">A</option>' +
288+
'</select>', {
289+
render: {
290+
option: function(item, escape) {
291+
return '<div class="option custom-option">' + escape(item.text) + '</div>'
292+
}
293+
}
294+
});
295+
});
296+
297+
it('should render the custom option element', function(done) {
298+
test.selectize.focus();
299+
300+
window.setTimeout(function() {
301+
expect(test.selectize.$dropdown.find('.custom-option').length).to.be.equal(1);
302+
done();
303+
}, 5);
304+
});
305+
});
306+
307+
describe('<select> (custom dom render)', function() {
308+
var test;
309+
310+
beforeEach(function() {
311+
test = setup_test('<select>' +
312+
'<option value="">Select an option...</option>' +
313+
'<option value="a">A</option>' +
314+
'</select>', {
315+
render: {
316+
option: function(item, escape) {
317+
var div = document.createElement('div');
318+
319+
div.className = 'option custom-option';
320+
div.innerHTML = escape(item.text);
321+
322+
return div;
323+
}
324+
}
325+
});
326+
});
327+
328+
it('should render the custom option element', function(done) {
329+
test.selectize.focus();
330+
331+
window.setTimeout(function() {
332+
expect(test.selectize.$dropdown_content.find('.custom-option').length).to.be.equal(1);
333+
done();
334+
}, 0);
335+
});
336+
});
337+
281338
});
282339

283340
})();

0 commit comments

Comments
 (0)