Objectives

Building on the baseline spacebook from the previous lab, implement stories 1 to 5 of the assignment specification from last semester.

Baseline project + Story 1

This is a completed project from last week:

Now download an unarchive the source project. In order to import this, you will need to do the following commands:

cd 'the directory into which you unarchived the project'
play deps
play eclipsify

In eclipse, import the project as an 'existing project'. Run the project in the usual way, and verify that it behaves as expected.

Assignment Story 1

We already have a 'data.yml' file in th conf directory (open this). This contained initial user data loaded at startup:

User(homer):
    firstName: Homer
    lastName: Simpson
    email: homer@simpson.com
    password: secret

User(marge):
    firstName: marge
    lastName: Simpson
    email: marge@simpson.com
    password: secret

Verify that these are in your app.

Extend this data to include the full homer clan. Note, the number of spaces (4) before each field is significant.

Assignment Story 1 Solution:

Introduce the following entries into the data.yml file:

User(lisa):
    firstName: Lisa
    lastName: Simpson
    email: lisa@simpson.com
    password: secret

User(bart):
    firstName: Bart
    lastName: Simpson
    email: bart@simpson.com
    password: secret

User(maggie):
    firstName: Maggie
    lastName: Simpson
    email: maggie@simpson.com
    password: secret

Assignment Story 2

Extend the User Model to include the following new fields:

  • Age
  • Nationality

These fields must be filled in when a user registers.

Assignment Story 2 Solution

Bring these fields into User.java:

  public int    age;
  public String nationality;

And change the constructor to inialize these fields:

  public User(String firstName, String lastName, String email, String password, int age, String nationality)
  {
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
    this.password = password;
    this.age = age;
    this.nationality = nationality;
  }

Now rework Accounts.register method:

  public static void register(String firstName, String lastName, int age, String nationality,
                              String email,     String password, String password2)
  {
    Logger.info(firstName + " " + lastName + " " + email + " " + password);
    User user = new User(firstName, lastName, age, nationality, email, password);
    user.save();
    index();
  }

Exercise:

Extend Accounts/signup.html to allow the user provide these new fields

Assignment Story 3

For the new fields accepted in Story 2, display them on the users Home Profile page.

In addition, on the users 'Public' profile (then one a friend can see), display just the 'Nationality' field

Assignment Story 3 Solution

In views/Profile/index.html we can introduce a table to render the user details:

        <table class="ui table segment">
          <thead>
            <tr>
              <th>Email</th>
              <th>Nationality</th>
              <th>Age</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>${user.email}</td>
              <td>${user.nationality}</td>
              <td>${user.age}</td>
            </tr>
          </tbody>
        </table>

Place this in the left hand column, above the profile image

In views/PublicProfile/visit.html, bring in a reduced version somewhere suitable:

      <table class="ui table segment"> 
        <thead>
          <tr>
            <th>Email</th>
            <th>Nationality</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>${user.email}</td>
            <td>${user.nationality}</td>
          </tr>
        </tbody>
      </table>

Assignment Story 4

Even if a user seems to have logged out, we can still access the spacebook pages by entering the url of some of the controllers. For instance, log in, then log our immediately and try these links:

How could you prevent this?

HINT: The session object has a method called 'clear()'. If we call this (say in logout action), then it will remove any things we have put in there. For every action, we could first check to make sure a valid ID is in the session, otherwise redirect to the start page.

Assignment Story 4 Solution

Introduce a new method into Accounts controller:

  public static User getLoggedInUser()
  {
    User user = null;
    if (session.contains("logged_in_userid"))
    {
      String userId = session.get("logged_in_userid");
      user = User.findById(Long.parseLong(userId));
    }
    else
    {
      login();
    }
    return user;
  }

Also, in Accounts.logout method - we clear the session:

  public static void logout()
  {
    session.clear();
    index();
  }

For every method in every controller, compare them with the following, and make the appropriate adjustments:

public class Home extends Controller
{
  public static void index()
  {
    User user = Accounts.getLoggedInUser();
    render(user);
  }

  public static void drop(Long id)
  {
    User user   = Accounts.getLoggedInUser();    
    User friend = User.findById(id);
    user.unfriend(friend);
    Logger.info("Dropping " + friend.email);
    index();
  }  
}
public class Profile extends Controller
{
  public static void index()
  {
    User user = Accounts.getLoggedInUser();
    render(user);
  }

  public static void changeStatus(String statusText)
  {
    User user = Accounts.getLoggedInUser();
    user.statusText = statusText;
    user.save();
    Logger.info("Status changed to " + statusText);
    index();
  }

  public static void getPicture(Long id) 
  {
    User user = User.findById(id);
    Blob picture = user.profilePicture;
    if (picture.exists())
    {
      response.setContentTypeIfNotSet(picture.type());
      renderBinary(picture.get());
    }
  }

  public static void uploadPicture(Long id, Blob picture)
  {
    User user = User.findById(id);
    user.profilePicture = picture;
    user.save();
    Logger.info("saving picture");
    index();
  }  
}
public class Members extends Controller
{
  public static void index()
  {
    User user = Accounts.getLoggedInUser();
    List<User> users = User.findAll();
    render(users);
  }

  public static void follow(Long id)
  {
    User user = Accounts.getLoggedInUser();
    User friend = User.findById(id);
    user.befriend(friend);
    Home.index();
  }
}
public class PublicProfile extends Controller
{
  public static void visit(Long id)
  {
    User currentUser = Accounts.getLoggedInUser();

    User user = User.findById(id);
    Logger.info("Just visiting the page for " + user.firstName + ' ' + user.lastName);
    render(user);
  }

  public static void sendMessage(Long id, String messageText)
  {
    User fromUser = Accounts.getLoggedInUser();;
    User toUser = User.findById(id);

    Logger.info("Message from user " + 
        fromUser.firstName + ' ' + fromUser.lastName +" to " +
        toUser.firstName + ' ' + toUser.lastName +": " +
        messageText);    

    fromUser.sendMessage(toUser, messageText);
    visit(id);
  }  

When all these changes have been made, run the app and, without logging in, try to visit the urls of the home, profile and members pages.

Then, log in and do the same. Finally, log out and do the same. Observe and verify the behaviour.

Assignment Story 5

Provide a way for a user, once logged in, to change some of their profile information. You could take two approaches to this:

  • Provide some extra fields on the home profile which could all be changed when the 'changeText' button is pressed.
  • Provide a link on the home profile - say 'edit details' - which takes you to a new page where you can edit the details.

Assignment Story 5 Solution

Introduce a new controller called EditProfile:

package controllers;

import play.*;
import play.mvc.*;
import java.util.*;
import models.*;

public class EditProfile extends Controller
{
  public static void change (String firstName,   String lastName, int    age, 
                             String nationality, String email,    String password, String password2)
  {
    User user = Accounts.getLoggedInUser();
    user.firstName = firstName;
    user.lastName = lastName;
    user.email = email;
    user.nationality = nationality;
    user.age = age;
    user.password = password;
    user.save();
    Profile.index();
  }

  public static void index()
  {
    User user = Accounts.getLoggedInUser();
    render(user);
  }
}

Match this with an accompanying view in views/EditProfile/index.html:

#{extends 'main.html' /}
#{set title:'Edit Details' /}

<nav class="ui inverted menu">
  <header class="header item"> Spacebook </header>
  <div class="right menu">
    <a class="active item" href="/profile"> Profile  </a>
  </div>
</nav> 

<div class="ui raised form segment">
  <form action="/editprofile/change" method="POST">
    <div class="two fields">
      <div class="field">
        <label>First Name</label>
        <input placeholder="First Name" type="text" name="firstName">
      </div>
      <div class="field">
        <label>Last Name</label>
        <input placeholder="Last Name" type="text" name="lastName">
      </div>
    </div>
    <div class="two fields">
      <div class="field">
        <label>Nationality</label>
        <input placeholder="Nationality" type="text" name="nationality">
      </div>
      <div class="field">
        <label>Age</label>
        <input placeholder="Age" type="text" name="age">
      </div>
    </div>    <div class="field">
      <label>Email</label>
      <input placeholder="Email" type="text" name="email">
    </div>
    <div class="field">
      <label>Password</label>
      <input type="password" name="password">
    </div>
    <button class="ui blue submit button">Submit</button>
  </form>
</div> 

In the views/HomeProfile/index.html, insert a link to this new controller:

        <a href="/editprofile" class="ui button"> Edit </a>

Finally, make introduce the appropriate routes:

# Edit Profiles
GET   /editprofile                              EditProfile.index
POST  /editprofile/change                       EditProfile.change

Test that this works as expected.

Exercises

Solution so far

You will need to run 'play deps' and 'play eclipsify' before you import this.

Initial Fields

The EditProfile view is problematic. It presents the save fields as the signup form, however they are all blank. We really should have these fields 'filled in' - so that the user can see and make changes to the entries.

The controller already passes the user details to the view:

  public static void index()
  {
    User user = Accounts.getLoggedInUser();
    render(user);
  }

We need to change each entry in the form views/EditProgfile/index.html to something like this:

        <input placeholder="First Name" type="text" name="firstName" value="${user.firstName}">

(Note the last 'value' entry)

Do this for all the fields in views/EditProgfile/index.html - except perhaps the password field

Resized Forms

Some of the form views are not correctly sized, with input boxes elongated. See if you can redesign the signup, login and editprofile forms to look like this:

Missing Fields

Edit data.yaml to make sure that all users have nationality and age initial values.

Additionally, it is possible to set up some initial messages:

Message(message1):
    from : marge
    to   : homer
    messageText: 'how are things going?'

Message(message2):
    from : lisa
    to   : homer
    messageText: 'Get me outta here!'

and even some friend relationships:

User(homer):
    firstName: Homer
    lastName: Simpson
    email: homer@simpson.com
    password: secret
    following: 
              - marge
              - lisa

Experiment with these to have a range of friends and messages for each user in the yaml file.