As developers, I imagine we've all been there. It is the beginning of our coding journey; we have all these ideas of fun projects we could do and every new idea is a potential new breakthrough.
That is a great feeling, and one of the reasons why some people turn to programming. Making things is fun!
What is ${!fun}
is failing at our treasured personal projects. I am confident enough to wait for whoever doesn't have at least one abandoned project to cast the first stone. We talk more about good project ideas for beginners. The topic I'd like to explore today is the importance of trying again when you abandon a project.
First, I would like to get something out of the way. There are myriad reasons why one would abandon a project, but I am talking specifically about abandoning a project that turned out to be too hard and frustrating.
My story
When I first started learning to code I had a pet project that I spent hours working on. I wanted a simple way of adding pixel art on the screen.
I had a few requirements in mind for it too:
- It had to be in Javascript (since that is what I was learning at the time)
- I wanted to be able to turn it into a library at some point
- I wanted to stay away from frameworks if possible
- I had some fun using Excel for pixel art making, and had written a small JSON generator in vbscript. I wanted to use my proprietary format to feed the script with the data it needed for the pixel art.
With the exception of the last item, this list of requirements isn't anything out of the ordinary. My JSON file was not super complex either. Here is a sample of the generated JSON output from my Excel VBScript:
[
{
"name":"Computer",
"pixelMap": [
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
],
"iconHeight": 15,
"iconWidth": 15
},
{
"name":"Apple",
"pixelMap": [
[0,0,0,0,1,1,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0],
[0,0,0,1,1,1,0,1,0,1,1,1,0,0,0],
[0,0,1,1,1,1,1,1,1,1,1,1,1,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,0,1,1,1,1,1,1,1,1,1,1,1,0,0],
[0,0,0,1,1,1,1,1,1,1,1,1,0,0,0],
[0,0,0,0,1,1,1,0,1,1,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
],
"iconHeight": 15,
"iconWidth": 15
},
{
"name":"Cat",
"pixelMap": [
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]
],
"iconHeight": 15,
"iconWidth": 15
}
]
Nothing to really write home about, but it had some important information for my purposes: It had the width and height of each icon, along with a matrix of 0s and 1s that I could use in my code.
With my data in hand I could start coding, finally!
My code - First version
After a few days of exploring, I had a very naive first version of the code. The only problem? It didn't work. Actually, it displayed a lot of promising behavior, but it didn't look right, and I was too inexperienced to separate what was wrong. In my mind, this result was nothing but a failure:
This is the code that produced that result, please pardon my mess:
document.addEventListener('DOMContentLoaded', function () {main();});
function main() {
loadIconsFromServer('https://example.com/fake/path/icons.json');
}
const loadIconsFromServer = url=> {
fetch(url)
.then(res => res.text())
.then(body => {
drawCanvas(body);
});
};
const drawCanvas = loadIconsFromServerResponse => {
imgPixel = JSON.parse(loadIconsFromServerResponse);
var i = imgPixel.length;
while (i--){
styleCanvas(imgPixel[i].name, imgPixel[i].iconHeight, imgPixel[i].iconWidth);
drawIcon(imgPixel[i].name,
imgPixel[i].iconHeight,
imgPixel[i].iconWidth,
imgPixel[i].pixelMap);
}
}
const styleCanvas = (className, canvasHeight, canvasWidth) => {
var canvasStyleSizePosition = "height:20vh; width:20vh; margin:0 auto;"
var canvasStyleDisplay = "display:grid;"
var canvasStyleGridColumns = "grid-template-columns:repeat(" + canvasHeight + ", 1fr);"
var canvasStyleGridRows = "grid-template-rows:repeat(" + canvasWidth + ", 1fr)"
var canvasStyle = canvasStyleSizePosition +
canvasStyleDisplay +
canvasStyleGridColumns +
canvasStyleGridRows;
var canvasObjects = document.getElementsByClassName(className);
for (i = 0; i < canvasObjects.length; i++){
canvasObjects[i].setAttribute("style", canvasStyle);
}
}
const drawIcon = (className, row, column, pixelOn) => {
var gridDivStyle = [];
var iconNameCount = document.getElementsByClassName(className);
for (i = 0; i < row; i++) {
gridDivStyle.push([]); //initializes array rows
for (j = 0; j < column; j++) {
gridDivStyle[i][j] = "col-" + i + "-row-" + j;
for (k=0; k < iconNameCount.length; k++){
var gridDiv = document.createElement("div");
var iconName = document.getElementsByClassName(className);
gridDiv.className = gridDivStyle[i][j];
iconName[k].appendChild(gridDiv);
if (pixelOn[i][j]) {
var pixelOnClass = "col-" + i + "-row-" + j;
var pixelOnObject = document.getElementsByClassName(pixelOnClass);
var pixelOnStyle = "background-color:limegreen;"
for (l = 0; l < pixelOnObject.length; l++){
pixelOnObject[l].setAttribute("style", pixelOnStyle);
}
}
}
}
}
}
There's plenty to criticize about this code, but this is the benefit of hindsight. At that point I tried fixing the code for days on end, never feeling like I was getting anywhere near close to figuring out why my pixelated masterwork was eluding me. Unsurprisingly, I quit working on that project.
The prodigal son - Returning to my code
I continued my learning over the course of the next year, building your usual variety of beginner friendly projects, and the projects that got too ambitious were met with the same fate as my first project. My project graveyard was growing almost as big as my portfolio at this point.
I didn't see the problem in letting go of those projects that no longer brought me joy. However, not all abandoned projects were abandoned for that reason. I kept looking back at my first project and wondering if I could finally slay that beast, and conquer my first real failure, now armed with the wisdom and skills honed through the year.
I opened up my old pen in codepen, and glanced at the code, feeling like nothing could stop me. I had spent a whole year learning new things, so I had to be ready, right?
As I felt my confidence waning with every new line, I tried to keep my wits about me. I could spot a few issues with my previous code, and I could see clear places where things could be improved a lot. So I started writing code again, with no plan, and just winging it as I went along, fixing what I could.
When I was done this was the final result! Are you ready for it?
What? Another failure! And this seemed related to my original bug. But I didn't understand what I was doing. I didn't understand the DOM very well, nor did I understood promises, so I used async/await instead (another example of my initial lack of understanding of Javascript's inner workings), and a litany of poor choices were sprinkled across my code. I had a hammer, and all my problems looked like nails to me.
You can see it for yourself what a year apart can do for a codebase:
async function main() {
let icons = await loadIconsFromServer(
"https://example.com/path/to/icons.json"
);
icons.map(icon => {
let { name, pixelMap, iconHeight, iconWidth } = icon;
drawCanvas(name, pixelMap, iconHeight, iconWidth);
});
}
const loadIconsFromServer = async function(url) {
return fetch(url)
.then(res => res.text())
.then(iconsArray => {
return JSON.parse(iconsArray);
});
};
const drawCanvas = (name, pixelMap, iconHeight, iconWidth) => {
styleCanvas(name, iconHeight, iconWidth);
drawIcon(name, iconHeight, iconWidth, pixelMap);
};
const styleCanvas = (className, canvasHeight, canvasWidth) => {
let canvasStyleSizePosition = "height:20vh; width:20vh; margin:0 auto;";
let canvasStyleDisplay = "display:grid;";
let canvasStyleGridColumns =
"grid-template-columns:repeat(" + canvasHeight + ", 1fr);";
let canvasStyleGridRows =
"grid-template-rows:repeat(" + canvasWidth + ", 1fr)";
let canvasStyle =
canvasStyleSizePosition +
canvasStyleDisplay +
canvasStyleGridColumns +
canvasStyleGridRows;
let canvasObjects = Array.from(document.querySelectorAll("." + className));
canvasObjects.map(function(obj) {
obj.setAttribute("style", canvasStyle);
});
};
let drawIcon = (className, row, column, pixelOn) => {
let gridDivStyle = [];
let iconName = document.querySelectorAll("." + className);
let iconNameCount = iconName.length;
for (let i = 0; i < row; i++) {
gridDivStyle.push([]); //initializes array rows
for (let j = 0; j < column; j++) {
gridDivStyle[i][j] = "col-" + i + "-row-" + j;
for (let k = 0; k < iconNameCount; k++) {
let gridDiv = document.createElement("div");
gridDiv.className = gridDivStyle[i][j];
iconName[k].appendChild(gridDiv);
if (pixelOn[i][j]) {
let pixelOnClass = "col-" + i + "-row-" + j;
let pixelOnObject = document.querySelectorAll("." + pixelOnClass);
let pixelOnStyle = "background-color:limegreen;";
for (let l = 0; l < pixelOnObject.length; l++) {
pixelOnObject[l].setAttribute("style", pixelOnStyle);
}
}
}
}
}
};
main();
At this point I didn't know what else to do, so I followed a familiar pattern: I abandoned my project once again.
The Third Time - It's always the charm
It's been over two years since I originally dreamed up my pixel icon generator, and since then I've learned a lot, and even started working full time as a developer. By now I felt not only confident, but also I understood what my previous code was doing! This was a huge win, so armed with this newfound energy I decided to tackle my problem a third time. However, before I even started I decided to spend some time determining how my application should work, what were some of the behaviors I wanted it to present, and what were some of the sensible places I could separate the code apart.
When I was done this time a lot of things had changed under the hood. My pixel icon creator had changed, but that was because I had changed myself. As I grew in my coding skills, so did my code grow in maturity. I was more seasoned and could see the progress clearly.
More importantly, however, is the fact that the feeling of success over having finally conquered my own hydra chipped away at the impostor syndrome! I felt as if I was a capable developer, because I was able to finally fix a bug I tried my hands at for years.
I think it is easy to not see your own progression when the scale of the problems you're trying to solve grow with your skills. And this is the take-away I want to leave you with: Working on abandoned projects can help you see your own personal growth over the years.
And if you're curious about the actual final version of the code:
(async () => {
const iconDivs: HTMLDivElement[] = Array.from(
document.querySelectorAll("div[class^=sc-icon]")
);
iconDivs.forEach(async (icon: HTMLDivElement) => {
const iconName: string = cssClassExtractor(icon);
icon.replaceWith(await iconGenerator(iconName));
});
})();
interface IIcon {
name: string;
pixelMap: number[];
iconHeight: number;
iconWidth: number;
failedToFetch?: boolean;
errorMessage?: string;
}
function cssClassExtractor(icon: HTMLDivElement): string {
return icon.classList.value.split("-").pop();
}
async function loadIconsFromServer(url: RequestInfo): Promise<IIcon[]> {
let request: Response, iconArray: IIcon[];
try {
request = await fetch(url);
iconArray = await request.json();
} catch ({ message }) {
return [
{
name: "",
pixelMap: [],
iconHeight: 0,
iconWidth: 0,
failedToFetch: true,
errorMessage: message
}
];
}
return iconArray;
}
async function iconGenerator(iconName: string): Promise<HTMLDivElement> {
const iconSheets: IIcon[] = await loadIconsFromServer(
"https://example.com/path/to/icons.json"
);
const currentIcon: IIcon = iconSheets.filter(
iconSheet => iconSheet.name.toUpperCase() === iconName.toUpperCase()
)[0];
let gridDivStyle = [];
const icon: HTMLDivElement = document.createElement("div");
icon.setAttribute(
"style",
`height:20vh;
width:20vh;
margin:0 auto;
display:grid;
grid-template-columns:repeat(${currentIcon.iconHeight}, 1fr);
grid-template-rows:repeat(${currentIcon.iconWidth}, 1fr)`
);
for (let i = 0; i < currentIcon.iconHeight; i++) {
gridDivStyle.push([]);
for (let j = 0; j < currentIcon.iconWidth; j++) {
gridDivStyle[i][j] = "col-" + i + "-row-" + j;
let gridDiv = document.createElement("div");
gridDiv.className = gridDivStyle[i][j];
if (currentIcon.pixelMap[i][j]) {
gridDiv.setAttribute("style", "background-color:limegreen;");
}
icon.append(gridDiv);
}
}
return icon;
}
Top comments (0)