We just wrote a very simple library (which I hope to open-source by the end of the year) and we write our components like this:
import bem from 'our-app/lib/bem';
// so we can easily grab it in tests
export default block = 'some-component';
export default Component.extend({
classNameBindings: ['tagClass'],
tagClass: bem({ block }),
aSubElementClass: bem({ block, element: 'look-an-el' }),
withModifiers: bem({ block, modifiers: ['neat', 'weird'] }),
withElAndModifiers: bem({ block, element: 'child-thing', modifiers: ['cool'] }),
somethingToWatch: true,
computingModifiers: computed('somethingToWatch', function() {
const modifiers = this.get('somethingToWatch') ? ['turn-it-on'], : [];
return bem({ block, element: 'whoa', modifiers });
}),
});Then we just use those classes on the component. (The component element itself gets the tagClass via the classNameBindings attribute.)
So at render, that ends up looking like this if somethingToWatch is false:
And then if somethingToWatch is true, it looks like this:
We played for a while with having a BemComponent we extended from, so that the classNameBindings stuff got set up automatically, but ended up just reverting to doing it explicitly. We did write a blueprint that generates much of it automatically for us though, so if we just do ember generate bem-component example-component we get this - the attributeBindings are there for use with ember-test-selectors
import Component from '@ember/component';
import { bem } from 'mobile-web/lib/utilities/bem';
export const block = 'example-component';
export default Component.extend({
// `attributeBindings` and `classNameBindings` must be here until we have TS
// support for the new decorator spec, as they need to be merged with other
// items of the same name in the prototype chain.
attributeBindings: [`elementId:data-test-${block}`],
// You should remove `classNameBindings` and `tagClass` if you're going to
// apply the BEM root to an element in the template explicitly.
classNameBindings: ['tagClass'],
tagClass: bem({ block }),
})In practice, that's worked extremely well for us. As you can see from the invocation here, the bem function we use just expects an options argument with at least the block - it'll error otherwise - and either or both of the elements and modifiers options.
Fairly soon, I'll also be adding SCSS template to the blueprint – we're in the midst of some changes around how we build our CSS, after which I'll automatically generate an _example-component.scss with the root class automatically generated and add it to our root styles.scss as well.
(A couple qualifications on this: we actually are generating it slightly differently, as an ES6 class, and we're doing it in TypeScript, but this is the gist.)