简体   繁体   English

如何将CodeMirror集成到KnockoutJS中?

[英]How to integrate CodeMirror into KnockoutJS?

I would like to integrate the CodeMirror JavaScript editor into KnockoutJS. 我想将CodeMirror JavaScript编辑器集成到KnockoutJS中。 I know there is also Ace, but it seems to me it would be easier with CodeMirror. 我知道还有Ace,但在我看来,使用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. 我已经为JQueryUI小部件和QTip集成了自定义绑定,但这些是我在Internet上找到的代码片段,然后我只需要修改非常小的部分。

Unfortunately, it seems I've reached my limits on Javascript so I'm turning to JavaScript Sith Masters here. 不幸的是,似乎我已经达到了我对Javascript的限制所以我在这里转向JavaScript Sith Masters。 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) HTML(我删除了textarea上已有的自定义绑定,这里没关系)

<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: 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. 目前,这不会产生JS错误,但会显示2个文本区域而不是1个。

So what should I do next ? 那我接下来该怎么办?

The code listed by Jalayn (or one in jsfiddle) doesn't update the observing variable also the editor doesnt show the value on load.. here is my updated code Jalayn(或jsfiddle中的一个)列出的代码不会更新观察变量,编辑器也不会显示加载时的值..这是我更新的代码

    ko.bindingHandlers.codemirror = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var options = valueAccessor();
            var editor = CodeMirror.fromTextArea(element, options);
            editor.on('change', function(cm) {
                allBindingsAccessor().value(cm.getValue());
            });
            element.editor = editor;
            if(allBindingsAccessor().value())
                editor.setValue(allBindingsAccessor().value());
            editor.refresh();
            var wrapperElement = $(editor.getWrapperElement());

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                wrapperElement.remove();
            });
        },
        update: function (element, valueAccessor) {
            if(element.editor)
                element.editor.refresh();
        }
    };

Well, I finally managed to do it (see the updated fiddle ). 好吧,我终于设法做到了(看到更新的小提琴 )。

I quickly managed to set the initial value in the custom textarea. 我很快设法在自定义textarea中设置初始值。 But after that, the bound element was not being updated. 但在那之后,绑定元素没有被更新。

However CodeMirror's API allows you to register a callback method to the onChange event to be called whenever the content of the textarea is modified. 但是,只要修改了textarea的内容,CodeMirror的API就允许您向onChange事件注册一个回调方法。 So it was just a matter of implementing the callback that updates the value of the bound element. 所以只需要实现更新绑定元素值的回调。 This is done at the creation of the custom text area, in the options. 这是在选项中创建自定义文本区域时完成的。

Here is the custom binding: 这是自定义绑定:

ko.bindingHandlers.codemirror = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var options = $.extend(valueAccessor(), {
            onChange: function(cm) {
                allBindingsAccessor().value(cm.getValue());
            }
        });
        var editor = CodeMirror.fromTextArea(element, options);
        element.editor = editor;
        editor.setValue(allBindingsAccessor().value());
        editor.refresh();
        var wrapperElement = $(editor.getWrapperElement()); 

        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            wrapperElement.remove();
        });
    }
};

It may lack some features maybe, but for what I need it works perfectly. 它可能缺少某些功能,但是对于我需要的功能,它可以完美运行。

Edit: Following Anders' remark, I've added the addDisposeCallback part which effectively destroys the DOM element produced by CodeMirror when the template is re-rendered. 编辑:按照Anders的说法,我添加了addDisposeCallback部分,它有效地破坏了重新渲染模板时CodeMirror生成的DOM元素。 Since everything CodeMirror produces is inside one node, it's just a matter of removing this node. 由于CodeMirror生成的所有内容都在一个节点内,因此只需删除此节点即可。

I was having an issue with the cursor being set to the first position. 我遇到了将光标设置为第一个位置的问题。 This fiddle fixes that and it also accepts CodeMirror options object as binding value, and the content is bound to an options.value observable (I find that less confusing, because that's actually the property name from where CM gets it's starting value ) 这个小提示修复了它并且它也接受CodeMirror options对象作为绑定值,并且内容绑定到options.value observable(我发现不那么混乱,因为这实际上是CM获取它的起始值的属性名称)

ko.bindingHandlers.codemirror = {
    init: function(element, valueAccessor) {
        var options = ko.unwrap(valueAccessor());
        element.editor = CodeMirror(element, ko.toJS(options));
        element.editor.on('change', function(cm) {
            options.value(cm.getValue());
        });

        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            var wrapper = element.editor.getWrapperElement();
            wrapper.parentNode.removeChild(wrapper);
        });
    },
    update: function(element, valueAccessor) {
        var value = ko.toJS(valueAccessor()).value;
        if (element.editor) {
            var cur = element.editor.getCursor();
            element.editor.setValue(value);
            element.editor.setCursor(cur);
            element.editor.refresh();
        }
    }
};

Sample HTML: 示例HTML:

<div data-bind="codemirror: {
    mode: 'javascript',
    value: text
}"></div>

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. 使用<div data-bind="codemirror:fileContent"></div>作为要附加到此代码镜像的目标,将创建一个新的codemirror实例,并从视图模型中传入选项(如果已设置)。

[ 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. [ edit ]我已经修改了codemirror绑定处理程序的更新方法来解包传递的valueAccessor,而且当observable更新时,该行不会触发更新方法 - 它现在可以像你期望的那样工作。

I'm trying to use the binding handler above, but I'm having an issue with no text being visible in the TextArea until I enter something (a space or any other character). 我正在尝试使用上面的绑定处理程序,但是我遇到了一个问题,在TextArea中没有文本可见,直到我输入内容(空格或任何其他字符)。

It's blank when loading is done, and as soon as I enter something in the area, all the bound data becomes visible. 加载完成后它是空白的,一旦我在区域中输入内容,所有绑定的数据都会变得可见。

One more question, if I bind a simple ko.observable() to the value of TextArea, everything works great, but if I replace this with one of the columns in an ko.observableArray, eg value: selectedProfile().QueryString, I'm getting the following error from the binding handler: 还有一个问题,如果我将一个简单的ko.observable()绑定到TextArea的值,一切都很好,但是如果我用ko.observableArray中的一个列替换它,例如value:selectedProfile()。QueryString,I从绑定处理程序中收到以下错误:

Line in question: editor.setValue(allBindingsAccessor().value()); 有问题的行:editor.setValue(allBindingsAccessor()。value()); Error: Uncaught TypeError: Property 'value' of object # is not a function 错误:未捕获的TypeError:对象#的属性“值”不是函数

Thoughts? 思考?

/LM / LM

I run into the same problem as Lars in one of the answers above ie codemirror didn't initially load data until first interaction. 我在上面的一个答案中遇到了与Lars相同的问题,即codemirror最初在第一次交互之前没有加载数据。 I resolved it by adding a flag to determine if the codemirror value changed by typing or programatcially and subscribing to the value. 我通过添加一个标志来解决它,以确定是否通过键入或编程并订阅该值来更改codemirror值。

ko.bindingHandlers.codemirror = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            var typed = false;
            var options = $.extend(valueAccessor(), {
                onChange: function (cm) {
                    typed = true;
                    allBindingsAccessor().value(cm.getValue());
                    typed = false;
                }
            });
            var editor = CodeMirror.fromTextArea(element, options);
            element.editor = editor;
            editor.setValue(allBindingsAccessor().value());
            editor.refresh();
            var wrapperElement = $(editor.getWrapperElement());
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                wrapperElement.remove();
            });

            allBindingsAccessor().value.subscribe(function (newValue) {
                if (!typed) {
                    editor.setValue(newValue);
                    editor.refresh();
                }
            });
        }
    };

I believe there would be a 'cleaner' method to achieve the same, but this seems fine for the time being. 我相信会有一种“更清洁”的方法来实现同样的目标,但这暂时还不错。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM