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