Skip to content

Instantly share code, notes, and snippets.

@sebworks
Last active August 1, 2016 19:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebworks/1f5238091b2c818ee4f63f7b0d9b297e to your computer and use it in GitHub Desktop.
Save sebworks/1f5238091b2c818ee4f63f7b0d9b297e to your computer and use it in GitHub Desktop.

Notes on Atomic Design

I highly suggest reading http://www.ebaytechblog.com/2014/10/02/dont-build-pages-build-modules/. It encompasses exactly what we are trying to achieve by building components using atomic design. I also want to stress that our front-end atomic architecture is still evolving.

Our components are broken down into templates, organisms, molecules, and atoms. We opted not to use the page component, although it exists in atomic design. Our components are composed of HTML, CSS, and Javascript. If a component doesn’t have user interactions or require styling, then it won’t have an associated js and/or css file. We compose our atomic components as follows:

Atoms

Prefixed with “a-” in CSS, Javascript, and HTML files.

HTML:

<div class="a-overlay u-hidden"></div>

CSS:

 .a-overlay {
        // Only show overlay at mobile/tablet size.
        .respond-to-max( @bp-sm-max, {
            height: 100%;
            width: 100%;

Molecues

Prefixed with “m-” in CSS, Javascript, and HTML files.

HTML:

<div class="m-notification m-notification__error m-notification__visible" data-js-hook="state_atomic_init">
      <span class="m-notification_icon cf-icon"></span>
      <div class="m-notification_content" role="alert"></div>
 </div>

CSS:

.m-notification {
    display: none;
    position: relative;
    padding: @m-notification-padding__px;
    padding-left: 40px;

Javascript:

function Notification( element ) { 
   // eslint-disable-line max-statements, inline-comments, max-len
   var BASE_CLASS = 'm-notification';
    
   // Constants for the state of this Notification.
   var SUCCESS = 'success';
   var WARNING = 'warning';
   var ERROR = 'error'; 
   // Constants for the Notification modifiers.
   var MODIFIER_VISIBLE = BASE_CLASS + '__visible'; 
   var _dom = atomicHelpers.checkDom( element, BASE_CLASS );
   var _contentDom = _dom.querySelector( '.' + BASE_CLASS + '_content' );

The notification molecule can be instantiated with the following code:

_notification = new Notification( _dom );
_notification.init();

###Organisms Prefixed with “o-” in CSS, Javascript, and HTML.

HTML:

<div data-qa-hook="expandable" class="o-expandable 
									  o-expandable__borders 
									  o-expandable__midtone 
									  o-expandable__expanded" 
							   data-js-hook="state_atomic_init">
	<button class="o-expandable_target" aria-pressed="true">
		<div class="o-expandable_header">

JS:

 function Expandable( element ) { 
  var BASE_CLASS = 'o-expandable';

  // Bitwise flags for the state of this Expandable.
  var COLLAPSED = 0;
  var COLLAPSING = 1;
  var EXPANDING = 2;
  var EXPANDED = 3;

  // The Expandable element will directly be the Expandable
  // when used in an ExpandableGroup, otherwise it can be the parent container.
  var _dom = atomicHelpers.checkDom( element, BASE_CLASS );
  var _target = _dom.querySelector( '.' + BASE_CLASS + '_target' );
  var _content = _dom.querySelector( '.' + BASE_CLASS + '_content' );

The Expandable organism can be instantiated with the following code:

_expandable = new Expandable( _dom.querySelector( '.o-expandable' ) );
_expandable.init( _expandable.EXPANDED );

or

var atomicHelpers = require( '../../modules/util/atomic-helpers' );
var Expandable = require( '../../organisms/Expandable' );
atomicHelpers.instantiateAll( '.o-expandable', Expandable );

There was considerable discussion about how we should instantiate components. We were trying to solve the problem of instantiating components when standalone components and contained components are on the same page. More background on this can be found in the following:

cfpb/consumerfinance.gov#2055

http://jsfiddle.net/0j9u66h0/8/

Templates

Prefixed with “t-” in CSS, Javascript, and HTML.

CSS:

.t-careers {
    &_social .m-social-media {
        float: right;
    }

    // TODO: Consolidate site-wide media_image responsive sizes into one class.
    &_students-and-graduates .media_image {
        width: 130px;
        .respond-to-min( @bp-med-min, {
            width: 150px;
        } );

##Folder Structure

Atomic code is currently separated and named based on asset type. This is a mistake in my view, as I believe we should begin migrating to a modular folder structure based on the component.

Current Structure

HTML:

cfgov-refresh/cfgov/jinja2/v1/_includes/atoms/
cfgov-refresh/cfgov/jinja2/v1/_includes/molecules/
cfgov-refresh/cfgov/jinja2/v1/_includes/organisms/

CSS:

cfgov-refresh/cfgov/unprocessed/css/atoms/
cfgov-refresh/cfgov/unprocessed/css/molecules/
cfgov-refresh/cfgov/unprocessed/css/organisms/

Javascript:

cfgov-refresh/cfgov/unprocessed/js/atoms/
cfgov-refresh/cfgov/unprocessed/js/molecules/
cfgov-refresh/cfgov/unprocessed/js/organisms/

Test:

cfgov-refresh/test/unit_tests/atoms/
cfgov-refresh/test/unit_tests/molecules/
cfgov-refresh/test/unit_tests/organisms/

Proposed Folder Structure

cfgov-refresh/cfgov/front-end/molecules/Expandable

Expandable.html
Expandable.css
Expandable.js
Expandable-unit-test.js
README.MD

JS Architecture

There was considerable discussion on how we should create JS components. The components aren't constructed to be used on SPAs (Single Page Applications). They are built to be rendered on the sever and then enhanced via Javascript on the client. The basic interface for the components is as follows:

function AtomicComponent( domElement ) {
	// Ensure the passed in Element is in the DOM. 
	// Query and store references to sub-elements.
	// Instantiate child atomic components.
	// Bind necessary events for referenced DOM elements.
	// Perform other initialization related tasks.
    this.init = function init(){}
    
    // General teardown function
    // We don't remove the element from the DOM so
    // we need to unbind the events.
    this.destroy = function destroy(){}
}

We aren't testing for interface adherence but we probably should. We generally favor composition over inheritance (I personally don't) . You can get more information by reading the following:

Articles:

https://medium.com/javascript-scene/a-simple-challenge-to-classical-inheritance-fans-e78c2cf5eead#.mtrvhcjiw

https://www.youtube.com/watch?v=wfMtDGfHWpA

Code and Related PRs:

cfpb/consumerfinance.gov#916

http://jsfiddle.net/0j9u66h0/9/

https://jsfiddle.net/cpsyLy3L/2/

Component Build Pipeline

Gulp

Gulp is used as a task automation tool. A specific breakdown of each task is contained in https://github.com/cfpb/cfgov-refresh#available-gulp-tasks.

Webpack

Wepback is used as a module bundler although it's capable of more. We create page, global, and atomic specific bundles. The configuration for the bundles is contained in webpack-config.js. An explanation for the usage of each bundle is contained in scripts.js.

Routes

Routes are used to serve Javascript bundles to the browser based on the requested url or Wagtail page Media property. The happens via code contained in base.html.

Base.html

This file serves as the base document for serving up assets and content. It's currently very complicated, obtrusive, and needs to be refactored.

Wagtail Page Media Property

Each Atomic component has a media property which list the Javascript files that should be rendered via base.html. When a page is requested via the browser, code contained in base.html will loop all Atomic components for the requested page and render the appropriate Atomic Javascript bundles.

Questions and Concerns

  • How do we support Single Page Applications and be functional when Javascript is disabled?

  • How do we ensure creation of performant atomic components?

  • Is the codebase lacking uniformity?

  • CSS bloat when multiple components are on the same page but from different Django apps.

  • Ensuring simplicity over complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment