Reputation: 851
I am trying to embed a Codemirror instance as a Vue component, similar to what was accomplished in this project. However, instead of returning a template of the Codemirror instance inside of a parent
<div class="vue-codemirror" :class="{ merge }">
<!-- Textarea will be replaced by Codemirror instance. -->
<textarea ref="textarea" :name="name" :placeholder="placeholder" v-else>
</textarea>
</div>
I am simplying trying to return the Codemirror instance alone. The reason why I can't simply remove the parent div and have
<!-- Textarea will be replaced by Codemirror instance. -->
<textarea ref="textarea" :name="name" :placeholder="placeholder" v-else>
</textarea>
is because the method to replace the textarea, CodeMirror.fromTextArea()
, requires the textarea to have a parentNode. Otherwise, a null error will be encountered.
Luckily, there is a way to create a Codemirror instance without using a textarea at all, CodeMirror()
. This instance has a function getWrapperElement()
that returns the DOM node:
<div class="CodeMirror cm-s-default">
...
</div>
I want to output this specific DOM node relating to Codemirror instance using the Vue template
/render
function. The current way I'm creating the instance is by initializing it in the component data
object.
data: function () {
// Create a CodeMirror instance.
let cm = new CodeMirror(null, {
...
})
return {
// Define a CodeMirror instance for each CodeBlockView.
cm : cm,
...
}
},
Update 1: I have found one way to do this, albeit very hacker-ish. We use the createElement
function that is passed in with render and pass into the data object argument the innerHTML of DOM node we want to render. It seems to be working visually but the CodeMirror instance isn't editable.
render: function (createElement) {
return createElement('div', {
class: this.dom.classList.value,
domProps: {innerHTML: this.dom.innerHTML}
})
}
Update 2: However, this doesn't pass in the actual DOM node rather it only shallowly copies it; this presents a problem as it doesn't allow it to be editable nor have a CodeMirror
instance attached.
Upvotes: 3
Views: 839
Reputation: 1697
I test many ways and the most simple template I find was keeping the textarea, after that the component should receive options and value and emit the new value.
you can test the component here
I'm not a codeMirror expert, I import many things for keep the codemirror component more flexible, you can adjust the imports with your needs.
//component codeMirror.vue
<template>
<textarea ref="myCm"></textarea>
</template>
<script>
import CodeMirror from 'codemirror';
// language
import 'codemirror/mode/javascript/javascript';
// theme css
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/monokai.css';
// require active-line.js
import 'codemirror/addon/selection/active-line';
// styleSelectedText
import 'codemirror/addon/selection/mark-selection';
import 'codemirror/addon/search/searchcursor';
// hint
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/hint/javascript-hint';
// highlightSelectionMatches
import 'codemirror/addon/scroll/annotatescrollbar';
import 'codemirror/addon/search/matchesonscrollbar';
import 'codemirror/addon/search/match-highlighter';
// keyMap
import 'codemirror/mode/clike/clike';
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/comment/comment';
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/search/search';
import 'codemirror/keymap/sublime';
// foldGutter
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/fold/foldcode';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/indent-fold';
import 'codemirror/addon/fold/markdown-fold';
import 'codemirror/addon/fold/xml-fold';
export default {
name: 'codeMirror',
props: {
value: String, // give to the component a start value
options: Object, // give the codeMirror options
},
mounted() {
const myCodemirror = new CodeMirror.fromTextArea(this.$refs.myCm, this.options);
myCodemirror.setValue(this.value);
myCodemirror.on('change', (cm) => {
if (this.$emit) {
this.$emit('input', cm.getValue());
}
});
},
};
</script>
after that is easy to use this component for render codeMirror in multiple instances, readOnly, recovery de data, multiple templates, no limits. ex:
<template>
<div>
Read Only, default theme, value multiple line
<codeMirror :value="value0" :options="noChanges"></codeMirror>
Value Dynamic, theme monokai, other extra fancy things
<codeMirror :value="value" :options="monokai" @input="onCmCodeChange"></codeMirror>
Only for debug, shows the modifications made inside codemirror
{{ value }}
</div>
</template>
<script>
// @ is an alias to /src
import codeMirror from '@/components/codeMirror.vue';
export default {
name: 'pageEditor',
components: {
codeMirror,
},
data() {
return {
value0: `let choco: "bombon";
let type: 'caramel;
choco + ' ' + type;`,
value: 'let frankie= "It\'s alive"',
noChanges: {
theme: 'default',
readOnly: true,
tabSize: 2,
line: true,
lineNumbers: true,
},
monokai: {
tabSize: 2,
styleActiveLine: true,
lineNumbers: true,
line: true,
foldGutter: true,
styleSelectedText: true,
mode: 'text/javascript',
keyMap: 'sublime',
matchBrackets: true,
showCursorWhenSelecting: true,
theme: 'monokai',
extraKeys: { Ctrl: 'autocomplete' },
hintOptions: {
completeSingle: false,
},
},
};
},
methods: {
onCmCodeChange(newValue) {
this.value = newValue;
},
},
};
</script>
I hope this help
Upvotes: 1
Reputation: 2473
You cannot change the template anymore after the component has been created. It's not a property of the component but a parameter that is passed to the constructor as far as I know.
Hence, you would need to create the CodeMirror instance before the component is created and inject it.
However, I fail to see the problem with a wrapping component, could you explain the why?
Upvotes: 1