Search code examples
javascriptknockout.jscodemirror

How to integrate CodeMirror into KnockoutJS?


I would like to integrate the CodeMirror JavaScript editor into KnockoutJS. I know there is also Ace, but it seems to me it would be easier with CodeMirror.

I already integrated custom bindings for JQueryUI widgets and QTip but these were pieces of code I found on the Internet and I then only needed to modify very small parts.

Unfortunately, it seems I've reached my limits on Javascript so I'm turning to JavaScript Sith Masters here. I don't necessarily want the whole thing written for me, pointers, and advice on how to continue would be of great help.

The piece of code I have:

The HTML (I removed custom bindings I already have on the textarea, they don't matter here)

<body>
    <textarea id="code" cols="60" rows="8" 
              data-bind="value: condition, 
              tooltip: 'Enter the conditions', 
              codemirror: { 'lineNumbers': true, 'matchBrackets': true, 'mode': 'text/typescript' }"></textarea>
</body>

The start of my custom binding handler for CodeMirror:

ko.bindingHandlers.codemirror = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var options = valueAccessor() || {};
        var editor = CodeMirror.fromTextArea($(element)[0], options);
    }
};

At the moment, this does not produce JS errors but 2 text areas are displayed instead of one.

So what should I do next ?


Solution

  • The solutions posted before seem a bit out of date and wouldn't work for me so I have rewritten them in a form that works:

    // Example view model with observable.
    var viewModel = {
        fileContent: ko.observable(''),
        options: {
            mode:  "markdown",
            lineNumbers: true
        }
    };
    
    // Knockout codemirror binding handler
    ko.bindingHandlers.codemirror = {
        init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    
            var options = viewModel.options || {};
            options.value = ko.unwrap(valueAccessor());
            var editor = CodeMirror(element, options);
    
            editor.on('change', function(cm) {
                var value = valueAccessor();
                value(cm.getValue());
            });
    
            element.editor = editor;
        },
        update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
            var observedValue = ko.unwrap(valueAccessor());
            if (element.editor) {
                element.editor.setValue(observedValue);
                element.editor.refresh();
            }
        }
    };
    
    ko.applyBindings(viewModel);
    

    With <div data-bind="codemirror:fileContent"></div> as the target for code mirror to attach to this will create a new codemirror instance and pass in options from the view model if they have been set.

    [edit] I have amended the update method of the codemirror binding handler to unwrap the passed valueAccessor, without that line knockout would not fire the update method when the observable is updated - it now works as you would expect it to.