Hello all, I am going to try and explain how you can resolve the issue of dynamic content with ABMaterial. (This method works for me)
First lets start with the reason why we see duplicate content. What you created in BuildPage method is known as static content as it gets written in the html file which the browser caches it and what is created in ConnectPage is known as dynamic content as this get's written throught the websocket connection to the browser. The problem is when a user loads a page in the browser that has dynamic content, then looses connectivity and then regains connectivity a new server websocket class/page will be created at your server therefore a the Websocket_Connected event will raise again and will call the ConnectPage which appends the dynamic content over what the users has in the browser.
So lets take the following scenario:
you build an label in the build page with the text Static Content and you build another label in the ConnectPage with the text Dynamic Content. When a user open the page in the browser the server will server the html file which contains the label with Static Content, when the browser makes the websocket connection the server will serve the label with Dynamic Content. Now lets assume that the users looses connectivity but leaves the browser open (page loaded in which he sees both Static Content and Dynamic Content labels) now when he regains connectivity the browser will not Refresh(reload) the page instead will try to make a new websocket connection therefore the server will serve again the Dynamic Content label and the user will end up having 2 Dynamic Content Labels .... This is more or less how it works
Now how to avoid this:
1. in ABMShared at Class_Globals you need to have this:
2. in ABMShared at NavigateToPage you need to have this:
3. in ABMApplication (the entry point of the application) at WebSocket_Connected:
4. in every page/server websocket class at WebSocket_Connected:
So let me explain a little point 1 sets the AppVersion as the time when the server was started and point 2 adds to NavigateToPage the AppVersion to the TargetUrl as a query string so we can have a reference in the browser when the server was started. Point 3 creates a session atrribute (ValidSession = True) as this is the entry point of the application and all clients must come through here - it will stay in session as long as the session is valid and as long as the session is valid the cache version of this page is valid too. Point 4 adds some checks in the WebSocket_Connected event that verify if the server has been restarted and if the current session is a valid session, if either one of them return false then the user will be redirected to the entry point of the application.
You kind of need to know your users connection or approximate ... If you are building an web app that will be mostly used on mobile phones there is a habbit that the user will not close the browser page and put the browser in minimized state - this kind of affects the heartbeat connection (to keep the session alive) even if the phone is connected to the internet, also if the phone looses internet connectivity for more time than you session timeout interval then the session will be destroyed as the heartbeat can't keep the session alive anymore. So it's your choice to handle the interval of how long a session will be kept and also how long the pages will stay in the cache why this setting from ABMShared Process_Globals:
So if you have a few users you could give this setting a higher value like 7 days (60*60*24*7) but if a lot of users access your app then you will need to keep this setting to a lower level as things will pile up in cache mechanism and also in the session store - and all this data is kept in your server (session and cache) ... so you are the only one that knows whats the perfect value for you.
I hope I haven't missed something and explained for everyone to understand ... if not fire your questions
First lets start with the reason why we see duplicate content. What you created in BuildPage method is known as static content as it gets written in the html file which the browser caches it and what is created in ConnectPage is known as dynamic content as this get's written throught the websocket connection to the browser. The problem is when a user loads a page in the browser that has dynamic content, then looses connectivity and then regains connectivity a new server websocket class/page will be created at your server therefore a the Websocket_Connected event will raise again and will call the ConnectPage which appends the dynamic content over what the users has in the browser.
So lets take the following scenario:
you build an label in the build page with the text Static Content and you build another label in the ConnectPage with the text Dynamic Content. When a user open the page in the browser the server will server the html file which contains the label with Static Content, when the browser makes the websocket connection the server will serve the label with Dynamic Content. Now lets assume that the users looses connectivity but leaves the browser open (page loaded in which he sees both Static Content and Dynamic Content labels) now when he regains connectivity the browser will not Refresh(reload) the page instead will try to make a new websocket connection therefore the server will serve again the Dynamic Content label and the user will end up having 2 Dynamic Content Labels .... This is more or less how it works
Now how to avoid this:
1. in ABMShared at Class_Globals you need to have this:
B4X:
Public AppVersion As String = DateTime.now
B4X:
Public Sub NavigateToPage(ws As WebSocket, PageId As String, TargetUrl As String)
If AppVersion.Length > 0 Then TargetUrl = $"${TargetUrl}?${AppVersion}"$
If PageId.Length > 0 Then ABM.RemoveMeFromCache(CachedPages, PageId)
If ws.Open Then
' it doesn't keep navigation history in the browser (the back button exists the application)
ws.Eval("window.location.replace(arguments[0])", Array As Object(TargetUrl))
' if you need browser history just comment the lines above and uncomment the lines below
' ' it keeps the navigation history in the browser
' ws.Eval("window.location = arguments[0]", Array As Object(TargetUrl))
ws.Flush
End If
End Sub
B4X:
Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
Log("Connected")
ws = WebSocket1
ABMPageId = ABM.GetPageID(AppPage, ABMShared.AppName,ws)
Dim session As HttpSession = ABM.GetSession(ws, ABMShared.SessionMaxInactiveIntervalSeconds)
session.SetAttribute("ValidSession", True)
... rest of your code ...
B4X:
Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
Log("Connected")
ws = WebSocket1
' here we check if the server has been restarted as the app version will have the date and time when the server started
If ABMShared.AppVersion.Length > 0 And ws.UpgradeRequest.ParameterMap.ContainsKey(ABMShared.AppVersion) = False Then
ABMShared.NavigateToPage(ws, "", "../")
End If
ABMPageId = ABM.GetPageID(page, Name,ws)
Dim session As HttpSession = ABM.GetSession(ws, ABMShared.SessionMaxInactiveIntervalSeconds)
' if the session has expired the statement below will return false as there is no ValidSession attribute set for this session
If session.GetAttribute2("ValidSession", False) = False Then
ABMShared.NavigateToPage(ws, ABMPageId, "../")
End If
... rest of your code ...
So let me explain a little point 1 sets the AppVersion as the time when the server was started and point 2 adds to NavigateToPage the AppVersion to the TargetUrl as a query string so we can have a reference in the browser when the server was started. Point 3 creates a session atrribute (ValidSession = True) as this is the entry point of the application and all clients must come through here - it will stay in session as long as the session is valid and as long as the session is valid the cache version of this page is valid too. Point 4 adds some checks in the WebSocket_Connected event that verify if the server has been restarted and if the current session is a valid session, if either one of them return false then the user will be redirected to the entry point of the application.
You kind of need to know your users connection or approximate ... If you are building an web app that will be mostly used on mobile phones there is a habbit that the user will not close the browser page and put the browser in minimized state - this kind of affects the heartbeat connection (to keep the session alive) even if the phone is connected to the internet, also if the phone looses internet connectivity for more time than you session timeout interval then the session will be destroyed as the heartbeat can't keep the session alive anymore. So it's your choice to handle the interval of how long a session will be kept and also how long the pages will stay in the cache why this setting from ABMShared Process_Globals:
B4X:
Public SessionMaxInactiveIntervalSeconds As Int = 30*60 ' 30 minutes '-1 = immortal but beware! This also means your cache is NEVER emptied!
So if you have a few users you could give this setting a higher value like 7 days (60*60*24*7) but if a lot of users access your app then you will need to keep this setting to a lower level as things will pile up in cache mechanism and also in the session store - and all this data is kept in your server (session and cache) ... so you are the only one that knows whats the perfect value for you.
I hope I haven't missed something and explained for everyone to understand ... if not fire your questions