8

我想将CodeMirror JavaScript 编辑器集成到 KnockoutJS 中。我知道还有 Ace,但在我看来,使用 CodeMirror 会更容易。

我已经为 JQueryUI 小部件和 QTip 集成了自定义绑定,但这些是我在 Internet 上找到的代码片段,然后我只需要修改非常小的部分。

不幸的是,我似乎已经达到了 Javascript 的极限,所以我在这里转向 JavaScript Sith Masters。我不一定想要为我写的全部内容,关于如何继续的指针和建议会有很大帮助。

我有一段代码:

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>

我的 CodeMirror 自定义绑定处理程序的开始:

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

目前,这不会产生 JS 错误,但会显示 2 个文本区域而不是 1 个。

那么接下来我该怎么做呢?

4

6 回答 6

4

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();
        }
    };
于 2013-07-05T12:55:38.590 回答
4

之前发布的解决方案似乎有点过时了,对我不起作用,所以我以一种有效的形式重写了它们:

// 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);

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

[编辑] 我已经修改了 codemirror 绑定处理程序的更新方法来解开传递的 valueAccessor,如果没有该行敲除将不会在更新 observable 时触发更新方法 - 它现在可以按照您的预期工作。

于 2015-11-28T00:19:05.500 回答
4

我在将光标设置为第一个位置时遇到问题。这个小提琴解决了这个问题,它还接受 CodeMirroroptions对象作为绑定值,并且内容绑定到一个options.valueobservable(我发现这不那么令人困惑,因为这实际上是 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();
        }
    }
};

示例 HTML:

<div data-bind="codemirror: {
    mode: 'javascript',
    value: text
}"></div>
于 2016-05-28T00:56:50.313 回答
3

好吧,我终于设法做到了(请参阅更新的小提琴)。

我很快设法在自定义文本区域中设置了初始值。但在那之后,绑定的元素没有被更新。

然而,CodeMirror 的 API 允许您向onChange事件注册一个回调方法,以便在修改 textarea 的内容时调用。因此,只需实现更新绑定元素值的回调即可。这是在选项中创建自定义文本区域时完成的。

这是自定义绑定:

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();
        });
    }
};

它可能缺少一些功能,但是对于我需要的东西,它可以完美地工作。

编辑:根据 Anders 的评论,我添加了在addDisposeCallback重新渲染模板时有效地破坏 CodeMirror 生成的 DOM 元素的部分。由于 CodeMirror 生成的所有内容都在一个节点内,因此只需删除该节点即可。

于 2012-11-14T11:51:51.847 回答
1

我正在尝试使用上面的绑定处理程序,但是在输入某些内容(空格或任何其他字符)之前,我遇到了 TextArea 中看不到任何文本的问题。

加载完成后它是空白的,只要我在该区域输入内容,所有绑定的数据就会变得可见。

还有一个问题,如果我将一个简单的 ko.observable() 绑定到 TextArea 的值,一切正常,但如果我用 ko.observableArray 中的一列替换它,例如 value: selectedProfile().QueryString,我'从绑定处理程序中得到以下错误:

有问题的行: editor.setValue(allBindingsAccessor().value()); 错误:未捕获的类型错误:对象# 的属性“值”不是函数

想法?

/LM

于 2012-11-30T12:47:50.873 回答
0

我在上面的一个答案中遇到了与 Lars 相同的问题,即 codemirror 在第一次交互之前最初没有加载数据。我通过添加一个标志来解决它,以确定 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();
                }
            });
        }
    };

我相信会有一种“更清洁”的方法来实现同样的效果,但这暂时看起来还不错。

于 2014-03-03T17:07:42.223 回答