Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Fun With Highlighting in KnockoutJS

| Comments

A question came up on the KO forums the other day about how to highlight part of some text that was being bound. This sounded to me like an interesting place to apply a custom binding. My idea to solve this issue was to use a highlightedText binding that looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
ko.bindingHandlers.highlightedText = {
    update: function(element, valueAccessor) {
        var options = valueAccessor();
        var value = ko.utils.unwrapObservable(options.text);
        var search = ko.utils.unwrapObservable(options.highlight);
        var css = ko.utils.unwrapObservable(options.css);
        if (options.sanitize) {
            value = $('<div/>').text(value).html(); //could do this or something similar to escape HTML before replacement, if there is a risk of HTML injection in this value
        }
        var replacement = '<span class="' + css + '">' + search + '</span>';
        element.innerHTML = value.replace(new RegExp(search, 'g'), replacement);
    }
};

You would use the binding like:

1
<div data-bind="highlightedText: { text: details, highlight: match, css: 'highlight' }"></div>

The code is not particularly clever and could likely be improved, but to me it at least illustrates how easy it is to write custom bindings that are useful and powerful. In this case, the actual text, the search string, and the css class to apply could all be observables. If any of them were to change, then the binding would automatically fire again and update the display.

After playing with this a bit, I thought that it would also be interesting if you could select some text with your mouse and have all of the matches for the text highlighted. I created another custom binding that sets an observable based on the currently selected text. So, this could be used to update the search string for the highlightedText binding and highlight any matches for the selected string. This selectedText binding looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//set a value based on the text that a user selects
ko.bindingHandlers.selectedText = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        ko.utils.registerEventHandler(element, 'mouseup', function() {
            var modelValue = valueAccessor();
            //get the selected text
            var selectedText = '';
            if (window.getSelection) {
                selectedText = window.getSelection();
            } else if (document.getSelection) {
                selectedText = document.getSelection();
            } else if (document.selection) {
                selectedText = document.selection.createRange().text;
            }
            //only change if something was selected
            if (selectedText.toString()) {
                if (ko.isWriteableObservable(modelValue)) {
                    modelValue(selectedText.toString());
                }
                else { //handle non-observables
                    var allBindings = allBindingsAccessor();
                    if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['selectedText']) allBindings['_ko_property_writers']['selectedText'](selectedText);
                }
            }
        });
    }
};

The binding works by handling the mouseup event of the element where we retrieve the selected text and properly sets the observable passed to the binding based on the text. It could be made a little less verbose, if there was an easier way to get the selected text across browsers and if it didn’t bother trying to support binding to non-observables.

I thought that maybe these bindings would be useful to someone or at least help to demonstrate how simple it is to create custom bindings that handle these types of behaviors.

Full sample here:

Link to sample on jsFiddle.net

Comments