Stylelint hyphenated BEM config

Stylelint.io is flavour of the Sass-linting month (replacing scss-lint as the discerning bandwagon chaser's Sass linter of choice). Scss-lint caters to the Harry Roberts approved variant, calling it 'hyphenated BEM'. There is a BEM pattern plugin for Stylelint, but out of the box, it doesn't support this flavour.

In order to validate your hyphenated BEM selectors with Stylelint, add the BEM plugin – npm install stylelint-selector-bem-pattern – and add these regexes to the Stylelint plugin .stylelintrc config:

{
  "plugins": [
    "stylelint-selector-bem-pattern"
  ],
  "rules": {
    "plugin/selector-bem-pattern": {
      "componentName": "(([a-z0-9]+(?!-$)-?)+)",
      "componentSelectors": {
        "initial": "\\.{componentName}(((__|--)(([a-z0-9\\[\\]'=]+(?!-$)-?)+))+)?$"
      },
      "ignoreSelectors": [
        ".*\\.no-js.*",
        ".*\\.js-.*",
        ".*\\.lt-ie.*"
      ]
    }
  }
}

Then add //* @define component-name-here; weak to the top of each Sass partial and liberally pepper the file with // stylelint-disable plugin/selector-bem-pattern before any lines on which you don’t care that you’re not being BEMmy enough (you can start caring again with // stylelint-enable plugin/selector-bem-pattern below). The weak flag ensures that while initial selector sequences (before combinators) must match the defined convention, sequences after combinators don’t. This helps to keep things relatively sane, so you don’t have to reach for stylelint-disable too often.

The ignoreSelectors array included above is just what seemed to work well for me on one project – your mileage may vary, depending on what kind of utility classes make their way into your components.

Conway’s Game of Life in JavaScript

Generation:

Rules (from Wikipedia)

  1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by over-population.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

JavaScript

function GameOfLife(grid) {
  var self = this;

  this.generation = 0;
  this.generationEl = $('#generation');
  this.boardState = [];
  this.shouldContinue = true;
  this.el = $('#grid');

  grid.forEach(function(row, yPos) {
    var tr = $('<tr>');

    self.boardState.push(row.map(function(item, xPos) {
      var td = $('<td>'),
          tdId = 'x' + xPos + 'y' + yPos;

      td.attr('id', tdId);
      tr.append(td);
      self.el.append(tr);

      return {
        x: xPos,
        y: yPos,
        item: new Cell(item, td)
      };
    }));
  });

  this.updateGeneration();
}

GameOfLife.prototype.getSurroundingCells = function(x, y) {
  var surroundingCells = [],
      boardState = this.boardState,
      possibleSurroundingPositions = [
        {xPos: x - 1, yPos: y - 1},
        {xPos: x - 1, yPos: y},
        {xPos: x - 1, yPos: y + 1},
        {xPos: x, yPos: y - 1},
        {xPos: x, yPos: y + 1},
        {xPos: x + 1, yPos: y - 1},
        {xPos: x + 1, yPos: y},
        {xPos: x + 1, yPos: y + 1}
      ];

  possibleSurroundingPositions.forEach(function(coords) {
    if (boardState[coords.yPos] && boardState[coords.yPos][coords.xPos]) {
      surroundingCells.push(boardState[coords.yPos][coords.xPos]);
    }
  });

  return surroundingCells;
};

GameOfLife.prototype.setCellNextStates = function() {
  if (!this.shouldContinue) return;

  var self = this,
      boardState = this.boardState;

  boardState.forEach(function(row, yPos) {
    row.forEach(function(item, xPos) {
      var surroundingCells = self.getSurroundingCells(xPos, yPos),
          aliveCount = 0;

      surroundingCells.forEach(function(cell) {
        aliveCount += cell.item.getIsAlive();
      });

      if (aliveCount < 2) {
        item.item.nextState = 0;
      } else if (aliveCount === 2) {
        item.item.nextState = item.item.nextState;
      } else if (aliveCount === 3) {
        item.item.nextState = 1;
      } else {
        item.item.nextState = 0;
      }

    });
  });

  this.updateGeneration();
};

GameOfLife.prototype.updateGeneration = function() {
  var self = this;

  this.shouldContinue = false;

  this.boardState.forEach(function(row, index) {
    row.forEach(function(item, index) {
      if (item.item.nextState !== item.item.getIsAlive() || !self.generation) {
        self.shouldContinue = true;
      }

      item.item.setIsAlive(item.item.nextState);
    });
  });

  this.generationEl.text(this.generation);
  this.generation++;

  setTimeout(function() {
    self.setCellNextStates();
  }, 100);
};


function Cell(initialState, el) {
  this.el = el;
  this.nextState = initialState;
  this.setIsAlive(initialState);
}

Cell.prototype.getIsAlive = function() {
  return this.isAlive;
};

Cell.prototype.setIsAlive = function(value) {
  this.isAlive = value;

  if (value) {
    this.el.addClass('alive');  
  } else {
    this.el.removeClass('alive');
  }
};

function buildGrid(size) {
  var grid = [];

  for (var i = 0; i < size; i++) {
    var row = [];

    for (var j = 0; j < size; j++) {
      var startCondition = Math.round(Math.random() * 0.6);

      row.push(startCondition);
    }

    grid.push(row);
  }

  return grid;
}

var grid = buildGrid(50),
    gameOfLife = new GameOfLife(grid);

CSS

.gol__caption {
  text-transform: uppercase;
  font-weight: normal;
  text-align: left;
  color: hotpink;
  padding-bottom: 0.2em;
  border-bottom: 3px solid hotpink;
}

.gol__table {
  margin: 0 auto;
  table-layout: fixed;
  width: 100%;
}

.gol__table td {
  padding: 0;
  background: #fff !important;
  border: 1px solid #ddd;
}

.gol__table td:before {
  content: '';
  display: block;
  padding-top: 100%;
}

.gol__table td.alive {
  background: hotpink !important;
}

A classical JavaScript inheritance helper

Setting up inheritance in JavaScript, you have to do a couple of not-particularly-intuitive steps with the prototype.

I’ma add this guy to my TextExpander snippets:

// Extend the Function object to simplify inheritance
Function.prototype.inheritsFrom = function(parentClass) {
  this.prototype = new parentClass();
  this.prototype.constructor = this;

  // Add a friendly name for the parent class
  this.prototype._super = parentClass.prototype;

  return this;
};

You have to remember to call this method directly after the constructor, before you do anything else with the extending object’s prototype.

Custom select boxes

I've often heard that it's a Very Bad Idea to try and emulate a browser's native select box using JavaScipt, so I thought I'd give it a go to see what the fuss is all about.

Here’s a far from exhaustive list of things I found to describe how browsers seem to handle their native select boxes:

  • Trigger open/close by pressing space on header
  • Trigger open by pressing cursor keys on header
  • Trigger open/close by clicking on header
  • Focus currently selected item on open
  • No dropdown focus if currently selected item is disabled
  • Navigate from top/bottom if currently selected item is disabled
  • Navigate from currently selected item using cursor keys
  • Select focused item on pressing return/space
  • Close (without updating) on pressing escape
  • Select hovered option (unless cursor key pressed since initial hover) on pressing return/space
  • Close (without updating) on pressing return/space when hovered over disabled option
  • Navigate using cursor keys from most recently hovered option
  • Skip over disabled options when navigating with cursor keys
  • A disabled select doesn’t respond to events
  • A disabled optgroup has all of its child options disabled

And here’s a JS Bin with my best shot. The styles are just for fun (i.e. don’t judge me) – it’s the functionality that I was interested in.

I feel like I’ve had a fairly good run at it – I think I’m relatively close to having implemented the keyboard and mouse driven idiosyncrasies.

It’s not even close to being acceptable for screenreader use, though. And I couldn’t even face attempting to handle the multiple attribute.

In any event, it’s been an interesting exercise – I’ll feel better able to defend against a custom select requirement in future.

Always equal flexbox columns

Assigning equal flex-grow values to flex-items will ensure those items are of equal width up to the point that their inner text content is larger than that width.

It’s pretty easy to get an equal-width column grid layout going using flexbox:

<div class="flex-container">
  <div class="flex-item"></div>
  <div class="flex-item"></div>
  <div class="flex-item"></div>
</div>
.flex-container {
  display: flex;
}

.flex-item {
  flex: 1;
  margin-right: 10px;

  &:last-child {
    margin-right: 0;
  }
}

However, the preferred width of a flex-item containing text seems to be the text without line breaks, so one long word can ruin your whole day.

To prevent this from happening, just add width: 0 to the flex-items. It’s then obviously up to you to handle how the text should flow, but at least the layout behaves consistently.

.flex-container {
  display: flex;
}

.flex-item {
  flex: 1;
  width: 0; // Force equal-width columns
  margin-right: 10px;

  &:last-child {
    margin-right: 0;
  }
}