Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

knockout contentEditable binding

Tags:

knockout.js

I've written a custom binding handler that toggles whether or not an element is contentEditable. I also want any html bindings to update when the element's contents are edited, so it listens for input events and updates the html bindings if available.

ko.bindingHandlers.contentEditable = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        var value = ko.unwrap(valueAccessor());
        element.contentEditable = value;

        var $element = $(element);

        if (value) {
            var allBindings = allBindingsAccessor();
            var htmlBinding = allBindings.html;

            if (ko.isWriteableObservable(htmlBinding)) {
                $element.on("input", function (event) {
                    htmlBinding(element.innerHTML);
                });
            }
        } else {
            $element.off("input");
        }
    }
};

However, here is the problem:

  • The user types something into the element
  • An input event fires
  • The html binding is updated
  • The element's innerHTML is updated
  • The cursor position in the element goes back to the beginning

A jsfiddle says a thousand words... http://jsfiddle.net/93eEr/1/

I'm a bit stumped as to how exactly to handle this.

like image 808
enoshixi Avatar asked Oct 14 '13 22:10

enoshixi


People also ask

What is binding in knockout?

A binding context is an object that holds data that you can reference from your bindings. While applying bindings, Knockout automatically creates and manages a hierarchy of binding contexts. The root level of the hierarchy refers to the viewModel parameter you supplied to ko. applyBindings(viewModel) .

How do I use knockout js in HTML?

It is very easy to use KnockoutJS. Simply refer the JavaScript file using <script> tag in HTML pages. A page as in the following image will be displayed. Click on download link and you will get the latest knockout.


1 Answers

ko.bindingHandlers.htmlLazy = {
    update: function (element, valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        
        if (!element.isContentEditable) {
            element.innerHTML = value;
        }
    }
};
ko.bindingHandlers.contentEditable = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var value = ko.unwrap(valueAccessor()),
            htmlLazy = allBindingsAccessor().htmlLazy;
        
        $(element).on("input", function () {
            if (this.isContentEditable && ko.isWriteableObservable(htmlLazy)) {
                htmlLazy(this.innerHTML);
            }
        });
    },
    update: function (element, valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        
        element.contentEditable = value;
        
        if (!element.isContentEditable) {
            $(element).trigger("input");
        }
    }
};

var viewModel = {
    editable: ko.observable(false),
    content: ko.observable("<i>This</i> is the initial content!")
};

ko.applyBindings(viewModel);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<label>Editable: <input type="checkbox" data-bind="checked: editable"/></label>
<hr>
<div data-bind="contentEditable: editable, htmlLazy: content"></div>
<hr>
<pre data-bind="text: content"></pre>

do the trick with minimal change. See http://jsfiddle.net/93eEr/3/

You could call the binding handler htmlEditable, maybe that's better than calling it "lazy". Up to you.

Note that the "input" event does not really need to be unbound every time. It won't fire anyway when the element is not contenteditable.

like image 194
Tomalak Avatar answered Oct 20 '22 13:10

Tomalak