knockout contentEditable binding

In previous I have problem with bdo that resolve it by this code. also this code is usable for other contentEditable. so I suggest the following:

 ko.bindingHandlers.bdoValue =
 ko.bindingHandlers.contentEditable = {
      'init': function(element, valueAccessor) {
          var updateHandler = function() {
              var modelValue = valueAccessor(),
                  elementValue = element.innerHTML;
              modelValue(elementValue);
          };
          ko.utils.registerEventHandler(element, "keyup", updateHandler);
          ko.utils.registerEventHandler(element, "input", updateHandler);
      },
      'update': function(elem, valueAccessor) {
          var value = ko.utils.unwrap(valueAccessor())||"";
          var current = elem.innerHTML;
          if (value !== current) {
              elem.innerHTML = value;    
          }
      }
   };

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.


@Tomalak's answer is perfect, hence my upvote.

But for those arriving here, like myself:

  • looking for a one-and-done custom binding
  • that assumes the content is always editable
  • is ok with value:-esque updates (ie: on blur)

I suggest the following:

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

        function onBlur(){
            if (ko.isWriteableObservable(value)) {
                value(this.innerHTML);
            }
        };                

        element.innerHTML = value(); //set initial value
        element.contentEditable = true; //mark contentEditable true
        element.addEventListener('blur', onBlur); //add blur listener
    }
};

Tags:

Knockout.Js