$dispatch select change event to update button focus - alpine.js

Hello I have a select element I would like to set a buttons focus on change. My code is as follows. I have included a $dispatch but not sure I am doing it correctly
function data() {
return {
open: "tab 1",
tabs: [
{
value: "tab 1",
text: "Text for tab 1"
},
{
value: "tab 2",
text: "Text for tab 2"
},
{
value: "tab 3",
text: "Text for tab 3"
}
]
};
}
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine#v2.x.x/dist/alpine.min.js"></script>
<div x-data="data()" x-init="select = open, $watch('select', value => console.log(value))" $watch="('select', value => console.log(select))" class="w-full h-screen bg-gray-200 flex items-center justify-center">
<div class="flex flex-wrap gap-2">
<template x-for="btn in tabs" :key="btn.value">
<button x-on:selectchange=":focus = $event.detail.value === btn.value" x-text="btn.value" #click="open = btn.value, select = btn.value" class="bg-blue-400 px-6 py-4 rounded-sm border-b-4 border-blue-800 uppercase tracking-widest font-bold " :class="{'bg-blue-800 text-blue-300' : btn.value === open}">toggle</button>
</template>
<div class="w-full">
<form action="">
<select class="w-full appearance-none px-6 py-4 border border-blue-800 rounded-sm" name="" id="" x-model="select">
<template x-for="opt in tabs" x-on:change="$dispatch='selectchange',{value:opt.value}" :key="opt.value">
<option x-text="opt.value"></option>
</template>
</select>
</form>
</div>
<div>
<template x-for="txt in tabs">
<p x-text="txt.text" x-show="open === txt.value"></p>
</template>
</div>
</div>
</div>
Thanks in advance

$dispatch is a function you need to call. You also want to use a ref so you can focus your button easily.
<div x-data="data()" x-init="select = open, $watch('select', value => console.log(value))" $watch="('select', value => console.log(select))" class="w-full h-screen bg-gray-200 flex items-center justify-center">
<div class="flex flex-wrap gap-2">
<template x-for="btn in tabs" :key="btn.value">
<button x-ref="button" x-on:selectchange.window="$refs.button.focus()" x-text="btn.value" #click="open = btn.value, select = btn.value" class="bg-blue-400 px-6 py-4 rounded-sm border-b-4 border-blue-800 uppercase tracking-widest font-bold " :class="{'bg-blue-800 text-blue-300' : btn.value === open}">toggle</button>
</template>
<div class="w-full">
<form action="">
<select class="w-full appearance-none px-6 py-4 border border-blue-800 rounded-sm" name="" id="" x-model="select">
<template x-for="opt in tabs" x-on:change="$dispatch('selectchange', {value:opt.value})" :key="opt.value">
<option x-text="opt.value"></option>
</template>
</select>
</form>
</div>
<div>
<template x-for="txt in tabs">
<p x-text="txt.text" x-show="open === txt.value"></p>
</template>
</div>
</div>
</div>

Related

How can I create an array of id's from the selected option in vue-multiselect plugin?

I've created an Laravel Vue SPA app. Right now, I can successfully retrieved the data in object format and can display the names in vue-multiselect (https://vue-multiselect.js.org/) when selected. Also, I can already save the selected options to the database in an object format. My question is how can I save only the id's in an array?
This is the results from api:
[
{
"id": 1,
"name": "Shoe Machine Operators",
"description": "Iusto cupiditate quo veniam.",
"created_at": "2022-03-23T10:23:35.000000Z",
"updated_at": "2022-03-23T10:23:35.000000Z"
},
{
"id": 2,
"name": "Librarian",
"description": "Vero eius quidem quo fugiat.",
"created_at": "2022-03-23T10:23:35.000000Z",
"updated_at": "2022-03-23T10:23:35.000000Z"
}
]
Here's my markup code where my vue-multiselect element is:
<form #submit.prevent="addEmployee">
<div class="flex space-x-6 md:w-3/4">
<div class="md:w-3/6 mb-4 flex-1">
<label class="block text-gray-700 text-sm font-bold mb-2" for="name">First Name</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline required" name="name" id="name" type="text" placeholder="First Name" tabindex="1" v-model="employee.first_name" />
</div>
<div class="md:w-3/6 mb-4 flex-1">
<label class="block text-gray-700 text-sm font-bold mb-2" for="name">Last Name</label>
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline required" name="name" id="name" type="text" placeholder="Last Name" tabindex="2" v-model="employee.last_name" />
</div>
</div>
<div class="flex space-x-6 md:w-3/4">
<div class="md:w-3/6 mb-4 flex-1">
<label class="block text-gray-700 text-sm font-bold mb-2" for="designation_id">Designation</label>
<multiselect v-model="employee.designation_id"
:options="designation_options"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick one or many"
label="name"
track-by="name"
:preselect-first="true"
>
<template slot="selection" slot-scope="{ values, search, isOpen }"><span class="multiselect__single" v-if="values.length && !isOpen">{{ values.length }} options selected</span></template>
</multiselect>
<pre class="language-json"><code>{{ employee.designation_id }}</code></pre>
</div>
<div class="md:w-3/6 mb-4 flex-1">
<label class="block text-gray-700 text-sm font-bold mb-2" for="position_id">Position</label>
<multiselect v-model="employee.position_id"
:options="position_options"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick one or many"
label="name"
track-by="name"
:preselect-first="true"
>
<template slot="selection" slot-scope="{ values, search, isOpen }"><span class="multiselect__single" v-if="values.length && !isOpen">{{ values.length }} options selected</span></template>
</multiselect>
<pre class="language-json"><code>{{ employee.position_id }}</code></pre>
</div>
</div>
<div class="flex space-x-6 md:w-3/4">
<div class="md:w-3/6 mb-4 flex-1">
<label class="block text-gray-700 text-sm font-bold mb-2" for="basic_pay">Basic Pay</label>
<div class="relative rounded">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span class="text-gray-700">₱</span>
</div>
<input class="shadow appearance-none border rounded w-full py-2 pl-8 pr-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline required" name="basic_pay" id="basic_pay" type="number" step="any" placeholder="00.00" tabindex="5" v-model="employee.basic_pay" />
<div class="absolute inset-y-0 right-0 flex items-center"><label for="basic_pay" class="sr-only">Basic Pay</label>
</div>
</div>
</div>
<div class="md:w-3/6 mb-4 flex-1"> </div>
</div>
<button type="submit" class="sm:hidden md:flex bg-blue-500 hover:bg-blue-400 text-white font-bold py-2 px-4 border-b-4 border-blue-700 hover:border-blue-500 rounded outline-none focus:outline-none">Create</button>
</form>
Here's my script:
<script>
export default {
data() {
return {
employee: {},
designation_id: [],
designation_options: [],
}
},
methods: {
addEmployee() {
this.axios.post('/api/employees', this.employee).then(response => (this.$router.push({
name: 'employees'
}))).catch(err => console.log(err)).finally(() => this.loading = false)
},
getDesignationNames() {
this.axios.get('/api/designation').then((res) => {
this.designation_options = res.data;
}).catch(err => console.log(err))
},
},
created: function() {
this.getDesignationNames();
},
}
</script>
Any help is much appreciated.
I tried getting only the id using .map() function but it caused the vue-multiselect to not show the names and return only the first clicked result in an id format.
getDesignationNames() {
this.axios.get('/api/designation').then((res) => {
this.designation_options = res.data.map((a) => {
return a.id;
});
}).catch(err => console.log(err))
},
Use v-model.
Define v-model binding as an array.
Check this example
<script setup>
import { ref } from 'vue'
const data = ref([{id:1,name:"first"},{id:2,name:"second"},{id:3,name:"third"},{id:4,name:"forth"},{id:5,name:"fifth"}])
const selected = ref(["2","4"])
</script>
<template>
<select v-model="selected" multiple>
<option v-for="item in data" :value="item.id">{{item.name}}</option>
</select>
<div>Selected: {{selected}}</div>
</template>

Multiple reusable modals vue 3 - how to keep track of paging? be more in control of modal functions

I'm trying to create a reusable modal in Vue 3 using the composition API. I'm finding it difficult due to a lack of knowledge to keep track of the modals that are shown because at the end when I submit my data using Inertia with Laravel I want the errors to display on the relevant modal.
I want to be able to keep track of which modal is open. I want to be able to close specific modals and open specific modals with a method. I would like to display the modal page where errors occur. For example, page 1 of 3 contains validation errors. Or an alternative to clicking next and getting errors on the fly for the current scope?
Any suggestions are welcome.
Here is my reusable modal vue component:
<script setup>
import {onMounted, reactive, watch} from 'vue'
import {Modal} from 'bootstrap'
const modal = reactive({});
let props = defineProps({
id: {
type: String,
required: true,
},
showModal: {
type: Boolean,
default: false,
},
});
onMounted(() => {
modal.value = new Modal('#' + props.id);
})
watch(
() => props.showModal,
show => {
modal.value.show()
},
)
</script>
<template>
<teleport to="body">
<div
:id="id"
class="modal fade"
tabindex="-1"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-centered max_630">
<div class="modal-content">
<div
v-if="$slots.title"
class="modal-header"
>
<h5 class="modal-title">
<slot name="title"/>
</h5>
<a
type="button"
data-bs-dismiss="modal"
aria-label="Close"
><span
class="iconify"
data-icon="ant-design:close-outlined"
/></a>
</div>
<div
v-if="$slots.body"
class="modal-body"
>
<slot name="body"/>
</div>
<div
v-if="$slots.footer"
class="modal-footer"
>
<slot name="footer"/>
</div>
</div>
</div>
</div>
</teleport>
</template>
This is how im using the reusable modal by defining 3 of them for 3 pages of inputs and the final page being the submission:
<script setup>
import Modal from '#/Components/Modal';
import {defineExpose, ref} from 'vue'
import {useForm} from "#inertiajs/inertia-vue3";
import BreezeInput from '#/Components/Input.vue';
import BreezeLabel from '#/Components/Label.vue';
import InputError from '#/Components/InputError.vue';
import Button from '#/Components/Button';
const form = useForm({
company: {
brand_name: null,
trading_name: null,
registered_name: null,
reg_no: null,
vat_no: null,
email: null,
telephone: null,
website_url: null
},
address: {
line1: null
}
})
const submit = () => {
form.post(route('dealership.store'), {
onError: (err) => {
// form.errors
},
onSuccess: () => {
}
});
};
let props = defineProps({
showModal: {
type: Boolean,
default: false,
},
});
</script>
<template>
<Button
class="blue_bg_button"
#click="showModal = !showModal"
>
<span
class="iconify"
data-icon="dashicons:plus-alt2"
/>
{{ __('Create Dealership') }}
</Button>
<Modal :id="'page1'" :showModal="showModal">
<template #title>
Create New Dealership
</template>
<template #body>
<h6 class="font_600 mb-4">Dealership Information</h6>
<div class="row">
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="brand_name" value="Brand Name"/>
<BreezeInput id="brand_name" type="text" class="form-control" v-model="form.company.brand_name" placeholder="Brand Name"/>
<InputError :message="form.errors['company.brand_name']"></InputError>
</div>
</div>
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="trading_name" value="Dealership Trading Name"/>
<BreezeInput id="trading_name" type="text" class="form-control" v-model="form.company.trading_name" placeholder="Trading Name"/>
<InputError :message="form.errors['company.trading_name']"></InputError>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="registered_name" value="Dealership Registration Name"/>
<BreezeInput id="registered_name" type="text" class="form-control" v-model="form.company.registered_name" placeholder="Dealership Registration Name"/>
<InputError :message="form.errors['company.registered_name']"></InputError>
</div>
</div>
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="reg_no" value="Registration Number"/>
<BreezeInput id="reg_no" type="text" class="form-control" v-model="form.company.reg_no" placeholder="00/000/000"/>
<InputError :message="form.errors['company.reg_no']"></InputError>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="vat_no" value="VAT Number"/>
<BreezeInput id="vat_no" type="text" class="form-control" v-model="form.company.vat_no" placeholder="4333455555"/>
<InputError :message="form.errors['company.vat_no']"></InputError>
</div>
</div>
<div class="col-6">
</div>
</div>
<hr>
<h6 class="font_600 mb-4">Location</h6>
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="line1" value="Address Line"/>
<BreezeInput id="line1" type="text" class="form-control" v-model="form.address.line1" placeholder="12 Harrington Street, Cape Town, Cape Town City Centre"/>
<InputError :message="form.errors['address.line1']"></InputError>
</div>
<div class="pagination-wrapper">
<div class="pagination">
<span class="active"></span>
<span></span>
<span></span>
</div>
</div>
</template>
<template #footer>
<button type="button" class="blue_bg_button" data-bs-dismiss="page1" data-bs-toggle="modal" data-bs-target="#page2">Next</button>
<button type="button" class="edit_button" data-bs-dismiss="modal">Cancel</button>
</template>
</Modal>
<Modal :id="'page2'">
<template #title>
Create New Dealership
</template>
<template #body>
<h6 class="font_600 mb-4">Dealership Contact Details</h6>
<div class="row">
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="dealer_email" value="Dealership Email"/>
<BreezeInput id="dealer_email" type="email" class="form-control" v-model="form.company.email" placeholder="Dealership Email"/>
<InputError :message="form.errors['company.email']"></InputError>
</div>
</div>
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="telephone" value="Contact Number"/>
<BreezeInput id="telephone" type="text" class="form-control" v-model="form.company.telephone" placeholder="012 234 6789"/>
<InputError :message="form.errors['company.telephone']"></InputError>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-wrapper-block mb-4">
<BreezeLabel class="form-label" for="website_url" value="Dealership Website"/>
<BreezeInput id="website_url" type="text" class="form-control" v-model="form.company.website_url" placeholder="www.dealership.com"/>
<InputError :message="form.errors['company.website_url']"></InputError>
</div>
</div>
<div class="col-6">
</div>
</div>
<div class="pagination-wrapper">
<div class="pagination">
<span class="active"></span>
<span class="active"></span>
<span></span>
</div>
</div>
</template>
<template #footer>
<div class="d-flex align-items-center">
<a href="#" class="gray_7" data-bs-dismiss="modal" data-bs-toggle="modal" data-bs-target="#page1"><span class="iconify prev_ico" data-icon="ant-design:arrow-left-outlined"></span>
Previous</a>
</div>
<div>
<button type="button" class="edit_button" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="blue_bg_button" data-bs-dismiss="modal" data-bs-toggle="modal" data-bs-target="#page3">Next</button>
</div>
</template>
</Modal>
<Modal :id="'page3'">
<template #title>
Create New Dealership
</template>
<template #footer>
<div class="d-flex align-items-center">
<a href="#" class="gray_7" data-bs-dismiss="modal" data-bs-toggle="modal" data-bs-target="#page2"><span class="iconify prev_ico" data-icon="ant-design:arrow-left-outlined"></span>
Previous</a>
</div>
<div>
<button type="button" class="edit_button" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="blue_bg_button" :disabled="form.processing" #click="submit()">Add dealership</button>
</div>
</template>
</Modal>
</template>

Alpine.js bind the change of select back to x-data

I have two anchor tags whose #click directives? update my select options. I would like when the options are changes to update the value of activeTab to be either 0 or 1. I've been trying #change but no joy. Thanks in advance
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine#v2.x.x/dist/alpine.min.js"></script>
<div x-data="{activeTab : window.location.hash ? window.location.hash.substring(1) : 0, lessons:[{id:0,room:'online',description:'Online description'},{id:1,room:'in class',description:'in class description'}]}" x-init="select = lessons[0].room" class="w-full">
<nav class="w-full flex flex-no-wrap justify-between mb-8">
<template x-for="lesson in lessons">
<a href="#" #click.prevent="activeTab = lesson.id; window.location.hash = 0; select = lesson.room" class="focus:outline-none focus:text-teal-800 hover:text-teal-800 meta bold py-1 uppercase mr-1 flex items-center justify-between text-lg w-1/2 border-b-4 focus:border-teal-800 hover:border-teal-800 border-teal-600 tracking-widest text-teal-600"><span x-text="lesson.room"></span><svg class="w-6 h-6" width="6" height="6" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
<path d="m8.5.5-4 4-4-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" transform="translate(6 8)" /></svg></a>
</template>
</nav>
<template x-for="lesson in lessons" :key="lesson.id">
<div x-show="activeTab === lesson.id">
<p x-text="lesson.description" class="text-gray-800 mb-6">Online classes are streaemed to your device. You can atned a yoga class wherever there is a why-fi</p>
</div>
</template>
<form action="">
<fieldset class="border p-4">
<legend class="text-center text-xs uppercase tracking-widest text-orange-800 px-2">choose a classroom</legend>
<select class="relative uppercase text-lg tracking-widest text-teal-800 w-full border border-teal-800 px-5 py-4 focus:outline-none focus:border-shadow rounded" name="" id="" x-model="select">
<template x-for="lesson in lessons" :key="lesson.id">
<option :id="lesson.id"><span x-text="lesson.room"></span></option>
</template>
</select>
</fieldset>
</form>
</div>
Here is a simplified version of your code that works. I didn't really want to change your code, but I had to simplify it a bit to comprehend it. For example, activeTab and select seemed redundant, and I didn't understand the part about window.location.hash.
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
<div
x-data="{
activeTab: 0,
lessons: [
{ id: 0, room: 'online', description: 'Online description' },
{ id: 1, room: 'in class', description: 'In class description' }
],
}"
>
<nav>
<template x-for="lesson in lessons">
<a
href="#"
#click.prevent="window.location.hash = activeTab = lesson.id"
:style="activeTab === lesson.id ? 'font-weight:bold' : 'text-decoration:none'"
>
<span x-text="lesson.room"></span></a>
</template>
</nav>
<template x-for="lesson in lessons">
<div x-show="activeTab === lesson.id">
<p x-text="lesson.description"></p>
</div>
</template>
<form>
<fieldset>
<legend>choose a classroom</legend>
<select x-model.number="activeTab">
<template x-for="lesson in lessons">
<option :value="lesson.id" x-text="lesson.room"></option>
</template>
</select>
</fieldset>
</form>
</div>
JSFiddle
I believe the problem was a strict equality comparison (===) between values which were sometimes strings and sometimes integers. For example, "1" === 1 is never true.
I think there were two problems with your original code:
select wasn't declared part of x-data, thus it wasn't christened a "reactive" property, thus changing the value of the <select> had no actual effect
fix this by adding something like select: null to your x-data
that aside, there was still nothing to trigger the update of activeTab when the <select> was changed
binding lesson.id to the <option> values and the <select>'s value to activeTab with x-model.number (forgoing the select variable entirely) is how I chose to address this problem
this way the x-shows for the active tab trigger directly from a change to the <select>'s value
This didn't apply in your code as originally written, but keep in mind that an x-model on a <select> will always return strings, unless you use x-model.number to coerce to integer.

How do you use confirm dialogues in a custom Laravel Nova tool?

Is it possible to use the built in Laravel Nova confirm dialogue in your own tool? All I would like to use is interact with it how Nova does itself.
The docs are quite light on the JS topic, as the only built in UI you seem to be able to work with is the toasted plugin: https://nova.laravel.com/docs/1.0/customization/frontend.html#javascript
You can use <modal> component whenever you want.
Here is how it work internally in Nova:
<template>
<modal
data-testid="confirm-action-modal"
tabindex="-1"
role="dialog"
#modal-close="handleClose"
class-whitelist="flatpickr-calendar"
>
<form
autocomplete="off"
#keydown="handleKeydown"
#submit.prevent.stop="handleConfirm"
class="bg-white rounded-lg shadow-lg overflow-hidden"
:class="{
'w-action-fields': action.fields.length > 0,
'w-action': action.fields.length == 0,
}"
>
<div>
<heading :level="2" class="border-b border-40 py-8 px-8">{{ action.name }}</heading>
<p v-if="action.fields.length == 0" class="text-80 px-8 my-8">
{{ __('Are you sure you want to run this action?') }}
</p>
<div v-else>
<!-- Validation Errors -->
<validation-errors :errors="errors" />
<!-- Action Fields -->
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:errors="errors"
:resource-name="resourceName"
:field="field"
/>
</div>
</div>
</div>
<div class="bg-30 px-6 py-3 flex">
<div class="flex items-center ml-auto">
<button
dusk="cancel-action-button"
type="button"
#click.prevent="handleClose"
class="btn text-80 font-normal h-9 px-3 mr-3 btn-link"
>
{{ __('Cancel') }}
</button>
<button
ref="runButton"
dusk="confirm-action-button"
:disabled="working"
type="submit"
class="btn btn-default"
:class="{
'btn-primary': !action.destructive,
'btn-danger': action.destructive,
}"
>
<loader v-if="working" width="30"></loader>
<span v-else>{{ __('Run Action') }}</span>
</button>
</div>
</div>
</form>
</modal>
</template>
<script>
export default {
props: {
working: Boolean,
resourceName: { type: String, required: true },
action: { type: Object, required: true },
selectedResources: { type: [Array, String], required: true },
errors: { type: Object, required: true },
},
/**
* Mount the component.
*/
mounted() {
// If the modal has inputs, let's highlight the first one, otherwise
// let's highlight the submit button
if (document.querySelectorAll('.modal input').length) {
document.querySelectorAll('.modal input')[0].focus()
} else {
this.$refs.runButton.focus()
}
},
methods: {
/**
* Stop propogation of input events unless it's for an escape or enter keypress
*/
handleKeydown(e) {
if (['Escape', 'Enter'].indexOf(e.key) !== -1) {
return
}
e.stopPropagation()
},
/**
* Execute the selected action.
*/
handleConfirm() {
this.$emit('confirm')
},
/**
* Close the modal.
*/
handleClose() {
this.$emit('close')
},
},
}
</script>
Here is simplified example:
<modal>
<form
autocomplete="off"
class="bg-white rounded-lg shadow-lg overflow-hidden"
>
<div>
<heading :level="2" class="border-b border-40 py-8 px-8">test</heading>
test
</div>
<div class="bg-30 px-6 py-3 flex">
<div class="flex items-center ml-auto">
<button
type="button"
class="btn text-80 font-normal h-9 px-3 mr-3 btn-link"
>
{{ __('Cancel') }}
</button>
<button
ref="runButton"
type="submit"
class="btn-danger"
>
<span>{{ __('Run Action') }}</span>
</button>
</div>
</div>
</form>
</modal>
You need to create a new component in the same folder of Tool.vue
I'll attach the component I used here
Then in the "handleConfirm" method, you can add a Ajax call to API
You can add you logic in that API.
You can find the API file in path, ToolName/routes/api.php
/* CustomModal.vue */
<template>
<modal tabindex="-1" role="dialog" #modal-close="handleClose">
<form #keydown="handleKeydown" #submit.prevent.stop="handleConfirm" class="bg-white rounded-lg shadow-lg overflow-hidden w-action">
<div>
<heading :level="2" class="border-b border-40 py-8 px-8">Confirm action</heading>
<p class="text-80 px-8 my-8"> Are you sure you want to perform this action? </p>
</div>
<div class="bg-30 px-6 py-3 flex">
<div class="flex items-center ml-auto">
<button type="button" #click.prevent="handleClose" class="btn btn-link dim cursor-pointer text-80 ml-auto mr-6">
Cancel
</button>
<button :disabled="working" type="submit" class="btn btn-default btn-primary">
<loader v-if="working" width="30"></loader>
<span v-else>Confirm</span>
</button>
</div>
</div>
</form>
</modal>
</template>
<script>
export default {
methods: {
handleConfirm() {
// Here you can add an ajax call to API and you can add your logic there.
},
handleClose() {
// Logic to hide the component
},
},
}
</script>
For more detailed explanation : https://medium.com/vineeth-vijayan/how-to-use-confirm-dialogs-in-a-laravel-nova-tool-b16424ffee87

vee-validate: Required only if a condition is met

I'm using Vuejs2 and vee-validate for form validation. It's a great package, however I'm struggling to implement a conditional required field.
When a particular radio option is selected, I want two select fields to be required. And when that radio is not selected, I want the two select fields to be optional.
I've tried using the attach and detach methods. I can successfully detach the validation. And I can see when I attach a field it appears in the fields object. But it's not picked up by the validator.
Here is my code:
<template>
<form class="ui form" role="form" method="POST" action="/activate" v-on:submit.prevent="onSubmit" :class="{ 'error': errors.any() }">
<div class="ui segment">
<h4 class="ui header">Basic Company Information</h4>
<div class="ui message">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
<div class="field" :class="{ 'error': errors.has('name') }">
<div class="ui labeled input">
<label class="ui label" for="name">
Company
</label>
<input id="name" type="text" name="name" v-validate="'required'" v-model="name">
</div>
</div>
<div class="ui error message" v-show="errors.has('name')">
<p>{{ errors.first('name') }}</p>
</div>
<div class="grouped fields" :class="{ 'error': errors.has('organisation_type_id') }">
<label for="organisation_type_id">Organisation type</label>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden" type="radio" name="organisation_type_id" value="1" data-vv-as="organisation type" v-validate="'required'" v-model="organisation_type">
<label>Buyer</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden" type="radio" name="organisation_type_id" value="2" checked>
<label>Seller</label>
</div>
</div>
</div>
<div class="ui error message" v-show="errors.has('organisation_type_id')">
<p>{{ errors.first('organisation_type_id') }}</p>
</div>
<div v-show="organisation_type == '2'">
<div class="field" :class="{ 'error': errors.has('countries[]') }">
<label for="countries">Countries</label>
<select class="ui fluid search dropdown" id="countries" name="countries[]" multiple data-vv-as="countries" v-validate="'required'">
<option v-for="country in countries" :value="country.value">{{ country.text }}</option>
</select>
</div>
<div class="ui error message" v-show="errors.has('countries[]')">
<p>{{ errors.first('countries[]') }}</p>
</div>
<div class="ui message field-description">
<p>Select all the countries you export to.</p>
</div>
<div class="field" :class="{ 'error': errors.has('ciphers[]') }">
<label for="ciphers">Ciphers</label>
<select class="ui fluid search dropdown" id="ciphers" name="ciphers[]" multiple data-vv-as="ciphers" v-validate="'required'">
<option v-for="cipher in ciphers" :value="cipher.value">{{ cipher.text }}</option>
</select>
</div>
<div class="ui error message" v-show="errors.has('ciphers[]')">
<p>{{ errors.first('ciphers[]') }}</p>
</div>
<div class="ui message field-description">
<p>Select all the ciphers you support.</p>
</div>
</div> <!-- End organisation_type_id -->
<button class="ui fluid green button" type="submit">Continue</button>
</div> <!-- .ui.segment -->
</form>
</template>
<script>
export default {
props: ['countriesJson', 'ciphersJson'],
data() {
return {
name: null,
organisation_type: '2',
countries: [],
ciphers: [],
}
},
watch: {
organisation_type: function(value) {
var vm = this
if (value == '2') {
vm.$validator.attach('countries[]', 'required');
const select = document.getElementById('countries');
select.addEventListener('change', function() {
vm.$validator.validate('required', this.value);
});
vm.$validator.attach('ciphers[]', 'required');
const select = document.getElementById('ciphers');
select.addEventListener('change', function() {
vm.$validator.validate('required', this.value);
});
} else {
vm.$validator.detach('countries[]')
vm.$validator.detach('ciphers[]')
}
},
},
mounted() {
this.countries = JSON.parse(this.countriesJson)
this.ciphers = JSON.parse(this.ciphersJson)
},
methods: {
onSubmit: function(e) {
this.$validator.validateAll().then(success => {
e.target.submit()
}).catch(() => {
return
})
}
}
}
</script>
May be you mean something like this?
<input id="name"
type="text"
name="name"
v-validate="{ required: this.isRequired }"
v-model="name">
Where "isRequired" is computed field, which depend from condition
<input id="name"
type="text"
name="name"
v-validate=" isRequired ? 'required' : '' "
v-model="name">
In my case it worked by giving above condition.. Also it is helpful in case of multiple validation rules... e.g. 'required|integer|between:18,99'..
Adding {} will break the expression
you can simply use
v-validate="`required_if:${condition}`"

Resources