Vue How to pass dynamic to value in router-link from prop - javascript

I made a component, that handles custom buttons. I wanted to change <a> tags to <router-link> and know I'm getting an error, because the router-link is rendering before the prop gets its value. I fixed it with an if statement, I'm looking for a prettier solution.
<template>
<input
v-if="type === 'submit'"
type="submit"
class="button"
:value="$slots.default[0].text"
:class="{'button--inactive': disabled}"
/>
<router-link
v-else-if="type === 'button' && href !== undefined"
class="button"
:class="{'button--inactive': disabled}"
:to="href"
>
<slot></slot>
</router-link>
</template>
<script>
export default {
name: 'Button',
props: {
href: {
type: String
},
type: {
type: String,
default: 'button',
validator: value => ['button', 'submit'].indexOf(value) !== -1
},
disabled: {
type: Boolean
}
}
}
</script>
Could anybody please help?

Didi you tried adding a default value to the href prop ?
props: {
href: {
type: String,
default: '#',
},
},

Related

Vue not updating same type of component on v-if argument change

I have a custom Toggle component, made from scratch in Vue.
<template>
<div :class="{Switch: true, disabled: disabled}" #click="toggle" :title="disabled ? disabledtitle : ''">
<div :class="{Background: true, active: isToggled}"></div>
<div :class="{Toggle: true, active: isToggled}"></div>
</div>
</template>
<script>
export default {
name: 'Toggle',
props: {
toggled: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
disabledtitle: {
type: String,
default: ''
}
},
data () {
return {
isToggled: false
}
},
methods: {
toggle () {
if(this.disabled)
return
this.isToggled = !this.isToggled
this.$emit('input', this.isToggled)
}
},
mounted () {
this.isToggled = this.toggled
}
}
</script>
...
It has multiple instances in an another component, like this:
<template>
<div v-if="category === 'Basic'" class="Basic">
<Toggle :toggled="config.isToggledThisOption" #input="updateConfig($event, 'isToggledThisOption')" />
</div>
<div v-else-if="category === 'Other'" class="Other">
<Toggle :toggled="config.isToggledOtherOption" #input="updateConfig($event, 'isToggledOtherOption')" />
</div>
</template>
...
There is also a menu, that switches between the categories (changes the category variable)
This works well, but the problem is, that the toggled prop does not update when switching between the categories.
I managed to track it down, and the problem is the following:
Whenever the value of the category changes, the <Toggle /> component does not update. It always stays the same, even in inspect element, no updates happen. mounted() also doesn't get executed again, only when the page is reloaded (and even then, not all <Toggle />s' mounted() function get executed).
Do I need to add some kind of id/identificator to the component, so it gets re-mounted/re-rendered/re-initiated?

Clear input VUE component data from parent

I'm a newbie of Vue, and I'm trying to simply clear the data of input component once I've submitted, but it seems I'm missing something, because since it's parent data is cleared, I still see the filled value of the input component.
Here is a living example.
I've set to the input child component v-model="title" from it's parent wrapper. Once I submit the data to the parent, I call addItem and in the end, I supposed to clear the data model just by clear it this.title = '', but I probably do something wrong on how to bind data from parent to child.
And above the code, starting from the parent component:
<template>
<form #submit="addItem" class="todo-insert">
<input-item
icon="create"
name="title"
placeholder="Add a ToVue item..."
v-model="title"
/>
<button-item tone="confirm" class="todo-insert__action">
Aggiungi
</button-item>
</form>
</template>
<script>
import ButtonItem from '#vue/Form/ButtonItem/ButtonItem.vue'
import InputItem from '#vue/Form/InputItem/InputItem.vue'
import uuid from 'uuid'
export default {
name: 'TodoInsert',
components: {
ButtonItem,
InputItem
},
data () {
return {
title: ''
}
},
methods: {
addItem (e) {
e.preventDefault()
const todo = {
id: uuid.v4(),
isComplete: false,
title: this.title
}
this.$emit('add-todo', todo)
this.title = ''
}
}
}
</script>
<style lang="scss" scoped src="./TodoList.scss"></style>
This is the child input component:
<template lang="html">
<label class="input">
<div v-if="label" class="input__label text-sans text-sans--label">
{{ label }}
</div>
<div class="input__row">
<input
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
<div v-if="placeholderLabel" class="input__placeholder text-sans text-sans--placeholder">
{{ placeholderLabel }}
</div>
<div v-if="icon" class="input__icon-area">
<icon-item
:name="icon"
/>
</div>
</div>
</label>
</template>
<script>
import IconItem from '../../IconItem/IconItem.vue'
export default {
name: 'InputItem',
props: {
autocomplete: {
type: String,
default: 'off'
},
icon: String,
label: String,
name: {
type: String,
default: 'input-text'
},
placeholder: String,
placeholderLabel: String
},
computed: {
hasPlaceholderLabel () {
return this.placeholderLabel ? 'input__field--placeholder-label' : ''
},
isDirty () {
// return this.$attrs.value ? 'input__field--dirty' : ''
return 'input__field--dirty'
}
},
methods: {
updateValue: function (value) {
this.$emit('input', value)
}
},
components: {
IconItem
}
}
</script>
<style lang="scss" src="./InputItem.scss"></style>
What am I missing?
Your child component is bound unidirectionally. It means that it can change the value, but does not receive any update from the parent component. To receive updates, you need to receive the property value in your child:
props: {
value: String
}
Then, you need to pass the value received to the input :
<input
:value="value"
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
Now the input should update when the parent component changes the value

Vue.Js, binding a value to a checkbox in a component

I'm making a component which is a wrapper around a checkbox (I've done similar with inputs of type 'text' and 'number') but I cannot get my passed in value to bind correctly.
My component is:
<template>
<div class="field">
<label :for="name" class="label">
{{ label }}
</label>
<div class="control">
<input :id="name" :name="name" type="checkbox" class="control" :checked="value" v-on="listeners" />
</div>
<p v-show="this.hasErrors" class="help has-text-danger">
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
</template>
<script>
export default {
name: 'check-edit',
props: {
value: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
errors: {
type: Array,
default: () => []
}
},
mounted () {
},
computed: {
listeners () {
return {
// Pass all component listeners directly to input
...this.$listeners,
// Override input listener to work with v-model
input: event => this.$emit('input', event.target.value)
}
},
hasErrors () {
return this.errors.length > 0
}
},
}
</script>
I've imported it globally; and am invoking it in another view by doing:
<check-edit name="ShowInCalendar" v-model="model.ShowInCalendar" label="Show in calendar?" :errors="this.errors.ShowInCalendar"></check-edit>
My model is in data and the property ShowInCalendar is a boolean and in my test case is true. So when I view the page the box is checked. Using the Vue tools in firefox I can see the model.ShowInCalendar is true, and the box is checked. However, when I click it the box remains checked and the value of ShowInCalendar changes to 'on', then changes thereafter do not change the value of ShowInCalendar.
I found this example here: https://jsfiddle.net/robertkern/oovb8ym7/ and have tried to implement a local data property for it but the result is not working.
The crux of what I'm trying to do is have the initial checkstate of the checkbox be that of ShowInCalendar (or whatever property is bound via v-model on the component) and then have that property be update (to be true or false) when the checkbox is checked.
Can anyone offer me any advice please?
Thank you.
You should not $emit event.target.value, it's the value of the checkbox, it's not a Boolean value. If you want to detect the checkbox is update(to be true or false) or not, You should $emit event.target.checked just like fstep said.
If v-on is the only listener that will be used it might be easier to use v-model as in the checkbox example from the Vue input docs.
However you can use listeners based on Binding-Native-Events-to-Components docs
<template>
<div class="field">
<label :for="name" class="label">
{{ label }}
</label>
<div class="control">
<input :id="name" :name="name" type="checkbox" class="control" checked="value" v-on="listeners" />
</div>
<p v-show="this.hasErrors" class="help has-text-danger">
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
</template>
<script>
export default {
name: 'check-edit',
props: {
value: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
errors: {
type: Array,
default: () => []
}
},
mounted() {},
computed: {
listeners() {
var vm = this;
// `Object.assign` merges objects together to form a new object
return Object.assign(
{},
// We add all the listeners from the parent
this.$listeners,
// Then we can add custom listeners or override the
// behavior of some listeners.
{
// This ensures that the component works with v-model
input: function(event) {
vm.$emit('input', event.target.checked);
}
}
);
},
hasErrors() {
return this.errors.length > 0;
}
}
};
</script>
Don't change props. Your component, having a v-model, should be emitting input events on change. The parent will handle the actual changing of the value.

CoreUI only one dropdown at a time

I want my sidebar dropdown to be available only one at a time, so when I click on another dropdown, the previous dropdown will be hidden again.
Below is the example of my current dropdown, which where you can open multiple dropdowns at a time. https://coreui.io/vue/demo/#/dashboard
<template>
<router-link tag="li" class="nav-item nav-dropdown" :to="url" disabled>
<div class="nav-link nav-dropdown-toggle" #click="handleClick"><i :class="icon"></i> {{name}}</div>
<ul class="nav-dropdown-items">
<slot></slot>
</ul>
</router-link>
</template>
<script>
export default {
props: {
name: {
type: String,
default: ''
},
url: {
type: String,
default: ''
},
icon: {
type: String,
default: ''
}
},
methods: {
handleClick(e) {
e.preventDefault();
e.target.parentElement.classList.toggle('open');
}
}
};
</script>
Please help.
The usual way to make a radio-group type controller (where only one item can be selected at once) is to have a variable that indicates which one is selected. Then each element compares itself to the selected one to determine whether it should be in the open state.
Since you have multiple router-links that don't know about each other, the parent object is going to have to own the which-one-is-selected indicator variable. The handleClick of your router-link should $emit an event that the parent will handle by changing the indicator variable. And the router-link should receive the indicator variable as a prop and use it in a computed to set the open class as appropriate.
Your code might look like this:
<template>
<router-link tag="li" class="nav-item nav-dropdown" :class="openClass" :to="url" disabled>
<div class="nav-link nav-dropdown-toggle" #click="handleClick"><i :class="icon"></i> {{name}}</div>
<ul class="nav-dropdown-items">
<slot></slot>
</ul>
</router-link>
</template>
<script>
export default {
props: {
name: {
type: String,
default: '',
selectedItem: Object
},
url: {
type: String,
default: ''
},
icon: {
type: String,
default: ''
}
},
computed: {
openClass() {
return this.selectedItem === this ? 'open' : '';
}
}
methods: {
handleClick(e) {
e.preventDefault();
this.$emit('setSelected', this);
}
}
};
</script>
You can add "itemAttr" property in _nav.js like:
items: [
{
name: 'Dropdown',
url: '/dropdown',
icon: 'icon-grid',
itemAttr: { id: 'drop-1' },
children: [{
name: 'Sub-Item 1',
url: '/dropdown/subitem1'
}, {
name: 'Sub-Item 2',
url: '/dropdown/subitem2'
}, {
name: 'Sub-Item 3',
url: '/dashboard/subitem3'
}]
},
{
name: 'Base',
url: '/base',
icon: 'icon-base',
itemAttr: { id: 'item-1' }
}
]
and in DefaultLayout.js, add event-listeners for click on these two id's, like:
var e1 = document.getElementById("drop-1")
e1.addEventListener("click", function () {
e1.classList.className += " open";
});
var ev1 = document.getElementById("item-1")
ev1.addEventListener("click", function () {
e1.className = "nav-item nav-dropdown"
});
Similarly, you can add more dropdowns and give them id's "drop-2" and "drop-3". OnClick, if you want to open that dropdown list use:
e<i>.classList.className += " open";
and for all the remaining dropdowns that you want to close use:
e<j>.className = "nav-item nav-dropdown";
When clicking on an item you want to close all dropdowns, so use:
e<i>.className = "nav-item nav-dropdown"; //for all the dropdown items.

Prop value lost after running a method Vuejs

I made a material design input field as a Vue component. I listen for the focus event and run a function to check the value every time the user focuses out. Here's the code:
<template>
<span class="h-input-container">
<input :type="type" :name="name" v-validate="validation"
#focusout="classHandle" :id="id" :value="value">
<p :class="focusClass"><i :class="icon"></i> {{placeholder}}</p>
</span>
</template>
<script>
export default {
mounted: function(){
if (this.value != '') {
this.focusClass = 'focused'
}
},
props: {
placeholder: {
default: ''
},
name: {
default: 'no-name'
},
type: {
default: 'text'
},
validation: {
default: ''
},
icon: {
default: ''
},
id: {
default: ''
},
value: {
default: ''
}
},
data: function(){
return {
focusClass: '',
}
},
methods: {
classHandle: function(event){
if (event.target.value != '') {
this.focusClass = 'focused'
} else {
this.focusClass = ''
}
}
}
};
</script>
I pass the value as a prop called value and I've used that value for the input field using :value="value". The thing is, every time the method classHandle runs, the input field's value disappears. I can't figure out why.
To clarify the accepted answer, Vue does not "refresh" the value when the focusout handler fires. The property, value, never changed, the input element's value changed.
Changing the focusClass forces Vue to re-render the component to the DOM. Because you've told Vue to use the property, value, as the value of the input via :value="value", it uses the current state of the property, which has never changed as stated above, to render the component and whatever you typed in the input disappears.
The accepted answer goes on to state that you should fix this by updating this.value. In a component, Vue will allow you to do that but it will throw a warning in the console.
[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: "value"
Properties of components in Vue are just like function arguments in javascript. Inside the component, you can change them, but that change is limited to the scope of the component. If the parent component ever has to re-render, then the property "value" of your input will be redrawn using the parent's version of the value, and you will lose your changes.
Component communication in Vue "props down, events up". In order to change the value outside your component, you have to $emit it. Here is a version of your component that works, without throwing any warnings, and properly emits your changes.
{
props: {
placeholder: {
default: ''
},
name: {
default: 'no-name'
},
type: {
default: 'text'
},
validation: {
default: ''
},
icon: {
default: ''
},
id: {
default: ''
},
value: {
default: ''
}
},
template:`
<div class="h-input-container" style="background-color: lightgray">
<input :type="type" :name="name"
#focusout="classHandle" :id="id" :value="value" #input="$emit('input', $event.target.value)" />
<p :class="focusClass"><i :class="icon"></i> {{placeholder}}</p>
</div>
`,
data: function() {
return {
focusClass: '',
}
},
methods: {
classHandle: function(event) {
if (event.target.value != '') {
this.focusClass = 'focused'
} else {
this.focusClass = ''
}
}
}
}
I've put together an example to demonstrate the differences in the two approaches.
Typically, I would not use :value="value" #input="$emit('input', $event.target.value)" and use v-model instead, but you are using :type="type" as well, and Vue will throw a warning about using v-model with a dynamic type.
Your current component do not seems to refresh this.value when you make change in the input. Vue cause a component refresh when you focusOut, and because your value is not updated, it shown empy. To resolve your problem, you need to update this.value on event input
new Vue(
{
el: '#app',
props: {
placeholder: {
default: ''
},
name: {
default: 'no-name'
},
type: {
default: 'text'
},
validation: {
default: ''
},
icon: {
default: ''
},
id: {
default: ''
},
value: {
default: ''
}
},
data: function() {
return {
focusClass: '',
}
},
methods: {
updateValue(event) {
this.value = event.target.value
},
classHandle: function(event) {
if (event.target.value != '') {
this.focusClass = 'focused'
} else {
this.focusClass = ''
}
}
}
});
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<span class="h-input-container">
<input :type="type" :name="name" v-validate="validation"
#focusout="classHandle" :id="id" :value="value" #input="updateValue" />
<p :class="focusClass"><i :class="icon"></i> {{placeholder}}</p>
</span>
</div>
So you should put your attention on:
#input="updateValue"
And
updateValue(event) {
this.value = event.target.value
}

Categories