enoshixi
enoshixi

Reputation: 309

knockout contentEditable binding

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:

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

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

Upvotes: 16

Views: 6452

Answers (3)

Hamid Taebi
Hamid Taebi

Reputation: 407

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;    
          }
      }
   };

Upvotes: 1

pim
pim

Reputation: 12587

@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
    }
};

Upvotes: 4

Tomalak
Tomalak

Reputation: 338326

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.

Upvotes: 23

Related Questions