Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save gumayunov/176808 to your computer and use it in GitHub Desktop.
Save gumayunov/176808 to your computer and use it in GitHub Desktop.
Organizational Principles of CSS and JS in Rails
=== Framework requirements
1. Modularity
2. Complex components are built from simple, atomic components
3. Cross-browser compatibility
a. Follow W3C standards
b. Keep IE hacks in a separate style file
4. Bulletproof
a. Components are independent of their surroundings
b. They work even with disabled Javascript (if they use Javascript)
c. They are accessible even with disabled images
5. Expansibility
a. The same rules apply at all levels of the hierarchy
=== Approach
Key idea: *independent blocks*
We split the page into non-overlapping, non-interfering modules. They can be nested. These are the independent blocks.
Every block has a name.
Conditions of independence
1. Minimum of global styles
2. Don't use 'id', only 'class'
3. Name of block starts with prefix (.b-mega-block instead of .mega-block)
4. There are no classes outside of blocks
Prefixes: b - for block, l - for layout fragments, m - mixins.
=== Types of classes
1. Blocks - b
Blocks are simple or complex (complex = composed of simple) independent modules.
Generally, 80% of all markup of the page are blocks.
Astro-alerts, Tours, Polls, etc are blocks.
2. Layouts - l
Layouts - Divs/tables that are used only for creating structure of the page.
E.g., when we need a 3-column layout, we will have .l-left, .l-center, .l-right.
Generally, blocks are placed in these layouts.
3. Mixins - m
Mixins are like modules (mixins) you include in classes in Ruby.
E.g., .m-clear is the clearfix for the floats, .m-label-on-input moves labels over input,
.m-calendar adds calendar to some block, .m-carousel makes carousel from ul/li list.
=== Structure of directories.
The following directory structure is used within each Rails application.
== Blocks
Every block is stored in a separate directory. Different parts of blocks (CSS, JS, HTML) are stored in different parts of the app. E.g.,
blocks can be placed in any of these directories:
/stylesheets/framework/block/something/ - stylesheets
/javascripts/framework/block/something/ - javascript logic for the block
/images/framework/block/something/ - images for the block
/app/views/framework/block/something/ - for the HTML markup of the block
Of course, if a block doesn't have javascript logic, it will not have /javascripts/block/something directory.
1. Stylesheets:
The stylesheets directory can contain:
b-something.css - main file, for all browsers.
b-something.ie.css - styles for all IE browsers.
b-something.reset.css - reset styles for all browsers.
b-something.css is always included.
IE styles are included using conditional comments.
Reset styles file is included if the site doesn't have a global "reset styles" file.
So, a stylesheet directory might look like this:
stylesheets/
framework/
block/
something/
b-something.css - main CSS file of the block
b-something.ie.css - styles for IE
b-something.reset.css - reset file for the block
Sometimes you want to make use of the same block, but with a slightly different look on different pages.
We do this by adding an additional class.
E.g., we have b-something. We need modifications: super. We add
additional classes .b-something_super. Then, we put main styles in .b-something,
and add additional styles to .b-something_super.
For additional classes (modifications):
block/
something/
super/
b-something_super.css
b-something_super.ie.css
b-something.css
b-something.ie.css
...newFolder
All necessary styles are included in application.css and application.ie.css files by @import. E.g.:
@import 'framework/block/round/b-round.css'
@import 'framework/block/mega-block/b-mega-block.css'
...
There is a small ruby script, that generates one big file from these imports, css_js_generator.rb. It generates style.css and style.ie.css.
You should always use classes to refer to DOM-elements, and these classes should use a prefix which is the name of the block. E.g., classes might look like the following:
.b-something { }
.b-something_title { }
.b-something_contents { }
You *may* use tag names and classes without a prefix only if you are *absolutely sure* that this block will never contain other blocks.
Notes:
a. Using of '-' and '_'
You should use '-' as space and '_' as separator (from name of block, or from subclass). Examples:
.b-something_header - class of header of b-something,
.b-something-cool_header - class of header of b-something-cool
.b-something_cool-header - class of cool header of b-something
.b-something_super_header - class of header of modification super of block b-something
b. Abstract blocks
Sometimes we have very similar blocks with some common styles.
In such cases, we can add an abstract block, that is not used without modifiers.
Common styles are described in the abstract block; other styles in the modified
blocks (analogous idea to OOP - inheritance from abstract class).
In general, this is a good idea - apply OOP principles to the creation of xHTML/CSS.
2. Javascript
If block has JS logic, it should have a javascript file there: /javascripts/framework/block/something/b-something.js
JS logic implementation should follow the jQuery approach. Example of a template:
jQuery(function($) {
$('.b-something').b_something();
});
(function($) {
var classes = {
some_class: 'b-something_some-class'
// Other classes
};
$.fn.b_something = function(options) {
var opts = $.extend({}, $.fn.b_something.defaults, options);
return this.each(function() {
var self = $(this);
// Code of the JS logic
});
};
$.fn.b_something.defaults = {
// Defaults
};
})(jQuery);
It is stricly recommended to use variables with class names (like in example above) instead of hard-coded classes in jQuery selectors and overall JavaScript code.
JS should try to work with DOM using only classes of the current block.
I.e., you should try to avoid including $('.b-another').hide(); or similar references.
But sometimes you need to refer to other blocks or mixins. In this case, you
should use the public methods of these blocks, i.e. $('.b-another').b_another_hide();
(Unfortunately, you can't easily create namespaces in jQuery, like $('.b-antoher').b_another.hide(),
so it is suggested to use the name of the block as a prefix to simulate namespaces: b_another_hide()).
Sometimes, you will need to add some styles only if JavaScript is enabled in the user's browser. For this, you can use this trick:
a. Add #nojs id to the <body>
b. Add some styles only for enabled javascript:
#js .b-something { display: block }
c. Add JS code that changes body id to #js: document.body.id = "js";
For development, you should use the jquery.include.js plugin, and include JS logic of blocks in application.js:
jQuery.include('/javascripts/framework/block/tour/b-tour.js');
jQuery.include('/javascripts/framework/block/quick-signup/b-quick-signup.js');
For production, you should use css_js_generator.rb. It will generate app.js with all JS code in one file - app.js.
3. Images
If a block uses images, they should be stored here: /images/framework/block/something. E.g.:
/images/framework/block/something/bg.png
4. HTML (Views)
HTML markup for a block should be stored in /app/views/framework/block/something. Main file should be _b-something.html.haml. It can include
other files from this directory, e.g., _image.html.haml, using 'render :partial'. If a block has other blocks, they should be included by
render :partial. Ideally, action views (index.html.haml, edit.html.haml, etc.) should contain only Layouts and 'render :partial's. E.g.:
app/views/session/index.html.haml:
.l-wrapper
render :partial => '/framework/block/login-form/b_login_form'
render :partial => '/framework/block/something/b_something'
app/views/framework/block/login-form/_b_login_form.html.haml
.b-login-form
- form_for :user do |f|
# Code of the form
Unfortunately, you can't use hyphens ('-') in names of partials. Use underscores instead.
Ideally, HTML of blocks should not directly contain the HTML of other blocks, only render :partials. It should also not contain any Layouts.
But blocks can have Mixins.
== Layouts
Rules are pretty much the same as for Blocks. But they are placed in different directories:
/stylesheets/framework/layout/something/ - stylesheets
/javascripts/framework/layout/something/ - javascript logic for the layout
/images/framework/layout/something/ - images for the layout
/app/views/framework/layout/something/ - for the HTML markup of the layout
But generally, layouts don't need JS logic, separate HTML markup, and they need images very rarely. If your layout needs many of these things,
maybe it is already a block? :)
It is ok to use layout markup in action views or global layout views (e.g., app/views/layout/application.html.haml)
== Mixins
The rules are pretty much the same as for Blocks. But they are placed in different directories:
/stylesheets/framework/mixin/something/ - stylesheets
/javascripts/framework/mixin/something/ - javascript logic for the mixin
/images/framework/mixin/something/ - images for the layout
/app/views/framework/mixin/something/ - for the HTML markup of the mixin - rarely needed
Mixins should not be used as separate blocks. They should be included in another blocks to give them additional functionality.
If a piece of functionality is repeated in several different places on a site, it is a good idea to move this functionality to a separate
mixin and add this mixin to blocks. E.g. - m-calendar - adds JS calendar to any block that contains date/time selects (hides date/time selects,
adds textfield input, adds calendar, adds events for calendar, hide calendar, etc).
Generally, you don't need separate HTML markup for mixins. It is simply used in other blocks.
It is recommended to add names of mixins *after* name of block. E.g.:
<div class="b-block m-popup m-carousel">
<!-- Something here -->
</div>
Or for HAML:
.b-block.m-popup.m-carousel
=# Something here...
=== Global styles
1. /stylesheets/framework/global/global.css
Contains all global styles. Number of these styles should be small. Generally, only html, body.
2. /stylesheets/framework/global/global.reset.css
Contains global reset styles, like * { margin:0;padding:0 } and others.
3. /stylesheets/framework/global/global.ie.css
Contains all global styles for IE.
=== Overall structure of the app with just one block: b-something
app/
views/
framework/
block/
something/
_b_something.html.haml
public/
stylesheets/
framework/
block/
something/
b-something.css
b-something.ie.css
application.css
application.ie.css
style.css
style.ie.css
javascripts/
framework/
block/
something/
b-something.js
application.js
app.js
images/
framework/
block/
something/
bg.png
== Thanks
This document was heavily inspired by a presentation by Vitaly Harisov and by the articles on his site.
(Warning - Russian language!)
http://vitaly.harisov.name/article/independent-blocks.html - About independent blocks
http://company.yandex.ru/experience/css-framework/theory.html - Presentation (text)
http://narod.ru/disk/5655929000/V_Harisov_CSS_Framework_svoimi_rukami_teoriya.avi.html - Presentation (video)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment