Tuesday 28 August 2012

Adding a prefix to Bootstrap CSS classes

Update 2013-03-15: Thanks to everyone for the bug reports and support. I apologise for the long silence - I had been busy with a lot of other things and didn't get round to updating the script. Should be fine from now on again, though. :-)

I recently started integrating Twitter's excellent Bootstrap framework into a hobby project of mine, but hit a snag: Bootstrap uses a lot of fairly common CSS classnames (e.g. "row", "container", etc)... and many of these clashed with classes that I had already defined in my own code, resulting in a fine mess.

The obvious solution was to modify the Bootstrap CSS so that everything in there uses a common prefix... however, the creator of Bootstrap made it clear that no prefixing of Bootstrap CSS classes would be implemented. Fair enough, it's their library, their call - and I suppose writing "row" instead of something like "tb-row" makes more sense if you just start out. But it does make implementing Bootstrap into an existing project harder than it needs to be, and I really did not want to rename my own CSS classes.

An alternative: Namespacing


One way to avoid the CSS name conflicts would be to wrap the Bootstrap CSS classes into a new top-level namespace using the Bootstrap Less source code and then wrapping your Bootstrap-sensitve page elements into a suitable container (as suggested in this stackoverflow answer). Not really what I was aiming for - having an option to merely define a "$prefix" variable in Bootstrap source and recompiling would've been far nicer. But, alas, things are not that simple: the CSS classes are referenced heavily in the Bootstrap Javascript source files, negating the usefulness of such a "prefix" Less variable.

Solution: Prefixing via post-processing script


To address this issue I whipped up a quick Python script that edits the Bootstrap CSS and Javascript files and adds a prefix to any CSS classnames it finds. The prefix defaults to "tb-" (for "Twitter Bootstrap"), but can be easily changed (see the docs below).

Using it is simple: just supply it with the path to your Bootstrap installation (it expects the default Bootstrap directory structure to be in place). The script will then output modified versions of the CSS and Javascript files it finds, with a ".prefixed" tag added to the filename.

Finally, include these "prefixed" files in your project instead of the original Bootstrap CSS and Javascript files - now you can (and must) refer to all Bootstrap CSS classes by adding the "tb-" prefix to the name, e.g. "row" becomes "tb-row", etc.

The "bootstrap_namespace_prefixer.py" script is available from GitHub, or you can download it by clicking here.

Please note: this script works for me. YMMV, but let me know if something goes wrong and I'll take a look at it.

Documentation


The script expects a Bootstrap directory structure like the following:
/bootstrap - top-level bootstrap dir; this is the path that must be supplied to the script
/bootstrap/css - directory containing CSS files for Boostrap
/bootstrap/js - directory containing Bootstrap  Javascript files

Call the script as follows:
$ python bootstrap_namespace_prefixer.py /path/to/boostrap/dir
The script will output the following files:
/bootstrap/css/bootstrap.prefixed.css - CSS with the added prefix
/bootstrap/css/bootstrap.min.prefixed.css - Minified CSS with the added prefix
/bootstrap/js/bootstrap.prefixed.js - Javascript using the prefixed classes
/bootstrap/js/bootstrap.min.prefixed.js - Minified Javascript using the prefixed classes

To change the classname prefix to something other than "tb-", edit the bootstrap_namespace_prefixer.py file and set the CSS_CLASS_PREFIX constant to whatever you want to use as a prefix.

33 comments:

  1. Hello,
    the Script is great! But it has a bug
    In the file bootstrap.min.prefixed.css it will add the prefix to the both png files:

    background-image:url("../img/glyphicons-halflings.sf_png");

    background-image:url("../img/glyphicons-halflings-white.sf_png")}

    The prefix was: sf_

    thanks

    ReplyDelete
    Replies
    1. Glad you find the script useful, and many thanks for pointing out the bug! I've pushed a fix to the github repository.

      Delete
  2. Great tool, thanks a lot!

    I think I found two possible bugs:

    1) Prefix is not added to CSS styles defined that uses attribute selectors like
    .tb-btn-large [class^="icon-"]
    this should by replaced by
    .tb-btn-large [class^="tb-icon-"]
    2) Wrongly added prefix for filter-styles; for example:
    filter: progid:DXImageTransform.Microsoft.gradient
    is replaced by
    filter: progid:DXImageTransform.tb-Microsoft.tb-gradient

    ReplyDelete
    Replies
    1. Thanks for reporting these! I've included a fix for them in the latest version on github.

      Delete
  3. Thanks for this post, really need to prefix the css (I searched another way to prefix with a kind of namespace as :

    @namespace tbootstrap

    //All css of bootstrap

    @end

    and use it like : <_balise_ identifier_type='@bootstrap-name'> (for example)

    I wrote crap, it doesn't exist as I wish :D

    ReplyDelete
  4. Hi,

    I think I found another bug/problem. Your script does not prefix CSS-classes where addClass or removeClass calls in the JavaScript files are generated dynamically which breaks the functionality of the "Responsive navbar".

    To fix the problem, the following (additional) replacements needs to be done by the prefix-script:
    - this.$element[method]('in') => this.$element[method]('tb-in')
    - $this[$(target).hasClass('tb-in') ? 'addClass' : 'removeClass']('collapsed') => $this[$(target).hasClass('tb-in') ? 'addClass' : 'removeClass']('tb-collapsed')

    Best regards,
    Jan

    ReplyDelete
    Replies
    1. Thanks again for the report!

      The script actually does prefix CSS classes when JQuery's addClass and removeClass methods are dynamically generated, but the case you mention is different: the "collapsed" CSS class is never defined in any of the CSS source files (the script only recognizes CSS classnames that it has already seen in the actual CSS files when dealing with JavaScript in order to avoid potential false positives).
      I had noticed that "collapsed" reference before, but decided against modifying it for some obscure reason. :-/

      Anyhow, I've now added a mechanism to force specific CSS classes to be recognized in JavaScript, and "collapsed" will now be detected and prefixed as expected.

      As for the first "this.$element[method]('in')" case: thanks, completely missed that one, fixed now.

      Delete
  5. Hi Francois,

    thanks for the update. The new version of the script fixes the bugs.

    I found two more problems (replacements that are missed by your script):

    1) CSS-File(s)
    .tb-nav-list [class*=" icon-"] => .tb-nav-list [class*=" tb-icon-"]

    2) JS-File (only in the minified version)
    this.$element[b]("in") => this.$element[b]("tb-in")

    Best regards,
    Jan

    ReplyDelete
    Replies
    1. Thanks again. I have fixed the script and pushed an update.

      Delete
  6. Hi,

    This is a great script, I just can't get it to work, I am sure its me because I have nothing to do with Python what so ever but I would like to learn so I can use your script.

    I have downloaded and install python on windows and have tried to run it through command prompt but it errors at line 34 saying cssfilename is invalid syntax. From the command prompt I have entered

    C:\>py c:\prefixer.py c:\bootstrap\

    Can you please let me know what I am doing wrong, and how I could do it better.

    Thanks

    Lee

    ReplyDelete
    Replies
    1. Sounds like you are using Python 3 - this script was written for Python 2.7. I'll update the script syntax to conform to Python 3, but in the meantime you can either install Python 2.7, or use the "2to3" tool to update the script yourself; see http://stackoverflow.com/questions/10104805/python-2to3-windows-cmd for more info.

      Delete
    2. Hi, I've updated the script - it now works fine in Python 3, and I've also made some modifications to ensure it works properly in Windows.

      Delete
  7. Hi Francois, first of all: Thank you very much for this script. It saved me a lot of time :)

    I use your script on windows, where I discovered, that .table-striped is not processed.

    ReplyDelete
    Replies
    1. Thanks! I've updated the script to address this - the bug was introduced when fixes for the IE-specific filter-styles were added.

      Delete
  8. Hi Francois,
    thank you for this script. it saved me a lot of time!

    is there a way of "somehow" prefixing css which goes directly to html elements, like button, select, article ...

    best Simon

    ReplyDelete
    Replies
    1. If you're using less, you could use nesting. I.e.:

      .mybootstrap {
      @import "reset.less";
      @import "variables.less";
      @import "...
      }

      br, m~

      Delete
  9. Hi Francois,
    i found a small bug, the script does not recognise following line

    Line 928 unprefixed
    animate = this.$element.hasClass('fade') ? 'fade' : ''
    Line 928 prefixed
    animate = this.$element.hasClass('tb-fade') ? 'fade' : ''

    ReplyDelete
    Replies
    1. Thanks Simon, I've fixed this in the github repo.

      Delete
  10. Hi,

    I'm not familiar with Python but possibility to use this script is very interesting so I'm trying to give it a go... Unfortunately I'm getting this error:

    Python 3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:06:53) [MSC v.1600 64 bit (AM
    D64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> $python bootstrap_namespace_prefixer.py c:\bootstrap
    File "", line 1
    $python bootstrap_namespace_prefixer.py c:\bootstrap
    ^
    SyntaxError: invalid syntax
    >>>

    Thannks for any help, need it so bad.

    ReplyDelete
    Replies
    1. You seem to be starting the Python interpreter and then typing in "$python bootstrap_namespace_prefixer.py c:\bootstrap" into its interactive prompt - this is not going to work. The "$" is there just to indicate a command line prompt (lots of Linux shells use this as a prompt).

      You need to launch the script from the standard command line; in Windows you can do this by running "cmd.exe" to get to the command line, then cd'ing to the directory containing the script, and typing "python bootstrap_namespace_prefixer.py c:\bootstrap". Note: this assumes "python.exe" is on your system's path - please see the Python Windows docs for more info: http://docs.python.org/2/faq/windows

      Delete
    2. Thank you Francois! It works now. Perfect script saved my time.

      Delete
  11. Hey Francois - great job and thanks for sharing this! I have a slightly different need, wondering if you could modify your script for such use case? Basically, unlike you, I really need to just namespace everything via wrapper class, so that I can specifically mark which portions of the page to use the bootstrap.css on, and which to leave out (internal corporate site, can't touch the legacy stuff, but ok to use on new projects, the problem is - they are intertwined). I've done as much with LESS without problem - wrapped everything in .bootstrapped and it separated the areas of implementation fine. The problem with JS is still valid though - this totally breaks things like modals for example. Would that be a lot of trouble to accommodate that scenario?
    In any case - thanks for what's already done!

    Vlad

    ReplyDelete
  12. Actually resolved this myself, and it had nothing to do with bootstrap.js file. I am using this in conjunction with AngularJS and also UI-bootstrap, which is a collection of directives for AngularJS. So, in fact, I had to modify the ui-bootstrap.js and make sure that it attaches the created modal to my namespaced element, and not the 'body' element as it does by default (because then it's not really a modal since the namespaced bootstrap.css doesn't apply there)
    So, nevermind my request, I clearly wasn't looking at it right :)

    ReplyDelete
  13. I am very happy to be able to find this excellent article! After reading your article, I feel learned a lot of things

    small business outsourcing

    ReplyDelete
  14. Hi Francois,

    I think I found a minor bug in your script: In the prefixed bootstrap.min.prefixed.js file

    return this.$element.removeClass("tb-collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this}

    needs to be

    return this.$element.removeClass("tb-collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("tb-collapse"),this}

    (column 19809 in my bootstrap.min.prefixed.js file)

    Thanks again for the great tool :)
    Jan

    ReplyDelete
  15. Does this work for 3.1.1?

    Processing CSS file: css/bootstrap-responsive.css
    Failed to open file; skipping: css/bootstrap-responsive.css
    Processing CSS file: css/bootstrap-responsive.min.css
    Failed to open file; skipping: css/bootstrap-responsive.min.css

    These were removed in Bootstrap v3.

    ReplyDelete
  16. Hello I m new in this field so i need your help for using this file i want to change my bootstrap css but i dont understand where i place this code in css can you kindly decsribe me steps how to use this script for css ids or class names to change

    ReplyDelete
  17. Hi guys I am not a coder so don't know how to use this. I come from a design stand point knowing some css and html, visual. Is there a finished files somewhere that can be downloaded? thanks

    ReplyDelete
  18. Hi, is this script compatible with bootstrap v3?

    ReplyDelete
  19. Join many telephone gadgets with one of these adaptors to some individual socket. Modular attaches might be linked to one particular modular socket utilizing these kind of modular increase adaptors. Normal cabling construction is usually 6P6C straight-thru. For details : http://www.dueltek.com.au/collections/adsl-telephone-accessories

    ReplyDelete
  20. In my view, if all web owners and bloggers made just right content as you probably did, the internet will probably be a lot more useful than ever before.

    ReplyDelete
  21. Thanks for the script. Can you please update this according to the materializecss. Same script works but there need some changes to make it working properly.

    ReplyDelete