So I am trying to integrate Inline Editing from CKEditor with Knockout.js. I am able to successfully load the CKEditor and knockout.js.
I just can't seem to get the ko.observable update the property:
<script type="text/javascript">
var viewModel = function () {
var self = this;
self.editorText = ko.observable('ABC');
self.testNewValue = function () {
console.log(this.editorText());
};
}
ko.applyBindings(new viewModel());
</script>
Here is the html:
<div id="editable" contenteditable="true" data-bind="html: editorText">
</div>
<div>
<input type="button" data-bind="click: testNewValue" value="test" />
</div>
The console.log result always shows "ABC" regardless if you updated it or not. Note: I also tried data-bind="text: editorText"
You have to write your custom binding handler in order for your observable property to be linked with an instance of CKEditor.
First, you could start from the custom binding found here. One of the posts contains a custom binding, though I'm not sure it works. You have to check. I copied it down here, credits do not go to me of course:
ko.bindingHandlers.ckEditor = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var txtBoxID = $(element).attr("id");
var options = allBindingsAccessor().richTextOptions || {};
options.toolbar_Full = [
['Source', '-', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor', '-', 'Bold', 'Italic', 'Underline', 'SpellChecker'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl'],
['Link', 'Unlink', 'Image', 'Table']
];
// handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
if (CKEDITOR.instances[txtBoxID]){
CKEDITOR.remove(CKEDITOR.instances[txtBoxID]);
}
});
$(element).ckeditor(options);
// wire up the blur event to ensure our observable is properly updated
CKEDITOR.instances[txtBoxID].focusManager.blur = function () {
var observable = valueAccessor();
observable($(element).val());
};
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
$(element).val(val);
}
}
A typical use then would be in the HTML:
<textarea id="txt_viewModelVariableName"
data-bind="ckEditor: viewModelVariableName"></textarea>
Secondly, you could check out the custom binding handler for TinyMCE initially written by Ryan Niemeyer and updated by other talented people. Maybe TinyMCE could work out for you instead of CKEditor ?
To answer your specific question, you would need to keep track of where an edit is coming from so that the update isn't triggered twice. When the observable is being updated not from the editor you don't want the sudden change in the editor to re-update the observable. Same idea when the editor updates the observable, you do not want the observable to notify the editor again. I used to booleans to keep track of them. The editor-agnostic code is below:
var isObservableChange = false;
var isEditorChange = false;
editor.change = function () {
if(!isObservableChange){
isEditorChange = true;
observable(editor.data);
isEditorChange = false;
}
};
observable.subscribe(function (newValue) {
if(!isEditorChange){
isObservableChange = true;
editor.data = observable();
isObservableChange = false;
}
});
I had a project where I was trying my best to get inline editing with CKEditor. I finally gave up and tried TinyMCE with the same type of code and the solution worked. The following example uses knockout 2.3.0, tinymce 4.0.8, and jquery 1.10.2. The jquery can be replaced with regular document id accessing, but I use jquery as a crutch for fast code. The binding code is as follows:
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.attr('id', 'wysiwyg_' + Date.now());
if (ko.isObservable(value)) {
var isSubscriberChange = false;
var isEditorChange = true;
$element.html(value());
var isEditorChange = false;
tinymce.init({
selector: '#' + $element.attr('id'),
inline: true,
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table contextmenu paste"
],
toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
setup: function (editor) {
editor.on('change', function () {
if (!isSubscriberChange) {
isEditorChange = true;
value($element.html());
isEditorChange = false;
}
});
}
});
value.subscribe(function (newValue) {
if (!isEditorChange) {
isSubscriberChange = true;
$element.html(newValue);
isSubscriberChange = false;
}
});
}
}
}
To use it just bind it to a div. like so
<div data-bind="wysiwyg: test"></div>
A working example can be found here http://jsfiddle.net/dhQk/2xjKc/ I hope this helps.
It looks like the CKEditor version works after all. I just had to use a different cdn. The link for that is http://jsfiddle.net/dhQk/CSwr6/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With