vue 3 send value to ElMessageBox with element ui - vue.js

codes are below, I send String to function clickDetails, and then show a ELMessageBox dialog, but the problem, I can't use String in ELMessageBox, show me error "ESLint: 'imgUrl' is defined but never used.(no-unused-vars)"
<template>
<el-tabs type="border-card" class="demo-tabs">
<el-tab-pane label="List">
<h4>Image List</h4>
<li v-for="item in pixelData.hits" v-bind:key="item.id">
<img :src=item.previewURL #click="clickDetails('show me!')">
<p></p>
</li>
</el-tab-pane>
<el-tab-pane label="Details">Config</el-tab-pane>
<el-tab-pane label="Search">Role</el-tab-pane>
</el-tabs>
</template>
<script setup>
import {reactive,onMounted} from 'vue'
import axiosService from "#/axios_uti/axiosService"
import { ElMessageBox } from 'element-plus'
const pixelData = reactive({
hits:[]
})
const clickDetails = (imgUrl) => {
//console.log("item",imgUrl);
ElMessageBox.confirm(
"<p>imgUrl</p>",
'imgUrl.value',
{
dangerouslyUseHTMLString: true,
},)
}
onMounted(() => {
axiosService.getAll().then(response => {
pixelData.hits = response.data.hits;
pixelData.hits.forEach(item => console.log(item.previewURL));
}).catch(error => {
console.log("error",error)
})
})
</script>

Well the way you use your imgUrl is only a simple string containing "imgUrl" text. Use template literals instead.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

Related

Prop being passed but not updating the component

So I have a function called updateAll(data.items) which should take the array and randomly assign a new name, it appears the data.items is updating when I view in vue dev tools and is being passed to the component but not actually updating the browser
I've also added in my buildData.js this is being used for the same app but in react so was trying to keep it as general as I could
//HomeView.vue
<script setup>
import { reactive, watch } from "vue";
import Hero from '#/components/Hero.vue'
import FunctionButton from '#/components/FunctionButton.vue'
import ItemsContainer from '#/components/ItemContainer.vue';
import * as dataFunc from '#/data/buildData'
const data = reactive({
items: JSON.parse(localStorage.getItem('items')) || []
})
console.log(data.items)
watch(() => data.items,(newValue) => {
localStorage.setItem('items', JSON.stringify(newValue))
}, {deep: true});
</script>
<template>
<div class='w-2/3 mx-auto text-center'>
<Hero HeroText="The one stop shop to store recipes!" />
<main>
<h1 class="text-5xl font-extrabold dark:text-white">Recipe Manager</h1>
<div class='functions gap-4 grid grid-cols-2 md:grid-cols-3 mt-8 mx-auto w-fit'>
<FunctionButton name="Add new recipe" #click="() => data.items = [...data.items,...dataFunc.buildData(1)]"/>
<FunctionButton name="Add 100 recipes" #click="() => data.items = [...data.items,...dataFunc.buildData(100)]"/>
<FunctionButton name="Add 1000 recipes" #click="() => data.items = [...data.items,...dataFunc.buildData(1000)]"/>
<FunctionButton name="Delete all recipes" #click="() => data.items = dataFunc.deleteAll()"/>
<FunctionButton name="Edit all recipes" #click="() => data.items = dataFunc.updateAll(data.items)"/>
</div>
<ItemsContainer :items="data.items"/>
</main>
</div>
</template>
//ItemContainer.vue
<script setup>
import Item from '#/components/Item.vue';
const props = defineProps({
items: {type: Array, default: () => []},
})
</script>
<template>
<div
class='items-container w-full md:w-max bg-gray-800 dark:bg-gray-800 text-white dark:text-black mx-auto mt-12 p-4 space-y-4'>
<Item
v-for="item in items"
v-if="items.length > 0"
:key="item.id"
:meal="item"
/>
<p v-else class='text-white'>No items to display</p>
</div>
</template>
I tried to make this as generic as possible as trying to make a similar app on Vue and React so they could share this
import { v4 as uuidv4 } from 'uuid';
function create(){
const data = {
id: uuidv4(),
category: category[random(category.length)],
name: meals[random(meals.length)],
prepTime: randomTime(),
cookTime: randomTime(),
isFav: false
}
return data
}
function random(max){
return Math.round(Math.random() * 1000) % max;
}
const meals = [
"Roast Chicken", "Omelette", "Steak Pie", "Beef Stir Fry", "Fish And Chips", "Tomato And Prawn Risotto", "Pepperoni Pizza", "Cheesy Nachos", "Fajitas", "Baked Potato",
"Full English Breakfast", "Pancakes", "Chocolate Brownies", "Meatballs With Red Pepper Sauce", "Chicken Cesar Salad", "Beef Burger", "Chips","Macaroni Cheese", "Fillet Steak", "Chicken Wings",
"BBQ Ribs", "Tomato Soup", "Prawn Dim Sum", "Pork Gyozas", "Tomato Bruschetta", "Spring Rolls", "Beef Jerky", "Lasagne", "Spagetti Carbonara", "Salmon And Potatoes"
]
const category = [
"Breakfast", "Lunch", "Dinner"
]
function randomTime(){
return Math.round(Math.random() * 30)
}
function buildData(count){
const data = new Array(count);
for(let i = 0; i<count; i++){
data[i] = create();
}
return data;
}
function updateAll(currentArray){
return currentArray.map((item) => {
return{...item, name: meals[random(meals.length)], }
})
}
function updateOne(item){
return{...item, name: meals[random(meals.length)], }
}
function favouriteAll(currentArray = []){
return currentArray.map((item) => {
return {...item, isFav: true}
})
}
function deleteAll(){
const data = []
return data;
}
export {buildData, deleteAll, favouriteAll, updateAll, updateOne};
Check the console for errors.
If I take the functionality from HomeView.vue, your ItemContainer.vue and an Item.vue (which you didn't provided), then it works.
Check the working Vue SFC Playground
The code:
App.vue
<script setup>
import { ref, watch, reactive } from 'vue'
import ItemsContainer from './ItemContainer.vue';
const data = reactive({
items: []
})
console.log(data.items)
watch(() => data.items, (newValue) => {
alert(JSON.stringify(newValue))
}, {deep: true});
const addItem = () => {
data.items.push({ id: data.items.length, name: 'New Item'})
}
</script>
<template>
<ItemsContainer :items="data.items"/><br />
<button type="button" #click="addItem()">Add</button>
</template>
ItemContainer.vue is the same.
Item.vue
<script setup>
const props = defineProps({
meal: {
type: Object,
default: {undefined}
}
})
</script>
<template>
<div>
{{JSON.stringify(meal)}}<br />
</div>
</template>
UPDATE
I've updated the playground with your functions and it still does work well. The problem is somewhere else.

How do I use the mounted function in VueJs 3 composition api?

I am trying out the composition api in vueJs 3. Unfortunately I don't know how to include my mounted code in the setup(). When I put the code into the setup, the javascript tries to access the not yet rendered DOM. Can anyone tell me how I have to rewrite this?
Option api sytle (works)
<script>
export default {
name: "NavBar",
data() {
return {};
},
methods: {},
mounted() {
const bm = document.querySelector('#toggle');
const menu = document.querySelectorAll('nav ul li');
const overlay = document.querySelector('#overlay');
// ...
bm.addEventListener('click', () => {
bm.classList.toggle("active");
overlay.classList.toggle("open");
})
},
}
</script>
<template>
<header>
<div class="button_container" id="toggle">
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
<div class="overlay" id="overlay">
<nav class="overlay-menu">
<ul>
<li>Home</li>
<!-- ... -->
</ul>
</nav>
</div>
</header>
</template>
First of all, this not a good practice to get a html element via document.querySelector(), see how to bind events in vue3
// bad
<div class="button_container" id="toggle">...</div>
mounted() {
const bm = document.querySelector('#toggle');
bm.addEventListener('click', () => {
bm.classList.toggle("active");
overlay.classList.toggle("open");
})
}
// good
<div class="button_container" id="toggle" #click="toggleContainer">...</div>
methods: {
toggleContainer() {
...
}
}
If you really want to document.querySelector() in setup(), you can use onMounted
import { onMounted } from 'vue';
export default {
setup() {
onMounted(() => {
const bm = document.querySelector('#toggle');
bm.addEventListener('click', () => {
bm.classList.toggle("active");
overlay.classList.toggle("open");
})
});
}
}
// or inside <script setup>
import { onMounted } from 'vue';
onMounted(() => {
....
});
But, just as #kissu commented, ref is a better way if you have to handle the html tag directly in vue
<div
ref="toggler">
...
</div>
<script setup>
import { ref, onMounted } from 'vue';
const toggler = ref(null);
onMounted(() => {
console.log(toggler.value) // <div></div>
});
</script>
But none of the above follow the concept of Vue, which is Vue is driven by data.
Since you seem to create a click event listener which will effect the class in html, here is the Vue way:
<template>
<header>
<div
class="button_container"
id="toggle"
:class="{
'active': isActiveBm
}"
#click="toggle">
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
<div
class="overlay"
id="overlay"
:class="{
'open': isOpenOverlay
}">
<nav class="overlay-menu">
<ul>
<li>Home</li>
<!-- ... -->
</ul>
</nav>
</div>
</header>
</template>
<script setup>
import { ref } from 'vue';
const isActiveBm = ref(false);
const isOpenOverlay = ref(false);
const toggle = () => {
isActiveBm.value = !isActiveBm.value;
isOpenOverlay.value = !isOpenOverlay.value;
}
</script>

How do i get a Vue Compsable to only work with a single target item in a list

I have a composable that takes a function and calls the function upon the long-press of a component that uses it(the composable). however, if I use it in a list of components, it seems to call the function on all the components instead of only the component/item I long-pressed in the list. How do I make it so that the function is only called on the target component/item?
The composable(/composables/useLongPress)
import { ref, onMounted, onUnmounted } from 'vue';
const longPress = (binding) => {
const isHeld = ref(false);
const activeHoldTimer = ref(null);
const onHoldStart = () => {
isHeld.value = true;
activeHoldTimer.value = setTimeout(() => {
if (isHeld.value) {
binding();
}
}, 1000);
};
const onHoldEnd = () => {
isHeld.value = false;
if (activeHoldTimer.value) {
clearTimeout(activeHoldTimer.value);
}
};
onMounted(() => {
window.addEventListener('mousedown', onHoldStart);
window.addEventListener('touchstart', onHoldStart);
window.addEventListener('click', onHoldEnd);
window.addEventListener('mouseout', onHoldEnd);
window.addEventListener('touchend', onHoldEnd);
window.addEventListener('touchcancel', onHoldEnd);
});
onUnmounted(() => {
window.removeEventListener('mousedown', onHoldStart);
window.removeEventListener('touchstart', onHoldStart);
window.removeEventListener('click', onHoldEnd);
window.removeEventListener('mouseout', onHoldEnd);
window.removeEventListener('touchend', onHoldEnd);
window.removeEventListener('touchcancel', onHoldEnd);
});
};
export default longPress;
The child component/item (/InvoiceListItem.vue)
<template>
<div class="grid grid-cols-2 py-4">
<div>
<p class="text-lg font-medium">{{ invoice.client }}</p>
<p class="mt-2 text-xs font-light text-gray-500">Due {{ invoice.due }}</p>
</div>
<div class="flex flex-col items-end">
<p class="text-lg font-bold">${{ invoice.amount }}</p>
<base-pill
:variant="invoice.status"
:label="capitalize(invoice.status)"
class="mt-2"
/>
</div>
</div>
</template>
<script setup>
import BasePill from '../../BasePill.vue';
import { capitalize } from '../../../utils';
import useLongPress from '../../../composables/useLongPress';
const props = defineProps({
invoice: {
type: Object,
required: true,
},
});
const log = () => console.log(props.invoice);
useLongPress(log);
</script>
<style lang="postcss" scoped></style>
The parent/list of components (/InvoiceList.vue)
<template>
<div class="divide-y">
<div v-for="(invoice, index) in invoices" :key="index">
<invoice-list-item :invoice="invoice" />
</div>
</div>
</template>
<script setup>
import InvoiceListItem from './InvoiceListItem.vue';
defineProps({
invoices: {
type: Array,
default: () => [],
},
});
</script>
<style lang="scss" scoped></style>
when I long press on one invoice item I am supposed to log the value of the one item but instead, I get the value of all items in the list

get value from child component Vuejs

This is my LoggedUser component which return the name of the logged user and its scope. the name will be displayed in the side bar and the scope will be used to display countries whom under the user's scope
<template>
{{ message }}
</template>
<script lang="ts">
import { onMounted, ref } from 'vue';
export default {
name: "LoggedUser",
setup() {
const message = ref('You are not logged in!');
const scope = ref ('');
onMounted(async () => {
let token = '??';
const response = await fetch('https://localhost:44391/api/Auth/User', {
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' +
token },
credentials: 'include'
});
const content = await response.json();
message.value = `Hi ${content.name}`;
scope.value = `${content.scope}`;
});
return {
message,
scope
}
}
}
{{message}}is used in the sideBar component but i need scope in my Home.vue to use it in a test.
Here is my Home.vue component
<template>
<div class="container w-75" v-show="showGrid">
<search-bar v-show="searchbar"></search-bar>
<div class="row" style="width:900px; height:900px; padding-left:200px">
<div class="col-md-4" v-for="country of countries" v-bind:key="country">
<div class="card p-3" style="cursor:pointer">
<router-link :to="{ path: '/FetchData', query: { query: country.countryName }}">
<div class="d-flex flex-row mb-3">
<div class="d-flex flex-column ml-2"><span>{{country.countryId}}</span></div>
</div>
<h6 style="text-align:left">{{country.countryName}}</h6>
</router-link>
<div class="d-flex justify-content-between install mt-3">
</div>
</div>
<br /><span v-if="!countries"><img src="../assets/loader.gif" /></span><br />
</div>
this is the vue part. I want to test user scope == country scope
<script>
import axios from 'axios'
import SearchBar from './SearchBar.vue'
import SideBar from './SideBar.vue'
import LoggedUser from './LoggedUser.vue'
import swal from 'sweetalert';
import '#trevoreyre/autocomplete-vue/dist/style.css'
export default {
name: "Home",
components: {
SearchBar,
SideBar,
LoggedUser
},
data() {
return {
countries: [],
showGrid: true,
}
},
methods: {
getCountries() {
let country = this.$route.query.query
if (!country) {
axios.get("https://localhost:44391/api/Pho/GetCountries")
.then(res => this.countries = res.data)
} else {
axios.get("https://localhost:44391/api/Pho/GetCountries?country=" + this.$route.query.query)
.then(res => this.countries = res.data);
this.searchbar = false;
}
},
I need to get scope value in Home.vue from LoggedUser.vue. How could i do it?
you have multiple options, one to put the api call to home, second option would be to use composition api and share a common state between these components and the third option would be to use a store management tool such as pinia or vuex.
Composition api would probably be the simplest solution. You basically just need to set a variable outside of the function that will be used in setup, short code example:
const cart = ref({})
function useCart () {
// super complicated cart logic
return {
cart: computed(() => cart.value)
}
}
you still need to adjust this snippet to your needs it was just to show you a way
you can watch this talk by Vanessa Otto to hear more about how it works: https://www.youtube.com/watch?v=MgtQ9t74mhw

(Vue) I have problems reusing references from a composable function

I hope it is okay that I included my full code. Otherwise it would be difficult to understand my question.
I have made a composable function for my Vue application, which purpose is to fetch a collection of documents from a database.
The composable looks like this:
import { ref, watchEffect } from 'vue'
import { projectFirestore } from '../firebase/config'
const getCollection = (collection, query) => {
const documents = ref(null)
const error = ref(null)
let collectionRef = projectFirestore.collection(collection)
.orderBy('createdAt')
if (query) {
collectionRef = collectionRef.where(...query)
}
const unsub = collectionRef.onSnapshot(snap => {
let results = []
snap.docs.forEach(doc => {
doc.data().createdAt && results.push({ ...doc.data(), id: doc.id })
})
documents.value = results
error.value = null
}, (err) => {
console.log(err.message)
document.value = null
error.value = 'could not fetch data'
})
watchEffect((onInvalidate) =>{
onInvalidate(() => unsub());
});
return {
documents,
error
}
}
export default getCollection
Then I have a component where I store the data from the database
<template>
<div v-for="playlist in playlists" :key="playlist.id">
<div class="single">
<div class="thumbnail">
<img :src="playlist.coverUrl">
</div>
<div class="info">
<h3>{‌{ playlist.title }}</h3>
<p>created by {‌{ playlist.userName }}</p>
</div>
<div class="song-number">
<p>{‌{ playlist.songs.length }} songs</p>
</div>
</div>
</div>
</template>
<script>
export default {
// receiving props
props: ['playlists'],
}
</script>
And finally, I output the data inside the main Home component, where I use the documents and error reference from the composable file.
<template>
<div class="home">
<div v-if="error" class="error">Could not fetch the data</div>
<div v-if="documents">
<ListView :playlists="documents" />
</div>
</div>
</template>
<script>
import ListView from '../components/ListView.vue'
import getCollection from '../composables/getCollection'
export default {
name: 'Home',
components: { ListView },
setup() {
const { error, documents } = getCollection('playlists')
return { error, documents }
}
}
</script>
That is all well and good.
But now I wish to add data from a second collection called "books", and the idea is to use the same composable to fetch the data from that collection as well,
but the problem is that inside the Home component, I cannot use the references twice.
I cannot write:
<template>
<div class="home">
<div v-if="error" class="error">Could not fetch the data</div>
<div v-if="documents">
<ListView :playlists="documents" />
<ListView2 :books="documents" />
</div>
</div>
</template>
export default {
name: 'Home',
components: { ListView, ListView2 },
setup() {
const { error, documents } = getCollection('playlists')
const { error, documents } = getCollection('books')
return { error, documents }
}
}
This will give me an error because I reference documents and error twice.
So what I tried was to nest these inside the components themselves
Example:
<template>
<div v-for="playlist in playlists" :key="playlist.id">
<div class="single">
<div class="thumbnail">
<img :src="playlist.coverUrl">
</div>
<div class="title">
{{ playlist.title }}
</div>
<div class="description">
{{ playlist.description }}
</div>
<div>
<router-link :to="{ name: 'PlaylistDetails', params: { id: playlist.id }}">Edit</router-link>
</div>
</div>
</div>
</template>
<script>
import getCollection from '../composables/getCollection'
export default {
setup() {
const { documents, error } = getCollection('playlists')
return {
documents,
error
}
}
}
</script>
This does not work either.
I will just get a 404 error if I try to view this component.
So what is the correct way of writing this?
Try out to rename the destructed fields like :
const { error : playlistsError, documents : playlists } = getCollection('playlists')
const { error : booksError, documents : books } = getCollection('books')
return { playlistsError, playlists , booksError , books }