Rundown of all of the code within our website (First version..Different to final version) - Alna132/GO-Group-Web-App GitHub Wiki
- We backtracked from using Macaron as we were having issues with getting the CSS to show up. We instead used Angular JS by placing 'ng-app' within the HTML tag to allow HTML to become the route element for AngularJS. All AngularJS applications must have a root element. Only one instance allowed, if there are more than one instances included then the first one will be used.
<html ng-app>
- To use a template from Bootstrap you include a link to it within the head tags in your HTML page:
<head>
<title>WorkTracker</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
- After a few attempts on getting the layout right we decided to display a header and a welcome message first:
<body>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<!--Changed the top header here to include a name being entered and shown back to the user with a friendly hello-->
<div class="container">
<h2><input type="text" placeholder="Your Name here" ng-model="yes"></h2>
<h3><p>Hello <span ng-bind="yes"></span></p></h3>
<p>This is a place where you can record all of your daily duties in one place, ready to show the boss. </p>
</div>
</div>
-
As you can see we use a placeholder which includes a hint for the user on what to write into the textbox. We use Angular here to bind to the page what the user writes into the textbox using ng-model and ng-bind.
-
We then add a navbar, fixed to the top of the page. We include the title of the webapp (WorkTracker) using navbar-brand and then we use the navbar form controls to include a Username and Password textbox with a Login button. The Login button is of type "btn-success" to give it it's green colour.
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Work Tracker</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form navbar-right">
<div class="form-group">
<input type="text" placeholder="Email" class="form-control">
</div>
<div class="form-group">
<input type="password" placeholder="Password" class="form-control">
</div>
<button type="submit" class="btn btn-success">Sign in</button>
</form>
</div><!--/.navbar-collapse -->
</div>
</nav>
- We then include a dropdown list to display our different Tradesmen's jobs:
<div class="row">
<div class="col-md-4">
<h2>Job Title</h2>
<!-- Dropdown list with Tradesperson's job titles
adapted from https://www.socketloop.com/tutorials/golang-populate-dropdown-with-html-template-example -->
<select>
<option value="Beautician">Beautician</option>
<option value="Builder">Builder</option>
<option value="Carpenter">Carpenter</option>
<option value="Cleaner">Cleaner</option>
<option value="Delivery">Delivery</option>
<option value="Electrician">Electrician</option>
<option value="Farmer">Farmer</option>
<option value="Gardener">Gardener</option>
<option value="Hair and Beauty">Hair and Beauty</option>
<option value="Hairdresser">Hairdresser</option>
<option value="Mechanic">Mechanic</option>
<option value="Painter">Painter</option>
<option value="Plumber">Plumber</option>
<option value="Technician">Technician</option>
<option value="Tiler">Tiler</option>
<option value="Transport">Transport</option>
<option value="Other">Other</option>
</select>
- And an "Hours Worked" dropdown list:
<!-- Code adapted from http://www.w3schools.com/html/html_form_elements.asp -->
<div class="col-md-4">
<h2>Hours Worked This Week</h2>
<select name="HoursWorked">
<option value="Less than 10 hours">Less than 10 Hours</option>
<option value="10-15">10-15 Hours</option>
<option value="15-20">15-20 Hours</option>
<option value="25-30">25-30 Hours</option>
<option value="30-40">30-40 Hours</option>
<option value="40+">40+ Hours</option>
</select>
- We included our "Additional Information" textbox and added a function to send the form:
<div class="col-md-4">
<h2>Additonal Information</h2>
<!--Adapted from http://www.w3schools.com/tags/tag_input.asp-->
<form action="demo_form.asp">
<input type="text" name="AddInfo" value=""><br>
<!--<input type="submit" value="Submit"> -->
<button onclick="sendFunction()">Send Form</button>
<script>
function sendFunction()
{
alert("Thank you for sending us your details!");
}
</script>
<button onclick="clearFunction()">Clear</button>
- Lastly, we thought it fitting to include a logo for our 'company' as a footer:
<footer>
<p>© 2016 WorkTracker, Inc.</p>
</footer>
- At the bottom of our html page we include a reference to the angularjs library to be able to use it within our website:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
- Inside the package main we first had to import a number of packages into our app:
package main
import (
//"flag"
"fmt"
//"log" // It defines a type, Logger, with methods for formatting output.
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/securecookie"
)
- We straight away use our gorilla package to handle cookies for the website:
var cookieHandler = securecookie.New(
securecookie.GenerateRandomKey(64),
securecookie.GenerateRandomKey(32))
- We then handle getting the username using these cookies for the login:
func getUserName(request *http.Request) (userName string) {
if cookie, err := request.Cookie("session"); err == nil {
cookieValue := make(map[string]string)
if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
userName = cookieValue["name"]
}
}
return userName
}
- We then save this username by using the http ResponseWriter to map the username string and encodes the value to store in a cookie:
func setSession(userName string, response http.ResponseWriter) {
value := map[string]string{
"name": userName,
}
if encoded, err := cookieHandler.Encode("session", value); err == nil {
cookie := &http.Cookie{
Name: "session",
Value: encoded,
Path: "/",
}
http.SetCookie(response, cookie)
}
}
- Now we create a function called clearSession which we will call later to allow the user to logout of the webpage:
//Returns to indexPage and clears cookies
func clearSession(response http.ResponseWriter) {
cookie := &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
}
http.SetCookie(response, cookie)
}
We set the MaxAge property (the length of time the cookie has before expiring) to -1 in order to allow the page not to be cached, forcing the user to have to log back in.
- In order to understand how we only allow logged in users to see the content the website has to offer I need to digress: We set the non-logged in path to be "/" above. Now we specify an "Internal" page so that only the logged in user has access, We make a httpRequest for the name and password of the user and redirect them to the internal page after checking their credentials:
func loginHandler(response http.ResponseWriter, request *http.Request) {
name := request.FormValue("name")
pass := request.FormValue("password")
redirectTarget := "/"
if name != "" && pass != "" {
// .. check credentials ..
setSession(name, response)
redirectTarget = "/internal"
}
http.Redirect(response, request, redirectTarget, 302)
}
- Now we use the clearSession function to handle the user logging out of the website and back to the "/" path so that the user will not have access to or see the Internal page functionality anymore:
// logout handler logs the current user out
func logoutHandler(response http.ResponseWriter, request *http.Request) {
clearSession(response)
http.Redirect(response, request, "/", 302)
}
- This is our Index page or as I said above the "/" path the non-logged-in user will see:
const indexPage = `
<!-- Incorporating some HTML -->
<h1>Login</h1>
<head>
<!-- Nav bar -->
<form class="navbar-form navbar-left">
<title>WorkTracker</title>
</form>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<div class="container">
</div>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Work Tracker</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<small>User: jjj</small>
<form method="post" action="/login">
<input type="text" placeholder="Name" id="name" name="name">
<input type="password" placeholder="Password" id="password" name="password">
<button type="submit" class="btn btn-success">Login</button>
</form>
</div><!--/.navbar-collapse -->
</div>
</nav>
`
- We need to handle our Index Html code now in this next function:
func indexPageHandler(response http.ResponseWriter, request *http.Request) {
fmt.Fprintf(response, indexPage)
}
- We then specify what the user will see within the internal page:
const internalPage = `
<h1>Internal</h1>
<hr>
<small>User: %s</small>
<html ng-app> <!-- 'ng-app'' placed within a tag (in this case, the HTML tag)
allows HTML to become the route element for AngularJS.
All AngularJS applications must have a root element. Only one instance allowed. -->
<head>
<title>WorkTracker</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<!--Changed the top header here to include a name being entered and shown back to the user with a friendly hello-->
<div class="container">
<h2><input type="text" placeholder="Your Name here" ng-model="yes"></h2>
<h3><p>Hello <span ng-bind="yes"></span></p></h3>
<p>This is a place where you can record all of your daily duties in one place, ready to show the boss. </p>
</div>
</div>
<div class="container">
</div>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<form method="post" action="/logout">
<button type="submit" class="btn btn-success">Logout</button>
</button>
<a class="navbar-brand" href="#">Work Tracker</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form navbar-right">
</form>
</form>
</div><!--/.navbar-collapse -->
</div>
</nav>
<div class="container">
<!-- Example row of columns -->
<div class="row">
<div class="col-md-4">
<h2>Job Title</h2>
<!-- Dropdown list with Tradesperson's job titles
adapted from https://www.socketloop.com/tutorials/golang-populate-dropdown-with-html-template-example -->
<select>
<option value="Beautician">Beautician</option>
<option value="Builder">Builder</option>
<option value="Carpenter">Carpenter</option>
<option value="Cleaner">Cleaner</option>
<option value="Delivery">Delivery</option>
<option value="Electrician">Electrician</option>
<option value="Farmer">Farmer</option>
<option value="Gardener">Gardener</option>
<option value="Hair and Beauty">Hair and Beauty</option>
<option value="Hairdresser">Hairdresser</option>
<option value="Mechanic">Mechanic</option>
<option value="Painter">Painter</option>
<option value="Plumber">Plumber</option>
<option value="Technician">Technician</option>
<option value="Tiler">Tiler</option>
<option value="Transport">Transport</option>
<option value="Other">Other</option>
</select>
<p><a class="btn btn-default" href="#" role="button">View details »</a></p>
</div>
<!-- Code adapted from http://www.w3schools.com/html/html_form_elements.asp -->
<div class="col-md-4">
<h2>Hours Worked This Week</h2>
<select name="HoursWorker">
<option value=">10 hours">>10 Hours</option>
<option value="10-15">10-15 Hours</option>
<option value="15-20">15-20 Hours</option>
<option value="25-30">25-30 Hours</option>
<option value="30-40">30-40 Hours</option>
<option value="40+">40+ Hours</option>
</select>
<p><a class="btn btn-default" href="#" role="button">View details »</a></p>
</div>
<div class="col-md-4">
<h2>Additonal Information</h2>
<!--Adapted from http://www.w3schools.com/tags/tag_input.asp-->
<form action="demo_form.asp">
<input type="text" name="AddInfo" value=""><br>
<input type="submit" value="Submit">
</form><p><a class="btn btn-default" href="#" role="button">View details »</a></p>
</div>
</div>
<hr>
<footer>
<p>© 2016 WorkTracker, Inc.</p>
</footer>
</div> <!-- /container -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
</body>
</html>
- We then handle moving to internal page and bringing along the userName:
func internalPageHandler(response http.ResponseWriter, request *http.Request) {
userName := getUserName(request)
if userName != "" {
fmt.Fprintf(response, internalPage, userName)
} else {
http.Redirect(response, request, "/", 302)
}
}
- Finally, we need to serve up our code to port 8000 and handle all requests within the main function:
var router = mux.NewRouter()
func main() {
router.HandleFunc("/", indexPageHandler)
router.HandleFunc("/internal", internalPageHandler)
router.HandleFunc("/login", loginHandler).Methods("POST")
router.HandleFunc("/logout", logoutHandler).Methods("POST")
http.Handle("/", router)
http.ListenAndServe(":8000", nil)
}