For the first tutorial, we're just going to work with JavaScript ES5 (plus `let`) and the DOM API.
For the second tutorial, we're going to work with SVG, and CSS via SASS.
As you'll see later, what we're doing has similar principles to the way modern web frameworks work — we're going to synchronise a JavaScript array with what is showing on the screen. But we're going to do it manually.
The tutorial is quite short, because you have another task: choosing a game you're going to build and thinking about the data structures you will need to model it.
## Clone the code
First, get the code and put it somewhere you can load it in your browser. If you are working on turing (our student development machine), you can put it inside a folder in your `public_html` directory and access it from http://turing.une.edu.au/~yourusername/yourpath
If you're working locally, you can install a web server, or you can run the [examples server](https://github.com/UNEadvancedweb/client-example-server-p4s) written in Play — a framework you'll see later in the unit.
First, get the code and put it somewhere you can load it in your browser.
To get the code, open a terminal, `cd` into the public directory of whichever webserver you are using, and:
This will clone this code using the git version control system. That will, amongst other things, let you switch between different branches (eg, the solution and the start of the exercise)
## Checkout the *solution*
First, just to check everything is working, check out the solution to this part:
```sh
git checkout solution
```
Now open index.html in your web browser. You should be shown a grid of dots and a Step button. Clicking on a dot should turn it into a hash. Try drawing some shapes and clicking step to step the game of life forward.
## Checkout the *master* branch
## Check it's working
Let's now check out the master branch to get back to the beginning of the exercise
```sh
git checkout master
```
Now hit refresh in the browser, and you should find yourself faced with a disappointingly empty screen.
Open `index.html` in the browser, and you should find yourself faced with Conway's Game of Life from Tutorial 1.
Time to open the code. I recommedn Visual Studio Code as an editor for this.
Take a look in gameOfLife.js and see the game itself. Have a look at how it's implemented, and what functions are available.
## Change the game to use SVG
Your first challenge is to change the game so that instead of using a grid of dots and hashes, it uses SVG elements to render the game a little more prettily.
Then take a look in index.html. This contains a `div` element where the game board is going to be rendered:
...
...
@@ -49,118 +33,52 @@ Then take a look in index.html. This contains a `div` element where the game boa
</div>
```
It also loads the JavaScript files and does some initialisation.
We're going to need to change that to an svg element
However, `render()` is not implemented. Your first task in the tutorial is to implement `render`.
### Implementing `render`
Next, we need to alter render.js
First, let's blank the contents of the board. Get a reference to the game element:
1. Let's name a couple of constants.
```js
letgameDiv=document.getElementById("game")
let xmlns = "http://www.w3.org/2000/svg"
let cellSize = 20
```
We can delete its contents quickly by setting its `innerHTML` blank:
2. In render, in the outer loop, change the rows it inserts from `div` elements to SVG `g` elements. Remember you'll need to use `document.createElementNS` and give it the SVG namespace
```js
gameDiv.innerHTML=""
```
Next, we're going to loop across every cell in the game and create a new element for it. We'll create a `div` element for every row, and within each row, we'll create a `span` element for every cell.
This means we'll need to make the outer loop do the rows (the Y axis), so a basic loop (not doing anything) would be:
3. In render, in the inner loop, change the cells it inserts from `span` elements to SVG `rect` elements. Again you'll need to use the namespace. Set the attributes on each `rect`:
```js
for (lety=0;y<gameOfLife.getH();y++){
for (letx=0;x<gameOfLife.getW();x++){
}
}
t.setAttribute("x", x * cellSize)
t.setAttribute("y", y * cellSize)
t.setAttribute("width", cellSize)
t.setAttribute("height", cellSize)
t.classList.add("cell")
```
Now, within the code, first create a `div` for each row.
Save your code, reload it in the browser, and inspect the HTML to see if the row divs were created. (They'll be empty so you'll need to inspect the HTML to see them.)
4. The code that set `innerHTML` on the old `span` elements isn't needed. Instead, add the `alive` class to each `rect` if the corresponding cell is alive.
Next, in the inner loop, create a `span` element for each cell.
Reload the page, and you will probably see a large black rectangles. We have our rectangles, but they are all black and unstyled.
Again, reload your code to see if that worked.
Next, if the cell is alive, put a text node of `#` in the `span`. Otherwise put a `.` in the span. You can discover if a cell is alive by calling `gameOfLife.isAlive(x, y)`.
Reload your code and see if it works. You should see a grid of dots, because initially none of the cells are alive.
### Adding an event handler
Let's wire up an event handler so that clicking a cell will toggle its alive state.
Inside the innermost loop, create an event handler:
```js
lethandler=function(evt){
gameOfLife.toggleCell(x,y)
render()
}
```
And attach this handler to listen to the `mousedown` event on the span element.
```js
span.addEventListener("mousedown",handler)
```
Reload your code and see if clicking cells toggles them.
Next, let's wire the button up to step the game forward. In index.html, give the button an `onclick` attribute:
For the moment, let's do the minimum to get the game rendering nicely. In `index.html` paste the following stylesheet tag:
Reload, and see if you can draw patterns and step the game.
### The problem with var
Just to reinforce the difference between `var` and `let` in JavaScript, edit your nested for-loops in render.js to
```js
for (vary=0;y<gameOfLife.getH();y++){
// etc
for (varx=0;x<gameOfLife.getW();x++){
// etc
<style>
.cell{
fill:#ddd;
stroke:#444;
cursor:pointer;
}
.cell.alive{
fill:#88f;
}
</style>
```
Reload, and you should find the game no longer works.
Set a breakpoint in the event handler, and add a watch expression on `y` and `x`. No matter which cell you click, `y` and `x` are the same and one larger than the maximum index of the board. Because we used `var`, `x` and `y` in the loop have *function scope* instead of *block scope*, and every event handler is using the same variable.
Best reset that to use `let` and block scope so it works again!
### Conclusion
What we've done manually in this tutorial is render some elements based on some data held in JavaScript. Our event handlers call functions that update our *JavaScript game state*, and then we re-render the HTML based on the state of that JavaScript data.
In principle, this is very similar to how many modern web frameworks work -- you design your code to update data in plain-old JavaScript objects, and then the framework synchronises the HTML elements to match the data.
However, we're doing this manually, and it's quite a lot of code that doesn't look like HTML. For example:
```js
letrow=document.createElement("div")
row.setAttribute("class","gamerow")
gameDiv.appendChild(row)
```
Now reload the page, and you should have a slightly prettier Conway's Game of Life. (But only slightly)
When we introduce client-side frameworks, they will let us describe how to render our HTML from our JavaScript state in ways that look more component-oriented and use friendlier notations.