IB MYP5 Personal Project Process Journal
Product Creation
Task: Environment Initialization
Image 2.1: Creation of directory &
Installation of modules
The aforementioned screenshot (Image 2.1) shows the process of creating the “app” folder in which the app’s files will be
stored, I then open that directory and install the necessary modules for setting up the application. The next step is to create a
stable and functioning backend. Within the app folder, I created a file named “[Link]” which will be the primary file of the
backend, determining the logic of the routes and API requests. created a file named “.env” to store environmental variables
or extremely valuable data to hide it from attackers, and a file named “.gitignore” to ignore the “.env” file I had just created
when dealing with Git.
Task: Backend Setup
The following screenshot (Image 2.2) displays
a minimal setup of the backend, showing the Image 2.2: Minimal Backend Setup
configuration of reading the .env file, setting
the view engine of the app to EJS (Embedded
JavaScript), setting a views directory where the
frontend files will be and setting a public
directory where assets such as fonts and
images will be, creating the primary route of
the application which renders the main page,
and running the server.
Task: Interactive Map Visualization
After this, the next primary task was to create the main page on the frontend and display an interactive map of the world
without any environmental factors. The HTML & JavaScript setup was quite simple, the page was split up into a container
which wrapped 2 components: the sidebar and the map. As previously mentioned, the application would use a sidebar
layout, wherein the sidebar would take
around 25% of the page and the map would
take 75% of the page. This was taken as
inspiration from EnviroAtlas, the product that Image 2.3: Interactive Map Visualization
was analyzed in A2.
Within the script (Image 2.3), I am importing
[Link] and adding an event for when the
page has loaded, and I am creating a map
without zoom controls (to ensure that I can
later add zoom controls at the bottom right of
the map and customize the appearance of the
map). I then am using a free map tiling
available at OpenStreetMap.
The CSS logic of the sidebar layout can be seen in the following image (Image 2.4), in which I am using CSS variables to make
switching of themes for the user accessible, since all I would have to do is
change the variables and it would by extension change the look of the
entire page. As you can see, the sidebar takes 25% of the width, and the
map occupies 100% of the viewport height of the user’s device initially
and floats towards right, meaning it would be on the right side. Another
detail is that overflow-x is hidden, ensuring that a horizontal scroll bar will Image 2.4: Styling
be hidden if the width of the map increases or decreases through the of body, container,
and sidebar
zoom features.
The next step in the product’s creation is to add an option to turn the
map full-screen and ensure it occupies 100% of both the user’s width and
height. To do this, I need to create a div for controlling the full screen
option, give it an icon and hide and show this div based on the status of
the full screen (show the full screen button when the user is not on full
screen, and hide it when the ser is on full screen).
As can be seen in Image 2.5, when the user exits full screen, the button’s
display is set to block, and when the user is on full screen, the button’s
display is set to none, meaning it is hidden. The full-screen button was
also styled to have a circular shape, a white background with adequate padding, and absolute positioning to set it above the
controls for the zoom.
Image 2.5: Addition
of full-screen option
The next step is allowing input from the user, and allowing the user to select a city or country through an input bar. This
process is known as geosearch. The HTML update for the same was not difficult, since all I had to do was create an absolutely
positioned input bar within the map, since despite it having an absolute position, its position will always be relative to its
parent divisions (known as div in
Image 2.7: Creation
HTML), which in this case would be
of input bar
the map. I also created an unordered
list below it, which would encompass
the suggestions that the app would
display to the user as the user
searches for a place.
In the aforementioned snippet
(Image 2.7), I have linked the input with the function
findLocation, which will determine whether to show or hide
search options. Currently, the unordered list will be empty, since
I cannot manually update each city or country in the world, I will
later need to populate it with data as soon as the page initializes.
The findLocation function can be seen in Image 2.8.
Image 2.8:
I defined several variables in the beginning of the function and findLocation
checked whether the user had typed more than 3 letters, if not function
then I determined that it would be too soon to infer what the
user may be typing, so it would be wise to hide the entire
unordered list. If the user had typed more than 3 characters, I
looped through each list item and checked whether the content
of any list item was included in the user’s search and displayed
them.
The styling for the input bar and unordered list were as follows
(Image 2.9 and Image 2.10
respectively). I continued
using the CSS variables, since Image 2.9:
that will be useful later on, Input Bar CSS
when I add the functionality Image 2.10:
to change the theme of the Unordered List
CSS
application.
The next goal, for greater
clarity, is that instead of
displaying nothing on the
screen if the user has not
found any options available,
there should be a message
sent to the user telling them
that they have entered a
search term that does not
match any results. I can check whether the user found any matching
search queries by keeping track of them and adding a list item that tells the user that they have not found any options. I also
wanted to exclude the styles of common list items to the list item that is created when the user does not have any options,
since that is not truly a list item. Image 2.11
displays these styles.
The next task is to populate the list, since
currently the list is empty, and despite what
the user searches, there are no matches.
We can do this by using the Countries & Image 2.12: Populating
Cities API call a request that returns each list of cities or
country of the world and all of its cities. The GeoSearch
following asynchronous function uses the
APIs to make a request and retrieves the required data. (Image 2.12)
It uses DOM (Document Object Model) to loop through each country, convert the country’s name from “india“ to “India” by
capitalizing the first letter for formality and punctuation. As it loops through each city of each country, it sets the text content
of the list item to the city name and then the country name, making it appear as “Delhi, India” or “Berlin, Germany”. This is to
ensure that if the user searches “India” in the search box, they will receive a plethora of options of cities to choose from.
While the application takes half a second more to load, due to the sheer number of cities in the world, the search bar can
now take any city as input. The next primary task of the
application is to respond to the user’s selection of the
input, meaning after the user clicks on the list item as the
city they would like to select, the application should
display that city on the map and start fetching
environmental data for that city. Rather than looping
through each list item and adding an independent event
listener for each of them, and responding to individual
Image 2.13: Storing the
clicks, logically I need to track clicks of the user on the
selected city in a variable
unordered list, since all the list items are in the unordered
list. The following image (Image 2.13) displays storing the
selected city of the user in a variable.
After finding the city that was selected, the next step is to
clear the input bar of what the user has just typed and
hide all the list items seamlessly. To display the location of
the city that the user has chosen, I would need to
find a way to convert the address to coordinates.
Image 2.14: Installing &
There are a plethora of APIs that allow for “reverse
importing the module.
geocoding”, or the process of converting an address
to coordinates, but I chose OpenCage,
Considering that this API required an API key, which
is information that is supposed to be kept personal
to each user of the API, and since this information should not
be revealed or exposed in the frontend, I would need to
create a POST request in the backend and call it in the Image 2.15: API request
for reverse geocoding
frontend to perform this operation of reverse geocoding.
The first step was to install a new module and import it in the
code, which can be seen in Image 2.14.
After this, I had to call this to find the coordinates of the user,
and then use the function [Link] to take the user to
their desired location on the map, which would not be
difficult considering the API fetches the coordinates and
returns them to the backend, which then returns them to the
frontend through the POST request. The primary reason why this request was a POST request instead of a GET request is
because GET requests reveal sensitive information in the URL itself, can be accessed by users of the web, whereas POST
requests cannot just be visited or accessed, since users will need to pass in headers or variables in the request’s body for it to
function. As we can see in this post request (Image 2.15), we are extracting the query, or the location which the user has
selected from the request’s body, and in the [Link] function the module reads the API key from the .env file and
that is the only reason why the request functions and the data is retrieved.
As we can see (Image 2.16), the application is fetching the
/geocode route that we just created in the backend, passing Image 2.16:
in the query as the city selected by the user, retrieves the Reverse
latitude and longitude, and proceeds to use the [Link] geocoding &
function. The second argument, in this case 8, represents the setting view
amount of zoom the user has. Notice how, in Image 2.3 (the
initialization of the map), we set the view of the user to (45,
0) which would be the coordinates to see the entirety of the
world map, and there the number was 2, which indicated that
by default users would see a zoomed-out version of the map.
Here, the number 8 indicates a higher zoom, which is crucial
to viewing the coordinates, since it allows the user to see the
city at a narrower scope, snice they searched for that city
specifically.
The next step would undoubtedly be to start working on the
environmental factors, however we also need to keep in mind
the aesthetics of the application, and with that in mind we
should enable users to change the theme of the application
and set a font for the application. I chose the font DM Sans,
since it is a sans-serif font, and it is big, bold, and easy to read. Image 2.17a
As for changing the theme of the application, since I used CSS
variables since the beginning of the creation of the product, all
I must do is change the value of these variables upon the click
of a button. That can be demonstrated in the following
screenshots (Image 2.17a, 2.17b, 2.17c, 2.17d).
Image 2.17b
Before delving into creating the most important part of the
application, which is showing the environmental factors, for an Image 2.17c
enhanced user experience, it is essential to add text to the sidebar
and status indicators for each environmental factor, and allowing
the user to select which environmental factors they would like to
select.
I did this by adding an initialization message in the sidebar, telling
the user how to use the application as it was intended. I then
added to the toggle-menu that our button was in as in seen in
Image 2.17b, within this I added 3 labels and inputs, in the form
of a checkbox and a slider, which I would need to style further.
By default, I checked all 3 environmental factors, since I want to
provide as much data as possible to users, and if they do not Image 2.17d
want data from one environmental factor, then that is their
prerogative, and they can always disable it at a later stage. (this
was done in Image 2.18a)
In the last line of Image 2.18a, one can see a new
division being created with the ID set to “results”,
this is where, after the user selects their location or
city in the world, we will be displaying the
magnitude of the environmental factors that were Image 2.18a
checked. It is a new division since I wanted to add
spacing in-between the settings, and the results or
the outcome of those settings. CSS was primarily
used to create the animation as the user clicks on
the input (Image 2.18b), and I was able to create it
because the input bar had type set to checkbox. I
used a flex styled layout and absolutely positioned
the slider.
Now that the checkboxes had been created, I
needed to start where I had left the project last,
setting the view of the user. After setting the view
of the user onto the city they selected, I had to
check which checkboxes does the user have
enabled, and for each one that is enabled, fetch
them through an API.
Programming this feature of detecting whether the
inputs have been enabled would be easy
due to JavaScript’s property “checked” Image 2.18b
which is a Boolean. This can be seen in
Image 2.19. Here, the variable
“checkboxes” is an array, and in JavaScript,
like in many other programming
languages, the 1st element of the array
has the index 0, meaning that
checkboxes[0] refers to the temperature &
climate option.
Image 2.19
As the cases of the user enabling each of
these environmental factors have been
defined in the code, the next step is to
create backend requests that fetch these
environmental factors, fetching them from
the frontend, and displaying the results in
the “results-container” division that was
defined in Image 2.18a.
The first environmental factor we can
tackle is AQI levels, since there are several
APIs that return the AQI levels of locations.
As mentioned in the design specifications, I
am using the OpenWeatherMap API for this. The backend request can be seen in Image 2.20, one can see how the API key is
fetched from the .env file as sensitive
information is stored there. For this request, I
installed the NodeJS module “axios” and
imported it, which can be thought of as a
module that fetches both GET and POST
requests from the backend.
Considering that I have already stored the
latitude and longitude of the desired location of
the user in the frontend, and those are the only Image 2.20
2 arguments for this request, calling this
request would be similar to calling the
/geocode request, since it all entails is passing
variables with code and displaying the output.
However, before I pass the request, I have
decided that the best way to present this data
to the user is with the use of the <details> and
<summary> tag in HTML. Essentially, the combination of <details> and <summary> will create an accordion by default, that
the user can toggle on and off. This will allow the user to show and hide data as they search for multiple environmental
factors across multiple
locations, enhancing their
user experience. Image 2.21
As we can see in Image
2.21, in the process of
displaying the data,
several new functions are
called such as
“initializeDetails”,
“describeAQI”, and
“describeCO2”. Another
fallacy is that a displayDiv
element has been fetched
but was never created.
However, these other
actions have happened as
a process of calling the
functions. We are calling
the initializeDetails
function to ensure the overarching accordion has been
created, so with each city that the user chooses, one large
accordion is created, and within this accordion all the
environmental factors data will be stored. If the user has not
checked the first checkbox, then this accordion wouldn’t exist,
therefore we need a function to check whether this accordion
exists and if it doesn’t then it needs to be created. The
functions for describing environmental factors convert the
numerical responses from the API into strings which give a
qualitative representation of the quantitative data, for
instance it describes what is an exceptional AQI and CO2
level.
The initializeDetails function is quite standard and functions as it was described. For reference, however, it has
Image
been shown in Image 2.22 with guiding comments as to what is happening. The next steps in product 2.22
development are to do the same for each environmental factor and display data in the same manner and call
the same functions (initializeDetails). The only code that differs is the code for getting the request, which has another API
linked to it and the variables to display.
While I could document the process of adding
the other environmental factors, the process is
extremely repetitive and similar to adding AQI
levels. Therefore, I will show the product’s
aesthetics and appearance after adding both
UV intensity and temperature and climate
patterns. This can be seen in Image 2.23.
While the primary function of the product has Image
been completed, this does not mean that other 2.23
improvements cannot be added. I will still have
to add: 2 more environmental factors, the alert system that will help users stay updated about trends with environmental
factors, by extension authentication system to inform users specifically about such events and features to clear the data
received on the sidebar. On the theme of aesthetics, more user themes can be added and additionally the feature to open
and close the sidebar, mobile support should be added too. For security, I will still have to add security headers and a privacy
and cookie policy.
While the primary function of the product has been completed, this does not mean that other improvements cannot be
added. I will still have to add: 2 more environmental factors, the alert system that will help users stay updated about trends
with environmental factors, by extension authentication system to inform users specifically about such events and features to
clear the data received on the sidebar. On the theme of aesthetics, more user themes can be added and additionally the
feature to open and close the sidebar, mobile support should be added too. For security, I will still have to add security
headers and a privacy and cookie policy.
Starting with the privacy and cookie policy, I needed to create a menu
that would appear and close when the user accepts the policy.
Another primary feature of this menu is that before the user has
accepted the policy, or while the menu is being displayed, none of the
other features, buttons, or elements of the application should be
Image 2.24a
responsive. For this, we can create basic HTML as shown in Image
2.24a.
The next step is to write basic JavaScript code
which checks whether the user has accepted the
Image 2.24b
privacy policy or not, if they have not then we can
display the consent banner element. If the user
happens to click the button which says “I Agree”
and agrees to the privacy policy, then we can
change the value of consent in the database, and
remove the banner and the overlay element
entirely and proceed with the other code. This
entire process can be seen in Image 2.24b and the
localStorage part has been annotated. Essentially,
localStorage is where one stores cookies and data
which is directly related and dependent on the
browser the user is using. Meaning, if the user
opened the map on an incognito tab, there would
be no previous data.
Lastly, we must make sure that the overlay element functions properly. To do this, we can simply set its z-index higher than all
other elements except for the banner, meaning the only accessible element will be the banner, which fulfills the requirement
of making the banner the only functional element on the page. Once the user has accepted the terms as seen before in
JavaScript, the overlay element is deleted. The background of the overlay element ensures that it also acts as a translucent
filter, adding 50% black to every other element, which makes it darker than it should be, which gives off the effect that the
elements are disabled. This urges the user to read and accept the privacy policy. This setup in CSS can be seen in Image 2.24c.
To program the alert system, I would need to create a database, or have a
method to communicate with the user via the user’s details. As for the database,
I have chosen SQLite3 for several reasons such as the fact that it does not
require a server to setup, operates with zero configuration, it is lightweight and
extremely fast, which is necessary considering that large amounts of data will be
fetched time and time again from the frontend, and to deliver data quickly I
would need to use a database that is known for its speed. Additionally, it also
has a minimal footprint, allowing the application to be deployed over various
environments, including mobile devices. It’s also well-suited to handle user
Image 2.24c
preferences and is adequate for user alerts. Therefore, I installed sqlite3 in my
app and created the database as shown in Image 2.25.
The table includes the following columns: the ID is essential to fetch the row, the
email of the user is essential for emailing them when the factor reaches the
threshold, the latitude and longitude to locate the location, the factor that they
are looking to check for, the threshold or the magnitude of the factor
they want to be alerted for, and the threshold condition which defines
whether they want it to be less than or greater than the threshold.
This is important because if a user wants to be alerted for 45 degrees
Celsius in New Delhi, and if the temperature rises to 47 degrees and if
the application checks for whether the temperature is equal to the
user’s threshold the application will return false, meaning that we
require the threshold condition variable for a higher accuracy.
Image 2.25
After creating the database, I have to allow a method for the user to
populate the database via the frontend. Creating a form in the
frontend is easy, however I would require a POST request to be called
from the frontend which adds the row in the backend. I created this
POST request, validated the arguments, validated that the email
address of the user is real, validated the latitude and Image 2.26
longitude led to a location, and then inserted the row
of data into the database. (POST request shown in
Image 2.26)
Image 2.27 displays the Vanilla JavaScript calling the
request, and on success creating a success message,
notifying the user that the request has gone through,
and that the addition has been made to the
Image 2.27
database. It also shows the application clearing the input fields after the submission of the form.
Storing the alerts in the
database wouldn’t mean
anything unless these alerts
were acted upon. To truly make
Image 2.28
the alert system work, I would
need to continuously call a
function every hour which
looped through each row of the
database, using the APIs to
check whether the factors
crossed the threshold, if yes,
then we send the user an email.
To combat this problem, I
looped through each row in the
DB, checked the environmental factors,
fetched the data using the same APIs, checked
whether the threshold has been reached, and
if it has then call a function to send them an
email. (as seen in Image 2.28) Image 2.29
The sendAlertEmail function takes in all the
parameters from the database, uses nodemailer’s transporter options to send an email to the user stating that the
environmental factor in their location has reached the threshold they requested for. Additionally, to ensure that the user does
not receive 100 emails (for instance, if the user wants alerts for >50 degrees Celsius in Gurgaon, India, and the temperature
gradually rises from 51 to 58, then the condition would be true 8 times), however we would only want to send them the
email once. To solve this, I deleted the alert from the database. (as seen in Image 2.29)