Posts
48
Comments
144
Trackbacks
0
October 2010 Entries
Detecting the File Download Dialog In the Browser

Updated 2011-01-28 to reference the correct question stackoverflow.com

Updated 2011-10-02 With a link to a sample application on github.

Updated 2013-07-01 with corrected jQuery Cookie plug-in link and syntax

In the web application I work on we sometimes need to create PDF documents based on some user input. In most cases, these documents are fairly simple and quick to create, allowing us to create the documents in memory and then send them back to the requesting user with a “Content Disposition: attachment” in the HTTP response. This causes the browser file download mechanism to kick in allowing the user to save or open the resulting PDF. This approach works great and I think has come to be the expected behavior for getting files in 3rd party formats (Word, Excel, Adobe PDF, etc.) from a web application.

Generating a file, however, can sometimes take a few seconds. This short duration is not necessarily long enough to justify offloading the file generation to a separate process (e.g. a service bus, a separate worker thread within your web server process, etc.) but is long enough that the user waiting for the download may think that nothing is happening and attempt to click the “create file” button again. We definitely do not want the user submitting a request to create the same file over and over again, so we might use something like the jQuery Block UI plug-in to display a “please wait” message and prevent further requests from being made from that browser. This works great, but you eventually need to un-block the UI so that your users can continue doing their work after downloading the file. Unfortunately, I don’t know of any way javascript tricks to detect when the file download dialog is displayed to the user. Without being able to hook in to that event, the UI will remain blocked forever, forcing the user to either close and re-open their browser or hit the back button to get away from the “Please Wait” message. You could use some kind of time out to automatically un-block the page, but in most cases you’d likely end up un-blocking too early or too late.

There are some ways to work around this by writing the file to disk or some caching mechanism and then providing a separate URL endpoint to download the finished file, but these approaches require what is, in my opinion, a non-trivial amount of server side code to accomplish. All I really want is a way to let the user know that we’re building their file and to keep them from hitting ‘submit’ multiple times. In seraching for this I discovered a question on Stack Overflow (originally found on another not-to-be-named site that scraped stackoverflow.com) where one of the answers presented an interesting idea for solving this problem. While we can’t use javascript to determine when the browser receives a response with the “content-disposition: attachment” header, we can use javascript to determine when the browser receives a cookie with a certain name and value. We can add a very minimal amount of code to our server side implementation to add a cookie to the file download response and then use javascript to equate the presence of that cookie with the fact that the file is available for download. Let’s take a look at some code:

The Client Side Setup

The code I’m showing here is adapted from an ASP .NET Web Forms application, but I’ll try to keep it as a generic looking as possible as this approach should work regardless of the platform you use. First, let’s see a simple HTML form that we might use to capture the needed input from the user to create our PDF file:

<form id="create_pdf_form"> <fieldset> <legend>Create Customer Info Sheet PDF</legend> <div> <label id="first_name_prompt_id" for="first_name_input_id">First Name</label> <input name="first_name_input" id="first_name_input_id"/> </div> <div> <label id="last_name_prompt_id" for="last_name_input_id">Last Name</label> <input name="last_name_input" id="last_name_input_id"/> </div> <input type="hidden" id="download_token_value_id"/> <input type="submit" value="Create File"/> </fieldset> </form>

Notice the ‘hidden’ input field I included in that form. We’ll use that field to provide a token value to be included in a cookie in the file download response. Now let’s look at the jQuery code that we’ll use when this form is initially submitted. Note that this requires jQuery 1.4.2, the jQuery Block UI plug-in, and the jQuery cookies plug-in.

$(document).ready(function () { $('#create_pdf_form').submit(function () { blockUIForDownload(); }); }); var fileDownloadCheckTimer; function blockUIForDownload() { var token = new Date().getTime(); //use the current timestamp as the token value $('#download_token_value_id').val(token); $.blockUI(); fileDownloadCheckTimer = window.setInterval(function () { var cookieValue = $.cookie('fileDownloadToken'); if (cookieValue == token) finishDownload(); }, 1000); }

First, we’re using jQuery to hook into the ‘submit’ event of the HTML form. This will get fired just prior to the form data being submitted to the server. When this happens we’re generating a token value using the current timestamp. This token can really be any arbitrary string value, but we do want to try and make it unique per request coming from the same browser, and using the timestamp is a pretty easy way to do this. We take that token and add it to the form within that hidden field that we created. This will ensure that the token value gets submitted up to the server (more on this later).

Next, we’re using the jQuery Block UI plug-in to block the user from submitting the form multiple times. The behavior of the Block UI plug-in is very configurable and I encourage you to go check out the documentation for details on all of the different ways you can customize what message is displayed to the user while the UI is blocked.

With the UI effectively blocked, we use the ‘window.setInterval’ function to create an interval timer. The first argument provided is the function that you want to have executed at each interval and the second argument is how long you want the interval to be in milliseconds. For our purposes we want some code to check for the presence of a cookie value every 1 second. We’re using the jQuery cookies plug-in to examine the value of a cookie named ‘fileDownloadToken’. When the value of that cookie matches the token value that we generated before (based on the current timestamp), we’re going to invoke a method called ‘finishDownload’. We’ll look at the finishDownload method in a bit. First, let’s see what we need to do on the server side.

The Server Side Setup

Again, I’ve adapted this example from an ASP .NET Web Forms application, so the code below is C#, but this approach should work for just about any web platform. I’ve taken out the particulars around how the file to be downloaded is actually created and written to the HTTP response, as that will vary from application to application.

var response = HttpContext.Current.Response; response.Clear(); response.AppendCookie(new HttpCookie("fileDownloadToken", downloadTokenValue); //downloadTokenValue will have been provided in the form submit via the hidden input field response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", desiredFileName)); //desiredFileName will be whatever the resutling file name should be when downloaded //Code to generate file and write file contents to response response.Flush();

What I like best about this approach is that you were going to have to write 99% of the above code one way or another. The only addition that had to be made to the server side functionality was the single line where we add the file download token cookie to the response. This is the cookie value that our javascript timer is polling for back on the browser while all of the server side code is building the file to be downloaded. Once the response is flushed back to the client, our timer will see that the cookie is present and invoke the ‘finishDownload’ method. Let’s take a look at that.

Finishing Up On The Client Side

Once the expected cookie value appears, all that’s left to do is a little bit of clean up in the ‘finishDownload’ function:

function finishDownload() { window.clearInterval(fileDownloadCheckTimer); $.removeCookie('fileDownloadToken'); //clears this cookie value $.unblockUI(); }
All we’re doing here is clearing out the previously set interval timer, removing the cookie, and unblocking the UI so that the user can continue doing their work after they decide what to do with the downloaded file. All in all, we added a line or two to the server side code and a few lines of javascript to accomplish our goal of informing the user that we’re working on their file.

I’ve put together a full working sample of this approach in the form of an ASP .NET MVC 3 application that you check out on github: https://github.com/appakz/Detect-Browser-File-Download-Dialog-Sample

Posted On Thursday, October 28, 2010 4:38 PM | Comments (94)
Linking Shopify Products Together With Naming Conventions (and a little jQuery)

As I mentioned in my last post I talked some about how my side project, PiX Eyewear, has given me an opportunity to venture out of the predominantly server-side .NET programming that I’ve been doing at my “day job” for the past several years. In this post I’ll be digging into a simple way that I’ve been able to leverage jQuery to solve a little problem I had with my Shopify site.

Before I go any further I want to call out an excellent approach for displaying “related products” on a product page that Caroline Schnapp posted up on the Shopify Design forums a little while back. This is a really clever approach to doing this, and one that I only discovered recently after coming up with the approach that I’m about to describe. I think that both approaches have their relative merits, and go into that in a bit more detail toward the end of this post. As always, choose the right tool for the job at hand.

demo_chain_verticalThe Problem

PiX Eyewear sells sunglasses and reading glasses that feature a channel embedded into the temple that accepts temple inserts. These temple inserts can feature virtually any design and can be made out of a variety of different materials. The idea is that you can buy a single pair of glasses and change up the look using these temple inserts. Pretty cool idea, right? :-)

We offer four different styles of sunglasses each available in two colors. Two of those styles are also available with two different lens colors. For reading glasses we have one frame style available in three different colors and about a half dozen different magnification strengths. Take all of those frame combinations and multiply it by an ever-expanding collection of temple inserts and you’ll begin to see why I’ve struggled a bit with finding the best way to get my products into my Shopify store in a way that makes sense and will let my customers find the things that they are looking for.

I’ve gone through three or four iterations of re-configuring my inventory to try and solve this problem. Obviously, Shopify features a very robust product database for storing and organizing the products for your store. Each product can have up to three different types of variants defined. Variants can be used to offer a single product in different sizes or colors and were perfect for letting users choose the various colors and magnification strengths that are offered with our frames. The problem with variants is that they don’t scale enough to manage all of the various combinations of frames and temple styles that my store offers. At first, we offered frames and temple inserts as completely different products. This approach made a lot of sense because we wanted to let people buy a single pair of frames and pick out as many different temple styles as they wanted. This approach lead to the situation where customers were seeing the temple styles that they liked while browsing the site but then not realizing that buying the temple style didn’t include any frames!

After having to give away frames to a few confused customers I decided I needed a different approach. This temple insert system is patented, so there’s no one else selling these inserts or frames anywhere else online (yet). That coupled with the fact that we’re such a new business made me realize that offering the temple inserts as products by themselves is pretty much pointless right now. Practically all of our potential customers are going to need to buy a pair of frames along with their temple inserts. That said, I wanted to be sure that people understood that the temple inserts were available by themselves as well. What I really wanted to be able to do was define a bunch of products that represented “frame combos”. A combo would be a temple style that comes with a frame of your choice. By making the frames variants of the temple insert products things became much simpler and easier to manage. I still didn’t have a good way to denote that fact that all of the “combo” styles are also available as inserts by themselves. I could have made one of the variants “inserts only”, but that felt clunky at best. What I really wanted to do was have temple inserts offered by themselves and temple inserts offered as combos managed as completely separate products with some means to establish a link between them. To the best of my knowledge Shopify doesn’t support explicitly linking two products together, so I had to get a little creative.

Enter jQuery

Shopify exposes a neat little jQuery API that you can easily load into your store. The API gives you quick and easy ways to perform some common tasks like add a product to the cart, or gather all the details of a given product. The best way to learn about the API is to check out the jQuery Ajax API Sandbox. In thinking about my problem I realized that I could leverage this API to do a quick look up on my product page to see if I had a complementary product that I wanted to show a link to. This can be accomplished using the ‘Shopify.getProduct’ method of the API. This is a simple method that takes a product handle and returns some JSON representing that product’s properties. Among those properties is the URL to access that product’s details page.

In order to use this, I first needed to load the Shopify API into my store. This is easily accomplished by adding a line to the <head> section of your ‘theme.liquid’. There are some convenient shortcuts for adding references to javascript libraries that are hosted by Shopify. Obviously, you also need to be sure to include a reference to jQuery as well. Here’s an example from my own theme.liquid (note that I’m using the Google CDN for hosting jQuery itself):


  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
  {{ 'api.jquery.js' | shopify_asset_url | script_tag }}
  

That’s all you need to be able to start using the ‘getProduct’ method on your site’s pages. The only problem I had now was knowing the product handle for the related product that I wanted to show a link to. At first I was trying to figure out if there was a way that I could embed the related product via some specially named product tag or some similar hack. If I could find a way to render the handle of the related product in the mark up of my product details page it would be easy to grab it with jQuery after the page was loaded and asynchronously get the related product’s details to display on the page somewhere. I probably could have accomplished this with some clever manipulation of product tags, but that seemed like it would be hard to maintain over the long term.

Convention Over Configuration

I eventually realized that the problem I was having could probably be solved with a convention rather than having to manually configure each product with tags that pointed to the related product. I would be sacrificing some flexibility (this flexibility being one of the main strengths to the alternative approach that I linked to at the beginning of this post), but my current needs are pretty simple. I have two basic scenarios that I need to support:

  • When a user is viewing a “frame combo” that features a temple style with a frame, I want to show a link to the “temple style only” product.
  • When a user is viewing a “temple style only” product I want to show a link to the “combo” offering for that same temple style.

I decided to use a naming convention for product handles that would let me easily take any given product handle and derive the handle of a related product. By default Shopify creates your product handles by just taking the product’s display name and replacing any white space or special characters with dashes. Product handles have to be unique on a store by store basis, so Shopify will automatically add a number to the end of any potential duplicates. I decided to link my products together by giving them the same handle but appending a suffix to the end of the handle to denote what type of product it was. For example, a suffix of “rc” would indicate that a product was a “reading glasses combo” product whereas a suffix of “ri” would indicate that it was a “reading glasses insert-only” product. So how do we deal with product handles that might happen to end in the letters “rc” or “ri”?

Since product handles are used to construct the unique URL of each product in your store, they can’t contain any crazy special characters. From what I can tell, they can only contain letters, numbers, dashes and underscores. Since Shopify already uses the dashes to make the handles more human readable I decided to use underscores to denote where a product type suffix begins in a product handle. This way I can easily tell that the product with handle “blue-butterfly_sc” represents a “sunglasses combo” for the blue butterfly style and that the corresponding “insert only” option should be named “blue-butterfly_si”. Note that I opted for simple two-letter suffixes, though these could easily be made longer and more descriptive if desired.

Show Me The Code

To make this a easier I packaged up some of the common bits of code into a little javascript library. It’s available in both minified and original flavors over at github. You’re free to download, use, and change this code however you like. This library takes care of determining what the related product handle should be, calling the AJAX API to get that product’s handle, dealing with the situation where that corresponding handle can’t be found, and finally showing the link to the corresponding product in some pre-determined area of your product details page. The code sample below shows how to use the library as an external javascript reference after you’ve added it as an asset to your store’s theme.

To make this work, you need to tell the library a few things including what product type suffixes you’re using, how they relate to each other, and where on the product details page you want to display the related product link. We’ll do all of this in our ‘product.liquid’ template. First, you’ll want to define some HTML element that will serve as a container for your related product link. I opted to use a div with the id ‘relatedProducts’:

<div id="relatedProducts">
</div>

I put this div just below my product price / product type display, but you can put it anywhere you like and style it any way that you want. I would suggest using an ‘id’ attribute to uniquely identify it on the page, as that will make it easier for the related products library to find it later. You’ll also want to expose the handle of the product being viewed somewhere on the page. I opted to do this in a hidden field by adding this little bit of code to my product.liquid template:

<input type="hidden" id="prodHandle" value="{{ product.handle }}"/>

Next, we just need to define some javascript that will configure the related products library and show the related products link once the product detail page has been completely loaded. The following code snippet comes directly from the very top of my own ‘product.liquid’ template:

{{ 'RelatedProducts.min.js' | asset_url | script_tag }}

<script type="text/javascript">

jQuery(document).ready(function () {
  
  relatedProductsLib.outputSelector = '#relatedProducts';
  relatedProductsLib.productLookupFunction = Shopify.getProduct;
  relatedProductsLib.suffixLookup = {
    'sc' : 'si',
    'rc' : 'ri',
    'si' : 'sc',
    'ri' : 'rc'
  };

  relatedProductsLib.messageLookup = {
    'si' : 'Already have a pair of PiX frames? <br/><a href="{0}">You can buy these temple inserts without frames.</a>',
    'ri' : 'Already have a pair of PiX frames? <br/><a href="{0}">You can buy these temple inserts without frames.</a>',
    'sc' : 'Need a pair of PiX frames to go with these inserts? <br/><a href="{0}">You can buy these inserts with frames as a combo!</a>',
    'rc' : 'Need a pair of PiX frames to go with these inserts? <br/><a href="{0}">You can buy these inserts with frames as a combo!</a>'
  }

  var fullHandle = jQuery('#prodHandle').val();
  relatedProductsLib.showRelatedProduct(fullHandle);
});
</script>

Let me talk through each piece of this:

First, I’ve used Liquid to output a <script> tag that will contain the minified version of the related products library that I linked to above. You’ll want to upload that .js file as an asset to your theme.

Next I’m configuring the related products library by setting a few properties that the library exposes:

  • outputSelector: This is the jQuery selector for the DOM element where the related products link should be output. I used a div with an id of ‘relatedProducts’, so my selector is simple: ‘#relatedProducts’.
  • productLookupFunction: This is the function that should be invoked to perform the actual product lookup. I exposed this as a property with the intention of possibly replacing it with a dummy function of some kind for use while developing/testing, but for your actual product.liquid template you’ll want to use the value shown there: ‘Shopify.getProduct’.
  • suffixLookup: This is a simple object containing key-value pairs defining the relationships between the product prefixes that you want to relate to one another. This could be cleaner, and I plan on revamping that a bit in a future update to this library. In the example above I’m basically saying ‘map all ‘sc’ products to ‘si’ products and all ‘rc’ products to ‘ri’ products and vice versa.
  • messageLookup: Again, I’m not completely happy with this as an API and there’s room for improvement. This property is another set of key-value pairs defining  the format of the link that you want to display for each product based on the suffix of the product being displayed. In the example above I’m saying ‘if the current product being viewed has a suffix of ‘si’ or ‘ri’, then the related product link should read ‘Already have a pair of PiX Frames?…’. Note that the ‘{0}’ token in that string is the place holder for where the related products URL will go.

After setting those four properties, I grab the product handle of the current product out of the hidden field I defined in my product.liquid template and pass that into the ‘showRelatedProducts’ method of the library. The library takes care of the rest. If a product is found with the expected product handle, the corresponding message and link will appear in the HTML element denoted by the ‘outputSelector’ property. If no product is found (or some error occurs during the product look up), nothing happens. This way you can safely add this code to your normal product.liquid template without fear of having javascript errors crop up in the event that some of your products don’t follow this naming convention, or you just haven’t gotten around to setting up all of your product handles with the correct suffixes yet.

You can see this approach in action on the PiX site today by checking out the Blue Butterfly Sunglasses Combo product. You’ll see a ‘You can buy these temple inserts without frames’ link appear in the product details area of the page that will link over to the “insert only” product.

Rough Around the Edges

I’m the first to admit that this approach is far from perfect, but it seems to suit my needs for the time being. I like this approach because it relies on some very simple naming conventions and requires very little configuration. I think it would pretty easy to do mass updates of product handles using the bulk inventory upload feature to add the needed product type suffixes to your product handles. I think that in some cases this would end up being a lot less work than trying to manage tags on each and every product to establish a relationship between that product and others in your store.

That said, I’m always open to making this better. A few ideas I have off the top of my head:

  • Add support for mapping a product type suffix to multiple other suffixes. For example, I’ll eventually want to be able to offer the same temple style as a “sunglasses combo”, “reading glasses combo” and “temple insert only”. The current code won’t support this scenario.
  • Add the ability to use jQuery templates to jazz up the presentation.
  • Explore ways to bake this into a pre-packaged Shopify theme.

In what other ways can I make this better?

Posted On Monday, October 11, 2010 9:34 PM | Comments (0)
Meta
Tag Cloud