Search code examples
javascriptknockout.jscustom-bindingpagedown

Why isn't the PageDown Editor triggering Knockout value update when using the button bar?


I am having a problem getting the knockout model to update when a user uses the PageDown button bar to make changes to the editor text. Any typing, pasting, or cutting works fine but the button bar actions do not.

I have tried adding a hook for onPreviewRefresh to the editor but that never seems to fire.

Here is a Fiddle showing the issue. If you type test into the editor, test will show up in the preview section. However, if you type test into the editor and then use the menu bar to make test bold then the preview section does not see this update until you type another character.

This is the custom binding that I am using to initialize the PageDown editor:

var ME = {};
ME.MarkdownConverter = Markdown.getSanitizingConverter();
ME.MarkdownCounter = 0;

ko.bindingHandlers.markdownEditor = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        ++ME.MarkdownCounter;
        // Create the elements needed for a new PageDown editor.
        $(element).append($('<div id="wmd-button-bar-' + ME.MarkdownCounter + '" class="wmd-button-bar"></div>'));
        $(element).append($('<textarea id="wmd-input-' + ME.MarkdownCounter + '" class="wmd-input"></textarea>'));

        // Make sure the textarea is properly binded up so that the view model is updated.
        var newBindings = { textInput: valueAccessor };
        ko.applyBindingAccessorsToNode($('#wmd-input-' + ME.MarkdownCounter)[0], newBindings, viewModel);

        // Create the editor and apply to the new elements ensuring that we detect all
        // changes from the wmd-button-bar.
        var editor = new Markdown.Editor(ME.MarkdownConverter, "-" + ME.MarkdownCounter);
        editor.hooks.chain("onPreviewRefresh", function () {
            var value = valueAccessor();
            debugger;
            value($('#wmd-input-' + ME.MarkdownCounter).val());
        });
        editor.run();

        return { controlsDescendantBindings: true }; 
    }
};

Solution

  • The value update isn't triggered because the value is set programmatically, which doesn't trigger the textarea's change event.

    The onPreviewRefresh event is not triggered because there is no preview element. As a workaround you can add the element and hide it:

    var ME = {};
    ME.MarkdownConverter = Markdown.getSanitizingConverter();
    ME.MarkdownCounter = 0;
    
    ko.bindingHandlers.markdownText = {
        init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
            $(element).addClass('markdown-text');
        },
        update: function(element, valueAccessor, allBindings) {
            var value = valueAccessor();
            var valueUnwrapped = ko.unwrap(value);
            $(element).html(ME.MarkdownConverter.makeHtml(valueUnwrapped));
        }
    };
    
    ko.bindingHandlers.markdownEditor = {
        init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
            ++ME.MarkdownCounter;
            // Create the elements needed for a new PageDown editor.
            $(element).append($('<div id="wmd-button-bar-' + ME.MarkdownCounter + '" class="wmd-button-bar"></div>'));
            $(element).append($('<textarea id="wmd-input-' + ME.MarkdownCounter + '" class="wmd-input"></textarea>'));        
            // if no preview element is found, onPreviewRefresh is not triggered
            $(element).append('<div style="display: none" class="wmd-panel wmd-preview" id="wmd-preview-' + ME.MarkdownCounter + '"></div>');
    
            var $input = $('#wmd-input-' + ME.MarkdownCounter);
    
            // Make sure the textarea is properly binded up so that the view model is updated.
            var newBindings = { textInput: valueAccessor };
            ko.applyBindingAccessorsToNode($input[0], newBindings, viewModel);
    
            // Create the editor and apply to the new elements ensuring that we detect all
            // changes from the wmd-button-bar.
            var editor = new Markdown.Editor(ME.MarkdownConverter, "-" + ME.MarkdownCounter);
            editor.hooks.chain("onPreviewRefresh", function () {
                $input.change();
            });
            editor.run();
    
            return { controlsDescendantBindings: true }; 
        }
    };
    
    var vm = { noteText: ko.observable('') };
    ko.applyBindings(vm);
    

    See http://jsfiddle.net/vaako62z/3/