Clean design for centralized navigation? - javascript

Context
Single page / ajax web app
Basic code structure
LocationManager (responsible for updating the browser hash and switching the application location to a different tile)
Page/Tile Flow
Basic Info > Household Info > Vehicle Info > Purchase Options > Review Order > Enter Payment and Submit
Problem
When the user navigates from Purchase Options to Review Order, a long (5-8 second) service call is made to calculate order details. Upon the call's resolution, the callback is designed to navigate the user to Review Order page. The issue is, if the user clicks back during that time and goes back to Household Info, as soon as the call resolves, they will be "automatically" brought to Review Order. Very awkward user experience.
Limitations
Canceling the call is not an option. Need a solution to handle the navigation.
Current Proposed Implementation
Save "currentLocation" prior to making the calculateOrder call.
Pass the "currentLocation" in the callback to the setLocation method as intendedStartingPoint.
Inside setLocation method if(intendedStartingPoint === Locationmanager.currentLocation) {//Navigate}
To sum it up, if the user changes the location while the call is in progress, upon the call's resolution, we won't navigate since the user doesn't expect to be navigated to Review Order at that point.
This works, right?
The Catch
We have many places in the app where setLocation is called within a callback for a long-running call. This means that I will have to update all the setLocation calls with a new parameter - intendedStartingPoint. While it makes sense to me, it does seem like it has potential to get a bit cluttered.
Any ideas on how to clean it up and centralize it?

So, right now a user can click the Calculate button on a Purchase Options page. You then display some kind of a loading indicator (hopefully)
and send an asynchronous request to a server with setLocation('ReviewOrder') attached in a continuation. There is quite a number of places in the application where you use this pattern.
The problem of unexpected (from a user point of view) redirects is there because with this approach server data retrieval and UI navigation are coupled. A solution that comes to mind is to decouple them and remove setLocation calls
from all long-running request continuations. It can work the following way.
When the user clicks the Calculate button, you start an asynchronous request and at the same time immediately navigate to the Review Order page (this is important from a UX perspective since users now clearly understand that the Calculate button navigates to Review Order). On the Review Order page, display a loading indicator saying something like 'please wait, about 10 seconds remaining...' When a request completes, hide the loading indicator and show the data.
This way your users will have a consistent UX knowing that whenever they click a button in your application the same thing happens (they navigate to a view), and there are no surprising automagical redirects.

Given that you can't prevent the user from navigating among the tiles, notifying her about the calculation delay won't solve the whole problem. You can tell the user the estimated time to completion, you can display a progress bar, and you can take her immediately to the Review Order tile to wait for the results, but if she navigates away from the tile, you're left with your original problem.
If the user chose to navigate away after all of that information, she must have made a conscious decision to interrupt the proceedings. It would be bad UX to transport her back to Review Order. What now?
You propose, quite reasonably, that the callback function sent with calculateOrder should pass an intendedStartingPoint parameter to setLocation. You worry that this would require you to modify every call to setLocation to accommodate the new parameter. Never fear, JavaScript offers a neat way to solve this dilemma.
You can add a new parameter to setLocation without modifying the existing calls. This merely requires that intendedStartingPoint be the last argument in setLocation's argument list. Then your new version of setLocation can check the value of intendedStartingPoint to see if it's undefined.
If intendedStartingPoint is undefined, you know that setLocation is receiving one of the old calls, the ones that don't pass intendedStartingPoint. In these cases you ignore intendedStartingPoint and proceed as before. Otherwise, you compare intendedStartingPoint to the current location and proceed according to the result of the comparison.
An even better approach would be to make the new parameter not intendedStartingPoint, but an object called options that contains intendedStartingPoint as one of its attributes. This allows you to pass further optional values to setLocation if the need arises in the future.
The new behavior of setLocation is quite simple. Before setting a new location, you check whether intendedStartingPoint is equal to the current location. If it is, you don't have to do anything because the user is already where she's intended to be. But if the intendedStartingPoint is different from the current location, the user has navigated away, so you do something like this:
if (LocationManager.currentLocation !== options.intendedStartingPoint) {
// Tell the user that the calculation has finished.
// Ask her if she wants to return to Review Order now.
}

First thing, calculate order details via asynchronous call and show/simulate a progress bar to the end-user via javascript.
The second thing: do not enforce ReviewOrder tile opening in your service callback function. As the service completes it's calculation, your callback function checks the current tile, and if it is not ReviewOrder tile, then it stores the calculated information in the Session or Local Storage.
As user navigates ReviewOrder tile, compare order details which came from the user with the stored order details (via hashing function, for example).
If hashcodes of user order details and stored order details are the same, then show saved order information, otherwise call the service again.
Important note: to prevent order forging, consider the following way:
Upon calculating order details on the server, generate unique order id, that will be returned to the user. Then store the calculated order details along with this id in the server database. If user did not change order details, your script will post only this order id to the server as a sign, that order has been accepted. Then read your database and process the order by this id.
If order was not completed then employ a scheduled task, that cleans up your database from non-completed orders (for example - orders, calculated 24 hours ago, but still not completed).

First of all, if the user is able to go back and change any entered information on previous pages, it is a must to invalidate any pending service calls. If a service call based upon outdated information returns, it must be discarded.
This means, if(intendedStartingPoint === Locationmanager.currentLocation) {//Navigate} is not sufficient. You have to do something like if(intendedStartingPoint === Locationmanager.currentLocation && /* no information altered in the meantime*/) {//Navigate}.
Now for your design question: It's a little hard to construct this without any concrete code, but you could do the following:
Provide means to register and manage long-running calls in LocationManager
Long-running calls should then always be registered with the LocationManager
LocationManager should assure that at most one long-running call is active at a moment
If any location change occurs, all (or the one active) long-running call must be invalidated
Call-back of a long-running call should check if it not has been invalidated, and only navigate if this is the case. LocationManager could do this in a unified manner for all call-backs.
New long-running calls could replace/invalidate an already running call or be rejected, as you like.
I hope this makes sense in your concrete situation.

Related

Dealing with a certain async situation in JavaScript

Imagine I have such setup. Imagine I have some map on my page and next to it there is a dialog where there is a toggle button.
Imagine when user clicks the toggle button a different request (depending on toggle state) is sent to server (which returns different type of map data depending on the request). If toggle is clicked once it receives say data about restaurants, next time it is clicked it receives data about hospitals and so on.
Also imagine when user drags a map, a refresh signal is sent to server, which sends same type of data but with updated information. For example, if user drags map when hospitals are shown on the map, again hospitals are received but with updated location.
Problem
Imagine two situations:
Assume user quickly clicks the toggle several times - then following thing will happen. First, request is sent to receive the restaurant data. But restaurant data has not arrived yet - and now before the restaurant data is received, the next toggle click happens, and request is sent to get hospital data (normally this second click on the toggle would delete restaurant data but since it is not on the map yet, it can't). So in the end we will end up with both restaurant and hospital data on the map which is what we don't want.
Imagine user clicks toggle and request is sent to receive restaurant data. But before restaurant data is received imagine user drags map (which causes refresh). What happens now due to drag is that since currently there is hospital data on the map, due to refresh, request to get updated hospital data will be sent. In the end we will again end up with hospital and restaurant data on the map.
I hope you can see the pattern of the kind of problems I am encountering here.
What is the best practice to deal with such situations?
I have solutions in my mind. One has the impact on User Experience and UI, the other one involves sending the last request. I will explain how:
1. Disabling the map and the button until the request is complete.
So what you can do is a loading or overlay div that stays until the request info is returned. Until this, the user is not allowed to use the map or toggle. This is UI impact but I have seen sites behaving this manner.
2. Map position.
The other option is to store the map position in like local storage and match the map position again on request success. If the map is on a different position, serve a message like Search in this area etc. Google maps behave something like this manner.
3. Serve the latest ajax request and abort others:
This can be done, by pushing the requests in a queue/order and send the last one to get the response. like if you are catering it via ajax, so some code like below code, if I call getFoo for 10 times, the last one will be fired
var ajax = null;
var getFoo = function() {
if(ajax) ajax.abort();
ajax= $.ajax({});
};
What you're describing is a simple case of race conditions.
There is no general solution for this kind of problem, it needs to be handled case-by-case. But in most cases, you probably want to display the results of the latest action user has done. If you don't have another way of knowing which request is which, you can attach some sort of identifier to the request, for example a GUID, and only display the data when the corresponding request is done.
Sometimes, the solution can be simply to disable all actions which could cause a race condition for the duration of the request, but this can deteriorate the user experience. Take for example Google Docs online editor, imagine that the whole editor would get disabled every time the auto-save function is triggered. In cases like this, it would be beneficial to store every update and compose the state from these actions. One of JavaScript that does state management like this is Redux. If you want to store data like this, you could use a database, such as EventStore.

When multiple users are viewing a record and 1 person updates the record, how to notify other the record is updated?

In my Project Management app I want to solve the issue of multiple users viewing the same Task record and 1 or more of them updating some of the Tasks Data while the others are viewing it.
Here is a scenario...
User #1 views Task #1 and User #2 views Task #1
Now User #2 updates the Task #1 Description
User #1 is now viewing the Task record but his view shows a different out-dated Description since User #2 had just updated it! Even worse he may want to edit the description himself which would completely over-write User #2's Description update.
That is 1 example of the issue. Just add more users and the issue multiplies!
Ideally I would use something like Sockets or a service like Pusher.com to update the data for all users as soon as an update by any user is made. However this project is going to be on hundreds of servers and has limited capabilities as far as server requirements so sockets and even a service like Pusher are out of the question!
Another idea for a solution is based on what Twitter does. If you view a persons Twitter page and they make a new post while you have there page loaded. It will show a notification message DIV to tell you there are X number of new posts and give you a link to click to reload the stream of posts with the latest posts.
I believe a similar approach could work for my project. If a user makes an update to any Task data while other users are viewing that Task record. It would show a Notification message on the Task modal window telling the user that the Task data has been updated and that they should reload the Task.
To make this work I know there will need to be some AJAX request made at some interval.
That AJAX request would then need to compare the timestamp of the last update made on the Task record and compare it with the time the user viewing the Task record started viewing it or since they last reloaded the task.
I feel like my logic is missing a piece of the puzzle though? Is this all correct or am I missing something?
Can someone explain how I can do this or tell me if what I have in mind is right?
I know that in short I simply need to determine if the Task last modified Timestamp is AFTER the other user started viewing the Task. At some point though I feel the users time should be updated too?
UPDATE
I completely forgot that Stack Overflow does this exact task on questions and answers! When you view a page on SO and a Answer is updated it will show a notification message telling you to reload the answer and provide a link to reload it. That is what I want to do!
StackOverflow uses Web Sockets to do this but in my app which is a plugin to be used on many different server configurations, I cannot use Sockets. I am trying to achieve similar result with AJAX. Even if it's an AJAX request made every 30 seconds to get the task modified Time and compare it to another to determine if user should reload task data would work in my case
Your question is too broad, but you are basically describing a Pub/Sub.
Idea
Whenever a user enters your site, he gets a token to identify them.
If he accesses a task, he subscribes to that task, which means that any modification on it would be alerted to him.
He polls the server to check if there is any alert to him.
Implementation
Regarding implementation, you can set-up a list with each user's subscriptions.
Using your example:
User1 subscribes to (Task1, Task2)
User2 subscribes to (Task1)
With each subscription, you keep some value that represents the last state the user has about that topic (e.g, the last modification timestamp).
The user polls your application every n seconds. Whenever the request reaches your application, you check the user's subscriptions, and check if the timestamp has changed (if they have the latest). If so, you update the last state that the user has and retrieves him the new values for the tasks that changed.
Things to consider
This list of subscriptions will be constantly accessed, so becareful where you are going to store it. If in memory, consider that you will need to share it across the different instances (if you are load balancing). You can use Redis, or something like that.
You do not need to go to your database everytime you need to retrieve data. If someone is subscribing to it, keep it on a cache.
The concept and idea is fairly trivial, and the implementation shouldn't be any more difficult. You need a Last Updated timestamp for each task as you say, as well as a Last Update timestamp on the client if you go with the described approach. Generally, when a User is viewing a Task, you'd want to (on the client side, Javascript):
Query the server for the Last Updated timestamp of the Task being viewed with AJAX.
If the Last Updated timestamp of the Task being viewed is greater (newer) than the Last Update timestamp on the client goto step 5.
Wait (asynchronously) n seconds.
Goto step 1.
Notify User that the Task being viewed has been updated.
End (since it is already known to the client that the Task has been updated, there is no need to keep polling whether it's been updated or not).
One approach could be simply creating an asynchronous interval using setInterval() that would be cleared once it has been determined an update occurred, and the user is shown a message afterwards.
var lastUpdate = Date.now();
var intervalDuration = 30000; // 30 seconds
var interval = setInterval(function () {
var xhr = new XMLHttpRequest();
...
xhr.onload = function () {
if (...) { // if the Task's Last Updated timestamp is newer than lastUpdate
clearInterval(interval);
// show message to user that the Task has been updated
}
};
}, intervalDuration);
I can imagine in a significantly massive system this approach could be easily inadequate, but for non-enterprise grade solutions or for follow-up UI improvements this can be a quick, cheap, and spectacular solution.
Of course there are potentially more robust and flexible alternatives to this: long-polling or websockets.

How to perform an ajax call on page unload?

I have a dashboard where users can toggle some input values in order to configure the appearance of the page.
I want to store those changes in a DB table, so when user comes back, the dashboard appears according to the specific user configuration retrieved from the DB.
I know how to send those values to DB and how to retrieve them
I don't want to make ajax calls every time the user changes a configuration.
Instead, I think this senario would be better:
Page load (retrieve DB configuration if exist)
User toggles the configuration ui items (e.g. checkboxes, select etc) and the appropriate client side changes take place (some divs get hidden, some others are shown etc and the config input values are stored to a hidden field), but no ajax call takes place.
When user clicks a link to another page, the configuration input values (which have been stored to the hidden field) are sent to the DB via ajax call.
MY QUESTION
One solution(?) would be the use of onbeforeunload event like this:
window.onbeforeunload = function(e) {
// Perform the ajax call
return 'Do you want to save the configuration changes?';
};
But, if the user's browser prevent popups, the function will not get executed?
Is there a way to perform an ajax call on onbeforeunload event, without calling a dialog box?
No. During unload, the browser will kill all pending requests. So the AJAX might or might not arrive at the server. You also can't do it inside the handler of the beforeunload event because the first A in AJAX means: Asynchronous -> Just put the request on the stack and eventually execute it. So the request will be looked at only after the handler returns. But very soon after that, the browser will kill anything related to the page.
The solution is to always send updates to the server while the users makes changes. Put those into a special "temporary" table from which you can restore the state later.
You could also use something like localStorage in the browser but then, the data wouldn't move with the user. For example if they were working on an tablet and that broke or had a power loss, they could move to their PC to continue where they left off.
You can't guarantee that an Ajax call will complete in this way, see Aaron's response. My suggestion would be to use something like localStorage to read / write the user's settings instead.
If you need a user's appearance to persist across multiple devices, add a periodic request to read / write the recent updates from localStorage to the central DB.
Try using Navigator.sendBeacon on the visibilitychange event.
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
navigator.sendBeacon("/ajaxUrl");
}
});
Regarding Navigator.sendBeacon(), MDN details that when leaving a page,
the browser may choose not to send asynchronous XMLHttpRequest requests.
With the sendBeacon() method, the data is transmitted asynchronously when the user agent has an opportunity to do so, without delaying unload or the next navigation. This means:
The data is sent reliably
Regarding the visibilitychange event, MDN recommends:
Web sites often want to send analytics or diagnostics to the server when the user has finished with the page. The most reliable way to do this is to send the data on the visibilitychange event

Wait until a callback address is called

I am programming an educational web in PHP and JS and I interact with another webpage that corrects for me some programming exercises.
I don't know when this correction will finish, but this correction webpage calls a given address when the correction is available.
Is it possible to wait for that result with PHP or JS and then do some stuff (not just showing the result to the user), even if the user closes the tab?
Thanks in advance.
STEP BY STEP explanation:
A student is trying to do a programming exercise. The statement of the exercise is shown, as well as other details to complete it.
When he thinks he has reached a solution, he uploads his code. Then, he can wait (or not) for the correction to finish.
My web page uploads then the code file (with other files as well as an address to callback when it finishes) to the corrector webpage.
I keep waiting until the callback is made.
When the correction is finished, I do some stuff like assigning a grade to the student and, if he/she stayed on the same page, he/she can see details of the correction.
The correction is done by another webpage and I don't know when it would finish, but when it finishes it calls a given adress (for example: www.example.com/ex1sub1usr1 ).
The doubts are between step 2 and 3. It is possible to do all this process with PHP/JS? If not, what are the alternatives?
Updated Answer
Create a PHP script on your server which updates your grade database when visited with a specially formulated URL, eg. http://yourserver.com/update.php?user=2429&sub=1&ex=1
specify this as the target URL when you submit for correction
if you are restricted to your /ex1sub1usr1 format then just use regex to parse the URL
When user refreshes (or returns to your site) the data should be appropriately pulled from your PHP backend
For bonus points, you could have your submission screen (or the entire website) intermittently poll another PHP script URL on your server (with XHR) for an "updated" flag, and live update the page.
Considerations
Your only major concern will then be ensuring that the form data (ie. their code) is submitted completely. Consider using XHR to submit your form data and temporarily enabling a window closure warning until upload is complete.
Original "Answer"
Since there isn't much information to go by here, I will have to make a few assumptions:
you presumably can't reliably spawn a process to watch the associated results address
you don't need to notify the user immediately (push notifications), but they need eventually see their result, eg. when they return to your page
results stay around for a while
as you mention, this must work even if the user closes the tab
Given this scenario, I would merely check the results page when the user returns to your site.
Just store the results address in session vars - either via PHP or a cookie - then when the user returns you can perform any unfinished checks and update accordingly.
NB: Should further information surface in the question I will gladly provide further details
You can perform all necessary actions on a script behind the given address that is called by the remote website after the excersises have been corrected.
It is absolutely not possible to show/do anything on client side if the user has closed the tab.

Mixpanel anonymous user converts to identified user tracking

I'm adding Mixpanel to my web application and I'm curious about the "process" around what happens when a user transitions from "anonymous" (not logged in/registered) to "identified" (when they register / create an account on the site).
If a user comes in and is new to the site, they get an anonymous UUID (according to the documentation). The documentation also says that Mixpanel can not translate between IDs at this time.
Does this mean Mixpanel is incapable of handling the transition of a non-registered user to a registered user, and keep track of their events from before they became a registered/identified user?
If so, does anyone have experience with working around this? How'd you go about it?
As of December 2012, you can now use the mixpanel.alias method call to alias two ids:
https://mixpanel.com/docs/integration-libraries/using-mixpanel-alias
From the above docs:
John comes to your website, example.com, for the first time. He is
assigned a randomly generated ID (perhaps 123123) by Mixpanel.
Everything he does is associated with that ID.
After clicking through a few pages, he successfully signs up. On the
signup confirmation page, you call mixpanel.alias("john#hotmail.com").
This doesn't actually change his ID - he is still being identified
using the random ID we originally assigned him.
What it does do is add the ID "john#hotmail.com" to a lookup table on
our end. Whenever we see data for "john#hotmail.com", we know to remap
it to 123123, his original ID.
So, you can start calling mixpanel.identify("john#hotmail.com") on all
your pages, and your events, funnels, and retention will all continue
to work perfectly.
There are ways to make this work. But what you are really asking for is a feature called distinct id aliasing, which would allow you to reference one distinct_id ID to another. Unfortunately, we don't offer that right now. This turns out to be a much harder issue than you'd expect due to the unique nature of the data-store we wrote for mixpanel.
In the meantime, I can give you a few strategies to get around this limitation:
When a user first comes to your website, set a distinct id for them which you generate internally. Once they register for an account, reference that distinct_id in your user detail table, and then continue to register subsequent events with that id. Each subsequent time a user auths, use the stored value as the distinct id. Hopefully when they return the cookie will still be around, and you will capture all the events without a hitch.
You could, also, let mixpanel give them an auto-issued distinct_id value, and then grab that at the time of registration by using mixpanel.get_property() then add that to your users table, and use that when you identify them in the future.
But what if they auth from one machine and then come on from another, or a different browser, or from a mobile device? Then the time in between when they hit your site and when they auth they'll be issued a new distinct_id by your site... and there is no way to alias! The solution here is a bit hackier. The only way to get that data is to log those events that were sent before the authentication (maybe server-side) and then send them via HTTP specification to the rest API with the correct distinct_id once the user auths. As long as you keep the correct time stamps, it will all appear correctly, chronologically within mixpanel. If the user never auths, then you can have the logged events time out and send them anyways.
Would either of these work for you?
When a user hits your site, identify them with a unique id and save it in a cookie if they don't already have one, then use the Mixpanel Identify API call to identify them. You can persist the unique id to your database in the user's record once they have registered, so you can re-set it in the case they clear their cookies.
If the user clears their cookies before registering, then you would be out of luck, but that's the nature of this beast and would be an issue anywhere.

Categories

Resources