Skip to content

Week 0: Fundamentals of JavaScript#1

Open
github-learning-lab[bot] wants to merge 4 commits intomasterfrom
bitcamp
Open

Week 0: Fundamentals of JavaScript#1
github-learning-lab[bot] wants to merge 4 commits intomasterfrom
bitcamp

Conversation

@github-learning-lab
Copy link

Prerequisite: Fundamentals of JavaScript

Welcome to Bit Camp, where students get hands-on experience with software engineering. Before the program starts, please find some time to complete the following assignment: learn/review JavaScript fundamentals that will later be used in the program. To complete this assignment, you will be creating a digital clock using JavaScript to demonstrate your skills! If you need any help, refer to these resources:

W3Schools: https://www.w3schools.com/js/default.asp

Tutorialspoint: https://www.tutorialspoint.com/javascript/javascript_overview.htm

Modern JS: https://javascript.info/

If you have specific questions, try out Stack Overflow (it is super duper helpful):

Stack Overflow: https://stackoverflow.com/

After you have learned/refreshed your knowledge of JavaScript, let's get started on making that clock. Here is the list of the requirements and some extensions you can complete if you have extra time!

Requirements:

  • Display 12/24 HR time with hours, minutes, and seconds.
  • Must be created using JavaScript (plus appropriate HTML and CSS for formatting and styling)

Extensions:

  • Turn your clock from digital to analog mode (hint: you're gonna need a lot of CSS for this...)
  • Use CSS to style your clock (ie: make it blue, green, yellow, etc)
  • Create animations for your clock (ie. when the minute hand changes, it rotates/spins)

Don't know where to get started? Start off by creating a new function that starts your clock:

function startTime() {}

After this, you will need to create three variables:

var hr = " ";
var min = " ";
var sec = " ";

Also, make sure to reference the HTML you created the "clock" id in:

document.getElementById("clock").innerHTML = " ";

Good job, hopefully your digital clock is fully working and your jQuery is linked to our HTML and CSS files. Your next task is a bit more tricky... you're making another clock but this time... in analog mode!

If you have any other questions or need further assistance, feel free to reach out to your TAs!

@aschinkmann
Copy link
Owner

this is very cool.

@github-learning-lab
Copy link
Author

Downloading an IDE

For this assignment, you will follow the instructions and complete a task showing your knowledge of the subject at the end. If at any moment you need help, feel free to contact your TAs.

Visual Studio Code

Before we start coding, we need to install an IDE. An IDE is a software application that provides comprehensive facilities to computer programmers for software development. An IDE normally consists of at least a source code editor, build automation tools, and a debugger. Although there are hundreds of IDEs to choose from, we are going to use Visual Studio Code due to its popularity and integration with Azure (via extensions and libraries).

To install VSC, go to: https://code.visualstudio.com/download and choose your operating system (ie. Windows, Mac, Linux, etc). Then click Download and run the installer (usually a .exe or .zip). After it's installed, open it up and try it out. If you need some help navigating VSC, check out this super helpful Youtube video.

Make sure to use Dark Theme unless you want to live life on the edge...

Task 1: create a JavaScript file that prints out "Hello World" in the terminal.

@aschinkmann
Copy link
Owner

beau cool.

@github-learning-lab
Copy link
Author

Configuring Azure

For this assignment, you will follow the instructions and complete a task showing your knowledge of the subject at the end. If at any moment you need help, feel free to contact your TAs.

Azure

So what is "Azure"? Well if you made it into Bit Camp, you really should know already... but according to Microsoft, "Azure is an ever-expanding set of cloud services to help your organization meet your business challenges. It’s the freedom to build, manage, and deploy applications on a massive, global network using your favorite tools and frameworks." If you want to learn more about Azure and all its cloud applications, feel free to check out this link, an overview of its capabilities.

To create an Azure account, go to: https://azure.microsoft.com/en-us/free/ and press Start free to be relocated to a signup page. After signing in with your Microsoft account and filling in your personal details, you will be asked to add a credit card. Rest assured, this is only for security purposes (preventing multiple free accounts per person), and you won't be charged unless you choose to buy a premium account, which we do not need for this course. If you need some help navigating Azure, check out this super helpful resource provided by Microsoft.

Task 2: create and deploy an HTTP trigger Azure Function that outputs the current time and date.

@aschinkmann
Copy link
Owner

this course is cool.

@github-learning-lab
Copy link
Author

Learning GitHub

For this assignment, you will follow the instructions and complete a task showing your knowledge of the subject at the end. If at any moment you need help, feel free to contact your TAs.

GitHub

If you haven't heard of GitHub before, you must have been living under a rock for the past few years. GitHub is a cloud-based repository hosting service that is widely used in the tech industry. It allows teams to use Git for version control, collaboration, and file management. If you don't know what Git is, it's a version-control system for tracking changes, managing state, and concurrently developing on the same files or directories. Git and Github's tools are specifically designed to make coordinating work easier, and they are one of the most pervasive shared tools among students and the industry.

If you want to learn more about what it is and how to use it, try taking this GitHub Learning Lab Course. After finishing it, you will have a strong understanding of all the features GitHub has to offer. To make an account, go to: https://github.com/join to sign up. After making an account, you're all set to complete Task 3!

Task 3: create a repository and commit a README.md file (make sure you know key functions like commits, forking, pull request, branch, etc).

Good job, you're done with all three parts!

@aschinkmann
Copy link
Owner

scooby doo

@github-learning-lab
Copy link
Author

Learning GitHub

For this assignment, you will follow the instructions and complete a task showing your knowledge of the subject at the end. If at any moment you need help, feel free to contact your TAs.

GitHub

If you haven't heard of GitHub before, you must have been living under a rock for the past few years. GitHub is a cloud-based repository hosting service that is widely used in the tech industry. It allows teams to use Git for version control, collaboration, and file management. If you don't know what Git is, it's a version-control system for tracking changes, managing state, and concurrently developing on the same files or directories. Git and Github's tools are specifically designed to make coordinating work easier, and they are one of the most pervasive shared tools among students and the industry.

If you want to learn more about what it is and how to use it, try taking this GitHub Learning Lab Course. After finishing it, you will have a strong understanding of all the features GitHub has to offer. To make an account, go to: https://github.com/join to sign up. After making an account, you're all set to complete Task 3!

Task 3: create a repository and commit a README.md file (make sure you know key functions like commits, forking, pull request, branch, etc).

Good job, you're done with all three parts!

@aschinkmann
Copy link
Owner

scumpy dump

@github-learning-lab
Copy link
Author

Create a Face API Endpoint

This step is fairly straightforward:

  1. log into your Azure portal
  2. press Create a Resource
  3. press the AI + Machine Learning tab on the left
  4. press Face and fill out the necessary information

Record and save the API endpoint and subscription key, as we'll be using it in the following parts.

@aschinkmann
Copy link
Owner

more!?

@github-learning-lab
Copy link
Author

Call the Face API P1: Setting Params

At this point, your Azure function should look like this:

var multipart = require("parse-multipart");
  
module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.'); 

    var boundary = multipart.getBoundary(req.headers['content-type']);
    
    var body = req.body;
  
    var parts = multipart.Parse(body, boundary);
};


We're going to create a new function, outside of module.exports that will handle analyzing the image (this function is async because we will be using the await keyword with the API call). This function is called analyzeImage(img) and takes in one parameter, img, that contains the image we're trying to analyze. Inside, we have two variables involved in the call: subscriptionKey and uriBase. Substitute the necessary values with your own info.

async function analyzeImage(img){
    const subscriptionKey = '<YOUR SUBSCRIPTION KEY>';
    const uriBase = '<YOUR ENDPOINT>' + '/face/v1.0/detect';
}


Now, we want to set the parameters of our POST request and specify the exact data that we want.

The documentation for the Face API is here. Read through it, and notice that the request url is this:

https://{endpoint}/face/v1.0/detect[?returnFaceId][&returnFaceLandmarks][&returnFaceAttributes][&recognitionModel][&returnRecognitionModel][&detectionModel]


All of the bracketed sections represent possible request parameters. Read through Request Parameters section carefully. How can we specify that we want to get the emotion data?


In order to specify all of our parameters easily, we're going to create a new URLSearchParams object. Here's the object declared for you. I've also already specified one parameter, returnFaceId, as true to provide an example. Add in a new parameter that requests emotion.

let params = new URLSearchParams({
	'returnFaceId': 'true',
	'<PARAMETER NAME>': '<PARAMETER VALUE>'     //FILL IN THIS LINE
})

@aschinkmann
Copy link
Owner

this is unbelievable!

@github-learning-lab
Copy link
Author

Call the Face API P2: Using Fetch

There are many ways to make a POST request, but to stay consistent, we're going to use the package node-fetch. This package makes HTTP requests in a similar format as what we're going to use for the rest of the project. Install the package using the same format we did for parse-multipart .

//install the node-fetch pacakge
var fetch = '<CODE HERE>'


Read through the API section of the documentation. Link here. We're going to make a call using the fetch(url, {options}) function.


We're calling the fetch function- notice the await keyword, which we need because fetch returns a Promise(a promise is a proxy for a value that isn't currently known). Read about Javascript promises here. I've set the url for you- notice that it is just the uriBase with the params we specified earlier appended on.


For now, fill in the method and body.

async function analyzeImage(img){
    
    const subscriptionKey = '<YOUR SUBSCRIPTION KEY>';
    const uriBase = '<YOUR ENDPOINT>' + '/face/v1.0/detect';

    let params = new URLSearchParams({
        'returnFaceId': 'true',
        'returnFaceAttributes': 'emotion'
    })

    
    //COMPLETE THE CODE
    let resp = await fetch(uriBase + '?' + params.toString(), {
        method: '<METHOD>',  //WHAT TYPE OF REQUEST?
        body: '<BODY>',  //WHAT ARE WE SENDING TO THE API?
        headers: {
            '<HEADER NAME>': '<HEADER VALUE>'  //do this in the next section
        }
    })

    let data = await resp.json();
    
    return data; 
}


Finally, we have to specify the request headers. Go back the the Face API documentation here, and find the Request headers section. There are two headers that you need. I've provided the format below. Enter in the two header names and their two corresponding values.


FYI: The Content-Type header should be set to'application/octet-stream'. This specifies a binary file.

    //COMPLETE THE CODE
    let resp = await fetch(uriBase + '?' + params.toString(), {
        method: '<METHOD>',  //WHAT TYPE OF REQUEST?
        body: '<BODY>',  //WHAT ARE WE SENDING TO THE API?
      
      	//ADD YOUR TWO HEADERS HERE
        headers: {
            '<HEADER NAME>': '<HEADER VALUE>'
        }
    })


Lastly, we want to call the analyzeImage function in module.exports. Add the code below into module.exports.


Remember that parts represents the parsed multipart form data. It is an array of parts, each one described by a filename, a type and a data. Since we only sent one file, it is stored in index 0, and we want the data property to access the binary file– hence parts[0].data. Then in the HTTP response of our Azure function, we store the result of the API call.

//module.exports function

//analyze the image
var result = await analyzeImage(parts[0].data);

context.res = {
	body: {
		result
	}
};

console.log(result)
context.done(); 

@aschinkmann
Copy link
Owner

incredible!!!!!

@github-learning-lab
Copy link
Author

Call the Face API P2: Using Fetch

There are many ways to make a POST request, but to stay consistent, we're going to use the package node-fetch. This package makes HTTP requests in a similar format as what we're going to use for the rest of the project. Install the package using the same format we did for parse-multipart .

//install the node-fetch pacakge
var fetch = '<CODE HERE>'


Read through the API section of the documentation. Link here. We're going to make a call using the fetch(url, {options}) function.


We're calling the fetch function- notice the await keyword, which we need because fetch returns a Promise(a promise is a proxy for a value that isn't currently known). Read about Javascript promises here. I've set the url for you- notice that it is just the uriBase with the params we specified earlier appended on.


For now, fill in the method and body.

async function analyzeImage(img){
    
    const subscriptionKey = '<YOUR SUBSCRIPTION KEY>';
    const uriBase = '<YOUR ENDPOINT>' + '/face/v1.0/detect';

    let params = new URLSearchParams({
        'returnFaceId': 'true',
        'returnFaceAttributes': 'emotion'
    })

    
    //COMPLETE THE CODE
    let resp = await fetch(uriBase + '?' + params.toString(), {
        method: '<METHOD>',  //WHAT TYPE OF REQUEST?
        body: '<BODY>',  //WHAT ARE WE SENDING TO THE API?
        headers: {
            '<HEADER NAME>': '<HEADER VALUE>'  //do this in the next section
        }
    })

    let data = await resp.json();
    
    return data; 
}


Finally, we have to specify the request headers. Go back the the Face API documentation here, and find the Request headers section. There are two headers that you need. I've provided the format below. Enter in the two header names and their two corresponding values.


FYI: The Content-Type header should be set to'application/octet-stream'. This specifies a binary file.

    //COMPLETE THE CODE
    let resp = await fetch(uriBase + '?' + params.toString(), {
        method: '<METHOD>',  //WHAT TYPE OF REQUEST?
        body: '<BODY>',  //WHAT ARE WE SENDING TO THE API?
      
      	//ADD YOUR TWO HEADERS HERE
        headers: {
            '<HEADER NAME>': '<HEADER VALUE>'
        }
    })


Lastly, we want to call the analyzeImage function in module.exports. Add the code below into module.exports.


Remember that parts represents the parsed multipart form data. It is an array of parts, each one described by a filename, a type and a data. Since we only sent one file, it is stored in index 0, and we want the data property to access the binary file– hence parts[0].data. Then in the HTTP response of our Azure function, we store the result of the API call.

//module.exports function

//analyze the image
var result = await analyzeImage(parts[0].data);

context.res = {
	body: {
		result
	}
};

console.log(result)
context.done(); 

@aschinkmann
Copy link
Owner

this site is so cool !

@github-learning-lab
Copy link
Author

Creating an HTML Page

After watching the live demo, you should know the basics of how to create a simple website using the coding language, HTML, and some CSS if you want your webpage to look fancy. Now, your task is to create your own HTML page that inputs an image using a <form> and outputs the image's emotion data and the user's recommended song.


If you still need some help learning HTML and CSS, checkout these resources:


W3Schools (HTML): https://www.w3schools.com/html/default.asp

W3Schools (CSS): https://www.w3schools.com/css/default.asp


Here's a list of HTML items you need to create (please use the id's specified)

  1. header element that says anything you want... mine says Example Project
  2. div element with id container that will surround all of your elements.
    1. empty div with id hidden-emotion and type hidden. This is going to hold but not display the emotion data we receive from Face API.

    2. form element with id image-form. Also specify onsubmit="handle(event)". Set the enctype attribute to multipart/form-data. <-- Remember that for forms that receive file uploads, we need to specify this type of encoding.

      • (next three elements are in the form element): input element that allows a file upload, where the user will upload an image. This link could be helpful. Set the onChange attribute to "loadFile(event)". Use the accept attribute to only allow image submissions. Finally, set the name attribute to image.

      • img element with id output- this is going to display the image that the user selects

      • button element with the type attribute set to submit. The text inside should say "Submit Picture" or something similar. This will submit the image.

    3. (Out of the form element now): empty div with the id emotion. This is where the emotion analysis results will be displayed.

    4. button with id song-button that says something like Find Song.

    5. Empty div with id song-detail. The song recommendation will be shown here.



Lastly, make sure to reference jQuery:

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
     
    <script src="config.js" type="text/javascript"></script>
    <script src="song.js" type="text/javascript"></script>
    <script src="face.js" type="text/javascript"></script>

After that, you're done with the frontend. It's time to use JavaScript!

@aschinkmann
Copy link
Owner

Youtube.com/Bahburs for fortnite!

@github-learning-lab
Copy link
Author

Displaying Emotion Data and Calculating Valence

Now that our image has been analyzed by the Face API, we have received emotion data in JSON format. Our task is to read the JSON file and do two things, output the emotions in the image (ie. anger, contempt, disgust, etc) and convert the emotions to a scale called valence. This is what we are going to use to determine whether we recommend a song that is happy, sad, angry, etc.

The first thing we need to do is create a function called loadFile(event) {} which does two things, creates a variable called image which gets the ID "output" and displays the image the user uploaded. To do this, we need to get the element ID by using document.getElementByID(id):

var image = //Call for element ID here

We also need to call for the image source using .createObjectURL(event.target.files[0]). Use .src on the image variable and call for the first file uploaded using the code above. When it's all put together, it should look something like this:

function loadFile(event) {
    var image = //set div for image output
    image.src = //Load inputted image
};

Now we need to create our main function called handle(event) {}, it will take in the response and using the data from the Face API, create a new form that includes the amount of different emotions. As well, using the data from the Face API, it will convert the numbers into valence which will be later used to determine which song(s) to recommend to the user.

  1. Using jQuery, target the output element ID and change the content to equal "Loading". Then add the line, event.preventDefault(); to disable the ability to reload the page. To event target with jQuery, use this sample:

    console.log ("submitting form...");
    $(/*Add ID here*/).html(/*Add content here*/)
    //make sure to disable reload here...
  2. After telling our HTML to show the content "Loading", we need to set a few variables to create a new form with our emotion data. We can do this by adding a variable set to an element ID and creating a new FormData based on the received information:

    var myform = document.getElementById('myform');
        var payload = new FormData(myform);
    
        const resp = await fetch(/*Add Function URL */, {
            method: 'POST',
            body: payload
    });
  3. Next we have to add a variable for the JSON data. Make a new variable called data and set it to the response JSON like we did earlier... If you need a hint, use the .json() function. We also need a couple more variables, the first called emotion which will be set to data.result[0].faceAttributes.emotion;. This basically sets emotion to the first result in our JSON data to pull the information out into a value. Lastly, we have to create the HTML that will actually be displayed:

    ​ i. First, create an <h3> tag for the title labelled emotions in this image: (make sure to add <br /> at the end to skip a line)

    ​ ii. Now create 8 <p> tags that each show data for a different emotion. The list of emotions are anger, contempt, disgust, fear, happiness, neutral, sadness, and surprise.

    ​ iii. To get the data, remember we set var emotion to pull the first value. All we have to do is use jQuery and use this formatting, I've done the first one for you:

    var resultString = `
    <h3> Emotions in the image: </h3><br />
    <p> anger: ${emotion.anger}</p>
    
    `;
    // Finish for other data types using the same format (i.e. ${emotion.contempt}, and etc)
  4. Now that we have our emotion data, we need to calculate valence. Valence is the measurement of happiness or positivity in a song. For example, if a song is sad and depressing, it will have a lower valence and vice-versa. If you want a more detailed explanation, check out this post here.

    var valence = emotion.happiness + emotion.surprise - emotion.anger - emotion.contempt - emotion.disgust - emotion.fear - emotion.sadness;

    I've given you the first few lines of code that set valence to a value from 0-1 based on the values of given emotions. If you looked closely, you would notice that emotion.neutral isn't there. What you need to do is create three conditionals that do three different things.

    ​ i. The first conditional checks if valence is less than emotion.neutral, if it is, it sets valence to 0.5.

    ​ ii. The second conditional checks whether valence is greater than 1, if it is, it sets valence to 1.

    ​ iii. The last conditional checks whether valence is less than 0, if it is, it sets valence to 0.

    These conditionals make it easier to recommend a song. Rather than having a crazy number between 0-1, it uses the data and sorts the song to either be 0, 0.5, or 1. The last thing we need to do is to use jQuery to change two divs, hidden-emotion and emotion.

    $('#emotion').html(/* Emotion Data should go here */);
    $('#hidden-emotion').html(/* Valence should go here */);

@aschinkmann
Copy link
Owner

straightforward is one word contrary to popular belief

@github-learning-lab
Copy link
Author

Overview

Login to Spotify's developer dashboard here. Then press create an app. Give it a cool name and press Create!

Click into your app and record the Client Id and Client Secret.



Before the actual programming, here's a quick overview of how the project is structured. We're going to be using the revealing module pattern in javascript, which kind of mimics classes in OOP languages like Java.


Watch this video, which gives a brief overview of this concept.



Let's take a look at an example of a module:

Notice that there are parentheses around the function– this indicates that it is an expression and should be evaluated first. So remember:

The first set of parentheses evaluate the function.

The second set of parentheses call the function.

So, the function is essentially declared and run immediately. This is called an IIFE, or immediately invoked function expression.

const car = (function(){
  var speed = 100;
  return {
  	forward() {
      speed += 1;
    }
	}
})();


Notice that because the variable speed was declared within this function, it acts as a private variable and can't be accessed outside of the function. However, returning the method forward allows us to change the value of speed indirectly. Important! All methods/variables are private unless included in the return object. This is how modules enable us to encapsulate private variables or methods and reveal other ones through returning them.

//doesn't work!
car.speed = 101;

//works
car.forward();


Also notice that the method forward does not exist globally. It must be called with a reference to car. This encapsulation is what will really make it feel object-oriented, with this example having an object-private field and an object-public method.

//doesn't work
forward();

//works
car.forward();


Essentially, just remember that modules:

  • keep all variables and methods private unless explicitly revealed(in return)
    • prevents variables from polluting global scope
  • are IIFEs- so they must be declared with function expressions and two sets of parentheses


For more reading, check out this excerpt on the revealing module pattern, as this is not a natural javascript concept if you're new to object-oriented programming.


In this project, we're going to have two separate modules that handle distinct concerns– one called APIController for handling API calls and the other called UIController for handling our HTML input fields. A third module, called APPController, will handle retrieving and displaying the data. Each module will have public methods that the APPController will call.

modules



To get started, create a new js file called Song.js . I've already declared APPController for you. Finish writing the function expressions for APIController and UIController.

//look at the above example and the APPController declaration for syntax
//both API and UI modules take in no parameters

//spotify api call
const APIController = ??

// UI Module
const UIController = ??

  
const APPController = (function(UICtrl, APICtrl) {

})(UIController, APIController);

@aschinkmann
Copy link
Owner

Christianity

@github-learning-lab
Copy link
Author

APIController

The APIController module is responsible for making calls to the Spotify API. To have our app get data from the Spotify API, we have to go through an Authorization process. Read the Client Credentials Flow section of the Spotify Authorization Guide here. This type of flow does not allow access to any user-specific data, so we can only access public data.


Inside the APIController module, we're going to write a private method called _getToken that will make the http request for a token. Note: it's common syntax for private methods to have an underscore in the beginning.


Use the information in the Authorization Guide (link again) to fill in the necessary fields. Notice also that we've declared variables for the client ID and secret that you received when you registered the app.


Hint: use clientId and clientSecret in the Authorization section of your http request.

Click for bigger hint:

Read the image!! For syntax:

'Authorization' : 'Basic ' + btoa(clientId + ':' + clientSecret)
body: 'grant_type=client_credentials'

const APIController = (function() {
    
    const clientId = config.CLIENT_ID;
    const clientSecret = config.CLIENT_SECRET;

    // private methods
    const _getToken = async () => {

        const result = await fetch('<INSERT CORRECT ENDPOINT>', {
            method: '<INSERT CORRECT METHOD>',
            headers: {
              	//content-type: how should the parameters be encoded?
                'Content-Type' : '<INSERT CONTENT TYPE>', 
              	
              	//btoa() encodes a string in base64
              	//what is the string that needs to be encoded?
                'Authorization' : 'Basic ' + btoa('<INSERT AUTH STRING>')
            },
						
          	//what request body parameters are needed?
            //the format should be a string, no spaces. ex/  'parameter_name=value'
            body: '<INSERT REQUEST PARAMETERS>'
        });

        const data = await result.json();
        return data.access_token;
    }

})();


The second private method we need is _getPlaylistItems. This is responsible for getting all the tracks from any playlist. Within the APIController, add in _getPlaylistItems.


_getPlaylistItems has three parameters

  • access token
  • playlistId- this is a unique base-62 identifier that you can find at the end of every Spotify URI for an artist, track, album, playlist, etc.
  • limit- the number of tracks you want from the playlist

Read the documentation for the getting playlist tracks endpoint here. Notice that in the documentation, there are certain Query Parameters that you can add. To add a query parameter, it needs to be in the form of a string like so:

//key-value pairs connected by &
const queryString = '{param_1_Name}={param_1_Value}&{param_2_Name}={param_2_Value}'

//then, add your queryString onto the api endpoint with a question mark before
const result = await fetch('{api_endpoint}' + '?' + queryString, {
  //blah blah stuff here
})

Using the given code, add these things:

  1. find the correct api endpoint
  2. append a queryString that sets the query parameter limit to the limit parameter that the function receives
  3. set the correct headers

Hint: For the headers, there should be one field called Authorization set to the value 'Bearer' + token


Bigger authorization hint:
  //we specify one field called 'Authorization' and set it to 'Bearer' + token
  headers: { 'Authorization' : 'Bearer ' + token}

const _getPlaylistItems = async (token, playlistId, limit) => {
 	//set the query parameter limit to this limit ^ param
  	const queryString = '<QUERY STRING>'
        
        //hint: the api endpoint should include the playlistId parameter somewhere
	//'https://api.spotify.com/v1/playlists/' + playlistId + '/tracks'
        const result = await fetch('<API ENDPOINT>' + '?' + queryString, {
            method: 'GET',
            headers: { '<HEADER_FIELD>' : '<HEADER VALUE>'}
        });

        const data = await result.json();
        return data.items;
    }

@aschinkmann
Copy link
Owner

log

@github-learning-lab
Copy link
Author

API Controller P2- Get Recommendations Params

Now for the most important function in the APIController module.

This function gets song recommendations using this Spotify endpoint. This is a critical resource, so...

READ THE WHOLE THING.

ALL THE QUERY PARAMETERS

  • which parameters are required?

READ THE TUNEABLE TRACK ATTRIBUTES

  • which one relates to the emotion/mood of the song?

TRY THE ENDPOINT USING THIS LINK






Here's some starting code for _getRecommendations. Based on the data we received from the Face API, I set a minimum and maximum valence for our recommendations.

At this point you should know that valence is a Tuneable Track Attribute that specifies how happy or sad a song is. Setting a maximum and minimum means that we can only get song recommendations that fit in the range we specify.

const _getRecommendations = async(token, seedTracks, limit) => {
    console.log('getting recommendations...')

  
  	//uses emotion data to set a minimum and max valence
    const emotion = document.querySelector('#hiddenemotion').value;
  
  	//default values
    let minValence = 0;
    let maxValence = 1;

  	//if sad, then range from 0 to 0.33
    if (emotion < .33){
        maxValence = .33;
    }
  
  	//if happy, range from .66 to 1
    else if (emotion > .66){
        minValence = .66;
    }
  
  	//if neutral, range from .33 to .66
    else{
        minValence = .33;
        maxValence = .66;
    }

    //make api call below
}


Now, we're going to work on the actual api call. Remember how we created a parameter string for our Face API call?

let params = new URLSearchParams({
	'returnFaceId': 'true',
	'<PARAMETER NAME>': '<PARAMETER VALUE>'     //FILL IN THIS LINE
})

We created a URLSearchParams object. Let's do the same thing here:

//Create a new URLSearchParams object called params
let params = <CODE HERE>

Inside of your search params, specify these parameter-value pairs:

  1. Set min_popularity to 70.
  2. Set limit to the function's limit parameter(_getRecommendations(token, seedTracks, limit))
  3. Set seed_tracks to the function's seedTracks parameter(_getRecommendations(token, seedTracks, limit))
  4. Set min_valence to the minValence variable that I already declared for you.
  5. Set max_valence to the maxValence variable

@aschinkmann
Copy link
Owner

bdfehjfbsdjhfbjsdhfbdsh

@github-learning-lab
Copy link
Author

API Controller P3- Make Call

At this point, your code should look similar to this:

//this is the _getRecommendations method inside of your APIController module

const _getRecommendations = async(token, seedTracks, limit) => {
  console.log('getting recommendations...')

  const emotion = document.querySelector('#hidden-emotion').value;
  let minValence = 0;
  let maxValence = 1;

  if (emotion < .33){
      maxValence = .33;
  }
  else if (emotion > .66){
      minValence = .66;
  }
  else{
      minValence = .33;
      maxValence = .66;
  }

  let params = new URLSearchParams({
    'min_popularity': '70',
    'limit': limit,
    'seed_tracks': seedTracks,
    'min_valence': minValence,
    'max_valence': maxValence
})
	
  //API CALL
}


The last part of the _getRecommendations function is to make the fetch call. The only thing you have to add here is the recommendations endpoint and the parameter string. Again, the documentation for this endpoint is here.


Hint: Look back on your Face API call to see how we can add a parameter string.

Click for bigger hint: the actual face api code
  //FACE API CALL!! notice the '?' and 'params.toString()' !!!
  let resp = await fetch(endpoint + '?' + params.toString(), {
      //random code
  }

const result = await fetch('<API ENDPOINT + PARAM STRING>', {
      method: 'GET',
      headers: { 'Authorization' : 'Bearer ' + token}
  });

  const data = await result.json();
  return data.tracks[0];

@aschinkmann
Copy link
Owner

beaubers cool

@github-learning-lab
Copy link
Author

API Controller P4- the Return

Here's the code we have so far for the entire APIController module:

The actual code inside the private methods is hidden for length. Assume that it's there.

const APIController = (function() {
    
    const clientId = config.CLIENT_ID;
    const clientSecret = config.CLIENT_SECRET;

    // private methods
    const _getToken = async () => {
				//working code 
    }

    const _getPlaylistItems = async (token, playlistId, limit) => {
        // cool code
    }

    const _getRecommendations = async(token, seedTracks, limit) => {
        //amazing code
    }

})();


At this point, everything within the APIController module is private. None of these methods can be called.

Remember that in the revealing module pattern, we have to explicitly reveal any variables or methods we want to be public.


Check out the return statement below. We want to publicly reveal all three of our private methods, and I've done two of them for you. In the return object, I'm creating a new function with the same name but no underscore (_getToken --> getToken and _getPlaylistItems --> getPlaylistItems). These new functions just call the corresponding private method and return the result.

Follow the syntax and to return _getRecommendations(token, seedTracks,limit). Don't forget the parameters!


const APIController = (function() {
    
    const clientId = config.CLIENT_ID;
    const clientSecret = config.CLIENT_SECRET;
    // private methods
    const _getToken = async () => {
        // working code 
    }
    const _getPlaylistItems = async (token, playlistId, limit) => {
        // cool code
    }
    const _getRecommendations = async(token, seedTracks, limit) => {
        //amazing code
    }
    
    //this reveals methods we want to be public
    return {
      
      	//public method has the same name but no underscore
        getToken() {
          //inside the method I'm calling the private method and returning the result
            return _getToken();
        },
      	getPlaylistItems(token, playlistId, limit) {
            return _getPlaylistItems(token, playlistId, limit);
        },
      
      	//reveal _getRecommendations here with the same syntax

    }
})();

Once this is done, we can actually call the public methods:

//these are all valid calls now
APIController.getToken();
APIController.getPlaylistItems(token, playlistId, limit);
APIController.getRecommendations(token, seedTracks, limit);

// these are still invalid because the methods are private
APIController._getToken();
APIController._getPlaylistItems(token, playlistId, limit);

Side note: the return method could also be written like so:

return {
        getToken: _getToken,
        getPlaylistItems: _getPlaylistItems,
        getRecommendations: _getRecommendations 
}

It's quite a bit shorter but doesn't allow you to see the necessary parameters for each function, so I prefer the previous method.

That's the completed APIController module!

@aschinkmann
Copy link
Owner

bahburs cool

@github-learning-lab
Copy link
Author

Create the UIController Module

You should already have declared a UIController module like so:

// UI Module
const UIController = (function() {


})();

There isn't a whole lot this module has to do since our app only has to display one song.

What we do need to add is a reference to the id of html elements that we will need to alter or listen to during runtime. The two elements are the Find Song button (it has the id song-button) and the #song-detail html div that displays the actual song recommendation.


Notice that variables submit and divSongDetail only store the html id, not a reference to the actual object.

//object to hold references to html selectors
const DOMElements = {
    button: '#song-button',
    divSongDetail: '#song-detail'
}

DOMElements is the only "private" variable we need, so we can write the return object now:

Two objects to return:

  1. inputField- this is an object with actual references to the html objects using document.querySelector and the id variables that we declared in DOMElements - this is already done for you
  2. createTrackDetail- this method creates a html div where the image, title, and artist of the recommended song is displayed - this is mostly done, just need to add a little


Task: add a line of code at the end of createTrackDetail that inserts the string html into detailDiv.

Use the function insertAdjacentHTML()(documentation here)


//public methods
return {

  //the inputField is an object containing references to the html fields 
   inputField: {
      songButton: document.querySelector(DOMElements.button),
      songDetail: document.querySelector(DOMElements.divSongDetail)
  },

  // need method to create the song detail
  createTrackDetail(img, title, artist) {
      const detailDiv = document.querySelector(DOMElements.divSongDetail)

      // any time user clicks a new song, we need to clear out the song detail div
      detailDiv.innerHTML = '';

      const html = 
      `
      <div class="songdisplay">
          <img src="${img}" alt="">     
          <br />           
          ${title}- ${artist}

      </div> 
      `;

    
    	//TODO: use the insertAdjacentHTML function here to stick the html string into detailDiv
      <CODE HERE>
  }

}

Note: you could also do this with the Element.innerHTML function but in the case that we need to display more than one song, insertAdjacentHTML() allows that.


And that's it for the UIController!

@aschinkmann
Copy link
Owner

yuh

@github-learning-lab
Copy link
Author

APPController

Your project should already have an APPController module declared for you from the first step:

const APPController = (function(UICtrl, APICtrl) {

})(UIController, APIController);  //second pair of parentheses need to pass in parameters!

Notice that unlike the APIController and UIController , this module takes in parameters because we need a reference to the API and UI controllers in order to call the methods inside.

This also means that the second pair of parentheses need to have the correct parameters- just like a normal function call. So we have to pass in our previously declared modules as parameters.


So what does APPController really do?

It controls the actual logic of the app and controls when the APICalls are made and the UI input fields are changed.

The first thing we need to do is get a reference to the UIController html fields:

Set the const DOMInputs to the inputField object that we returned in UIController

const APPController = (function(UICtrl, APICtrl) {

    // get input field object ref
  	//hint: u need to use the UICtrl parameter... something like UICtrl.<objectName>
    const DOMInputs = <the input field object from UI>
    

})(UIController, APIController);

Next, we need to ensure that pressing the Find Song button will actually call the Spotify API and get a song. This means that we need to add an eventListener to the button. An eventListener listens for changes in the html element it is attached to (like a button click) and runs a function when the event happens.


We have to use the addEventListener(event, function) function. It has two parameters:

  1. event - string that specifies the actual event to listen for. In our case, the event is a 'click'.
  2. function - function that runs whenever the event happens

Let's create the eventListener:

Click for hint
  //DOMInputs is the input field object so to get the button just do:
  const buttonElement = DOMInputs.songButton
//TODO: create submit button click event listener
const buttonElement = '<set this to the songButton using DOMInputs! >'

//this adds the eventListener
buttonElement.addEventListener('click', async(event) => {

});


@aschinkmann
Copy link
Owner

uysadgsadjh

@github-learning-lab
Copy link
Author

Event Listener

Your code should look something like this:

const APPController = (function(UICtrl, APICtrl) {

	// get input field object ref
	const DOMInputs = UICtrl.inputField;

	// create submit button click event listener
	const buttonElement = DOMInputs.songButton;
	buttonElement.addEventListener('click', async (event) => {

	});

})(UIController, APIController);

Let's zoom into the eventListener function. What does it actually need to do?

  1. use the APICtrl parameter to call the getToken method
  2. use the token to get 5 popular tracks from the Top 50 Hits Playlist using getPlaylistItems
    1. Remember that to get recommendations, we need to provide seed tracks (read here) in Query Parameters so we need to grab these 5 songs
  3. use the token and 5 seed tracks to call getRecommendations

That's quite a bit of work. Let's go step by step.

Step 1: call getToken

Hint: remember that we need to reference APICtrl to access getToken! Maybe something like

APICtrl.<funcName>

ALSO!! ALL THE API-RELATED FUNCTIONS ARE ASYNC AND NEED TO BE CALLED WITH THE AWAIT KEYWORD BEFORE soooo... await APICtrl.<funcName>


const APPController = (function(UICtrl, APICtrl) {

    //get input field object ref
    const DOMInputs = UICtrl.inputField;
    
    //create submit button click event listener
    const buttonElement = DOMInputs.songButton;
    buttonElement.addEventListener('click', async(event) => {
    	//TODO: call the getToken function using a reference to APICtrl
      	const token = '<CALL THE FUNCTION HERE>'
    });

})(UIController, APIController); 

Step 2: call getPlaylistItems(token, playlistId, limit)

const APPController = (function(UICtrl, APICtrl) {

    // get input field object ref
    const DOMInputs = UICtrl.inputField;
    
    // create submit button click event listener
    const buttonElement = DOMInputs.songButton;
    buttonElement.addEventListener('click', async(event) => {
				
      	const token = '<VALID TOKEN CALL>';
          
        // did this for you- this is the playlist id of Top 50 Hits
        const playlistId = "37i9dQZF1DXcBWIGoYBM5M";
        
        // TODO- call getPlaylistItems to get 5 tracks with appropriate params
        // REMEMBER TO USE AWAIT!
        const tracks = '<CALL THE FUNCTION HERE>';
    });

})(UIController, APIController); 

Step 3: call getRecommendations(token, seedTracks, limit)

const APPController = (function(UICtrl, APICtrl) {

	// get input field object ref
	const DOMInputs = UICtrl.inputField;

	// create submit button click event listener
	const buttonElement = DOMInputs.songButton;
	buttonElement.addEventListener('click', async(event) => {
		const token = '<VALID TOKEN CALL>';
		const playlistId = "37i9dQZF1DXcBWIGoYBM5M"  
		const tracks = '<VALID TRACK CALL>';


		//seedTracks is declared for you- this is just an array of the IDs of the 5 tracks we just received
		let seedTracks = tracks.map(a => a.track.id) 


		//TODO: call getRecommendations to get 1 track
		//USE AWAIT
		const recommendedTrack = '<CALL FUNCTION HERE>'

		//creating a new trackDetail to display the song  
		UICtrl.createTrackDetail(recommendedTrack.album.images[2].url, 
		    recommendedTrack.name, recommendedTrack.artists[0].name);
	});

})(UIController, APIController); 

That's it for the APP Controller! In Visual Studio code, install the Live Server extension and run your index.html file with Live Server to test your code.

@aschinkmann
Copy link
Owner

jrjksdhfkjsdhfjsd

@aschinkmann
Copy link
Owner

dhhdhdsh

@aschinkmann
Copy link
Owner

dhfhsdjkfhksdjfsdjk

@aschinkmann
Copy link
Owner

hshshahahshshhsdajdhajshdasdsajhdhjas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants