如何将CodeMirror集成到KnockoutJS中?

时间:2012-11-14 08:52:28

标签: javascript knockout.js codemirror

我想将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个文本区域而不是一个。

那我接下来该怎么办?

6 个答案:

答案 0 :(得分: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();
        }
    };

答案 1 :(得分:3)

好吧,我终于成功了(请参阅更新的fiddle)。

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

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

这是自定义绑定:

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生成的所有内容都在一个节点内,因此只需删除此节点即可。

答案 2 :(得分:3)

我遇到了将光标设置为第一个位置的问题。 Bo Personn's answer修复了它并且它也接受CodeMirror options.value对象作为绑定值,并且内容绑定到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(); } } }; 可观察对象(我发现不那么混乱,因为它实际上是CM获取它的属性名称This fiddle

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

示例HTML:

foreach ($this->getElements() as $element){
            if(is_object($element->getDecorator('label'))){
                $element->getDecorator('label')->setOption('escape', false);
            }
        }

答案 3 :(得分:2)

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

// 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实例,并在视图模型中设置后传入选项。

[编辑]我已经修改了codemirror绑定处理程序的update方法来解包传递的valueAccessor,而且当observable更新时,该行不会触发更新方法 - 它现在可以用作你会期待它。

答案 4 :(得分:1)

我正在尝试使用上面的绑定处理程序,但我遇到一个问题,在TextArea中没有文本可见,直到我输入内容(空格或任何其他字符)。

加载完成后它是空白的,一旦我在区域中输入内容,所有绑定数据都会变得可见。

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

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

思想?

/ LM

答案 5 :(得分: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();
                }
            });
        }
    };

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