Static Pages

September 2018: This material is under very active development, and should all be considered the "brainstorming" phase of a rational lesson design process. We would appreciate your help: please email us, file an issue in our GitHub repository, or submit a pull request. (We would particularly appreciate descriptions of common errors and how to fix them.) Everyone whose work is incorporated will be acknowledged; please note that all contributors are required to abide by our Code of Conduct.

Questions

  • What JavaScript libraries should I use to create a web pages?
  • How can I use them to create basic HTML elements?
  • How can I style those pages?
  • How can I mix my JavaScript with HTML?
  • How can I create reusable components for building web pages?
  • How can separate my code into multiple files to make it more manageable?
  • In the beginning, people created HTML pages by typing them in
  • Quickly realized that a lot of pages shared a lot of content
    • Headers, footers, etc.
  • Create a template with some embedded commands to:
    • Include other bits of HTML (like headers)
    • Loop over data structures to create lists and tables
  • Server-side page generation
    • HTML generated by the server
    • That’s where the data was
    • That was the only place complex code could be run

FIXME-18: diagram

  • Balance shifted as browsers and JavaScript became more powerful
  • Current standard model is:
    • JavaScript running in the browser fetches data from one or more servers
    • Uses that data to generate HTML in the browser for display
  • Client-side page generation
    • Allows the client (e.g., the browser) to decide how best to render data
    • Increasingly important as mobile devices take over from PCs

FIXME-18: diagram

  • Lots and lots (and lots) of JavaScript frameworks for building views
  • We have chosen React because it is:
    • Freely available
    • Simpler than many alternatives
    • Widely used
    • Well documented
  • Central design principles are:
    • Use functions to describe the desired HTML
    • Let React decide which functions to run when data changes
  • Show how to do it the pure-JavaScript way
  • Then introduce a tool called JSX that simplifies things

Hello, World

<!DOCTYPE html>
<html>
  <head>
    <title>Hello React</title>
    <meta charset="utf-8">
    <script src="https://fb.me/react-15.0.1.js"></script>
    <script src="https://fb.me/react-dom-15.0.1.js"></script>
  </head>
  <body>
    <div id="app">
      <!-- this is filled in -->
    </div>
    <script>
      ReactDOM.render(
        React.DOM.h1(null, "Hello React"),
        document.getElementById("app")
      )
    </script>
  </body>
</html>
  • Head of the page loads two React libraries from the web
    • Use locally-installed libraries later
  • Body contains a div with an ID to make it findable
    • React will replace this entire div
  • Script
    • Create an h1 with the text “Hello, React”
    • Find the element whose ID is “app”
    • Insert the former into the latter
  • Notes
    • Alters the representation of the page in memory, not the source of the page on disk
    • Can’t put the script in a separate JavaScript file and load it in the head because the body might not exist in memory when the script is run
    • Browsers try to do things asynchronously to improve speed from the user’s point of view
    • Which means we have to think a little harder
  • The first parameter to React.DOM.h1 can be an object of attributes
    • Note: fontStyle rather than font-style to make things look like valid JavaScript variables
  <body>
    <div id="app"></div>
    <script>
      const attributes = {
        'style': {
          'background': 'pink',
          'fontStyle': 'italic'
        }
      }
      ReactDOM.render(
        React.DOM.h1(attributes, "Hello Stylish"),
        document.getElementById("app")
      )
    </script>
  </body>

JSX

  • Writing nested functions is a clumsy way to write HTML
  • So use a tool called JSX that translates HTML into JavaScript function calls
<!DOCTYPE html>
<html>
  <head>
    <title>Hello JSX</title>
    <meta charset="utf-8">
    <script src="https://fb.me/react-15.0.1.js"></script>
    <script src="https://fb.me/react-dom-15.0.1.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="text/babel">
      ReactDOM.render(
        <h1>Hello JSX</h1>,
        document.getElementById('app')
      )
    </script>
  </body>
</html>
  • Include Babel to translate mixed content into pure JavaScript
  • Add type="text/babel" to the script tag to tell Babel where to do its work
  • Remember: the script is translated into pure JavaScript and then run as before

  • Why bother?
  • Because we can put JavaScript inside our HTML (inside our JavaScript (inside our HTML))
  • E.g., use map to turn a list of strings into an HTML list
  <body>
    <h1>JSX List</h1>
    <div id="app"></div>
    <script type="text/babel">
      const allNames = ['McNulty', 'Jennings', 'Snyder', 'Meltzer', 'Bilas', 'Lichterman']
      ReactDOM.render(
        <ul>{allNames.map((name) => { return <li>{name}</li> })}</ul>,
        document.getElementById('app')
      )
    </script>
  </body>
  • Have to use map rather than a loop because the function has to return something
    • Could build up a string through repeated concatenation, but this is cleaner
  • Must return exactly one root node, because this is one function call
  • The browser console will warn us that each list element ought to have a unique key property
    • Want each element of the page to be selectable
    • Add this later

Creating Components

  • If we’re defining functions, we can write new ones
  • React requires component names to start with a capital letter to differentiate them from regular tags.
  <body>
    <h1>Create Component</h1>
    <div id="app"></div>
    <script type="text/babel">
      const allNames = ['McNulty', 'Jennings', 'Snyder', 'Meltzer', 'Bilas', 'Lichterman']

      const ListOfNames = () => {
        return (<ul>{allNames.map((name) => { return <li>{name}</li> })}</ul>)
      }

      ReactDOM.render(
        <div>
          <ListOfNames />
        </div>,
        document.getElementById('app')
      )
    </script>
  </body>
  • What we really want to do is parameterize
    • After all, the JSX is being turned into a function
  • All the attributes are passed to our function in a single props object
  <body>
    <h1>Pass Parameters</h1>
    <div id="app"></div>
    <script type="text/babel">
      const allNames = ['McNulty', 'Jennings', 'Snyder', 'Meltzer', 'Bilas', 'Lichterman']

      const ListElement = (props) => {
        return (<li id="{props.name}"><em>{props.name}</em></li>)
      }

      ReactDOM.render(
        <div>
          <ul>{allNames.map((name) => { return <ListElement name={name} /> })}</ul>
        </div>,
        document.getElementById('app')
      )
    </script>
  </body>
  • Gives us exactly one logical place to do calculations, set style, etc.

Developing with Parcel

  • Putting all the source in the HTML file, in one block, is bad practice
  • Already seen problems with loading source in header
    • Page doesn’t exist yet in memory, so ReactDOM can’t find the element it’s supposed to fill in
  • And what about require statements?
    • Browser tries to load files when it sees those
    • But whose serves them?
  • Solution: use a [bundler]{#g:bundler} to combine everything into one big file
    • And run a server for previewing during development
  • So… many… options…
    • Webpack is probably the most widely used, but it is rather complex
  • We will use Parcel
    • Younger, and therefore not yet bloated, but give it time
  • npm install parcel-bundler
  • parcel serve -p 4000 src/display/pass-parameters.html
    • Looks in the named file to find JavaScript
    • Looks recursively at what that loads, etc.
    • Creates a single file in a directory called ./dist
    • Serves out of there
    • Also caches things in ./.cache
    • So both directories need to be added to .gitignore
  • Very common to add tasks like this to package.json
  "scripts": {
    "dev": "parcel serve -p 4000",
    ...
  },
  • Now use npm run dev -- src/display/pass-parameters.html
    • Everything after -- is passed to the script being run
    • And now other developers have a record of how to use the project
    • Unfortunately, there is no standard way to add comments to a JSON file…
  • If we give the directory name src/display instead of the filename, we get an error message in the console telling us that the character encoding of the document was not recognized
    • Because Parcel is trying to read the actual directory structure as a file

Multiple Files

  • Putting scripts in the body of the page is a bad practice
  • So move the JSX into app.js and load that in the head of the page
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Separate</title>
    <meta charset="utf-8">
    <script src="https://fb.me/react-15.0.1.js"></script>
    <script src="https://fb.me/react-dom-15.0.1.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
    <script src="app.js"></script>
  </head>
  <body>
    <h1>Hello Separate</h1>
    <div id="app"></div>
  </body>
</html>
ReactDOM.render(
  <p>Rendered by React</p>,
  document.getElementById("app")
)
``
{: title="src/display/hello-separate/app.js"}

- Get the `h1` title but *not* the paragraph
- Look in the browser console
  - `Error: _registerComponent(...): Target container is not a DOM element.`
- As mentioned earlier, the script loads and runs before the page has been constructed in memory
  - So ReactDOM can't find something with an ID of `"app"`
- Solution: load the script in the body of the page

```js
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Bottom</title>
    <meta charset="utf-8">
  </head>
  <body>
    <h1>Hello Bottom</h1>
    <div id="app"></div>
    <script src="./app.js"></script>
  </body>
</html>
  • And rewrite app.js so that it loads the libraries it needs
    • Because there’s no guarantee that libraries loaded in head will be available when app.js runs
const React = require('react')
const ReactDOM = require('react-dom')

ReactDOM.render(
  <p>Rendered by React</p>,
  document.getElementById('app')
)
  • Note: don’t have to shut down the server every time we make changes
    • Parcel watches for changes in files
  • Note also: Parcel looks at the libraries app.js loads and bundles them up for us
    • dist/app.ef6b320b.js is 19930 lines
  • A better option than loading in the bottom is to add the async attribute to the script in the head of the page
    • Tells the browser not to do anything with the JavaScript until the page has finished building
    • Added to the HTML spec for exactly this purpose
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Parcel</title>
    <meta charset="utf-8">
    <script src="./app.js" async></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

Exercises

Real Data

  1. Create a file called programmers.js that defines a list of JSON objects called programmers with firstName and lastName fields for our programmers. (You can search the Internet to find their names.)
  2. Load that file in your page like any other JavaScript file.
  3. Delete the list allNames from the application and modify it to use data from the list programmers instead.

Loading constant data like this is a common practice during testing.

Ordering

What happens if you change the order in which the JavaScript files are loaded in your web page? For example, what happens if you load app.js before you load ListElement.js?

Multiple Targets

What happens if your HTML page contains two div elements with id="app"?

Creating a Component for Names

Create a new React component that renders a name, and modify the example to use it instead of always displaying names in <li> elements.

Striping

Suppose we want to render every second list element in italics. (This would be a horrible design, but once we start creating tables, we might want to highlight alternate rows in different background colors to make it easier to read.) Modify the application so that even-numbered list elements are <li>{name}</li> and odd-numbered list elements are <li><em>{name}</em></li>. (You may want to use the fact that a map callback can have two parameters instead of one.)

Key Points

  • Older dynamic web sites generated pages on the server.
  • Newer dynamic web sites generate pages in the client.
  • React is a JavaScript library for client-side page generation that represents HTML elements as function calls.
  • React replaces page elements with dynamically-generated content in memory (not on disk).
  • React functions can be customized with elements.
  • JSX translates HTML into React function calls so that HTML and JavaScript can be mixed freely.
  • Use Babel to translate JSX into JavaScript in the browser.
  • Define new React components with a pseudo-HTML element and a corresponding function.
  • Attributes to pseudo-HTML are passed to the JavaScript function as a props object.