Emit a second variable from Vue component to parent (vue / inertiajs) - vue.js

I have the following component, I would like to emit a second value isDeleted to the parent:
<template>
<div class="flex items-center">
<button class="btn my-auto inset-y-0 ml-1 mr-3"
:class="isDeleted ? 'btn-danger' : 'btn-secondary'"
#click="switchMode"
>
<Icon name="Trash" class="w-4 h-4" />
</button>
<div class="w-56 relative text-slate-500 mr-2">
<input
id="tabulator-html-filter-value"
name="search"
type="text"
autocomplete="off"
class="form-control w-56 box pr-10"
aria-label="default input example"
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
placeholder="Search..."
/>
<Icon name="Search" class="w-4 h-4 absolute my-auto inset-y-0 mr-3 right-0"/>
</div>
<button
id="tabulator-html-filter-reset"
type="button"
class="btn btn-secondary w-full sm:w-16 mt-2 sm:mt-0 sm:ml-1"
#click="$emit('reset')"
>
Reset
</button>
</div>
</template>
<script>
import Dropdown from '#/components/Dropdown/Dropdown'
import Icon from '#/components/Icons/Icons'
export default {
components: {
Dropdown,
Icon
},
props: {
modelValue: String,
maxWidth: {
type: Number,
default: 300,
},
},
data() {
return {
isDeleted: null,
}
},
emits: ['update:modelValue', 'reset'],
methods: {
switchMode() {
this.isDeleted = this.isDeleted ? null : 'only';
console.log('1'+ this.isDeleted);
}
}
}
</script>
The parent has the following code:
<search-filter v-model="form.search" class="mr-4 w-full max-w-md" #reset="reset" />
I tried following this example: https://dev.to/codybontecou/vuejs-custom-event-emit-multiple-values-221b to add emit the second value isDeleted without success.
Thanks

In child
<button
id="tabulator-html-filter-reset"
type="button"
class="btn btn-secondary w-full sm:w-16 mt-2 sm:mt-0 sm:ml-1"
#click="$emit('reset', isDeleted /* <---- Pass your value */)"
>
In parent
<search-filter
v-model="form.search"
class="mr-4 w-full max-w-md"
#reset="reset($event /* <---- Here got your value */)"
/>
const reset = (isDeleted) =>
{
console.log(isDeleted)
}

Related

AxiosError: Request failed with status code 403

I tried using this api-key provided by RapidAPI for my code. I made sure that the API key entered is correct. I was previously using the .env file,'X-RapidAPI-Host': process.env.COIN_RANKING_HOST, for both the api host and api key but to make sure that the error is not caused by the .env file not loading, I entered both directly. This is my code:
import Header from '../components/Header'
import PortfolioChart from '../components/PortfolioChart'
import BuyTokens from '../components/BuyTokens'
import Notice from '../components/Notice'
import Asset from '../components/Asset'
import { BiDotsHorizontalRounded } from 'react-icons/bi'
import { AiOutlinePlus } from 'react-icons/ai'
import axios from 'axios'
const styles = {
wrapper: 'w-screen h-screen flex flex-col',
mainContainer: 'w-2/3 h-full m-auto flex mt-16',
leftMain: 'flex flex-col w-3/4 h-full p-6 overflow-y-scroll',
portfolioAmountContainer: 'flex flex-col ',
portfolioAmount: 'text-white text-4xl',
portfolioPercent: 'text-white font-bold text-sm',
pastHour: 'text-gray-400',
chartContainer:
'text-5xl flex justify-center w-full h-1/3 text-white mt-11 mb-11',
buyingPowerContainer:
'w-full border-t mb-24 border-b h-16 border-[#30363b] flex justify-between items-center p-4',
buyingPowerTitle: 'text-white font-bolder text-lg',
buyingPowerAmount: 'text-white font-bolder text-xl',
notice: 'flex border border-[#30363b] mx-11 my-4 p-5 flex-col flex-1',
noticeContainer: 'flex-1',
noticeTitle: 'text-gray-500',
noticeMessage: 'text-white font-bold',
noticeCTA: 'font-bold text-green-500 cursor-pointer mt-5',
rightMain:
'flex flex-col flex-1 h-4/5 bg-[#1E2123] mt-6 rounded-lg overflow-y-scroll noScroll',
rightMainItem: 'flex items-center text-white p-5 border-b border-[#30363b]',
ItemTitle: 'flex-1 font-bold',
moreOptions: 'cursor-pointer text-xl',
}
export default function Home({coins}) {
console.log(coins)
return(
<div className={styles.wrapper}>
<Header />
<div className={styles.mainContainer}>
<div className={styles.leftMain}>
<div className={styles.portfolioAmountContainer}>
<div className={styles.portfolioAmount}>
23 SOLANA
</div>
<div className={styles.portfolioPercent}>
+0.0008(+0.57%)
<span className={styles.pastHour}>
Past Hour
</span>
</div>
</div>
<div>
<div className={styles.chartContainer}>
<PortfolioChart />
</div>
</div>
<div className={styles.buyingPowerContainer}>
<div className={styles.buyingPowerTitle}>Buying Power</div>
<div className={styles.buyingPowerAmount}>12 SOLANA</div>
</div>
<div className={styles.notice}>
<div className={styles.noticeContainer}>
<div className={styles.noticeTitle}>
Send Funds
</div>
<div className={styles.noticeMessage}>
Transfer your funds here.
</div>
<BuyTokens />
</div>
</div>
<Notice />
</div>
<div className={styles.rightMain}>
<div className={styles.rightMainItem}>
<div className={styles.ItemTitle}>
Crypto Currencies
</div>
<BiDotsHorizontalRounded className={styles.moreOptions} />
</div>
<Asset coin={"BTC"} price={0.89} />
<Asset coin={"SOL"} price={0.90} />
<Asset coin={"ETH"} price={-1} />
<Asset coin={"USDC"} price={2} />
<div className={styles.rightMain}>
<div className={styles.ItemTitle}>
Lists
</div>
<AiOutlinePlus className={styles.moreOptions} />
</div>
</div>
</div>
</div>
)
}
export const getStaticProps = async () => {
const options = {
method: 'GET',
url: 'https://coinranking1.p.rapidapi.com/coins',
params: {
referenceCurrencyUuid: 'yhjMzLPhuIDl',
timePeriod: '24h',
tiers: '1',
orderBy: 'marketCap',
orderDirection: 'desc',
limit: '50',
offset: '0',
},
headers: {
'X-RapidAPI-Host': 'coinranking1.p.rapidapi.com',
'X-RapidAPI-Key': '23734db4e2msha5580c4a7b981c0p1557d0jsn9c9dcf2b8505',
},
}
const res = await axios.request(options)
const coins = res.data.data.coins
return {
props: { coins },
}
}
The error showing in the terminal is this.The error was very lengthy and I couldn't post the entire error due to character limit. I have put up the initial two lines and the last two lines of the error though.
error - [AxiosError: Request failed with status code 403] {
code: 'ERR_BAD_REQUEST',
...
data: { message: 'You are not subscribed to this API.' }
},
page: '/'
}
I am unable to figure out why I am getting this error. Please help.
I am subscribed to RapidAPI.

Pass data from component

I'm fairly new to Vue and I'm trying to pass data from a component to a view. I'm not sure if I'm using props right. I have a dialog and when I save, I want to insert the data to the database. I also want to reuse the addCustomer() function that's why I didn't place the function in the component.
pages/customers.vue
<template>
<div>
<div class="items-center justify-between md:flex">
<Heading
title="Customers"
desc="The list of customers or companies you work with."
/>
<button #click="openModal" class="btn-primary">Add New Customer</button>
</div>
<CustomerList class="mt-4" :customers="customers" />
</div>
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
:name="name"
:address="address"
:email="email"
:add-customer="addCustomer"
/>
</template>
<script setup>
const client = useSupabaseClient();
const name = ref("");
const address = ref("");
const email = ref("");
const isOpen = ref(false);
function closeModal() {
isOpen.value = false;
}
function openModal() {
isOpen.value = true;
}
const { data: customers } = await useAsyncData("customers", async () => {
const { data } = await client.from("customers").select("*");
return data;
});
async function addCustomer() {
if (name.value == "" || address.value == "" || email.value == "") return;
const { data } = await client.from("customers").upsert({
name: name.value,
address: address.value,
email: email.value,
});
customers.value.push(data[0]);
name.value = "";
address.value = "";
email.value = "";
closeModal();
}
</script>
components/customer/Dialog.vue
<template>
<Dialog as="div" #close="closeModal" class="relative z-10">
<input type="text" id="name" v-model="name" />
<input type="text" id="address" v-model="address" />
<input type="email" id="email" v-model="email" />
<button type="button" #click="addCustomer">Save</button>
<button type="button" #click="closeModal">Cancel</button>
</Dialog>
</template>
<script setup>
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
</script>
EDIT: The Cancel button in the Dialog works while Save button doesn't.
You cannot bind props directly to the v-model directive, in your case you've to use Multiple v-model bindings
<template>
<Dialog as="div" #close="closeModal" class="relative z-10">
<input type="text" id="name" :value="name" #input="$emit('update:name', $event.target.value)"/>
<input type="text" id="address" :value="adress" #input="$emit('update:address', $event.target.value)" />
<input type="email" id="email" :value="email" #input="$emit('update:email', $event.target.value)" />
<button type="button" #click="$emit('add-customer')">Save</button>
<button type="button" #click="closeModal">Cancel</button>
</Dialog>
</template>
<script setup>
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
defineEmits(['update:name', 'update:email','update:address','add-customer'])
</script>
in parent component :
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
v-model:name="name"
v-model:address="address"
v-model:email="email"
#add-customer="addCustomer"
/>
As addCustomer method is available in parent. What you can do is that emit an event on Save button click from the dialog component and then capture the event in parent and invoke addCustomer method.
I just created a below code snippet with Vue 2.* just for your understanding purpose.
Vue.component('customerdialog', {
data() {
return {
customerDetailObj: {}
}
},
props: ['name', 'address', 'email'],
mounted() {
this.customerDetailObj = {
name: this.name,
address: this.address,
email: this.email
}
},
template: `<div><input type="text" id="name" v-model="customerDetailObj.name" />
<input type="text" id="address" v-model="customerDetailObj.address" />
<input type="email" id="email" v-model="customerDetailObj.email" />
<button type="button" #click="$emit('add-customer', customerDetailObj)">Save</button></div>`
});
var app = new Vue({
el: '#app',
data: {
name: 'Alpha',
address: 'Street 1',
email: 'alpha#testmail.com',
customerList: []
},
methods: {
addCustomer(customerObj) {
// Created user details
this.customerList.push(customerObj);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<CustomerDialog :name="name"
:address="address"
:email="email" #add-customer="addCustomer($event)"></CustomerDialog>
<pre>{{ customerList }}</pre>
</div>
You can defineEmits
<script setup>
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "#headlessui/vue";
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
let emit = defineEmits(["add"])
</script>
Or better with typescript:
<script lang="ts" setup>
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "#headlessui/vue";
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
let emit = defineEmits<{ (name: "add"): void }>()
</script>
Now you can use it in your button:
<button
type="button"
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-red-900 bg-red-100 border border-transparent rounded-md hover:bg-red-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
#click="emit('add')"
>
Save
</button>
Now you can listen to this add event and trigger your addCustomer function
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
:name="name"
:address="address"
:email="email"
#add="addCustomer"
/>
You'll want to use event emitters:
In the dialog:
<button #click="$emit('addCustomer')">save</button>
In customers.vue:
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
:name="name"
:address="address"
:email="email"
#add-customer="addCustomer"
/>

nesting an object inside an object

Looking for some tips on how to nest objects inside objects using a form. My form currently changes the key and value of an object. However, I'm now wanting a second button to be able to create a child (correct termanology?)form input. below you can see an example. I've spent the morning looking at props but I'm unsure if this is the correct way to go, any suggestions are greatly appriciated
{
"color": "black",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,255,1],
"hex": "#000"
}
},
<form id="app">
<h1>
Title goes here
</h1>
<hr>
<div class="row">
<div class="col-xs-2">
<button type="button" v-on:click="addNewObject" class="btn btn-block btn-success">
(Add +) Parent
</button>
</div>
<div class="col-xs-10 text_info">
Click 'Add +' to add an object
</div>
</div>
<div v-for="(object, index) in objects">
<div class="row">
<div class="col-xs-1">
<label> </label>
<button type="button" v-on:click="removeObject(index)" class="btn btn-rem btn-block btn-danger">
Delete
</button>
<button type="button" v-on:click="addNewChildObject()" class="btn btn-rem btn-block btn-success btn-suc">
add { }
</button>
</div>
<div class="form-group col-xs-7">
<div class="test">
<select v-model="object.type" class="selectBox classic">
<option value="" disabled selected hidden>Choose Datatype</option>
<option v-for="type in types"> {{ type }}</option>
</select>
<input v-model="object.name" :name="'objects[' + index + '][name]'" type="string" class="form-control" placeholder="Enter key">
<input v-model="object.dataValue" :name="'objects[' + index + '][dataValue]'" type="string" class="form-control" placeholder="Enter value">
</div>
</div>
</div>
</div>
<hr>
<div>
<pre v-if="seen">{{ mappedObjects }}</pre>
</div>
<button type="button" class="btn-primary" v-on:click="seen = !seen">{{ seen ? 'Click to Hide the JSON' : 'Click to Show the JSON' }}</button>
</form>
const getDefaultObject = () => ({
name: '',
dataValue: '',
type: ''
})
const app = new Vue({
el: '#app',
computed: {
mappedObjects() {
return this.objects.map(({
name,
dataValue,
type
}) => ({
[name]: dataValue,
type
}))
}
},
props: {
},
data() {
return {
seen: false,
types: ['string', 'character', 'number', 'int', 'floating-point', 'boolean', 'date;'],
objects: []
}
},
methods: {
addNewObject: function() {
this.objects.push(getDefaultObject())
},
removeObject: function(index) {
Vue.delete(this.objects, index);
},
addNewChildObject: function () {
}
}
})
If you want n forms with n children just create a model for it following that same structure and pass props to the form.
parent = {
...props,
children: []
}
child = {
...props
}
If the forms are too complex (or a little complex really), split them in separate components and pass children as props.
If you want to use the same form both in parent and children take a look at slots, they will allow you to create flexible layouts.

Vue-formulate - Group item collapsible / toggle collapse

Is there a possibility to make group item collapsible?
<FormulateInput type="group" name="employments" :repeatable="true" label="Employments"
add-label="+ Add Employment" #default="groupProps">
<!-- Clickable area -->
<div class="group text-sm font-semibold py-2 cursor-pointer relative" #click="groupProps.showForm">
....
</div>
<!-- Nested form: must be collapsible accordion -->
<div class="nested-form" v-show="groupProps.showForm">
....
</div>
</FormulateInput>
I thought to add showForm property to the group context.
For this I need to do Custom input types or is there some other way?
If anyone has any other ideas?
Thanks
I figured it out with the gist of #jpschroeder.
CollapsableGroupItem.vue:
<template>
<div class="group-item" :data-is-open="itemId === showIndex">
<div class="group group-item-title text-sm font-semibold py-2 cursor-pointer relative hover:text-blue-400" #click="toggleBody">
<slot name="title" v-bind="groupItem">#{{ context.index }}</slot>
</div>
<div class="group-item-body" v-show="itemId === showIndex">
<slot name="body">
<FormulateInput type="pelleditor" name="description" label="Description"/>
</slot>
</div>
</div>
</template>
<script>
export default {
name: "CollapsableGroupItem",
props: {
context: {
type: Object,
required: true,
},
showIndex: {
type: [Number, String],
required: true,
},
groupItem: {
type: Object,
required: true,
},
},
data () {
return {
itemId: this.context.name + this.context.index
}
},
created: function () {
// show current item
this.$emit("open", this.itemId);
},
methods: {
toggleBody() {
if (this.itemId === this.showIndex) {
// dont show anything
this.$emit("open", -1);
} else {
// show this one
this.$emit("open", this.itemId);
}
},
}
};
FormTemplate.vue:
<CollapsableGroupItem
:context="context"
:show-index="showIndex"
:group-item="educations[context.index]"
#open="showIndex = $event"
>
<template v-slot:title="education">
<span v-if="education.institution || education.degree"
>
{{ education.institution }}
<span v-if="education.institution && education.degree">at</span>
{{ education.degree }}
</span>
...
</template>
<template v-slot:body>
...
</template>
</CollapsableGroupItem>
Maybe it will help someone else or will be useful 😀

Vue $refs undefined when accessing property of element

I have an element part of a modal, like this:
<template>
<portal to="portal-level-3">
<div
v-if="isOpen"
class="fixed z-50 px-4 pb-4 inset-0 flex items-center justify-center"
:class="[isContentHeightGreaterThanHolder ? 'items-baseline' : 'items-center']"
>
<div
class="fixed inset-0 transition-opacity"
enter-active-class="ease-out duration-300"
enter-class="opacity-0"
enter-to-class="scale-100"
leave-active-class="ease-in duration-200"
leave-class="opacity-100"
leave-to-class="opacity-0"
>
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<div
v-if="isOpen"
v-click-outside="closeModal"
ref="refContent"
:class="[
notFullWidth ? '' : 'w-full',
modalWidthClass
]"
class="bg-white rounded-lg overflow-hidden shadow-xl transform transition-all px-4 pt-5 pb-4"
enter-active-class="ease-out duration-300"
enter-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to-class="opacity-100 translate-y-0 sm:scale-100"
leave-active-class="ease-in duration-200"
leave-class="opacity-100 translate-y-0 sm:scale-100"
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div class="flex mb-4">
<div v-if="title" class="flex-1">
<h3 class="text-lg leading-6 font-medium text-brand">
{{ title }}
</h3>
</div>
<div class="pl-3">
<button
#click.prevent="closeModal()"
type="button"
class="text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150"
>
<font-awesome-icon class="h-6 w-6" :icon="['fal', 'times']" />
</button>
</div>
</div>
<slot></slot>
</div>
</div>
</portal>
</template>
In the Vue instance, I watch for the modal opening like this:
watch: {
visible(val) {
this.isOpen = val
},
isOpen(val) {
if (val) {
this.$nextTick(() => {
let holderHeight = this.$el.offsetHeight
let contentHeight = this.$refs.refContent.offsetHeight
this.isContentHeightGreaterThanHolder = contentHeight >= (holderHeight - 30)
})
}
}
},
But this results in:
"TypeError: Cannot read property 'offsetHeight' of undefined"
But if I console.log(this.$refs) I get:
{}
refContent: div.bg-white.rounded-lg.overflow-hidden.shadow-xl.transform.transition-all.px-4.pt-5.pb-4.w-full.max-w-xl
##clickoutsideContext: {id: 5, methodName: "closeModal", documentHandler: Æ’, bindingFn: Æ’}
accessKey: ""
align: ""
ariaAtomic: null
ariaAutoComplete: null
ariaBusy: null
ariaChecked: null
ariaColCount: null
ariaColIndex: null
ariaColSpan: null
ariaCurrent: null
ariaDescription: null
ariaDisabled: null
ariaExpanded: null
....
offsetHeight: 805
Why can I not access this.$refs.refContent.offsetHeight? Thanks!