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>
Related
First Image without clicking on Edit
Second Image when i click on Edit
here when i click on which ever edit button all the task which is in loop plus it is in if part will be hidden and else part will be shown but i want to hide particular task when i click on edit button. can anyone help me with that?.
<script>
export default {
data(){
return{
newTaskTitle: "",
isEditing : false
}
},
props:{
Task:{
type:Array,
required: true
},
},
methods:{
removeTask: function(idx){
this.Index = idx;
this.$emit('remove',this.Index);
},
EditTaskI(tsk){
this.task = tsk;
console.log(this.task);
this.isEditing = this.isEditing == true ? false : true;
this.newTaskTitle = this.task;
},
TaskUpdated(indx){
this.Index = indx
this.$emit('update',this.Index,this.newTaskTitle);
this.isEditing = this.isEditing == true ? false : true;
},
taskContentChange(e){
this.newTaskTitle = e.target.value;
}
}
}
</script>
<template>
<section v-if="Task.length > 0" class="taskMainSection">
<section v-for="(tasks,index) in Task" :key="index" class="sectionTask" >
<section class="TaskBox" v-if="!isEditing">
<div class="TaskTitleList" >
<div class="TaskSection">
<p class="listTask">{{ tasks.Task }}</p>
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-close" #click="removeTask(index)"></p>
<p class="editTask fa fa-edit" #click="EditTaskI(tasks.Task,index)"></p>
</div>
</div>
</section>
<section class="TaskBoxEdit" v-else>
<div class="TaskTitleList" >
<div class="TaskSection">
<input type="text" class="form-control" :value="newTaskTitle" #change="taskContentChange">
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-check" #click="TaskUpdated(index)"></p>
</div>
</div>
</section>
</section>
</section>
</template>
Instead of boolean use index for isEditing:
Vue.component('child', {
template: `
<section v-if="Task.length > 0" class="taskMainSection">
<section v-for="(tasks,index) in Task" :key="index" class="sectionTask" >
<section class="TaskBox" >
<div class="TaskTitleList" >
<div class="TaskSection">
<p class="listTask">{{ tasks.Task }}</p>
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-close" #click="removeTask(index)"></p>
<p class="editTask fa fa-edit" #click="EditTaskI(tasks.Task,index)"></p>
</div>
</div>
</section>
<section class="TaskBoxEdit" v-if="isEditing === index">
<div class="TaskTitleList" >
<div class="TaskSection">
<input type="text" class="form-control" :value="newTaskTitle" #change="taskContentChange">
</div>
</div>
<div class="OptionSectionMain">
<div class="OptionSection">
<p class="removeTask fa fa-check" #click="TaskUpdated(index)"></p>
</div>
</div>
</section>
</section>
</section>
`,
data(){
return{
newTaskTitle: "",
isEditing : null
}
},
props:{
Task:{
type:Array,
required: true
},
},
methods:{
removeTask(idx){
console.log(idx)
this.$emit('remove', idx);
},
EditTaskI(tsk, i){
this.task = tsk;
this.isEditing = i;
this.newTaskTitle = this.task;
},
TaskUpdated(indx){
this.Index = indx
this.$emit('update',this.Index,this.newTaskTitle);
this.isEditing = null;
},
taskContentChange(e){
this.newTaskTitle = e.target.value;
}
}
})
new Vue({
el: "#demo",
data(){
return{
tasks: [{Task: 'aaa'}, {Task: 'bbb'}, {Task: 'ccc'}],
}
},
methods: {
updateTasks(i, name) {
this.tasks[i].Task = name
},
removeTask(i) {
this.tasks.splice(i, 1)
}
}
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<child :task="tasks" #update="updateTasks" #remove="removeTask"></child>
</div>
Observation : isEditing is a culprit in your code. As isEditing is a global variable containing boolean value. Hence, On edit you are updating the value of isEditing which impact for all the tasks.
Solution : Instead of defining isEditing globally, You can add isEditing in each object of Task array. So that you can just update the value of clicked task not for every task.
Your template code will be :
<section class="TaskBox" v-if="!tasks.isEditing">
instead of
<section class="TaskBox" v-if="!isEditing">
I developed one page called Dashboard.vue and this page contains three child components(Display,sortBooksLowtoHigh,sortBooksHightoLow). Dashboard component contains one select options also inside that it have two options "Price:High to Low and Price:Low to High ",
if i click on price:LowToHigh option then it hides the Display component and displays the sortBooksLowtoHigh component utpo this it's working fine,
Now i import one more component called sortBooksHightoLow when i click on "price:High to Low" option it should hides the DisplayComponent and displays the sortBooksHightoLow 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>
<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>
<DisplayBooks v-show="flag==='noOrder'" />
<sortBooksLowtoHigh v-show="flag==='lowToHigh'" />
<sortBooksHightoLow v-show="flag==='highToLow'" />
</div>
</template>
<script>
import service from '../service/User'
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: '',
visible:true,
books: [{
}]
}
},
methods: {
flip() {
this.flag = !this.flag;
},
applyOption(evt) {
if (evt.target.value === "HighToLow") this.flag = 'highToLow';
else this.flag = 'lowToHigh';
},
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/Dashboard.scss";
</style>
sortBooksHightoLow.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">
<button type="button" #click="toggle(book.id);flip(book.id);" v-if="state==true" class="AddBag">Add to Bag</button>
<button v-if="state==true" class="wishlist">wishlist</button>
</div>
<div v-if="state==false" class="AddedBag">
<button class="big-btn">Added to Bag</button>
</div>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
data() {
return {
result: 0,
authorPrefix: 'by',
pricePrefix: 'Rs.',
defaultStrikePrice: '(2000)',
buttonValue: 'close',
flag: true,
state: true,
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'High to Low',
author: 'Saioii',
price: '1500'
}, ]
}
},
methods: {
toggle(id) {
this.clickedCard = id;
// this.card.content = this.notes.filter((note) => note.id === id);
console.log(this.clickedCard);
},
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handlesubmit() {
service.userDisplayBooksHightoLow().then(response => {
this.books.push(...response.data);
})
},
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/DisplayBooks.scss";
</style>
sortBooksLowtoHigh.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">
<button type="button" #click="toggle(book.id);flip(book.id);" v-if="state==true" class="AddBag">Add to Bag</button>
<button v-if="state==true" class="wishlist">wishlist</button>
</div>
<div v-if="state==false" class="AddedBag">
<button class="big-btn">Added to Bag</button>
</div>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
data() {
return {
result: 0,
authorPrefix: 'by',
pricePrefix: 'Rs.',
defaultStrikePrice: '(2000)',
buttonValue: 'close',
flag: true,
state: true,
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'Default Card',
author: 'Sai',
price: '..'
}, ]
}
},
methods: {
toggle(id) {
this.clickedCard = id;
// this.card.content = this.notes.filter((note) => note.id === id);
console.log(this.clickedCard);
},
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handlesubmit() {
service.userDisplayBooksLowtoHigh().then(response => {
this.books.push(...response.data);
console.log(this.response);
})
},
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/DisplayBooks.scss";
</style>
emmmm...
HightoLow => HighToLow.
There can be several methods, in my opinion, to achieve conditional rendering of components which I think your question asks for. Two of them which are highly useful are:
Using v-if and v-else where you must define a flag that handles the logic for component rendering. Also, wrapping them in a transition tag would a good idea to make the switch with a transition.
<transition>
<component1 v-if="flag" />
<component2 v-else />
</transition>
Dynamic Components, we use the component tag and is attribute. The component can then be switched using the name of the component.
<component is="nameofComponent" />
You can read more about dynamic components in vuejs docs.
While the dynamic component looks neat, a switch with transition can be a nice addition.
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 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'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 ()
}
},