It's Phase 1 project week at Flatiron School for my Amazon Software Development cohort, and anyone who's been through a bootcamp knows what that means - borderline overdoses of caffeine and a complete disregard for every bit of scientific research claiming that sleep is necessary. I'll preface this post by saying that it's still early days in the course, and that the application presented here is a summation of what I've learned so far, but is by no means meant to be a professional attempt at creating a web app. It's necessarily amateurish, because I'm working from the knowledge we've gained so far, and any experienced developer looking at the code behind this app will probably have an absolute fit. However, with that disclaimer out of the way, and with no apologies for the absolutely terrible pun in the name, I'm extremely proud to present my very first functioning web application, Gotta Fetch 'Em All!
The idea for the project revolved around creating a simple web-based implementation of a Pokemon game by pulling data from the PokeAPI and displaying it to the user, with options for catching, releasing, renaming, and running for your life from Pokemon, as in the real games. Specifically, I wanted the user to be able to:
- See a random Pokemon pulled from the PokeAPI
- Interact with said wild Pokemon by choosing to attempt to catch them, or by running away and having a new random Pokemon pulled and displayed
- Collect Pokemon in a basic implementation of a Pokedex
- Rename any Pokemon in the Pokedex
- Release any caught Pokemon
- Maintain their collection through page refreshes
I have several stretch goals that I simply ran out of time to implement, but that I'm looking forward to retroactively adding, including:
- Error handling. My time was too limited to go in depth on adding detailed error handling to the app, but that's a necessary portion of the user experience that I'll be researching and implementing soon
- Adding an Experience tracker that affects a user's chance to catch a Pokemon, dependent on how many Pokemon a user has seen and caught previously
- Implementing a more detailed model for the Pokedex, namely by allowing users to track how many different types of Pokemon they've seen, deep dive into the stats of their current Pokemon, sort Pokemon by type, and displaying their progress toward c̶a̶t̶c̶h̶i̶n̶g̶ fetching them all!
- Offering multiple user profiles, allowing multiple users' statistics to be tracked simultaneously
As it stands, the application currently has the core functionality that I wanted as a baseline for the project, and is built in a modular way that should allow me to add new features relatively easily. Let's talk about how it works!
On the user's initial page load, an event listener waits for the page's DOM content to load, and then triggers an initial asynchronous function that uses a fetch request in combination with a random number generator to pull a random Pokemon from the PokeAPI. Once the Pokemon's data is returned, a card is generated displaying the Pokemon's name and picture, and a function is called that builds a dynamic color background for the picture based on the type(s) of said Pokemon. Two buttons are generated and appended to the card as well, along with their respective event listeners- one allowing the user to attempt to catch the Pokemon, and another allowing them to run away and try their luck with another random Pokemon.
The user's Pokedex is also generated, but unless there is stored JSON data in the local server database, it will remain empty. Let's break the card functionality down a bit.
PokeAPI returns an absolutely huge amount of data for each Pokemon. It's absolutely great for planning out extended features, but the prospect of fetching and then storing all of that data repeatedly is a bit terrifying. To simplify things, the app fetches the data from the server, and then runs it through a handler function that extracts and returns the data necessary for the application. Once the data is returned from the handler, it's sent to a function that generates a boilerplate card, and then populates the card with a name, picture, background colors, and the aforementioned catch and run buttons. A function then clears the existing wild Pokemon card from the DOM container, after which the new card is appended to the DOM.
A word on the background colors on the card pictures- they may look like they're part of the picture linked by the PokeAPI, but they're actually dynamically generated for each card depending on what Pokemon types are listed in the JSON data that the API returns. For Pokemon types, the data returned comes in an array, and isn't limited to a single type, which means that each type in the array has to be accounted for in generating a background. The function to handle this contains an object that stores each possible individual Pokemon type as a key, and the corresponding hexadecimal color (pulled from Bulbapedia using a color picker) as that respective key's value. The function takes the array of types from the API data as an argument, and uses the .map method to build a new array of hexadecimal color codes in place of types. That array is then converted to a string using the .join method, and returned in the form of a CSS property, linear-gradient, that will create a pretty gradient of all of the colors in the array. The function in charge of constructing the Pokemon's card then uses the .style attribute to change the background-image property of the image to the gradient generated by the function. It's a fairly simple function, but I feel like it's one feature that really classed up the look of the application.
At this point, the user has a wild Pokemon displayed on the screen, an empty Pokedex, and two options, to either throw a PokeBall, or to run away. They want to start filling that Pokedex up, but let's start by covering the run away option, in case the Pokemon that was pulled doesn't fit their tastes. The event listener for the run away button simply calls the same function that's automatically called on the application's initial load, repeating the prior steps of generating a random number, using it to fetch a Pokemon from the API, and then creating and displaying a card for that Pokemon. Simple, right?
Now on to the actual point of the app, catching Pokemon. The user sees a Pokemon they like, they smash that Throw A PokeBall button, and... they get an alert message saying that the Pokemon got away! That's because there's a random chance that you'll fail to catch a Pokemon, triggered through a simple function that generates a random number between 0 and 1, and then compares that to a default catch chance. This function was implemented as the first part of the player experience extended feature I mentioned above, and will eventually have another function that tracks experience plugged into it, modifying the catch chance dynamically based on a user's experience level. At the moment, it's just a static value, but it nonetheless means that the user won't catch every Pokemon they toss a PokeBall at.
If they're not caught out by the random number generator, their throw will be successful, and the Pokemon will be caught! Yay! This is where the local server and data persistence goals of the project come in. Once a Pokemon is caught, its respective object is immediately sent to the local server through a POST request. Once that data is stored, we once again loop back to the initial page load function, but this time, some additional functionality built into that function is triggered. On every page refresh, or anytime the initial page load function is called, a GET request is made to the local server. If the local server doesn't have any Pokemon data, then the Pokedex remains empty, and the user will see the same functionality as when they open the page for the first time. However, in this case, we have a Pokemon stored on the local server, and this triggers a function that passes the local server's JSON data to another card creation function. This function builds a card much in the same way as the function mentioned above, but instead of "Throw a PokeBall!" and "Run Away!" buttons, the card has "Rename" and "Release" options. Once the card is constructed, it's appended to the DOM, and any further Pokemon objects stored on the local server are iterated over as well to create a PokeDex showing the user's Pokemon collection.
Alright, so the user now has the Eeveelution of their dreams, but they want to rename them. That functionality is built into the card that was appended to the Pokedex earlier through the "Rename" button. On clicking the button, the user is presented with a pop up window asking for a nickname for their Pokemon. The functionality at this point is a bit wonky, as the local server template I'm working with only allowed Pokemon objects to be accessed via an ID number that was sequentially generated when the Pokemon was POSTed to the server. Because of this, the rename function first GETs the data for every Pokemon in the Pokedex from the local server, and then iterates through them, searching for the object and ID associated with the old name of their Pokemon. That ID is stored, and then used in a PATCH request that references that specific ID, and then updates the corresponding object with the new nickname entered by the user. The name on the respective card is then updated, without having to regenerate the entire Pokedex.
The user has now braved the random number generator, run away from a Pokemon, caught another, renamed them, and now has a Pokemon in their Pokedex that they can show off to the world. Perfect! But let's say they catch a second Pokemon, and the two Pokemon just don't get along. How could they rectify this situation? Easily! The "Release" button!
When the user clicks the "Release" button, similar functionality to the "Rename" button is triggered, starting with all of the Pokemon JSON data from the local server being pulled with a GET request. Once that data is in hand, the function searches for the relevant object's ID, just as described above. Once it has that ID, a DELETE request is sent to the local server to remove that Pokemon from the stored Pokedex. The user is presented with a pop up confirming that the Pokemon has been released, and the relevant card is removed from the Pokedex. Lastly, a function checks if the Pokedex is currently empty, and if it is, updates it with a message instead of leaving it as a blank screen.
That's the basic functionality of the app covered. I'll have a separate blog post covering some of what I learned in greater detail while building this app, but I've already identified some areas that could be improved. First, the application is mostly statically sized. I simply didn't have the time to look deeply into responsive design, and to make the app look good on multiple devices. It's one of the key things I've taken away from this project, and will be one of my focuses going forward. Second, my time for this was 95% devoted to functionality. While my interest mostly lies in the code and not graphic design, this app made me want to learn more about combining function and aesthetics, instead of using generic styles and making it look somewhat acceptable. Front-end design will definitely be another topic I'll be spending time looking into. Third, this entire app was built before our cohort started lessons on context and the "this" keyword, and I can already see multiple opportunities to refactor the code using those lessons. Fourth, and finally, I feel like I need to work more on my understanding of how to best use asynchronous code. I came out of this project feeling like I knew how to make it work, but not like I really deeply understood why it was working, or how I could leverage asynchronicity to improve the performance and user experience of my applications. That'll definitely be a further focus in the weeks ahead.
That wraps up my companion post for my first web application! Check out the repository if you want to read over the code, and if you have any advice, I'm always open to hearing how I can improve. Thanks for reading!