Building a demo chat app in MVC3, backbone.js and a little SignalR for fun…

I have been doing a lot of reading lately on backbone.js. One of the key features I wanted to explore was it’s concept of Models and how it keeps the server side in sync with RESTful JSON endpoints.

What is backbone.js?

Backbone supplies structure to JavaScript-heavy applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful JSON interface.

I started searching the internet to find an example in the .NET world, surely someone has an example already?  I did run across a few posts that got me in the right direction:

The Backbone.js Todo List Sample, Refactored – Part 1 via @robconery
ASP.NET MVC3 RESTful application tutorial with Backbone.js – Part I
ASP.NET MVC3 RESTful application tutorial with Backbone.js – Part II
ASP.NET MVC3 RESTful application tutorial with Backbone.js – Part III
A Backbone.js demo app (Sinatra Backend) via ryandotsmith

The BitCandies series was more than I needed, the last article with Sinatra is what I was looking for.  All I wanted to see was how backbone.js posts to the RESTful endpoints.   If I could see that in action I knew I could do the same with MVC3 JsonResults in a Controller.

The chat demo:
This demo uses MVC3 Controllers as the RESTful JSON endpoints utilizing the Json ActionResult. I also made a MVC3 Model called Message to keep the client and server-side parity. I cheated with persistence using a static class, no database needed to run the demo. The original demo I forked from used setInterval to poll the server for new messages. This worked but for only one client. I wanted this to work like a real chat app would, so I replaced that with SignalR to let the clients know new messages have arrived and to update their data. I know I could replace the Controllers with SignalR all together but this is a demo to show how to use all the pieces, not a best practices.

/Views/Shared/_Layout.cshtml(script references needed)

[sourcecode language="html"]
………
<script src="@Url.Content("~/Scripts/jquery-1.6.4.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/underscore.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/backbone.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.signalR.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script>
………
</body>
<script src="@Url.Content("~/Scripts/application.js")" type="text/javascript"></script>
………
[/sourcecode]

/Scripts/application .js (this is basically the single page app initializer for this example)

[sourcecode language="javascript"]
// SignalR Proxy created on the fly
var chat = $.connection.chat;

var Message = Backbone.Model.extend({});

var MessageStore = Backbone.Collection.extend({
model: Message,
url: ‘/messages’
});
var messages = new MessageStore;

var MessageView = Backbone.View.extend({

events: { "submit #chatForm" : "handleNewMessage" }

, handleNewMessage: function(data) {
var inputField = $(‘input[name=newMessageString]‘);
messages.create({ content: inputField.val() });

//signalr call to server
chat.send("dummy message, just signaling")
.done(function () {
console.log(‘Success!’)
})
.fail(function (e) {
console.warn(e);
})
inputField.val(”);
}

, render: function() {
var data = messages.map(function(message) { return message.get(‘content’) + ‘\n’});
var result = data.reduce(function(memo,str) { return memo + str }, ”);
$("#chatHistory").text(result);
return this;
}

});

messages.bind(‘add’, function(message) {
messages.fetch({success: function(){view.render();}});
});

var view = new MessageView({el: $(‘#chatArea’)});

//replaced with SignalR
//setInterval(function(){
// messages.fetch({success: function(){view.render();}});
//},10000)

// Declare a function on the chat hub so the server can invoke it
chat.reloadMessages = function (message) {
//server callback, reload messages from server via backbone!
messages.fetch({ success: function () { view.render(); } });
};

// Start the connection
$.connection.hub.start();

//get any messages on load, .fetch is backbone.js working here
messages.fetch({ success: function () { view.render(); } });
[/sourcecode]

/Models/MessageModels.cs

[sourcecode language="csharp"]
namespace BackboneMVC3SignalR.Models
{
public class Message
{
public string content { get; set; }
}

}
[/sourcecode]

/Controllers/MessagesController.cs

[sourcecode language="csharp"]
namespace BackboneMVC3SignalR.Controllers
{
public class MessagesController : Controller
{
//
// GET: /Messages/

public ActionResult Index()
{
List<Message> s = new List<Message>();
if (GlobalVariables.Messages != null)
{
s = GlobalVariables.Messages;

}

return Json(s, JsonRequestBehavior.AllowGet);
}

[HttpPost]
public ActionResult Index(string content)
{
List<Message> s = new List<Message>();
if (GlobalVariables.Messages != null)
{
s = GlobalVariables.Messages;

}
s.Add(new Message{ content = content});
GlobalVariables.Messages = s;

return Json(s, JsonRequestBehavior.AllowGet);

}

}
}
[/sourcecode]

/Hubs/Chat.cs

[sourcecode language="csharp"]
namespace BackboneMVC3SignalR.Hubs
{
public class Chat : Hub
{
public void Send(string message)
{
// Call the reloadMessages method on all clients
Clients.reloadMessages(message);
}
}
}
[/sourcecode]

Full working demo can be found on GitHub.

Postback UpatePanel on close of jQuery dialog

  • Put a hidden asp:button within the UpdatePanel or outside and set it as an AsyncPostBackTrigger.
  • Call the js function from ItemDataBound if needed, passing in the ClientID of the hidden asp:button.
  • The js function will call the click event on the button passed in once the “OK” or whatever you set buttonTxt to, button is clicked.
  • You can then handle the UpdatePanel.Update automatically if the button is inside the UpdatePanel or call Update within the butHidden_Click.

Markup:

[sourcecode language="xml"]
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
<asp:button id="btnHidden" style="display:none" runat="server" onclick="btnHidden_Click"/>
</asp:UpdatePanel>
[/sourcecode]

Script:
[sourcecode language="javascript"]
function showjQueryUIDialogOkBtnCallback(buttonToClick, dialogSelector, buttonTxt, isModal, width, height)
{
var buttonOpts = {};
buttonOpts[buttonTxt] = function () {
$("#" + buttonToClick).trigger(‘click’);
};

buttonOpts['Cancel'] = function () {
$(this).dialog("close");
$(this).dialog(‘destroy’);
}

$(dialogSelector).dialog({
resizable: false,
height: height,
width: width,
modal: isModal,
open: function (type, data) {
$(this).parent().appendTo("form"); //won’t postback unless within the form tag
},
buttons: buttonOpts

});

$(dialogSelector).dialog(‘open’);

}
[/sourcecode]

ASP.NET with jQuery popup dialog: how to post back on dialog closing