Knockout, contenteditable (and markdown)

2019-07-16 14:01发布

问题:

Working on a Knockout bindinghandler that is backed by a markdown string, and renders (using markdown.js and the built-in 'html' bindinghandler).

Works fine, but tried to add contenteditable behavior, and having trouble with the observable value not updating on blur, or updating the observable with only the text with all markdown formatting removed.

Any ideas or an alternate approach you can think of?

Full fiddle here: http://jsfiddle.net/ZdxAS/3/

Binding Handler:

ko.bindingHandlers["editable"] = {
    init: function (element, valueAccessor) {
        $(element).on('blur', function (event) {
            var observable = valueAccessor();
            observable($(this).text());
            return true;
        });
        $(element).on('focus', function (event) {
            var observable = ko.utils.unwrapObservable(valueAccessor());
            $(this).text(observable);
            return true;
        });
        return $(element).on('keydown', function (event) {
            var esc, observable, submit;
            esc = event.which === 27;
            element = event.target;
            if (esc) {
                document.execCommand("undo");
                element.blur();
                return true;
            } else {
                return true;
            }
        });
    },

    update: function (element, valueAccessor, allBindings, viewModel, context) {
        var accessor = function () {
            var text = ko.utils.unwrapObservable(valueAccessor());
            return markdown.toHTML(text);
        }
        return ko.bindingHandlers.html.update(element, accessor, allBindings, viewModel, context)
    }
};

回答1:

That's rather weird. I ran the tests in Chrome, and here is what I saw:

When using the contenteditable=true attribute on a pre tag, the content editor appears properly when the pre element is clicked on. However, when you then edit the content and by hitting enter a few times, it inserts html tags such as <br /> and <div><br/></div> rather than new line characters. When you then stop editing the pre element by clicking off it, you then see the raw HTML rather than actual line feeds.

And from the jQuery text() documentation (used by your blur event handler):

Due to variations in the HTML parsers in different browsers, the text returned may vary in newlines and other white space.

I'm no markdown expert, but it looks to me like markdown depends on whitespace - you get a big header if there is a "#" at the beginning of a line, but if the line feed before the "#" is missing, markdown won't give you a big header. So if your whitespace is messed up, as can be caused by the text() call interacting with the results of the contenteditable pre, this would cause the markdown to get messed up.

So what can you do instead? Set up click-to-edit with knockout. You can see how this works at http://adventuresindotnet.blogspot.com/2012/04/knockout-and-click-to-edit.html and http://knockoutjs.com/documentation/hasfocus-binding.html

Example fiddle at http://jsfiddle.net/tlarson/w93BR/

The basic idea is to have a read-only element and an editable element, and only one of them appears at a time depending on whether you are in edit mode or not.

<pre data-bind="text: text, visible: !editingText(), click: textClick"></pre>        
<textarea data-bind="value: text, valueUpdate: 'afterkeyup', 
          visible: editingText, hasfocus: editingText" type='text'"></textarea>