Implement the Assignment Stories 6 - 10.
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.
Now deploy the project to cloudbees.
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: