Vue.js input validation w VeeValidate - How to avoid onInput action while invalid - validation

I am validating a input field ( required , min length 3 ) with VeeValidate plugin.. it's working fine
but how I can avoid the onInput action to be called ( to avoid commit in store when the input becomes invalid ( as soon as input aria-invalid switch to false )
shortly said : Is there anyway to switch calling/not Calling onInput: 'changeTitle' when the input field aria-invalid is false/true ?
thanks for feedback
ChangeTitleComponent.vue
<template>
<div>
<em>Change the title of your shopping list here</em>
<input name="title" data-vv-delay="1000" v-validate="'required|min:3'" :class="{'input': true, 'is-danger': errors.has('required') }" :value="title" #input="onInput({ title: $event.target.value, id: id })"/>
<p v-show="errors.has('title')">{{ errors.first('title') }}</p>
</div>
</template>
<style scoped>
</style>
<script>
import { mapActions } from 'vuex'
import Vue from 'vue'
import VeeValidate from 'vee-validate'
Vue.use(VeeValidate)
export default {
props: ['title', 'id'],
methods: mapActions({ // dispatching actions in components
onInput: 'changeTitle'
})
}
</script>
vuex/actions.js
import * as types from './mutation_types'
import api from '../api'
import getters from './getters'
export default {
...
changeTitle: (store, data) => {
store.commit(types.CHANGE_TITLE, data)
store.dispatch('updateList', data.id)
},
updateList: (store, id) => {
let shoppingList = getters.getListById(store.state, id)
return api.updateShoppingList(shoppingList)
.then(response => {
return response
})
.catch(error => {
throw error
})
},
...
}
UPDATE
I tried to capture the input value with #input="testValidation) and check for a valid input value (required)
if valid ( aria-invalid: false) then I emit the input value, but the props are not updated in the parent component and the vuex action 'changeTitle' is not triggered
<template>
<div>
<em>Change the title of your shopping list here</em>
<input name="title" ref="inputTitle" data-vv-delay="1000" v-validate="'required'" :class="{'input': true, 'is-danger': errors.has('required') }" :value="title" #input="testValidation({ title: $event.target.value, id: id })"/>
<p v-show="errors.has('title')">{{ errors.first('title') }}</p>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import Vue from 'vue'
import VeeValidate from 'vee-validate'
Vue.use(VeeValidate)
export default {
props: ['title', 'id'],
methods: {
testValidation: function (value) {
const ariaInvalid = this.$refs.inputTitle.getAttribute('aria-invalid')
if (ariaInvalid === 'false') {
this.$nextTick(() => {
this.$emit('input', value) // should change props in parent components
})
} else {
console.log('INVALID !') // do not change props
}
},
...mapActions({
onInput: 'changeTitle' // update store
})
}
}
</script>

like you access the errors collection in the VUE template, you can also access the same errors collection in your testValidation method
so replace
const ariaInvalid = this.$refs.inputTitle.getAttribute('aria-invalid')
with
const ariaInvalid = this.$validator.errors.has('title')
grtz

Related

how to get each data from response api laravel in vue js 3

Need help , i can get all data from response api but having some problem when try to get data (looping data ) from key "get_item_cards" . Here's my response and code in vue js
Response api
<script setup>
<script>
import axios from 'axios'
export default {
name: 'ListNotes',
data() {
return {
cardNotes: [],
}
},
mounted() {
// console.log('Page mounted');
this.getListNotes();
},
methods: {
getListNotes() {
axios.get('http://localhost:8000/api/card').then(res => {
this.cardNotes = res.data.cardNotes
console.log(this.cardNotes);
})
}
}
}
</script>
how the best way to get all data & each data from relationship in vue js 3
Since the this.cardNote returns an array with three elements, you can use loop using v-for and access to the get_item_cards array like below,
<template>
<div>
<div v-for=(note, index) in cardNote>
<p>{{node.cardname}}</p>
<div v-for=(item, key) in note.get_item_cards>
<p>{{item.content}}</p>
</div>
</div>
</div>
</template>
<script setup>
<script>
import axios from 'axios'
export default {
name: 'ListNotes',
data() {
return {
cardNotes: [],
}
},
mounted() {
// console.log('Page mounted');
this.getListNotes();
},
methods: {
getListNotes() {
axios.get('http://localhost:8000/api/card').then(res => {
this.cardNotes = res.data.cardNotes
console.log(this.cardNotes);
})
}
}
}
</script>

How to plug existing Observables into Alpine.js (liveQuery from Dexie.js)

How are existing reactive observables connected to Alpine.js?
The Dexie.js website lists a few examples with React and Svelte but how would I use Dexie.js liveQuery with Alpine.js? Is it as simple as passing the variable to x-data?
You cannot pass directly a liveQuery object to an Alpine.js property because it will lose reactivity. We need to create a small wrapper that updates Alpine.js data when a liveQuery returns new data. Here I provide a small example that uses a products table, the Alpine.js component just lists the products and there's a small form that can add new products to the DB.
Example database definition in db.js:
import Dexie from 'dexie'
export const db = new Dexie('myDatabase')
db.version(1).stores({
products: '++id, name, color',
})
In main.js we make db and liveQuery global:
import Alpine from 'alpinejs'
import { liveQuery } from "dexie"
window.liveQuery = liveQuery
import { db } from './db'
window.db = db
window.Alpine = Alpine
window.Alpine.start()
The example Alpine.js component:
<div x-data="productsComponent">
<div>
<input type="text" x-model="name" placeholder="Name" />
<input type="text" x-model="color" placeholder="Color" />
<button #click="add">Add product</button>
</div>
<div>
<h2>Products</h2>
<template x-for="p in products">
<div x-text="`ID: ${p.id} Name: ${p.name} Color: ${p.color}`"></div>
</template>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('productsComponent', () => ({
products: [],
name: '',
color: '',
observe(dataName, observable) {
const subscription = observable.subscribe({
next: val => {this[dataName] = val}
})
},
init() {
this.observe('products', liveQuery(() => db.products.toArray()))
},
async add() {
const id = await db.products.add({
name: this.name,
color: this.color,
})
this.name = ''
this.color = ''
}
}))
})
</script>
In the observe method we subscribe the specific liveQuery event and update the Alpine.js data when it changes.

Vuejs custom Modal event bus is not firing

I have created my own custom Modal plugin in vuejs to be added to my Laravel 8 app. The problem I am facing is opening the modal.
I have created the plugin in my app.js file
const Modal = {
install (Vue) {
this.event = new Vue()
Vue.prototype.$modal = {
show (modal, params = {}) {
Modal.event.$emit('show', modal, params)
},
$event: this.event
}
}
}
Vue.use(Modal)
I have created two vue components for my modal
<!-- AppModal //-->
<template>
<transition name="modal">
<div v-if="visible">
<div class="app-modal" #click.prevent="$modal.hide(name)"></div>
<div class="app-modal-inner">
close
<slot name="body" :params="params"/>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "AppModal",
data () {
return {
params: {},
visible: false,
}
},
props: {
name: {
required: true,
type: String,
}
},
methods: {
setVisible () {
this.visible = true
},
setHidden () {
this.visible = false
}
},
beforeMount() {
this.$modal.$event.$on('show', (modal, params) => {
if (this.name !== modal) {
return
}
this.params = params
this.setVisible()
})
},
}
</script>
<!-- AppNonMemberRegisterModal //-->
<template>
<app-modal name="register">
<template slot="header">
<h1 class="text-lg-left text-4xl border border-b-2">Register Now</h1>
</template>
<template slot="body" slot-scope="{ params }">
<p>You need to register in order to share, comment and like on the site</p>
</template>
</app-modal>
</template>
<script>
import AppModal from "../AppModal";
export default {
name: "AppNonMemberRegisterModal",
components: { AppModal },
}
</script>
Where I am firing the event I import the AppNonMemberRegisterModal and I have the following click event: #click.prevent="$modal.show('register')".
When the following code is reached Modal.event.$emit('show', modal, params) I get the following error messages in my cnosole console.log(Modal)
Vue 3 removes the Event API (i.e., $on, $off, etc.). The migration guide recommends using tiny-emitter to create your own event bus. That example shows how to create a global bus, but it seems your plugin just needs a local bus, which you could create like this:
// eventBus.js
import Emitter from 'tiny-emitter'
export function createEventBus() {
const emitter = new Emitter()
return {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args)
}
}
Then in your plugin, create a global with app.config.globalProperties, referring to the locally created event bus:
// myPlugin.js
import { createEventBus } from './eventBus'
export default {
install(app) {
const eventBus = createEventBus()
app.config.globalProperties.$modal = {
show (modal, params = {}) {
eventBus.$emit('show', modal, params)
},
$event: eventBus
}
}
}
And install it:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import myPlugin from './myPlugin'
createApp(App).use(myPlugin).mount('#app')
Also be aware that your slot usage in AppRegisterModal.vue needs to be updated to the latest syntax (v-slot or # shorthand):
<app-modal name="register">
<!--
<template slot="header">
-->
<template #header>
<!--
<template slot="body" slot-scope="{ params }">
-->
<template #body="{ params }">
</app-modal>
demo

Laravel vue.js and vuex link body text by id and show in a new component

I am very new to Laravel and Vuex, I have a simple array of post on my page.
test 1
test 2
test 3
I am trying to link the text on the AppPost.vue component and show the post that has been clicked on a new component (AppShowpost.vue) on the same page. I believe I have to get the post by id and change the state? any help would be good. Thank you.
when you click test 1 it will show "test 1" on a new component (AppShowpost.vue)
In side my store timeline.js, I belive I need to get the post by id and change the state ?
import axios from 'axios'
export default {
namespaced: true,
state: {
posts: []
},
getters: {
posts (state) {
return state.posts
}
},
mutations: {
PUSH_POSTS (state, data) {
state.posts.push(...data)
}
},
actions: {
async getPosts ({ commit }) {
let response = await axios.get('timeline')
commit('PUSH_POSTS', response.data.data)
}
}
}
My AppTimeline.vue component
<template>
<div>
<app-post
v-for="post in posts"
:key="post.id"
:post="post"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters({
posts: 'timeline/posts'
})
},
methods: {
...mapActions({
getPosts: 'timeline/getPosts'
})
},
mounted () {
this.getPosts()
}
}
</script>
My AppPost.vue component. I need to link the post.body to display the post in my AppShowpost.vue component.
<template>
<div class="w-full inline-block p-4">
<div class="flex w-full">
<p>
{{ post.body }}
</p>
</div>
</div>
</template>
<script>
export default {
props: {
post: {
required: true,
type: Object
}
}
}
</script>
My AppSowpost.vue component that needs to display the post that is clicked.
<template>
<div>
// Displaypost ?
</div>
</template>
<script>
export default {
// Get post from id ?
}
</script>
Okay you can create a new state in your vuex "current_state", asyou said, you can dispatch a mutation by passing the id to the vuex.
state: {
posts: [],
current_state_id : null
},
In your mutations
set_state_id (state, data) {
state.current_state_id = data;
}
On your app post.vue, you can set a computed property that watches the current state
computed: {
currentState() {
return this.$store.getters["timeline/current_state_id"];
}}
And create a watcher for the computed property to display the current id/post
watch: {
currentState: function(val) {
console.log(val);
},
Maybe this will help you. First I will recommend to use router-link. Read about router link here if your interested. It is very helpful and easy to use. But you will have to define the url and pass parameter on our vue-route(see bellow).
1.You can wrap your post.body in router-link as follow.
//With this approach, you don't need any function in methods
<router-link :to="'/posts/show/' + post.id">
{{ post.body }}
</router-link>
2. In your AppSowpost.vue component, you can find the post in vuex state based on url params as follow.
<template>
<div> {{ thisPost.body }} </div>
</template>
// ****************
computed: {
...mapState({ posts: state=>state.posts }),
// Let's get our single post with the help of url parameter passed on our url
thisPost() { return this.posts.find(p => p.id == this.$route.params.id) || {}; }
},
mounted() { this.$store.dispatch("getPosts");}
3. Let's define our vue route.
path: "posts/show/:id",
name: "showpost",
params: true, // Make sure the params is set to true
component: () => import("#/Components/AppShowPost.vue"),
Your Mutations should look as simple as this.
mutations: {
PUSH_POSTS (state, data) {
state.posts = data;
}
},
Please let me know how it goes.

React Redux Material-UI autocomplete

I am struggling to get the value out of the Material-ui Autocomplete when using redux-form. Has anyone solved this? I am using the exact example from the material-ui Autocomplete https://material-ui.com/components/autocomplete/ I am able to see the list options and it populates after clicking it twice, but I am unable to extract the real value, instead I am returning ({ title : 0 }) instead of the value.
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { Field, reduxForm } from "redux-form";
import { connect } from "react-redux";
class Form extends React.Component {
onSubmit = formValues => {
console.log(formValues);
};
renderTextField = ({
label,
input,
meta: { touched, invalid, error },
...custom
}) => (
<Autocomplete
label={label}
options={this.props.movies}
placeholder={label}
getOptionLabel={option => option.title}
onChange={input.onChange}
{...input}
{...custom}
renderInput={params => (
<TextField {...params} label={label} variant="outlined" fullWidth />
)}
/>
);
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit)}>
<Field
name="propertySelector"
component={this.renderTextField}
label="Select Property"
type="text"
/>
</form>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state);
return {
movies: [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "The Godfather", year: 1972 },
{ title: "Schindler's List", year: 1993 }
]
};
};
Form = reduxForm({
form: "auto_complete"
});
export default connect(mapStateToProps, null)(Form);
Solved by passing in the (event, value) to the onChange props.
onChange={(event, value) => console.log(value)}
From the docs;
Callback fired when the value changes.
Signature:
function(event: object, value: T) => void
event: The event source of the callback.
value: null

Resources