Skip to content

Instantly share code, notes, and snippets.

@ASH-Bryan
Last active August 22, 2018 23:25
Show Gist options
  • Save ASH-Bryan/cbc2478e97b9b480260d66aeade3c1b6 to your computer and use it in GitHub Desktop.
Save ASH-Bryan/cbc2478e97b9b480260d66aeade3c1b6 to your computer and use it in GitHub Desktop.
css-oocss
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'h1',
classNames: ['title']
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['actionBtn']
});
import Ember from 'ember';
export default Ember.Component.extend({
});
import Ember from 'ember'
import { computed } from '@ember/object'
import { htmlSafe } from '@ember/string'
export default Ember.Component.extend({
classNames: ['actionBtn'],
largeFab: false,
baseSize: computed('largeFab', function() {
return this.largeFab ? 65 : 50
})
});
import Ember from 'ember'
import { computed, getProperties } from '@ember/object'
import { htmlSafe } from '@ember/string'
export default Ember.Component.extend({
tagName: 'section',
classNames: ['mediaCard'],
classNameBindings: ['skin'],
showDescription: false,
largeFab: false,
image: null,
skin: computed('image', 'fab', 'largeFab', 'dark',
'description', 'bodyTitle', 'links', function() {
const { image, fab, largeFab, dark, description, bodyTitle, links } =
getProperties(this, 'image', 'fab', 'largeFab', 'dark', 'description', 'bodyTitle', 'links')
//Have to do the most complex first
return (image && largeFab && bodyTitle)
? 'imageLinksBigfabBelowtitle'
: (image && fab)
? 'imageLinksFab'
: (image && links)
? 'imageLinks'
: (dark && links)
? 'darkLinks'
: (description)
? 'reveal'
: ''
}),
headerBackground: computed('image', function() {
return this.image
? htmlSafe(`background-image: url("${this.image}");`)
: null
}),
headerClass: computed('image', function() {
return this.image ? `h-48` : ''
}),
fabStyle: computed('largeFab', function() {
return this.largeFab
? htmlSafe('top: -3rem; right: 1rem;')
: htmlSafe('top: -2.5rem; right: 1rem;')
}),
actions: {
toggleDescription() {
this.toggleProperty('showDescription')
}
}
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['actionBtn']
})
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'button',
classNames: ['btn']
});
import Ember from 'ember';
export default Ember.Component.extend({
tagName: ''
}).reopenClass({
positionalParams: ['text']
});
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'CSS Playground'
});
import Ember from 'ember';
export default Ember.Controller.extend({
hasturDescription: `I found myself faced by names and terms that I had heard elsewhere in the most hideous of connections—Yuggoth, Great Cthulhu, Tsathoggua, Yog-Sothoth, R'lyeh, Nyarlathotep, Azathoth, Hastur, Yian, Leng, the Lake of Hali, Bethmoora, the Yellow Sign, L'mur-Kathulos, Bran and the Magnum Innominandum—and was drawn back through nameless aeons and inconceivable dimensions to worlds of elder, outer entity at which the crazed author of the Necronomicon had only guessed in the vaguest way.... There is a whole secret cult of evil men (a man of your mystical erudition will understand me when I link them with Hastur and the Yellow Sign) devoted to the purpose of tracking them down and injuring them on behalf of the monstrous powers from other dimensions.`,
yogLinks: [
{
name: 'Feed Me',
url: 'https://en.wikipedia.org/wiki/Yog-Sothoth'
},
{
name: 'Ask a Question',
url: 'https://en.wikipedia.org/wiki/Yog-Sothoth'
}
]
});
import EmberRouter from '@ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: 'none',
rootURL: config.rootURL
});
Router.map(function() {
this.route('faq')
});
export default Router;
/* Plumbing classes: These are not part of the fun */
body {
padding: 0.25rem;
margin: 0.25rem;
}
.navButton {
border: 1px solid black;
padding: .3rem;
text-decoration: none;
color: blue;
}
.navButton.active {
color: white;
background-color: blue;
}
/* OOCSS mediaCard */
.mediaCard {
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
border-radius: .25rem;
}
.hd {
display: flex;
padding: 0.75rem;
align-items: flex-end;
}
.bd {
position: relative;
padding: 0.75rem;
}
.links {
padding: 0.75rem;
border-top: 1px solid #dae1e7;
margin-top: 0.5rem;
padding-top: 0.5rem;
}
.link {
color: #f2d024;
text-decoration: none;
margin-right: 1.5rem;
}
.title {
display: flex;
justify-content: space-between;
margin: 0rem;
}
.actionBtn {
cursor: pointer;
}
.fabContainer {
position: absolute;
}
/* mediaCard skins */
.reveal .hd {
height: 12rem;
color: white;
}
.darkLinks.mediaCard {
background-color: #606f7b;
color: white;
}
.imageLinks .hd {
height: 12rem;
color: white;
}
.imageLinksFab .hd {
height: 12rem;
color: white;
}
.imageLinksBigfabBelowtitle .bd {
padding-top: 1rem;
}
.imageLinksBigfabBelowtitle .hd {
height: 12rem;
color: white;
}
/* OOCSS buttons */
.btn {
color: white;
background-color: #4dc0b5;
border-radius: .25rem;
display: flex;
}
.cont {
padding: 0.5rem;
height: 2rem;
display: flex;
align-items: center;
box-sizing: border-box;
}
.icon {
fill: currentColor;
margin: 0 0.5rem;
}
/* button skins */
.flip .cont {
flex-direction: row-reverse;
}
/* FAQ */
.faq {
margin-bottom: 1rem;
}
.question {
font-weight: bold;
}
<div style="padding: 1rem 0;">
Object-oriented CSS (OOCSS):
{{link-to 'Demo' 'index' class="navButton"}}
{{link-to 'FAQ' 'faq' class="navButton"}}
</div>
{{outlet}}
{{title}}
{{#if actionButton}}
{{component actionButton}}
{{/if}}
<svg
version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width="24px" height="24px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Bounding_Boxes">
<path fill="none" d="M0,0h24v24H0V0z"/>
</g>
<g id="Rounded">
<path d="M18.3,5.71L18.3,5.71c-0.39-0.39-1.02-0.39-1.41,0L12,10.59L7.11,5.7c-0.39-0.39-1.02-0.39-1.41,0l0,0
c-0.39,0.39-0.39,1.02,0,1.41L10.59,12L5.7,16.89c-0.39,0.39-0.39,1.02,0,1.41h0c0.39,0.39,1.02,0.39,1.41,0L12,13.41l4.89,4.89
c0.39,0.39,1.02,0.39,1.41,0l0,0c0.39-0.39,0.39-1.02,0-1.41L13.41,12l4.89-4.89C18.68,6.73,18.68,6.09,18.3,5.71z"/>
</g>
</svg>
<dl class="faq">
<dt class="question">{{question}}</dt>
<dd>{{yield}}</dd>
</dl>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width={{baseSize}}
height={{baseSize}}
viewBox="0 0 24 24"
xml:space="preserve">
<circle cx="12" cy="12" r="6" fill="white"/>
<path fill="red" d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M16,13h-3v3c0,0.55-0.45,1-1,1h0c-0.55,0-1-0.45-1-1v-3H8c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h3V8c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v3h3c0.55,0,1,0.45,1,1v0C17,12.55,16.55,13,16,13z"/>
</svg>
{{#if showDescription}}
<div class="bd">
{{card-title
title=title
actionButton='close-button'
click=(action 'toggleDescription')
}}
<p>
{{description}}
</p>
</div>
{{else}}
<div class="hd" style={{headerBackground}}>
{{#unless bodyTitle}}
{{card-title
title=title
}}
{{/unless}}
</div>
<p class="bd">
{{#if fab}}
<div class="fabContainer" style={{fabStyle}}>
{{floating-action-button largeFab=largeFab}}
</div>
{{/if}}
{{#if bodyTitle}}
{{card-title
title=title
actionButton=(if description 'more-button')
click=(action 'toggleDescription')
}}
{{/if}}
{{yield}}
</p>
{{#if links}}
<div class="links">
{{#each links as |link|}}
{{card-link name=link.name url=link.url}}
{{/each}}
</div>
{{/if}}
{{/if}}
<svg
version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width="24px" height="24px"
viewBox="0 0 24 24"
enable-background="new 0 0 24 24"
xml:space="preserve">
<g id="Bounding_Boxes">
<path fill="none" d="M0,0h24v24H0V0z"/>
</g>
<g id="Rounded">
<path d="M12,8c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S10.9,8,12,8z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,10,12,10z
M12,16c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,16,12,16z"/>
</g>
</svg>
<div class="cont {{if iconFlip 'flip'}}">
{{#if icon}}
<svg version="1.1"
class="icon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width="24px" height="24px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Bounding_Boxes">
<path fill="none" d="M0,0h24v24H0V0z"/>
</g>
<g id="Rounded">
<path d="M19.35,10.04C18.67,6.59,15.64,4,12,4C9.11,4,6.6,5.64,5.35,8.04C2.34,8.36,0,10.91,0,14c0,3.31,2.69,6,6,6h13c2.76,0,5-2.24,5-5C24,12.36,21.95,10.22,19.35,10.04z"/>
</g>
</svg>
{{/if}}
{{text}}
</div>
{{! Inline styles so we don't have to restyle this little helper }}
<code style="padding: 0 0.25rem; background-color: lightgray;">{{text}}</code>
{{#faq-item question='What is the impact to the client?'}}
1,488 bytes (about 100 more than BEM) are added to the CSS sent to the client to account for all of the OOCSS classes and the comments I felt were necessary for clarity.
{{/faq-item}}
{{#faq-item question='What is the impact to the developer?'}}
The cognitive load for this style is high.
{{/faq-item}}
{{#faq-item question="Okay, well don't we save bytes in the templates?"}}
In {{x-code 'media-card.hbs'}}, which is the largest template with the most styles, the OOCSS version is actually over 100 bytes smaller than Tailwind because of the very short classnames. The code for {{x-code 'media-card.js'}}, however, is longer and would require additional tests for the quite complex computed that is needed to determine the skin. Because of that, I'd rate this the worst of the 3 I did in terms of overall added complexity.
{{/faq-item}}
{{#faq-item question='How well did this work with the component structure?'}}
It didn't work as well with component structure as BEM. From looking the <a href="http://oocss.org/module.html">module example</a> of OOCSS, the trick with objects of different layouts and appearances seems to be having idential classes in the sub-structure with one skin class at the top that mutates everything. In my examples that means <strong>five</strong> skin classes (seven if I had done all of the ones in the template). Then when adding the component to a template, I either have to make the user pass in a layout name or try to infer it based on the options passed in (I went with the latter). Look at the {{x-code 'skin'}} computed for media-card. It's nasty. This is unnessary complexity and I felt dirty doing it. In fact I'm sure that not all combinations of allowed options will work property since I only created skins for my examples. Let me know if I'm doing this wrong, but it seems <strong>terrible</strong>.
{{/faq-item}}
{{#faq-item question='Is the skin idea really that bad?'}}
It's cool to be able to add new skins and easily have them target the structure of a component without having to mess with the markup. I'd say this is easily the strongest attribute of OOCSS. This would be great if we were writing static html where we slapped a class name on it and called it a day. For components that use properties (some optional) to determine their appearance, yes, it is very bad.
{{/faq-item}}
{{#faq-item question='What about side effects and reuse?'}}
OOCSS favors using tiny, bare classnames like {{x-code 'hd'}} for a header. Great, but that means the class has to be so generic that every header in every component in every site can use it. That means it can likely only contain one or two styles. Maybe that's good. But I think more likely it just means we put all of the interesting stuff in the skins and we're back where we started. I can only imagine the debates in code reviews as to whether a change should go in the skin or the base {{x-code 'hd'}} class. Oh, and then the fun when half of our sites break because of it. With namespaced components OOCSS is <em>no more or less reusable</em> than utility-first.
{{/faq-item}}
{{#faq-item question='Are you sure you implemented this correctly?'}}
You've got me, dude. I did the best I could after reading the docs a few times and looking at the examples in the inspector. It wasn't until the end of this effort that I felt like I was really starting to understand the methodology.
{{/faq-item}}
{{#faq-item question='What about razor templates?'}}
These classes can easily be reused anywhere you are willing to duplicate the HTML. Again, that's not DRY and I think components should be our direction.
{{/faq-item}}
{{#faq-item question='How do we deal with site customizations?'}}
We can use our current method of having each site provide such things in SASS variables.
{{/faq-item}}
{{#faq-item question='How do you really feel about OOCSS?'}}
It seems like maybe it could be good if you start out architecting your site(s) with this in mind and you're okay with letting CSS constrain how you write your code and reuse objects. For us, I think it would be a nightmare heckscape. If UX thinks we limit them now, imagine when we tell them every site has to agree on the common properties of a link...
{{/faq-item}}
<div style="padding: 0.5rem">
{{#media-card
title='Yog-Sothoth'
links=yogLinks
image='https://vignette.wikia.nocookie.net/lovecraft/images/f/fc/Yog_sothoth_rising_by_butttornado-d6ubvy6.jpg/revision/latest/scale-to-width-down/640?cb=20161222095032'
}}
Yog-Sothoth knows the gate. Yog-Sothoth is the gate. Yog-Sothoth is the key and guardian of the gate. Past, present, future, all are one in Yog-Sothoth. He knows where the Old Ones broke through of old, and where They shall break through again. He knows where They have trod earth's fields, and where They still tread them, and why no one can behold Them as They tread.
{{/media-card}}
<br/>
{{#media-card
title='Azathoth'
dark=true
links=yogLinks
}}
Outside the ordered universe [is] that amorphous blight of nethermost confusion which blasphemes and bubbles at the center of all infinity—the boundless daemon sultan Azathoth, whose name no lips dare speak aloud, and who gnaws hungrily in inconceivable, unlighted chambers beyond time and space amidst the muffled, maddening beating of vile drums and the thin monotonous whine of accursed flutes.
{{/media-card}}
<br/>
{{#media-card
title='Nyarlathotep'
fab=true
image='https://vignette.wikia.nocookie.net/lovecraft/images/0/00/Nyarlthotep.jpg/revision/latest/scale-to-width-down/391?cb=20150525013305'
}}
And through this revolting graveyard of the universe the muffled, maddening beating of drums, and thin, monotonous whine of blasphemous flutes from inconceivable, unlighted chambers beyond Time; the detestable pounding and piping whereunto dance slowly, awkwardly, and absurdly the gigantic, tenebrous ultimate gods — the blind, voiceless, mindless gargoyles whose soul is Nyarlathotep.
{{/media-card}}
<br/>
{{#media-card
title='Cthulhu'
fab=true
largeFab=true
bodyTitle=true
image='https://vignette.wikia.nocookie.net/lovecraft/images/9/9e/Cthulhu.png/revision/latest/scale-to-width-down/370?cb=20150909190114'
}}
A monster of vaguely anthropoid outline, but with an octopus-like head whose face was a mass of feelers, a scaly, rubbery-looking body, prodigious claws on hind and fore feet, and long, narrow wings behind.
{{/media-card}}
<br/>
{{#media-card
title='Hastur'
description=hasturDescription
bodyTitle=true
image='https://vignette.wikia.nocookie.net/lovecraft/images/a/af/Hastur.jpg/revision/latest/scale-to-width-down/390?cb=20150525022426'
}}
... after stumbling queerly upon the hellish and forbidden book of horrors the two learn, among other hideous things which no sane mortal should know, that this talisman is indeed the nameless Yellow Sign handed down from the accursed cult of Hastur—from primordial Carcosa, whereof the volume treats...
{{/media-card}}
<br/>
{{text-button text='Devour' icon=true}}
{{text-button text='Derange'}}
{{text-button text='Destroy' icon=true iconFlip=true}}
{{floating-action-button}}
</div>
{
"version": "0.15.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js",
"ember": "3.1.3",
"ember-template-compiler": "3.1.3",
"ember-testing": "3.1.3"
},
"addons": {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment