Create a bindable content-editable div with CSS styling and observe mutations for updating an observable property.

by whelkaholism

HTML

<div data-bind="contenteditable: Text"></div>
<hr/>
<xmp data-bind="text: Text"></xmp>

CSS

.cfaf-content-editable {
  padding: 1em;
  border: 1px dotted #909090;
  border-radius: 0.5em;
  outline: none;
}

.cfaf-content-editable:focus {
  border-color: #000000;
}

JavaScript

(function()
{
    ko.bindingHandlers.contenteditable = {
        init:
            function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
            {
                var obs = f_valueaccessor();

                el.classList.add('cfaf-content-editable');
                el.setAttribute('contenteditable', true);

                // Callback function to execute when mutations are observed
                const callback = function(mutations, observer) 
                {
                    obs.__contenteditable_updating = true;
                    try
                    {
                        obs(el.innerHTML);
                    }
                    finally
                    {
                        delete obs.__contenteditable_updating;
                    }
                }

                const observer = new MutationObserver(callback);
                observer.observe(el, { characterData: true, childList: true, subtree: true });

                ko.utils.domNodeDisposal.addDisposeCallback(el, function() { observer.disconnect(); });
            },
        update: function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext) 
        {
            var obs = f_valueaccessor();
            !obs.__contenteditable_updating && (el.innerHTML = ko.unwrap(f_valueaccessor()));
        },
    };
})();

var model = new function() 
{
  this.Text = ko.observable('XXX');
}

ko.applyBindings(model);