ajax dialog… but not if not yet authenticated

The problem

In my Blacklight implementation, I am implementing the ILS request function. Doing this by basically a glorified screen-scrape of the OPAC behind the scenes. (It’s a BIT better than an html screen-scrape, but the details are irrelevant to this post).

I wanted to do that in an in-page javascripty dialog box, not take the user to another page.  This will improve our somewhat terrible workflow for making requests — some things I can’t change because they aren’t really supported by the underlying ILS/OPAC, like you don’t know if an item is requestable until you click ‘request’ and find out. So I figured, okay, at least make this as painless as possible by doing it in a nice light in-window dialog box without a page load.

So far so good, but the problem is authentication. You can only make a request if you are authenticated. In the non-JS original implementation (everything I do starts with an implementation that works reasonably without JS, with JS added unobtrusively), no problem, if you try to make a request and are not logged in, it’ll redirect you to the login page, then redirect you back when done.

But I can’t really do THAT part in an in-window popup dialog. In part because it’s probably a bad idea no matter what, but also becuase our SSO login is incompatible with that, it’s got to be in a page of it’s own.

The design

I thought and thought on how to deal with this, before coming up with a pretty good solution, such that if the user IS logged in they’ll get an in-window javascripty dialog. If they are NOT logged in, they’ll be redirected to the login page, and then redirected back to the page they came from–and an in-window javascripty dialog will then immediately be launched, so actual request interface is consistent either way.

The tricky part is that last part. When you return the user after login, how does the page know to put up the request dialog?  Answer:  The trusty technique I’ve been seeing more these days, of sticking something in the page #anchor as a message to javascript. The nifty Blacklight auth code written by Chris Beer already allows you to tell the login action the ‘referer’ the user should sent back to after login. So I just make sure to give it a ‘referer’ URL that includes an #anchor that will be a message to javascript to pop up a request dialog.

What string should be used as a message? How about just putting the exact path URL that will be used for making the request? It’s available to the javascript sending the user off to the login action; and then it’ll be there for the receiving javascript to know exactly what to do. All works out niftily.

So basically:

  1. Javascripty goodness tries to make an AJAXy request to to put the ILS Request function in a jQuery UI dialog.
  2. Rails app returns an 403 if the user is not logged in, otherwise the HTML to unobtrusively stick in the dialog.
  3. Javascript trying to fill the in-window dialog notices the 403, and if found does a document.location.href to redirect to send the user to the login screen.
  4. When doing that document.location.href, we give the login action a referer url to come back to. When constructing that referer URL, give it an #anchor that’s the request URL we originally tried.
  5. On every page, javascript checks the #anchor against a regexp to see if it looks like a request URL, and if it does, fire off the request in a javascripty in-window dialog.

The code

The actual code is quite a bit shorter than that explanation!  Using the ajaxyDialog JQuery UI widget I wrote; writing that was motivated by this use case, as well as a general abstracting of the times I’ve done similar (although slightly less complex) things with javascripty in-window dialogs added unobtrusively.

The rails app has a before_filter that just returns a 403 if it’s an xhr javascripty request and there’s no logged in user, basically like this:

def check_auth
  if request.xhr? && ! current_user
    render :status => 403, :text => "Can't request over xhr when not logged in."
  end
end

The javascript uses the ajaxyDialog widget, with the error callback to catch the 403, and redirect to our login action, with referer set to the current page, but with an #anchor added in with the URL of the request action. Amazingly easy with ajaxyDialog:

$("a.request").ajaxyDialog({
   width:600,               
   error: function(event, ui) {
       if (ui && ui.xhr && ui.xhr.status == "403") {  
       //User has to login. Redirect them to app login panel, manually
       //setting the return URL to be ourserlves, plus a special
       //anchor we'll use to notice we're supposed to open the request
       //dialog when we return.
       document.location.href = '/login?referer=' +
          escape( document.location.href.split("#")[0] ) +
          escape('#' + $(this).attr("href"));
       return false;
   }
 });

And then we just have some code in document.ready that looks at the current anchor, regex matches it to see if it looks like a request url indicating we should pop open a request dialog, and if so open it up. My request urls look like “/catalog/[id]/item/[id]/request”, so that’s what the regex looks for. To open it up, we just add a hidden hyperlink to the DOM with the right href, and apply the ajaxyWindow to it.

(Okay, there’s one part I’m hiding from you for clarity — I actually used a trick to attach the ajaxyDialog widget in a ‘live’ way to any a.request as it gets added to the DOM, is why I can just add an <a class=’request’> and know it’ll be ajaxyDialog-ified. Details on that in the readme for ajaxyDialog).

/* Okay, this is tricky, if we had auth on a request, we redirected
 to auth and then back, saving the intent to make a request in
 the anchor. If we discover it in the anchor, we want to make
 a request again. An actual link to make the request may or
 may not currently be on the page (may be in a collapsed not
 yet loaded component), so we'll just add a little anchor
 to the page to do it. */        
 if ( /#\/catalog\/.*\/item\/.*\/request/.test(document.location.hash)) {       
     $("<a class='request' href='" + document.location.hash.split("#")[1] +
      "' />").hide().
      appendTo("body").trigger("click");          
 }

Ta-da!

After I get a few UI rough edges taken care of, I’ll make a screencast so you can see it in action without having login credentials for my demo.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s