[CallbackMethod] protected DataTable GetCustomerList() { busCustomer Customer = new busCustomer(); if ( Customer.GetCustomerList() < 0) throw new ApplicationException(Customer.ErrorMessage); return Customer.DataSet.Tables["TCustomerList"]; }
The method simply uses a business object to create a datatable internally and then returns this value. The [CallbackMethod] attribute makes this method accessible to the client.
For the client to call this method and databind the data to you can use code like this (ToolTip optional):
<script> function GetCustomerList() { var ToolTip = new wwToolTip('lstCustomerList'); ToolTip.show("Updating...",0); // *** Call the Server GetCustomerList method Proxy.GetCustomerList(GetCustomerList_Callback,OnPageError); } function GetCustomerList_Callback(Result) { // *** Clear the tooltip set when the request started var ToolTip = new wwToolTip('lstCustomerList'); ToolTip.hide(); // *** Our Result here is a DataTable which is now // *** an object that has an array of rows and properties // *** for each of the column values. var DataTable = Result; // *** Use a list control to databind easily var List = new wwList("lstCustomerList"); List.dataValueField = "CustomerId"; List.dataTextField = "CompanyName"; List.setData(DataTable); } </script>
The callback is made and the DataTable returned as an object to the callback method. This code uses a couple of DOM object wrappers in the wwScriptLibrary, so the code to bind the DataTable to a list control consists just of a few lines of code. This code hides the structure of the DataTable object returned, so if you were to manually loop through the items and add them you could also use code like this:
List.clear(); for(x = 0; x < DataTable.Rows.length; x++) { var Row = DataTable.Rows[x]; List.addItem(Row.CompanyName,Row.CustomerId); // Display/Value }
public class CustomerEntity : customersRow { public CustomerEntity() : base() { } public string Company = ""; public string Name = ""; public string Address = ""; public DateTime Entered = DateTime.MinValue; ... public PhoneNumber PhoneNumbers = new PhoneNumber(); public ordersRow[] Orders = null; public void LoadOrders() { busInvoice Invoice = new busInvoice(); if (Invoice.GetInvoicesForCustomer(this.Customerid) < 1) { this.Orders = null; return; } List<ordersRow> OrderList = new List<ordersRow>(); foreach (DataRow Row in Invoice.DataSet.Tables["TCustomerInvoiceList"].Rows) { ordersRow Order = new ordersRow(); Westwind.Tools.wwDataUtils.CopyObjectFromDataRow(Row, Order); OrderList.Add(Order); } this.Orders = OrderList.ToArray(); } }
You can use either properties or fields. You can now publish this object from a Server method like this:
[CallbackMethod] protected CustomerEntity GetCustomerEntity(string CustomerId) { busCustomer Customer = new busCustomer(); if (CustomerId == null || CustomerId == "") CustomerId = "ALFKI"; if (!Customer.Load(CustomerId)) throw new ApplicationException("Customer doesn't exist"); if (this.chkReturnOrderEntities.Checked) { // *** Load array of orders into Orders property of entity Customer.Entity.LoadOrders(); } return Customer.Entity; }
On the client you can start the request like this:
<asp:TextBox ID="txtCustomerId" runat="server" Width="126px">ALFKI</asp:TextBox> <input id="btnReturnCustomer" type="button" value="Return Customer Object" onclick="GetCustomerEntity();" />
and then using this code:
<script> // *** Global Customer reference so we can update the customer // *** and pass it back to the server var Customer = null; function GetCustomerEntity() { Proxy.GetCustomerEntity($w('txtCustomerId').value,GetCustomerEntity_Callback,OnPageError); } function GetCustomerEntity_Callback(Result) { // *** Update global Customer reference Customer = Result; // *** Note the $w() function is equal to document.getElementById() $w('txtCompanyName').value = Customer.Companyname; $w('txtContactName').value = Customer.Contactname; $w('txtAddress').value = Customer.Address; // *** Child object value $w('txtPhone').value = Customer.PhoneNumbers.HomePhone; var Panel = $w('OrderDetailPane'); if ( !$w('chkReturnOrderEntities').checked ) { Panel.style.display='none'; return; } Panel.style.display=''; // *** Now bind the line items to a listbox var List = new wwList("lstOrders"); List.clear(); for (x=0; x < Customer.Orders.length; x++ ) { var Order = Customer.Orders[x]; List.addItem( Order.Orderid.toString() + ' - ' + Order.Orderdate.toLocaleDateString() ); } } </script>
The customer callback proxy is used to pass a parameter to the server. The Customer Id is passed which the server uses to process the request and return a complex entity object. The callback returns to the callback function with the object as the result value. The object has the same structure as the server object so there are properties for CompanyName, ContactName and there's a PhoneNumbers subobject with a HomePhone property and if requested an Orders collection which is represented as a a plain array on the client.
Note that the object returned is only a 'Message' object so none of the server methods are available - only the data structure. Think of the data in the same way you do of Web Service proxies which return only proxy objects with the data
The code above takes the object and parses the base values into the form values into the appropriate single field controls. For the Orders array, you can use a wwList object to bind the values, or manually loop through the list and assign each value. In this example, I have to use the latter, because the value displayed is not a single field value but rather composite of two fields. You can't databind so manual looping is required. To do so you can loop through each Order item and then assign it to the list individually.
I'm using the wwList control because it makes things much easier, but you can of course use raw JavaScript and raw HTML DOM access to do this as well, but it requires a little more code and some workarounds for DOM incompatibilities.
Here's the code to send to the server. The ReturnObject() function can be called from an onclick of a button:
// Pass the Customer Object loaded on the page back to // the server. function ReturnObject() { // *** Take the object with it's updated data // *** and send it to the server. if (Customer) { Customer.Companyname = $w('txtCompanyName').value; Customer.Contactname = $w('txtContactName').value; Customer.Address = $w('txtAddress').value; Customer.PhoneNumbers.HomePhone = $w('txtPhone').value; // *** Note we're passing the Customer Object as a parameter here Proxy.ReturnCustomerEntity(Customer,ReturnObject_Callback,OnPageError); } else alert('Customer Object doesn\'t exist'); }
The call now goes to the server to hit the ReturnCustomerEntity method which simply echos the content of the object back to the client as a string:
[CallbackMethod] protected string ReturnCustomerEntity(CustomerEntity Customer) { StringBuilder sb = new StringBuilder("Echo from Server (" + DateTime.Now.ToString("T") + ")\r\n\r\n" + Customer.Companyname + "\r\n" + Customer.Contactname + "\r\n" + Customer.Address + "\r\n" + Customer.PhoneNumbers.HomePhone ); if (Customer.Orders != null) { sb.Append("\r\n\r\nOrders returned:\r\n"); foreach(ordersRow Order in Customer.Orders) { sb.Append(Order.Orderid.ToString() + " - " + Order.Orderdate.ToString("d") + "\r\n"); } } return sb.ToString(); }
The client then gets this string result returned in the specified Callback function in this case ReturnObject_Callback which is a one liner:
function ReturnObject_Callback(Result) { alert(Result); }
So as you can see it's pretty easy to pass data back and forth between the client and the server.
The parser also won't deal with recursive object structures.
So rather than passing parameters you can also choose to pass the form's POST data to the server by setting the wwCallbackMethod Control's PostBackMode=PostbBackModes.Post or PostBackModes.PostNoViewState. This will send the current page data back to the server in addition to the method parameters.
You can then access the POST data directly from the server with Request.Form. It's often just as easy or easier to read form variables on the server and perform conversions than to do it on the client side so it can be more efficient to bypass parameters and use the POST data directly on the server.
For example, if you look back at the ReturnObject() method you'll notice that I make manual assignments to the Customer object - this assignment can be done on the client or the server and in the example, you are really duplicating work as well as causing the client to encode to JSON and the server to decode from JSON, when we could just simply read the POST data directly on the server.
The wwCallbackMethod control allows returning the current POST buffer to the client with or without ViewState information via the PostBackMode property setting. If ViewState info is passed back, control data gets reassigned from the POST data with the latest values this means that you can reference this.txtCompany.Text in a callback and expect to see the current value. If you use POSTNoViewState all of the ASP.NET state data is not passed back, which can significantly reduce the size of the request buffer. In this case you can use Request.Form[] to retrieve form content.