Info¶
Classes are undoubtedly the most powerful and dynamic concept in ctr
. In comparative terms, all other features in ctr
are more or less sugary abstractions over CSS to that of classes. But before you jump off this cliff with me it’s essential to learn why you would want to jump in the first place.
The idea of “classes” is the same, or similar, to that of extend depending on your definition. For many years it’s been an elusive dream for legions in the CSS community. In fact, way back in 2013 before I even knew what CSS was, Philip Walton wrote an article titled “The Future of OOCSS: A Proposal” in which he posed two fundamental questions. What if CSS had Object Oriented functionality built in? Further, what if there was a way to create a rich hierarchy of inherited CSS components that didn’t require listing each class in the HTML every single time? Only recently did I have the pleasure to learn about Philip’s work and I’m happy to report I have an answer to these two questions.
The Problem¶
It’s age old wisdom that a multi-class CSS pattern in HTML is a superior approach to maintainable scalability compared to that of a single-class approach. The principal reason can be demonstrated through a good old fashion button dilemma. It’s a common dilemma that we all face: there are three different types of buttons we need to create for our money-making website all of which share common properties but none of which are the same.
For the sake of simplicity, we have a .hook
, .line
, and .sinker
button, and they all share the following CSS dimension properties.
.button {
width: 125px;
height: 75px;
}
Now comes the dilemma; each button will have a different background. One solution is to create a separate class for each button like so.
.hook-button {
width: 125px;
height: 75px;
background: green;
}
However, since we are astute developers practiced in the dark arts of CSS wizardry we recognize this solution is repetitive, and it violates the DRY (don’t repeat yourself) CSS principal. As such, we decide to leverage class inheritance to inherit the .button
class and then create a separate background
class for each style.
<body>
<!-- hook content -->
<div class="button hook"></div>
<!-- line content -->
<div class="button line"></div>
<!-- sinker content -->
<div class="button sinker"></div>
</body>
Things are looking great, but then we realize the design is a bit too edgy for our tastes do we decide to add a border-radius
property to .line
and .sinker
. Here’s where things get tricky because one solution is to add border-radius
directly to .line
and .sinker
. Another valid and multi-class solution is to create a separate border-radius
class and apply it to .line
and .sinker
. Nonetheless, once the border-radius
dilemma is figured out, we then decide the color
of the text is not fitting, and then, and then, and then. This is the crux of the button dilemma, it’s the inevitability of the next “and then(s)” compounded with the desire to not repeat yourself.
The Solution¶
I believe good design comes from iterations of decent design, which is why I find a single-class approach very enticing compared to that of a multi-class approach. With a single class, I can quickly target and alter any element without having to worry about potential side effects. Yet, as exemplified in the button dilemma, it’s an ill-advised pursuit. Cue the lights and grab the popcorn because the stage is set for extend — aka classes.
The core concept of extend is the ability to “extend” the properties of one class into another. For instance, we could extend the .button
into .hook
thus mitigating our button dilemma and achieving the same result as stacking classes in HTML: class="button hook"
. The origins of this concept stem from a principle characteristic in classical Object Oriented programming that allows a base class to be extended into a target class. In doing so, the target class inherits all of the properties and methods of said base class. In short, classes are a way to extend a “base” ctr
class into any ctr
instance. Hence, instead of stacking CSS classes in HTML, ctr
classes are designed to be stacked in ctr
instances to create a CSS-centric multi-class pattern rather than an HTML-centric one.
The relationship shift that ctr
classes introduce streamlines the design process in a manner that eliminates the inevitable fragmentation that occurs in an HTML multi-class design pattern. Not only do classes centralize styles but they also eradicate the pains of CSS priority rules since what you see is what you get. In that, a ctr
instance scope can only have a single background property. However, the party does not stop there: multiple classes can rely on shared variables while retaining fine grain control and extensibility on an individual level. The abstraction provided by classes, combined with everything else ctr
has to offer, creates an absurd amount of flexibility and control which is nothing short of delicious. So to answer your question Philip, it’s delicious, it’s really fucking delicious. In fact, I would go so far as to say it’s in the same league as my Mother’s baking.
A Word of Caution¶
Classes are the one feature in ctr
that give me pause because they can quickly turn against you. Admittedly, classes are a half-baked idea that I implemented during the latter part of the development, but by that time the core data structure of ctr
was beyond rewriting. As a consequence, I was not able to implement various controls which I believe are needed to a large extent. For example, classes can extend other classes but I recommend you shy away from doing so as of right now. With the rewrite of ctr
, classes will be treated as a first-class citizen and not as an afterthought and will include debugging and control features which are currently lacking. That being said, I’m not your Mother and you can do as you please, but if things go awry don’t tell me I didn’t warn you.
Syntax¶
Description: ctrSetClass
and CtrClass
is defined as a method to create a <class>
instance of predefined ctr
styles.
ctrSetClass('<class>', {
<...>: <...>
})
// longhand
ctr('<#selector>', {
extend: {
// String | List
class: '<class>'
}
})
ctr:::setClass:<class>:
<...>: <...>
# longhand
<#selector>:
extend:
# String | List
class: <class>
ctrSetClass('<class>', {
<...>: <...>
})
// shorthand
ctr('<#selector>', {
// String | List
extend: '<class>'
})
ctr:::setClass:<class>:
<...>: <...>
# shorthand
<#selector>:
# String | List
extend: <class>
// Assigns Stylus Variable
<class> = CrtClass({
<...>: <...>
})
// longhand
ctr('<#selector>', {
extend: {
// String | Literal
class: <class>
}
})
// Assigns Stylus Variable
<class> = CrtClass({
<...>: <...>
})
// shorthand
ctr('<#selector>', {
// String | Literal
extend: <class>
})
Notes
- Da bomb-dot-com of
ctr
features - Both syntaxes achieve the same result in Stylus
- Longhand syntax is intended to be used in combination with
variable
- Under the hood, a
<class>
is exactly like a normalctr
instance with some small behavoir alterations- To make a relatable comparison, you can think of a normal
ctr
instance like an immediately invoked function and a<class>
as a Function declaration that is only invoked when inside actr
instance
- To make a relatable comparison, you can think of a normal
- I recommend using
ctrSetClass
rather than usingCtrClass
due to the nature of Stylus global variable assignment- Additionally, the naming convention of
ctrSetClass
is similar to that of YAML and Javascript
- Additionally, the naming convention of
Basic¶
Description: A ctrSetClass
instance creates an internal data reference to the <class>
name. This <class>
name can then be referenced in a ctr
instance through the extend
property to merge its data into the ctr
instance at the scope level it is defined.
// create class
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctr('.test', {
extend: 'Box'
})
# create class
ctr:::setClass:Box:
width: 200px
height: 400px
.test:
extend: Box
.test {
width: 200px;
height: 400px;
}
// create class
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctr('.test', {
extend: 'Box'
})
.test {
width: 200px;
height: 400px;
}
# create class
ctr:::setClass:Box:
width: 200px
height: 400px
.test:
extend: Box
Notes
- Classes can only be used after their declaration
- Class data is deep merged into the
ctr
instance - Source data always supersedes class data
- Don’t overthink it
With Data¶
Description: A <class>
can can be extended with data and at any scope level.
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctr('.test', {
width: 200px
components: {
'.blue-box': {
extend: 'Box'
background: blue
}
'.red-box': {
extend: 'Box'
background: red
height: 500px
}
}
})
# create class
ctr:::setClass:Box:
width: 200px
height: 400px
.test:
width: 200px
components:
.blue-box:
extend: Box
background: blue
.red-box:
extend: Box
background: red
height: 500px
.test {
width: 200px;
}
.test > .blue-box {
width: 200px;
height: 400px;
background: #00f;
}
.test > .red-box {
width: 200px;
height: 500px;
background: #f00;
}
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctr('.test', {
width: 200px
components: {
'.blue-box': {
extend: 'Box'
background: blue
}
'.red-box': {
extend: 'Box'
background: red
height: 500px
}
}
})
.test {
width: 200px;
}
.test > .blue-box {
width: 200px;
height: 400px;
background: #00f;
}
.test > .red-box {
width: 200px;
height: 500px;
background: #f00;
}
# create class
ctr:::setClass:Box:
width: 200px
height: 400px
.test:
width: 200px
components:
.blue-box:
extend: Box
background: blue
.red-box:
extend: Box
background: red
height: 500px
Notes
- Source data always supersedes class data
Multiple¶
Description: A List value of classes for the extend
property extends the class data into the ctr
instance in the order it is received.
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctrSetClass('Round-Soft', {
border-radius: 4px
})
ctr('.test', {
width: 200px
'component-.blue-box': {
extend: 'Box' 'Round-Soft'
background: blue
}
})
ctr:::setClass:Box:
width: 200px
height: 400px
ctr:::setClass:Round-Soft:
border-radius: 4px
.test:
width: 200px
component-.blue-box:
extend: [Box, Round-Soft]
background: blue
.test {
width: 200px;
}
.test > .blue-box {
width: 200px;
height: 400px;
background: #00f;
border-radius: 4px;
}
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctrSetClass('Round-Soft', {
border-radius: 4px
})
ctr('.test', {
width: 200px
'component-.blue-box': {
extend: 'Box' 'Round-Soft'
background: blue
}
})
.test {
width: 200px;
}
.test > .blue-box {
width: 200px;
height: 400px;
background: #00f;
border-radius: 4px;
}
ctr:::setClass:Box:
width: 200px
height: 400px
ctr:::setClass:Round-Soft:
border-radius: 4px
.test:
width: 200px
component-.blue-box:
extend: [Box, Round-Soft]
background: blue
Notes
- Lowest index supersedes in conflicts
Extend Class¶
Description: A <class>
can extend another <class>
.
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctrSetClass('Box-Round-Soft', {
extend: 'Box'
border-radius: 4px
})
ctr('.test', {
width: 200px
'component-.blue-box': {
extend: 'Box-Round-Soft'
background: blue
}
})
ctr:::setClass:Box:
width: 200px
height: 400px
ctr:::setClass:Box-Round-Soft:
extend: Box
border-radius: 4px
.test:
width: 200px
component-.blue-box:
extend: Box-Round-Soft
background: blue
.test {
width: 200px;
}
.test > .blue-box {
width: 200px;
height: 400px;
background: #00f;
border-radius: 4px;
}
ctrSetClass('Box', {
width: 200px
height: 400px
})
ctrSetClass('Box-Round-Soft', {
extend: 'Box'
border-radius: 4px
})
ctr('.test', {
width: 200px
'component-.blue-box': {
extend: 'Box-Round-Soft'
background: blue
}
})
.test {
width: 200px;
}
.test > .blue-box {
width: 200px;
height: 400px;
background: #00f;
border-radius: 4px;
}
ctr:::setClass:Box:
width: 200px
height: 400px
ctr:::setClass:Box-Round-Soft:
extend: Box
border-radius: 4px
.test:
width: 200px
component-.blue-box:
extend: Box-Round-Soft
background: blue
Notes
- Be careful with this, your code quickly becomes convoluted the deeper you go
classLock¶
Description: For extra safety, the classLock
option can be turned on to effectively lock all classes from being modified or overwritten during runtime development.
// turn on classLock
ctrSetOption({
classLock: true
})
ctrSetClass('Box', {
width: 200px
height: 200px
})
// Will not be able to overwritten or modified
ctrSetClass('Box', {
width: 444444px
height: 44444px
})
// width + height still 200px
ctr('.test', {
extend: 'Box'
})
# turn on classLock
ctr:::setOption:
classLock: true
ctr:::setClass:Box:
width: 200px
height: 200px
# NOTE: In YAML you can't have duplicate keys
# Will not be able to overwritten or modified
# ctr:::setClass:Box:
# width: 444444px
# height: 44444px
# width + height still 200px
.test:
extend: Box
.test {
width: 200px;
height: 200px;
}
// turn on classLock
ctrSetOption({
classLock: true
})
ctrSetClass('Box', {
width: 200px
height: 200px
})
// Will not be able to overwritten or modified
ctrSetClass('Box', {
width: 444444px
height: 44444px
})
// width + height still 200px
ctr('.test', {
extend: 'Box'
})
.test {
width: 200px;
height: 200px;
}
# turn on classLock
ctr:::setOption:
classLock: true
ctr:::setClass:Box:
width: 200px
height: 200px
# NOTE: In YAML you can't have duplicate keys
# Will not be able to overwritten or modified
# ctr:::setClass:Box:
# width: 444444px
# height: 44444px
# width + height still 200px
.test:
extend: Box
Notes
- If the
classLock
option is turned on and you attempt to edit a class,ctr
will throw you an error to tell you what’s up - Option property:
classLock: ture
- As you would expect, the
classLock
option can also be set in the.ctrrc
- As you would expect, the