Vue Component Button is not being disabled dynamically - vue.js

I have a component command-button wrapping a Vuetify v-btn component.
command-button receives a property called disabled of type boolean, non-required and false by default.
command-button is being used inside another component called toolbar where inside a v-for loop I add the command-button using a configuration array of actions objects passed to toolbar as a property.
<command-button
v-for="(action, index) in actions"
:key="index"
:label="action.label"
:disabled="action.disabled"
#click="action.handler"
></command-button>
When I use my toolbar component in my view component like this:
<toolbar :actions="actions"></toolbar>
I declare the actions of the toolbar in the data of my view component, as follows:
data() {
return {
...
actions: [
{
label: "Delete",
handler: this.onDelete,
disabled: this.disable // This can be a computed or a data element
},
{
label: "Add",
handler: this.onAdd
}
],
...
},
The issue is that the command-button is not being disabled dynamically, no matter if I use a computed or a member in data. It only works if I use the literal true inside the actions configuration object. Making some debugging, the value received inside toolbar for the disabled attribute of actions element is undefined.

You can't reference a computed property in your data object as it won't be defined when the instance is created. You could add a watcher on this.disable and update the value of actions[0].disabled when it changes.

Related

Vue component updated (rerendered) without reason

I have a component which is being updated (rerendered) without reason when parent's data item is changed even though it has nothing to do with component in question.
This is a simple reproduction https://codesandbox.io/s/gracious-cannon-s6jgp?file=/public/index.html if anyone can shed some light on this. (clicking button will fire update event in component)
If I remove element whose class is changed by vue (or remove dynamic class) it works as expected.
Because at each render you define new objects for the something property:
<hello-world :something="[{prop: 'vvv'},{prop: 'rrr'}]"></hello-world>
By clicking the button, you update the global data. The root element is rerendered.
During render, a new array with new objects is created as assigned at the something property in your component. While the objects created at each render are equal, they are different (i.e. they map to a different memory point)
Your component finds that the property something changes its reference, so it re-renders.
If you create an items property in your data and you pass that reference as the prop, the component is not re-rendered again:
main.js:
data: {
active: false,
items: [{ prop: "vvv" }, { prop: "rrr" }]
},
index.html:
<hello-world :something="items"></hello-world>
Note that this behavior occurs because you are passing an array 8and it would be the same with an object. It would not happen with a constant variable of the primitive types (such as string, int, boolean, float), such as :something="'string'"

vuejs ref property has no effect when using with createElement

I've written a custom render function for a Vue Component, but when I set the "ref" property in the data object that is passed to the createElement function, nothing shows up in the $refs of the root vm (VueComponent)
Vue.component('sm-form-row', {
render: function (createElement) {
// Create the Row Div and append the columns
return createElement('div', {
class: {
'row': true
},
ref: 'some computed value'
});
}
});
What am I missing, the class is being applied correctly but the $refs keep showing empty.
The ref is beign applied and i made a fiddle to see it that it works.
But,if you want to add a reference to sm-form-row component then you have to add the ref attribute in the parent component.For example in parent component:
<sm-form-row ref="formRow" />
And in your parent component you can access it as:
this.$refs.formRow
Also you will be able to access the methods of the child component.For example if the child component has a method called myMethod you can access it in parent component like this:
this.$refs.formRow.myMethod

vuejs pass model from parent component to children component and allow mutation

Currently I have a vue-multiselect component which requires a v-model.
I want to wrap this component so that I can build one single-select component and one multi-select component.
While working on the single select component I encountered the following warning
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "model"
They are right but in my case I really need to change the value from the parent (like I replace my single-select code with the vue-multiselect code) component and I also do not want this warning.
Here is the code for my component:
Vue.component('single-select', {
props: {
model: {
required: true
}
}
template: '<multiselect\n' +
' v-model="model"\n' +
...>\n' +
...
'</multiselect>'
});
One solution would be to pass a function as a model parameter and return the field from the parent but I really hope for a better solution.
Vue has a shortcut for 2 way binding called .sync modifier.
How it works in your case:
add .sync when you pass model as prop
<single-select :model.sync="..."></single-select>
emit an update:model in the child's input event
Vue.component('single-select', {
props: {
model: {
required: true
}
},
template: `<multiselect :value="model" #input="$emit('update:model', $event)"> </multiselect>`
});
Just give the internal model reference a different name, and the in the Vue component's data function map it manually:
Vue.component('single-select', {
props: {
model: {
required: true
}
},
data: function() {
return {
singleSelectModel: this.model
};
}
template: '<multiselect v-model="singleSelectModel"></multiselect>';
});
This is of course, assuming that you do not want to mutate the parent data, but simply making a copy of model and giving the child component the freedom to change it whenever it wants.
If what you want is to also update the parent data from the child, you will have to look into emitting events from the child and listening in the parent.

How to define Vue Events on Render Method using createElement

I'm using ElementUi NavMenu, and render function with the createElement method to make the items of the menu just using JSON with titles and index of the menu, just HTML and JS files, not .vue files.
The menu is mounted, the submenus are shown when I click it, but the actions of the submenu (el-menu-item) does not work. I even try the attributes click, item-click, v-on: click when creating the-menu-item (the documentation of ElementUi tells that #click must be used, but this causes an error on createElement when the attributes are defined), but no one works, no error occurs, as if the method was not been declared.
Only onclick attribute works on the el-menu-item, but when I use it, the method of vue component is not called, and so I have to make a function outside of component (on a class for example), and when this function is called it performs a call to component method (I try $ emits) and an error occurs, because the method of component is not found.
How can I add #click (or similar) event on the el-menu-item inside render function of the component to call a method of the same component?
Documenation of NavMenu of ElementUI.
How I'm creating menu item:
createElement("el-menu-item",{
attrs:{
index:json[i].id,
click:json[i].onclick
}},
json[i].title
)
Actually, this is mentioned in Vue.js documentation.
See https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth .
e.g. https://codepen.io/jacobgoh101/pen/ypjGqw?editors=0010
Vue.component("test", {
render: function(createElement) {
return createElement(
"button",
{
on: {
click: function() {
alert('click');
}
}
},
"Header"
);
}
});

Passing data to Vue.js component

I am creating a component and want to pass two properties (item & brokerageID) to the component. Here is the HTML code:
{{brokerageID}}
<holiday-component v-bind:item="item" v-bind:brokerageID="brokerageID" testID="45" ></holiday-component>
Here is the code for 'holiday-component'
Vue.component('holiday-component', {
props: ['item',
'brokerageID',
'testID',
],
data () {
return {
holidaysData: [],
showHolidays: false,
}
},
methods: {
getHolidays(contactID) {
....
},
template: <div> {{testID}} {{item.contactName}} {{brokerageID}}
....
The 'item' property is getting passed to the component (item.contactName is displayed correctly in the component template. However, somehow, brokerageID (property of the Vue object) is not getting passed. This property exists which is confirmed as {{brokerageID}} used above the component in HTML displays value. But, within the component template, brokerageID is not available. Also, the testID property passed to the component is not displayed.
Could someone please advise, what is wrong in my implementation that I am unable to use brokerageID in my component?
See Vue's docs about prop naming https://v2.vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
In this instance, using v-bind:brokerage-id and v-bind:test-id should do the trick.