Objectives

Implement the Assignment Stories 6 - 10.

Reference implementation

Download the following project:

and import into your workspace. It is named 'spacebook-app-lab02-final' - so it will not interfere with your existing projects.

Run the app, and verify its main features. Contrast it with your solution form the last lab.

Assignment Story 6

The Members page now shows a list of all members - including the currently logged in member. This clearly makes no sense and we should try to remove the current member from the list.

HINT: This is the Members controller index method:

  public static void index()
  {
    List<User> users = User.findAll();
    render(users);
  }

The challenge is to remove the current user from the users list before we send it to the view. Objects can be deleted from a list using the remove method:

   user.remove(currentUser);

How would you get a User object representing the current user? From the session of course - something like this perhaps:

    String userId = session.get("logged_in_userid");
    User currentUser = User.findById(Long.parseLong(userId));

Assignment Story 6 Solution

We just remove the current user from the list of users:

  public static void index()
  {
    User user = Accounts.getLoggedInUser();
    List<User> users = User.findAll();
    users.remove(user);
    render(users);
  }

Assignment Story 7

In the members page - show each users name + their current status message.

Assignment Story 7 Solution

views/Members/index.html:

      <div class="ui item"> 
        <i class="user icon"></i>
        ${user.firstName} ${user.lastName}: : ${user.statusText}  <a href="members/follow/${user.id}"> (follow) </a> 
      </div>

Assignment Story 8

On a users home profile, provide a way for two images to be uploaded - one will be their profile picture as currently implemented, the second is to be to be called the "thumbnail" image, which can be used in story 10 below.

Assignment Story 8 Solution

Introduce a new attribute into User.java:

  public Blob   thumbnailPicture;

In HomeProfile controller, introduce methods to upload/download this thumbnail:

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

  public static void uploadThumbnail(Long id, Blob picture)
  {
    User user = User.findById(id);
    user.thumbnailPicture = picture;
    user.save();
    index();
  }  

Provide routing for these methods in routes.conf:

POST    /profile/uploadthumbnail/{id}           Profile.uploadThumbnail
GET     /profile/getthumbnail/{id}              Profile.getThumbnail

Finally, adjust view/HomeProfile.index.html to display/upload the thumbnail image

        <div class="ui medium image">
          <label class="ui ribbon label">Thumbnail Image</label>
          <img src="/profile/getthumbnail/${user.id}">
        </div>
        <section class="ui raised form segment">
          <form action="/profile/uploadthumbnail/${user.id}" method="post" enctype="multipart/form-data">     
            <div class="ui field">
              <input type="file" name="picture">  </input> 
           </div>     
           <button class="ui blue submit button"> Upload</button>       
          </form>        
        </section>

Place the above below the current image upload panel.

Assignment Story 9

In the messages displayed on the home page, show the message text (as currently) + a link to the senders profile. In this way users can easily navigate from a message directly to the sender, and perhaps leave a message.

HINT:

This is the list of members we are following:

      <ul> 
        #{list items:user.following, as:'user'}
          <li> 
            <a href="userprofile/${user.id}"> ${user.firstName} ${user.lastName} </a> : <a href="home/drop/${user.id}">Drop</a>
          </li>
        #{/list}
      </ul>

and this is the list of messages:

      #{if usermessages.size() > 0}
        <ul>
          #{list items:usermessages, as:'usermessage'}
            <li> ${usermessage.from.firstName} says ${usermessage.messageText} </li>
          #{/list}
       </ul>
      #{/if}

Your task would be to somehow turn this part of the message (before the 'says' string):

            <li> ${usermessage.from.firstName} says ${usermessage.messageText} </li>

into a link - similar to the link in the following list....

Assignment Story 9 Solution

In views/Home/index.html, we can change the messages item to look like the following:

              <div class="item"> 
                <i class="chat icon"></i>
                 <a href="/publicprofile/${message.from.id}"> ${message.from.firstName}</a>  says "${message.messageText}" 
              </div>

Assignment Story 10

Turn the list of messages on the home page into a table - with three columns. One column for the message text, one for the senders name, and one showing the senders profile picture.

HINT:

Recall that in the UserProfile/visit.html page we are displaying images as follows:

    <p> 
      <img src="/homeprofile/getpicture/${user.id}"/> 
    </p>

this same mechanism can be also used to display the images on the home page.

Assignment Story 10 Solution

The messages are displayed on the home page - views/Home/index.html. Open this and replace the 'Messages' section with the following:

      <section class="ui stacked segment">
        <h4 class="ui inverted blue block header">Messages</h4>
        #{if user.inbox.size() > 0}
          <table class="ui table segment">
            <thead>
              <tr>
                <th>From</th>
                <th></th>
                <th>Message</th>
              </tr>
            </thead>
            <tbody>
              #{list items:user.inbox, as:'message'}
                <tr>
                  <td>
                    <div class="ui small image">
                      <img src="/profile/getthumbnail/${message.from.id}">
                    </div>
                  </td>
                  <td>
                     <a href="/publicprofile/${message.from.id}"> ${message.from.firstName}</a>
                   </td>
                   <td> 
                      ${message.messageText}
                    </td>
                </tr>
              #{/list}
            </tbody>
          </table>  
        #{/if} 
      </section>

You must have images loaded for the fiends who have left messages.

Exercises

Solution so far:

Compare your project with this one, which has all of the exercises implemented so far:

You can safely import it into your project, as it has been renamed.

Exercise 1: Cloudbees

Now deploy the project to cloudbees.

Exercise 2: Decompose Views into Components

Explore the possibility of breaking the view into segments - and hence simplifying the more complicated views.

For instance, this is home/index.html:

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

<nav class="ui inverted menu">
  <header class="header item"> Spacebook </header>
  <div class="right menu">
    <a class="active item" href="/home">Home</a></li>
    <a class="item" href="/members">Members</a>
    <a class="item" href="/profile">Profile</a>
    <a class="item" href="/logout">Logout</a>
  </div>
</nav> 

<h1 class="ui inverted teal block header" >${user.firstName} ${user.lastName}</h1>
<section class="ui two column grid segment">
  <div class="row">
    <div class="ui column">
      <section class="ui stacked segment">
        <h4 class="ui inverted red block header">Friends (${user.friendships.size()})</h4>
        <div class="ui list"> 
          #{list items:user.friendships, as:'friendship'}
            <div class="ui item"> 
              <i class="user icon"></i>
              <a href="/publicprofile/${friendship.targetUser.id}"> ${friendship.targetUser.firstName} ${friendship.targetUser.lastName} </a>
               (<a href="/home/drop/${friendship.targetUser.id}"> drop </a>)
            </div>
          #{/list}
        </div> 
      </section>  
    </div>
    <div class="column">
      <section class="ui stacked segment">
        <h4 class="ui inverted blue block header">Messages</h4>
        #{if user.inbox.size() > 0}
          <table class="ui table segment">
            <thead>
              <tr>
                <th>From</th>
                <th></th>
                <th>Message</th>
              </tr>
            </thead>
            <tbody>
              #{list items:user.inbox, as:'message'}
                <tr>
                  <td>
                    <div class="ui small image">
                      <img src="/profile/getpicture/${message.from.id}">
                    </div>
                  </td>
                  <td>
                     <a href="/publicprofile/${message.from.id}"> ${message.from.firstName}</a>
                   </td>
                   <td> 
                      ${message.messageText}
                    </td>
                </tr>
              #{/list}
            </tbody>
          </table>  
        #{/if} 
      </section>
    </div>
  </div>
</section>

Large and complicated, and very easy to make mistakes with this level of nesting / indentation. We could break it up into smaller pieces.

For instance - create a new folder in views called components. In this folder, create two new views:

frields.html

 <section class="ui stacked segment">
   <h4 class="ui inverted red block header">Friends (${user.friendships.size()})</h4>
   <div class="ui list"> 
     #{list items:user.friendships, as:'friendship'}
       <div class="ui item"> 
         <i class="user icon"></i>
         <a href="/publicprofile/${friendship.targetUser.id}"> ${friendship.targetUser.firstName} ${friendship.targetUser.lastName} </a>
          (<a href="/home/drop/${friendship.targetUser.id}"> drop </a>)
       </div>
     #{/list}
   </div> 
 </section>  

message.html

 <section class="ui stacked segment">
   <h4 class="ui inverted blue block header">Messages</h4>
   #{if user.inbox.size() > 0}
     <table class="ui table segment">
       <thead>
         <tr>
           <th>From</th>
           <th></th>
           <th>Message</th>
         </tr>
       </thead>
       <tbody>
         #{list items:user.inbox, as:'message'}
           <tr>
             <td>
               <div class="ui small image">
                 <img src="/profile/getpicture/${message.from.id}">
               </div>
             </td>
             <td>
                <a href="/publicprofile/${message.from.id}"> ${message.from.firstName}</a>
              </td>
              <td> 
                 ${message.messageText}
               </td>
           </tr>
         #{/list}
       </tbody>
     </table>  
   #{/if} 
 </section>

Note that the above contain sections taken directly from home/index.html.

Now, replace home/index.html with the following:

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

<nav class="ui inverted menu">
  <header class="header item"> Spacebook </header>
  <div class="right menu">
    <a class="active item" href="/home">Home</a></li>
    <a class="item" href="/members">Members</a>
    <a class="item" href="/profile">Profile</a>
    <a class="item" href="/logout">Logout</a>
  </div>
</nav> 

<h1 class="ui inverted teal block header" >${user.firstName} ${user.lastName}</h1>
<section class="ui two column grid segment">
  <div class="row">
    <div class="ui column">
      #{include "components/friends.html" /} 
    </div>
    <div class="column">
      #{include "components/messages.html" /} 
    </div>
  </div>
</section>

This is considerably easier to read and manage.

Exercise 3: More Decomposed Components

Can you see opportunities for further components in the views? Profile/index.html and PublicProfile/index.html are both large an complicated views. Can you use the above techniques to split these up into components making them easier to manage?

Exercise 4: Recompose Components

Are there now opportunities for re-composing components into new views? For instance, a 'dashboard' view, which might contain: