As promised. Here's the Visual Basic 9.0 version of the Part 2 of the chat application. To see this article and code in
C# 3.5,
click here.
Introduction:
In Part 1 we built a very simple, flicker-free, frame-less, web chat application using ASP.Net 3.5, LINQ-to-SQL, AJAX and MS SQL Server, with your choice of either C# 3.5 or VB 9.0 in about 2 hours. As promised,
we will add functionality to our previous chatroom application so that users will have the ability to chat with each other privately. You can read and download the code of Part 1 in
C# 3.5 here,
or in
VB 9.0 here.
Here's a snapshot of the Chatroom area.
I noticed while coding for part 2 that the names (in black) of the chatters didn't show in the chatroom messages, so I added them real quickly. Although I have
no doubt that you could have as easily done so, but here's the code revision anyway:
177 If message.User.Sex.ToString().ToLower() = "m" Then
178 sb.Append("<img src='Images/manIcon.gif' style='vertical-align:middle' alt=''> <span style='color: black; font-weight: bold;'>" & _
179 message.User.Username & ":</span> " & message.Text + "</div>")
180 Else
181 sb.Append("<img src='Images/womanIcon.gif' style='vertical-align:middle' alt=''> <span style='color: black; font-weight: bold;'>" & _
182 message.User.Username & ":</span> " & message.Text + "</div>")
183 End If
Database Revision/Addition
We will need a table to hold information regarding users that sends invitations to other chatters, so they can chat privately. As shown below, I added a table called PrivateMessage.
Chat With Another User Privately
There are several things that happens when we send someone an invitation to chat privately.
-
To start sending another chatter an invitation to chat, we simply click the chatter's user name from the list of the users, shown in a box to the right, on the
Chatroom.aspx page. This should open another window, the ChatWindow.aspx. A new window opens because of the additional code shown below:
The ChatWindow.aspx opens or pops up:
Using the code below:
139 sb.Append(userIcon & "<a href=# onclick=""window.open('ChatWindow.aspx?FromUserId=" & Session("ChatUserID") & _
140 "&ToUserId=" & loggedInUser.User.UserID & "&Username=" & loggedInUser.User.Username & _
141 "','','width=400,height=200,scrollbars=no,toolbars=no,titlebar=no,menubar=no'); isLostFocus = 'true';"">" & _
142 loggedInUser.User.Username & "</a><br>")
-
To send an invitation, we first need to send a message to the other chatter on the newly popped-up window (ChatWindow.aspx), just like other chat applications out there. When you send
a message to let's say "John Doe" from this window for the very first time, information is saved in the PrivateMessage table. So, just to make it clear,
all messages sent after the first one to "John Doe" will not be saved in the PrivateMessage table.
49 ' This is where we send a private chat invitation to other chatters
50 Private Sub InsertPrivateMessage()
51 ' if the private message is sent to this user already,
52 ' don't send it again
53 If String.IsNullOrEmpty(lblMessageSent.Text) Then
54 ' if any private message is found based on the
55 ' from user id, or the to user id, then this
56 ' private message will not be inserted
57 Dim privateMessage As PrivateMessage = New PrivateMessage()
58 privateMessage.UserID = Convert.ToInt32(lblFromUserId.Text)
59 privateMessage.ToUserID = Convert.ToInt32(lblToUserId.Text)
60
61 Dim db As LinqChatDataContext = New LinqChatDataContext()
62 db.PrivateMessages.InsertOnSubmit(privateMessage)
63 db.SubmitChanges()
64
65 ' make sure to assign any value to this label
66 ' to confirm that a message is sent to this user
67 lblMessageSent.Text = ConfigurationManager.AppSettings("ChatWindowMessageSent")
68 End If
69 End Sub
-
The information saved in the PrivateMessage table is then retrieved from the main chatroom page (Chatroom.aspx) every seven (7) seconds or whenever you hit
the send button, checking if another user(s) have sent you an invitation to chat privately. If an entry is found, the hidden panel server control's visible attribute
is then set to "true", showing the invitation to chat privately.
Here's the code that checks if an invitation to chat privately is sent to you:
213 ''' <summary>
214 ''' Check if anyone invited me to chat privately
215 ''' </summary>
216 Private Sub GetPrivateMessages()
217 Dim db As LinqChatDataContext = New LinqChatDataContext()
218
219 Dim privateMessage = (From pm In db.PrivateMessages _
220 Where pm.ToUserID = Convert.ToInt32(Session("ChatUserID")) _
221 Select pm).FirstOrDefault()
222
223 If Not privateMessage Is Nothing Then
224 lblChatNowUser.Text = privateMessage.User.Username
225 btnChatNow.OnClientClick = _
226 "window.open('ChatWindow.aspx?FromUserId=" & Session("ChatUserID") & _
227 "&ToUserId=" & privateMessage.UserID & "&Username=" & privateMessage.User.Username & _
228 "&IsReply=yes','','width=400,height=200,scrollbars=no,toolbars=no,titlebar=no,menubar=no'); isLostFocus = 'true';"
229
230 pnlChatNow.Visible = True
231
232 db.PrivateMessages.DeleteOnSubmit(privateMessage)
233 db.SubmitChanges()
234 End If
235 End Sub
Here's a snapshot of the panel server control showing that someone has invited you to chat privately:
-
You will notice that there are two buttons on the invitation box, the "Chat Now" and "Cancel" buttons. Whether you click the Chat Now or the Cancel button,
the entry on the PrivateMessage table for you and the user that invited you will be deleted (Line 259 and 260 shown above) and the panel's visible attribute will be
set to "false", and this is primarily what the Cancel button does. On the other hand, if you click the Chat Now button, a new window will pop-up (Lines 251-254 shown above)
and you can now start chatting privately with the user that invited you to chat. Code is shown below from the Chatroom.aspx code file:
237 Protected Sub BtnChatNow_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnChatNow.Click
238 pnlChatNow.Visible = False
239 End Sub
240
241 Protected Sub BtnCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCancel.Click
242 pnlChatNow.Visible = False
243 End Sub
As usual, I was in a hurry to finish this code. One suggestion would be, when the user clicks the Cancel button, send the other user who sent the invitation
a message saying that you declined the invitation to chat privately. This can easily be done on the Cancel button's click event. Maybe I'll add this
in Part 3.
Difference Between Chatting Privately and Chatting Corporately
Other than the obvious missing list of users when chatting privately, the main difference code-wise is; we're sending the message to a specific user. Remember in Part 1 that the ToUserID
property of the Message object/table is not being filled or passed, now it's still optional to assign a value in this property. But, if you want to chat privately with someone, the
ToUserID must be assigned. See code below from the ChatWindow.aspx code file:
49 ' This is where we send a private chat invitation to other chatters
50 Private Sub InsertPrivateMessage()
51 ' if the private message is sent to this user already,
52 ' don't send it again
53 If String.IsNullOrEmpty(lblMessageSent.Text) Then
54 ' if any private message is found based on the
55 ' from user id, or the to user id, then this
56 ' private message will not be inserted
57 Dim privateMessage As PrivateMessage = New PrivateMessage()
58 privateMessage.UserID = Convert.ToInt32(lblFromUserId.Text)
59 privateMessage.ToUserID = Convert.ToInt32(lblToUserId.Text)
60
61 Dim db As LinqChatDataContext = New LinqChatDataContext()
62 db.PrivateMessages.InsertOnSubmit(privateMessage)
63 db.SubmitChanges()
64
65 ' make sure to assign any value to this label
66 ' to confirm that a message is sent to this user
67 lblMessageSent.Text = ConfigurationManager.AppSettings("ChatWindowMessageSent")
68 End If
69 End Sub
Focusing The Current Window
In Part 1 we focused the Send button by setting the defaultbutton attribute of the form tag to the ID of the Send button. This made a continous focus on the Send button
so that when we hit the "Enter key" on our keyboard, the message is sent, there's no need to manually click the Send button with your mouse to send a message. Interestingly enough,
we cannot do this when another window opens up as a child or children window because the focus will keep returning back to the parent window. Or if we also set the defaultbutton
attribute of the form tag to the ID of the Send button in the child window (ChatWindow.aspx), the focus will go back and forth from the Chatroom.aspx to the ChatWindow.aspx, and
vice versa.
So why does this happen?
-
The window will refocus the Send button, therefore focusing back on the window itself every single time there's a post back. The post back occurs
every 7 seconds on the timer tick event.
-
The parent and child windows share Sessions. You will notice in Part 1 that if you open two or more instances of the Chatroom.aspx window,
the focus does not go back and fort these windows even though the defaultbutton attribute is set for the Send button. This is
because the windows are instances of the browser, and therefore does not share Session with each other.
The solution:
We need to be able to focus the Message TextBox (where you type your messages) and the Send button dynamically and on demand. To do this, we need to be able to tell
which window is currently in focus, is it the main chat room (Chatroom.aspx)? Or is it one of the opened private chat windows (ChatWindow.aspx)? Listed below are the
events from which we need to set the focus of our message text box and the send button:
-
Shown below is the reusable method that we will call to set the focus from different events in our code. Why is the code longer in the ChatWindow.aspx code file? Because
we're assuming that each Chatroom.aspx that is open are separate instances of the browser, therefore, each Chatroom.aspx window that is
open only need one marker, "MainWindow". On the other hand, each ChatWindow.aspx that is open shares the same Session as the parent, and are not a separate instances of the
browser, this is why we need to know which of these ChatWindows is supposed to receive the focus.
From the Chatroom.aspx:
245 Private Sub FocusThisWindow()
246 form1.DefaultButton = "btnSend"
247 form1.DefaultFocus = "txtMessage"
248 Session("DefaultWindow") = "MainWindow"
249 End Sub
From the ChatWindow.aspx:
126 Private Sub FocusThisWindow()
127 Dim chatWindowToFocus As String = lblFromUserId.Text & "_" & lblToUserId.Text
128
129 If Session("DefaultWindow").ToString() = chatWindowToFocus Then
130 form1.DefaultButton = "btnSend"
131 form1.DefaultFocus = "txtMessage"
132 End If
133 End Sub
-
Every single time a window loads, or opens, whether it be the Chatroom.aspx or the ChatWindow.aspx, we need to set the focus on
the Page Load event under the !IsPostBack validation.
From the Chatroom.aspx, we simply call the method:
13 Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
14 If Not IsPostBack Then
.
.
28 Me.FocusThisWindow()
.
.
But from the ChatWindow.aspx, we need to remember which chat window must receive the focus:
13 Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
.
.
27 ' focus this window
28 Dim chatWindowToFocus As String = lblFromUserId.Text & "_" & lblToUserId.Text
29 Session("DefaultWindow") = chatWindowToFocus
30 Me.FocusThisWindow()
.
.
-
In the OnClick event of the Send button. Both Chatroom.aspx and ChatWindow.aspx code file have similar code here.
39 Protected Sub BtnSend_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSend.Click
.
.
45 Me.FocusThisWindow()
.
.
-
In the tick event of the Timer Control.
From the Chatroom.aspx page:
65 Protected Sub Timer1_OnTick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick
66 Me.GetLoggedInUsers()
67 Me.GetMessages()
68 Me.GetPrivateMessages()
69
70 If Not Session("DefaultWindow") Is Nothing Then
71 If Session("DefaultWindow").ToString() = "MainWindow" Then
72 Me.FocusThisWindow()
73 End If
74 End If
75 End Sub
From the ChatWindow.aspx page:
118 Protected Sub Timer1_OnTick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick
119 Me.GetPrivateMessages()
120
121 If Not Session("DefaultWindow") Is Nothing Then
122 Me.FocusThisWindow()
123 End If
124 End Sub
-
And lastly, in the onclick event of the Message TextBox control. The only catch here is that; the Message Text Box control does not have
a server side onclick event. So we'll have to do this from the client-side, and from the client, we will call a server-side method. In Part 1,
we implemented a callback feature so that when a chatter closes his browser, we're still able to log that user out by catching a client-side event
that calls to a server-side method, where the actual logging out is done. We will do a similar code here.
From the TextBox, we will call a client-side function, shown below:
<
asp:TextBox
Id="txtMessage"
onkeyup="ReplaceChars()"
onclick="FocusMe()"
onfocus="SetToEnd(this)"
runat="server"
MaxLength="100"
Width="500px"
/>
Here's the client-side function that is called from the onclick event of the Text Box control. Notice that it's calling
another method. This is a call-back method called asynchronously with the client-side function, which we will wire-up from the server side.
function
FocusMe()
{
FocusThisWindowCallBack(
'FocusThisWindow');
}
In the code file (code-behind), because we're implementing the ICallbackEventHandler (see Part 1 for explanation), we can easily
register scripts for the call back functionality.
35 ' create a call back reference so that we can refocus to this window when the cursor is placed in the message text box
36 Dim focusWindowCallBackReference As String = Page.ClientScript.GetCallbackEventReference(Me, "arg", "FocusThisWindow", "")
37 Dim focusThisWindowCallBackScript As String = "function FocusThisWindowCallBack(arg, context) { " + focusWindowCallBackReference + "; }"
38 Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), "FocusThisWindowCallBack", focusThisWindowCallBackScript, True)
Now, the magic happens in the ICallbackEventHandler's RaiseCallbackEvent method. The control was passed here when
we called the FocusThisWindowCallBack('FocusThisWindow') from the client-side. The "eventArgument" works as as comma-delimited
string variable, so if you passed more than one string as a parameter from the client-side function, all will be caught
by this variable. Note that all callback methods from the client-side will pass through the RaiseCallbackEvent. With
that said, in Part 1, we did not pass any parameter(s) from the Log Out callback method, because there's only one method that
will pass through here. But now both the Log Out and the Message Text Box control's client-side onclick
event will use the RaiseCallbackEvent. In short, we will differentiate who did the call back by checking the eventArgument's value.
From the Chatroom.aspx code file:
255 ''' <summary>
256 ''' We're doing 2 things here now so we want to validate whether we're trying to "LogOut" or "FocusThisWindow"
257 ''' based on the eventArgument parameter that is passed via a JavaScript callback method
258 ''' </summary>
259 Public Sub RaiseCallbackEvent(ByVal eventArgument As String) Implements System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
260 _callBackStatus = "failed"
261
262 If Not String.IsNullOrEmpty(eventArgument) Then
263 ' put back the focus on this window
264 If eventArgument = "FocusThisWindow" Then
265 Me.FocusThisWindow()
266 End If
267 End If
268
269 If Not String.IsNullOrEmpty(eventArgument) Then
270 If eventArgument = "LogOut" Then
271 ' log out the user by deleting from the LoggedInUser table
272 Dim db As LinqChatDataContext = New LinqChatDataContext()
273
274 Dim loggedInUser = (From l In db.LoggedInUsers _
275 Where l.UserID = Convert.ToInt32(Session("ChatUserID")) _
276 And l.RoomID = Convert.ToInt32(lblRoomId.Text) _
277 Select l).SingleOrDefault()
278
279 db.LoggedInUsers.DeleteOnSubmit(loggedInUser)
280 db.SubmitChanges()
281
282 ' insert a message that this user has logged out
283 Me.InsertMessage("Just logged out! " + DateTime.Now.ToString())
284 End If
285 End If
286
287 _callBackStatus = "success"
288 End Sub
From the ChatWindow.aspx code file:
139 Public Sub RaiseCallbackEvent(ByVal eventArgument As String) Implements System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
140 If Not String.IsNullOrEmpty(eventArgument) Then
141 If eventArgument = "FocusThisWindow" Then
142 Dim chatWindowToFocus As String = lblFromUserId.Text & "_" & lblToUserId.Text
143 Session("DefaultWindow") = chatWindowToFocus
144 Me.FocusThisWindow()
145 End If
146 End If
147 End Sub
Last Words:
Adding features to this web chat application using ASP.Net 3.5, LINQ-to-SQL, and ASP.Net AJAX surely made coding a whole lot faster, and therefore fun. Adding
this private imming or chatting functionality took about an hour to complete. Doing this in ASP.Net 2.0 would have taken at least 3 times longer to complete. So kudos to
the ASP.Net team. I'm planning to write a Part 3, but I'm not sure what to write about or what functionalities to add, so if you have any idea of the functionalities that I can add, please email me.
As always, the code and the article are provided "As Is", there is absolutely no warranties. Use at your own risk.