I followed the Vaadin tutorial (Creating Collaborative Views) for broadcasting events and register on them.
Registration eventRegistration;
#Override
protected void onAttach(AttachEvent attachEvent) {
log.debug("In attach...");
UI ui = attachEvent.getUI();
eventRegistration= Broadcaster.register(
"eventName",
message -> ui.access(() -> {
log.debug("Request to refresh grid...");
presenter.refreshGrid();
ui.push();
}));
}
#Override
protected void onDetach(DetachEvent detachEvent) {
log.debug("In detach...");
if(eventRegistration != null) {
eventRegistration.remove();
eventRegistration = null;
}
}
Everything works except the fact that when refreshing the page, the logic in the onDetach() is not executed. After refresh, however, you will enter the onAttach() method. Because of this you are actually going to register several of 'the same' listeners without removing the previous one and you actually get a doubling of listeners. The onDetach() method is only accessed if you go to another menu item, for example.
You can find an example log below.
What is the Vaadin recommended way to remove these listeners before/during refresh?
The onDetach method should be called eventually.
No event is sent to the server when you close or refresh a tab, and as such the server is not aware that the old UI should be detached.
This is where the heartbeat requests come in. The UIs send heartbeat requests every 5 minutes per default, and if the server notices that the old UI has missed three heartbeats, it will be detached. Alternatively, it will be detached when the session expires.
In other words, the onDetach method should be called after about 20 minutes.
The reason no event is sent to the server when the tab is closed or refreshed is that this could prevent the tab from refreshing/closing while the request is being handled, which is bad user experience. Also, this wouldn't cover the cases where the computer is turned off or the network disconnected.
There is something called the Beacon API that could be used to notify the server when a tab is refreshed or closed without causing a delay in the browser. There is an issue for using this to immediately detach UIs.
I'd recommend using the Unload Beacon add-on: https://vaadin.com/directory/component/unload-beacon-for-vaadin-flow or a similar approach which is demonstrated in the Cookbook: https://cookbook.vaadin.com/notice-closed - essentially, it's executing the JavaScript snippet to add an event listener for Window's unload event:
ui.getElement().executeJs(
"window.addEventListener('unload', function() {navigator.sendBeacon && navigator.sendBeacon($0)})", relativeBeaconPath);
and the beacon is sent to a custom SynchronizedRequestHandler.
Simplest way to workaround the problem would be checking if eventRegistration is null before adding one.
#Override
protected void onAttach(AttachEvent attachEvent) {
log.debug("In attach...");
UI ui = attachEvent.getUI();
if (eventRegistration == null) {
eventRegistration= Broadcaster.register(
"eventName",
message -> ui.access(() -> {
log.debug("Request to refresh grid...");
presenter.refreshGrid();
ui.push();
}));
}
}
Check the other answer by Erik why calling of onDetach is delayed.
Related
in my Vaadin 14.2.0 application there is a BeforeLeaveListener to show a confirmation dialog when the input is dirty (= there are unsaved changes in the input fields) to cancel (or intentionally proceed) the navigation:
BeforeLeaveListener listener = new BeforeLeaveListener() {
#Override
public void beforeLeave(BeforeLeaveEvent event) {
if (dirtyFlag.isDirty()) {
ContinueNavigationAction postponeAction = event.postpone();
// show confirmation dialog and maybe execute a proceed
[...] () -> postponeAction.proceed();
}
}
};
UI.getCurrent().addBeforeLeaveListener(listener);
This works fine for everything but for the logout.
This is how my logout button looked like in the start (what works as long as I do not want to postpone/cancel the logout):
Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
VaadinSession.getCurrent().getSession().invalidate();
UI.getCurrent().navigate(LoginView.class);
});
Now I want to postpone the logout until the user confirms that the unsaved changes should get discarded. (Switching both lines in the EventListener seems to be a good idea, but did not work because of the following.)
Within the navigate-call the BeforeLeaveListener is called (good), but the logout is done nevertheless, because the code after the navigate()-call is executed (of course, because it is no blocking call), the session is invalidated and the user is logged out though the confirmation dialog just popped out (but the user had no chance to confirm/deny).
I tried to move the session invalidation into the LoginView (into a BeforeEnterObserver), but the result is that the login view is reloaded in an endless loop.
Question: is there something like "navigate to LoginView and if navigation is not postponed and not cancelled, then invalidate session"?
A workaround is to navigate to an intercepting LogoutView that just invalidates the session and redirects/forwards to the LoginView:
#Route(value = "logout")
public class LogoutView implements BeforeEnterObserver {
#Override
public void beforeEnter(BeforeEnterEvent event) {
VaadinSession.getCurrent().getSession().invalidate();
event.forwardTo(LoginView.class);
}
}
But this seems to me to be just a bad workaround with some overhead (creating a view just to forward to another view)...
I know, you didnt mention Spring. But that's actually what I believe Spring Security is doing under the hood.
My Logout Button (using Spring Security) is looking like this:
new Button("Logout", click -> {
UI.getCurrent().getPage().executeJs("location.assign('logout')");
});
Once you click it, you are logged out and redirected to the login view (more details from Baeldung). I think it will be just fine if you do your LogoutView redirection detour.
The fact that you can even use the Navigator to go to your LogoutView is even better here, since this is caught by BeforeLeaveObserver (in contrast to completely new requests by assigning a new location or refreshing the page. See BeforeLeaveObserver vs. beforeunload for more details on that)
I would still like to propose another idea for your usecase. I think it will be much simpler, but requires that the logout link knows of the dirtyFlag:
Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
if(dirtyFlag.isDirty()){
// show Dialog with
// - a confirm btn that calls doLogout() on click
// - a cancel btn that closes the Dialog
} else {
doLogout();
}
});
...
private void doLogout(){
VaadinSession.getCurrent().getSession().invalidate();
UI.getCurrent().navigate(LoginView.class);
}
I can see custom menu items and custom conversation windows and events inside them but nothing referring to how you'd execute code once a user signs into Lync. Does such an API exist?
I guess my alternative would be creating a Lync Automation object/my own client using the Suppressed ui and building whatever features I want into one of those?
There's nothing you can build into the Lync application, but you could run a separate application which can subscribe to the SignIn state of the user. That way, you'd know when a user signs-in, and could take appropriate action. You wouldn't need to create a SuppressedUI application for that, just something that ran in the background, or taskbar or something.
Here's a bare bones example:
namespace ThoughtStuff
{
class Program
{
static void Main(string[] args)
{
var client = LyncClient.GetClient();
client.StateChanged += client_StateChanged;
}
static void client_StateChanged(object sender, ClientStateChangedEventArgs e)
{
if (e.NewState == ClientState.SignedIn)
{
//do something on sign in
}
}
}
}
You might get errors if you try and attach to Lync in the SDK code using LyncClient.GetClient() if the Lync exe isn't running...but if you know that's likely to be a problem (such as if your application might be running before the user starts Lync), then you can gracefully handle it and retry in code.
I am creating a chat embedded in my asp.net mvc 4 project. I have an online users ul list which add a user on OnConnected and remove it on OnDisconnected.
So, my app isn't a SinglePage app, which means that it refreshes on pages all the time.
I am encountering some difficulties to treat with this online users list on the client side, because signalr calls OnDisconnected and OnConnected on every page refresh.
While the other client is navigating normally in app, it keep being removed and added on every refresh of page.
How to avoid this behavior on client?
I am trying to do some like this, on client which are running the page with usersOnline list...
var timeout;
chat.client.login = function (chatUser) {
addUser(chatUser);
window.clearTimeout(timeout);
};
chat.client.logout = function (chatUser) {
timeout = setTimeout(function () { removeUser(chatUser.Id); }, 3000);
};
But I am suffering to deal with multi-users scenario... Because if more than one user executes the hub onDisconnected before the timeout runs, the second will override the instance of the first.
There is indeed no real way around this. A client will always disconnect when leaving a page, and connects to SignalR again when the next page is loaded.
The only way around this is to create a SPA, so SignalR doesn't need to be disconnected by navigating away.
Using the idea of SignalR hubs is to allow real-time actions with minimal programming or complications - the best way would be for SignalR to be pulling from a list of currently logged in users, not active connections, as that could have the same user multiple times.
Therefore, I suggest, instead of OnConnected and OnDisconnected, put it in your AccountController, in the LogIn and LogOut methods. For example:
public ActionResult LogIn()
{
//other stuff
var hub = GlobalHost.ConnectionManager.GetHubContext</*Hub Title*/>();
hub.client.chat.login()
}
public ActionResult LogOut()
{
// other stuff
var hub = GlobalHost.ConnectionManager.GetHubContext</*Hub Title*/>();
hub.client.chat.logout()
}
After complete of asynchronous call to WCF service I want set success message into session and show user the notification .
I tried use two ways for complete this operation.
1) Event Based Model.
client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(GetDataCompleted);
client.GetDataAsync(id, client);
private void GetDataCompleted(object obj, GetDataCompletedEventArgs e)
{
this.SetNotification(new Notification() { Message = e.Result, Type = NotificationType.Success });
}
In MyOperationCompleted event i can set notification to HttpContext.Current.Session, but I must waiting before this operation will completed and can't navigate to others pages.
2) IAsyncResult Model.
In this way I can navigate to other pages and make asynchronous calls to wcf service, but in GetDataCallback method can't set notification, becouse session = null.
client.BeginGetData(id, GetDataCallback, client);
private void GetDataCallback(IAsyncResult ar)
{
string name = ((ServiceReference1.Service1Client)ar.AsyncState).EndGetData(ar);
this.SetNotification(new Notification() { Message = name, Type = NotificationType.Success });
}
"Generate asynchronous operations" in service reference enabled.
Please help me with this trouble. Thanks.
I'm no wcf expert, but what I've found to work is wrapping your call to the Async version of your method in ThreadPool.QueueUserWorkItem. Without this, I had same blocking issue. So this seems to free up the main thread in your asp mvc to move on while another worker thread waits for the callback.
Also, I used AsyncController, although that alone was not enough without the worker thread.
See this: http://msdn.microsoft.com/en-us/library/ee728598.aspx
I used this as a guide, but still needed the ThreadPool.
Cheers
Scenario: I have a Delphi Intraweb application that has some edit components and buttons on a screen. In the TIWEdit.OnAsyncExit and TIWButton.OnClick a flag is set, and another thread in the application sets the enabled properties of the buttons depending on the flags and some other application data.
By the time the TIWButton.Enabled properties are set, the request has already finished and the next interaction is cancelled as IW finds out that internal representation and HTML form are out of sync. It resynchonizes and you have to click again.
I would like to refresh the screen somehow on demand.
A timer that finds out whether the two are synchronized and issues a refresh has drawbacks in traffic and timing (I can click a button before a timer run).
A method that could push data would be great.
Maybe IW has a possibility to do an non-save sync without cancelling the action I just committed.
As my screens are built model driven (I cannot predict what components will be on the screen and what the interdependencies between components are, that is in the business logic), I cannot add JavaScript to enable or disable a button depending on user actions.
I am not completely sure if your question is the same as mine, yet I think there is a lot in common. See the demo project (v2) I posted in the Intraweb forum.
Based on some comments from Jackson Gomes I enable a TIWTimer before a long running thread starts and disable this after the thread has ended. See: http://forums3.atozed.com/IntraWeb.aspx (atozedsoftware.intraweb.attachments), thread 'IWLabel update via Thread', Oct 15, 2009.
The OnASync timer event is fired every 500 ms and is using some bandwith. Acceptable in my situation (company intranet).
Gert
You could use the Interop Web Module from the IWElite component pack.
Essentially you would write a bit of Javascript using the XMLHTTPRequest (XHR) object to call into your IW app's Web Module Action which returns when the processing is finished. If you need your IW app to continue to function as normal while the process is running, your Javascript could open a progress window and make the XHR call from there.
IW Elite can be found here:
http://code.google.com/p/iwelite/
An XHR request would look something like this:
function NewXHR() {
if (typeof XMLHttpRequest == "undefined") {
try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP');} catch(e) {}
try { return new ActiveXObject('Microsoft.XMLHTTP');} catch(e) {}
throw new Error('AJAX not supported in this browser.');
} else {
return = new XMLHttpRequest();
}
var xhr = NewXHR();
xhr.open("get", '/mywebaction', false);
xhr.send(null);
window.alert(xhr.responseText);
The above code will block and wait for the response. If you would rather have it act asynchronously, you could instead do the following:
var xhr = NewXHR();
xhr.open("get", '/mywebaction', true);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if ((xhr.status == 200) || (xhr.status == 304) || (xhr.status === 0)) {
window.alert('Success: '+xhr.responseText);
} else {
window.alert('Error: ('+xhr.status+') '+xhr.statusText;
}
}
};
xhr.send(null);