Install Elixir and Erlang (see guides page)
sudo apt install elixir=1.8.0-1 esl-erlang=1:21.2.3-1
Clone repo:
# clone git repo
$ git clone https://github.com/NatTuck/elixir-practice.git
$ cd elixir-practice
# fetch deps
$ mix deps.get
$ (cd assets && npm install)
# run local dev server
$ mix phx.server
HTTP story for doubling a number.
We can think of a HTTP request as being like a function call. The server needs to take inputs (request path, query string, post data) and produce an output (the response).
We can think of Phoenix as being a tool that helps us define those functions.
The basic flow in Phoenix is:
request
|> router
|> pipeline
|> controller action
|> view
|> template
Let's walk through that for doubling a number.
Note that most of the logic is in lib/pratice_web, where the web-specific stuff lives, but that we eventually call out to a file outside that directory to actually double the number. Our app could conceptually end up having, e.g., a command line interface that doesn't use HTTP and the code to double a number would still be used.
Set up deployment environment on dev server.
# wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
# apt update
# apt install -y build-essential
# apt install -y esl-erlang elixir
# apt install -y nodejs npm
# apt install -y postgresql postgresql-client libpq-dev
Deploy hw02 app on demo server
$ sudo su -
# apt install -y pwgen
# pwgen 12 1
<random pw>
# adduser practice
...
New password: <random pw>
Retype new password: <random pw>
...
Full name: Practice App
...
# sudo su - practice
$ git clone https://github.com/NatTuck/elixir-practice.git
# cd ~practice/elixir-practice
# cp practice.nginx /etc/nginx/sites-available/practice.ironbeard.com
# cd /etc/nginx
# view sites-available/practice.ironbeard.com
# ln -s /etc/nginx/sites-available/practice.ironbeard.com sites-enabled
# service nginx restart
# sudo su - practice
$ cd elixir-practice
$ view deploy.sh
$ ./deploy.sh
Here's our first sample app (show it).
Let's build it.
$ mix phx.new hangman --no-ecto
$ cd hangman
$ mix phx.server
$ cd assets
$ rm static/images/phoenix.png
$ rm css/phoenix.css
$ npm install --save milligram
css/app.css
# remove @import "./phoenix.css"
@import "~milligram/dist/milligram";
add "resolve" block to webpack config:
//},
resolve: {
extensions: ['.js', '.jsx', '.css'],
},
//plugins: [
In lib/hangman_web/templates/layout/app.html.eex:
Some applications can benefit from having dynamic content on a single page, without further page loads.
React uses plan B:
React was developed at Facebook because this design works well with how HTML is actually displayed in browsers, and has become one of the most popular client-side rendering systems. It also happens to work really well with our plan to use Elixir and Phoenix for server-side logic.
In order to produce DOM trees as its output, React has a kind of weird seeming plan: Instead of normal templates, it uses a JavaScript extension called JSX to embed XML directly in JavaScript code. Luckly, babel can handle JSX.
(in assets)
$ npm install --save-dev @babel/preset-env @babel/preset-react
$ npm install --save react react-dom
$ npm install --save lodash jquery # common libraries
Add react preset to babel in webpack config:
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
In lib/hangman_web/templates/page/index.html.eex
Replace the contents with:
<div class="row">
<div class="column">
<h1>TODO</h1>
<div id="root">
React component loading...
</div>
</div>
</div>
Call your new component from assets/js/app.js:
// Add to bottom of app.js
import todo_init from "./todo";
window.addEventListener("load", (_ev) => {
let root = document.getElementById('root');
if (root) {
todo_init(root);
}
});
TODO state:
TODO rendering:
Bulleted list
toggle buttons
new item form.
Write todo.jsx
Create assets/js/hangman.jsx:
import React from 'react';
import ReactDOM from 'react-dom';
export default function hangman_init(root) {
ReactDOM.render(<Hangman />, root);
}
class Hangman extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<h2>Hangman loaded.</h2>
</div>;
}
}
Call your new component from assets/js/app.js:
// Add to bottom of app.js
import hangman_init from "./hangman";
window.addEventListener("load", (_ev) => {
let root = document.getElementById('root');
if (root) {
hangman_init(root);
}
});
The game:
The game state: