I have a popover, that takes me to another page, where I pop back to the root page (popToRoot), reload the data/dom on an event and then dismiss the popup in the promise when the json data comes back from the server. It all works fine if I have a large timeout on the dismiss.
dismissPopup() {
if (this.popover) {
let that = this;
setTimeout(function () {
that.popover.dismiss();
}, 500);
}
}
If I make the timeout too low, say 100ms, it does not dismiss because the dom is still loading.
However, I don't think having a timeout is probably the best practice. What happens if someone has a slow devise, and the time is not enough?
Can anyone please make any suggestions? Should I detect when the dom has loaded, and then call dismiss? How do I check if the dom had loaded?
Thanks
Instead of using a timeout, you can use Events. By doing that, you can publish and event when the data comes back from the server (and everything is ready) and subscribe to that event to know when you need to dismiss the popup.
import { Events } from 'ionic-angular';
constructor(public events: Events) {}
// first page (publish an event when data is ready)
events.publish('loading:finished', data);
// second page (listen for the loading finished event)
events.subscribe('loading:finished', (eventData) => {
// eventData is an array of parameters, so grab our first and only arg
console.log('Data:', eventData[0]);
});
The popover can also be dismissed from within the popover's view by calling the dismiss() method on the ViewController
constructor(public navParams:NavParams,public navCtrl:NavController,public viewController:ViewController) {
console.log('Hello PopOverComponent Component');
}
blah()
{
//do something
this.viewController.dismiss();
}
Related
Before this is flagged as a duplicate, I have checked the majority of other posts and solutions, to no avail. If anyone wants to double check, here they are:
1) Socket.io Multiple returns for a single event
2) Socket.io message event firing multiple times
3) socket.on event gets triggered multiple times
And many others.
Now to get to the meat of my question!
I have a socket in my client and my server code. Once my server-side socket emits a message, it is received by the client-side socket and prints out the message. With research, this probably ties into event listeners, but I can't find out how to apply it to my code. FYI the following client code is ran when a button is clicked.
Here are snippets of my client code
onButtonClick() {
socket.emit('message_to_server', 'ping');
socket.on('reply', (tmp) => {
console.log(tmp); // in this case, call it 'pong'
this.doSomethingWithMe(tmp);
});
}
doSomethingWithMe(msg) {
// do something with the information
}
The first time I click the button, I receive
> pong
The second time I click the button, I receive
> pong
> pong
It continues to grow exponentially.
I can post my server code if needed, but I'm 100% sure that it emits the information correctly.
Does anyone have any idea for fixing this issue? I can't figure out how the listeners play into this scenario, so I would appreciate any advice!
EDIT: I changed some of my code to the following:
import React ...
const socket = socketIOClient('http://localhost:3000')
socket.on('reply', (tmp) => {
console.log(tmp); // in this case, call it 'pong'
var inst = new drawMe();
inst.doSomethingWithMe(tmp);
});
class drawMe extends Component {
constructor(props) { this.state = { allData: ''}}
onButtonClick() {
...
}
doSomethingWithMe(data) {
this.setState({ allData: data });
}
I am now receiving an error saying that you cannot call setState on a component that is not yet mounted. I probably will not open another question for this issue, but I would appreciate any advice on it. If mods/anyone wants me to close, I have no problem doing so.
EDIT2: If anyone else has this issue, I made it work by moving the code to instantiate the socket and for the event inside my constructor.
You're attaching your event handler again every time the button is clicked. You have socket.on inside the click handler; that method attaches a new handler -- that is, each time it's run, it adds the specified function to the end of the list of functions to run when the event fires. So yes, every time you click the button, you're adding the function to the end of the list and it'll run once for every time it was added. (More accurately, since you're using an anonymous function there, it's creating a new function every time you click the button and adding it to the event handler list to be run when the event fires.)
You should only be attaching event handlers once, for instance just after creating the socket, not on every click.
I'm trying to call a function every time I refresh the page. I'm doing the following:
componentDidMount() {
this.props.fetchData(this.state.data);
window.addEventListener('beforeunload', this.handleRefresh);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.handleRefresh);
}
And the function I'm trying to call is the following:
handleRefresh = () => {
// refreshPage here is an action method that talks to the backend
this.props.refreshPage(this.props.some_data)
}
The issue here is, the first couple of times I refresh the page, I get the expected output on the backend. After a couple of times, refreshing the page doesn't seem to trigger the backend method at all. Is this the correct way to trigger and get access to the refresh event?
Why are you using an event listener? React provides "lifecycle methods" that fire during specific stages of a page's life. If you want to call a function every time you refresh the page, just put the function inside componentDidMount or the class constructor. Those are executed whenever the page is loaded. See https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
componentDidMount() {
this.props.refreshPage(this.props.some_data);
}
What is the purpose of fetchData? You seem to be running the function but not doing anything with its return value.
I've got this listener setup on a form of mine that checks for a state transition to occur via angular router. When the listener is tripped it checks if the form is dirty, if it is it throws a window.confirm alert up saying the user may have unsaved changes.
All of that looks like this
this.setListener('form.dirty-check', this.setExitCheck);
setListener = (el, cb) => {
if ($(el).length) {
cb();
} else {
setTimeout(() => {
this.setListener(el, cb);
}, 500);
}
};
setExitCheck = () => {
this.$transitions.onStart({}, () => {
if ($('#compForm').hasClass('ng-dirty')) {
if (window.confirm('You may have unsaved changes! Press ok to continue, or press cancel to go back and save your work.') === false) {
return false;
} else {
return true;
}
}
});
};
This code is working pretty well, except for a singular bit of unexpected behaviour.
For some reason, when I hit, "Ok" to leave the page the transition will fire off just fine, but if I go back to the page and try it again, I now have to hit okay twice, and get two window.confirm alerts. If I go back a third time to try, I get three window.confirm alerts, and have to hit Ok on all three of them. I tried this up to the point of receiving 10 alerts, and have to press ok 10 times.
Once I refresh the page though, it seems to reset, and I start it all over again. Works right away, then takes two Ok's, then three, and so on.
Does anyone know what might be going on causing this incremental behaviour?
ui-router won't clear listeners automatically, so you have to clear it manually.
and $transitions.onStart returns a function which will destroy the listener's hook when it's called. documentation here(the last line).
the syntax is the same as deregister events of $rootScope, refer How can I unregister a broadcast event to rootscope in AngularJS?
$scope.onStartHandler = this.$transitions.onStart(...);
$scope.$on('destroy', function() {
$scope.onStartHandler();
});
Good afternoon,
I've been struggling with an unfamiliar problem. But first let me explain what I'm trying to achieve.
Purpose
What I'm trying to do is, setting a timer on each view, so if the internet connection is slow the timer calls a function which displays an ionicPopup with the question: "Would you like to refresh the view?" combined with a refresh button. When the button is clicked, the view will refresh. If the view loads in time (I've set the timer on 10000 milliseconds), the $timeout.cancel action is being called and the timer stops.
If that would be all it would be working fine at the moment.
Problem
If I navigate through my main pages rapidly, it looks like the timers aren't stop and the "Refresh popup" pops up on another page. That only happens when you click through at high speed. It looks like a few popup pop up at once beneath each other. I don't know what might be the problem, but I guess that the $timeout.cancel isn't being called if you navigate through the pages to quickly.
Code
Service
.factory('Timer', function ($ionicPopup, $state, $timeout) {
return {
start: start
};
function start() {
return $timeout(showPopup, 10000);
}
function showPopup() {
$ionicPopup.show({
title: 'Slow internetconnection',
template: 'Click refresh to try again.',
buttons: [
{
text: 'Refresh',
onTap: function (e) {
$state.reload()
}
}
]
})
}
})
Controller
.controller('HomeCtrl', function ($scope, Request, Timer, $timeout) {
var timer = Timer.start();
Request.execute().finally(function () {
$timeout.cancel(timer);
})
$scope.$on('$destroy', function () {
$timeout.cancel(timer);
});
})
I hope my problem is understandable.
The key thing to note here is:
If I navigate through my main pages rapidly, it looks like the timers
aren't stop and the "Refresh popup" pops up on another page.
As you navigate through, your ionic views are created/destroyed, and therefore you lose references to timer objects.
So, in controllers where you are using timers, you must invalidate the timer before you leave that page. This is because timers are wrappers around JavaScript's setTimeout and setInterval, and JavaScript has method level scope that passes the method as a parameter to setInterval which will hold a reference to it, while your controller gets destroyed. Therefore, when you change view, although your controller instance may get removed, the timer does not.
Add $ionicView.afterLeave callbacks in your controllers.
$scope.$on("$ionicView.afterLeave", function(event, data){
$timeout.cancel(timer);
});
This will cancel the timer for a particular controller, after that controller's view is dismissed.
I have some pretty basic jQuery code:
...
$(this).find('img').load(function(){
loadedImages++;
if(loadedImages == $this.find('img').length){
...
However, thats not firing consistently. It fires if i do a hard refresh or close my browser, but a normal refresh, or just hitting the same URL twice at any time without erasing the cache makes the .load() never fire.
Any ideas on how to fix this?
I think this has been discussed before. It’s not the caching per se that is the problem but the timing: the images may already have been loaded by the time the event handler is attached (so it never gets fired). This may also occur if no caching happens, for example in a multithreaded browser on a very fast connection. Fortunately, there is a property .complete that you can use:
var load_handler = function() {
loadedImages++;
…
}
$(this).find('img').filter(function() {
return this.complete;
}).each(load_handler).end().load(load_handler);
You can also create your own event attach function:
jQuery.fn.extend({
ensureLoad: function(handler) {
return this.each(function() {
if(this.complete) {
handler.call(this);
} else {
$(this).load(handler);
}
});
}
});
And then call it as follows:
$(this).find('img').ensureLoad(function(){
loadedImages++;
if(loadedImages == $this.find('img').length){
…
});
A way would be to add a "dummy variable" at the end of the URL that you use to grab the image... such as the time in milliseconds appended as a query string param.
Edit: Preventing the Browser to Cache images is a very bad idea in 99% of the cases as it will slow down your application. Instead you should check if an image is already loaded, and if not wait for the load event to complete.
As Ron and El Guapo said, the answer is to add a query at the end of the URL. I did this like this:
$(this).find('img').each(function(){
$(this).attr('src',$(this).attr('src')+'?'+new Date().getTime())
}).load(function(){
//This will always run now!