I started thinking about a way to join the three most powerful tools for web development: MonoRail, Ext JS and JSON.Net. I realy think they were meant to live together for all eternity (which in IT world, means two to tree and a half years. :D)
My way to join this three tecnologies is the simplest possible. Only a different way in using old things.
First of all, it's possible with Ext JS to make AJAX call directly to pages. MonoRail enables us to have a page without actually reneding a page. If you do something like this in Ext JS:
Ext.Ajax.request({
url: '/home/LoadProducts.rails',
success: loadData,
params: { Category: 3 }
});
function loadData(data) {
//Do something usefull with data like defining input form values...
}
All you need to have in ServerSide is a HomeControler.cs with this:
[Layout("default")]
public class HomeController : SmartDispatcherController {
public void LoadProducts(Int32 category) {
String response = String.Empty;
//Do your server side code to get a Category object
response = JavaScriptConvert.SerializeObject(yourCategoryObject); //Using JSON.Net to serialize the object in JSON.
Response.Charset = "UTF-8";
Response.ContentType = "text/javascript";
RenderText(response);
}
}
You don't need a LoadProducts view because the response will be sent to the loadData JavaScript function. And that function is the key aspect of the presentation layer. There, all the data will be processed and put in its right place, because it knows exactly how to do it.
After creating this structure, I tried to match this to some known pattern. My good friend Bernardo had some observations on this (as he always does). Since we defined the same entry point, in the web page, for all messages sent to Server Sider and, of course, we'll have the same callback function to process them.
But, in real world applications, there's a lot of complex functionalities which require a lot of posts and re-arrange of page values. So, how to keep each piece in its place? And better yet: how to simplify the flow of data? And even better: how can we make the interaction between components in a message-coupled level?
At this point, my friends and I desagree, but I will show how I envision this solution and they can comment back on it (as I'm sure they will).
Taking the idea behind __doPostBack JavaScript function used by ASP.NET, I created a sendPost function in my page:
var invoker = new InvokeFor();
function sendPost(moduleId, action, jsonData) {
var pageData = //put here some JSON data that must be used for each post, like generic information.
invoker.invokeFor(moduleId, action, jsonData, pageData);
}
This function must be visible for all JavaScript code in execution in this page. It just receives the data to be sent to server and calls the invokeFor method of the InvokeFor JavaScript class. This class is the actual code that sends the post. The sendPost funcion is the last chance to treat the data before it gets sent.
The InvokeFor class then does the AJAX call:
function Invoke() {
this.invokeFor = function(moduleId, actionName, jsonData, pageData) {
Ext.Ajax.request({
url: '/home/InvokeFor.rails',
success: loadData,
params: { TextModule: textModule, Action: actionName, Json: jsonValue, PageData = pageData }
});
}
this.loadData = function(data) {
modules = eval(data);
for (i = 0; i < modules.length; i++) {
var module = eval("new " + modules[i].JavaScriptClassName + "();");
module.loadData(modules[i].JsonData, modules[i].ModuleId);
}
}
}
Ok, check the loadData function used to receive the data as a callback function. It expects to receive a JSON module wich has a JavaScriptClassName, ModuleId and a JsonData properties. This class must feature the name of the message in JavaScriptClassName, a loadData function wich receives a JSON data object and a module id property. The loadData function doesn´t expect only one module, but an array of them. Complicated? I don't think so. If I stopped this post rigth now, I guess you would be capable to finish the code.
The next step, at the client side, is to build these JavaScript class. Here is an example:
function CategoryGrid() {
this.loadData = function(myData) {
Ext.get("listOfCategories").html('');
myData = eval(myData);
function btnDelete(val) {
return '<input type="button" id="btnCategoryDelete" value="Delete" onclick="sendPost(\'listOfCategories\', \'delete\', ' + val + ')" />';
}
var store = new Ext.data.JsonStore({
fields: ['Id', 'Name'],
data: myData
});
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{id:'Id',header: "Id", width: 50, sortable: true, dataIndex: 'Id'},
{header: "Name", width: 120, sortable: true, dataIndex: 'Name'},
{header: "", width: 80, sortable: false, renderer: btnDelete, dataIndex: 'Id'}
],
stripeRows: true,
height:300,
width:270,
title:'Categories',
renderTo: 'listOfCategories'
});
grid.on("cellclick", function(grid, rowIndex, columnIndex, e) {
if (columnIndex != 2) {
var record = grid.getStore().getAt(rowIndex);
var data = '{ "Id":"' + record.get("Id") + '", "Name": "' + record.get("Name") + '" }';
sendPost("listOfCategories", "select", data);
}
});
}
}
Very simple hum??? That's all that is to it. If you look at this class, you will see at the grid.on("cellclick", function(grid, rowIndex, columnIndex, e) listener that it uses the sendPost function too, to execute a select action. Who subscribes to this data? Who will use this data? You can't tell, right? Neither does the class. And this is the very idea behind this code.
In my next post I will explain what happens at the server side. And how one click can change all the page without an ASP.NET post back.