我想将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个文本区域而不是一个。
那我接下来该怎么办?
答案 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();
}
});
}
};
我相信会有一种“更清洁”的方法来实现同样的目标,但这暂时还不错。