RequireJS and Django
by Justin Michalicek on May 5, 2014, 1:49 a.m. UTCI just finished converting one of my projects over to using RequireJS. I've been moving towards more Javascript heavy stuff lately and keeping all of the dependencies in order has begun to get very messy. RequireJS seemed to be the standard way of managing this, so I decided to give it a try on one of my side projects (which will hopefully go live soon-ish).
RequireJS basically gives you about as close as you're going to get to includes in other languages implemented in Javascript. Overall it works very well, with just a couple of small, easy to work around hurdles. The first is that it requires that modules be written in a fairly specific way and of course not everything works well to do that way, unfortunately. This causes trouble with the recommended way of handling ajax csrf with Django and globally setting jQuery's ajax list POST style to traditional, since it defaults to some silly thing to help PHP and Ruby people out which just gets in the way in Python frameworks. There's also a configuration for Require which needs to be loaded at each page load and the most common way of doing it found in examples would require a lot of boilerplate.
What I ended up doing is first, in my base template which all other templates extend, I did this
<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.11/require.min.js"></script>
Normally you are shown to have a data-main attribute on there which references a js file to start running right away. If you do that you can't have your global config all in one spot, though. So after that line I put in my global config like below, which allowed me to solve the other two problems.
<script type="text/javascript"> require.config({ baseUrl: "/static/js/", paths: { beer: 'beer', accounts: 'accounts', sighting: 'sighting', watchlist: 'watchlist', venue: 'venue', vendor: 'vendor', knockout: '//ajax.aspnetcdn.com/ajax/knockout/knockout-3.0.0', jquery: '//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min', bootstrap: 'vendor/bootstrap', underscore: '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min', }, shim: { infinitescroll: ['knockout'], bootstrap: ['jquery'], csrf: ['jquery'] }, deps: ['jquery', 'bootstrap', 'knockout', 'csrf'], callback: function () { jQuery.ajaxSettings.traditional = true; $.ajaxSetup({ crossDomain: false, // obviates need for sameOrigin test beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type)) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); }, }); </script>
You can see in the shim section I have csrf: ['jquery'] which tells it to load jquery before csrf (which it defaults to looking for in baseUrl) even though csrf.js is not set up for Require and AMD. I may not be 100% accurate on that description as I'm still fumbling about a bit with RequireJS configuration and behavior, but that is how I understood it from reading their docs. Then you can see in the deps section I specify some libs to load and the there's a callback section, which gets called after all of the dependencies in the deps loads. That is where I globally set jQuery to use traditional style parameters and pop in the jQuery.ajaxSetup() bit from the previously linked Django csrf Javascript which uses the rest of the script.
One thing to be aware of with the deps and callback is that my understanding after reading the docs is that those may not be guaranteed to have loaded by the time other scripts being loaded using RequireJS have already loaded, so it's possible those settings would not yet be set. I haven't run into any trouble with that in any testing I've done since making the change, though.