diff --git a/examples/trivia-quiz/index.html b/examples/trivia-quiz/index.html new file mode 100644 index 00000000..149b5150 --- /dev/null +++ b/examples/trivia-quiz/index.html @@ -0,0 +1,24 @@ + + + + + + codebar.io - Trivia! + + +
+ +
+
+
+

Select Difficulty:

+
+

Questions

+
+
+
+ + + \ No newline at end of file diff --git a/examples/trivia-quiz/index.js b/examples/trivia-quiz/index.js new file mode 100644 index 00000000..090cf5a5 --- /dev/null +++ b/examples/trivia-quiz/index.js @@ -0,0 +1,78 @@ +// As fetch() is an asynchronous function we can add the async/await keywords +async function fetchQuizQuestions(difficulty) { + return await fetch(`https://opentdb.com/api.php?amount=10&difficulty=${difficulty}`) + .then(response => { + return response.json() + }) + .then(data => { + renderQuizQuestions(data) + }) + .catch(error => { + console.error('Error: ', error) + }) +} + +function renderQuizQuestions(data) { + const container = document.getElementById('questions') + //when new data is fetched we want to remove the pre-existing list + container.removeChild(container.firstChild) + + const list = document.createElement('ul') + container.appendChild(list) + + data.results.map((questionObject) => { + const listElement = document.createElement('li') + const text = document.createTextNode(questionObject.question) + listElement.appendChild(text) + list.appendChild(listElement) + }) +} + +// Bonus part of the tutorial functions +function createSelector() { + const difficulties = ['easy', 'medium', 'hard'] + + const selectorContainer = document.getElementById('selector-container') + const selector = document.createElement('select') + selector.id = 'selector' + + difficulties.map((difficulty) => { + const option = new Option(difficulty, difficulty) + selector.appendChild(option) + }) + selectorContainer.appendChild(selector) +} + +function selectorChangeListener() { + const selector = document.getElementById('selector') + + selector.addEventListener('change', event => { + const difficulty = event.target.value.toLowerCase() + + fetchQuizQuestions(difficulty) + }) +} +// // // + +function renderApp() { + createSelector() + // set to easy to load the page with + fetchQuizQuestions('easy') + selectorChangeListener() +} + +document.addEventListener('readystatechange', event => { + const readyState = event.target.readyState + const container = document.getElementById('questions') + + if (readyState === 'interactive') { + const image = new Image(100, 100) + image.src = 'spinner.gif' + + container.appendChild(image) + } else if (readyState === 'complete') { + //remove spinner when document is ready + container.removeChild(container.firstChild) + renderApp() + } +}) \ No newline at end of file diff --git a/examples/tv-schedule/spinner.gif b/examples/trivia-quiz/spinner.gif similarity index 100% rename from examples/tv-schedule/spinner.gif rename to examples/trivia-quiz/spinner.gif diff --git a/examples/trivia-quiz/styles.css b/examples/trivia-quiz/styles.css new file mode 100644 index 00000000..2314ce0a --- /dev/null +++ b/examples/trivia-quiz/styles.css @@ -0,0 +1,40 @@ +body { + font-family: Helvetica, Arial, sans-serif; + border-top: 5px solid #4c4066; + margin: 0px auto; + font-size: 16px; +} + +header { + text-align: center; +} + +li { + padding-bottom: 10px; +} + +#selector { + font-size: 16px; +} + +#questions { + max-width: 750px; +} + +#logo { + position: relative; + top: 2px; +} + +#container { + width: 1040px; + padding-left: 8px; + min-height: 750px; +} + +footer { + padding-bottom: 10px; + border-bottom: 5px solid #4c4066; + position: absolute; + width: 100%; +} diff --git a/examples/tv-schedule/index.html b/examples/tv-schedule/index.html deleted file mode 100644 index 4622124c..00000000 --- a/examples/tv-schedule/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - codebar.io - What's on TV tomorrow? - - - - -
- -
-
-

What's on TV tomorrow?

- - -
- - - diff --git a/examples/tv-schedule/script.js b/examples/tv-schedule/script.js deleted file mode 100644 index 792f07bc..00000000 --- a/examples/tv-schedule/script.js +++ /dev/null @@ -1,114 +0,0 @@ -function formatDate(start, end) { - start_date = new Date(start); - end_date = new Date(end); - - day = start_date.getDate(); - month = start_date.getMonth() + 1; - year = start_date.getFullYear(); - - start_hour = start_date.getHours(); - start_mins = start_date.getMinutes(); - - end_hour = end_date.getHours(); - end_mins = end_date.getMinutes(); - - date = day + "/" + month + "/" + year + " "; - date += ("0"+start_hour).slice(-2) + ":" + ("0"+start_mins).slice(-2) + " - " + - ('0' + end_hour).slice(-2) + ":" + ( "0" + end_mins).slice(-2); - return date; -} - -function getGenres() { - $.ajax({ - url: "http://www.bbc.co.uk/tv/programmes/genres.json" - }).done(function(data) { - console.log(data); - data.categories.forEach(function(item) { - $('#genres').append("
  • " + item.title + "
  • "); - }) - }).fail(function() { - console.log("something went wrong"); - }); -} - -function getTomorrowsSchedule(genre) { - $.ajax({ - url: "http://www.bbc.co.uk/tv/programmes/genres/"+genre+"/schedules/tomorrow.json", - dataType: 'json', - beforeSend: function() { - $("#programmes").empty(); - $("#programmes").append("
    "); - } - }).done(function(data) { - $(".spinner").remove(); - if (data.broadcasts.length > 0) { - data.broadcasts.forEach(function(episode) { - $("#programmes").append(processEpisode(episode)); - }) - } else { - $("#programmes").append("
    No programmes under " + genre + "
    "); - } - }).fail(function() { - console.log("something went wrong"); - }); -} - -function getUpcomingEpisodes(pid) { - $.ajax({ - url: "http://www.bbc.co.uk/programmes/" + pid + "/episodes/upcoming.json", - beforeSend: function() { - $("#programmes").empty(); - $("#programmes").append("
    "); - } - }).done(function(data) { - $(".spinner").remove(); - - data.broadcasts.forEach(function(episode) { - $("#programmes").append(processEpisode(episode)); - }) - }).fail(function() { - console.log("something went wrong"); - }); -} - -function processEpisode(episode) { - item_html = "
  • " + episode.programme.display_titles.title + "

    "; - item_html += "

    " + episode.programme.short_synopsis + "

    "; - - if (episode.programme.image) { - item_html += ""; - } else { - item_html += ""; - } - - item_html += "

    " + formatDate(episode.start, episode.end)+ "

    "; - item_html += "

    Duration: " + episode.duration/60 + " minutes

    "; - - if (episode.programme.position) { - pid = episode.programme.programme.pid; - item_html += " View all upcoming " + episode.programme.display_titles.title + ""; - } - - item_html += "" + episode.service.title + "
  • "; - - return item_html; -} - - -$(document).ready(function(){ - $(document).on('click', '#genres li', function(e){ - genre = $(this).attr('id'); - $("#genres li").removeClass('active'); - $(this).addClass('active'); - - getTomorrowsSchedule(genre); - }) - - $(document).on('click', '.view-more', function(e){ - pid = $(this).attr('id'); - - getUpcomingEpisodes(pid); - }) - - getGenres(); -}); diff --git a/examples/tv-schedule/style.css b/examples/tv-schedule/style.css deleted file mode 100644 index 805fbb5e..00000000 --- a/examples/tv-schedule/style.css +++ /dev/null @@ -1,110 +0,0 @@ -body { - font-family: Helvetica, Arial, sans-serif; - border-top: 5px solid #4c4066; - margin: 0px auto; - font-size: 14px; - color: #666666; - font-size: 16px; -} - -header { - text-align: center; - background-color: #fdfdfd; - border-bottom: 1px solid #e5e2e0; -} - -#logo { - position: relative; - top: -2px; -} - -#container { - width: 1040px; - margin: 0px auto; - min-height: 750px; -} - -input[type='text'] { - height: 2.2em; - font-size: 2em; - width: 400px; - padding: 5px; -} - -.information, .avatar { - vertical-align: top; - display: inline-block; - width: 40%; -} - -.search { - text-align: center; - margin: 50px auto; -} - -h2 { - color: #A26CD2; -} - -ul { - list-style: none; - margin: 0; - padding: 0; -} - -li { - display: inline-block; -} - -#genres li { - padding: 10px; - margin: 5px; - background-color: #5E5A6B; - color: #ffffff;; - border-radius: 5px; -} - -#genres li.active { - background-color: purple; -} - -#programmes li { - width: 45%; - margin: 10px; - padding: 10px; - background-color: #FDFDFD; - border-radius: 5px; - box-shadow: 0px -1px 1px 2px #dbdbdb; - vertical-align: top; -} - -.service { - background-color: #A26CD2; - padding: 5px; - float: right; - border-radius: 5px; - color: black; -} - -.spinner { - width: 100%; - margin-top: 200px; - text-align: center; -} - -.no-programmes { - margin-top: 100px; - text-align: center; -} - -footer { - padding-bottom: 10px; - border-bottom: 5px solid #4c4066; - position: absolute; - width: 100%; -} - -footer #content { - width: 1040px; - margin: 0px auto; -} diff --git a/js/lesson4/tutorial.md b/js/lesson4/tutorial.md index ea9b8549..1e212ab9 100644 --- a/js/lesson4/tutorial.md +++ b/js/lesson4/tutorial.md @@ -13,13 +13,14 @@ In this tutorial we are going to look at: * JSON * Loading API data into web pages * Using jQuery AJAX functionality +* Using the fetch() API ### Goal By the end of this tutorial you will have built: * A webpage that can retrieve information about a specified GitHub user -* A webpage that can show the upcoming schedule for BBC shows +* A webpage that can show random generated quiz questions # HTTP Requests @@ -227,214 +228,183 @@ Well done, you've finished! For a bonus, switch your `getGithubInfo` method to r > Coach... explain the difference between synchronous and asynchronous requests. There's a good explanation on [Mozilla Developer Network (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests) +## Exercise 2 - Trivia! -## ~~Exercise 2 - BBC's tomorrow's TV schedule~~ -![](https://cdn0.iconfinder.com/data/icons/shift-free/32/Error-128.png) - -**Part 2 of this exercise is no longer possible as the API that it uses has been taken down by the BBC.** -**Sorry, we're working on fixing the exercise!** - -[Download](https://gist.github.com/despo/05cab2f0b38bc02318e7/download) the exercise files or clone them directly from github `git clone https://gist.github.com/05cab2f0b38bc02318e7.git` - -For the second exercise, we will build an application that retrieves tomorrow's TV schedule for each genre using BBC's API. +For this exercise, we will build a web app that generates trivia questions, using this open trivia questions api: https://opentdb.com/api_config.php. +[Download](https://gist.github.com/kyorkston/a2768287fe15fc693da3dc62e8a8d697/download) the exercise files or clone them directly from github `git clone https://gist.github.com/a2768287fe15fc693da3dc62e8a8d697.git`. Shoutout to [despo](https://gist.github.com/despo) for originially making these files! ### What we will be doing: -1. Retrieve and render available genres using `http://www.bbc.co.uk/tv/programmes/genres.json` +1. Retrieve and log quiz questions using the quiz API: `https://opentdb.com/api.php?amount=10` -2. Write a function that retrieves tomorrow's TV schedule using a genre `http://www.bbc.co.uk/tv/programmes/genres//schedules/tomorrow.json` +2. Write a function that displays each question -3. Write a function that displays each programme +3. **Bonus** Set the quiz difficulty, category or type using the API URL generator. Then display whatever you'd like with the API response data. -4. **Bonus** Retrieve all upcoming episodes of a programme +### Retrieve and render log questions -### Request using jQuery +Firstly, here is an introduction to the fetch() Web API - [Introduction to fetch() by Matt Gaunt](https://developers.google.com/web/updates/2015/03/introduction-to-fetch) -This time, let's use jQuery's `ajax()` method. Things are a bit easier when using jQuery as we can create different code blocks that handle successful or failed requests. +There is a whole lot of jargon in there but what the fetch() function does is simplify things for the developer when making a request to an API. -Also, jQuery isolates us from the differences between browser implementations of AJAX calls (for example, if we wanted to make the previous AJAX call work in Internet Explorer, we will have to write [a much longer method](http://www.tutorialspoint.com/ajax/ajax_browser_support.htm)!) +Lets first create the fetch request to the trivia API: ```js -$.ajax({ - url: request_url, - dataType: 'json', - beforeSend: function() { - // do something before running the request - } -}).done(function(data) { - // process data -}).fail(function() { - // code -}).always(function() { - // code that runs regardless of request succeeding or failing -}); +fetch('https://opentdb.com/api.php?amount=10') + .then(function(response) { + return response.json() + }) + .then(function(data) { + //work with the json response data here + console.log(data) + }) + ``` +We send a request with `fetch`, the reposnse returns a Promise. This response is passed into a +`then()` function (a Promise) where we transform it into something readable and usable - a JSON object. When that Promise resolves _then_ (`then()`) we can use the response (`response.json()`) however we want to use it in our code when that Promise is resolved - in our case a `console.log()`. -`datatype` defines the type of result we will be getting back. This avoids us having to parse the response to JSON. +Since things can happen with API responses and its not always a 200 OK status, we can add a `.catch` to the fetch so we can log any issues when fetching data from the api. -`beforeSend` can be used if we need to perform something before running the request. +```js +fetch('https://opentdb.com/api.php?amount=10') + .then(function(response) { + return response.json() + }) + .then(function(data) { + //work with the json response data here + console.log(data) + }) + .catch(function(error) { + console.error('Error: ', error) + }) -`.done()` handles a response that returns a success status code +``` -`.fail()` is called when the request fails +For more information on Promises and asynchronous functions check out [javascript.info/promise-basics](https://javascript.info/promise-basics). +Now open the `index.html` file in a web browser and then inspect the page - here is an article on how to do so in every browser: [How to Inspect Web Page Elements](https://www.lifewire.com/get-inspect-element-tool-for-browser-756549). +Once you have inspected the page, a panel will appear and it will show you the html elements on your page. In this panel there is a 'Console' tab, click this and see what has been logged. -## Retrieving and displaying all available genres +You should see printed out something that looks like this: +```js +{ + "response_code": 0, + "results": [ + { + "category": "Entertainment: Video Games", + "type": "multiple", + "difficulty": "easy", + "question": "How many games in the Crash Bandicoot series were released on the original Playstation?", + "correct_answer": "5", + "incorrect_answers": [ + "4", + "3", + "6" + ] + }, + { + "category": "Entertainment: Video Games", + "type": "multiple", + "difficulty": "easy", + "question": "League of Legends, DOTA 2, Smite and Heroes of the Storm are all part of which game genre?", + "correct_answer": "Multiplayer Online Battle Arena (MOBA)", + "incorrect_answers": [ + "Real Time Strategy (RTS)", + "First Person Shooter (FPS)", + "Role Playing Game (RPG)" + ] + }, + ... + ] +} +``` -Write a function `retrieveGenres()` that does an AJAX call to the API. +It is returning 10 questions because we asked the api to return 10 with the query parameter `?amount=10` on the end of the URL. -```javascript -function retrieveGenres() { - // AJAX call using jQuery that retrieve and process the result +Okay lets now put the fetch request inside a function: +```js +function fetchQuizQuestions() { + // add fetch function in here } ``` -> Try logging the resulted data and have a look in the console to see and explore the created objects -> `` is the genre format we need to retrieve results from the API +### Write a function that displays each question -> You can use `` to display a humanly readable format of the Genre +Now we need to render the questions onto our webpage. We will add them in a list format, for that we use the `<li>` html tag. This will be going inside the `<ul>` tag that we have in `index.html`, it has the id "questions". Refer back to [Javascript Lesson 2](tutorials.codebar.io/js/lesson2/tutorial.html) on how to create html elements and add data to them. -As you can see from the console, the resulting objects are returned inside an Array. We want to iterate over the list using the native Array `forEach( )` function and add each item to the `#genres` list, as a **list item**. As we need to have access to the `key` as well, we can set that as the list item's `id`. -Now that we have all the available genres, we can move on to making calls to the API using the genre to retrieve tomorrow's schedule!! +As the results come back in the form of an array we can run `results.map()` over the array and access the properties inside the result object. -## Retrieve schedule - -Now, let's create a function that retrieves films using genre. +```js +function renderQuizQuestions() { -```javascript -function getTomorrowsSchedule(genre) { - // call to retrieve TV schedule } ``` -The response you get back should look similar to this, with multiple objects in the broadcasts array. +Put this into the second part of the Promise (`then()`) of your fetching function, something like: -```json -{ - "broadcasts": [ - { - "is_repeat": false, - "is_blanked": false, - "schedule_date": "2014-01-15", - "start": "2014-01-15T00:10:00Z", - "end": "2014-01-15T01:50:00Z", - "duration": 6000, - "service": { - "type": "tv", - "id": "bbc_one", - "key": "bbcone", - "title": "BBC One", - "outlets": [ - { - "id": "bbc_one_wales", - "key": "wales", - "title": "Wales" - }, - { - "id": "bbc_one_wales_hd", - "key": "wales_hd", - "title": "Wales HD" - } - ] - }, - "programme": { - "type": "episode", - "pid": "b00sbk03", - "position": null, - "title": "Disturbia", - "short_synopsis": "Thriller about a high school student convinced that his neighbour is a serial killer.", - "media_type": "audio_video", - "duration": 6000, - "image": { - "pid": "p01gqbj3" - }, - "display_titles": { - "title": "Disturbia", - "subtitle": "" - }, - "first_broadcast_date": "2010-05-03T22:30:00+01:00", - "ownership": { - "service": { - "type": "tv", - "id": "bbc_three", - "key": "bbcthree", - "title": "BBC Three" - } - }, - "is_available_mediaset_pc_sd": false, - "is_legacy_media": false - } - }] - } +```js +fetch(...) + .then(...) + .then(function(data) { + renderQuizQuestions(data) + }) ``` -To process the response, we want to iterate over the `response.broadcasts` array and add each item, to `#programmes` as a list item. - -To make this a bit easier, this is how you can access the values we need: - -* `item.programme.display_titles.title` -* `item.programme.short_synopsis` -* `item.programme.image.pid` _only if `item.programme.image` is set_ -* `item.start` and `item.end` -* `item.duration` -* `item.service.title` - -It would be easier to use string concatenation to construct the html, before appending each item to the list. -Also, to make your code easier to read, try constructing the html in a method that you pass the response object. - -```javascript -function processEpisode(episode) { - var item_html = '<li>'; - item_html += '<h2>' + episode.programme.display_titles.title + '</h2>'; - // display short short synopsis - // display image - // display date and time - // display duration (HINT: the duration is in seconds, convert that to minutes) - // display the channel (or service, as its called by the API) - add this in a span with the class `service` - ... -} -``` +To run your code you just have to call our fetch function like `fetchQuizQuestions()` at the bottom of the index.js file. + +Because fetch() is an asynchronous function the rest of the code in your file could run at the same time as the fetch +function, meaning that the fetched data may not appear if used in an outside function, for example: -> To display the date formatted correctly, you can use the `formatDate( )` function as we won't be going into details about dates in this tutorial. If you want to know how it works, try going through the code and ask your coach any questions you have. +```js +function fetchQuizQuestions() {...} -To display an image for a programme, we need to use `<img src=http://ichef.bbci.co.uk/images/ic/272x153/<pid>.jpg />`. As not all programmes have an image, we can use an image placeholder when no image is set `<img src='http://placehold.it/272x153' />` +function renderQuizQuestions() { + const data = fetchQuizQuestions() -### Binding the call to the click event + // render the data into UI components +} +``` -Handle a `click` event on `#genres li` and make a call to `getTomorrowsSchedule(genre)` +The `data` inside `renderQuizQuestions()` will come back as undefined as it is run before the asynchronous function has resolved/finished. -### Improving our function +So now you should hopefully have some Questions rendered onto your webpage, if not please ask for help from a Codebar coach, a fellow Codebar attendee or checkout the Codebar Slack chatrooms. -To make the genre we have just clicked active, we also want to add the CSS class `active` to the element that the event has been triggered from. Don't forget to remove the class `active` from any other `#genres li` items. -> Did you remember to commit your changes? +### **BONUS** Play around with the API URL generator -### Using `beforeSend` +The trivia api we're using is pretty cool and you can configure it in a few ways, check it out: [https://opentdb.com/api_config.php](https://opentdb.com/api_config.php). +If you select a few of the options and then generate an api, you could get something like this: -Every time we issue a call to the API, we want to clear the `#programmes` list. We can do that using `empty()`. -Also, as some of the requests take a while, we want to display a spinning image `<div class='spinner'><img src='spinner.gif' /></div>`. +`https://opentdb.com/api.php?amount=20&category=10&difficulty=medium` -> Don't forget to remove the spinner, when the request is completed successfully. +We have `amount=20`, this is the amount of questions. `category=10` this is the book category, so each category is mapped to a number in the API. And we have `difficulty=medium`, setting the difficulty of the questions. +You can play around with this a lot and its a simple interface and you can make the questions as easy or as difficult as you want! -## Bonus: Retrieving all upcoming episodes of a programme +Let's create a selector to choose what difficulty of questions we would like. +There is 3 difficulty settings `['easy', 'medium', 'hard']` which we can create a html selector for. +We would then inject the selected value into our `fetchQuizQuestions()` function. -To get back all the upcoming shows for an episode, we need to utilise the programme pid, that we can retrieve from the response using `episode.programme.programme.pid`. The URL for the request is `http://www.bbc.co.uk/programmes/<pid>/episodes/upcoming.json` +Create a function where we can loop through the list of difficulties, creating an `<option>` for each one and adding the options value. +Then append this list of options to a `<select>` element. -> **Hint:** The programme `pid` is only available if `episode.programme.position` is set. +Now add an event listener to the selector's `'change'` event. This will be triggered whenever a user selects a new option. In the listener we want to pass the value of the event into our `fetchQuizQuestions` function. +We can expand the API call to look like this: -```javascript -function getUpcomingEpisodes(pid) { - // AJAX call to retrieve upcoming episodes -} +```js +fetchQuizQuestions(difficulty) { + return fetch(`https://opentdb.com/api.php?amount=10&difficulty=${difficulty}`) ``` -Since the response structure is similar to the one for retrieving tomorrow's schedule, we should be able to re-use the `processEpisode( )` function to display each item from the broadcasts array. -Handle the `click` event to retrieve and display the upcoming episodes! +You've now got the tools to play around with this API as much as you want, where you can create an amazing quiz app! + +Here is our version of the [trivia quiz app](../../examples/trivia-quiz/index.html). -Here is our version of the [tv schedule app](../../examples/tv-schedule/index.html). +Now that you can render the questions and the user can change the difficulty setting, how about now rendering the answers? +Perhaps you could have a button to reveal the answer? +Or if you select the multi-choice questions you could add some fun CSS styles to let the user know if they got the answer right or wrong! --- This ends our **HTTP Requests, AJAX and APIs** tutorial. Is there something you don't understand? Try and go through the provided resources with your coach. If you have any feedback, or can think of ways to improve this tutorial [send us an email](mailto:feedback@codebar.io) and let us know.