Watch $route.params.slug doesn't triggered vuejs - javascript

I'm trying to use watch property of vuejs to perform action when my $route.params.slug change, but it's seems doen't work...
My $route.params.slug actualy change when i click to my router-link but the watcher's not triggered...
There is my app.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Clients from './components/ClientsComponent'
import Slider from './components/SliderComponent'
import store from './store/store'
require('./bootstrap');
Vue.use(VueRouter)
const routes = [
{path: '/references/clients', component: Slider, name: 'client'},
{path: '/references/clients/:slug-:id', component: Slider, name: 'client.show'}
]
const router = new VueRouter({
mode: 'history',
routes
})
const app = new Vue({
el: '#app',
store: store,
router,
components: {
Clients,
Slider
}
});
My store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
strict: true,
state: {
clients: {},
clientsPacks: [],
slides: {}
},
getters: {
clients: function (state) {
return state.clients
},
clientsPacks: function (state) {
return state.clientsPacks
},
slides: function (state) {
return state.slides
}
},
mutations: {
addClient: function (state, {clients}) {
let obj = {}
let array = []
let arraysPack = []
let arraysPacks = []
clients.forEach(function (client) {
array.push(client)
obj[client.id] = client
})
for (let i = 0; i < Math.ceil(array.length/9); i++) {
arraysPack = []
for (let y = i*9; y < 9 + i*9; y++) {
if (y < array.length) {
arraysPack.push(array[y])
}
}
arraysPacks.push(arraysPack)
}
state.clients = obj
state.clientsPacks = arraysPacks
},
addSlide: function (state, {slides}) {
let obj = {}
slides.forEach(function (slide) {
obj[slide.pos] = slide
})
state.slides = obj
}
},
actions: {
loadClients: async function (context) {
axios.get('/api/clients')
.then((response) => {
context.commit('addClient', {
clients: response.data.clients
})
})
.catch((error) => {
console.log(error);
throw new Error(error)
});
},
loadSlides: async function (context, slug) {
axios.get('/api/slides/' + slug)
.then((response) => {
context.commit('addSlide', {
slides: response.data.slides
})
})
.catch((error) => {
console.log(error);
throw new Error(error)
});
}
}
})
And my Components
ClientsComponent.vue
<template>
<section id="clients">
<article class="col-md-6 mosaic">
<div v-if="clientsPacks.length > 1" id="Carousel" class="carousel vertical slide" data-ride="carousel" data-interval="false">
<!-- Wrapper for slides -->
<div class="carousel-inner">
<div v-for="(clientsPack, index) in clientsPacks" class="item" :class="{ 'active': index === 0 }">
<div v-for="client in clientsPack" class="col-sm-4 mosaic-item client">
<router-link
:to="{
name: 'client.show',
params: {
slug: slugify(client.title),
id: client.id
}
}">
<div class="embed-responsive embed-responsive-16by9">
<div class="slide-image embed-responsive-item" :style="{ backgroundImage: 'url(\'/storage/' + client.logo_URL + '\')' }"></div>
</div>
</router-link>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="left carousel-control" href="#Carousel" data-slide="prev">
<span class="fa fa-chevron-left"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#Carousel" data-slide="next">
<span class="fa fa-chevron-right"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div v-else>
<div v-for="client in clients" class="col-sm-4 mosaic-item client">
<router-link
:to="{
name: 'client.show',
params: {
slug: slugify(client.title),
id: client.id
}
}">
<div class="embed-responsive embed-responsive-16by9">
<div class="slide-image embed-responsive-item" :style="{ backgroundImage: 'url(\'/storage/' + client.logo_URL + '\')' }"></div>
</div>
</router-link>
</div>
</div>
</article>
<article class="col-md-6">
<router-view></router-view>
</article>
</section>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
props: {
client: Number
},
computed: {
...mapGetters(['clients']),
...mapGetters(['clientsPacks'])
},
mounted () {
this.$store.dispatch('loadClients')
},
methods: {
slugify: function (value) {
if (!value) return ''
var slug = "";
// Change to lower case
var valueLower = value.toLowerCase();
// Letter "e"
slug = valueLower.replace(/e|é|è|ẽ|ẻ|ẹ|ê|ế|ề|ễ|ể|ệ/gi, 'e');
// Letter "a"
slug = slug.replace(/a|á|à|ã|ả|ạ|ă|ắ|ằ|ẵ|ẳ|ặ|â|ấ|ầ|ẫ|ẩ|ậ/gi, 'a');
// Letter "o"
slug = slug.replace(/o|ó|ò|õ|ỏ|ọ|ô|ố|ồ|ỗ|ổ|ộ|ơ|ớ|ờ|ỡ|ở|ợ/gi, 'o');
// Letter "u"
slug = slug.replace(/u|ú|ù|ũ|ủ|ụ|ư|ứ|ừ|ữ|ử|ự/gi, 'u');
// Letter "d"
slug = slug.replace(/đ/gi, 'd');
// Trim the last whitespace
slug = slug.replace(/\s*$/g, '');
// Change whitespace to "-"
slug = slug.replace(/\s+/g, '_');
return slug;
}
}
}
</script>
SliderComponent.vue
<template>
<div id="MainCarousel" class="carousel move-carousel slide" data-ride="carousel">
<!-- Indicators -->
<ol class="carousel-indicators">
<li v-for="slide in slides" data-target="#MainCarousel" data-slide-to="{ slide.pos - 1 }" :class="{ 'active': slide.pos == 1 }"></li>
</ol>
<!-- Wrapper for slides -->
<div class="carousel-inner">
<div v-for="slide in slides" class="item" :class="{ 'active': slide.pos == 1 }">
<div class="embed-responsive embed-responsive-16by9">
<div class="slide-image embed-responsive-item" :style="{ backgroundImage: 'url(\'/storage/' + slide.image_URL + '\')' }"></div>
</div>
<div class="carousel-caption">
<div class="col-xs-10 vcenter">
<h3>{{ slide.title }}</h3>
<p>{{ slide.caption }}</p>
</div><!--
--><div class="col-xs-2 logo vcenter">
<img v-if="slide.client_logo_URL" :src="'/storage/' + slide.client_logo_URL" width="100%">
</div>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="left carousel-control" href="#MainCarousel" data-slide="prev">
<span class="fa fa-chevron-left"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#MainCarousel" data-slide="next">
<span class="fa fa-chevron-right"></span>
<span class="sr-only">Next</span>
</a>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
props: {
slide: Number
},
computed: {
...mapGetters(['slides'])
},
mounted () {
this.loadSlides ()
},
watch: {
'this.$route.params.slug': function () {
this.loadSlides ()
}
},
methods: {
loadSlides () {
console.log(this.$route.params.slug)
this.$store.dispatch('loadSlides', this.$route.params.slug)
}
}
}
</script>
I google it since 3 hours and i'm going crazy with this so...
Thank's for help.

You should watch $route.params.slug instead of this.$route.params.slug
watch: {
'$route.params.slug': function () {
this.loadSlides ()
}
},

Related

Vue3 toggle element in v-for loop

I am looping through an array on v-for loop in vue 3. Is there a way to toggle the v-show of a paragraph by clicking on the Heading element. Below is my code :
<div class="defs">
<ul v-for="(d, index) in definitions"
:key="'d' + index">
<li>
<div class="project" >
<div #click="showDetails" class="actions">
<h3>{{ d.title }}</h3>
<div class="icons">
<span class="material-icons" ><i class="fas fa-trash-alt"></i></span>
<span class="material-icons" ><i class="fas fa-pencil-alt"></i></span>
</div>
</div>
<div v-if="show" class="details">
<p>{{d.explanation}}</p>
</div>
</div>
</li>
</ul>
</div>
<script>
import { ref } from "vue";
import { projectDatabase} from '../../firebase/config'
export default {
props: ['id'],
setup(props){
const show = ref(false);
const showDetails = () =>{
show.value = !show.value
}
return {
definitions, props, show, showDetails,
}
}
}
</script>
I know we cant use this in composition API. so how can we solve the toggle issue ?
Try like following snippet, here is the codesandbox with composition API
const demo = {
data() {
return {
definitions: [
{ title: "aaa", explanation: "aaa" },
{ title: "bbb", explanation: "bbb" },
{ title: "ccc", explanation: "ccc" },
],
show: null
}
},
methods: {
showDetails(idx) {
this.show === idx ? (this.show = null) : (this.show = idx);
}
},
// same code with coposition API
/*import { ref } from "vue";
import { projectDatabase} from '../../firebase/config'
setup() {
const show = ref(null);
const definitions = ref([
{ title: "aaa", explanation: "aaa" },
{ title: "bbb", explanation: "bbb" },
{ title: "ccc", explanation: "ccc" },
]);
const showDetails = (idx) => {
show.value === idx ? (show.value = null) : (show.value = idx);
};
return { definitions, show, showDetails }
},*/
};
Vue.createApp(demo).mount("#demo");
<script src="https://unpkg.com/vue#next"></script>
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous" />
<div id="demo">
<div class="defs">
<ul>
<li v-for="(d, index) in definitions" :key="index">
<div class="project">
<div #click="showDetails(index)" class="actions">
<h3>{{ d.title }}</h3>
<div class="icons">
<span class="material-icons"
><i class="fas fa-trash-alt"></i
></span>
<span class="material-icons"
><i class="fas fa-pencil-alt"></i
></span>
</div>
</div>
<div v-if="show === index" class="details">
<p>{{ d.explanation }}</p>
</div>
</div>
</li>
</ul>
</div>
</div>

pass change of prop from parent to child with change Vue 3 Option api

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

Vuejs emit not working form child to parent

I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)

Multiple links passing parameter to same route

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...

Vue modal with a router

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>

Categories