Question:
What is 2015_0926 Code Challenge?
The 2015_0926 Code Challenge is a learning exercise for students attending Dan's Linux JavaScript Class at 3pm on 2015-09-26 at Hacker Dojo:
http://www.meetup.com/Dans-Linux-JavaScript-Class/events/225571816/
We start with three tasks: Install Ubuntu, enhance it, and create account named ann:
Next, we learn some Emacs:
I see the buffer-menu as similar to the Mac-Dock, Ubuntu-Launcher or the Windows-Taskbar.
The buffer-menu is my favorite emacs feature; it allows my mind to QUICKLY switch focus among three types of objects:
- different shells
- different files
- different folders
The buffer-menu floats 'hot' shells, files and folders to the top.
This is useful behavior.
I mostly do 4 types of tasks on a laptop:
- Interact with file
- Interact with folder
- Interact with shell
- Interact with browser
I can do the first three types inside of emacs.
When I coordinate tasks with emacs rather than a Dock, I work much faster (because of the buffer-menu).
That sequence of creating a shell, and renaming it is the most difficult task I need to know.
All other emacs tasks can be done using mouse and arrow keys.
If I am an emacs power-user, I know two ways to use emacs to interact with folders.
The GUI-way is to click on the file-cabinet at the top.
Another way, which is quicker, is to type command ctrl-x then letter 'f'.
I should see something like this:
-
After I learn some Emacs or some other editor,
I Install Node.js in this folder: ~ann/node/
-
cd ~ann
wget https://nodejs.org/dist/v5.0.0/node-v5.0.0-linux-x64.tar.gz
tar zxf node-v5.0.0-linux-x64.tar.gz
rm -rf node
mv node-v5.0.0-linux-x64 node
-
Then, I add Node.js to PATH:
export PATH="/home/ann/node/bin:${PATH}"
echo 'export PATH="/home/ann/node/bin:${PATH}"' >> ~ann/.bashrc
-
Run a test:
which node
node -e 'console.log("hello world")'
-
Install CoffeScript to test npm:
which npm
npm install -g coffee-script
ls -la /home/ann/node/lib/node_modules/
which coffee
coffee -e 'console.log "hello coffee!"'
-
Start work on an app:
cd ~ann
mkdir app13
cd ~ann/app13
mkdir fee haml app
cd ~ann/app13/app
mkdir css img js partials phones
cd ~ann/app13
npm init # Respond with all defaults here
# Now I have ~ann/app13/package.json
# I will change it later.
echo node_modules >> .gitignore
git init
git add .
git commit -am ItsAlive
-
Install HAML to enhance app13:
cd ~ann/app13
echo gems >> .gitignore
mkdir gems
export GEM_HOME=/home/ann/app13/gems
export PATH=${GEM_HOME}/bin:${PATH}
gem install haml
-
With emacs, create ~ann/app13/haml/index.haml
!!!
%html(lang="en" ng-app="")
%head
%meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
%meta(charset="utf-8")/
%title Google Phone Gallery
%link(href="/app/css/bootstrap.css" rel="stylesheet")/
%link(href="/app/css/app.css" rel="stylesheet")/
%script(src="/app/js/angular.js")
%body
%ul
%li
%span Nexus S
%p
Fast just got faster with Nexus S.
%li
%span Motorola XOOM™ with Wi-Fi
%p
The Next, Next Generation tablet.
-
Use haml shell command to create ~ann/app13/app/index.html
haml -eq ~ann/app13/haml/index.haml ~ann/app13/app/index.html
-
HAML should create this:
<!DOCTYPE html>
<html lang="en" ng-app="">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link href="/app/css/bootstrap.css" rel="stylesheet">
<link href="/app/css/app.css" rel="stylesheet">
<script src="/app/js/angular.js"></script>
</head>
<body>
<ul>
<li>
<span>Nexus S</span>
<p>
Fast just got faster with Nexus S.
</p>
</li>
<li>
<span>Motorola XOOM™ with Wi-Fi</span>
<p>
The Next, Next Generation tablet.
</p>
</li>
</ul>
</body>
</html>
Obviously HAML is easier to read than HTML.
Note that the above code comes from AngularJS Tutorial.
Many thanks to angularjs.org
-
I add files needed by ~ann/app13/app/index.html
cd ~ann/app13/app/css
wget http://angular.github.io/angular-phonecat/step-1/app/bower_components/bootstrap/dist/css/bootstrap.css
wget http://angular.github.io/angular-phonecat/step-1/app/css/app.css
cd ~ann/app13/app/js
wget http://angular.github.io/angular-phonecat/step-1/app/bower_components/angular/angular.js
-
I enhnace the app so it can serve the above files:
cd ~ann/app13
npm install --save http-server
node ~ann/app13/node_modules/http-server/bin/http-server
-
With broswer, I visit localhost:8080/app and should see something like this:
- Next, I kill the http-server process to free-up port 8080 on localhost.
-
Now, I work towards deploying this software to Heroku.
I use emacs to enhance ~ann/app13/package.json:
{
"name": "app13",
"version": "1.0.0",
"description": "Demo by Dan",
"main": "node_modules/http-server/bin/http-server",
"scripts": {
"test": "echo 'hello world' && exit"
},
"author": "Dan",
"license": "ISC",
"dependencies": {
"http-server": "^0.8.0"
},
"engines": {
"node": "4.0.0"
}
}
-
Create a one-line Procfile:
cd ~ann/app13/
echo 'web: node node_modules/http-server/bin/http-server -p $PORT' > Procfile
-
Install Heroku Toolbelt (standalone):
dan@dev06:~ $
dan@dev06:~ $
dan@dev06:~ $ cd ~ann
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $ curl https://toolbelt.heroku.com/install.sh | grep tgz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 975 100 975 0 0 1465 0 --:--:-- --:--:-- --:--:-- 1495
HEROKU_CLIENT_URL="https://s3.amazonaws.com/assets.heroku.com/heroku-client/heroku-client.tgz"
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $ wget https://s3.amazonaws.com/assets.heroku.com/heroku-client/heroku-client.tgz
--2015-09-14 18:52:29-- https://s3.amazonaws.com/assets.heroku.com/heroku-client/heroku-client.tgz
Resolving s3.amazonaws.com (s3.amazonaws.com)... 54.231.17.120
Connecting to s3.amazonaws.com (s3.amazonaws.com)|54.231.17.120|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1828732 (1.7M) [application/x-gtar]
Saving to: ‘heroku-client.tgz’
100%[======================================>] 1,828,732 893KB/s in 2.0s
2015-09-14 18:52:32 (893 KB/s) - ‘heroku-client.tgz’ saved [1828732/1828732]
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $ tar zxf heroku-client.tgz
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $ ls -la heroku-client/bin/
total 12
drwxr-xr-x 2 dan dan 4096 Aug 31 23:56 .
drwxr-xr-x 6 dan dan 4096 Aug 31 23:56 ..
-rwxr-xr-x 1 dan dan 644 Aug 31 23:56 heroku
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $ echo export PATH=/home/ann/heroku-client/bin:${PATH} >> ~/.bashrc
dan@dev06:/home/ann $
dan@dev06:/home/ann $ export PATH=/home/ann/heroku-client/bin:${PATH}
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $ heroku help
Usage: heroku COMMAND [--app APP] [command-specific-options]
Primary help topics, type "heroku help TOPIC" for more details:
addons # manage addon resources
apps # manage apps (create, destroy)
auth # authentication (login, logout)
config # manage app config vars
domains # manage domains
logs # display logs for an app
ps # manage dynos (dynos, workers)
releases # manage app releases
run # run one-off commands (console, rake)
sharing # manage collaborators on an app
Additional topics:
buildpacks # manage the buildpack for an app
certs # manage ssl endpoints for an app
drains # display drains for an app
features # manage optional features
fork # clone an existing app
git # manage git for apps
help # list commands and display help
keys # manage authentication keys
labs # manage optional features
local # run heroku app locally
maintenance # manage maintenance mode for an app
members # manage membership in organization accounts
orgs # manage organization accounts
pg # manage heroku-postgresql databases
pgbackups # manage backups of heroku postgresql databases
plugins # manage plugins to the heroku gem
redis # list redis databases for an app
regions # list available regions
stack # manage the stack for an app
status # check status of heroku platform
twofactor # manage two-factor authentication settings
update # update the heroku client
version # display version
dan@dev06:/home/ann $
dan@dev06:/home/ann $
dan@dev06:/home/ann $
-
Create Heroku Account (cost: $0.00!):
https://www.heroku.com
Memorize login and password
-
Create ssh-key for ann account (assuming ann has none yet):
ann@dev06:~$
ann@dev06:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ann/.ssh/id_rsa):
Created directory '/home/ann/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ann/.ssh/id_rsa.
Your public key has been saved in /home/ann/.ssh/id_rsa.pub.
The key fingerprint is:
7c:73:e0:26:8b:62:f6:a4:1d:78:22:e5:cd:3a:5c:eb ann@dev06
The key randomart image is:
+--[ RSA 2048]----+
| |
| |
| . |
| . . . |
| . S = . |
| o +.. = o |
| ..B.B.. |
| +oX.. |
| ooE |
+-----------------+
ann@dev06:~$
ann@dev06:~$
ann@dev06:~$
-
Give a copy of ann public ssh-key to heroku:
heroku status
heroku auth:login
heroku auth:whoami
heroku keys:add
-
Create blank Heroku app and then git-push my app in:
cd ~ann/app13
heroku create ann926
git add .
git commit -am ready4heroku
git push heroku master
I should see something like this:
ann@dev06:~/app13$
ann@dev06:~/app13$
ann@dev06:~/app13$ echo 'web: node node_modules/http-server/bin/http-server -p $PORT' > Procfile
ann@dev06:~/app13$ ls -la ~/heroku-client/bin/
total 12
drwxr-xr-x 2 ann ann 4096 Aug 31 23:56 .
drwxr-xr-x 6 ann ann 4096 Aug 31 23:56 ..
-rwxr-xr-x 1 ann ann 644 Aug 31 23:56 heroku
ann@dev06:~/app13$ heroku auth:whoami
pizza4.us@gmail.com
ann@dev06:~/app13$
ann@dev06:~/app13$
ann@dev06:~/app13$ heroku create ann926
Creating ann926... done, stack is cedar-14
https://ann926.herokuapp.com/ | https://git.heroku.com/ann926.git
Git remote heroku added
updating Heroku CLI...done. Updated to 3.42.2
ann@dev06:~/app13$ git add .
ann@dev06:~/app13$ git commit -am ready4heroku
[master 859e064] ready4heroku
11 files changed, 34575 insertions(+), 4 deletions(-)
create mode 100644 Procfile
create mode 100644 app/css/app.css
create mode 100644 app/css/bootstrap.css
create mode 100644 app/index.html
create mode 100644 app/js/angular.js
create mode 100644 haml/index.haml
ann@dev06:~/app13$ git push heroku master
Counting objects: 22, done.
Delta compression using up to 3 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (22/22), 272.77 KiB | 0 bytes/s, done.
Total 22 (delta 2), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Node.js app detected
remote:
remote: -----> Creating runtime environment
remote:
remote: NPM_CONFIG_LOGLEVEL=error
remote: NPM_CONFIG_PRODUCTION=true
remote: NODE_ENV=production
remote: NODE_MODULES_CACHE=true
remote:
remote: -----> Installing binaries
remote: engines.node (package.json): 4.0.0
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Downloading and installing node 4.0.0...
remote: Using default npm version: 2.14.2
remote:
remote: -----> Restoring cache
remote: Skipping cache (new runtime signature)
remote:
remote: -----> Building dependencies
remote: Pruning any extraneous modules
remote: Installing node modules (package.json)
remote: http-server@0.8.0 node_modules/http-server
remote: ├── opener@1.4.1
remote: ├── corser@2.0.0
remote: ├── colors@1.0.3
remote: ├── http-proxy@1.11.2 (eventemitter3@1.1.1, requires-port@0.0.1)
remote: ├── optimist@0.6.1 (wordwrap@0.0.3, minimist@0.0.10)
remote: ├── portfinder@0.4.0 (async@0.9.0, mkdirp@0.5.1)
remote: ├── union@0.4.4 (qs@2.3.3)
remote: └── ecstatic@0.7.6 (url-join@0.0.1, mime@1.3.4, minimist@1.2.0, he@0.5.0)
remote:
remote: -----> Caching build
remote: Clearing previous node cache
remote: Saving 1 cacheDirectories (default):
remote: - node_modules
remote:
remote: -----> Build succeeded!
remote: └── http-server@0.8.0
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing... done, 12.2MB
remote: -----> Launching... done, v3
remote: https://ann926.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy.... done.
To https://git.heroku.com/ann926.git
* [new branch] master -> master
ann@dev06:~/app13$
ann@dev06:~/app13$
ann@dev06:~/app13$
I visit:
https://ann926.herokuapp.com/app/index.html
It loads. Yay!
Step 2 of the AngularJS tutorial is the next stop in the code challenge:
https://docs.angularjs.org/tutorial/step_02
I follow the steps below to do this:
-
cd ~ann
git clone app13 app14
rsync -a app13/node_modules app14
rsync -a app13/gems app14
cd app14
heroku create ann9262
git push heroku master
-
I enhance ~ann/app14/haml/index.haml
!!!
%html(lang="en" ng-app="phonecatApp")
%head
%meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
%meta(charset="utf-8")/
%title Google Phone Gallery
%link(href="/app/css/bootstrap.css" rel="stylesheet")/
%link(href="/app/css/app.css" rel="stylesheet")/
%script(src="/app/js/angular.js")
%script(src="/app/js/controllers.js")
%body(ng-controller="PhoneListCtrl")
%ul
%li(ng-repeat="phone in phones")
%span {{phone.name}}
%p {{phone.snippet}}
-
A good feature of AngularJS can be seen in the above HAML.
I see three attributes which tell me that AngularJS may be affecting regions of the page.
Many other JS libraries do not offer this feature.
For example When I look at HTML controlled by jQuery,
the HTML does not tell me this fact.
To understand jQuery control of a page, I need to walk my mind through a multi-step procedure.
At the end of this procedure, my mind will have three pieces of information cached:
- Some HTML
- Some CSS
- Some jQuery syntax
At that point, my mind can start writing code.
To me it feels cumbersome and mentally taxing.
-
To see a description of the above attributes (AKA 'Directives'):
- ng-app
- ng-controller
- ng-repeat
I should read this page:
https://docs.angularjs.org/tutorial/step_02
-
Often my concept of %html(lang="en" ng-app="phonecatApp") is simple:
- I have some JS code named "phonecatApp"
- That code affects this html element
-
My concept of %body(ng-controller="PhoneListCtrl") is also simple:
- I have some JS code named "PhoneListCtrl"
- That code affects this body element
- The HAML declares that body element is inside html element
- As developer, I should place PhoneListCtrl-code inside phonecatApp
-
My concept of %li(ng-repeat="phone in phones") is this:
- I have an Angular object named 'phones'
- The 'phones' array should be created by PhoneListCtrl because the li-element is inside the body-element
- The ng-repeat attribute will create an li-element for each phone in the phones array
-
Now that I understand the above HAML better,
I use haml shell command to create ~ann/app14/app/index.html
haml -eq ~ann/app14/haml/index.haml ~ann/app14/app/index.html
-
Next, I create a file of CoffeeScript named ~ann/app14/fee/controllers.coffee
'use strict'
### Controllers ###
phonecatApp = angular.module('phonecatApp', [])
phonecatApp.controller 'PhoneListCtrl', ($scope) ->
$scope.phones = [
{
'name': 'Nexus S'
'snippet': 'Fast just got faster with Nexus S.'
}
{
'name': 'Motorola XOOM™ with Wi-Fi'
'snippet': 'The Next, Next Generation tablet.'
}
{
'name': 'MOTOROLA XOOM™'
'snippet': 'The Next, Next Generation tablet.'
}
]
return
-
The above code helps me:
- It creates object named "phonecatApp"
- It creates object named "PhoneListCtrl" inside "phonecatApp"
- It creates array, $scope.phones inside "PhoneListCtrl"
- $scope.phones will be exposed as 'phones' inside of index.html
-
Now that I understand the above CoffeeScript better,
I transform it into JavaScript using the coffee command:
coffee -bpc ~ann/app14/fee/controllers.coffee > ~ann/app14/app/js/controllers.js
-
I start the local server to see how app14 behaves
cd ~ann/app14
node ~ann/app14/node_modules/http-server/bin/http-server
-
With broswer, I visit localhost:8080/app and should see something like this:
- Next, I kill the http-server process to free-up port 8080 on localhost.
-
Then, I deploy app14 to heroku:
git add .
git commit -am DanWasHere
git push heroku master
-
I should see app14 here:
https://ann9262.herokuapp.com/app/index.html
Step 5 of the AngularJS tutorial is the next stop in the code challenge:
https://docs.angularjs.org/tutorial/step_05
I follow the steps below to do this:
-
cd ~ann
git clone app14 app15
rsync -a app14/node_modules app15
rsync -a app14/gems app15
cd app15
heroku create ann9265
git push heroku master
-
I enhance ~ann/app15/haml/index.haml
!!!
%html(lang="en" ng-app="phonecatApp")
%head
%meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
%meta(charset="utf-8")/
%title Google Phone Gallery
%link(href="/app/css/bootstrap.css" rel="stylesheet")/
%link(href="/app/css/app.css" rel="stylesheet")/
%script(src="/app/js/angular.js")
%script(src="/app/js/app.js")
%body(ng-controller="PhoneListCtrl")
.container-fluid
.row
.col-md-2
/ Sidebar content
Search:
%input(ng-model="query")/
Sort by:
%select(ng-model="orderProp")
%option(value="name") Alphabetical
%option(value="age") Newest
.col-md-10
/ Body content
%ul.phones
%li(ng-repeat="phone in phones | filter:query | orderBy:orderProp")
%span {{phone.name}}
%p {{phone.snippet}}
The head-element is the same as before except that I changed the name of controllers.js to app.js.
The new name suggests I have more than just controller syntax in that file.
-
The next Angular-related enhancement is this syntax:
%input(ng-model="query")/
The above syntax allows the end-user to enter a string which is then referred to as "query".
-
The next Angular-related enhancement is this syntax:
%select(ng-model="orderProp")
%option(value="name") Alphabetical
%option(value="age") Newest
The above syntax allows the end-user to enter a string (either "name" or "age").
That string is then referred to as "orderProp".
-
The next Angular-related enhancement is this syntax:
%li(ng-repeat="phone in phones | filter:query | orderBy:orderProp")
The above syntax allows an end-user to display phone-names which match a string held by the query variable.
After the match is made, the end-user can order the resulting list by name or age.
-
After I enhance ~ann/app15/haml/index.haml
I transform it into this file: ~ann/app15/app/index.html
haml -eq ~ann/app15/haml/index.haml ~ann/app15/app/index.html
The HTML file should look like this:
<!DOCTYPE html>
<html lang="en" ng-app="phonecatApp">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link href="/app/css/bootstrap.css" rel="stylesheet">
<link href="/app/css/app.css" rel="stylesheet">
<script src="/app/js/angular.js"></script>
<script src="/app/js/app.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!-- Sidebar content -->
Search:
<input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</div>
<div class="col-md-10">
<!-- Body content -->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
</body>
</html>
Notice that the HAML file is easier to read than the HTML file
-
Next, I create a CoffeeScript file named ~ann/app15/fee/app.coffee
'use strict'
### app.coffee, app.js ###
phonecatApp = angular.module('phonecatApp', [])
phonecatApp.controller 'PhoneListCtrl', [
'$scope'
'$http'
($scope, $http) ->
$http.get('phones/phones.json').success (data) ->
$scope.phones = data
return
$scope.orderProp = 'age'
return
]
The list below describes my understanding of the above syntax:
- I have an object named 'phonecatApp'
- Inside of phonecatApp I have an object named 'PhoneListCtrl'
-
Inside of PhoneListCtrl I have three objects:
- $scope
- $http
-
Anonymous function which:
- Copies phones/phones.json into $scope.phones
- Sets $scope.orderProp = 'age'
-
Next, I create app.js from app.coffee so that app.js can run in your browser:
coffee -bpc ~ann/app15/fee/app.coffee > ~ann/app15/app/js/app.js
The file app.js should look like this:
// Generated by CoffeeScript 1.10.0
'use strict';
/* app.coffee, app.js */
var phonecatApp;
phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', [
'$scope', '$http', function($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}
]);
-
Then, I get a copy of phones/phones.json so my site can serve it when PhoneListCtrl asks for it:
cd ~ann/app15/app
mkdir phones
curl http://angular.github.io/angular-phonecat/step-5/app/phones/phones.json > phones/phones.json
-
I start the local server to see how app15 behaves
cd ~ann/app15
node ~ann/app15/node_modules/http-server/bin/http-server
-
I should see something like this:
-
Next, I deploy it to Heroku:
cd ~ann/app15
git add .
git commit -am DanWasHere
git push heroku master
-
Then I visit this URL:
https://ann9265.herokuapp.com/app/index.html
-
Step 8 of the AngularJS tutorial is the next stop in the code challenge:
https://docs.angularjs.org/tutorial/step_08
I follow the steps below to do this:
mkdir ~ann/app16
cd ~ann/app16
mkdir app fee haml
echo node_modules > .gitignore
echo 'web: node node_modules/http-server/bin/http-server -p $PORT' > Procfile
cd ~ann/app16/app
mkdir css js partials
cd /tmp
git clone https://github.com/angular/angular-phonecat.git phonecat
cd phonecat
git checkout step-8
cp app/css/app.css ~ann/app16/app/css/
rsync -a app/img ~ann/app16/app
rsync -a app/phones ~ann/app16/app
-
Next, I attach Node.js modules to app16:
cd ~ann/app16
npm init # Creates node_modules
npm install --save http-server
-
Then, I copy ~ann/app13/package.json into ~ann/app16/package.json so it has syntax below:
{
"name": "app13",
"version": "1.0.0",
"description": "Demo by Dan",
"main": "node_modules/http-server/bin/http-server",
"scripts": {
"test": "echo 'hello world' && exit"
},
"author": "Dan",
"license": "ISC",
"dependencies": {
"http-server": "^0.8.0"
},
"engines": {
"node": "4.0.0"
}
}
-
Next, I wget software from twitter and angularjs.org:
cd ~ann/app16/app/css
wget http://angular.github.io/angular-phonecat/step-8/app/bower_components/bootstrap/dist/css/bootstrap.css
cd ~ann/app16/app/js
wget http://angular.github.io/angular-phonecat/step-8/app/bower_components/angular/angular.js
wget http://angular.github.io/angular-phonecat/step-8/app/bower_components/angular-route/angular-route.js
-
Then, I create a HAML file ~ann/app16/haml/index.haml
!!!
%html(lang="en" ng-app="phonecatApp")
%head
%meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
%meta(charset="utf-8")/
%title Google Phone Gallery
%link(href="/app/css/bootstrap.css" rel="stylesheet")/
%link(href="/app/css/app.css" rel="stylesheet")/
%script(src="/app/js/angular.js")
%script(src="/app/js/angular-route.js")
%script(src="/app/js/app.js")
%body
%div(ng-view="")
-
The above HAML asks your browser to load angular-route.js which provides an API which allows me,
the developer, to change page-content when the URL field changes in your browser.
-
Also the above HAML contains the ng-view attribute:
%div(ng-view="")
The ng-view attribute is part of an AngularJS mechanism which adds modular behavior to HTML styntax.
The most common modular behavior which I want is 'code reuse'.
In this app, the code I want to reuse is the syntax in the head-element of the above HAML.
The code I want to be changeable is in the body-element.
I will control this changeable code with the help of ng-view attribute, my code in app.js,
and HTML files I put under ~ann/app16/app/partials
-
Then, I create a HAML file ~ann/app16/haml/phone-list.haml
.container-fluid
.row
.col-md-2
/ Sidebar content
Search:
%input(ng-model="query")/
Sort by:
%select(ng-model="orderProp")
%option(value="name") Alphabetical
%option(value="age") Newest
.col-md-10
/ Body content
%ul.phones
%li.thumbnail(ng-repeat="phone in phones | filter:query | orderBy:orderProp")
%a.thumb(href="#/phones/{{phone.id}}")
%img(ng-src="{{phone.imageUrl}}")/
%a(href="#/phones/{{phone.id}}") {{phone.name}}
%p {{phone.snippet}}
The only new syntax in the above HAML is the ng-src attribute in the img-element:
%img(ng-src="{{phone.imageUrl}}")/
It is obvious what that attribute does. It creates a src-attribute for %img.
Also ng-src offers a performance enhancement.
It creates the src-attribute BEFORE the page loads into your browser.
-
Next, I create a HAML file ~ann/app16/haml/phone-detail.haml
%img.phone(ng-src="{{phone.images[0]}}")/
%h1 {{phone.name}}
%p {{phone.description}}
%ul.phone-thumbs
%li(ng-repeat="img in phone.images")
%img(ng-src="{{img}}")/
%ul.specs
%li
%span Availability and Networks
%dl
%dt Availability
%dd(ng-repeat="availability in phone.availability") {{availability}}
%li
%span Battery
%dl
%dt Type
%dd {{phone.battery.type}}
%dt Talk Time
%dd {{phone.battery.talkTime}}
%dt Standby time (max)
%dd {{phone.battery.standbyTime}}
%li
%span Storage and Memory
%dl
%dt RAM
%dd {{phone.storage.ram}}
%dt Internal Storage
%dd {{phone.storage.flash}}
%li
%span Connectivity
%dl
%dt Network Support
%dd {{phone.connectivity.cell}}
%dt WiFi
%dd {{phone.connectivity.wifi}}
%dt Bluetooth
%dd {{phone.connectivity.bluetooth}}
%dt Infrared
%dd {{phone.connectivity.infrared}}
%dt GPS
%dd {{phone.connectivity.gps}}
%li
%span Android
%dl
%dt OS Version
%dd {{phone.android.os}}
%dt UI
%dd {{phone.android.ui}}
%li
%span Size and Weight
%dl
%dt Dimensions
%dd(ng-repeat="dim in phone.sizeAndWeight.dimensions") {{dim}}
%dt Weight
%dd {{phone.sizeAndWeight.weight}}
%li
%span Display
%dl
%dt Screen size
%dd {{phone.display.screenSize}}
%dt Screen resolution
%dd {{phone.display.screenResolution}}
%dt Touch screen
%dd {{phone.display.touchScreen}}
%li
%span Hardware
%dl
%dt CPU
%dd {{phone.hardware.cpu}}
%dt USB
%dd {{phone.hardware.usb}}
%dt Audio / headphone jack
%dd {{phone.hardware.audioJack}}
%dt FM Radio
%dd {{phone.hardware.fmRadio}}
%dt Accelerometer
%dd {{phone.hardware.accelerometer}}
%li
%span Camera
%dl
%dt Primary
%dd {{phone.camera.primary}}
%dt Features
%dd {{phone.camera.features.join(', ')}}
%li
%span Additional Features
%dd {{phone.additionalFeatures}}
The only new syntax in the above HAML is the join() method:
%dd {{phone.camera.features.join(', ')}}
The above syntax creates a string from an array.
-
Then, I create HTML files from the above HAML files:
cd ~ann/app16/haml
haml -eq index.haml ~ann/app16/app/index.html
haml -eq phone-list.haml ~ann/app16/app/partials/phone-list.html
haml -eq phone-detail.haml ~ann/app16/app/partials/phone-detail.html
-
Then, I add CoffeeScript syntax to a file named ~ann/app16/fee/app.coffee
'use strict'
### app.coffee, app.js ###
phonecatApp = angular.module('phonecatApp', [
'ngRoute'
'phonecatControllers'
])
phonecatApp.config [
'$routeProvider'
($routeProvider) ->
$routeProvider.when('/phones',
templateUrl: 'partials/phone-list.html'
controller: 'PhoneListCtrl').when('/phones/:phoneId',
templateUrl: 'partials/phone-detail.html'
controller: 'PhoneDetailCtrl').otherwise redirectTo: '/phones'
return
]
phonecatControllers = angular.module('phonecatControllers', [])
phonecatControllers.controller 'PhoneListCtrl', [
'$scope'
'$http'
($scope, $http) ->
$http.get('phones/phones.json').success (data) ->
$scope.phones = data
return
$scope.orderProp = 'age'
return
]
phonecatControllers.controller 'PhoneDetailCtrl', [
'$scope'
'$routeParams'
'$http'
($scope, $routeParams, $http) ->
$http.get('phones/' + $routeParams.phoneId + '.json').success (data) ->
$scope.phone = data
return
return
]
In the above file the most interesting syntax is the call to:
phonecatApp.config
The above syntax uses a concept I call 'route'.
A route has four ideas:
- A path (which will appear in the browser URL field)
- A templateUrl (HTML served in response to path you type into URL field)
- A controller (JavaScript which runs also in response to path)
Know that a path can contain a static part and a parameter.
The value of the parameter can be fed to a controller.
-
With the above ideas in my mind, I can explain the English logic behind the call to phonecatApp.config().
-
If I set path to /phones,
Angular serves partials/phone-list.html
My browser runs JS in PhoneListCtrl
-
If I set path to /phones/myPhone,
Angular serves partials/phone-detail.html
My browser runs JS in PhoneDetailCtrl
-
If I set path to /helloWorld,
Angular sets path to /phones
-
Next, I create app.js from app.coffee
coffee -bpc ~ann/app16/fee/app.coffee > ~ann/app16/app/js/app.js
-
At this point I have all the syntax I need to run this app.
Time to commit it to git:
cd ~ann/app16/
git init
git add .
git commit -am helloApp16
-
Then I deploy it to heroku
heroku create ann9268
git push heroku master
-
I should see my app at this URL:
https://ann9268.herokuapp.com/app/index.html
-
Step 12 of the AngularJS tutorial is the next stop in the code challenge:
https://docs.angularjs.org/tutorial/step_012
I follow the steps below to do this:
mkdir ~ann/tmp
cd ~ann/tmp
git clone https://github.com/angular/angular-phonecat.git phonecat
cd ~ann/tmp/phonecat
git checkout step-12
mkdir -p ~ann/app17/app/js
cd ~ann/tmp/phonecat/app
rsync -a css img phones ~ann/app17/app
cd ~ann/app17
mkdir fee haml
echo node_modules > .gitignore
echo 'web: node node_modules/http-server/bin/http-server -p $PORT' > Procfile
cp ~ann/app13/package.json .
cd ~ann/app17/app/css
wget http://angular.github.io/angular-phonecat/step-12/app/bower_components/bootstrap/dist/css/bootstrap.css
cd ~ann/app17/app/js
wget http://angular.github.io/angular-phonecat/step-12/app/bower_components/jquery/dist/jquery.js
wget http://angular.github.io/angular-phonecat/step-12/app/bower_components/angular/angular.js
wget http://angular.github.io/angular-phonecat/step-12/app/bower_components/angular-animate/angular-animate.js
wget http://angular.github.io/angular-phonecat/step-12/app/bower_components/angular-route/angular-route.js
wget http://angular.github.io/angular-phonecat/step-12/app/bower_components/angular-resource/angular-resource.js
-
Then, I create a HAML file ~ann/app17/haml/index.haml
!!!
%html(lang="en" ng-app="phonecatApp")
%head
%meta(content="text/html; charset=UTF-8" http-equiv="Content-Type")/
%meta(charset="utf-8")/
%title Google Phone Gallery
%link(href="/app/css/bootstrap.css" rel="stylesheet")/
%link(href="/app/css/app.css" rel="stylesheet")/
%link(href="/app/css/animations.css" rel="stylesheet")/
%script(src="/app/js/jquery.js")
%script(src="/app/js/angular.js")
%script(src="/app/js/angular-animate.js")
%script(src="/app/js/angular-route.js")
%script(src="/app/js/angular-resource.js")
%script(src="/app/js/app.js")
%body
.view-container
.view-frame(ng-view="")
The above syntax contains no new AngularJS API calls.
It does however ask the browser to load some new third party JS files.
-
Next, I create a HAML file ~ann/app17/haml/phone-list.haml
.container-fluid
.row
.col-md-2
/ Sidebar content
Search:
%input(ng-model="query")/
Sort by:
%select(ng-model="orderProp")
%option(value="name") Alphabetical
%option(value="age") Newest
.col-md-10
/ Body content
%ul.phones
%li.thumbnail.phone-listing(ng-repeat="phone in phones | filter:query | orderBy:orderProp")
%a.thumb(href="#/phones/{{phone.id}}")
%img(ng-src="{{phone.imageUrl}}")/
%a(href="#/phones/{{phone.id}}") {{phone.name}}
%p {{phone.snippet}}
-
Then, I create a HAML file ~ann/app17/haml/phone-detail.haml
.phone-images
%img.phone(ng-class="{active: mainImageUrl==img}" ng-repeat="img in phone.images" ng-src="{{img}}")/
%h1 {{phone.name}}
%p {{phone.description}}
%ul.phone-thumbs
%li(ng-repeat="img in phone.images")
%img(ng-click="setImage(img)" ng-src="{{img}}")/
%ul.specs
%li
%span Availability and Networks
%dl
%dt Availability
%dd(ng-repeat="availability in phone.availability") {{availability}}
%li
%span Battery
%dl
%dt Type
%dd {{phone.battery.type}}
%dt Talk Time
%dd {{phone.battery.talkTime}}
%dt Standby time (max)
%dd {{phone.battery.standbyTime}}
%li
%span Storage and Memory
%dl
%dt RAM
%dd {{phone.storage.ram}}
%dt Internal Storage
%dd {{phone.storage.flash}}
%li
%span Connectivity
%dl
%dt Network Support
%dd {{phone.connectivity.cell}}
%dt WiFi
%dd {{phone.connectivity.wifi}}
%dt Bluetooth
%dd {{phone.connectivity.bluetooth}}
%dt Infrared
%dd {{phone.connectivity.infrared | checkmark}}
%dt GPS
%dd {{phone.connectivity.gps | checkmark}}
%li
%span Android
%dl
%dt OS Version
%dd {{phone.android.os}}
%dt UI
%dd {{phone.android.ui}}
%li
%span Size and Weight
%dl
%dt Dimensions
%dd(ng-repeat="dim in phone.sizeAndWeight.dimensions") {{dim}}
%dt Weight
%dd {{phone.sizeAndWeight.weight}}
%li
%span Display
%dl
%dt Screen size
%dd {{phone.display.screenSize}}
%dt Screen resolution
%dd {{phone.display.screenResolution}}
%dt Touch screen
%dd {{phone.display.touchScreen | checkmark}}
%li
%span Hardware
%dl
%dt CPU
%dd {{phone.hardware.cpu}}
%dt USB
%dd {{phone.hardware.usb}}
%dt Audio / headphone jack
%dd {{phone.hardware.audioJack}}
%dt FM Radio
%dd {{phone.hardware.fmRadio | checkmark}}
%dt Accelerometer
%dd {{phone.hardware.accelerometer | checkmark}}
%li
%span Camera
%dl
%dt Primary
%dd {{phone.camera.primary}}
%dt Features
%dd {{phone.camera.features.join(', ')}}
%li
%span Additional Features
%dd {{phone.additionalFeatures}}
-
Next, I create HTML files from the above HAML files:
cd ~ann/app17/haml
haml -eq index.haml ~ann/app17/app/index.html
mkdir ~ann/app17/app/partials
haml -eq phone-list.haml ~ann/app17/app/partials/phone-list.html
haml -eq phone-detail.haml ~ann/app17/app/partials/phone-detail.html
-
Then, I add CoffeeScript syntax to a file named ~ann/app17/fee/app.coffee
'use strict'
### app.coffee, app.js ###
phonecatApp = angular.module('phonecatApp', [
'ngRoute'
'phonecatAnimations'
'phonecatControllers'
'phonecatFilters'
'phonecatServices'
])
phonecatApp.config [
'$routeProvider'
($routeProvider) ->
$routeProvider.when('/phones',
templateUrl: 'partials/phone-list.html'
controller: 'PhoneListCtrl').when('/phones/:phoneId',
templateUrl: 'partials/phone-detail.html'
controller: 'PhoneDetailCtrl').otherwise redirectTo: '/phones'
return
]
### Controllers ###
phonecatControllers = angular.module('phonecatControllers', [])
phonecatControllers.controller 'PhoneListCtrl', [
'$scope'
'Phone'
($scope, Phone) ->
$scope.phones = Phone.query()
$scope.orderProp = 'age'
return
]
phonecatControllers.controller 'PhoneDetailCtrl', [
'$scope'
'$routeParams'
'Phone'
($scope, $routeParams, Phone) ->
$scope.phone = Phone.get({ phoneId: $routeParams.phoneId }, (phone) ->
$scope.mainImageUrl = phone.images[0]
return
)
$scope.setImage = (imageUrl) ->
$scope.mainImageUrl = imageUrl
return
return
]
### Filters ###
angular.module('phonecatFilters', []).filter 'checkmark', ->
(input) ->
if input then '\u2713' else '\u2718'
### animations ###
phonecatAnimations = angular.module('phonecatAnimations', [ 'ngAnimate' ])
phonecatAnimations.animation '.phone', ->
animateUp = (element, className, done) ->
if className != 'active'
return
element.css
position: 'absolute'
top: 500
left: 0
display: 'block'
jQuery(element).animate { top: 0 }, done
(cancel) ->
if cancel
element.stop()
return
animateDown = (element, className, done) ->
if className != 'active'
return
element.css
position: 'absolute'
left: 0
top: 0
jQuery(element).animate { top: -500 }, done
(cancel) ->
if cancel
element.stop()
return
{
addClass: animateUp
removeClass: animateDown
}
### Services ###
phonecatServices = angular.module('phonecatServices', [ 'ngResource' ])
phonecatServices.factory 'Phone', [
'$resource'
($resource) ->
$resource 'phones/:phoneId.json', {}, query:
method: 'GET'
params: phoneId: 'phones'
isArray: true
]
-
Next, I create app.js from app.coffee
coffee -bpc ~ann/app17/fee/app.coffee > ~ann/app17/app/js/app.js
-
At this point I have all the syntax I need to run this app.
Time to commit it to git:
cd ~ann/app17/
git init
git add .
git commit -am helloApp17
-
Then I deploy it to heroku
heroku create ann92612
git push heroku master
-
I should see my app at this URL:
https://ann92612.herokuapp.com/app/index.html
That could be considered an adequate code challenge for a two hour Meetup.
If you have questions, e-me:
bikle101@gmail.com
|