Last active
January 3, 2020 01:22
-
-
Save jkreitzman/09013da7560e11e71aac43432ec416df to your computer and use it in GitHub Desktop.
Knockout Brace/Ace Binding (TypeScript)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A knockout binding for brace/ace. | |
// Inspired by https://github.com/probonogeek/knockout-ace, but updated to the latest version of knockout and rewritten in TypeScript. | |
// To consume in a webpacked view model: | |
// import(/* webpackMode: "eager" */ "./knockout-brace.ts"); // eager ensures the module is included. | |
import ace from "brace"; | |
import "brace/ext/language_tools"; | |
import "brace/mode/css"; | |
import "brace/mode/javascript"; | |
import "brace/theme/clouds"; | |
import ko from "knockout"; | |
interface IDictionary<T> { | |
[key: string]: T; | |
} | |
const instancesById = {} as IDictionary<ace.Editor>; // needed for referencing instances during updates. | |
let initId = 0; | |
ko.bindingHandlers.ace = { | |
init(element: HTMLElement, valueAccessor: () => ko.Observable | any, allBindings: any, viewModel: any, bindingContext: ko.BindingContext) { | |
const options = allBindings.get("aceOptions") || {}; | |
const value = ko.unwrap(valueAccessor()) || ""; | |
// Ace attaches to the element by DOM id, so we need to make one for the element if it doesn't have one already. | |
if (!element.id) { | |
element.id = "knockout-ace-" + initId; | |
initId += 1; | |
} | |
const editor = ace.edit(element.id); | |
editor.$blockScrolling = Infinity; | |
editor.setOptions({ | |
// Could be added to the binding options to allow binding-based config. | |
enableBasicAutocompletion: false, | |
enableLiveAutocompletion: true, | |
enableSnippets: false, | |
highlightActiveLine: false, | |
showPrintMargin: false, | |
useSoftTabs: false | |
}); | |
// Could require/import all themes/modes to allow dynamic theming. | |
if (options.theme) { editor.setTheme("ace/theme/" + options.theme); } | |
if (options.mode) { editor.getSession().setMode("ace/mode/" + options.mode); } | |
if (options.enabled) { | |
// Auto-update | |
if (ko.isObservable(options.enabled)) { | |
(options.enabled as ko.Observable<boolean>).subscribe(newVal => { | |
editor.setReadOnly(!newVal); | |
}); | |
} | |
editor.setReadOnly(!ko.unwrap(options.enabled)); | |
} | |
editor.session.setValue(value); | |
editor.gotoLine(0); | |
editor.getSession().on("change", _ => { | |
valueAccessor()(editor.getValue()); | |
}); | |
instancesById[element.id] = editor; | |
// destroy the editor instance when the element is removed | |
ko.utils.domNodeDisposal.addDisposeCallback(element, _ => { | |
editor.destroy(); | |
delete instancesById[element.id]; | |
}); | |
}, | |
update(element: HTMLElement, valueAccessor: () => ko.Observable | any, allBindings: any, viewModel: any, bindingContext: ko.BindingContext) { | |
const value = ko.unwrap(valueAccessor()) || ""; | |
const id = element.id; | |
// handle programmatic updates to the observable | |
// also makes sure it doesn't update it if it's the same. | |
// otherwise, it will reload the instance, causing the cursor to jump. | |
if (id !== undefined && id !== "" && instancesById.hasOwnProperty(id)) { | |
const editor = instancesById[id]; | |
const content = editor.getValue(); | |
if (content !== value) { | |
editor.$blockScrolling = Infinity; | |
editor.session.setValue(value); | |
editor.gotoLine(0); | |
} | |
} | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Sample of how to implement this binding --> | |
<!-- | |
"css" should exist on your view model and be an observable. | |
In this example, "css" contains your CSS code. | |
"cssEnabled" is also observable and would enable/disable the control. | |
--> | |
<div data-bind="ace: css, aceOptions: { mode: 'css', theme: 'clouds', enabled: cssEnabled }"></div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment