title: jQuery Datatables with Twitter Bootstrap Shows Differently on Heroku
body: <p>I have a Rails 3.1 app running on Heroku where I'm using Bootstrap v1.4 with jQuery (I need to stay with Bootstrap 1 - so I can't move to version 2) datatables.  While the datatables appear fine locally, when I move it the changes to Heroku, it doesn't work.  </p>
  
  <p>In particular, it does not show the text for how many rows there are and it doesn't put a line break/spacing after the pagination.  Also, the table sorts data correctly, but the pagination doesn't work (it doesn't show page numbers and Next/Previous links don't do anything).</p>
  
  <p>Locally, the bottom of a datatable looks <a href="https://docs.google.com/open?id=0B8VE4g-Xc6CITHRBNFBDc3Z2M2s" rel="nofollow">like this</a>.</p>
  
  <p>However, on Heroku it looks like <a href="https://docs.google.com/open?id=0B8VE4g-Xc6CIUVpjOUU1cHVfTFE" rel="nofollow">this</a>.</p>
  
  <p>I implemented the code on the <a href="http://www.datatables.net/blog/Twitter_Bootstrap" rel="nofollow">Datatables blog</a>.</p>
  
  <p>My layout file looks like this in the header:</p>
  
  <pre><code>&lt;head&gt;
    &lt;%= stylesheet_link_tag "application" %&gt;
    &lt;%= javascript_include_tag "application"%&gt;
  
    &lt;%= yield :javascript%&gt;
    &lt;%= yield :script%&gt;
  
    &lt;script type="text/javascript"&gt;
      jQuery.noConflict();
      /* Table initialisation */
      jQuery(document).ready(function() {
        jQuery('#datatable').dataTable( {
          sDom: "&lt;'row'&lt;'span5'l&gt;&lt;'span8'f&gt;r&gt;t&lt;'row'&lt;'span4'i&gt;&lt;'span8'p&gt;&gt;",
          "sPaginationType": "bootstrap",
          "oLanguage": {
            "sLengthMenu": "_MENU_ records per page"
          }
        } );
      } );
    &lt;/script&gt;
  
  &lt;/head&gt; 
  
  
  &lt;table class="bordered-table zebra-striped" id="datatable" &gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Date&lt;/th&gt;
        &lt;th&gt;id&lt;/th&gt;
        &lt;th&gt;User Name&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;%= user.date.strftime("%m/%d/%Y") %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= user.id %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= user.user_name %&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
  </code></pre>
  
  <p>Application CSS</p>
  
  <pre><code>/*
  *= require_self
  *= require bootstrap
  */
  
  div.dataTables_length label {
    width: 460px;
    float: left;
    text-align: left;
  }
  
  div.dataTables_length select {
    width: 75px;
  }
  
  div.dataTables_filter label {
    float: right;
    width: 460px;
  }
  
  div.dataTables_info {
    padding-top: 8px;
  }
  
  div.dataTables_paginate {
    float: right;
    margin: 0;
  }
  
  table {
    margin: 1em 0;
    clear: both;
  }
  
  table.dataTable th:active {
    outline: none;
  }
  </code></pre>
  
  <p>application.js</p>
  
  <pre><code>//= require jquery.min
  //= require jquery-ui.min
  //= require jquery_ujs
  //= require_self
  //= require jquery.dataTables.min
  //= require dataTables_bootstrap_plugin
  </code></pre>
  
  <p>dataTables_bootstrap_plugin.js (uses code from <a href="http://www.datatables.net/blog/Twitter_Bootstrap" rel="nofollow">Datatables blog</a>)</p>
  
  <pre><code>      /* Default class modification */
    jQuery.extend( jQuery.fn.dataTableExt.oStdClasses, {
      "sSortAsc": "header headerSortDown",
      "sSortDesc": "header headerSortUp",
      "sSortable": "header"
    } );
  
    /* API method to get paging information */
    jQuery.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
    {
      return {
        "iStart":         oSettings._iDisplayStart,
        "iEnd":           oSettings.fnDisplayEnd(),
        "iLength":        oSettings._iDisplayLength,
        "iTotal":         oSettings.fnRecordsTotal(),
        "iFilteredTotal": oSettings.fnRecordsDisplay(),
        "iPage":          Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
        "iTotalPages":    Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
      };
    }
  
    /* Bootstrap style pagination control */
    jQuery.extend( $.fn.dataTableExt.oPagination, {
      "bootstrap": {
        "fnInit": function( oSettings, nPaging, fnDraw ) {
          var oLang = oSettings.oLanguage.oPaginate;
          var fnClickHandler = function ( e ) {
            e.preventDefault();
            if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
              fnDraw( oSettings );
            }
          };
  
          jQuery(nPaging).addClass('pagination').append(
            '&lt;ul&gt;'+
              '&lt;li class="prev disabled"&gt;&lt;a href="#"&gt;&amp;larr; '+oLang.sPrevious+'&lt;/a&gt;&lt;/li&gt;'+
              '&lt;li class="next disabled"&gt;&lt;a href="#"&gt;'+oLang.sNext+' &amp;rarr; &lt;/a&gt;&lt;/li&gt;'+
            '&lt;/ul&gt;'
          );
          var els = jQuery('a', nPaging);
          jQuery(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
          jQuery(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
        },
  
        "fnUpdate": function ( oSettings, fnDraw ) {
          var iListLength = 5;
          var oPaging = oSettings.oInstance.fnPagingInfo();
          var an = oSettings.aanFeatures.p;
          var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);
  
          if ( oPaging.iTotalPages &lt; iListLength) {
            iStart = 1;
            iEnd = oPaging.iTotalPages;
          }
          else if ( oPaging.iPage &lt;= iHalf ) {
            iStart = 1;
            iEnd = iListLength;
          } else if ( oPaging.iPage &gt;= (oPaging.iTotalPages-iHalf) ) {
            iStart = oPaging.iTotalPages - iListLength + 1;
            iEnd = oPaging.iTotalPages;
          } else {
            iStart = oPaging.iPage - iHalf + 1;
            iEnd = iStart + iListLength - 1;
          }
  
          for ( i=0, iLen=an.length ; i&lt;iLen ; i++ ) {
            // Remove the middle elements
            jQuery('li:gt(0)', an[i]).filter(':not(:last)').remove();
  
            // Add the new list items and their event handlers
            for ( j=iStart ; j&lt;=iEnd ; j++ ) {
              sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
              jQuery('&lt;li '+sClass+'&gt;&lt;a href="#"&gt;'+j+'&lt;/a&gt;&lt;/li&gt;')
                .insertBefore( $('li:last', an[i])[0] )
                .bind('click', function (e) {
                  e.preventDefault();
                  oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
                  fnDraw( oSettings );
                } );
            }
  
            // Add / remove disabled classes from the static elements
            if ( oPaging.iPage === 0 ) {
              jQuery('li:first', an[i]).addClass('disabled');
            } else {
              jQuery('li:first', an[i]).removeClass('disabled');
            }
  
            if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
              jQuery('li:last', an[i]).addClass('disabled');
            } else {
              jQuery('li:last', an[i]).removeClass('disabled');
            }
          }
        }
      }
    } );
  </code></pre>