vuejs2-ace-editor: accessing editor instance - vuejs2

I am using this Vue2 component for ACE editor:
https://github.com/chairuosen/vue2-ace-editor
This is how I add the component to my app
var app = new Vue({
el: '#vue_app',
data: {
message: 'Hello Vue!',
editor_content: 'somecontent'
},
methods:{
editorInit:function (el) {
require('brace/mode/json');
require('brace/theme/tomorrow');
}
},
components: {
editor:require('vue2-ace-editor')
}
});
And when I put this into my HTML everything works correctly:
<editor v-model="editor_content" #init="editorInit();" lang="json" theme="tomorrow" width="500" height="100"></editor>
However the editor renders with a gutter which I don't need, so I want to access the editor instance to tweak some properties.
The source code for this component says it emits an event on mount:
mounted: function () {
var vm = this;
var lang = this.lang||'text';
var theme = this.theme||'chrome';
require('brace/ext/emmet');
var editor = vm.editor = ace.edit(this.$el);
this.$emit('init',editor);
editor.$blockScrolling = Infinity;
editor.setOption("enableEmmet", true);
editor.getSession().setMode('ace/mode/'+lang);
editor.setTheme('ace/theme/'+theme);
editor.setValue(this.value,1);
editor.on('change',function () {
var content = editor.getValue();
vm.$emit('input',content);
vm.contentBackup = content;
});
}
How and where do I catch this event and access the editor object?

You are already listening to the init event and calling the editorInit method. However, you need to pass the data being emitted by the <editor> component.
You can either do that explicitly by using $event:
<editor v-model="editor_content" #init="editorInit($event)" ...></editor>
Or implicitly by simply providing the method name as the event handler:
<editor v-model="editor_content" #init="editorInit" ...></editor>
Then, in your editorInit method, the param being passed in will be the instance of the editor. And I believe you can specify to not render the gutter like so:
editorInit:function (editor) {
editor.renderer.setShowGutter(false)
require('brace/mode/json');
require('brace/theme/tomorrow');
}

Related

How to avoid using Vue.set() within external js class files preserving reactivity for nested object properties?

I have this component:
<template>
<div class="simple-editor">
{{editor.view.toolbarManager.buttons}}
<component
v-for="(button, name) in editor.view.toolbarManager.buttons"
:is="button.component"
:options="button.options"
:key="name"
></component></div>
//.......................
I am trying to use editor.view.toolbarManager.buttons within v-for loop. Vue devtools shows me (for all 3 cases bellow) that editor.view.toolbarManager.buttons is an Object and contains 4 properties which contains another object.
<script>
export default {
data: function() {
return {
editor: new Editor({
doc: this.value,
init: this.init,
}),
}
},
editor.view.toolbarManager.buttons is filling in within subclasses of Editor() class with dynamically imported scripts like this:
props.plugins.forEach(plugin => {
this.plugins[plugin] = import(/* webpackMode: "eager" */ '../plugin/' + plugin);
});
I fill in editor.view.toolbarManager.buttons like this:
// case 1: works fine as expected
Vue.set(this.buttons, name, {
component,
options,
});
/* case 2: loses vue reactivity
var button = {};
button[name] = {
component,
options,
};
Object.assign(this.buttons, button);
*/
/* case 3: loses vue reactivity
this.buttons[name] = {
component,
options,
};
*/
The issue is next: when I try to render {{editor.view.toolbarManager.buttons}} within template I see empty object for cases 2 and 3 like this:
{}
which means vue reactivity is broken. Editor() is external class and I don't want to tie it to Vue. Vue reactivity is fine for within external classes for arrays because of using splice/push methods. Does exist a similar methods for object properties with preserving Vue reactivity?
Oh! I have messed up with Object.assign(). This is a right using of Object.assign() instead of Vue.set():
var button = {};
button[name] = {
component,
options,
};
this.buttons = Object.assign({}, this.buttons, button);
This works fine for me. And documentation is here https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects :
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

How to access data from the SVG map with Vue.js

I use SVG map of Austria. Each province has "title" attribute which contains a name of the province.
[full example available in the snippet below]
Outside the map in my HTML I have a paragraph, where I want to display the name of the province that was clicked by the user.
{{ province }}
How can I achieve that?
Here is my snippet with my code:
https://mdbootstrap.com/snippets/jquery/marektchas/500840?view=project
If you can change the svg part you can add a click event on each path:
<path #click="setProvince('Burgenland')" id="AT-1" title="Burgenland" class="land" d="..." />
And add a method in your script:
methods: {
setProvince (title) {
this.province = title
}
}
Updated answer
If you have a lot of provinces, you can add the click event on mounted by selecting all the <path> selectors (or class name or any selector you find relevant for your case):
new Vue({
el: '#app',
data: {
province: null
},
mounted () {
this.addClickHandler()
},
methods: {
setProvince (title) {
this.province = title
},
addClickHandler () {
let paths = this.$el.querySelectorAll('path')
paths.forEach(el => {
let title = el.attributes.title.value
el.addEventListener('click', () => {
this.setProvince(title)
})
})
}
}
});
There's no need of #click in template anymore this way.
Live example here
You need to learn about event delegation. You should be able to attach an event handler to the svg root and get events for any element inside the svg
https://recursive.codes/blog/post/34
https://jqfundamentals.com/chapter/events

How to pass an array of objects to child component in VueJS 2.x

I am trying to send an array containing arrays which in turn contains objects to one component from another, but the content from the array seems to be empty in the child component.
I have tried sending the data as a String using JSON.Stringify() and also as an array
My parent component:
data: function(){
return{
myLineItems : []
}
},
created(){
this.CreateLineItems();
},
methods:{
CreateLineItems(){
let myArrayData = [[{"title":"Title1","value":2768.88}],[{"title":"Title2","value":9}],[{"title":"Title3","value":53.61},{"title":"Title4","value":888.77},{"title":"Title5","value":1206.11},{"title":"Title6","value":162.5}]]
this.myLineItems = myArrayData;
}
}
My parent component's template:
/*
template: `<div><InvoiceChart v-bind:lineItems="myLineItems"></InvoiceChart></div>`
My child component:
const ChildComponent= {
props: {
lineItems: {
type: Array
}
},
mounted() {
console.log(this.lineItems);
}
};
The parent component is created as so (inside a method of our main component):
var ComponentClass = Vue.extend(InvoiceDetails);
var instance = new ComponentClass({
propsData: { invoiceid: invoiceId }
});
instance.$mount();
var elem = this.$refs['details-' + invoiceId];
elem[0].innerHTML = "";
elem[0].appendChild(instance.$el);
If I try to do a console.log(this) inside the childcomponent, I can see the correct array data exist on the lineItems property..but i can't seem to access it.
I have just started using VueJS so I haven't quite gotten a hang of the dataflow here yet, though I've tried reading the documentation as well as similar cases here on stackoverflow to no avail.
Expected result: using this.lineItems should be a populated array of my values sent from the parent.
Actual results: this.lineItems is an empty Array
Edit: The problem seemed to be related to how I created my parent component:
var ComponentClass = Vue.extend(InvoiceDetails);
var instance = new ComponentClass({
propsData: { invoiceid: invoiceId }
});
instance.$mount();
var elem = this.$refs['details-' + invoiceId];
elem[0].innerHTML = "";
elem[0].appendChild(instance.$el);
Changing this to a regular custom vue component fixed the issue
Code - https://codesandbox.io/s/znl2yy478p
You can print your object through function JSON.stringify() - in this case all functions will be omitted and only values will be printed.
Everything looks good in your code.
The issue is the property is not correctly getting passed down, and the default property is being used.
Update the way you instantiate the top level component.
Try as below =>
const ChildComponent= {
props: {
lineItems: {
type: Array
}
},
mounted() {
console.log(this.lineItems);
}
};

Testing a ReactiveVar in Meteor

I am trying to test a event in a Template. Within the click event, there is a Reactive Var that gets set.
The test is blocked by the Reactive Var.
Any ideas on how I stub or mock a reactive var in a template? I tried adding it to the template (template.reactiveVar = "whatever"), but no go, as I don't think it's writeable.
The ?? indicate where I am wondering what to put.
it('sets a reactive var when a button is clicked', function(){
Template.button.fireEvent('click ', {
context: { some: 'values'},
event: {'target': {'text': 'randomText' } },
templateInstance:new Template('upload', function(e) {
this.reactiveVar = new ReactiveVar("not clicked)
})
});
chai.assert.equal(Template.reactiveVar, "clicked")});
// The actual template is below..
Template.button.events{(
'click button': function(evt, template){
var text = evt.target.text
template.reactiveVar.set(text)
}
});
The result is...
TypeError: Cannot read property 'set' of undefined
Any ideas?
I suspect the event is actually firing and getting through to your event handler before the callback that creates the reactive var has run.
You should create the reactive var when you first create the template independent of your test:
Template.button.onCreated(function(){
this.reactiveVar = new ReactiveVar("not clicked");
});

Dojo Exception on hiding a dijit.Dialog

I have a Dialog with a form inside. The following code is just an example of what I'm trying to do. When you close a dijit.Dialog, if you dont't destroy recursively his children, you just can't reopen it (with the same id).
If you don't want to destroy your widget you can do something like that :
var createDialog = function(){
try{
// try to show the hidden dialog
var dlg = dijit.byId('yourDialogId');
dlg.show();
} catch (err) {
// create the dialog
var btnClose = new dijit.form.Button({
label:'Close',
onClick: function(){
dialog.hide();
}
}, document.createElement("button"));
var dialog = new dijit.Dialog({
id:'yourDialogId',
title:'yourTitle',
content:btnClose
});
dialog.show();
}
}
I hope this can help but with this code the error thrown is :
exception in animation handler for: onEnd (_base/fx.js:153)
Type Error: Cannot call method 'callback' of undefined (_base/fx.js:154)
I have to say I'm a little lost with this one ! It is driving me crazy ^^
PS : sorry for my "French" English ^^
I'll introduce you to your new best friend: dojo.hitch()
This allows you to bind your onClick function to the context in which it was created. Chances are, when you push the button in your code, it is calling your .show() .hide() form the context of the global window. var dlg was bound to your createDialog function, so it's insides are not visible to the global window, so the global window sees this as undefined.
Here's an example of what I changed to your code:
var createDialog = function(){
// try to show the hidden dialog
var dlg = dijit.byId('yourDialogId');
dlg.show();
// create the dialog
var btnClose = new dijit.form.Button({
label:'Close',
onClick: function(){
dojo.hitch(this, dlg.hide());
}
}, document.createElement("button"));
dlg.domNode.appendChild(btnClose.domNode);
var btnShow = new dijit.form.Button({
label : 'Open',
onClick : function() {
dojo.hitch(this, dlg.show());
}
}, document.createElement("Button"));
dojo.body().appendChild(btnShow.domNode);
};
dojo.ready(function() {
createDialog();
});
Note the use of dojo.hitch() to bind any future calls or clicks of the various buttons to the context in which the dlg was created, forever granting the button's onclick method access to the inside of the createDialog function, where var dlg exists.
hi if i understand correctly, you didn't need to destroy dijit.Dialog every time. E.g.:
HTML: define simple button:
<button id="buttonTwo" dojotype="dijit.form.Button" onclick="showDialog();" type="button">
Show me!
</button>
Javascript:
// required 'namespaces'
dojo.require("dijit.form.Button");
dojo.require("dijit.Dialog");
// creating dialog
var secondDlg;
dojo.addOnLoad(function () {
// define dialog content
var content = new dijit.form.Button({
label: 'close',
onClick: function () {
dijit.byId('formDialog').hide();
}
});
// create the dialog:
secondDlg = new dijit.Dialog({
id: 'formDialog',
title: "Programatic Dialog Creation",
style: "width: 300px",
content: content
});
});
function showDialog() {
secondDlg.show();
}
See Example and reed about dijit.dialog