Skip to content

fortena/workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Workshop at Experis 30th of January 2019

During this workshop we will create a simple web application using a javascript library called React.
There will be two possible ways of completing the workshop.

Path A: Create a simple web application and deploy it online using GitHub Pages.

Goals

  • Create a simple web application using React
  • Use version control (Git) to manage your project
  • Host the webpage using GitHub Pages
  • Display information from an external Rest API

Requirements

  • Git installed on you machhine
  • GitHub account
  • Node 8.10 or later
  • Run “node -v” in your terminal, if it does not return a version number node is not properly installed on your machine.

Path B: Create a simple web application using JSFiddle.

There are no requirement for this path.

Goals

  • Create a simple web application using React
  • Display information from an external Rest API

The jsfiddle is in 5 steps.
Follow the links below to complete them.

JSFiddle: Step 1

JSFiddle: Step 2

JSFiddle: Step 3

JSFiddle: Step 4

JSFiddle: Step 5

Path A

Step 1: Bootstrap a React application

Open the terminal and navigate to where you want the project to be.
Run the following commands:

npx create-react-app workshop
cd workshop
npm start

Go to http://localhost:3000/ to see the sample application

Step 2: Create a repository on GitHub

Go to GitHub.
Click your avatar and select Your repositories in the dropdown.
Click New.
Write workshop under Repository name
Click Create repository
Copy the command under …or push an existing repository from the command line

Step 3: Connect your repository to your project

Open a new terminal window.
Navigate to the project we created.
Copy the command to the terminal.
Or type it, it was:

git remote add origin [email protected]:myusername/workshop.git

Change the myusername part with your GitHub username.
Run:

git push -u origin master

Step 4: Configure your application

Open the project in the editor.
Open package.json and add the following to line.

"homepage": “https://myusername.github.io/workshop",

Change the myusername part with your Gitub username.
Under scripts add the following two lines.

"predeploy": "npm run build",
"deploy": "gh-pages -d build",

Step 5: Deploy your sample app to GitHub

Go to the terminal.
Run:

npm install --save gh-pages
npm run deploy

Go to https://myusername.github.io/workshop/.
Change the myusername part with your GitHub username.
Give it a minute, your sample application should appear there shortly.

Step 6: Create a simple component

Go to the editor.
Under src create a file called MovieList.js.
To begin with let's keep it very simple.

Functions is JavaScript are either done like this:

function add(a, b) {
  return a + b;
}

Or they can be written like this:

const add = (a, b) => {
  return a + b;
}

Or if we have a oneliner it can simply be written like this

const add = (a, b) => a + b;

These are called Arrow functions and we will be using them here.

Write:

import React from 'react';

const MovieList = () => <div>MovieList</div>;

export default MovieList;

Now go to App.js and change it so it looks like this:

import React from 'react';
import MovieList from './MovieList';

export default () => <div><MovieList /></div>

Note: We export the MovieList function so it can be imported in the App.

If you go to http://localhost:3000 you should see that it now only displays MovieList in the top left corner.

Step 7: Fetch information from an external API

We will be using an open Studio Ghibli API to supply us with the information we want to display.

I found the API here listed with a bunch of other open API's. Open API's are great for practicing if you want to build your own project. Just be careful so you won't accidentally DDoS the kind souls hosting them.

Under src create a new file called api.js.

Here we will create two functions.

The first one is a utility function to GET from a Rest API that does not require an authentication.
The function return a promise which is an asynchronous operation.

const get = url => fetch(url).then(response => response.json());

The second one calls the get function with the URL which points to our data.

export const getMovieList = () => get('https://ghibliapi.herokuapp.com/films');

api.js should look like this now:

const get = url => fetch(url).then(response => response.json());

export const getMovieList = () => get('https://ghibliapi.herokuapp.com/films');

Note: We are only exporting the getMovieList function.
We don't need to use the get function in another file.

Now we need to do some changes to MovieList.js.

First of all we need to change MovieList from a function to a class and create an internal state to manage the movie list.

import React, {Component} from 'react';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = { movies: [] };
  }
}

export default MovieList;

We also need to add a Lifecycle method that only calls our endpoint when our component is initiated.
The method we need is called componentDidMount.

Now lets import the getMovieList from api.js and call it when MovieList is initiated. After the data is fetched from the API we updated the movies state with the information.

import React, { Component } from 'react';
import { getMovieList } from './api';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = { movies: [] };
  }

  componentDidMount() {
    getMovieList().then(movies => {
      this.setState({ movies })
    })
  }
}

export default MovieList;

Now we need to create the DOM that is rendered in our browser along with our data.
We add a render where we iterate through the list of movies and add them to the DOM.

import React, { Component } from 'react';
import { getMovieList } from './api';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = { movies: [] };
  }

  componentDidMount() {
    getMovieList().then(movies => {
      this.setState({ movies })
    })
  }

  render() {
    return (
      <div>
        <h1>Ghibli movies</h1>
        <div>
          {this.state.movies.map((movie) => <div key={movie.id} >{movie.title}</div>)}
        </div>
      </div>
    )
  }
}

export default MovieList;

This looks pretty good, however I want to make one minor adjustment.
Destructing is a way of unpacking values from objects or arrays.
I want to use it to make the syntax a little cleaner.
It works like this:

const a = { b: { c : 'd' } };
const { b } = a; // { c : 'd' }
const { c } = b; // 'd'

After a few changes MovieList.js should looks like this:

import React, { Component } from 'react';
import { getMovieList } from './api';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = { movies: [] };
  }

  componentDidMount() {
    getMovieList().then(movies => {
      this.setState({ movies })
    })
  }

  render() {
    const { movies } = this.state;
    return (
      <div>
        <h1>Ghibli movies</h1>
        <div>
          {movies.map(({ id, title }) => <div key={id} >{title}</div>)}
        </div>
      </div>
    )
  }
}

export default MovieList;

Go to http://localhost:3000 and you should now see a list of movie titles there.

Step 8: Display the rest of the data in a table

Now lets display the rest of the data in a table.

Change MovieList.js so it looks like this:

import React, { Component } from 'react';
import { getMovieList } from './api';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = { movies: [] };
  }

  componentDidMount() {
    getMovieList().then(movies => {
      this.setState({ movies })
    })
  }

  render() {
    const { movies } = this.state;
    return (
      <div>
        <h1>Ghibli movies</h1>
        <table>
          <thead>
            <tr>
              <th>Title</th>
              <th>Description</th>
              <th>Director</th>
              <th>Producer</th>
              <th>Release year</th>
              <th>Score (Rotten Tomatoes)</th>
            </tr>
          </thead>
          <tbody>
            {movies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
              <td>{title}</td>
              <td>{description}</td>
              <td>{director}</td>
              <td>{producer}</td>
              <td>{release_date}</td>
              <td>{rt_score}</td>
            </tr>)}
          </tbody>
        </table>
      </div>
    )
  }
}

export default MovieList;

Go to http://localhost:3000 and view the changes.

Step 9: Add some CSS đź’…

Let's give this a minor facelift.
I'm using a color pallette from here.

Create a new file under src called MovieList.css.

Add the following css:

.wrapper {
  background: #E68A3E;
  display: flex;
  flex-direction: column;
  align-items: center;
}

table {
  border-spacing: 0px;
  width: 100%;
}

th {
  text-align: start;
  padding: 5px;
  color: #443635;
}

td {
  padding: 5px;
  max-width: 700px;
  vertical-align: top;
  color: #443635;
}

tbody > tr:nth-child(odd) {
  background: #F9F778;
}

tbody > tr:nth-child(even) {
  background: #8EC0E8;
}

h1 {
  color: #443635;
}

Go back to MovieList.js and import MovieList.css

import React, { Component } from 'react';
import { getMovieList } from './api';
import './MovieList.css';

and add className="wrapper" to the the first div.

return (
  <div className="wrapper">

MovieList.js should now look like this.

import React, { Component } from 'react';
import { getMovieList } from './api';
import './MovieList.css';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = { movies: [] };
  }

  componentDidMount() {
    getMovieList().then(movies => {
      this.setState({ movies })
    })
  }

  render() {
    const { movies } = this.state;
    return (
      <div className="wrapper">
        <h1>Ghibli movies</h1>
        <table>
          <thead>
            <tr>
              <th>Title</th>
              <th>Description</th>
              <th>Director</th>
              <th>Producer</th>
              <th>Release year</th>
              <th>Score (Rotten Tomatoes)</th>
            </tr>
          </thead>
          <tbody>
            {movies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
              <td>{title}</td>
              <td>{description}</td>
              <td>{director}</td>
              <td>{producer}</td>
              <td>{release_date}</td>
              <td>{rt_score}</td>
            </tr>)}
          </tbody>
        </table>
      </div>
    )
  }
}

export default MovieList;

Go to http://localhost:3000 and view the changes.

Lets make one minor adjustment. I want the numberic fields to centered so add className="number" to the last two fields.

<td className="number">{release_date}</td>
<td className="number">{rt_score}</td>

Also add this after td in MovieList.css

.number {
  text-align: center;
}

Step 10: Add filtering

Let's finish this off by adding some basic filter functionality so we can easily find the movie we want.
We will only filter by the first four attributes.
We begin with adding a state for our filters.
Change the constructor so it looks like this.

  class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      movies: [],
      filters: {
        title: '',
        description: '',
        director: '',
        producer: '',
      }
    };
  }

Now lets add a generic filter component.
Under src create a new file called Filter.js

import React from 'react';

const Filter = ({ filterKey, placeholder, value, onChange }) =>
  <input
    type="search"
    placeholder={placeholder}
    value={value}
    onChange={event => {
      onChange(filterKey, event)
    }}
  />


export default Filter;

Go back to MovieList.js and import Filter component from Filter.js.

import Filter from './Filter.js';

We should also create a generic filter function to the MovieList component.
This should do the trick.

handleFilters = (key, event) => {
  const value = event.target.value;
  this.setState(({ filters }) => ({ filters: { ...filters, [key]: value } }))
}

Now edit the table header so it looks like this:

<thead>
  <tr>
    <th>Title</th>
    <th>Description</th>
    <th>Director</th>
    <th>Producer</th>
    <th>Release year</th>
    <th>Score (Rotten Tomatoes)</th>
  </tr>
  <tr>
    <th><Filter placeholder="Filter by title" filterKey="title" value={filters.title} onChange={this.handleFilters} /></th>
    <th><Filter placeholder="Filter by description" filterKey="description" value={filters.description} onChange={this.handleFilters} /></th>
    <th><Filter placeholder="Filter by director" filterKey="director" value={filters.director} onChange={this.handleFilters} /></th>
    <th><Filter placeholder="Filter by producer" filterKey="producer" value={filters.producer} onChange={this.handleFilters} /></th>
    <th /><th />
  </tr>
</thead>

Now we have filtering but we need to apply it to our data.
We need to create another method to MovieList.js to complete this.

filterMovies = () => {
  const { movies, filters } = this.state;
  const activeFilterKeys = Object.keys(filters).filter(key => filters[key] !== '');
  if (activeFilterKeys.length === 0) {
    return movies;
  }
  return movies.filter((movie) =>
    activeFilterKeys.every(key => movie[key].toLowerCase().includes(filters[key].toLowerCase()))
  )
}

Finally we need to filter the movie state before we render the content of it.
Change the render method so it looks like this:

render() {
  const { movies, filters } = this.state;
  const filteredMovies = this.filterMovies(movies);
  return (
    <div className="wrapper">
      <h1>Ghibli movies</h1>
      <table>
        <thead>
          <tr>
            <th>Title</th>
            <th>Description</th>
            <th>Director</th>
            <th>Producer</th>
            <th>Release year</th>
            <th>Score (Rotten Tomatoes)</th>
          </tr>
          <tr>
            <th><Filter placeholder="Filter by title" filterKey="title" value={filters.title} onChange={this.handleFilters} /></th>
            <th><Filter placeholder="Filter by description" filterKey="description" value={filters.description} onChange={this.handleFilters} /></th>
            <th><Filter placeholder="Filter by director" filterKey="director" value={filters.director} onChange={this.handleFilters} /></th>
            <th><Filter placeholder="Filter by producer" filterKey="producer" value={filters.producer} onChange={this.handleFilters} /></th>
            <th /><th />
          </tr>
        </thead>
        <tbody>
          {filteredMovies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
            <td>{title}</td>
            <td>{description}</td>
            <td>{director}</td>
            <td>{producer}</td>
            <td className="number">{release_date}</td>
            <td className="number">{rt_score}</td>
          </tr>)}
        </tbody>
      </table>
    </div>
  )
}

MovieList.js should now look like this:

import React, { Component } from 'react';
import { getMovieList } from './api';
import './MovieList.css';
import Filter from './Filter';

class MovieList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      movies: [],
      filters: {
        title: '',
        description: '',
        director: '',
        producer: '',
      }
    };
  }

  componentDidMount() {
    getMovieList().then(movies => {
      this.setState({ movies })
    })
  }

  handleFilters = (key, event) => {
    const value = event.target.value;
    this.setState(({ filters }) => ({ filters: { ...filters, [key]: value } }))
  }

  filterMovies = () => {
    const { movies, filters } = this.state;
    const activeFilterKeys = Object.keys(filters).filter(key => filters[key] !== '');
    if (activeFilterKeys.length === 0) {
      return movies;
    }
    return movies.filter((movie) =>
      activeFilterKeys.every(key => movie[key].toLowerCase().includes(filters[key].toLowerCase()))
    )
  }

  render() {
    const { movies, filters } = this.state;
    const filteredMovies = this.filterMovies(movies);
    return (
      <div className="wrapper">
        <h1>Ghibli movies</h1>
        <table>
          <thead>
            <tr>
              <th>Title</th>
              <th>Description</th>
              <th>Director</th>
              <th>Producer</th>
              <th>Release year</th>
              <th>Score (Rotten Tomatoes)</th>
            </tr>
            <tr>
              <th><Filter placeholder="Filter by title" filterKey="title" value={filters.title} onChange={this.handleFilters} /></th>
              <th><Filter placeholder="Filter by description" filterKey="description" value={filters.description} onChange={this.handleFilters} /></th>
              <th><Filter placeholder="Filter by director" filterKey="director" value={filters.director} onChange={this.handleFilters} /></th>
              <th><Filter placeholder="Filter by producer" filterKey="producer" value={filters.producer} onChange={this.handleFilters} /></th>
              <th /><th />
            </tr>
          </thead>
          <tbody>
            {filteredMovies.map(({ id, title, description, director, producer, release_date, rt_score }) => <tr key={id} >
              <td>{title}</td>
              <td>{description}</td>
              <td>{director}</td>
              <td>{producer}</td>
              <td className="number">{release_date}</td>
              <td className="number">{rt_score}</td>
            </tr>)}
          </tbody>
        </table>
      </div>
    )
  }
}

export default MovieList;

Go to http://localhost:3000 and try out the filtering.

Lets add a final detail before we call it a day.

Under src create a file called Filter.css;

Add this to it:

.filter {
  font-size: 16px;
  width: 100%;
}

Import the css in Filter.js and add className="filter" to the input.

Filter.js should now look like this:

import React from 'react';
import './Filter.css';

const Filter = ({ filterKey, placeholder, value, onChange }) =>
  <input
    className="filter"
    type="search"
    placeholder={placeholder}
    value={value}
    onChange={event => {
      onChange(filterKey, event)
    }}
  />


export default Filter;

This is it.
Now lets end by pushing all out changes to the master and delpoying our project online.
In the terminal type:

git add --all
git commit -m "my ghibli movie list"
git push origin master
npm run deploy

Hope you enjoyed it and feel free to ask me anything after the workshop.

About

Workshop for Experis

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published