3.4 Implementing the presentation layer
Now that our framework has been updated to handle the
new concept of messaging, let's start creating some UI features so that
we can use our new tools. We are going to need at least three pages to
really utilize our messaging features. We will need a way to create and
send a new message, a way to receive and view the messages, and a way to
read an individual message. In addition to the creation of a message,
we will need a way to easily choose from our list of friends, the
recipients of our new messages. Also, while viewing our list of
messages, we will need a way of drilling into different folders of
messages. Let's get started!
New message
The UI for the new message page is relatively trivial. It consists of a To field, a Subject
field, a message field, and a button to signify that we are ready to
send the message. Where things are significantly different is in the use
of the Xinha WYSIWYG editor! This is a JavaScript library that allows
you to transform a multiline text box into a full-featured editor.
Then open the SiteMaster.Master page. Directly after the body add the following JavaScript.
<script type="text/javascript">
xinha_editors = null;
xinha_editors = xinha_editors ? xinha_editors : [];
</script>
This code is not part of the standard install
process. What it does is allow us to spin up multiple instances of the
editor all throughout a single page. You will see this later.
Then further down in our master page, just before the
ending body tag, we will insert another huge blob of JavaScript. This
is a very large blob that could just as easily be inserted into an
external .js file. I am not going to show it here.
You will see that this blog is responsible for
setting the vast amount of configuration options that Xinha exposes.
This code is heavily commented and so should be understandable. Also,
the configuration of this package is covered extensively on the net!
The only thing, but definitely the special one, I
added to in this configuration is the very first line that sets the base
URL of the site. In this case, I changed it to use a call into the WebContext.RootUrl property.
...
_editor_url = "<%= _webContext.RootUrl %>Xinha/";
...
This brings us back to our NewMessage.aspx
page. Now that we have Xinha installed, we can add a line below our page
UI that effectively ties our multiline text box control to the Xinha
library. This is done with a snippet of JavaScript.
<script type="text/javascript">
xinha_editors[xinha_editors.length] = 'ctl00_Content_txtMessage';
</script>
Note that this is using the same xinha_editors
variable that we defined initially in the Master page! What we have
done here is to insert this new control into an array of Xinha editors.
Note that we are using the full ClientID of the text box that we want associated as an editor.
Now, we will move on to the presenter of this page. This page has three primary tasks:
This is really the same thing with the exception that
to send a reply to a message, we would first have to load that reply.
We will get to that down the road!
To send a message, we have to bubble up the button
click event through our code behind and into our presenter. As I have
covered this concept extensively, I will not cover it here. So, in the
presenter, we have added a method called SendMessage(), which takes in the Subject, Message, and an array of To entries.
public void SendMessage(string Subject, string Message, string[] To)
{
_messageService.SendMessage(Message,Subject,To);
}
This method then calls into the MessageService.SendMessage() method and sends the message.
The preloading of a recipient is handled in the Init() method of the presenter. It checks to see if we have an AccountID in the QueryString (via our WebContext wrapper). If so, it gets the Accounts Username property and adds that to the To field in the UI.
Loading a reply message into the UI is very similar to loading a username. The Init() method checks the WebContext to see if we have a MessageID in the QueryString and loads the previous Message details into the UI.
//Mail/Presenter/NewMessagePresenter.cs
public void Init(INewMessage view)
{
_view = view;
if(_webContext.MessageID != 0)
_view.LoadReply(_messageRepository.GetMessageByMessageID(_ webContext.MessageID,_userSession.CurrentUser.AccountID));
if(_webContext.AccountID != 0)
_view.LoadTo(_accountRepository.GetAccountByID( _webContext.AccountID).Username);
}
Friends control
The other important feature that we have as part of the NewMessage.aspx
page is the ability to easily select a friend from a list of friends as
the recipient of a message. To
achieve this, we will create a user control that lists our friends.
The UI for this control is just a simple Repeater object that outputs a friend's username. So let's take a look at what populates the UI. In the Init() method in the FriendPresenter.cs file, we have a call into the FriendRepository.GetFriendsAccountsByAccountID() method, which loads all the users' friends. This is bound to the repeater control in the UI.
//Mail/UserControls/Presenter/FriendsPresenter.cs
public void Init(IFriends view)
{
_view = view;
_view.LoadFriends(_friendRepository.GetFriendsAccountsByAccountID(_us erSession.CurrentUser.AccountID));
}
In the code behind for our friend control, we insert a
snippet of JavaScript to allow us to click on a friend and carry
his/her username into our To field in our NewMessage UI.
//Mail/UserControls/Friends.aspx.cs
public void repFriends_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
HyperLink linkFriend = e.Item.FindControl("linkFriend") as HyperLink;
linkFriend.Attributes.Add("OnClick", "javascript:document.forms[0]. ctl00_Content_txtTo.value += '" + ((Account)e.Item.DataItem).Username + ";';");
}
}
Note that we are adding an attribute for the OnClick event of our link that will call a JavaScript to move the username to our To field with a semicolon delimiter.
This gives us a list of friends, but how do we tie that back into our UI? Open the NewMessage.aspx page. We will need to register the new control in the page and then add a reference to it in the page.
Just below the Page directive add the following code:
<%@ Register Src="~/Mail/UserControls/Friends.ascx"
TagPrefix="Fisharoo" TagName="Friends" %>
Then add a new Content section to our page where the friends control will live:
<asp:Content ContentPlaceHolderID="LeftNavTop" runat="server">
<Fisharoo:Friends ID="friends1" runat="server" />
</asp:Content>
This inserts the friends control into the top of our left nav!&;
Default (or Inbox)
Now that we can successfully send a message, we need a
way to receive those messages. This is done in folder view of our
messages. This is really just a page that shows a list of messages, who
sent them, and when. We can either click on the sender to go to their
profile page, or can click on the message to view it. We can also
navigate through pages of messages with a list of page navigation links.
And we will have the ability to delete one or many selected messages.
Additionally, we will create a Folders control that will allow us to navigate through our various folders to see the messages in those containers.
The UI for this page is very simple. It is just a repeater that iterates through the MessageWithRecipient
collection that is passed to it. So let's take a look at the presenter,
which actually populates our list of messages. As always this is
accomplished in the Init() method of the presenter.
public void Init(IDefault view)
{
_view = view;
if (_userSession.CurrentUser != null)
{
_view.LoadMessages(_messageRepository.GetMessagesByAccountID( _userSession.CurrentUser.AccountID,
_webContext.Page, (MessageFolders) _webContext.FolderID));
_view.DisplayPageNavigation(
_messageRepository.GetPageCount((MessageFolders) _webContext.FolderID,
_userSession.CurrentUser.AccountID),
(MessageFolders) _webContext.FolderID, _webContext.Page);
}
}
We first check to see that the user of the page is actually logged in. We then make a call into the MessageRepository to get a list of messages by the user's AccountID. We also make a call into MessageRepository to get a page count (the number of pages of messages that we have to navigate through).
Loading messages into the UI is simply a matter of binding the DataSource to the repeater.
public void LoadMessages(List<MessageWithRecipient> Messages)
{
repMessages.DataSource = Messages;
repMessages.DataBind();
}
As this requires no explanation, let's jump right into how we go about building our page navigation. Recall that in the Init() of our presenter we had a call into the view of DisplayPageNavigation(),which received the PageCount of the folder we were working with, and the current page we were viewing. Here is that method:
public void DisplayPageNavigation(Int32 PageCount, MessageFolders folder, Int32 CurrentPage)
{
if(PageCount == CurrentPage)
linkNext.Visible = false;
if (CurrentPage == 1)
linkPrevious.Visible = false;
linkNext.NavigateUrl = "~/mail/default.aspx?folder=" + ((int) folder).ToString() + "&page=" +
(CurrentPage + 1).ToString();
linkPrevious.NavigateUrl = "~/mail/default.aspx?folder=" + ((int) folder).ToString() + "&page=" +
(CurrentPage - 1).ToString();
for(int i = 1; i<=PageCount;i++)
{
HyperLink link = new HyperLink();
link.Text = i.ToString();
link.NavigateUrl = "~/mail/default.aspx?folder=" + ((int)folder).ToString() + "&page=" + i.ToString();
phPages.Controls.Add(link);
phPages.Controls.Add(new LiteralControl(" "));
}
}
This chunk of code interacts with three controls in our UI — two hyperlinks, one that displays Previous and one displaying Next, and a PlaceHolder control that will hold the individual page numbers of all the pages for this data set.
This method initially determines if we should show the Next or Previous
links based on our current page and our total page count. We then hook
up the navigation property of each of those links to take us to the next
page or the previous page of data. After that we use a for loop to iterate through all the possible pages, from 1 to PageCount, making a new hyperlink for each iteration that contains the location of that page.
The other feature of this page is the ability to
delete messages — as many or as few as we like. This is primarily
achieved with a helper function in the code behind of the view that
extracts all the messages that are selected, which can be called from
within the presenter. This allows the presenter to remain in control!
public List<Int32> ExtractSelectedMessages()
{
List<Int32> result = new List<Int32>();
foreach (RepeaterItem item in repMessages.Items)
{
if(item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem)
{
CheckBox chkMessage = item.FindControl("chkMessage") as CheckBox;
Int32 messageID = Convert.ToInt32(chkMessage.Attributes["MessageID"]);
if(chkMessage.Checked)
result.Add(messageID);
}
}
return result;
}
This method iterates through all the check boxes to see if they are selected or not. If they are, then it extracts the MessageID from an attribute that was created when we loaded the display. A collection of MessageIDs is then returned to the caller.
This then brings us to the Delete method in
the presenter. It is called from a button click event that is bubbled up
to the presenter, which then calls into the view to get a list of
selected MessageIDs. Then using the MessageRepository we get a copy of that message. With the copy, we then call into the MessageRecipientRepository.DeleteMessageRecipient() method and delete each selected MessageRecipient.
Folders
I hate to sound like a broken record but I find myself saying that this
UI is also simple. Well...it is! This UI is also made up of a Repeater that displays the bound data. In this case, we are displaying folders as hyperlinks, which then link to the same Default.aspx page, but additionally pass in the folder that we are interested in viewing.
I am going to jump right in to the presenter so that we can see the DataSource for our data.
public void Init(IFolders view)
{
_view = view;
_view.LoadFolders(_messageFolderRepository.GetMessageFoldersByAccountID(_userSession.CurrentUser.AccountID));
}
In this case, we are calling into the MessageFolderRepository
to get a list of folders for this user. We bind that directly to the UI
through our view. The view then iterates through the data in our
repeater. In our ItemDataBound method — in the code behind of the view — we update each hyperlink in the UI.
protected void repFolders_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
HyperLink linkFolder = e.Item.FindControl("linkFolder") as HyperLink;
linkFolder.Text = ((MessageFolder)e.Item.DataItem).FolderName;
linkFolder.NavigateUrl = "~/Mail/Default.aspx?folder=" +
((MessageFolder) e.Item.DataItem).MessageFolderID.ToString();
linkFolder.Attributes.Add("FolderID", ((MessageFolder)e.Item.DataItem). MessageFolderID.ToString());
}
}
This creates a list of folders for us in a control. But how do we get it into our UI? To do this, we have to go back to our Default.aspx
page. This will be done exactly the same way we did for our friends
control in our new message page. Open the default page so that we can
add the Folders control.
<%@ Register Src="~/Mail/UserControls/Folders.ascx" TagPrefix="Fisharoo" TagName="Folders" %>
<asp:Content ContentPlaceHolderID="LeftNavTop" runat="server">
<Fisharoo:Folders id="Folders1"
runat="server"></Fisharoo:Folders>
</asp:Content>
Read message
Reading a message is not that difficult at all. We are just loading a message into a static UI based on the MessageID
that is passed into the page. Let's first discuss the UI (which could
easily be made more complex down the road!). This UI will consist of a From Label, a Subject Label, a Message Label, and a Reply button.
Here is the message view:
And here is what will be seen when replying to the message:
As usual, as all the leg work is done in the presenter, let's jump straight to it.
public void Init(IReadMessage view)
{
_view = view;
_view.LoadMessage(_messageRepository.GetMessageByMessageID(_webContex t.MessageID,_userSession.CurrentUser.AccountID));
}
As you can see here, we are populating the UI based on a call into the MessageRepository.GetMessageByMessageID() method. This method sends a MessageWithRecipient into the view, which then loads the message for display. Now that is simple!
For&; the Reply button, we will bubble up the click event into the presenter. The presenter then makes a call to the Redirector class that passes the current MessageID to the NewMessage page. Simple again!
public void Reply()
{
_redirector.GoToMailNewMessage(_webContext.MessageID);
}