Implement the the complete spacebook assignment from semester 1. In the Exercises, explore an mechanisms to factor out more complicated view to components.
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.
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));
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);
}
In the members page - show each users name + their current status message.
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>
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.
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.
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....
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>
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.
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.
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.
Run an inspect the UI. Compare it with the version you have completed in this lab. Can you see any differences? What are they?
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:
<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>
<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.
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?
Are there now opportunities for re-composing components into new views? For instance, a 'dashboard' view, which might contain: