Using V-IF hides the template in the DOM - javascript

Inside of the DetailsForm.Vue I am using v-if and then in the Dom is hidding all the template. I dont understand why.
<template>
<div class="DetailsForm">
this is a test
<input type="checkbox" v-model="checked">I have filled all this page<br>
<div class="test" v-if="!$.checked.required">
This field is required
</div>
<button :disabled="!checked">Button</button>
</div>
</template>
<script>
import { required } from 'vuelidate/lib/validators'
export default {
name: 'DetailsForm',
data: function () {
return {
checked: false
}
},
validation: {
checked: {
required
}
}
}

Related

Getting Unexpected mutation of "buttonText" prop

I have created simple component for button in vuejs,When I clicked on the button then the text should show "I have been clicked". and it does but with this I also getting error that say: 49:7 error Unexpected mutation of "buttonText" prop vue/no-mutating-props
I have tried with this.$emit() but didnot mange to fix the mutation error issue. Can someone explain me what I am doing wrong and why I cannot mutate the buttonText props. Any help will be much appreciated!
here is my code:
App.vue
<template>
<div>
<ButtonDisabled buttonText="ChangedText" />
</div>
</template>
ButtonDisabled.vue:
<template>
<div class="main-container">
<h1>Button Disable if nothing is enter in email field</h1>
<div class="wrapper">
<input type="email" placeholder="email" v-model="email" />
<button :disabled="email.length < 1" class="btn-sub" #click="handleClick">
Subcribe
</button>
<!-- <button :disabled="!email.length">Subscribe</button> -->
<h2>{{ email }}</h2>
<h1>{{ buttonText }}</h1>
</div>
</div>
</template>
<script>
export default {
name: "ButtonDynamic",
props: {
buttonText: {
type: String,
default: "clickMe",
required: false,
}
},
data() {
return {
email: "",
};
},
methods: {
handleClick() {
// this.$emit("change-text-onclick", this.buttonText);
this.buttonText = "I have been clicked!!!";
},
},
};
</script>
<style scoped>
.main-container {
width: 300px;
height: 150px;
margin: 0 auto;
}
.wrapper {
display: inline-flex;
flex-direction: column;
}
.btn-sub {
margin: 10px;
}
</style>
Getting this error:
But my component still works when clicked:
Is it a bad practice to mutate a prop see link, you can mutate an inner component variable inside data or use v-model to have two-way dataflow on your props using v-model.
in this case, you can:
set up your internal data variable with the value of your prop, and then mutate that data variable ( not the prop )
Create and set the prop as a model value and expose it with a emit.
I will show you the easier path to get out of the error, but you need to reconsider your component responsibilities.
Vue.component('mybutton', {
template: `
<div class="main-container">
<h1>Button Disable if nothing is enter in email field</h1>
<div class="wrapper">
<input type="email" placeholder="email" v-model="email" />
<button :disabled="email.length < 1" class="btn-sub" #click="handleClick">
Subcribe
</button>
<!-- <button :disabled="!email.length">Subscribe</button> -->
<h2>{{ email }}</h2>
<h1>{{ inputMessage }}</h1>
</div>
</div>
`,
props: {
buttonText: {
type: String,
default: "clickMe",
required: false,
}
},
data() {
return {
email: "",
inputMessage: this.buttonText
};
},
methods: {
handleClick() {
this.inputMessage = "I have been clicked!!!";
},
},
});
new Vue({
el: '#app',
data(){
return {
message: "Changed Text"
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<mybutton button-text="Change Text" />
</div>
</div>

How to pass focus props in components VueJS

I have component child Input:
<template>
<div class="basic-input-outer" :style="styles">
<p class="paragraph-small">{{ title }}</p>
<input ref="name" :type="type" class="basic-input">
</div>
</template>
<script>
export default {
name: "Input",
props: ['type', 'styles', 'title', 'focus'],
watch: {
focus: function() {
this.setFocus()
}
},
methods: {
setFocus() {
this.$refs.name.focus()
}
}
}
</script>
<style lang="scss">
#import "../assets/css/components/Input";
</style>
And I have parent component where I work with this input:
<div class="login-options">
<p class="choose" #click="chooseLogin('email')">With Email</p>
<div class="vertical-line" />
<p class="choose" #click="chooseLogin('phone')">With Phone Number</p>
</div>
<div v-if="loginWithEmail">
<Input :focus="emailFocus" :title="'Email'" :type="'email'" />
</div>
<div v-else>
<Input :focus="phoneFocus" :title="'Phone number'" :type="'email'" />
</div>
...
chooseLogin(option) {
if (option === 'email') {
this.loginWithEmail = true
this.emailFocus = true
} else {
this.loginWithEmail = false
this.phoneFocus = true
}
}
So, the problem is, when I trigger the function it only focuses one time on one field, and then stops. I want to make that focus props works that way, so when it's triggered, the field will be focused, and it will be working not just one time, like in this case.
Here is solution. Actually, it was because v-if. v-if just remove element(-s) from DOM, so, the best way to solve such problems is to use conditional styling with display: none:
Input:
<template>
<div class="basic-input-outer" :style="styles">
<p class="paragraph-small">{{ title }}</p>
<input ref="name" :type="type" class="basic-input">
</div>
</template>
<script>
export default {
name: "Input",
props: ['type', 'styles', 'title', 'focus'],
watch: {
focus: function() {
if (this.focus) this.$refs.name.focus()
}
}
}
</script>
<style lang="scss">
#import "../assets/css/components/Input";
</style>
And parent component:
<Input :style="[!loginWithEmail ? {'display': 'none'} : {'': ''}]" :focus="emailFocus" :title="'Email'" :type="'email'" />
<Input :style="[loginWithEmail ? {'display': 'none'} : {'': ''}]" :focus="phoneFocus" :title="'Phone number'" :type="'email'" />
...
chooseLogin(option) {
if (option === 'email') {
this.loginWithEmail = true
this.emailFocus = true
this.phoneFocus = false
} else {
this.loginWithEmail = false
this.emailFocus = false
this.phoneFocus = true
}
}

Keep track of child component values in parent without using emit?

I have a custom component called HobbyForm which is a simple form with two controls, a checkbox and an input, this component is being called from a parent component called Content, along with other similar 'form' components.
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
data() {
return {
hobby: {
isActive: false,
hobby: null
}
}
},
}
</script>
My Content component is something like:
<template>
<language-form></language-form>
<hobby-form v-for="(hobbie, index) in hobbies" :key="index" v-bind="hobbies[index]"></hobby-form>
<Button label="Add Hobby" #click="addHobby"></Button>
</template>
<script>
export default {
name: "Content",
components: {
LanguageForm,
HobbyForm
},
data() {
return {
language: '',
hobbies: [
{
isActive: false,
hobby: null
}
]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
};
</script>
The idea is to be able to add more instances of the HobbyForm component to add another hobby record to my hobby data property; but I don't know how to keep track of these values from my parent without using an emit in my child components, since I don't want to manually trigger the emit, I just want to have the data updated in my parent component.
How should I access my child component's data from my parent and add it to my array?
In the current form passing parent data into a child component via v-bind="hobbies[index]" makes no sense as the child component (HobbyForm) has no props so it does not receive any data from the parent...
To make it work:
Remove data() from the child HobbyForm
Instead declare a prop of type Object
Bind form items to the properties of that Object
Pass the object into each HobbyForm
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
props: {
hobby: {
type: Object,
required: true
}
}
}
</script>
Even tho props are designed to be one way only so child should not mutate prop value, this is something else as you do not mutate prop value, you are changing (via a v-model) the properties of the object passed via a prop (see the note at the bottom of One-Way Data Flow paragraph)
Also change the parent to:
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
Demo:
const app = Vue.createApp({
data() {
return {
hobbies: [{
isActive: false,
hobby: null
}]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
})
app.component('hobby-form', {
props: {
hobby: {
type: Object,
required: true
}
},
template: `
<form>
<div class="row align-items-center">
<div class="col-1">
<input type="checkbox" id="isHobbyActive" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<input type="text" id="hobby" placeholder="Hobby" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
`
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.1.5/dist/vue.global.js"></script>
<div id='app'>
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
<button #click="addHobby">Add Hobby</button>
<hr/>
<pre> {{ hobbies }} </pre>
</div>

How to pass the chebox value from a parent to parent to child and after set it into the vuex store VueJS/Vuex

Hi everyone i have a big trouble i need to modify the value of checkbox from parent to another parent to child , is not this difficulty i know but i'm at the start and after spent 2 days for this i can't try anything else , now i'll add all the code the child is named "UICheckbox" and i pass it to "ToDoListItem" and this last is passed to "ToDoList" , and every Todos is an in an array into "store.js"
UICheckbox
<template>
<div class="checkbox-container">
<input
type="checkbox"
:id="id"
:v-model="localTodo"
>
<label :for="id">
<p class="font-bold">{{ text }}</p>
</label>
</div>
</template>
<script>
export default {
props: [
"id",
"value",
"text"
],
data() {
return {
localTodo: this.value
}
},
watch: {
localTodo:{
handler(newVal, oldVal) {
this.$emit("change", newVal)
},
deep: true,
}
}
}
</script>
<style>
</style>
ToDoListItem
<template>
<div class="flex flex-row w-full my-2">
<UICheckbox
:v-model="localTodo.checked"
:id="todo.id"
:text="todo.text"
#change="localTodo.cheked = $event"
/>
<delete-button :todo="todo" />
</div>
</template>
ToDoList
<to-do-list-item
:todo="todo"
#click="changeChecked(todo)"
#change="todo.checked = $event"
/>

Vue.js slots - how to retrieve slot content in computed properties

I have a problem with vue.js slots. On one hand I need to display the slot code. On the other hand I need to use it in a textarea to send it to external source.
main.vue
<template>
<div class="main">
<my-code>
<template v-slot:css-code>
#custom-css {
width: 300px
height: 200px;
}
</template>
<template v-slot:html-code>
<ul id="custom-css">
<li> aaa </li>
<li> bbb </li>
<li> ccc </li>
</ul>
</template>
</my-code>
</div>
</template>
my-code.vue
<template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"><slot name="css-code"></slot></component>
<slot name="html-code"></slot>
<!-- send the code -->
<form method="post" action="https://my-external-service.com/">
<textarea name="html">{{theHTML}}</textarea>
<textarea name="css">{{theCSS}}</textarea>
<input type="submit">
</form>
</div>
</template>
<script>
export default {
name: 'myCode',
props: {
codeId: String,
},
computed: {
theHTML() {
return this.$slots['html-code']; /* The problem is here, it returns vNodes. */
},
theCSS() {
return this.$slots['css-code'][0].text;
},
}
}
</script>
The issues is that vue doesn't turn the slot content. It's an array of <VNode> elements. Is there a way to use slots inside the textarea. Or a way to retrieve slot content in the theHTML() computed property.
NOTE: I use this component in vuePress.
You need to create a custom component or a custom function to render VNode to html directly. I think that will be the simplest solution.
vnode to html.vue
<script>
export default {
props: ["vnode"],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit(
"html",
[...this.$el.childNodes].map((n) => n.outerHTML).join("\n")
);
},
};
</script>
Then you can use it to your component
template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"
><slot name="css-code"></slot
></component>
<slot name="html-code"></slot>
<!-- send the code -->
<Vnode :vnode="theHTML" #html="html = $event" />
<form method="post" action="https://my-external-service.com/">
<textarea name="html" v-model="html"></textarea>
<textarea name="css" v-model="theCSS"></textarea>
<input type="submit" />
</form>
</div>
</template>
<script>
import Vnode from "./vnode-to-html";
export default {
name: "myCode",
components: {
Vnode,
},
props: {
codeId: String,
},
data() {
return {
html: "", // add this property to get the plain HTML
};
},
computed: {
theHTML() {
return this.$slots[
"html-code"
]
},
theCSS() {
return this.$slots["css-code"][0].text;
},
},
};
</script>
this thread might help How to pass html template as props to Vue component

Categories