Phase 1 Project at Flatiron School

Project by Bruno Aguiar, Reese Chamberlain, and Ashton MacKenzie

Post by Ashton MacKenzie

Navigate through the post:

The project

Discogs is a website where users enter data about music and the formats the music is available in. The Discogs Utility will allow a user to add functionality to Discogs that is present on other services (Spotify, Last.fm) but missing in the default platform.

Project Requirements

  1. Your app must be a HTML/CSS/JS frontend that accesses data from a public API. All interactions between the client and the API should be handled asynchronously and use JSON as the communication format. Try to avoid using an API that requires a key. APIs that are free and require no authorization will be easiest to use. For ideas, see this list of no-auth APIs (Links to an external site.). If you would like to use an API that requires a key, please consult with your instructor on how to protect that key. NEVER push your API key to github!
  2. Your entire app must run on a single page. There should be NO redirects. In other words, your project will contain a single HTML file.
  3. Use at least 3 unique event-listeners (Links to an external site.) that enable interactivity. Think search or filter functionality, toggling dark/light mode, upvoting posts, etc. Each of your event listeners should have its own unique callback function.
  4. Your project must implement at least one instance of array iteration using available array methods (mapforEachfilter, etc). Manipulating your API data in some way should present an opportunity to implement your array iteration.
  5. Follow good coding practices. Keep your code DRY (Do not repeat yourself) by utilizing functions to abstract repetitive code.

What We Actually Did

The first thing we did was run off into the distance and completely ignore directives one, and nearly also two. While the Discogs API does have a public, unauthenticated mode, there is data that can only be accessed by an authenticated user. We had an excellent time practicing using .gitignore to hide our personal access tokens. More on our actual implementation identity management later, as first we went down a dangerous path of temptation.

The Temptation of OAuth

First, I immediately tried to see if there was any way we could use OAuth for our project, because being able to log in once with conventional credentials securely was extremely compelling. Unfortunately, I was not able to find a way to make use of OAuth in a purely client-side environment.

Quick note – OAuth (Open Authorization) is a standard for securely sharing data by *delegating authorization* to another website or application, but not giving them your password. You’re essentially saying “Yeah, I approve them asking for my data.”

OAuth works like this: A web app has a front end and a back end. The *back end*, having been alerted that a user would like to make use of data from a third party that requires authentication (proof of authorization), will redirect the user to that third party. This is a lot like sending a student with a note home to their family. The teacher expects this note will make it home, and the family will make some sort of response. Let’s say the note is like a waiver for participating in a class activity like a student bake sale. If the family writes back that the student can participate, then that student will be coming back with a note.

In the case of an OAuth flow, the browser and third party website know to redirect the user back to the original site because of the note the user came with. In the case of the student, they were going back to school the next day anyway.

The back-end app looks at the note the user comes back with and the server for the third party has included a token. Now the back-end can include that token with all their requests that require authentication, and the third party server will know these requests came from that authenticated user. This is a lot like the teacher saying “Ok, families who are participating in the bake sale, I would like to make a request for your muffins and cookies. You know it is me and not a random person because I am the teacher who previously sent a request with your child.”

In any case, requirement 2 stipulates that the entire app must be contained within a single page. OAuth, even if it is in the rare situation that it is coerced to be client side (I heard Rumors that someone could do this in an extremely janky fashion in Node), requires redirects. So OAuth is out until we’re doing back-end tasks.

Actual Identity Management

I’m extremely resistant to having to do extra steps, so I actually designed an identity management system for our app that distinguishes between local and remote hosting behaviors. Locally, we can use a keys.js file and store our username and Personal Access Tokens. If we host the project remotely, then this keys.js file will not be present because it was configured to be kept private via .gitignore. Our project checks for whether userName and key have been set via keys.js, and if not, it will prompt the user to enter these things. Later, we also added the ability to specify how many album data records to request from Discogs from the same form.

Displaying the Albums

Discogs will share rather a lot of data about our albums, but we don’t need all of it for this project. Instead, we iterated over the list of albums and for every five albums, broke them out into rows and generated a HTML table to display all the albums. This had the benefit of ensuring that things would align nicely.

When an album is hovered, there is some feedback in the form of dimming to let the user know which album will have more information displayed when the album is clicked. When the album is clicked, another row is added to the table and the album art is displayed larger with some additional text about the album.

It was tricky to get the additional row to appear in the correct place. I tried a few things with querySelector and CSS pseudoselectors like :after to try and put another tr element after the current one that was just clicked. As it turned out, there is already a method that does what I wanted to do:

targetTr.insertAdjacentElement('afterEnd', trDetail);

It truly does just insert an adjacent element, how about that.

Event Listeners Everywhere, All The Time, Constantly

You know, at some point we were vaguely aware of how many event listeners we were supposed to create, but we were having so much fun making literally everything we could touch clickable and deleteable that now no one knows how many event listeners we have. I think Reese figured out that the default number of albums you can request is one hundred, so that would mean that there’s the event listener on the form, the event listener to dismiss the details box, and an event listener for every album. But, just because the default is one hundred, doesn’t mean we have to limit ourselves to that. You can easily type in something like five hundred and forty if that’s how much music you own.

Iteration is Life and Love

While we iterate over a lot of things, I feel especially proud of how I tried to make the code DRY per the requirements by iterating over the elements in a single album object to populate the details box.

let displayDetailsArray = ['artist', 'title', 'year', 'genre', 'descriptions', 'url'];
                /**
                 * Populate the description row table data with multiple p tags and the associated data from parsedReleases
                 * @param {array} displayDetailsArray - the list of details we want from parsedReleases
                 * @param {object} parsedReleases[id] - the object representing the album we want details on from parsedReleases
                 */
                displayDetailsArray.forEach((key) => {
                    let tdDetailP = document.createElement('p');
                    //if the data type is an array, then convert to a string with spaces between the elementsif (key == 'descriptions') {
                        tdDetailP.innerText = `${key}: ${Object.values(parsedDetails[key]).join(', ')}`;
                    }
                    elseif (key == 'url') {
                        // tdDetailP.innerHTML = `<a href="${parsedDetails[key]}">Album Page on Discogs</a>`;fetch(parsedDetails[key])
                            .then((res) => {
                                return res.json();;
                            })
                            .then((data) => {
                                console.log(data.uri);
                                tdDetailP.innerHTML = `<a href ="${data.uri}"> Album Page on Discogs</a>`;
                            })
                    else {
            tdDetailP.innerText = `${key}: ${parsedDetails[key]}`;
        }
        tdDetailP.id = key;
        tdDetailText.append(tdDetailP);

    })

    let tdDetailImageCover = document.createElement('img');
    tdDetailImageCover.src = parsedDetails.cover;
    tdDetailImage.append(tdDetailImageCover);

While I got started writing that, I got through hard coding two of them and then decided I had developed a sudden allergy to writing out all these generated table data elements. Instead, I wrote out a little array of all the keys of different parts of the data that we wanted to display more – there are some keys we wanted to handle differently, so this wasn’t really a case that could be handled by automatically extracting the keys and passing them as an array directly. While I might want to try refactoring it so that array is generated and the keys are handled or ignored appropriately later, I feel like the project is small enough and few enough hours are available that manually generating this array was acceptable.

We Require More Minerals

As it turns out, there are a couple of things that we decided that we did want that aren’t available in the data payload that is provided when you request a user’s collection.

Album Release Year

The year an album is released can be different from the year that the format that the user owns was released. We can determine this from the keywords that are associated with that album, and they tend to start with the prefix RE as in: remix, repress, rerelease.

Discogs Album Page

The page for the album isn’t provided with the payload, just a resource url for more on that album in particular.

In both of these cases, we needed to selectively make a second API call to avoid triggering the rate limit on calls to Discogs’ API and get the information we actually wanted. The release year is something we are still working on as of this posting, since we have a challenge within our promise chain we are working through.

We resolved this issue for the Discogs album page URI by making the change to the <a href> we wanted from within the final .then() in the promise chain, thus forcing all the previous promises to be resolved first. This solved problems like links that go to [[object promise]].

Project Management

It was awesome to work with Reese and Bruno, both of whom are much better at project management than I am. While I have a good sense of scope, and how long to spend troubleshooting an issue before pulling in another resource, they were instrumental in helping to model problems, break problems into smaller action items, and communicate the vision in a way that we could all reference it and determine what next to act on.

Bruno was especially awesome here. Neither Reese, who originally came up with the project idea, nor I had any concrete idea of how we wanted this to look and feel. We didn’t really have a specific experience in mind for how it would be to use this tool beyond “haha, this seems cool”. Bruno turned the vision description into something we could point at and compare with.

We also got a lot of practice using branches on GitHub. We started out using branches named after ourselves that we merged freely from and to main with, but we’re gaining better habits around deleting unused feature branches when done and using branches that are based around a particular goal or functionality item.

Making it work on WordPress

I actually had to refactor a lot of our project in order to get it to work with the WPCode WordPress extension, which is why the fork that lives on my WordPress site is missing the album quantity request functionality. My favorite piece of funny business I got up to was solving a problem of useability. At first, the albums displayed in a single column and the row that was added with additional information would show in the sixth slot. In order to get the CSS to apply, I wrote a function to add a <style> element with our style rules to the very end of the DOM, thus ensuring our style rules ran last and would win for the display computations.

With our rules active, if you clicked on an album cover, the large text and album art showed as intended. But on WordPress, the inactive albums shrank to tiny raisin-albums. In order to get the styles to apply, I had to rename our use of the row class so that it wouldn’t conflict with my WordPress theme’s use of the row class. Once that was done, our project displayed as we intended and it was ready to demo.

Our group also has our project hosted on GitHub Pages so that you can see the latest version of the project with our latest commits, plus a custom favicon.

Further Plans

We would like to take this further by implementing an ability to update a custom listen counter field in Discogs, and be able to print out paper squarecodes that make these API calls for us when we play our vinyl. We would also like to implement our take on the “Year in Review” where we can take a look at when listen counts were updated, and make a report about what music was played the most and when.

Thank you for reading. We had a great time with this project and are ready to learn some more skills to take it to the next level.

Demos

Project Demo on Reese’s GitHub Pages

Project Demo on Ashton’s Portfolio

Project Documents

Project GitHub

Project Planning Document

Project Wireframe by Bruno Aguiar

Related Posts