vue 3 composition api hooks and data rendering - vue.js

I would like to show data in template when the page loads after i fetch the data from a database.
The data is returned with async/await.
The select menus I use for filtering are now reactive only once I select a value from the menu. But I would like the full(unfiltered) data array to show when the page first loads and before any filters are applied.
How can I wait untill the data is fully fetched besore passing it to my template ?
<template>
<div class="mp" style="width: 100vw;">
<div class="col-2">
<span class="lbl">FILTER1</span>
<select v-model="selectedMA" class="form-select" >
<option value="">all</option>
<option value="Germany">Germany</option>
</select>
</div>
<div class="col-2">
<span class="lbl">FILTER2</span>
<select v-model="selectedMA2" class="form-select" >
<option value="">all</option>
<option value="Tier1">Tier1</option>
</select>
</div>
<p>DATA :</p>
<p> {{actorListTest2}} </p>
</div>
</template>
<script >
import {watchEffect, ref, computed} from 'vue'
import getActorDocs from '../composables/getActorDocs'
export default {
setup(){
const {actorDocs, loadActors} = getActorDocs()
const selectedMA = ref("")
const selectedMA2 = ref("")
loadActors();
var filteredActors = actorDocs
const actorListTest2 = computed(() => {
if(selectedMA == null && selectedMA2 == null){return filteredActors}
if(selectedMA.value == "" && selectedMA2.value == ""){return filteredActors}
else {
return filteredActors.filter(obj => {
return (!selectedMA2.value.length || obj.type == selectedMA2.value)
&& (!selectedMA.value.length || obj.country == selectedMA.value)
})}
});
watchEffect(() => console.log(actorListTest2.value))
return {filteredActors, actorListTest2, selectedMA, selectedMA2, actorDocs}
}//setup
}
</script>
As requested, here is getActorDocs():
import {ref} from 'vue'
import { projectFirestore } from '../firebase/config'
import { collection, getDocs } from "firebase/firestore";
const getActorDocs = () => {
const actorDocs = []
const error = ref(null)
const loadActors = async () => {
try {
const querySnapshot = await getDocs(collection(projectFirestore, "actors"));
querySnapshot.docs.map(doc => {
actorDocs.push(doc.data())
})
} catch (err) {
error.value = err.message
console.log(error.value)
}
}
return { actorDocs, error, loadActors}
}
export default getActorDocs

Related

Vue blog change rendered post when clicked on a link from "latest posts" sidebar

I've got a problem with my hobby project, I created a BlogPost.vue in which I render the previously clicked post from a Blog.vue page.
In this BlogPost, I render the clicked post's title, content etc, and on the sidebar, I show a small area in which I render 3 of the latest posts from this same category - the posts from Blog.vue.
When I click on the sidebar's links, any of the 3, the browser does change the slug, and the page does re-render itself, except it re-renders the same post that was clicked. If I refresh the page in the browser (or Ctrl+R or F5 etc), then it does render the correct content clicked from this sidebar.
I have no clue why it does that, I can only suppose that I should be watching the route change then somehow refresh what it renders, but no idea as to how.
Blog.vue, this works great, renders the single post clicked
<script setup lang="ts">
import axios from "axios";
import { ref } from "vue";
import { onMounted } from "vue";
import moment from "moment";
const postsUrl = "http://localhost/wordpress/wp-json/wp/v2/posts";
const posts = ref([] as any);
const isLoading = ref(false);
const errorCaught = ref(false);
var queryOptions = {
_embed: true,
};
const getPosts = () => {
isLoading.value = true;
axios
.get(postsUrl, { params: queryOptions })
.then((response) => {
posts.value = response.data;
console.log(posts.value);
isLoading.value = false;
})
.then(() => {
console.log(isLoading.value);
})
.catch((error) => {
if (error) {
isLoading.value = false;
errorCaught.value = true;
}
});
};
onMounted(async () => {
getPosts();
});
</script>
<template>
<transition name="fadeLoading">
<div v-if="isLoading" class="posts-loading">
<div class="circle"></div>
</div>
</transition>
<transition name="fadeLoading">
<div class="errorCaught" v-if="errorCaught">
There was an error loading news
</div>
</transition>
<div class="blog-container">
<div class="wrapper">
<transition-group name="fadeBlog">
<ul v-if="!isLoading" class="blog-posts-ul" v-for="post in posts">
<div class="posts-card">
<a
><router-link
:to="/blog/ + post.slug"
key="post.id"
class="posts-permalink"
>
</router-link
></a>
<img
v-if="post.featured_media != 0"
class="posts-featuredimage"
:src="post._embedded['wp:featuredmedia'][0].source_url"
:alt="post.title.rendered"
/>
<img v-else src="#/assets/logos/favicon-big.png" />
<div class="posts-date">
<p>
{{ moment(post.date).fromNow() + " " + "ago" }}
</p>
</div>
<div class="posts-text">
<h1 class="posts-title">{{ post.title.rendered }}</h1>
<p v-html="post.excerpt.rendered" class="posts-excerpt"></p>
</div>
</div>
</ul>
</transition-group>
</div>
</div>
</template>
BlogPost.vue, renders the previously clicked one, but does not show the sidebar's clicked content
<script setup lang="ts">
import { ref, watch, onMounted } from "vue";
import { useRoute } from "vue-router";
import axios from "axios";
import moment from "moment";
const route = useRoute();
const postsUrl = "http://localhost/wordpress/wp-json/wp/v2/posts";
const queryOptions = {
slug: route.params.blogSlug,
_embed: true,
};
const post = ref([] as any);
const isLoading = ref(false);
const latestPostsAPI = "http://localhost/wordpress/wp-json/wp/v2/posts";
const latestPosts = ref([] as any);
const errorCaughtLatest = ref(false);
var queryOptionsLatest = {
_embed: true,
per_page:3,
};
const getLatest = () => {
axios
.get(latestPostsAPI, { params: queryOptionsLatest })
.then((response) => {
latestPosts.value = response.data;
console.log(latestPosts.value);
})
.catch((error) => {
if (error) {
errorCaughtLatest.value = true;
}
});
};
const getPost = () => {
isLoading.value = true;
axios
.get(postsUrl, { params: queryOptions })
.then((response) => {
post.value = response.data;
console.log("Pages retrieved!");
})
.catch((error) => {
console.log(error);
})
.then(() => {
isLoading.value = false;
});
};
getLatest();
getPost();
</script>
<template>
<div v-if="!isLoading" class="post-wrapper">
<div class="wrapper">
<div class="post-title">{{ post[0].title.rendered }}</div>
<div class="post-date">
{{ moment(post[0].date).format("MMMM Do YYYY, h:mm, dddd") }}
</div>
<!-- THIS INCLUDES HTML TAGS -->
<div class="post-content" v-html="post[0].content.rendered"></div>
</div>
</div>
<div class="side-container">
<div class="side-wrapper">
<ul v-if="!isLoading" class="blog-posts-ul" v-for="latest in latestPosts">
<div class="posts-card">
<a
><router-link
:to="/blog/ + latest.slug"
key="latest.id"
class="posts-permalink"
>
</router-link
></a>
<img
v-if="latest.featured_media != 0"
class="posts-featuredimage"
:src="latest._embedded['wp:featuredmedia'][0].source_url"
:alt="latest.title.rendered"
/>
<img v-else src="#/assets/logos/favicon-big.png" />
<div class="posts-text">
<div class="posts-title">{{ latest.title.rendered }}</div>
<div class="posts-date">
<p>{{ moment(latest.date).fromNow() + " " + "ago" }}</p>
</div>
<div class="posts-author">
{{ latest._embedded.author[0].name }}
</div>
</div>
</div>
</ul>
</div>
</div>
</template>
Thanks for your time
In my original post as you can see, I'm using axios to get my post with the params queryOptions:
I declare my queryOptions with a const:
const queryOptions = {
slug: route.params.blogSlug,
_embed: true,
};
THEN, I call the getPost method, which uses this constant:
const getPost = () => {
isLoading.value = true;
axios
.get(postsUrl, { params: queryOptions } })
.then((response) => {
post.value = response.data;
console.log("getPost!");
})
.catch((error) => {
console.log(error);
})
.then(() => {
isLoading.value = false;
});
};
And the problem is right there: that queryOptions, IS a constant, gets defined at the onMounted lifecycle, or even before I'm not 100% sure, but it does NOT get re-defined WHEN I click on any link on this BlogPost.vue component.
SO, the solving is the following:
Change the getPost method so it does NOT use a pre-defined constant, rather just simply type in the params, like the following:
const getPost = () => {
isLoading.value = true;
axios
.get(postsUrl, { params: { slug: route.params.blogSlug, _embed: true } })
.then((response) => {
post.value = response.data;
console.log("getPost!");
})
.catch((error) => {
console.log(error);
})
.then(() => {
isLoading.value = false;
});
};
If I do it this way, every time the getPost method runs, which it WILL since I'm watching the blogSlug change with
watch(() => route.params.blogSlug, getPost);
the getPost function will always call the latest route.params.blogSlug, which gets changed before the watcher runs the getPost as I click on the sidebar link.

cannot remove items from cart in vue 3 with pinia

so in the console log it is saying it is being removed from the cart but i can still see the items in the cart... how can i remove them from the cart? im using pinia for state management and the cart is in the state. why it is not working for me?
the code:
shop.vue
<template>
<div class="shop">
Cart Items: <cart-badge :count="cartLength">{{ count }}</cart-badge>
<h1>shop</h1>
<div class="products" v-for="item in Products" :key="item.id">
{{ item.name }} {{ item.price }}$
<button #click="storeCounter.addToCart(item)">Add to Cart</button>
</div>
</div>
</template>
<script setup>
import { useCounterStore } from "../stores/counter";
import Products from "../db.json";
import CartBadge from "../components/CartBadge.vue";
import { computed } from "vue";
const storeCounter = useCounterStore();
const cartLength = computed(() => {
return storeCounter.cart.length;
});
</script>
store.js(pinia)
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
cart: [],
}),
actions: {
addToCart(id) {
this.cart.push(id);
console.log("test passed!");
},
removeFromCart(id) {
this.cart.splice(id);
console.log("removed from cart!");
},
},
});
cart.vue
<template>
<div class="cart">
<h1>cart</h1>
<div class="cartitems" v-for="item in storeCounter.cart" :key="item.id">{{ item.name }} {{ item.price }}$
<button #click="storeCounter.removeFromCart(item.id)">X</button>
</div>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/counter';
const storeCounter = useCounterStore()
</script>
Splice uses the index of the item to delete it.
Do this instead;
removeFromCart(id) {
let cartItemIndex = this.cart.findIndex(x => x.id === id);
if (cartItemIndex >= 0) {
this.cart.splice(cartItemIndex, 1);
console.log("Removed from cart");
}
}
or if you don't want to use splice anymore (and if performance matters);
removeFromCart(id) {
this.cart = this.cart.filter(x => x.id !== id);
console.log("Removed from cart");
}
Please use more descriptive parameter names. Rename "id" to "item" in addToCart, since you're not just adding the item id, but the entire item. This can be confusing to other developers and make your code unreadable.
addToCart(item) {
this.cart.push(item)
console.log('test passed!')
}
References: splice filter

How to use Axios with Vue 3 Composition API

I am attempting to build a Pokemon filtered search app with Vue 3 and Composition API based on the following tutorial: https://www.youtube.com/watch?v=QJhqr7jqxVo. (GitHub: https://github.com/ErikCH/PokemonVue)
The fetch method used in the search component includes a reduce() function to handle urlIdLookup based on a specific id assigned to each Pokemon in the API response:
const state = reactive({
pokemons: [],
filteredPokemon: computed(()=> updatePokemon()),
text: "",
urlIdLookup: {}
});
fetch("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((res) => res.json())
.then((data) => {
console.log(data);
state.pokemons = data.results;
state.urlIdLookup = data.results.reduce((acc, cur, idx)=>
acc = {...acc, [cur.name]:idx+1 }
,{})
console.log('url',state.urlIdLookup+1)
});
urlIdLookup is then passed into the route used to display selected Pokemon info:
<div
class="ml-4 text-2xl text-blue-400"
v-for="(pokemon, idx) in filteredPokemon"
:key="idx"
>
<router-link :to="`/about/${urlIdLookup[pokemon.name]}`">
{{ pokemon.name }}
</router-link>
</div>
Instead of using the above fetch setup, I wish to use Axios to handle the request and response from the Pokemon API. After installing Axios in the project and importing it into the component, I added a new fetchPokemon method:
const fetchPokemon = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?offset=0')
.then(response => {
state.pokemons = response.data
})
}
onMounted(() => {
fetchPokemon()
})
While using Axios in this new fetch method, I want to handle urlIdLookup similar to the previous fetch setup, but without using the reduce() method and de-structured accumulator, if possible. How can I go about using Axios to retrieve the urlId of each Pokemon, then pass that urlId into the "about" route in the template?
Here is the full component:
<template>
<div class="w-full flex justify-center">
<input placeholder="Enter Pokemon here" type="text"
class="mt-10 p-2 border-blue-500 border-2" v-model="text" />
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div
class="ml-4 text-2xl text-blue-400"
v-for="(pokemon, idx) in filteredPokemon"
:key="idx"
>
<router-link :to="`/about/${urlIdLookup[pokemon.name]}`">
{{ pokemon.name }}
</router-link>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { reactive, toRefs, computed, onMounted } from "vue";
export default {
setup() {
const state = reactive({
pokemons: [],
filteredPokemon: computed(()=> updatePokemon()),
text: "",
urlIdLookup: {}
});
const fetchPokemon = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?offset=0')
.then(response => {
state.pokemons = response.data
})
}
onMounted(() => {
fetchPokemon()
})
// fetch("https://pokeapi.co/api/v2/pokemon?offset=0")
// .then((res) => res.json())
// .then((data) => {
// console.log(data);
// state.pokemons = data.results;
// state.urlIdLookup = data.results.reduce((acc, cur, idx)=>
// acc = {...acc, [cur.name]:idx+1 }
// ,{})
// console.log('url',state.urlIdLookup+1)
// });
function updatePokemon(){
if(!state.text){
return []
}
return state.pokemons.filter((pokemon)=>
pokemon.name.includes(state.text)
)
}
return { ...toRefs(state), fetchPokemon, updatePokemon };
}
};
</script>
If I understood you correctly take a look at following snippet:
const { reactive, toRefs, computed, onMounted } = Vue
const { axioss } = axios
const app = Vue.createApp({
setup() {
const state = reactive({
pokemons: [],
filteredPokemon: computed(() => updatePokemon()),
text: "",
urlIdLookup: {},
});
const fetchPokemon = () => {
axios
.get("https://pokeapi.co/api/v2/pokemon?offset=0")
.then((response) => {
state.pokemons = response.data.results; // 👈 get just results
});
};
fetchPokemon();
// 👇 function to get index
const getPokemonId = (item) => {
return state.pokemons.findIndex((p) => p.name === item);
};
function updatePokemon() {
if (!state.text) {
return [];
}
return state.pokemons.filter((pokemon) =>
pokemon.name.includes(state.text)
);
}
// 👇 return new function
return { ...toRefs(state), fetchPokemon, updatePokemon, getPokemonId };
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.26.1/axios.min.js" integrity="sha512-bPh3uwgU5qEMipS/VOmRqynnMXGGSRv+72H/N260MQeXZIK4PG48401Bsby9Nq5P5fz7hy5UGNmC/W1Z51h2GQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="demo">
<div class="w-full flex justify-center">
<input
placeholder="Enter Pokemon here"
type="text"
class="mt-10 p-2 border-blue-500 border-2"
v-model="text"
/>
</div>
<div class="mt-10 p-4 flex flex-wrap justify-center">
<div
class="ml-4 text-2xl text-blue-400"
v-for="(pokemon, i) in filteredPokemon"
:key="i"
>
<!-- // 👇 call function to get index -->
<router-link :to="`/about/${getPokemonId(pokemon.name)}`">
{{ pokemon.name }} - id {{ getPokemonId(pokemon.name) }}
</router-link>
</div>
</div>
</div>
it seem id is not necessary, why not use name be id. if you want use interger
be must, you can foreach results set index be id to each item, then
<router-link :to="`/about/${pokemon.id}`">
{{ pokemon.name }}
</router-link>

vue 3 composition api, chaining filters uppon reactive data

I have an orbject array as data and I would like to have several select menus in order to filter the data and show it on screen in a reactive way.
I have managed to get one filter to work, but the idea here is to be able to chain filters so that the result can be filtered by any combination of select vlaues.
I have broken down my code to a minimalistic example below :
<template>
<div class="mp" style="width: 100vw;">
<div class="col-2">
<span class="lbl">FILTER1</span>
<select v-model="selectedMA" class="form-select" >
<option value="">all</option>
<option value="Germany">Germany</option>
</select>
</div>
<div class="col-2">
<span class="lbl">FILTER2</span>
<select v-model="selectedMA2" class="form-select" >
<option value="">all</option>
<option value="Tier1">Tier1</option>
</select>
</div>
<p>DATA :</p>
<p> {{actorListTest2}} </p>
</div>
</template>
<script >
import {onMounted, onBeforeMount, ref, computed, watch} from 'vue'
import getActorDocs from '../composables/getActorDocs'
export default {
setup(){
const {actorDocs, loadActors} = getActorDocs()
const selectedMA = ref("all")
const selectedMA2 = ref("all")
loadActors(); // load data
var filteredActors = actorDocs
const actorListTest2 = computed(() => {
if(selectedMA.value == "all" && selectedMA2.value == "all"){return filteredActors}
else{
return filteredActors.filter(obj => {
return obj.country == selectedMA.value
}).filter(obj => {
return obj.type == selectedMA2.value
})}
});
return {filteredActors, actorListTest2, selectedMA, selectedMA2, actorDocs}
}//setup
}
</script>
<style scoped>
</style>
Inspired by this post (Array filtering with multiple conditions javascript), I figured out a way to get it to work. Might not be the best way to do it , but it works
const actorListTest2 = computed(() => {
if(selectedMA.value == "" && selectedMA2.value == ""){return filteredActors}
else {
return filteredActors.filter(obj => {
return (!selectedMA2.value.length || obj.type == selectedMA2.value)
&& (!selectedMA.value.length || obj.country == selectedMA.value)
})}
});

Select checkbox and shows its related objects in Vue.js

In my Vue.js project, I have two separate components are Country and States. I have merged them in one page. So now if I select one country it will display related states. How to do this?
<template>
<div>
<div style=margin-left:355px><country-index></country-index></div>
<div style=margin-left:710px><state-index></state-index></div>
</div>
</template>
<script>
import { ROAST_CONFIG } from '../../../config/config.js';
import CountryIndex from './components/country/Index';
import StateIndex from './components/state/Index';
import { listen } from '../../../util/history.js';
import axios from 'axios'
let baseUrl = ROAST_CONFIG.API_URL;
export default {
name: 'LocationsView',
layout: 'admin/layouts/default/defaultLayout',
middleware: 'auth',
components: {
'country-index' : CountryIndex,
'state-index' : StateIndex,
},
data() {
return { currentComponent:'','countryId':''}
},
methods: {
updateCurrentComponent(){
console.log(this.$route.name);
let vm = this;
let route = vm.$route;
if(this.$route.name == "Locations"){
this.currentComponent = "country-index";
}
}
},
mounted() {
let vm = this;
let route = this.$route;
window.addEventListener('popstate',this.updateCurrentComponent);
},
created() {
this.updateCurrentComponent();
}
}
Country Component
<template>
<div style="display:flex;height:100%">
<d-dotloader v-if="componentLoading" />
<div id="parent" class="list-manager" v-if="!componentLoading">
<div class="list-header">
<div class="bulk-action" :class="{'hide': showTop}" >
<div class="pull-left">
Countries
</div>
<!-- /pull-left -->
<div class="pull-right">
<d-button #click.native = "addCountry();"><i class="icon icon-sm"></i><span>New</span></i></d-button>
</div>
</div>
<!-- /bulk-action -->
<div class="bulk-action" :style ="{display:(showTop)?'block!important':'none!important'}" >
<div class="btn-toolbar">
<d-check field-class="check" v-model="selectAll" wrapper-class="field-check field-check-inline" label-position="right" label="" value="sel" #click.native = "toggleSelectAll();"/>
<d-button :is-loading="isLoading" #click.native = "deleteCountry();">Delete<i class="icon icon-sm" name="trash-2"></i></d-button>
<!-- <div class="pull-right mt5"><div class="green-bubble"></div>{{SelectedItems}}</div> -->
<d-button #click.native = "closeBulkToolBar();">close<i class="icon icon-sm" name="x"></i></d-button>
</div>
</div>
<!-- /bulk-action -->
</div>
<d-dotloader v-if="subListComponentLoading" />
<d-list-items :data="fetchData" #rowClick="changeCountryView" ref="itemsTable">
<d-list-cell column-class="list-item-check" :column-styles="{width: '40px'}" type="selectAll">
<template scope="row">
<div class="field-check field-check-inline" #click.stop="toggleSelect(row.rowIndex)" >
<input type="checkbox" class="check" :id="row.id" :value="row.id" :checked="row.selectAll">
<label></label>
</div>
<d-button #click.native = "editCountry(row.id);">Edit</d-button>
</template>
</d-list-cell>
<d-list-cell column-class="list-item-content">
<template scope="row">
<div class="list-item-content">
<div class="list-item-title">
<div class="pull-right">{{row.ISO_Code}}</div>
<div title="" class="pull-left">{{row.country_name}}</div>
</div>
<div class="list-item-meta">
<div class="pull-right">{{row.Default_Currency}} | {{row.Call_prefix}} </div>
<div class="pull-left">{{row.Zone}}</div>
</div>
<span class="list-item-status enabled"></span>
</div>
</template>
</d-list-cell >
</d-list-items>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { ROAST_CONFIG } from '../../../../../config/config.js';
var baseUrl = ROAST_CONFIG.API_URL;
export default {
data () {
return {
SelectedItems:"",
isLoading:false,
show:true,
searchBy: '',
activeSearch: '',
showTop: false,
selectAll : false,
componentLoading:true,
subListComponentLoading:false,
showModal: false,
form :{
country_name: '',
isCountryEnabled: true,
}
}
},
methods: {
async fetchData ({search, page, filter, sort,rows}) {
let resData;
let vm = this;
axios.defaults.headers.common['Authorization'] = "Bearer "+localStorage.getItem('token');
const res = await axios.post(baseUrl+'/country/fetch',{search, page, filter, sort,rows})
.then((response) => {
if( (typeof(response) != 'undefined') && (typeof(response.data) != 'undefined') && (typeof(response.data.fetch) != 'undefined')){
return response.data.fetch;
}
});
return res;
},
toggleSelect(rowId){
if(typeof(this.$refs.itemsTable.rows[rowId]) != 'undefined'){
this.$refs.itemsTable.rows[rowId].selectAll = !this.$refs.itemsTable.rows[rowId].selectAll;
let data = this.$refs.itemsTable.rows;
let status = false;
let selectAllStatus = true;
let items = 0;
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')&&(data[i].selectAll)){
items++;
this.SelectedItems = items +" Selected Items";
status = true;
}
if((typeof(data[i])!= 'undefined')&&(!data[i].selectAll)){
selectAllStatus = false;
}
this.showTop = status
}
}
},
toggleSelectAll(){
this.selectAll = !this.selectAll;
let items = 0;
let data = this.$refs.itemsTable.rows;
let status = false;
let rowId = '1'
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')){
items++;
this.SelectedItems = items +" Selected Items";
status = this.selectAll;
data[i].selectAll = status;
}
}
this.showTop = status
},
closeBulkToolBar(){
this.SelectedItems = "";
this.showTop = false;
},
}
}
State Component
<template>
<div style="display:flex;height:100%">
<d-dotloader v-if="componentLoading" />
<div id="parent" class="list-manager" v-if="!componentLoading">
<div class="list-header">
<div class="bulk-action" :class="{'hide': showTop}" >
<div class="pull-left">
States
</div>
<!-- /pull-left -->
<div class="pull-right">
<d-button #click.native = "addState();"><i class="icon icon-sm"></i><span>New</span></i></d-button>
</div>
</div>
<!-- /bulk-action -->
<div class="bulk-action" :style ="{display:(showTop)?'block!important':'none!important'}" >
<div class="btn-toolbar">
<d-check field-class="check" v-model="selectAll" wrapper-class="field-check field-check-inline" label-position="right" label="" value="sel" #click.native = "toggleSelectAll();"/>
<d-button :is-loading="isLoading" #click.native = "deleteState();">Delete<i class="icon icon-sm" name="trash-2"></i></d-button>
<!-- <div class="pull-right mt5"><div class="green-bubble"></div>{{SelectedItems}}</div> -->
<d-button #click.native = "closeBulkToolBar();">close<i class="icon icon-sm" name="x"></i></d-button>
</div>
</div>
<!-- /bulk-action -->
</div>
<d-dotloader v-if="subListComponentLoading" />
<d-list-items :data="fetchData" #rowClick="changeStateView" ref="itemsTable">
<d-list-cell column-class="list-item-check" :column-styles="{width: '40px'}" type="selectAll">
<template scope="row">
<div class="field-check field-check-inline" #click.stop="toggleSelect(row.rowIndex)" >
<input type="checkbox" class="check" :id="row.id" :value="row.id" :checked="row.selectAll">
<label></label>
</div>
<d-button #click.native = "editState(row.id);">Edit</d-button>
</template>
</d-list-cell>
<d-list-cell column-class="list-item-content">
<template scope="row">
<div class="list-item-content">
<div class="list-item-title">
<div class="pull-right">{{row.ISO_Code}}</div>
<div title="" class="pull-left">{{row.state_name}}</div>
</div>
<div class="list-item-meta">
<div class="pull-left">{{row.country_name}} </div>
<div class="pull-right">{{row.Zone}}</div>
</div>
<span class="list-item-status enabled"></span>
</div>
</template>
</d-list-cell >
</d-list-items>
</div>
<state-add></state-add>
<state-edit></state-edit>
</div>
</template>
<script>
import axios from 'axios'
import { ROAST_CONFIG } from '../../../../../config/config.js';
var baseUrl = ROAST_CONFIG.API_URL;
export default {
data () {
return {
SelectedItems:"",
isLoading:false,
show:true,
searchBy: '',
activeSearch: '',
showTop: false,
selectAll : false,
componentLoading:true,
subListComponentLoading:false,
showModal: false,
form :{
country_name: '',
isCountryEnabled: true,
}
}
},
methods: {
async fetchData ({search, page, filter, sort,rows}) {
let resData;
let vm = this;
axios.defaults.headers.common['Authorization'] = "Bearer "+localStorage.getItem('token');
const res = await axios.post(baseUrl+'/state/fetch',{search, page, filter, sort,rows})
.then((response) => {
if( (typeof(response) != 'undefined') && (typeof(response.data) != 'undefined') && (typeof(response.data.fetch) != 'undefined')){
return response.data.fetch;
}
});
return res;
},
changeStateView(row){
if(typeof(this.$children[7]) != 'undefined'){
this.$parent.stateId = row.id;
this.viewComponent = "state-main";
this.$children[7].readState(this.$parent.stateId);
this.$router.push({name:"StatesView", params: {id:row.id}});
}
},
toggleSelect(rowId){
if(typeof(this.$refs.itemsTable.rows[rowId]) != 'undefined'){
this.$refs.itemsTable.rows[rowId].selectAll = !this.$refs.itemsTable.rows[rowId].selectAll;
let data = this.$refs.itemsTable.rows;
let status = false;
let selectAllStatus = true;
let items = 0;
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')&&(data[i].selectAll)){
items++;
this.SelectedItems = items +" Selected Items";
status = true;
}
if((typeof(data[i])!= 'undefined')&&(!data[i].selectAll)){
selectAllStatus = false;
}
this.showTop = status
}
}
},
toggleSelectAll(){
this.selectAll = !this.selectAll;
let items = 0;
let data = this.$refs.itemsTable.rows;
let status = false;
let rowId = '1'
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')){
items++;
this.SelectedItems = items +" Selected Items";
status = this.selectAll;
data[i].selectAll = status;
}
}
this.showTop = status
},
closeBulkToolBar(){
this.SelectedItems = "";
this.showTop = false;
},
}
}
Without your component codes it will be difficult to accuratly answer but I can give a try. To communicate between your two components that don't have parent/child relationship you can use an EventBus. You have several choices on how to set up your EventBus; you can pass your event through your Vue root instance using $root, or you can create a dedicated Vue component like in this example.
Considering that you already have binded the event countrySelected($event) on each of your country checkbox, you could achieve to display the related states using something like this:
./components/country/Index
The CountryIndex trigger an event while a country is selected
methods: {
countrySelected(event) {
let currentTarget = event.currentTarget
this.$root.$emit("display-states",currentTarget.countryId);
}
}
./components/state/Index
The stateIndex component listen to the event and display the related state
mounted() {
/**
* Event listener
*/
this.$root.$on("display-states", countryId => {
this.diplayStates(countryId);
});
},
methods: {
displayStates(countryId) {
//your method selecting the states to be diplayed
}