Clean design for centralized navigation?
Asked Answered
G

4

6

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?

Gambit answered 21/8, 2015 at 13:26 Comment(5)
Wrap the setLocation call in a specialized function, supply it to the Review Order callback and make the specialized function also take an intendedStartingPoint as a parameter and handle the intendedStartingPoint logic?Melchior
What about disabling back button (or any action) until the call returns? Otherwise simply check your current location on call return and redirect only if on the right tile. ( Note: in this case there can be still the problem of the user going back, modifying some data and then returning to the correct tile without relaunching the call, resulting in being redirected without the new data being saved/verified, so you should at least auto-relaunch the call on data change, but simply canceling the call on navigation/data change would be way more ideal. )Boarder
@SzabolcsPáll it's not possible to "disable" the browser buttons from within the website. The solution you proposed otherwise is that I have outlined in the question.Gambit
Then @Soggiorno's suggestion seems to be the right way to do it, but I really don't see the point of finishing a call that probably needs to be repeated. About the browser back button, you can also pause the functions that change your tile based on your URL until the call returns, thus blocking that navigation.Boarder
Please remove java tag (if I understand correctly)Hodge
T
5

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.

Tollbooth answered 27/8, 2015 at 14:21 Comment(0)
P
2

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.
}
Pleochroism answered 29/8, 2015 at 11:17 Comment(0)
P
1

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).

Pudency answered 27/8, 2015 at 16:49 Comment(0)
B
0

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.

Burnish answered 27/8, 2015 at 14:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.