I'm trying to use the same component to handle the Add and Edit part of my app. I'm using Firebase, so I'm checking if there is an id in the route params, if there is, it renders as the edit form, if not, renders as the add form. But this doesn't work and it has some wierd behaviour.
Here is the code for the ContactForm component
<template>
<div>
<div class="card mb-3">
<div class="card-header">{{ editing ? 'Edit' : 'Add' }} Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="contact.name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="contact.email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="contact.phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
import { db } from "../../firebase";
export default {
components: {
TextInputGroup
},
data() {
return {
contact: "",
editing: false,
email: "",
name: "",
phone: ""
};
},
methods: {
addContact() {
const newContact = {
name: this.name,
email: this.email,
phone: this.phone,
createdAt: new Date()
};
db.collection("contacts")
.add(newContact)
.then(docRef => {
console.log("Document written with ID: ", docRef.id);
})
.catch(error => {
console.error("Error adding document: ", error);
});
this.$router.push("/");
},
getContactById() {
db.collection("contacts")
.doc(this.$route.params.id)
.get()
.then(snapshot => {
if (!snapshot.exists) return;
this.contact = snapshot.data();
});
},
updateContact() {
const newContact = {
name: this.contact.name,
email: this.contact.email,
phone: this.contact.phone
};
db.collection("contacts")
.doc(this.$route.params.id)
.update(newContact)
.then(() => {
console.log("Updated document with ID: ");
})
.catch(function(error) {
console.error("Error updating document: ", error);
});
this.$router.push("/");
}
},
mounted() {
if ("id" in this.$route.params) {
this.getContactById();
this.editing = true;
console.log("id");
} else {
console.log("ups");
// this
}
}
};
</script>
This is the github link and the live app
I just cloned you repository and tested in local, Added fixes to use single form for editing and adding
Here is the code for following files, just copy paste this code in below mentioned files
src/components/contact/ContactForm.vue
<template>
<div>
<div class="card mb-3">
<div class="card-header">{{ editing ? 'Edit' : 'Add' }} Contact</div>
<div class="card-body">
<form #submit.prevent="addContact">
<TextInputGroup
label="Name"
name="name"
placeholder="Enter your name..."
v-model="contact.name"
for="name"
/>
<TextInputGroup
type="email"
label="Email"
name="email"
placeholder="Enter your email..."
v-model="contact.email"
/>
<TextInputGroup
type="phone"
label="Phone"
name="phone"
placeholder="Enter your phone number..."
v-model="contact.phone"
/>
<input type="submit" value="Add Contact" class="btn btn-block btn-light" />
</form>
</div>
</div>
</div>
</template>
<script>
import TextInputGroup from "../layout/TextInputGroup";
import { db } from "../../firebase";
var temp = Object.freeze({
name: '',
email: '',
phone: '',
});
export default {
components: {
TextInputGroup
},
props: {
type: {
type: String,
default: '',
},
},
data() {
return {
contact: Object.assign({}, temp),
editing: false,
};
},
methods: {
addContact() {
this.contact.createdAt = new Date();
db.collection("contacts")
.add(this.contact)
.then(docRef => {
console.log("Document written with ID: ", docRef.id);
})
.catch(error => {
console.error("Error adding document: ", error);
});
this.$router.push("/");
},
getContactById() {
db.collection("contacts")
.doc(this.$route.params.id)
.get()
.then(snapshot => {
if (!snapshot.exists) return;
this.contact = snapshot.data();
});
},
updateContact() {
db.collection("contacts")
.doc(this.$route.params.id)
.update(this.contact)
.then(() => {
console.log("Updated document with ID: ");
})
.catch(function(error) {
console.error("Error updating document: ", error);
});
this.$router.push("/");
}
},
created() {
if ("id" in this.$route.params) {
this.getContactById();
this.editing = true;
console.log("id");
} else {
console.log("ups");
// this
}
},
watch: {
type(val) {
if (val == 'add') {
this.contact = Object.assign({}, temp);
}
}
}
};
</script>
src/components/contact/ContactItem.vue
<template>
<div>
<div class="card card-body mb-3">
<h4>
{{ contact.name }}
<i
class="fas fa-sort-down pointer"
#click="showContactInfo = !showContactInfo"
></i>
<i class="fas fa-times delete right delete" #click="deleteContact(contact.id)"></i>
<router-link :to="{path: `contact/edit/${contact.id}`, params: { id: contact.id }, query: { type: 'edit' }}">
<i class="fas fa-pencil-alt edit right"></i>
</router-link>
</h4>
<ul class="list-group" v-if="showContactInfo">
<li class="list-group-item">Email: {{ contact.email }}</li>
<li class="list-group-item">Phone: {{ contact.phone }}</li>
</ul>
</div>
</div>
</template>
<script>
import { db } from "../../firebase";
export default {
props: {
contact: {
type: Object,
required: true
}
},
data() {
return {
showContactInfo: false
};
},
methods: {
deleteContact(id) {
db.collection("contacts")
.doc(id)
.delete()
.then(function() {
console.log("Document successfully deleted!");
})
.catch(function(error) {
console.error("Error removing document: ", error);
});
}
}
};
</script>
<style scoped>
.pointer {
cursor: pointer;
}
.right {
cursor: pointer;
float: right;
}
.edit {
color: black;
margin-right: 1rem;
}
.delete {
color: red;
}
</style>
src/components/layout/Navbar.vue
<template>
<div>
<nav class="navbar navbar-expand-sm navbar-dark bg-danger mb-3 py-0">
<div class="container">
Contact Manager
<div>
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/" class="nav-link">
<i class="fas fa-home" />
Home
</router-link>
</li>
<li class="nav-item">
<router-link :to="{ path: '/contact/add', query: { type: 'add' } }" class="nav-link">
<i class="fas fa-plus" />
Add
</router-link>
</li>
<li class="nav-item">
<router-link to="/about" class="nav-link">
<i class="fas fa-question" />
About
</router-link>
</li>
</ul>
</div>
</div>
</nav>
</div>
</template>
src/views/ContactForm.vue
<template>
<ContactForm :type="formType" />
</template>
<script>
// # is an alias to /src
import ContactForm from "#/components/contact/ContactForm.vue";
export default {
name: "home",
data() {
return {
formType: '',
};
},
components: {
ContactForm
},
watch: {
'$route.query.type': {
handler: function(type) {
this.formType = type;
},
deep: true,
immediate: true
}
}
};
</script>
Related
Ok...king..
I'm trying to pass a value to child when a click event occur from other component it will change the prop value from parent. But it only shown the first mount value that pass.
topdown component that emit change
<template>
<div class="dropdown">
<button
class="btn btn-secondary dropdown-toggle h-75"
type="button"
id="dropdownMenuButton1"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{{ value }}
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1" role="menu">
<li v-for="option in options" :key="option">
<a
class="dropdown-item"
#click="(value = option);dunder;dundet;"
href="javascript:void(0)"
>{{ option }}</a
>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "TopDown",
data() {
return {
options: ["Edit", "Delete"],
value: "",
};
},
computed:{
dunder(){
return this.$emit("edit-task", this.value)
},
dundet(){
return this.$emit("edit-task-index",this.value)
}
}
};
</script>
<style>
</style>
parent component
<template>
<div
v-for="(item, index) in tasker"
:items="item"
:key="index"
class="border border-dark "
>
<section class="d-flex justify-content-between">
<h4 class="w-50 font-weight-bold fs-5">
<u>{{ item.title }}</u>
</h4>
<TopDown
#edit-task="val"
#edit-task-of="show(item, index)"
:index="index"
/>
</section>
<p class="text-start">{{ item.description }}</p>
<GoTask :showVal="showVal" :bool="bool" />
</div>
</template>
<script>
import TopDown from "#/components/TopDown.vue";
import GoTask from "#/components/GoTask.vue";
export default {
inheritAttrs: false,
components: {
TopDown,
GoTask,
},
data() {
return {
takss: {
items: "sss",
index: "",
},
showVal: "",
bool: false,
};
},
name: "Taski",
props: ["tasker"],
methods: {
show(item, index) {
this.takss.items = item;
this.takss.index = index;
},
val(val) {
if (val == "Edit") {
setTimeout(() => {
console.log(this.takss);
this.showval = this.takss;
console.log(this.showval);
console.log(this.bool)
}, 1000);
this.bool = !this.bool;
}
},
},
};
</script>
<style></style>
child component
<template>
<section
v-if="bools"
class="bg-white"
style="z-index: 20"
#click="closeModal"
></section>
<section
v-if="bools"
class="position-absolute"
style="
z-index: 50;
left: 50%;
top: 50%;
height: 100vh;
margin-top: 20vh;
"
>
<form
class="mt-10"
#submit.prevent="editTask"
>
<input
class="border rounded p-2 mb-2"
v-model.lazy="newTask.title"
placeholder="Title"
type="text"
/>
<textarea
ref="texsearch"
rows="20"
class=" p-2 mb-2"
v-model.lazy="newTask.description"
placeholder="Task Details"
type="text"
></textarea>
<button
class="border rounded p-2 bg-success text-white"
type="submit"
#submit.prevent=""
>
New Task
</button>
</form>
</section>
<button #click="test">dd</button>
</template>
<script>
export default {
inheritAttrs: false,
name: "GoTask",
props: ["tasker", "showVal", "bool"],
data() {
return {
showVals: this.showVal,
bools: this.bool,
newTask: {
title: "",
description: "",
},
};
},
methods: {
test() {
console.log(this.bools);
console.log(this.showVal);
},
ModalOpen() {
this.bools = true;
},
closeModal() {
this.bools = false;
},
showModal() {
this.bools = true;
// auto focus
this.$nextTick(() => {
this.$refs.textsearch.focus();
});
},
showtheVal() {
console.log(this.showtheVal);
},
},
};
</script>
<style></style>
When I click the button form other component that emit change of #edit-task-of and #edit-task the it doesn't send the new value of bool and showval to child component the bool value still false as same as first mount and showVal = "" when click button that run test function to see this two value at child. I'm totally new to vue. Thanks
You do mistake in GoTask component , method test, first console.log(this.bools);, bools is local state which is not updated after prop bool changed, just do correct console.log(this.bool);. You don't need to set props to local state, you can use props directly.
GoTask correct example:
props: ["tasker", "showVal", "bool"],
data() {
return {
newTask: {
title: "",
description: "",
},
};
},
methods: {
test() {
console.log(this.bool);
console.log(this.showVal);
},
codesandbox
I have two components Dashboard.vue(parent) and DisplayBooks.vue(child). inside child component i have one array addedBooks:[] it should be incremented when user clicks on ADD TO BAG button and decremented when user clicks on ADDED TO BAG button upto this it's working fine ,Now hat my requirement is addedBooks.length (array length)should be transferred from DisplayBooks.vue to the Dashboard.vue component.how to acheive this thing please help me
Dashboard.vue
<template>
<div class="main">
<div class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img src="../assets/education.png" alt="notFound" class="education-image" />
</div>
<ul class="nav navbar-nav">
<li>
<p class="brand">Bookstore</p>
</li>
</ul>
<div class="input-group">
<i #click="handlesubmit();" class="fas fa-search"></i>
<div class="form-outline">
<input type="search" v-model="name" class="form-control" placeholder='search...' />
</div>
</div>
<ul class="nav navbar-nav navbar-right" id="right-bar">
<li><a> <i class="far fa-user"></i></a></li>
<p class="profile-content">profile</p>
<!-- here i want to display the array length -->
<li><a><i class="fas fa-cart-plus"></i></a></li>
<p class="cart-content">cart</p>
</ul>
</div>
<div class="mid-body">
<h6>Books<span class="items">(128items)</span></h6>
<select class="options" #change="applyOption">
<option disabled value="">Sort by relevance</option>
<option value="HighToLow">price:High to Low</option>
<option value="LowToHigh">price:Low to High</option>
</select>
</div>
<div v-if="flam==false">
<h2>Hello</h2>
</div>
<DisplayBooks v-show="flag==='noOrder'" />
<sortBooksLowtoHigh v-show="flag==='lowToHigh'" />
<sortBooksHightoLow v-show="flag==='highToLow'" />
</div>
</template>
<script>
import sortBooksLowtoHigh from './sortBooksLowtoHigh.vue'
import sortBooksHightoLow from './sortBooksHightoLow.vue'
import DisplayBooks from './DisplayBooks.vue'
export default {
components: {
DisplayBooks,
sortBooksLowtoHigh,
sortBooksHightoLow
},
data() {
return {
flag: 'noOrder',
brand: 'Bookstore',
name: '',
flam: true,
visible: true,
books: [{
}]
}
},
methods: {
flip() {
this.flam = !this.flam;
},
applyOption(evt) {
if (evt.target.value === "HighToLow") {
this.flag = 'highToLow';
} else this.flag = 'lowToHigh';
},
}
}
</script>
DisplayBooks.vue
<template>
<div class="carddisplay-section">
<div v-for="book in books" :key="book.id" class="card book">
<div class="image-section">
<div class="image-container">
<img v-bind:src="book.file" />
</div>
</div>
<div class="title-section">
{{book.name}}
</div>
<div class="author-section">
by {{book.author}}
</div>
<div class="price-section">
Rs. {{book.price}}<label class="default">(2000)</label>
<button v-if="flag" class="btn-grp" type="submit" #click="handlesubmit();Togglebtn();">close</button>
</div>
<div class="buttons">
<div class="button-groups" v-if="!addedBooks.includes(book.id)">
<button type="submit" #click="handleCart(book.id);toggle(book.id);addedBooks.push(book.id)" class="AddBag">Add to Bag</button>
<button class="wishlist">wishlist</button>
</div>
<div class="AddedBag" v-else>
<button class="big-btn" #click="removeFromCart(book.id);addedBooks=addedBooks.filter(id=>id!==book.id)">Added to Bag</button>
</div>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
data() {
return {
flag: true,
state: true,
addedBooks:[], // this array length should be passed to dashboard component
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'Dont Make me think',
author: 'Sai',
price: '1500'
}, ]
}
},
methods: {
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handlesubmit() {
service.userDisplayBooks().then(response => {
this.books.push(...response.data);
})
},
handleCart(bookId){
let userData={
id: bookId,
}
service.userUpdateCart(userData).then(response=>{
return response;
})
},
removeFromCart(bookId){
let userData={
id:bookId,
}
service.userRemoveFromCart(userData).then(response=>{
return response;
})
}
}
}
</script>
Watch the array change and emit its length to the parent :
DisplayBooks.vue
data() {
return {
flag: true,
state: true,
addedBooks:[], // this array length should be passed to dashboard component
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'Dont Make me think',
author: 'Sai',
price: '1500'
}, ]
}
},
watch:{
addedBooks:{
handler(val){
this.$emit('update-books-count',val.length)
},
deep:true
}
}
Dashboard
<ul class="nav navbar-nav navbar-right" id="right-bar">
<li><a> <i class="far fa-user"></i></a></li>
<p class="profile-content">profile</p>
<p>{{booksCount}}</p>
<li><a><i class="fas fa-cart-plus"></i></a></li>
<p class="cart-content">cart</p>
</ul>
...
<DisplayBooks v-show="flag==='noOrder'" #update-books-count="(n)=>booksCount=n"/>
...
data() {
return {
booksCount:0,
flag: 'noOrder',
brand: 'Bookstore',
name: '',
flam: true,
visible: true,
books: [{
}]
}
},
I have a 'Documentation' page which has 3 sub-menus in it.
What I'm trying to do is no matter which sub-menu is clicked I want it to go to one main documentation Vue component and then pass a variable into it so a sub-component also loads in the main component.
This is what I have so far:
Nav.vue
<template>
<div>
<nav class="navbar navbar-expand-lg">
<ul class="navbar-nav ml-auto h-100 d-flex align-items-center" style="margin-right: 5rem">
<div class="nav-item dropdown" #mouseover="openDocDropDown" #mouseleave="closeDocDropDown">
<div ref="docDropdown" class="d-flex align-items-center" role="button" data-toggle="dropdown" style="height: 42px; cursor: default">
<i class="far fa-file-alt" style="font-size: 1.9rem" />
Documentation
<i class="ml-2" :class="[docCollapsed ? 'fa-caret-down' : 'fa-caret-up', 'fas']" />
</div>
<div v-if="!docCollapsed" class="dropdown-menu subMenus" aria-labelledby="navbarDropdown">
<router-link to="/documentation" tag="div" class="nav-item d-flex align-items-center p-3" #click.native="docMenuClicked('admin')">
<i class="far fa-file-pdf" style="font-size: 1.9rem" />
<span class="pt-1 pl-1">Administrator documentation</span>
</router-link>
<router-link to="/documentation" tag="div" class="nav-item d-flex align-items-center p-3" #click.native="docMenuClicked('reseller_channel')">
<i class="far fa-file-pdf" style="font-size: 1.9rem" />
<span class="pt-1 pl-1">Reseller/Channel documentation</span>
</router-link>
<router-link to="/documentation" tag="div" class="nav-item d-flex align-items-center p-3" #click.native="docMenuClicked('cust')">
<i class="far fa-file-pdf" style="font-size: 1.9rem" />
<span class="pt-1 pl-1">Customer documentation</span>
</router-link>
</div>
</div>
<router-link to="/contactus" tag="li" :class="[currentPage.includes('/conactus/') ? 'router-link-exact-active router-link-active' : '', 'nav-item mainMenuLink d-flex align-items-center']">
<i class="d-none d-xl-inline icon-nav_contact_us iconSize" /> Contact us
</router-link>
</ul>
</nav>
</div>
</template>
<script>
export default {
name: "TopNav",
props: [ 'hasErrored' ],
data() {
return {
unCollapsed: true,
docCollapsed: true,
docMenuItemClicked: ''
}
},
computed: {
...mapGetters({
user: 'user'
}),
currentPage() {
return this.$route.path;
}
},
methods: {
openUnDropDown() {
this.$refs.unDropdown.visible = true;
this.unCollapsed = false;
},
closeUnDropDown() {
this.$refs.unDropdown.visible = false;
this.unCollapsed = true;
},
openDocDropDown() {
this.$refs.docDropdown.visible = true;
this.docCollapsed = false;
},
closeDocDropDown() {
this.$refs.docDropdown.visible = false;
this.docCollapsed = true;
},
docMenuClicked(data) {
this.docMenuItemClicked = data;
setTimeout(() => {
VueEvent.$emit('document-menu-clicked', { menuClicked: this.docMenuItemClicked });
this.docCollapsed = true;
}, 1)
}
}
}
</script>
DocumentationMain.vue
<template>
<div>
Main Document Page
{{ menuClicked }}
<ResellChanDocs v-if="menuClicked === 'reseller_channel'" />
<CustDocs v-else-if="menuClicked === 'cust'" />
<AdminDocs v-else />
</div>
</template>
<script>
import ResellChanDocs from './DocumentationResellerChannel';
import CustDocs from './DocumentationCustomer';
import AdminDocs from './DocumentationAdministrator';
export default {
name: 'MainDocumentation',
components: {
ResellChanDocs,
CustDocs,
AdminDocs
},
data() {
return {
menuClicked: 'admin'
}
},
beforeMount() {
VueEvent.$on('document-menu-clicked', (data) => {
this.menuClicked = data.menuClicked;
});
}
}
</script>
routes.js
import Home from './components/Home.vue';
import ContactUs from './components/ContactUs';
import Documentation from './components/DocumentationMain';
export const routes = [
{ path: '', component: Home },
{ path: '/contactus', component: ContactUs },
{ path: '/documentation', component: Documentation, props: true },
{ path: '*', redirect: '/' }
];
Gives
Issues
Is there an easier way of doing this?
When I click a menu my 'DocumenationMain' doesn't seem to register the VueEvent.$emit
When I click a link the second time, the correct component is rendered
When I click a link, all my other menus highlight blue
I get the feeling I'm doing this wrong
Just use Dynamic Route Matching
export const routes = [
{ path: '', component: Home },
{ path: '/contactus', component: ContactUs },
{ path: '/documentation/:doctype', component: Documentation, props: true },
{ path: '*', redirect: '/' }
];
nav.vue
<router-link to="/documentation/reseller" />
<router-link to="/documentation/cust" />
<router-link to="/documentation/admin" />
Documentation.vue
props: {
doctype: {
type: String,
default: 'admin'
}
}
Router should now pass :doctype value (from the URL) into Documentation component doctype prop - use it to decide what sub-component to activate inside...
I am new to Vue. I am building a simple app that will list all countries and when you click on a particular country it shows you more details about the country. Idea is to open country details in a modal.
I'm stuck with displaying that modal. The modal opens, but in the background. It also opens a detail page.
CountryDetail.vue:
<script>
import axios from 'axios';
export default {
name: 'country-detail',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
alpha3Code: [],
alpha3CodetoString: [],
}
},
mounted () {
this.pending = true;
axios
.get(`https://restcountries.eu/rest/v2/name/${this.$route.params.country}?fullText=true`)
.then((response) => {
(this.countryInfo = response.data)
this.alpha3CodetoString = this.alpha3Code.join(';');
})
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
}
}
</script>
<template>
<modal v-model="show">
<div class="modal-mask" :class="{ darkTheme : isDarkTheme }" name="modal">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
</slot>
</div>
<div v-for="country in countryInfo" class="countryTile modal-body" v-bind:key="country.id">
<slot name="body">
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="country-details">
<h1>{{country.name}}</h1>
<div class="listDiv">
<ul>
<li><span>Population:</span> {{country.population | formatNumbers }}</li>
<li><span>Capital:</span> {{country.capital}}</li>
<li><span>Iso:</span> {{country.alpha3Code}}</li>
</ul>
<ul>
<li><span>Currencies:</span> {{country.currencies['0'].name}}</li>
<li><span>Languages:</span>
<span
v-for="(language, index) in country.languages"
v-bind:key="index"
class="languages">
{{language.name}}<span v-if="index + 1 < country.languages.length">, </span>
</span>
</li>
</ul>
</div>
</div>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<a #click="$router.go(-1)" class="backBtn"><i class="fas fa-arrow-left" />Go Back</a>
</slot>
</div>
</div>
</div>
</div>
</modal>
</template>
Home.vue:
<script>
import axios from 'axios';
export default {
name: 'home',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
search: '',
darkMode: false,
}
},
mounted () {
this.pending = true;
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => (this.countryInfo = response.data))
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
},
computed: {
filteredCountries: function () {
return this.countryInfo.filter((country) => {
if (this.region === '' ) {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else if (this.search !== '') {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else {
return ('blbla');
}
})
}
},
}
</script>
<template>
<div class="home" :class="{ darkTheme : isDarkTheme }">
<div class="searchBar">
<div class="searchContainer">
<i class="fas fa-search searchIcon"></i>
<input
class="searchInput"
type="text"
v-model="search"
aria-label="Search for a country..."
placeholder="Search for a country..."
/>
<ul class="searchResults"></ul>
</div>
</div>
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
<div v-if="countryInfo" class="tileGrid" #click="showModal = true">
<div v-for="country in filteredCountries" class="countryTile" v-bind:key="country.id">
<router-link
:to="{ name: 'country-detail', params: {country: country.name }}"
class="linkTile"
>
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="text">
<h1>{{ country.name }}</h1>
</div>
</router-link>
</div>
</div>
</div>
</template>
The router-link will always redirect you to another page, because its basically <a href="..."> see here. You don't need router if you just want to show the detail on a modal, you could just add the modal component inside the Home.vue component, then bind the modal and the countryName with props, then pass them in when clicking a button.
Home.vue:
<template>
<div>
<button #click="showDetail">
Show Detail
</button>
<CountryDetail :countryName="countryName" :showModal="showModal"/>
<div>
</template>
<script>
import CountryDetail from './CountryDetail.vue'
export default {
name: 'Home',
components: { CountryDetail },
data: () => ({
countryName: '',
showModal: false,
}),
methods: {
showDetail() {
this.showModal = true;
},
},
}
</script>
And instead of making request on mounted, you could use watch to do something like watching for the showModal prop, and make request everytime it has a truthy value. Like this:
CountryDetail.vue:
<template>
<modal v-model="showModal">
<!-- modal content -->
</modal>
</template>
<script>
export default {
name: 'CountryDetail',
props: ['countryName', 'showModal'],
watch: {
'showModal': {
deep: true,
handler(val) {
if (val && this.countryName !== '') {
// Make request
}
}
}
}
}
</script>
I have a main component called VariantList which lists some boxes stacked on top of each other based on a json I created myself,there is a copy button on each box when clicked another box will be created below the first one exactly the same as the first one.
My problem is that when I save the data as an attribute in the box element, the inner object containing jsx will not be readable when the box is created an throw an error.
import React, { Component } from 'react';
class VariantList extends Component {
constructor() {
super();
this.handleCopy = this.handleCopy.bind(this);
this.state = {
variants: [
{
id: 1,
title: 'تنوع ۱',
status: {
number: 1,
title: 'تایید شده'
},
description: 'تنوع های مختص این محصول',
variants: [
{
icon: 'fa-paint-brush',
title: 'رنگ انتخاب شده:',
value: <div>
<span style={{ width:"12px",height:"12px",margin:"0 4px",backgroundColor:"#ff0000",borderRadius:"50%",display: "inline-block",verticalAlign: "middle" }}></span>
<span>قرمز</span>
</div>
},
{
icon: 'fa-pencil',
title: 'سایز:',
value: <span>XL</span>
},
{
icon: 'fa-plane',
title: 'گارانتی:',
value: <span>امرتات</span>
},
{
icon: 'fa-tag',
title: 'قیمت:',
value: <span>۱۲۰۰۰۰۰۰ تومان</span>
},
{
icon: 'fa-tint',
title: 'حافظه:',
value: <span>512 GB</span>
},
{
icon: 'fa-wifi',
title: 'تخفیف:',
value: <span>۲۵٪</span>
},
{
icon: 'fa-send',
title: 'هدیه:',
value: <span>دارد</span>
},
{
icon: 'fa-signal',
title: 'موجودی:',
value: <span>۱۰۰ عدد</span>
},
{
icon: 'fa-vcard',
title: 'زمان بندی:',
value: <span>۸ صبح پنج شنبه ۲۳ آبان</span>
}
]
}
]
}
}
handleCopy(variant) {
let variantArr = this.state.variants;
variantArr.splice(this.state.variants.findIndex(x => x.id === variant.id),0,variant);
this.setState({
variants: variantArr
});
}
render() {
let variant_boxes = [];
for (let i = 0; i < this.state.variants.length; i++) {
variant_boxes.push(<VariantBox key={ i } data = { this.state.variants[i] } copyVariant={ this.handleCopy } index={ i+1 } />);
}
return (
<div id="variantBoxContainer" className="row no-gutters">
{ variant_boxes }
</div>
);
}
}
class VariantBox extends Component {
constructor(props) {
super(props);
this.handleCopy = this.handleCopy.bind(this);
this.variantRef = React.createRef();
}
handleCopy = () => {
this.props.copyVariant(JSON.parse(this.variantRef.current.getAttribute('variant')));
}
render() {
return(
<div className="col-xxl-12 justify-content-center yelno-bg-white variant_box"
variant={ JSON.stringify(this.props.data) }
ref={this.variantRef} >
<div className="row no-gutters">
<div className="col-xxl variant_badges">
<div className="yelno-badge variant">
<span className="innerBadge">{ this.props.data.title }</span>
</div>
{
this.props.data.status.number ?
"" :
<div className="yelno-badge inprocess">
<span className="innerBadge">{ this.props.data.status.title }</span>
</div>
}
</div>
<div className="col-xxl text-left action_btn_container">
<div className="action_btn d-inline-block">
<i className="fa fa-copy fa-lg" variantid={ this.props.data.id } onClick={ this.handleCopy }></i>
</div>
<div className="action_btn d-inline-block">
<i className="fa fa-edit fa-lg"></i>
</div>
<div className="action_btn d-inline-block">
<i className="fa fa-eye fa-lg"></i>
</div>
<div className="action_btn d-inline-block">
<i className="fa fa-trash fa-lg"></i>
</div>
</div>
</div>
<div className="row no-gutters variant-attributes">
<VariantItem data = { this.props.data.variants } />
</div>
</div>
);
}
}
class VariantItem extends Component {
render() {
return(
this.props.data.map((data,index) => {
return (
<div key={ index } className="col-xxl text-center">
<i className={ "fa " + data.icon + " fa-2x yelno-color-light-grey variant-attribute-icon" }></i>
<span className="variant-attribute-title d-block yelno-color-light-grey">
{ data.title }
</span>
<div className="variant-attribute-value yelno-12BoldS">
{/* { data.value } */}
</div>
</div>
);
})
);
}
}
export default VariantList;
I have commented {/* { data.value } */} but if I uncomment it after clicking copy button an error is thrown due to the undefined this.state.variants[i].variants[j].value which contains html.
Your help is much appreaciated in advance.
I found the answer by the idea I was given in the comments, so I didn't save HTML node as JSX in JSON instead saved it as string and used the react-html-parser library to convert the string to react component like below:
import ReactHtmlParser from 'react-html-parser';
edited this line in the JSON in VariantList component
value: '<div><span style="width:12px;height:12px;margin:0 4px;background-color:#ff0000;border-radius:50%;display: inline-block;vertical-align: middle; "></span><span>قرمز</span></div>'
and finally in the VariantItem component converted { data.value } to { ReactHtmlParser(data.value) } and it worked perfectly.