I submit a new message which is rendered in the table, but the whole table reloads which creates a flicker effect. This started once I decided to use a table, when I was creating dynamic div I did not have this issue.
submit method-
const submit = async () => {
try {
if (!(message.value.length >= 10)) {
alert('The message has to be at least 10 characters')
} else {
const d: Date = new Date();
await api.createBlog("test#1234#" + d.getMilliseconds(), message.value).then(
posts
)
}
} catch (error) {
console.log('Error while submitting:', error)
}
}
The button-
<button type="submit" #click="submit" class="inline-flex justify-center p-2 text-blue-600 rounded-full cursor-pointer hover:bg-blue-100 dark:text-blue-500 dark:hover:bg-gray-600">
<span class="sr-only">message</span>
</button>
What I want is a new row created to populate without the flicker / reload effect.
advice?
If you don't specify the button type, the browser will automatically
set it to 'reset' or 'submit' which causes the page to reload.
Which indicates type="submit" could be the culprit.
So, you can try replacing type="submit" with type="button".
Related
I am not a vue.JS programmer, but have been asked to jump in and make a few changes to an existing app. I am trying to use a computed property to show/hide a DIV.
The problem is the computed property is not being called after the form POST (I put a console.log statement inside the computed method to verify it's not even being called). The findLoginProvider method is invoked successfully and the localStorage items are set... but the DIV is not hidden.
Like I said, I am not a vue.JS programmer and cannot figure out why this isn't working... this is a simplified example of the real page. I really want computed properties to work because I have multiple DIV's to show/hide based on a few computed properties (so manually settings the visibility of a specific DIV is not desired)
HTML:
<div v-if="showFindLoginProvider" :class="{'disabled': isLoading}">
<form #submit.prevent="findLoginProvider" method="POST">
<div>
<label class="block text-gray-700">Email Address</label>
<input
type="email"
name="email"
v-model="email"
placeholder="Enter Email Address"
autofocus
autocomplete
required
/>
</div>
<button type="submit">Continue</button>
</form>
</div>
Methods:
findLoginProvider() {
this.isLoading = true;
UserService
.getLoginProvider(this.email)
.then((response) => {
if (response?.status === 200) {
localStorage.setItem("login_email", this.email);
localStorage.setItem("login_provider", JSON.stringify(response.data));
} else {
this.isError = "Error retrieving login provider";
}});
this.isLoading = false;
},
Computed:
showFindLoginProvider() {
console.log("showFindLoginProvider")
return !this.isLoggedIn && (!this.hasLoginEmail || !this.hasLoginProvider);
},
According to the docs
A computed property will only re-evaluate when some of its reactive dependencies have changed
By saying that in your fnc findLoginProvider you need to change reactive variable to fire showFindLoginProvider run
findLoginProvider() {
this.isLoading = true;
UserService
.getLoginProvider(this.email)
.then((response) => {
if (response?.status === 200) {
// will tell computed to run
this.isLoggedIn = true
// rest of your code
} else {
this.isError = "Error retrieving login provider";
}
});
this.isLoading = false;
},
I have a SearchBar.vue child page with a form in this this code :
<template>
<div>
<form class="search-bar" #submit.prevent="SearchMovies()">
<input
type="text"
placeholder="Effectuez une recherche"
v-model="search"
/>
<button
type="submit"
class="search-input"
#click="$emit('get-movies', movies)"
>
CHERCHER
</button>
</form>
</div>
</template>
And my SearchMovies() function looks like :
setup() {
const search = ref("");
const movies = ref([]);
function SearchMovies () {
if (search.value != "") {
fetch(`${process.env.VUE_APP_API_URL_CALL_TWO}${search.value}`)
.then((response) => response.json())
.then((data) => {
movies.value = data.contents;
search.value = "";
console.log(
"Movies data from SearchBar.vue when fired up: ",
movies.value
);
});
}
this.$emit('get-movies', movies)
}
This is how I have tried to add the emit line
this.$emit('get-movies', movies)
And I receive the emitted data from SearchMovies() function to my parent Home.vue page like this :
<template>
<div>
<router-link to="/" href="/"
><img class="logo-img" alt="App logo" src="../assets/logo.png"
/></router-link>
<SearchBar #get-movies="getMovies($event)" />
<MovieList :movies="movies" />
</div>
</template>
methods: {
getMovies: function (movies) {
(this.movies = movies),
console.log("Movies data from Home.vue when fired up: ",
movies);
},
},
The problem is that I am not getting the movies data and when I console.log it in the Home.vue page
Movies data from Home.vue when fired up: Proxy {}
In your search bar, the #click event is never actually invoking the SearchMovies method. Try converting
<button type="submit" class="search-input" #click="searchMovies">...</button>
You're not exporting the function in your setup, at the bottom of setup
setup (_, {emit}) {
const search = ref("")
const movies = ref([])
const SearchMovies = () => {
const value = await fetch(`${process.env.VUE_APP_API_URL_CALL_TWO}${search.value}`)
const data = await value.json()
movies.value = data.contents
search.value = ""
console.log("Movies data from SearchBar.vue when fired up: ", movies.value);
emit('get-movies', movies.value)
}
return { search, movies, SearchMovies }
}
In your fetch statement, you're going to have some async code issue, the fetch statement will run, but then it will skip the await callbacks in favor of doing this.$emit. I'd convert it to
Then, finally, I wouldn't catch the value in Home.vue with
<SearchBar #get-movies="getMovies($event)" />
Instead, just use #get-movies="getMovies" You don't actually need make it call a function, it will just do it on it's own and I find trying to use the event bus causes confusion sometimes. You only need to use it if you have specific data from the template you could pass into it, like in a v-for loop you could pass in the specific object. Let me know if you need me to clarify anything so you can better understand why it's built like this.
<template>
<div class="search-bar">
<input
type="text"
placeholder="Effectuez une recherche"
v-model="search"
/>
<button
class="search-input"
#click="SearchMovies"
>
CHERCHER
</button>
</div>
</template>
I have a vue component that adds a search bar and search bar functionality. It contains this line:
<input class="input" type="text" placeholder="Address" v-model="searchQuery" v-on:input="(event) => this.$emit('queryChange', event)">
This captures the text in the search bar and emits it.
In my vue, this triggers my updateSearchQuery function:
this.searchQuery = event.data which merely saves the users input in the searchQuery property in my vue. Everything works fine when I do this, until, I make a search and then, make another call using the same this.searchQuery data.
For example, I'm trying to filter results with the search query '956'. I enter it and this call is made: GET /users?cp=1&pp=20&se=956, just like it should. Then after the page loads, if I go to page 2 of the results, this is the call that is made to the server: GET /users?cp=2&pp=20&se=6. Instead of saving 956 as the queryStr in the the view, it only saves the most recent character entered, instead of the entire content of the serch text.
This happens every time I type in multiple characters as a search query, and then make another call to the server using the unchanged this.searchQuery variable. If my initial search query is only a single character, it works just fine.
What am I doing wrong here? How can I emit the entirety of the text in the search bar, after any change, so that I can always save the whole search query, instead of the just the most recent change?
EDIT: I've add some more code below so the data flow is easier to follow:
Here is the template and script for the search component:
<template>
<div class="level-item">
<div class="field has-addons">
<div class="control">
<input class="input" type="text" placeholder="Address" v-model.lazy="searchQuery" v-on:input="(event) => this.$emit('queryChange', event)">
</div>
<div class="control">
<div class="button is-light" #click="clearInput">
<span class="icon is-small">
<i class="fa fa-times" style="color:#ffaaaa"></i>
</span>
</div>
</div>
<div class="control">
<button class="button is-info" #click="onSearch(searchQuery)">Search</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Search',
props: {onSearch: Function},
data () {
return {
searchQuery: ''
}
},
watch: {},
methods: {
clearInput () {
this.searchQuery = ''
}
}
}
</script>
the emitted queryChange event is caught and listened to in the vue page:
<Search :onSearch="onSearch" v-on:queryChange="updateSearchQuery"> and this triggers the updateSearchQuery function:
updateSearchQuery (event) {
this.searchQuery = event.data
console.log(event.data + ' || event.data')
console.log(this.searchQuery + ' || this.searchQuery')
}
Theoretically, the searchQuery data in my vue should be a copy of the searchQuery data in my component, which is itself merely a copy of whatever the user has input in the search bar.
Then when I make a call to the server I'm using the value in this.searchQuery in my vue:
onSearch (search) {
this.makeServerQuery(1, search)
},
onPaginate (page) {
this.makeServerQuery(page, this.searchQuery)
},
makeServerQuery (page = null, search = null) {
let queryStr = ''
if (page !== null) {
queryStr += '?cp=' + page + '&pp=' + this.perPage
}
if (this.searchQuery !== '') {
queryStr += '&se=' + this.searchQuery
} .....
The on onSearch(search) function is called whenever the search button is pressed. That seems to work fine, because when the button is pressed the entire searchQuery is passed, not just the last change.
An input event's data value appears to be the last typed character, and not the current value of the input. A simple fix is:
#input="$emit('queryChange', searchQuery)"
This works because the model will always be updated before the input event handler runs.
Here's a complete working component example:
<input
v-model="searchQuery"
type="text"
placeholder="Address"
#input="onInput"
/>
export default {
data() {
return { searchQuery: '' };
},
methods: {
onInput() {
console.log(this.searchQuery);
this.$emit('queryChange', this.searchQuery);
},
},
};
I want to disable my submit button until my form is filled out correctly, this is what I have so far:
<form>
<input type="text" class="form-control" v-validate="'required|email'" name="email" placeholder="Email" v-model="userCreate.userPrincipalName" />
<span v-show="errors.has('email')">{{ errors.first('email') }}</span>
<button v-if="errors.any()" disabled="disabled" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">Send Invite</button>
<button v-else="errors.any()" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">Send Invite</button>
</form>
The above only prints an error message and disables my submit button after I've started inputting a value. I need it to be disabled from the start, before I start interacting with the input, so that I cannot send an empty string.
Another question is if there is a better way than using v-ifto do this?
EDIT:
userCreate: {
customerId: null,
userPrincipalName: '',
name: 'unknown',
isAdmin: false,
isGlobalAdmin: false,
parkIds: []
}
Probably simpliest way is to use ValidationObserver slot for a form. Like this:
<ValidationObserver v-slot="{ invalid }">
<form #submit.prevent="submit">
<InputWithValidation rules="required" v-model="first" :error-messages="errors" />
<InputWithValidation rules="required" v-model="second" :error-messages="errors" />
<v-btn :disabled="invalid">Submit</v-btn>
</form>
</ValidationObserver>
More info - Validation Observer
Setting up the button to be :disabled:"errors.any()" disables the button after validation. However, when the component first loads it will still be enabled.
Running this.$validator.validate() in the mounted() method, as #im_tsm suggests, causes the form to validate on startup and immediately show the error messages. That solution will cause the form to look pretty ugly. Also, the Object.keys(this.fields).some(key => this.fields[key].invalid); syntax is super ugly.
Instead, run the validator when the button is clicked, get the validity in the promise, and then use it in a conditional. With this solution, the form looks clean on startup but if they click the button it will show the errors and disable the button.
<button :disabled="errors.any()" v-on:click="sendInvite();">
Send Invite
</button>
sendInvite() {
this.$validator.validate().then(valid=> {
if (valid) {
...
}
})
}
Validator API
One way to disable a button until all the values you need are filled, is to use a computed property that will return bool if all values are assigned or not
Example:
Create a computed property like this:
computed: {
isComplete () {
return this.username && this.password && this.email;
}
}
And bind it to the html disabled attribute as:
<button :disabled='!isComplete'>Send Invite</button
This means, disable the button if !isComplete is true
Also, in your case you don't need two if/else-bound buttons. You can use just one to hide/show it based on if the form is completed or has any errors:
<button :disabled="errors.any() || !isCompleted" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">Send Invite</button>
This button will be disabled until all fields are filled and no errors are found
Another way is to make use of v-validate.initial
<input type="text" class="form-control" v-validate.initial="'required|email'" name="email" placeholder="Email" v-model="userCreate.userPrincipalName" />
This will execute the validation of the email input element after the page is loaded. And makes that your button is disabled before interacting with the input.
To check whether a form is invalid or not we can add a computed property like this:
computed: {
isFormInValid() {
return Object.keys(this.fields).some(key => this.fields[key].invalid);
},
},
Now if you want to start checking immediately before user interaction with any of the fields, you can validate manually inside mounted lifecycle hooks:
mounted() {
this.$validator.validate();
}
or using computed
computed: {
formValidated() {
return Object.keys(this.fields).some(key => this.fields[key].validated) && Object.keys(this.fields).some(key => this.fields[key].valid);
}
}
and use
button :disabled="!formValidated" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">
For the current version 3 (As at the time of writing).
Step 1
Ensure form fields can be watched.
Step 2
Get a reference to the validator instance:
<ValidationObserver ref="validator">.
Step 3
Trigger validation silently whenever the form fields change.
Here's an example:
export default {
data() {
return {
form: {
isValid: false,
fields: {
name: '',
phone: '',
}
}
}
},
watch: {
'form.fields': {
deep: true,
handler: function() {
this.updateFormValidity();
}
}
},
methods: {
async updateFormValidity() {
this.form.isValid = await this.$refs.validator.validate({
silent: true // Validate silently and don't cause observer errors to be updated. We only need true/false. No side effects.
});
},
}
}
<button :disabled="form.isValid">
Submit
</button>
You can add computed properties
...
computed: {
isFormValid () {
return Object.values(this.fields).every(({valid}) => valid)
}
}
...
and it bind to the button:
<button :disabled="!isFormValid" class="btn btn-primary" type="submit">Send Invite</button>
i try this on vee-validate version ^2.0.3
I'm using the aurelia-dialog plugin to allow users to generate a set of objects, and want the dialog's response to return the chosen objects.
The workflow is that the list of options is generated from an API call using a promise when the activate() method is called on the dialog. The options are then displayed to the user, and selected from a dropdown. The user then clicks ok and the response should be sent back. Here is the code that is supposed to accomplish it:
this.ds.open({
viewModel: MyModal,
model: {
"title": "Select Objects",
"message": "I hate this modal"
}
}).then(response => {
console.log("closed modal");
console.log(response);
if (!response.wasCancelled) {
console.log('OK');
} else {
console.log('cancelled');
}
console.log(response.output);
});
And then in the modal.js:
import {inject} from 'aurelia-framework';
import {DialogController} from 'aurelia-dialog';
import {ModalAPI} from './../apis/modal-api';
//#inject(ModalAPI, DialogController)
export class MyModal {
static inject = [DialogController, ModalAPI];
constructor(controller, api){
this.controller = controller;
this.api = api;
controller.settings.centerHorizontalOnly = true;
}
activate(args){
this.title = args.title;
this.message = args.message;
this.returnedSet = null;
this.historicSetName = null;
this.reportHist = null;
return this.api.getReportHistory().then(reports => {
this.reportHist = reports;
});
}
selectHistoricReport() {
console.log(this.historicSetName);
if(this.historicSetName == "Select a report...") {
this.returnedSet = null;
} else {
var selectedReport = this.reportHist.filter(x => x.name == this.historicSetName)[0];
this.returnedSet = selectedReport.rsids;
}
console.log(this.returnedSet);
}
ok(returnedSet) {
console.log(returnedSet);
this.controller.ok(returnedSet);
}
}
And then the html:
<template>
<require from="../css/upload-set.css"></require>
<ai-dialog class="selector panel panel-primary">
<ai-dialog-header class="panel-heading">
<button type="button" class="close" click.trigger="controller.cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">${title}</h4>
</ai-dialog-header>
<ai-dialog-body class="panel-body container-fluid">
<div class="row">
<div class="col-sm-6">
<label>Report: </label>
<select value.bind="historicSetName" change.delegate="selectHistoricReport()" class="input-md form-control">
<option ref="historicSetPlaceholder">Select a report...</option>
<option repeat.for="historicReport of reportHist">${historicReport.name}</option>
</select>
</div>
</div>
</ai-dialog-body>
<ai-dialog-footer>
<button click.trigger="controller.cancel()">Cancel</button>
<button click.trigger="ok(returnedSet)">Save</button>
</ai-dialog-footer>
</ai-dialog>
</template>
As long as I don't touch the dropdown, the dialog will return a null (or any other value I initialize returnedSet to). However, as soon as I click on the dropdown, clicking either the Save or Cancel button leads to nothing being returned and the console.log lines at the end of my first code block just get skipped. I also tried removing the click.delegate line from my HTML, but that didn't change anything.
Anyone have any idea why this might be happening?
Also, I found this post(Aurelia Dialog and Handling Button Events) with an extremely similar problem, but can't seem to find any solution in there as to what I should do.
Thanks in advance.