Pregunta

So I'm trying to get knockout to play nicely with summernote and it's not really working. I realized this is because summernote uses a <div contenteditable> and not just an input field.

My binding is this:

ko.bindingHandlers.summernote = {
    init: function (element, valueAccessor) {
        var options = valueAccessor();
        $(element).summernote(options);
    }
};

Obviously knockout doesn't work too well with just a contenteditable, so what can I do?

¿Fue útil?

Solución 2

I actually began to think about this a bit more and decided to post my own solution since there wasn't really anything else on google. All I did was change the above binding to be like so:

ko.bindingHandlers.summernote = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var options = valueAccessor();
        var binding = ko.utils.unwrapObservable(allBindingsAccessor()).value;

        var updateObservable = function(e) {
            binding(e.currentTarget.innerHTML);
        };

        options.onkeydown = options.onkeyup = options.onfocus = options.onblur = updateObservable;

        $(element).summernote(options);
    }
};

Just watching the blur / focus / keypress / keyrelease events was sufficient to update the observables. It's not perfect, but it works.

Otros consejos

taking some other answers and making them work for the current version of summernote I've made the following:

Something to note: this bindinghandler accomodates inbound changes from the viewModel as well.

 ko.bindingHandlers.wysiwyg = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var value = ko.unwrap(valueAccessor());
        var allBindings = ko.unwrap(allBindingsAccessor())
        var optionsBinding = allBindings.wysiwygOptions || {};
        var $element = $(element);
        var options = $.extend({}, optionsBinding);

        var updateObservable = function (e) {
            valueAccessor($element.summernote('code'));
            return true;
        };

        options.callbacks = {};
        options.callbacks.onKeyup = options.callbacks.onFocus = options.callbacks.onBlur = updateObservable;

        $element.html(value).summernote(options);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var value = ko.unwrap(valueAccessor());
        $(element).summernote('code', value);
    }
};

A few options have changed and you don't have to investigate event dom data. Also I don't need to update on both keyDown and keyUp so my list of events are a bit different. And my binding handler isn't "summernote" in order to make this more flexible to be changed out in the future. it's just wysiwyg. YMMV

I'm using following two-way binding and it works for me...

Knockout binding (in CoffeeScript):

ko.bindingHandlers.summernote =
    init: (element, valueAccessor, allBindingsAccessor) ->
        options = valueAccessor()      
        options.toolbar = [    
            ['style', ['bold', 'italic', 'underline', 'clear']],            
            ['fontsize', ['style']],            
            ['para', ['ul', 'ol', 'paragraph']],
            ['table', ['table']],            
            ['misc', ['codeview']],
        ]

        options.onblur = () -> valueAccessor() $(element).code()                      
        $(element).summernote(options) 

    update: (element, valueAccessor, allBindingsAccessor) ->
        $(element).code(ko.utils.unwrapObservable(valueAccessor()))

Usage in HTML:

<textarea data-bind="summernote: Content"></textarea>

my solution is to listen to the onChange event

ko.bindingHandlers.summernote = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var value = ko.unwrap(valueAccessor());
        var $element = $(element);
        $element.html(value).summernote({
            onChange: function (contents) {
                valueAccessor()(contents);
            }
        });
    }
};

Edit

After v0.7.0, every callbacks should be wrapped by callbacks object.

ko.bindingHandlers.summernote = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var value = ko.unwrap(valueAccessor());
        var $element = $(element);
        $element.html(value).summernote({
            callbacks:{ // added for V0.7.0
                onChange: function (contents) {
                    valueAccessor()(contents);
                }
            }
        });
    }
};

A minor fix and change to the previous answers:

  1. There's a glitch that shows up for certain cases using the toolbar. To fix this the update has to be blocked when it occurs through the blur event.
  2. Also added the ability to override the options via "summerOptions" property on the element.

Here's my update:

ko.bindingHandlers.summernote = new function () {
    var isblur = false;

    this.init = function (element, valueAccessor, allBindings) {
        var value = valueAccessor();
        var options = $.extend(value, {
            height: 100,
            toolbar: [
                ["style", ["bold", "italic", "underline", "clear"]],
                ["fontstyle", ["style"]],
                ["fontsize", ["fontsize"]],
                ["lists", ["ul", "ol", "paragraph"]],
                ["links", ["link", "picture"]],
                ["misc", ["fullscreen", "codeview"]]
            ],
            onblur: function () {
                isblur = true;
                value($(element).code());
                isblur = false;
            }
        });
        $.extend(options, allBindings.get("summerOptions"));
        return $(element).summernote(options);
    };
    this.update = function (element, valueAccessor) {
        if (!isblur) {
            var value = valueAccessor();
            $(element).code(value());
        }
    };
};

And the usage:

<textarea data-bind="summernote: Content"></textarea>

Or with options override:

<textarea data-bind="summernote: Content, summerOptions: { height: 200 }"></textarea>
ko.bindingHandlers.summernote = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    var value = ko.unwrap(valueAccessor());
    var $element = $(element);
    $element.html(value).summernote({
         callbacks:{
        onChange: function (contents) {
            valueAccessor()(contents);
        }
   }
  });
 }
};

Use this solution, it works for me

ko.bindingHandlers.summernote = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var value = ko.unwrap(valueAccessor());                
            var allBindings = ko.unwrap(allBindingsAccessor())
            var optionsBinding = allBindings.summernoteOptions || {};
            var $element = $(element);
            var options = $.extend({}, optionsBinding);

            var updateObservable = function (e) {
                valueAccessor()($element.summernote('code'));
                return true;
            };

            options.callbacks = {};
            //options.callbacks.onFocus = options.callbacks.onBlur = updateObservable;

            $element.html(value).summernote(options);

            $(element).next().find('.note-editable').keyup(updateObservable);
            $(element).next().find('.note-editable').bind('DOMNodeInserted DOMNodeRemoved', updateObservable);
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var value = ko.unwrap(valueAccessor());
            if (value != $(element).summernote('code')) {
                $(element).summernote('code', value);
            }                
        }
    };
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top