I have created a custom binding for CodeMirror. Custom binding works with a simple string, but inside foreach
binding, it ceases to be initialized, although all the HTML and CSS is added.
Here's a working snippet:
var viewModel = {
options: {
mode: "text/x-csharp",
lineNumbers: true
},
//IT WORKS
fileContent: "public sealed class DictionaryAttribute : Attribute{}1",
//IT DOESN'T WORK
codes: ["public sealed class DictionaryAttribute : Attribute{}1"]
};
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;
}
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link href="https://codemirror.net/lib/codemirror.css" rel="stylesheet" />
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="https://codemirror.net/mode/clike/clike.js"></script>
<!-- This works -->
<div data-bind="codemirror: fileContent" style="width: 700px; height: 100px"></div>
<!-- This doesn't work -->
<div data-bind="foreach: codes">
<div data-bind="codemirror: $data" style="width: 700px; height: 100px"></div>
</div>
The problem is with var options = viewModel.options || {};
in your custom binding. viewModel
parameter refers to the current $data
in context, not the viewModel used in applyBindings
. It works for a simple string because in that case, viewModel
parameter is the main viewModel
object you're passing to applyBidnigs
. Inside foreach
, viewModel
will be each $data
in your array.
So, use the $root
property of bindingContext
parameter instead. Besides, viewModel
parameter is deprecated in Knockout 3.x:
Like this:
var options = bindingContext.$root.options || {};
Updated snippet:
var viewModel = {
options: {
mode: "text/x-csharp",
lineNumbers: true
},
//IT WORKS
fileContent: "public sealed class DictionaryAttribute : Attribute{}1",
//IT DOESN'T WORK
codes: ["public sealed class DictionaryAttribute : Attribute{}1"]
};
ko.bindingHandlers.codemirror = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var options = bindingContext.$root.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;
}
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link href="https://codemirror.net/lib/codemirror.css" rel="stylesheet" />
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="https://codemirror.net/mode/clike/clike.js"></script>
<div data-bind="codemirror: fileContent" style="width: 700px; height: 100px"></div>
<div data-bind="foreach: codes">
<div data-bind="codemirror: $data" style="width: 700px; height: 100px"></div>
</div>
The above code works in your case. But, the binding expects the top $root
object to have the options
property. Another way to do this would be to add a codeMirrorOptions
parameter to the binding and remove that dependency altogether.
var viewModel = {
options: {
mode: "text/x-csharp",
lineNumbers: true
},
//IT WORKS
fileContent: "public sealed class DictionaryAttribute : Attribute{}1",
//IT DOESN'T WORK
codes: ["public sealed class DictionaryAttribute : Attribute{}1"]
};
ko.bindingHandlers.codemirror = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// use allBindings
var options = ko.unwrap(allBindings().codeMirrorOptions) || {};
options.value = ko.unwrap(valueAccessor());
var editor = CodeMirror(element, options);
editor.on('change', function(cm) {
var value = valueAccessor();
value(cm.getValue());
});
element.editor = editor;
}
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link href="https://codemirror.net/lib/codemirror.css" rel="stylesheet" />
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="https://codemirror.net/mode/clike/clike.js"></script>
<div data-bind="codemirror: fileContent, codeMirrorOptions:options" style="width: 700px; height: 100px"></div>
<div data-bind="foreach: codes">
<div data-bind="codemirror: $data, codeMirrorOptions:$parent.options" style="width: 700px; height: 100px"></div>
</div>
In this case, the custom binding is independent of the viewModel. Even if your viewModel
isn't the $root
object, the custom binding will work.