Dynamic content with v-show in Bootstrap vue popover - javascript

I am struggling to show a dynamic content inside my popover built with Bootstrap Vue component.
I want to show a form first, and when that form is submitted successfully I want to show a confirmation text instead.
I tried to manage that with v-show directive within the popover content with no success. Everything is displayed of course outside the div with the v-show directive.
The result is that it just shows an empty popover (except the close link) inside the popover.
Here is my component :
<template>
<div class="email-content">
<div class="email-content__buttons">
<b-button variant="blue" class="fv-round" id="testEmailSend" :disabled="isPopoverDisplayed" #click="isPopoverEmailFormDisplayed = true">{{locales.testButton}}</b-button>
<b-popover target="testEmailSend"
:show="isPopoverDisplayed"
placement="auto"
#show="isPopoverEmailFormDisplayed = true"
#shown="setFocusOnPopoverForm">
<template>
<b-btn #click="isPopoverEmailFormDisplayed = false" class="close">
<span class="d-inline-block">×</span>
</b-btn>
</template>
<div v-show="isPopoverConfirmationDisplayed">
<b-form-group :label="`${locales.sendTo} :`" label-for="popoverEmailForm" class="mb-1" :invalid-feedback="locales.emailMandatory">
<b-input-group>
<b-form-input ref="emailTestReceiver" id="popoverEmailForm" :validationState="$v.emailTestReceiver" size="sm" v-model="emailTestReceiver"></b-form-input>
<b-input-group-append>
<b-btn #click="sendEmailTest" size="sm" :disabled="$v.$invalid" variant="primary">Ok</b-btn>
</b-input-group-append>
</b-input-group>
</b-form-group>
</div>
</b-popover>
<div v-show="isPopoverConfirmationDisplayed" placement="auto">
{{locales.emailSentConfirmation}}
</div>
<b-button variant="primary" class="fv-round" #click="saveTemplates">{{locales.saveButton}}</b-button>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { required, email } from 'vuelidate/lib/validators';
import feedbackHandler from '#/components/_shared/Alerts/ErrorHandler';
import SellerEmailContentConfig from '#/components/SellerDashboard/SellerEmail/SellerEmailContent/SellerEmailContentConfig';
import locales from './sellerEmailContent.locales.json';
export default {
name: 'SellerEmailContent',
data() {
return {
locales,
emailTestReceiver: '',
isPopoverEmailFormDisplayed: false,
isPopoverConfirmationDisplayed: false
}
},
validations: {
emailTestReceiver: {
required,
email
}
},
computed: {
isPopoverDisplayed() {
return this.isPopoverEmailFormDisplayed || this.isPopoverConfirmationDisplayed;
}
},
methods: {
...mapActions({
sendEmail: 'sendAuthCustomEmail'
}),
async sendEmailTest() {
const { error: errorSending } = await this.sendEmail({ type: 'Seller', email: this.emailTestReceiver });
if (!errorSending) {
this.isPopoverEmailFormDisplayed = false;
this.isPopoverConfirmationDisplayed = true;
setTimeout(() => { this.isPopoverConfirmationDisplayed = false }, 3000);
} else feedbackHandler.showError(errorSending);
},
setFocusOnPopoverForm() {
this.$refs.emailTestReceiver.focus();
}
},
}
</script>
My last was as you can see above using a computed property which combines both situation (form or confirmation to be displayed).
Any idea ? I must have missed something :)

Related

Why computed property works on preserving the reactivity but attributes in data() {} does not

I'm following this tutorial, where they point out a small bug with the checkbox status of forgetting the state of the ticked/unticked which can be solved by using the computed property.
I would like to ask, even if the attribute isDone in Data (ToDoItem.vue) has been changed to true (by ticking the checkbox), why the box is still unticked after clicking edit then cancel, and why computed property could solve this bug.
Below are parts of the scripts.
ToDoItem.vue
<template>
<div class="stack-small" v-if="!isEditing">
<div class="custom-checkbox">
<input
type="checkbox"
class="checkbox"
:id="id"
:checked="isDone"
#change="$emit('checkbox-changed')"
/>
<label :for="id" class="checkbox-label">{{ label }}</label>
</div>
<div class="btn-group">
<button
type="button"
class="btn"
ref="editButton"
#click="toggleToItemEditForm"
>
Edit <span class="visually-hidden">{{ label }}</span>
</button>
<button type="button" class="btn btn__danger" #click="deleteToDo">
Delete <span class="visually-hidden">{{ label }}</span>
</button>
</div>
</div>
<to-do-item-edit-form
v-else
:id="id"
:label="label"
#item-edited="itemEdited"
#edit-cancelled="editCancelled"
></to-do-item-edit-form>
</template>
<script>
import ToDoItemEditForm from "./ToDoItemEditForm";
export default {
components: {
ToDoItemEditForm,
},
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean },
id: { required: true, type: String },
},
data() {
return {
isEditing: false,
isDone: this.done, // after deleting this line and use
//computed: {} below, the bug is solved.
};
},
// computed: {
// isDone() {
// return this.done;
// },
// },
};
</script>
ToDoItem.vue
<template>
<div id="app">
<h1>To-Do List</h1>
<to-do-form #todo-added="addToDo"></to-do-form>
<h2 id="list-summary" ref="listSummary" tabindex="-1"> {{ listSummary }} </h2>
<ul aria-labelledby="list-summary" class="stack-large">
<li v-for="item in ToDoItems" :key="item.id">
<to-do-item
:label="item.label"
:done="item.done"
:id="item.id"
#checkbox-changed="updateDoneStatus(item.id)"
#item-deleted="deleteToDo(item.id)"
#item-edited="editToDo(item.id, $event)"
>
</to-do-item>
</li>
</ul>
</div>
</template>
<script>
import ToDoItem from "./components/ToDoItem.vue";
import ToDoForm from "./components/ToDoForm.vue";
import uniqueId from "lodash.uniqueid";
export default {
name: "app",
components: {
ToDoItem,
ToDoForm,
},
data() {
return {
ToDoItems: [],
};
},
methods: {
updateDoneStatus(toDoId) {
const toDoToUpdate = this.ToDoItems.find((item) => item.id === toDoId);
toDoToUpdate.done = !toDoToUpdate.done;
console.dir(toDoToUpdate.done)
},
};
</script>
I'm not an expert in vue, but I believe that the this.done being assigned to isDone: is only done once in data(), and it wouldn't be done if the props change (the value of isDone in data() won't change when the prop done changes). While in computed, isDone will watch the done prop value, and if that prop value changes, the computed will be notified and thus changes the isDone data.

Disable Submit button if the form fields' values have not changed in Vue/Nuxt

My question is simple. I have a form where a user can edit the entry in a Nuxt/Vue app. Now I want to disable the submit button until the form field's values have not changed. I use Vuex store to get the entry.
// store/users.js
export const state = () => ({
currentCustomer: null,
})
export const actions = {
async fetchCustomer({ commit }, customerId) {
const customer = await this.$strapi.$customers.findOne(customerId)
commit('setCurrentCustomer', customer)
}
}
export const mutations = {
setCurrentCustomer(state, customer) {
state.currentCustomer = customer
},
}
Here is my Vue/Nuxt Template:
<template>
<section class="d-flex flex-column mb-5">
<b-form v-else class="border rounded bg-white p-4 shadow-sm" #submit.stop.prevent="saveUser">
<b-form-group class="capital" label-for="name">
<template v-slot:label> Name <span class="text-danger">*</span> </template>
<b-form-input id="name" v-model.trim="form.name" type="text" autofocus required></b-form-input>
</b-form-group>
<b-form-group class="capital" label-for="email">
<template v-slot:label> Email <span class="text-danger">*</span> </template>
<b-form-input id="email" v-model.trim="form.email" type="email" required></b-form-input>
</b-form-group>
<p>Changed: {{ changed }}</p>
<code>Actual: {{ actual }}</code>
<code>Form: {{ form }}</code>
<b-row>
<b-col>
<button type="submit" :disabled="!changed">Save</button>
</b-col>
</b-row>
</b-form>
</section>
</template>
<script>
export default {
data() {
return {
changed: false,
}
},
computed: {
form() {
return this.$store.state.users.currentCustomer
},
actual() {
return this.$store.state.users.currentCustomer
},
},
watch: {
form(newValue) {
this.changed = newValue !== this.actual
},
},
created() {
this.$store.dispatch('users/fetchCustomer', this.$route.params.id)
},
}
</script>
The above code is not working. I know that something needs to be watched but I can't get my head around this issue. I would appreciate any help.
for object value, add a deep in watch, also, you need lodash.isEqual() to compare if two objects are equal
import _ from 'lodash';
...
watch: {
form: {
handler(value){
if(value) {
this.changed = !_.isEqual(value, this.actual);
}
},
deep: true,
}
},
...
Try to use #change="onChangeFn" or v-on:change="onChangeFn" on
methods: {
onChangeFn () {
this.changed = true
}
}

Passing Props Child -> Parent -> Other Child

I'm trying to create a basic form that, once input is submitted, sends data to the parent and is then rendered in a list as a card. Documentation has been pointing me towards using the Event Bus, but it all seems a little too over engineered for such a simple task. Not to mention its not working xD. Am I on the right track here? or missing the whole idea?
The Data seems to be updating on submit, but I'm not seeing a card render. I'm also seeing the follow error,
Property or method "initiativeList" is not defined on the instance but referenced during render.
I do, however, notice a particularly odd change in the render. Instead of a child being rendered in EncounterList.js the child's attributes are merging into the parent .
Any help is greatly appreciated.
EncounterDashboard.js
<template>
<div>
<NewCharacterForm #add-char="addChar" />
<EncounterList v-bind="encounterList" #add-char="addChar" />
</div>
</template>
<script>
import Character from "../classes/Encounter";
import NewCharacterForm from "./NewCharacterForm/NewCharacterForm.vue";
import EncounterList from "./EncounterList/EncounterList";
import EventBus from "./EventBus.js";
export default {
name: "EncounterDashboard",
components: { NewCharacterForm, EncounterList },
data() {
return {
newChar: {},
encounterList: []
};
},
methods: {
addChar(newChar) {
this.newChar = newChar;
this.encounterList.push(newChar);
EventBus.$emit("add-to-list", this.encounterList);
}
}
};
</script>
NewCharacterForm.js
<template>
<div class="new-char-wrapper">
<form class="char-form" ref="form" v-on:submit.prevent="handleSubmit">
<NewCharInput class="name-input" label="NAME" name="name" v-model="name" />
<div class="stat-wrapper">
<NewCharInput
class="init-input"
label="INITIATIVE"
name="initiative"
v-model="initiative"
type="number"
/>
<NewCharInput class="hp-input" label="HP" name="hp" v-model="hp" type="number" />
</div>
<div class="submit-row">
<button class="submit">SUBMIT</button>
</div>
</form>
</div>
</template>
<script>
import NewCharInput from "./NewCharInput";
import Character from "../../classes/Character";
import { uuid } from "vue-uuid";
export default {
name: "NewCharacterForm",
components: { NewCharInput },
data() {
return {
name: "",
initiative: "",
hp: 0
};
},
props: ["addChar"],
methods: {
handleSubmit() {
const charName = this.$refs.form.name.value;
const charInitiative = this.$refs.form.initiative.value;
const charHp = this.$refs.form.hp.value;
const charId = this.$uuid.v4();
const newChar = new Character(charName, charInitiative, charId, charHp);
this.$emit("add-char", newChar);
}
}
};
</script>
EncounterList.js
<template>
<div class="encounter-list">
<div class="header-row">
<h2 class="header col-init">INIT</h2>
<h2 class="header col-name">NAME</h2>
<h2 class="header col-hp">HP</h2>
</div>
<EncounterCard
v-for="character in initiativeList"
v-bind:key="character.id"
v-bind:hp="character.hp"
v-bind:name="character.name"
v-bind:initiative="character.initiative"
/>
</div>
</template>
<script>
import EncounterCard from "../EncounterCard/EncounterCard";
import EventBus from "../EventBus";
export default {
name: "EncounterList",
components: { EncounterCard },
data() {
return {
data: {
initiativeList: []
}
};
},
methods: {
populateList(charList) {
this.initiativeList = charList;
}
},
mounted() {
EventBus.$on("add-to-list", charList => {
this.populateList(charList);
});
}
};
</script>
EncounterCard.js
<template>
<div class="encounter-card-wrapper">
<h1 class="char-init">{{character.initiative}}</h1>
<h1 class="char-name">{{character.name}}</h1>
<h1 class="char-hp">{{character.hp}}</h1>
</div>
</template>
<script>
export default {
name: "EncounterCard",
props: ["character"]
};
</script>
data() {
return {
data: { //Is this what you're trying to do?
initiativeList: []
}
};
},
If the data attribute is intended, "initiativeList" should be changed to "data.initiativeList".
<EncounterCard
v-for="character in data.initiativeList"
v-bind:key="character.id"
v-bind:hp="character.hp"
v-bind:name="character.name"
v-bind:initiative="character.initiative"
/>
and
populateList(charList) {
this.data.initiativeList = charList;
}

The problem of data transfer between two child components

Good afternoon, I have two child components Header and Pagination. In Header, I have an input search engine and two inputs (title and body) in order to be able to add a post to Pagination. I managed to transfer the search value to the Pagination component, but I don’t know how to transfer the value from two inputs (title, body). I use to transfer the event bus. Help me please pass the value of the two inputs (title, body) into the Pagination component when you click the AddPost button.
My code on GitHub
Screenshot of app
My code of component Header:
<template>
<div class="header">
<input type="text" v-model="search" class="header_input_search" placeholder="Search" #input="saveMessage" />
<img src="src/assets/milk.png">
<div class="header_div_inputs">
<input type="text" v-model="createTitle" class="created"/>
<p><input type="text" v-model="createBody" class="createBody"/></p>
</div>
<button #click="addPost()" class="addPost">AddPost</button>
</div>
</template>
<script>
import axios from 'axios';
import {eventEmitter} from './main'
export default {
name: 'Header',
data () {
return {
search: '',
createTitle: '',
createBody: '',
}
},
methods:{
saveMessage(){
eventEmitter.$emit('messageSave', this.search)
},
}
}
</script>
My code of component Pagination:
<template>
<div class = "app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {eventEmitter} from './main'
export default {
name: 'app',
data () {
return {
current: null,
page: 0,
visiblePostID: '',
pSearch: ''
}
},
mounted(){
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'posts'
]),
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.filteredPosts.slice(start, end);
},
filteredPosts() {
return this.posts.filter((post) => {
return post.title.match(this.pSearch);
});
},
},
created(){
eventEmitter.$on('messageSave', (string) => {
this.pSearch = string
})
}
}
</script>
You can wrap title and body in an object
addPost() {
const post = {
title: this.createTitle,
body: this.createBody
}
eventEmitter.$emit('postAdd', post)
}
and then listen as normal
created(){
eventEmitter.$on('postAdd', (post) => {
console.log(post)
// do whatever you want
})
}
I have not worked on vue js but agreed with #ittus answer. You can make an object consisting of your required data which you want to share across the component and pass it as an event data.

How get a input value and send him to another component

I have an modal component, and inside this i have two input how i can get the value inserted in this input and send him to another component?
<template>
<modal v-if="Modal">
div(class="modal-title has-text-centered" slot="header")
div(slot="content")
div(class="control")
div(class="columns")
div(class="column")
div(class="field")
div(class="control")
input(class="input is-info is-large" type="text" placeholder="Analysis name")
div(class="field")
div(class="control")
input(class="input is-info is-large" type="text" placeholder="Item name")
div(slot="footer")
button(class="button is-info is-large modal-button" #click="closeModal" style="margin-right:5em;") Cancel
<router-link :to="{name: 'analyse'}" class="button is-info is-large modal-button">Create</router-link>
</modal>
</template>
<script>
import JwtDecode from 'jwt-decode'
import Modal from '../layout/modal/Basemodal.vue'
import Section from '../../views/Section.vue'
export default {
data: function() {
return {
user: '',
isActive: false,
isDrop: false,
Modal: false,
analyseName: '',
analyseItem: '',
application: '',
}
},
components: {
'modal': Modal,
'section-content': Section,
},
methods: {
// on-click logout the user and send him to the initial page
logout: function(){
this.$store.dispatch('logout')
.then(resp => {
this.$router.push('/')
})
},
// used to change the color of the selected button
toggle: function(event) {
event.ldaModal = !event.ldaModal
},
createAnalyse: function() {
this.$route.push('/analyse')
}
},
mounted() {
// render user token to get all user information
this.user = JwtDecode(localStorage.getItem('token'))
}
}
</script>
and here i have another component, in this component i need to get the value of modal and render it
<template lang="pug">
p {{ input1-value }}
p {{ input2-value }}
</template>
please, what's the best approach to do this?
I think that you should use the EventBus to solve your question.
in main.js
const bus = new Vue()
Vue.prototype.$bus = bus
you can register an event listener
this.$bus.on( 'test', ( data ) => {
console.log( 'test', data );
} );
and emit it
this.$bus.emit( 'test', { code: 1 } );
you also can look at https://github.com/yyued/hub.js

Categories