Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple Ace editor Vue component that you can include in your code base and then use as the ace-editor element.

To use the Ace editor Vue component you must:

  • Include Vue.
  • Include Ace.
  • Include the ace-editor-vue-component.js file.
  • Create a Vue container element (eg. <div id="myVue">...</div>).
  • Create an ace editor element within the Vue container (eg. <ace-editor ...></ace-editor>).
  • In JS make the element into a Vue (eg. new Vue({ el: '#myVue' ... })).

Doing that should do the trick. Example HTML code can be found in example.html and also in https://codepen.io/cwestify/full/ajKeKX/. This solution is written up here: http://cwestblog.com/2018/08/04/ace-editor-vue-component/

Events

The following events can be defined:

  • blur: v-on:blur="…"
  • change: v-on:change="…"
  • changeAnnotation: v-on:changeAnnotation="…" or v-on:change-annotation="…"
  • changeSelectionStyle: v-on:changeSelectionStyle="…" or v-on:change-selection-style="…"
  • changeSession: v-on:changeSession="…" or v-on:change-session="…"
  • copy: v-on:copy="…"
  • error: v-on:error="…"
  • errorsRemoved: v-on:errorsRemoved="…" or v-on:errors-removed="…"
  • focus: v-on:focus="…"
  • input: v-on:input="…"
  • keydown: v-on:keydown="…"
  • keypress: v-on:keypress="…"
  • keyup: v-on:keyup="…"
  • paste: v-on:paste="…"
  • warning: v-on:warning="…"
  • warningsRemoved: v-on:warningsRemoved="…" or v-on:warnings-removed="…"
/* START: <ace-editor> Vue component */
(function () {
var PROPS = {
selectionStyle: {},
highlightActiveLine: { f: toBool },
highlightSelectedWord: { f: toBool },
readOnly: { f: toBool },
cursorStyle: {},
mergeUndoDeltas: { f: toBool },
behavioursEnabled: { f: toBool },
wrapBehavioursEnabled: { f: toBool },
autoScrollEditorIntoView: { f: toBool, v: false },
copyWithEmptySelection: { f: toBool },
useSoftTabs: { f: toBool, v: false },
navigateWithinSoftTabs: { f: toBool, v: false },
hScrollBarAlwaysVisible: { f: toBool },
vScrollBarAlwaysVisible: { f: toBool },
highlightGutterLine: { f: toBool },
animatedScroll: { f: toBool },
showInvisibles: { f: toBool },
showPrintMargin: { f: toBool },
printMarginColumn: { f: toNum, v: 80 },
// shortcut for showPrintMargin and printMarginColumn
printMargin: { f: function (x) { return toBool(x, true) && toNum(x); } }, // false|number
fadeFoldWidgets: { f: toBool },
showFoldWidgets: { f: toBool, v: true },
showLineNumbers: { f: toBool, v: true },
showGutter: { f: toBool, v: true },
displayIndentGuides: { f: toBool, v: true },
fontSize: {},
fontFamily: {},
minLines: { f: toNum },
maxLines: { f: toNum },
scrollPastEnd: { f: toBoolOrNum },
fixedWidthGutter: { f: toBool, v: false },
theme: { v: 'chrome' },
scrollSpeed: { f: toNum },
dragDelay: { f: toNum },
dragEnabled: { f: toBool, v: true },
focusTimeout: { f: toNum },
tooltipFollowsMouse: { f: toBool },
firstLineNumber: { f: toNum, v: 1 },
overwrite: { f: toBool },
newLineMode: {},
useWorker: { f: toBool },
tabSize: { f: toNum, v: 2 },
wrap: { f: toBoolOrNum },
foldStyle: { v: 'markbegin' },
mode: { v: 'javascript' },
value: {},
};
var EDITOR_EVENTS = ['blur', 'change', 'changeSelectionStyle', 'changeSession', 'copy', 'focus', 'paste'];
var INPUT_EVENTS = ['keydown', 'keypress', 'keyup'];
function toBool(value, opt_ignoreNum) {
var result = value;
if (result != null) {
(value + '').replace(
/^(?:|0|false|no|off|(1|true|yes|on))$/,
function(m, isTrue) {
result = (/01/.test(m) && opt_ignoreNum) ? result : !!isTrue;
}
);
}
return result;
}
function toNum(value) {
return (value == null || isNaN(+value)) ? value : +value;
}
function toBoolOrNum(value) {
var result = toBool(value, true);
return 'boolean' === typeof result ? result : toNum(value);
}
function emit(component, name, event) {
component.$emit(name.toLowerCase(), event);
if (name !== name.toLowerCase()) {
component.$emit(
name.replace(/[A-Z]+/g, function(m) { return ('-' + m).toLowerCase(); }),
event
);
}
}
// Defined for IE11 compatibility
function entries(obj) {
return Object.keys(obj).map(function(key) { return [key, obj[key]]; });
}
Vue.component('aceEditor', {
template: '<div ref="root"></div>',
props: Object.keys(PROPS),
data: function() {
return {
editor: null,
isShowingError: false,
isShowingWarning: false,
allowInputEvent: true,
// NOTE: "lastValue" is needed to prevent cursor from always going to
// the end after typing
lastValue: ''
};
},
methods: {
setOption: function(key, value) {
var func = PROPS[key].f;
value = /^(theme|mode)$/.test(key)
? 'ace/' + key + '/' + value
: func
? func(value)
: value;
this.editor.setOption(key, value);
}
},
watch: (function () {
var watch = {
value: function(value) {
if (this.lastValue !== value) {
this.allowInputEvent = false;
this.editor.setValue(value);
this.allowInputEvent = true;
}
}
};
return entries(PROPS).reduce(
function(watch, propPair) {
var propName = propPair[0];
if (propName !== 'value') {
watch[propName] = function (newValue) {
this.setOption(propName, newValue);
};
}
return watch;
},
watch
);
})(),
mounted: function() {
var self = this;
self.editor = window.ace.edit(self.$refs.root, { value: self.value });
entries(PROPS).forEach(
function(propPair) {
var propName = propPair[0],
prop = propPair[1],
value = self.$props[propName];
if (value !== undefined || prop.hasOwnProperty('v')) {
self.setOption(propName, value === undefined ? prop.v : value);
}
}
);
self.editor.on('change', function(e) {
self.lastValue = self.editor.getValue();
if (self.allowInputEvent) {
emit(self, 'input', self.lastValue);
}
});
INPUT_EVENTS.forEach(
function(eName) {
self.editor.textInput.getElement().addEventListener(
eName, function(e) { emit(self, eName, e); }
);
}
);
EDITOR_EVENTS.forEach(function(eName) {
self.editor.on(eName, function(e) { emit(self, eName, e); });
});
var session = self.editor.getSession();
session.on('changeAnnotation', function() {
var annotations = session.getAnnotations(),
errors = annotations.filter(function(a) { return a.type === 'error'; }),
warnings = annotations.filter(function(a) { return a.type === 'warning'; });
emit(self, 'changeAnnotation', {
type: 'changeAnnotation',
annotations: annotations,
errors: errors,
warnings: warnings
});
if (errors.length) {
emit(self, 'error', { type: 'error', annotations: errors });
}
else if (self.isShowingError) {
emit(self, 'errorsRemoved', { type: 'errorsRemoved' });
}
self.isShowingError = !!errors.length;
if (warnings.length) {
emit(self, 'warning', { type: 'warning', annotations: warnings });
}
else if (self.isShowingWarning) {
emit(self, 'warningsRemoved', { type: 'warningsRemoved' });
}
self.isShowingWarning = !!warnings.length;
});
}
});
})();
/* END: <ace-editor> Vue component */
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Ace Editor Vue Component</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js"></script>
<script src="ace-editor-vue-component.js"></script>
</head>
<body>
<h1>Ace Editor Vue Component</h1>
<p>The following is an ace editor vue component that updates a vue variable and will update a code element and a textarea when changed. Updating the textarea will also change the ace editor vue component.</p>
<div id="myVue">
<ace-editor v-model="code" min-lines="3" max-lines="30"></ace-editor>
<pre><code>{{ code }}</code></pre>
<textarea style="width: 100%;" rows="5" v-model="code"></textarea>
</div>
<script type="text/JavaScript">
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#myVue',
data: {
code: 'console.log([\n\t1,\n\t2,\n\t5\n]);'
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment