Vue.js - How to load data with method call via click and toggle data? - vue.js

I'm loading data via my rest api when the button is clicked with a vue.js method. To hide the loaded data I'm clicking the button again. When I'm clicking the button to show the data again, the same method is called again and the data is shown twice.
<div v-if="comment.answers_count !== 0">
<button
#click="toggleAnswers"
:class="{
'btn-danger': loadAnswers,
'btn-outline-danger': !loadAnswers
}"><strong>{{ comment.answers_count }} show answers</strong></button>
</div>
How can I load the data once, hide it with button clicked and show the same loaded data when toggling the button?
Maybe theres a way to use a computed property?

Create a new property in data:function(){}
e.x
data:function(){
answers_loaded:false
}
then on your function toggleAnswers
do the following
methods:{
toggleAnswers:function(){
/** if answers are not loaded , load them **/
if(!this.answers_loaded){
/** mark answers_loaded to true, so we do not call this code
again. Should be better put on your success request handler
to make sure that they were successfully loaded**/
this.answers_loaded = true;
//code to load the answers
}
}
}
So the first time that you click the button and function toggleAnswers is called the variable answers_loaded pass the if check.
Every other time that you click the button since answers_loaded is equal to true, it will fail the if check and wont call again part of the code to load the answers

So,i can't get you real purpose?
if you want to just load data once, you can use v-once, eg
<button #click.once="loadData" #click="hideData"> loadData or hideData</button>
...
loadData() {
setTimeout(() => {
this.data = 'loaded data'
}, 500)
},
hideData() {
this.data = this.data && null
}
if you want the toggle circle then you should set a tag using %2 to judge which function to run .

Related

PrimeNg TabView with ConfirmDialog not working

I'm trying to use PrimeNg TabView component along with confirmDialog unsuccessfully
I am able to show this confirm dialog but it appears after user switch to target tab panel which is wrong.
<p-tabView (onChange)="handleChange($event)" [(activeIndex)]="index">...</p-tabView>
handleChange(e) {
this.confirmationService.confirm({
message: 'There are unsaved changes, do you want to proceed?',
accept: () => {
this.index = e.index;
},
reject:() =>{ }
});
}
Do you have an idea on how to prevent or allow tab change using confirm dialog ?
Thanks
there is no official way to prevent change to another tab by press on that tab , but πŸ˜… there is a work around it first we need to prevent the tab change by tab click,
1️⃣ we need to set the header by ng-template or it called a custom header
template
<p-tabPanel >
<ng-template pTemplate="header">
<div (click)="handleChange($event,0)">
Godfather I
</div>
</ng-template>
.....
</p-tabPanel>
2️⃣ we bind a click event to the new header text and by using mouse event stopPropagation method we can prevent the change πŸ‘Œ,now we can control the change by confirm result but you need to pass the current tab index, that why I add another parameter to handleChange
component
handleChange(e:MouseEvent,tabIndex:number) {
e.stopPropagation();
if (this.index == tabIndex){
return;
}
// console.log(tabIndex)
this.confirmationService.confirm({
message: "There are unsaved changes, do you want to proceed?",
accept: () => {
this.index = tabIndex;
},
reject: () => {}
});
}
the if block if (this.index == tabIndex){return;} use to prevent showing the confirm dialog if we click on the same active tab again
demo πŸš€πŸš€

You may have an infinite update loop in a component render function using click event conditional rendering

I am rendering two texts based on a condition and be able to pass methods to the click event based on the condition. The default text is ADD TO COLLECTION because initially hasPaid property is false. Once payment has been made, I want to set that property to true
The function addToCollection first opens a modal, on the modal, the handlePayment function is implemented. I have been able to conditionally render the div to show either ADD TO COLLECTION or DOWNLOAD using v-on="". I also return hasPaid property from the handlePayment function.
<div class="float-right peexo-faded-text card-inner-text" :face="face" v-on="!hasPaid ? {click: addToCollection} : {click: handleDownload(face)}">
{{!hasPaid ? 'ADD TO COLLECTION': 'DOWNLOAD' }}
</div>
data: function () {
return {
hasPaid: false,
}
},
addToCollection(){
this.showcollectionModal = true;
},
handlePayment(){
this.showcollectionModal = false;
let accept = true;
this.showpaymentsuccessmodal = true;
//this.hasPaid = true;
return {
hasPaid: accept
}
},
I want to be able to set hasPaid property on the handlePayment function for the render function to pick it, so that the handleDownload function can then work.
The last section of this bit is going to be problematic:
v-on="!hasPaid ? {click: addToCollection} : {click: handleDownload(face)}"
When hasPaid is true it will invoke the method handleDownload immediately. That is, it will be called during render, not when the <div> is clicked.
You could fix it by 'wrapping' it in a function:
{click: () => handleDownload(face)}
I've used an arrow function in my example but you could use a normal function if you prefer.
Personally I wouldn't try to do this using the object form of v-on.
My first instinct is that you should consider just having two <div> elements and use v-if to decide which one is showing.
If you did want to use a single <div> I would put the click logic in a method. So:
<div class="..." :face="face" #click="onDivClick(face)">
Note that despite the apparent syntactic similarity to the way you defined your click listener this won't invoke the method immediately.
Then in the methods for the component:
methods: {
onDivClick (face) {
if (this.hasPaid) {
this.handleDownload(face)
} else {
this.addToCollection()
}
}
}

How to prevent #change event when changing v-model value

I'm building an auto-complete menu in Vue.js backed by Firebase (using vue-fire). The aim is to start typing a user's display name and having match records show up in the list of divs below.
The template looks like this:
<b-form-input id="toUser"
type="text"
v-model="selectedTo"
#change="searcher">
</b-form-input>
<div v-on:click="selectToUser(user)" class="userSearchDropDownResult" v-for="user in searchResult" v-if="showSearcherDropdown">{{ user.name }}</div>
Upon clicking a potential match the intention is to set the value of the field and clear away the list of matches.
Here is the code portion of the component:
computed: {
/* method borrowed from Reddit user imGnarly: https://www.reddit.com/r/vuejs/comments/63w65c/client_side_autocomplete_search_with_vuejs/ */
searcher() {
let self = this;
let holder = [];
let rx = new RegExp(this.selectedTo, 'i');
this.users.forEach(function (val, key) {
if (rx.test(val.name) || rx.test(val.email)) {
let obj = {}
obj = val;
holder.push(obj);
} else {
self.searchResult = 'No matches found';
}
})
this.searchResult = holder;
return this.selectedTo;
},
showSearcherDropdown() {
if(this.searchResult == null) return false;
if(this.selectedTo === '') return false;
return true;
}
},
methods: {
selectToUser: function( user ) {
this.newMessage.to = user['.key'];
this.selectedTo = user.name;
this.searchResult = null;
}
}
Typeahead works well, on each change to the input field the searcher() function is called and populates the searchResult with the correct values. The v-for works and a list of divs is shown.
Upon clicking a div, I call selectToUser( user ). This correctly reports details from the user object to the console.
However, on first click I get an exception in the console and the divs don't clear away (I expect them to disappear because I'm setting searchResults to null).
[Vue warn]: Error in event handler for "change": "TypeError: fns.apply is not a function"
found in
---> <BFormInput>
<BFormGroup>
<BTab>
TypeError: fns.apply is not a function
at VueComponent.invoker (vue.esm.js?efeb:2004)
at VueComponent.Vue.$emit (vue.esm.js?efeb:2515)
at VueComponent.onChange (form-input.js?1465:138)
at boundFn (vue.esm.js?efeb:190)
at invoker (vue.esm.js?efeb:2004)
at HTMLInputElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1802)
If I click the div a second time then there's no error, the input value is set and the divs disappear.
So I suspect that writing a value to this.selectedTo (which is also the v-model object for the element is triggering a #change event. On the second click the value of doesn't actually change because it's already set, so no call to searcher() and no error.
I've noticed this also happens if the element loses focus.
Question: how to prevent an #change event when changing v-model value via a method?
(other info: according to package.json I'm on vue 2.5.2)
On:
<b-form-input id="toUser"
type="text"
v-model="selectedTo"
#change="searcher">
The "searcher" should be a method. A method that will be called whenever that b-component issues a change event.
But looking at your code, it is not a method, but a computed:
computed: {
searcher() {
...
},
showSearcherDropdown() {
...
}
},
methods: {
selectToUser: function( user ) {
...
}
}
So when the change event happens, it tries to call something that is not a method (or, in other words, it tries to call a method that doesn't exist). That's why you get the error.
Now, since what you actually want is to update searcher whenever this.selectedTo changes, to get that, it is actually not needed to have that #change handler. This is due to the code of computed: { searcher() { already depending on this.selectedTo. Whenever this.selectedTo changes, Vue will calculate searcher again.
Solution: simply remove #change="searcher" from b-form. Everything else will work.
#acdcjunior, thanks for your answer.
Of course just removing the reference to searcher() just means no action is taken upon field value change so the field won’t work at all.
Moving the searcher() function into methods: {} instead of computed: {} means that it will be called on an input event and not a change even (another mystery but not one for today). A subtle difference that takes away the typeahead feature I’m aiming at.
However, it did make me remember that the result of computed: {} functions are cached and will be re-computed when any parameters change. In this case I realised that the searcher() function is dependent upon the this.selectedTo variable. So when the selectToUser() function sets this.selectedTo it triggers another call to searcher().
Fixed now. In case anyone has a similar problem in the future, I resolved this by turning to old fashioned semaphore by adding another variable.
var userMadeSelection: false
Now, searcher() begins with a check for this scenario:
computed: {
searcher() {
if(this.userMadeSelection) {
this.userMadeSelection = false;
return this.selectedTo;
}
…
and then in selectToUser():
this.userMadeSelection = true;

Aurelia Dialog and Handling Button Events

I have set up the aurelia-dialog plugin. It's working using the example in the GitHub readme, but the documentation doesn't explain anything about how to use it otherwise. I have a simple use case with a list page. I want to click an "add new" button, pop the modal dialog which has it's own VM. The modal contains a simple dropdown list. I need to select an item on the list and make an API call to save the data, but I can't seem to figure out how to wire up my save method with the save button on the dialog.
The method that opens the dialog on my list page (which works just fine):
loadAgencyDialog(id){
this.dialogService.open({ viewModel: AddAgency, model: { id: id }}).then((result) => {
if (!result.wasCancelled) {
console.log('good');
console.log(result.output);
} else {
console.log('bad');
}
});
My modal add-agency.js (VM for the modal, also loads the select list just fine and yes, I have a variable named kase because case is reserved):
import {DialogController} from 'aurelia-dialog';
import {ApiClient} from 'lib/api-client';
import {inject} from 'aurelia-framework';
#inject(DialogController, apiClient)
export class AddAgency {
kase = { id: '' };
constructor(controller, apiClient){
this.controller = controller;
this.agencies = [];
this.apiClient = apiClient;
}
activate(kase){
this.kase = kase;
this.apiClient.get('agencies')
.then(response => response.json())
.then(agencies => this.agencies = agencies.data)
.then(() => console.log(this.agencies)); //these load fine
}
addAgency() {
//Do API call to save the agency here, but how?
}
}
This is part I'm unsure about. In the example, they use controller.ok(theobjectpassedin), which returns a promise. But I don't get where I can call my addAgency method. Any ideas?
It's possible I'm misunderstanding your question, but you should be able to just call addAgency() in your HTML:
<button click.trigger="addAgency()">Add</button>
And then do what you need to do in addAgency(), finishing with a call to this.controller.ok() to wrap up the modal.
As an example, here's my modal's dialog-footer:
<ai-dialog-footer>
<button click.trigger="controller.cancel()">Cancel</button>
<button click.trigger="ok(item)">Save</button>
</ai-dialog-footer>
And in my code:
ok(item) {
this.controller.ok(item);
}
Not too complex. Hope that helps.

how get I notified when the "onload" script has finished

I get a notification when a html page is loaded
-> onStateChange, stateFlags: STATE_IS_NETWORK + STATE_STOP
but I need a notification when the page ist loaded and a onload script has finished running.
Any hints ?
THX
Maybe you could add a second listener to "load" events. I was not able to find any documentation confirming that events listeners are called in order they are set, but some experiments with the code below shows that it seems to be the case. If there was a race condition I would expect to see sometimes "AB" and sometimes "BA":
<input type="text" id="field"/>
<script>
var t = document.getElementById("field");
t.value="";
function a(e) {
t.value = t.value + "A";
}
function b(e) {
t.value = t.value + "B";
}
window.addEventListener("load", a, false);
window.addEventListener("load", b, false);
</script>
One thing to be careful with this is that if you add the second event listener too late (i.e. the load event already fired) it will not be called.
Anyway, feels like a hack, but is just an idea in case you cannot find a better way to handle it.
You could use:
gBrowser.addEventListener("DOMContentLoaded", function()
{
// Page content is loaded
}, false);
but remember that gBrowser refers to the content of a tab so this will check every tab when it has been selected