Losing Control
Bruce Hillsworth
Posted on April 7, 2023
As a new developer, every single day is information overload. I am convinced that the pounding I feel in my head after encountering another challenging concept is actually my brain morphing, evolving and rearranging itself to brace for... you guessed it! More information. Objects, arrays, pure functions, context, THIS! These are just some of the concepts that loop through the brain of an aspiring developer and I am no exception. Yet, as I sit here, having completed my first collaborative frontend project, version control is what I am ruminating over. This is by no means a complete rundown of version control, rather a synopsis of what I learned working with others for the first time with an emphasis on version control.
The Project
The goal of the project was to build a single page app that fetches data from an external API and utilizes JavaScript to manipulate the DOM and provide data without without a page refresh. One of the members of my group is a bike enthusiast and put us on to a website called bikeindex.org that maintains an API. The API allows people to search the database for stolen bikes, report their bike as stolen, and register a newly purchased bike. After some brainstorming, we decided to do a page that upon load would pull the user's location and then display bikes that have been stolen locally.
What We Did Right
This was the first collaborative project for all three of us so we definitely ran into hurdles but we also did some things correctly. The first thing we did right, was taking the time to brainstorm and dial in what exactly we wanted to display. We determined that we wanted to populate the bike data on a reusable card that would allow us to use a render function. Likewise, we also decided we would need to get the geolocation of the user and would populate the page on load with local stolen bikes. Deciding on these base level items gave us a path from start to finish and ensured that none of us strayed too far from the original concept. Within a short period of time we developed a function to access the user location using Abstract API and combined it with an initialize function that fetched stolen bike data from the bikeindex.org API and rendered bikes on individual cards using a render function.
Geolocation Function Using XML:
function httpGetAsync(url, callback) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status === 200) {
callback(xmlHttp.responseText);
} else {
callback(null, new Error("Request failed"));
}
}
};
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}
Initialize Function:
function initialize(response) {
if (response === null) {
fetch(
`https://bikeindex.org:443/api/v3/search?page=1&per_page=25&location=ip&distance=${distance}&stolenness=proximity`
)
.then((response) => response.json())
.then((stolenBikes) => {
bikes = stolenBikes.bikes.filter(
(x) => x.large_img && x.title && x.description
);
if (!bikes.length) {
alert("Try extending the search radius");
}
bikes.forEach((bike) => renderDisplayCardsOnPageLoad(bike));
});
} else {
locationObject = JSON.parse(response);
zipCode = locationObject.postal_code;
fetch(
`https://bikeindex.org:443/api/v3/search?page=1&per_page=100&query=image&location=${zipCode}&distance=${distance}&stolenness=proximity`
)
.then((response) => response.json())
.then((stolenBikes) => {
console.log(stolenBikes);
bikes = stolenBikes.bikes.filter(
(x) => x.large_img && x.title && x.description
);
if (!bikes.length) {
alert("Try extending the search radius");
}
bikes.forEach((bike) => renderDisplayCardsOnPageLoad(bike));
});
}
}
Render Function:
function renderDisplayCardsOnPageLoad(bike) {
let imageOpacity = true;
const stolenLocation = document.createElement("p");
stolenLocation.setAttribute("id", "bike-name");
stolenLocation.textContent = getCityAndState(bike);
const card = document.createElement("div");
card.setAttribute("class", "card");
const img = document.createElement("img");
img.setAttribute("src", bike.large_img);
const location = document.createElement("p");
location.textContent = getCityAndState(bike);
img.addEventListener("click", (e) => {
imageOpacity = !imageOpacity;
e.preventDefault();
if (!imageOpacity) {
const reportButton = document.createElement("button");
reportButton.innerText = "REPORT SIGHTING";
reportButton.setAttribute("id", "report");
const description = document.createElement("p");
description.textContent = bike.title;
const serialNumber = document.createElement("p");
const serialNumberString = `Serial Number: ${bike.serial}`;
serialNumber.textContent = serialNumberString;
const dateStolen = document.createElement("p");
dateStolen.textContent = getDateStolen(bike);
const location = document.createElement("p");
location.textContent = getCityAndState(bike);
e.target.style.opacity = 0.15;
imageOpacity = false;
card.appendChild(reportButton);
card.appendChild(serialNumber);
card.appendChild(dateStolen);
card.appendChild(description);
reportButton.addEventListener("click", (e) => {
card.innerHTML = "";
const returnButton = document.createElement("button");
returnButton.innerText = "RETURN";
returnButton.className = "back-btn";
const reportFormSubmit = document.createElement("button");
reportFormSubmit.setAttribute = ("type", "submit");
reportFormSubmit.setAttribute = ("value", "submit");
reportFormSubmit.innerText = "SUBMIT";
reportFormSubmit.className = "btn";
const reportForm = document.createElement("form");
reportForm.id = "report-form";
const reportFormLocation = document.createElement("input");
reportFormLocation.type = "text";
reportFormLocation.className = "field";
reportFormLocation.id = "report_form_location";
reportFormLocation.placeholder = " ENTER SIGHTING LOCATION";
const reportFormComments = document.createElement("input");
reportFormComments.type = "text";
reportFormComments.className = "field";
reportFormComments.id = "report_form_comments";
reportFormComments.className = "field";
reportFormComments.placeholder = " ADDITIONAL COMMENTS";
const reportFormName = document.createElement("input");
reportFormName.type = "text";
reportFormName.id = "report_form_name";
reportFormName.className = "field";
reportFormName.placeholder = " NAME (optional)";
const bikeDetails = document.createElement("button");
bikeDetails.textContent = "BIKE DETAILS";
bikeDetails.className = "btn";
const location = document.createElement("p");
location.textContent = getCityAndState(bike);
location.className = "location";
card.appendChild(reportForm);
reportForm.appendChild(reportFormLocation);
reportForm.appendChild(reportFormComments);
reportForm.appendChild(reportFormName);
reportForm.appendChild(reportFormSubmit);
reportForm.appendChild(returnButton);
returnButton.addEventListener("click", (e) => {
card.innerHTML = "";
img.style.opacity = 1;
card.appendChild(img);
card.appendChild(stolenLocation);
});
reportForm.addEventListener("submit", (e) => {
e.preventDefault();
const fll = report_form_location.value;
const flc = report_form_comments.value;
const fln = report_form_name.value;
card.innerHTML = "";
img.style.opacity = 1;
card.appendChild(img);
card.appendChild(stolenLocation);
createSightingObj(bike, fll, flc, fln);
});
});
} else {
card.innerHTML = "";
e.target.style.opacity = 1;
card.appendChild(img);
card.appendChild(stolenLocation);
imageOpacity = true;
}
});
card.appendChild(img);
card.appendChild(stolenLocation);
gallery.appendChild(card);
}
Where We Lost Control
Feeling pretty good about our progress we opted to have some fun and each of us opened our own branch and played with the code as we saw fit. This is where our first invaluable lesson on version control came in to play and following our the tips I learned and would advise any new developer to follow unless they want to handle nightmare merges down the road...
Split Up The Work
As new developers we all wanted to be very involved in every aspect of the app. All the code above is what the final code wound up being but each of us, on our own time, made our own render and initialize functions. While similar in technique, they were also very different. We also had a tendency to make sweeping changes vs small. This became particularly burdensome when we would go to merge files which leads me to my next tip...
Commit Small and Commit Often
This should be self explanatory but all the members of my group strayed away from this concept too frequently. The end result was less time spent on our code and making our app better vs nightmare merge sessions where we are trying to get all of our code to work together.
Single Intent
This coincides with small commits but even small commits could influence multiple areas of an app. As stated above, when we played with the code, we each did huge changes to multiple areas(JS, HTML, CSS) prior to committing. By keeping commits to single intents, reversing the code to enable better commits is more readily available.
Branch Away
A technique that I feel helped me, too late of course, was to have a separate branch for each area that I was working on. For instance, a branch just to handle the JavaScript file, a branch for the CSS, and a branch for the HTML. I found it to be incredibly difficult to merge commits that had dabbled in multiple spaces.
Posted on April 7, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.