JQuery-UI css and images, and Rails Asset Pipeline

tl;dr version

For Rails 3.1 (3.1.3 is the latest release as I write this).

If you have JQuery-UI CSS generated by the theme roller, say for the ‘cupertino’ theme. One option is exploding the contents a bit, put the `jquery-ui-1.8.16.custom.css` file directly at ./app/assets/stylesheets/jquery-ui-1.8.16.custom.css, and put the corresponding jquery-ui theme ./images subdir directly at `./app/assets/stylesheets/images.`  (Or ./vendor/assets or ./lib/assets). Now just include the .css file in your application.css, ” *= require jquery-ui-1.8.16.custom.css “.

If instead you really do want to leave the JQuery-UI theme source in it’s own subdir by itself, say at `./app/assets/stylesheets/jquery-ui/cupertino` (or ./vendor/assets or ./lib/assets), then you’ve got to add this to your config/application.rb, matching the file path you’ve chosen to put it at:

initializer :after_append_asset_paths, 
            :group => :all, 
            :after => :append_assets_path do
   config.assets.paths.unshift Rails.root.join("app", "assets", "stylesheets", "jquery-ui", "cupertino").to_s
end

Your JQuery-UI theme images will now work, both in standard development config, standard production, as well as other combinations of asset piepline configuration.  Works well as far as I can tell in Rails 3.1.3, probably will continue working in the future. (can put in ./vendor/assets or ./lib/assets  instead of ./app/assets, same thing).

For more info and a couple other options, read on.

The problem

The jquery-rails gem, which is added to a new app’s Gemfile by default in rails 3.1, comes with JS for JQuery-UI.  You can include JQuery-UI JS just by adding this to your application.js:

//= require jquery-ui

Okay, but what about the JQuery-UI theme, which is CSS and some images referenced by that CSS?  The jquery-rails README kind of unhelpfully suggests:

In order to use the themed parts of jQuery UI, you will also need to supply your own theme CSS. See jqueryui.com for more information.

Okay, but if you do this the most straightforward way, you’ll find out it doesn’t work. I download a theme, say for the theme ‘cupertino’, and I get a zipfile `jquery-ui-1.8.16.custom.zip`.  Inside there, is a ./css directory.

Okay, I take this ./css directory and actually rename it into my app, say at: `./app/assets/stylesheets/jquery-ui`.  (Can put it in ./vendor/assets too, or even ./lib/assets, all will have the same outcome).

So now inside my local `./app/assets/stylesheets/jquery-ui`, I’ve got `./cupertino`, `./cupertino/jquery-ui-1.8.16.custom.css`, and `./cupertino/images`

So I go into my ./app/assets/application.css, and add:

*= require jquery-ui/cupertino/jquery-ui-1.8.16.custom.css

Start up my app, with some JQuery-UI code activated, and everything works fine in standard ‘development’ mode configuration. But in ‘production’ mode, with pre-compiled assets, none of the JQuery-UI theme images show up. (Which in some cases degrades usably but not as aesthetic, in other cases is a usability problem like lack of a ‘close’ button on a dialog).

What happened? Well, when compiled under sprockets, the contents of jquery-ui-1.8.16.custom.css is actually combined into the application.css file, which lives at the URL (for an app mounted at host root URL): `/assets/application.css`.   But that JQuery-UI CSS contains relative URLs to image assets, of the form `url(images/something.png)`

And relative to `/assets/application.css`, that means `/assets/images/something.png`.  But sprockets actually compiled the images to file path `./public/assets/jquery-ui/cupertino/images/something.png`, where it’ll be at url `/assets/jquery-ui/cupertino/images/something.png`  Not the right place for the CSS.

If I look around on the web for solutions to this, you get a lot of conflicting and confusing answers, perhaps because they were written for different versions of the asset pipeline (inc. some pre-release versions).  I’m not even going to link you to the various reddits, stackoverflows, etc, because it’s just too confusing and depressing. The most popular one seems to involve making symbolic links in your source. Come on really? Symbolic links, that tend to go wrong if you don’t do things perfectly when zipping up your source or copying it to another place or on Windows? I figured there’s got to be a better way.

In fact, I’m going to give you not just one alternate/better way, but four (count them, one, two, three, four) alternate/better ways.  Well, that’s kind of a lie, I’ll give you two better way that actually work with the asset pipeline, and two more that don’t use the pipeline for jquery-ui theme, but you can still use in your app that otherwise uses the pipeline.  For typical cases, any of em could work, but weird cases (say, wanting to have more than one theme available at runtime) each has trade-offs.

One way that works with the pipeline, take apart the subdir structure

Just take the files out of the jquery-ui generated directory stucture, and  put `jquery-ui-1.8.16.custom.css` directly  at ./app/assets/stylesheets/jquery-ui-1.8.16.custom.css, and put the corresponding jquery-ui theme ./images subdir _directly_ at `./app/assets/stylesheets/images.`

(Or put em in ./vendor/assets or ./lib/assets, will work identically in any of those. Maybe vendor is best, I dunno).

Now in your application.css, just

 *= require jquery-ui-1.8.16.custom.css

Now the relative paths in the CSS will resolve correctly both with and without pre-compiled assets, as well as even with `config.assets.debug = false` in dev.

Great, that’s pretty simple. I have to admit I didn’t think of this until I had spent quite a bit of time figuring out the next way, which will work with whatever subdirectory structure you want. Hey, maybe you really want to keep the subdir structure to keep your jquery-ui theme stuff nicely segregated. Or maybe you just want to learn a bit more about how Rails and Sprockets work. Then read, on.

Second way that works with the pipeline, whatever subdirs you want

So maybe you really still want to keep your jquery-ui theme all nicely grouped together in a subdir,  `./(app|vendor|lib)/assets/stylesheets/jquery-ui/themename`.  Just cause it’s tidier, or becuase you need multiple themes installed in your source (although that latter gets trickier).

First I thought, okay, well, maybe we can just add `./app/vendor/assets/stylesheets/themename`  to config.assets.paths in application.rb, now we’re telling sprockets to treat that as a ‘base’ dir, and compile the stuff (images in this case) in there directly to ./public/assets without the parent dirs, keeping the relative paths working.

Turns out I was on the right track, but that doesn’t work. Because  it turns out the order of paths matters to the Sprockets device that compiles assets. And Rails insists on adding ./(app|lib|vendor)/assets to the paths before anything you add yourself in config.assets.paths .  And Sprockets will then compile your stuff at that path, and when it sees a different subsequent path that also matches those same files, Sprockets is smart enough to know it already compiled that stuff from the previous path, and not compile it again.  So your addition to config.assets.paths of a subdir of one of the paths listed earlier essentially does nothing.

Turns out the place Rails insists on adding it’s own default paths first is in an append_assets_path initializer.    (Yes, despite the name “append_”, it actually prepends em with Array#unshift. Maybe it used to append but was changed to prepend to fix some other desired behavior?). Okay, great, the Rails 3.x initializer framework let’s us hook in right after Rails does this, to put what we want first in config.assets.paths instead.

Say we have our jquery-ui theme at `./vendor/assets/stylesheets/jquery-ui/cupertino`.  Add that path to the beginning of config.assets.paths, even before the stuff Rails prepended, by putting this in your config/application.rb:

initializer :after_append_asset_paths, 
            :group => :all, 
            :after => :append_assets_path do
   config.assets.paths.unshift Rails.root.join("vendor", "assets", "stylesheets", "jquery-ui", "cupertino").to_s
end

This works, to get the theme css’s relative URLs to images working,  in default (as installed by Rails) development mode asset config. It works in default production mode asset config, with precompiled assets.  It even works in development mode but with `config.assets.debug = false`. Each of these setups winds up with different ways of serving assets and potentially different path relationships between assets, but this works in every combo I tried. (Some of them probably work because config.assets.paths, depending on assets config, is sometimes used to create routing to assets too).

Will this keep working in the future? I dunno, I’m not technically using any internal API or anything, but I doing something not documented or popular, and counting on internal implementation not changing that much, the :append_assets_path initiailizer remaining there and named the same and used in the same way, etc. Guess it depends on how much Rails changes, like usual.

How the heck did I figure that out

I was not previously familiar with the inner workings of Sprockets or the Rails 3.1 asset pipeline. Instead, figuring that out took me about 5 hours of iteratively doing a bunch of this stuff:

  • Looking at source code for both Rails and Sprockets in like 6 open tabs of github source, trying to understand how they are interacting where.
  • Using github search function (wish it worked better), or grep’ing through checked out source to try and figure out where a particular method or module or class is defined. (Rails, why do you namespace classes in Rails source with ::Sprockets , fooling me into thinking I’d find em in Sprockets source? Couldn’t you have namespaced em Rails::Sprockets instead?)
  • Using ruby-debug to investigate live source, and put breakpoints at Rails or Sprockets source lines to investigate em there. One reason to investigate is to figure out what the heck class a given variable is (Rails.application.assets anyone?). Another trick I like to do is drop into debug, call #freeze on an object, continue, and then wait for the stacktrace to figure out exactly who/where tried to modify that object next. (It is a travesty that ruby 1.9.3 does not work with ruby-debug, a travesty I say)
  • Looking at the Rails config and asset pipeline guides to try and understand at least the big picture design.
  • Googling for hints from other people, or sometimes just hints as to what source file a particular method was actually defined in when I was having trouble finding it.
  • Wash, rinse, repeat, do these steps in a different order, go down blind alleys and realize that and backtrack in my investigation, etc.

Rails 3.x is really complicated code. Composed of multiple independent decoupled modules which interact with each other in complicated ways, occasionally even monkey-patching each other. (And Sprockets, would it kill you to add some one-two liner rdoc-style comments above your methods and classes, even non-public ones, telling the reader what they do?)

On the other hand, 4-6 hours ain’t that much time for getting to the bottom of fairly complicated functionality. And the solution I arrived at is fairly tidy, Rails 3.x is designed/factored pretty nicely these days to support surgical interventions (thanks Rails for just modifying config.assets.path once in an initiailizer instead of hard-coding it into the places Rails.application.assets is init’d and routes are init’d), and the 3.x initializer chain system is pretty sweet. (But Rails can’t decide whether to use strings or symbols for initializer names, it uses both, but you need to match the form used originally for the initializer :after or :before stuff. Grr.)

Third way, why host the theme yourself at all?

If you are taking a stock JQuery-UI theme and not customizing it at all, did you know that the Google ajax/open source tools CDN hosts all the stock JQuery-UI themes? (Although it’s not documented well, they’re there).  Why host it yourself at all, just link to the Google CDN. Sure it’ll be one more browser request since you haven’t combined the jquery-ui CSS into your application.css with the pipeline, but just one more request, to the Google CDN which is presumably relatively reliable and high performance, to save you the trouble of dealing with this crap or hosting it yourself at all?

Put in your layout:

<%= stylesheet_link_tag "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/cupertino/jquery-ui.css" %>

Replace ‘cupertino’ with whatever theme you want. Some legacy versions of themes also available, replace “1.8.16” with “1.7.2” for instance.

Or, do an import in your application.css instead:

@import url(http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/cupertino/jquery-ui.css)

It’ll still be an extra browser request, but it seems pleasant to keep all your CSS stuff, internal and external,  in your application.css, rather than splitting some of it out to the layout and then getting confused later.  CSS @import seems to be supported in all non-ancient browsers including IE6.

Fourth way, host it but skip the asset pipeline

Just cause you’re using the Rails 3.1 asset pipeline doesn’t mean you have to use it for all your assets. The Rails asset pipeline guide even says:

This is not to say that assets can (or should) no longer be placed in public; they still can be and will be served as static files by the application or web server. You would only use app/assets if you wish your files to undergo some pre-processing before they are served.

While we don’t really want any pre-processing of the source itself, if we just put the assets into public we do give up a couple things. We’d give up combining the CSS itself into assets.css to save a browser request (no big deal to save a headache in this special case, sez me. I ain’t running netflix here.) And we’d give up the cache-busting fingerprinting (again, seems tolerable to me — you aren’t going to be changing your jquery-ui theme very often, when you do, just make the changed theme a new theme in a new directory location in ./public, seems okay to me for most cases).

Now, the rails asset pipeline guide says you can do this, but it ends up being a pain, because when you have the asset pipeline turned on, you don’t have any helper methods anymore to generate URLs to assets in ./public, all the helper methods assume they’ll want an asset-pipeline-controlled asset. (Unless there’s a helper method I can’t find somewhere? Come on Rails, if you’re going to tell us it’s an option, can’t you support us doing it?)

You could I suppose just write hard-coded literal URLs — but that breaks if you are deploying an app at a sub-dir of your webserver, as Rails supports, and as I like to be able to do sometimes.

Okay, so create your own helper method, here you go, stick it in a helper somewhere:

# Much like usual rails route helpers, 
# whether you realized it or not, you can pass 
# option :only_path => false to get a
# full URL instead of a 
# relative path. 
 def public_static_path(path, options = nil)
   root_path(options) + path
 end

Now stick the jquery-UI theme at ./public/jquery-ui/cupertino, and in your layout:

<%= stylesheet_link_tag public_static_path("jquery-ui/cupertino/jquery-ui-1.8.16") %>

It’s not in the asset pipeline, but no problem.

What if you want to stick it in application.css with an “@import” instead? Make it into application.css.erb so we can use ERB to call out to a helper to generate the URL… Oops, again we run into the problem of having no way to generate the URL to the asset in public.  A helper method you add to your usual app helpers isn’t available in an erb asset. Yeah, there is a way to monkey-patch helpers into the asset context too, but I went down that path and it started getting more complicated there were a couple other tricks, so I just gave up. Oh well.

The end

The end. Any improvements, feedback, alternate methods, welcome. I wasn’t actually finding any of these suggestions on google myself (maybe i’m just a bad googler), so hopefully having em here now will make it easier for others trying to figure out what’s going on. Symbolic links, geez, you don’t have to do that.

28 thoughts on “JQuery-UI css and images, and Rails Asset Pipeline

  1. @Carl: Huh, so that plugin will cause sprockets to look inside all the .css files, and actually modify the url() strings during pre- processing? Neat. Hadn’t seen that before. I’ll see if I can find time to play around with it; I’m wondering how it manages to predict at compile time _what_ the proper absolute URL should be, such that it will still work in various contexts/environments/configurations.

  2. With jquery-rails gem
    application.js -> *= require jquery-ui
    application.css -> *= require jquery-ui

    vendor/assets/stylesheets/jquery-ui.css
    vendor/assets/images/put-jquery-images.pngs-here

    Open jquery-ui.css and to a ‘find and replace’ and replace ‘images/’ with ”

    Boom

    I also really love `jammit` but with the asset pipeline and rails 3.1 I don’t use it

  3. Darren: You have to be willing to scatter all jquery-ui images in the root directory of your assets/images directory, and you have to do the find-and-replace again if you change themes or update to a new version. And I’m not certain if it’ll work if you aren’t deploying at host URI root (like if you’re using Passenger SubUri). But if that’s all good, then, sure.

  4. Thanks very much for this write-up. It forced me to finally understand the Asset Pipeline.

    Our main goal was to do as little harm to the jQuery files as possible and keep them out of the way.

    One other option we considered was to make a copy of a given jQuery file, make a SASS file by renaming to *.css.scss — then search and replace in the SASS file using the image-url helpers. This solution has the benefit of leaving vendor files untouched, but in the end, it seemed inelegant and more hacky than the other solutions.

    So we went with option 2 (update config/application.rb to add the path). All of our jQuery CSS is in one place and all CSS background images are, too — at least they are not all in our main images directory. Because our manifest file uses “require-tree” we only needed to list the one subdirectory in assets where we dump our jQuery stuff. In the end, it was one line of code. Not bad.

  5. Tom Harrison wrote:
    > One other option we considered was to make a copy of a given jQuery file, make a SASS file by renaming to *.css.scss — then search and replace in the SASS file using the image-url helpers.

    That search-and-replace pretty much what the Rakefile in jquery-ui-rails does: http://github.com/joliss/jquery-ui-rails The thusly-modified asset files are then distributed in the jquery-ui-rails gem.

    I released it just now — see http://www.solitr.com/blog/2012/02/jquery-ui-rails-gem-for-the-asset-pipeline/ — but so far it seems to be working well.

  6. Awesome, thanks Jo, looks nice.

    I recommend you explicitly mention what version of JQuery-ui is packaged by your gem, in the blog announcement and in the README. Do it now, in case later a new version of JQuery-ui is released and someone finds your gem or announcement and you haven’t updated it yet, and their wondering or could get confused about what’s what.

    Also, even if you bundled all the ‘standard’ themes, there would still be a question of “yes”, but what if I make my own custom theme with the themeroller?”, true?

    You say you have a rake task that will automatically make any jquery-ui theme rails-asset-pipeline friendly? Is it feasible to provide instructions for running that rake task on your own custom theme, and deploying that? Does this make sense?

    (All the various hacky solutions in this blog post will work with ANY jquery-ui theme, standard or custom rolled).

  7. Good point. I just updated the README. (Though there’s a History file too, just in case.)

    > Also, even if you bundled all the ‘standard’ themes, there would still be a question of “yes”, but what if I make my own custom theme with the themeroller?”, true?

    Yes, that’s right.

    > You say you have a rake task that will automatically make any jquery-ui theme rails-asset-pipeline friendly? Is it feasible to provide instructions for running that rake task on your own custom theme, and deploying that?

    It probably wouldn’t be hard to implement this. The Rakefile could pick its CSS and images from user-specified paths (that is, the “development-bundle/themes/whatever” directory in the custom tarball). I probably won’t get around to coding this up any time soon, but if you want to have a stab at adding a set of rake tasks for this, I’ll be happy to review a pull request.

  8. I’m running Rails 3.2.1 and managed to get everything running by doing to following.

    1. I downloaded a custom version of jquery ui and copied the .js file to /app/assets/javascripts/vendor/jquery-ui-1.8.18.custom.min.js
    2. I copied the stylesheet into /app/assets/stylesheets/vendor/jquery-ui-1.8.18.custom.css
    3. I copied the images into /app/assets/vendor/images/…

    So as long as all the asset paths mirror each other (in my case everything is under a ‘vendor’ folder) it just works without messing around with application configuration.

    Possibly there have been some changes since this article was written which makes this a bit easier now.

    Scott Harvey

  9. Thank you very much. I had issue where after having customized jquery-ui and running assets:precompile the production page complained that is is not compiled though it was.

    Doing the first version helped me fix this.

  10. Isn\’t it simply possible to replace the image links in the jQuery UI CSS by an url with a different path, for example url(images/ui-icons_256x240.png) by url(/assets/jquery-ui/ui-icons_256x240.png); } ?

  11. @jofr: Yes, that is possible. However, generally we do not want to have to modify the JQuery-UI source (and do it again manually for a new version of JQuery-UI or a different theme). Also, config settings or deployment options in Rails can change the actual URL of assets, including sometimes leading to different URLs in development vs production ; we want to be able to change these config settings or deployment options without having to re-modify the URLs in JQuery-UI source by hand each time. But if these things aren’t a problem for you and editing the JQuery-UI source by hand to be new absolute paths works for you, you certainly can do it.

  12. Thanks! Your article really helped me out. Things like this help me understand why it’s so important to continue delving into the Rails code.

  13. Great post, really helpful.

    By now we are at rails 3.2.8, and maybe things have changed since the post was originally written, but it seems to me that much of the confusion is introduced by mucking around inside assets/javascripts and assets/stylesheets dirs, where sprockets et al have some opinions about what should happen.

    Here’s what ultimately worked cleanly for me:

    1) unpack the zip file into an subdir of an assets dir, something like app/assets/jquery-ui-1.8.23.custom
    2) in application.rb add:
    ‘config.assets.paths << Rails.root.join('app', 'assets', 'jquery-ui-1.8.23.custom').to_s
    3) add manifest files in the usual places:
    a. app/assets/javascripts/jquery-ui.js
    //= require_tree ../jquery-ui-1.8.23.custom
    b. app/assets/stylesheets/jquery-ui.css
    *= require_tree ../jquery-ui.1.8.23.custom
    4) in config/environments/production.rb, add (referring to manifest filenames):
    config.assets.precompile += %w(jquery-ui.js jquery-ui.css)
    5) in views:

  14. @Ken, that actually sounds like a pretty good solution I think, but I’m having trouble following it and the end of step 5 has been cut off. Would you want to put it in a gist at gist.github.com and leave a link to it here?

  15. @Ken Ah, I see. In your solution, the jquery-ui CSS ends up a separate file, not pre-compiled into your overall application.css per the asset pipeline, but a seperate CSS file of it’s own. In fact, while you are using the asset pipeline to refer to it and place it in the assets directory, no actual pre-compilation of the jquery CSS file happens, it’s just copied over as is, right? You’re basically using it as a static asset.

    I think that makes it basically equivalent to the “fourth way” in my original post: “Just don’t use the asset pipeline.” Although some confusing aspects of using static assets in a rails app that otherwise uses the asset pipeline may make your alternative somewhat less confusing — make it a free standing static asset, but still use the asset pipeline to deliver it, as a seperate free standign asset.

  16. @jrochkind, in fact, after rake assets:precompile, in public/assets I see .css, .css.gz, -.css, and -.css.gz; and similar files for the .js resources.

    I’m not quite sure what rails intends to do with all of those, but can confirm through firebug that the -.* compiled versions are what’s delivered to the client.

    The reason that I’m not compiling these into my overall application.js and application.css files is that they are assets that are only required on a limited subset of pages, so I want to keep them separate and include them only where necessary.

  17. yeah, good point, the asset pipeline will add fingerprinting and gzipping even with your relatively static asset, good point there.

  18. I finally figured this out using the sprockets-urlrewriter gem!

    I included the gem in my Gemfile and added a initializers/sprockets.rb with:
    Rails.application.assets.register_preprocessor ‘text/css’, Sprockets::UrlRewriter

    Here’s where I was getting hung up:
    I’m using Sass, so my main css file was application.scss. First I tried:
    @import “jquery-ui-bootstrap/jquery-ui-1.8.16.custom.css”

    As Sass will use css’s native @import method if the file is a css file and not scss, this resulted in an @import call in my css, which I didn’t want. Furthermore, it was pointing to a file that wasn’t precompiled, so nothing worked.

    Then I tried @include “jquery-ui-bootstrap/jquery-ui-1.8.16.custom” and renamed the file to .scss instead of css. This compiled everything into 1 css file as I wanted, but now the url rewriting didn’t work. I also wasn’t thrilled about renaming the file, as one of the points of putting it in its own folder in vendor/ was to isolate it so I could overwrite the whole folder with a new one if I wanted.

    The magic combination that worked:
    At the top of my application.scss file, I use the sprockets manifest method of importing my theme:
    /**
    * = require jquery-ui-bootstrap/jquery-ui-1.8.16.custom.css
    */
    Then I go about my business as usual, @import-ing w/ Sass/Compass after that. The gem rewrites the required css urls to properly reference the images, and all the css is compiled to one application.css!

  19. very useful stuff. however, I realized that once you do this, all image paths get digested for your plugins (if digest is enabled, which it should be on production). to avoid this, u have to call precompile again with the non-digest option:

    bundle exec rake assets:precompile:nondigest

Leave a comment