In another Code Review, it was recommended that I alphabetize my CSS. So I made a tool to do this quickly in JavaScript.
Because CSS has two levels (selector on the outside, parameters on the inside), and they both need to be alphabetized, alphabetizing it is not as easy as just doing array.sort()
. A custom tool is needed.
Feel free to comment on anything JavaScript. Here's my code.
"use strict";
class CSSAlphabetizer {
alphabetize(s) {
let lines, blocks;
// TODO: this._validate();
// TODO: this._beautify(); using regex and replace of { ; }
lines = this._makeLinesArray(s);
blocks = this._sortSelectors(lines);
blocks = this._sortParameters(blocks);
s = this._makeString(blocks);
s = this._addBlankLineBetweenBlocks(s);
s = s.trim();
return s;
}
_addBlankLineBetweenBlocks(s) {
return s.replace(/}\n([^\n])/, "}\n\n$1");
}
_makeString(blocks) {
// blocks => lines
let lines = [];
for ( let block of blocks ) {
let lines2 = block[1];
for ( let line of lines2 ) {
lines.push(line);
}
}
// lines => string
let s = "";
for ( let line of lines ) {
s += line[1] + "\n";
}
s = s.slice(0, s.length-1);
return s;
}
_sortSelectors(lines) {
/** [parameterTrimmed, [line, line, line, line]] */
let blocks = [];
/** [line, line, line, line] **/
let buffer = [];
let lineNumTracker = 0;
let parameterTrimmed = "";
let len = lines.length;
for ( let i = 0; i < len; i++ ) {
let line = lines[i];
let lineNum = line[2];
if ( ! parameterTrimmed && line[0].includes("{") ) {
parameterTrimmed = line[0];
}
if ( lineNum !== lineNumTracker ) {
lineNumTracker++;
blocks.push([parameterTrimmed, buffer]);
buffer = [];
parameterTrimmed = "";
}
buffer.push(line);
// Last line. Finish the block.
if ( i === len-1 ) {
lineNumTracker++;
blocks.push([parameterTrimmed, buffer]);
buffer = [];
parameterTrimmed = "";
}
}
blocks = blocks.sort();
return blocks;
}
_sortParameters(blocks) {
for ( let key in blocks ) {
let lines = blocks[key][1];
let lineBuffer = [];
let sortBuffer = [];
for ( let line of lines ) {
let isParameter = line[3];
if ( isParameter ) {
sortBuffer.push(line);
} else {
if ( sortBuffer ) {
sortBuffer = sortBuffer.sort();
lineBuffer = lineBuffer.concat(sortBuffer);
}
lineBuffer.push(line);
}
}
blocks[key][1] = lineBuffer;
}
console.log(blocks);
return blocks;
}
/** Makes an array of arrays. [trimmed, notTrimmed, blockNum, isParameter]. Having unique block numbers will be important later, when we are alphabetizing things. */
_makeLinesArray(s) {
// TODO: refactor to use associative array, more readable code, and less likely to have bugs if later we want to add/change/delete fields
let lines = s.split("\n");
let blockNum = 0;
let isParameter = false;
for ( let key in lines ) {
let value = lines[key];
if ( value.includes("}") ) {
isParameter = false;
}
lines[key] = [
// Trimmed line in first spot. Important for sorting.
value.trim(),
value,
blockNum,
isParameter,
];
// When } is found, increment the block number. This keeps comment lines above block grouped with that block.
if ( value.includes("}") ) {
blockNum++;
}
if ( value.includes("{") ) {
isParameter = true;
}
}
console.log(lines);
return lines;
}
}
window.addEventListener('DOMContentLoaded', (e) => {
let css = document.getElementById('css');
let alphabetize = document.getElementById('alphabetize');
// TODO: Combo box that loads tests.
alphabetize.addEventListener('click', function(e) {
let alphabetizer = new CSSAlphabetizer();
css.value = alphabetizer.alphabetize(css.value);
});
});
body {
margin: 1em;
width: 95%;
max-width: 700px;
font-family: sans-serif;
}
textarea {
width: 100%;
height: 360px;
tab-size: 4;
}
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>CSS Alphabetizer</title>
<link rel="stylesheet" href="style.css" />
<script type="module" src="css-alphabetizer.js"></script>
</head>
<body>
<h1>
CSS Alphabetizer
</h1>
<p>
Beautify your CSS. Sort your selectors alphabetically (which groups together #id's, .classes, and then elements), and sort your parameters alphabetically too. Parameters are nested within braces, so those aren't easy to sort with a normal A-Z sort.
</p>
<p>
<textarea id="css">body {
background-color: #999999;
margin-bottom: 0;
}
#page-container {
background-color: #DFDFDF;
width: 1150px;
margin: 12px auto 0 auto;
}
#page-container2 {
padding: 12px 12px 0 12px;
}
header {
background-color: white;
}
.category {
display: inline-block;
padding: 18px 25px;
font-family: sans-serif;
}
.category:hover {
background-color: #059BD8;
}</textarea>
</p>
<p>
<button id="alphabetize">Alphabetize</button>
</p>
<p>
Want to report a bug or request a feature? <a href="https://github.com/RedDragonWebDesign/css-alphabetizer/issues">Create an issue</a> on our GitHub.
</p>
</body>
</html>
#
and.
etc. for sorting - something I'd prefer. \$\endgroup\$p
elements. The first blockcolor: red;
style is later replaced withcolor: black;
. It would be best for the author to catch the inconsistencies, but unrealistic that everyone would be handled \$\endgroup\$