Friday, October 11, 2013

Expanding jQuery datatable not working in IE 10

In previous post, we put master data in jQuery datatable and when user expands a row in master data we show detail data under the row. So when expanded, the inner detail rows are inserted as shown below. Please note that this actually increased the height of datatable section.



In most of browsers (including IE 9) this works fine, but in IE 10.0 I ran into the case that the detail expansion actually did not expand the datatable. It only added vertical scroll bar to datatable and the UI looked ugly as below.



So how to solve this problem?
If end user clicks IE 10.0 compatibility mode icon (red circle in the picture above), it will resolve the issue.
But since developer better not to force end user to do that, the following meta tag section can be added to resolve the issue. That is, add X-UA-Compatible meta tag at the first position of head section.

<html lang="en">
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />  
        <meta charset="utf-8" />
        <title>@ViewBag.Title - My ASP.NET MVC Application</title>


Thursday, October 10, 2013

Simple MVC Example - Master/Detail drill-down in jQuery datatable

This post shows a simple ASP.NET MVC example where master data table is displayed in initial state and when user clicks expand icon it drills in detail table under the selected row.



The below steps are logical flow to implement this master-detail relationship in a web page.

1) Let's first look at a very simple main input form as a staring point. In the input view (Home/Index), we have two textboxes for input date range. Once submitted, it calls Home/ResultView as defined in BeginForm().

@using (Html.BeginForm("ResultView", "Home", FormMethod.Post))
{
   <div>Start Date: @Html.TextBox("StartDate")</div>
   <div>End Date: @Html.TextBox("EndDate")</div>
   <input type="submit" />    
}

2) In Home Controller, ResultView() method handles form input and gets order data for given input order dates. The method calls repository's GetOrders() method, explained in Step 4.

public class HomeController : Controller
{
    private Repository _repository = new Repository();

    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    [ValidateInput(false)]
    public ActionResult ResultView(FormCollection collection)
    {
        DateTime start = DateTime.Parse(collection["StartDate"]);
        DateTime end = DateTime.Parse(collection["EndDate"]);

        var model = _repository.GetOrders(start, end);
        return View(model);
    }

    public ActionResult ResultDetailView(int id)
    {
        var model = _repository.GetOrderItems(id);
        return PartialView(model);
    }
}

3) In Model, there are two entities - Order and OrderItem. Order (master) class contains multiple OrderItems (details or children).

public class Order
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public int CustomerId { get; set; }        
    public IEnumerable<orderitem> OrderItems { get; set; }
}

public class OrderItem
{
    public int OrderId { get; set; }
    public int Seq { get; set; }
    public int ProductId { get; set; }        
    public int Qty { get; set; }
}

4) Based on Order/OrderItem classes, we have a repository class (optional, but good to have) that handles GET requests from Controller. As said in Step 2, GetOrders() method call from Controller's ResultView() method collects sample orders for given dates.

public class Repository
{
    // Sample data
    private List<Order> _orders = new List<Order>()
        {
            new Order
                {
                    OrderId = 1,
                    OrderDate = new DateTime(2013, 10, 1),
                    CustomerId = 100,
                    OrderItems = new []
                        {
                            new OrderItem {OrderId = 1, Seq = 1, ProductId = 101, Qty = 1},
                            new OrderItem {OrderId = 1, Seq = 2, ProductId = 201, Qty = 2},
                            new OrderItem {OrderId = 1, Seq = 3, ProductId = 301, Qty = 3},
                        }
                },
            new Order
                {
                    OrderId = 2,
                    OrderDate = new DateTime(2013, 10, 2),
                    CustomerId = 200,
                    OrderItems = new []
                        {
                            new OrderItem {OrderId = 2, Seq = 1, ProductId = 401, Qty = 4},
                            new OrderItem {OrderId = 2, Seq = 2, ProductId = 501, Qty = 5}                                
                        }
                }
        };

    public IEnumerable<Order> GetOrders(DateTime startDate, DateTime endDate)
    {
        return _orders.Where(p => p.OrderDate >= startDate && p.OrderDate <= endDate)
                .Select(o => new Order { OrderId = o.OrderId, 
                    OrderDate = o.OrderDate, 
                    CustomerId = o.CustomerId} );            
    }

    public IEnumerable<OrderItem> GetOrderItems(int orderId)
    {
        var ord = _orders.SingleOrDefault(p => p.OrderId == orderId);
        return (ord != null) ? ord.OrderItems : null;
    }
}

5) After getting order data from repository, Step 2 calls ResultView.cshtml with the order data and this view shows orders (Orders only without Order Items!) in jQuery datatable. The view has a table called ResultTable and it is converted to jQuery ui datatable in $('#ResultTable').dataTable() method call as shown in the first part of javascript below.

@using MvcApplication1.Models
@model IEnumerable<Order>
@{
    ViewBag.Title = "Result View";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@{    
    <table id="ResultTable">
        <thead>  
            <tr>  
                <th>&nbsp;</th>
                <th>OrderId</th>
                <th>OrderDate</th>
                <th>CustomerId</th>                    
            </tr>  
        </thead>  
        <tbody>
            @foreach (var order in Model)
            {                                
                <tr>  
                    <td>
                        <img class="expand" src="@Url.Content("~/Content/open.png")" 
                            alt="Expand/Collapse" rel="@order.OrderId" />
                    </td>                                                           
                    <td>@order.OrderId</td>
                    <td>@order.OrderDate</td>
                    <td>@order.CustomerId</td>
                </tr>                                    
            }
        </tbody>
    </table>
    
    <div id="DetailsDialog"></div>
}
  
<script type="text/javascript">
$(document).ready(function () {
    var resultTable = $('#ResultTable').dataTable({
        "bJQueryUI": "true",        
        "sScrollX": "100%",        
        "sPaginationType": "full_numbers"
    });       
    
    $('#ResultTable tbody td img.expand').click(function () {
        var nTr = this.parentNode.parentNode;        
        if (this.src.match('close')) {
            this.src = "@Url.Content("~/Content/open.png")";
            resultTable.fnClose(nTr);
        } else {
            this.src = "@Url.Content("~/Content/close.png")";
            var orderId = $(this).attr("rel");
            var url = "@Url.Action("ResultDetailView", "Home")";
            $.get(url, { id: orderId }, function (details) {                
                resultTable.fnOpen(nTr, details, 'Details');
            });
        }
    });    
    // DetailsDialog Section
    var detailsDialog = $("#DetailsDialog").dialog({
        autoOpen: false,
        modal: true,
        title: "Details",        
        width: "auto"
    });
    $("#ResultTable").on('click', 'a.productLink', function () {        
        var productId = $(this).text();                
        detailsDialog.html(productId);                
        detailsDialog.dialog('open');
    });
});
</script>

6) In javascript above, $('#ResultTable tbody td img.expand').click() detects user click on the image icon. If image is clicked, it checks to see if the image src attribute has a substring match with (close). This pattern matching is simply to determine what the current icon name is, say, open,png or close.png. If it is open.png, we change the icon to close.png and makes an AJAX calls to Home/ResultDetailView with order id parameter. Please note that we used (id) parameter name in $.get() and that should match the parameter name of ResultDetailView(int id) method in Controller.

7) So AJAX call in Step 6 goes to ResultDetailView() method in Controller. It fetches order items (detail) with a given order id. Once data is retrieved, the partial view for order details will be generated with this view template.

@model IEnumerable<MvcApplication1.Models.OrderItem>

<table>            
    <tr>
        <th>Seq</th>   
        <th>ProductId</th>   
        <th>Qty</th>   
    </tr>    
        
    @foreach (var item in Model)
    {            
        <tr>                              
            <td>@item.Seq</td>
            <td>
                <a href="#" class="productLink">@item.ProductId</a>
            </td>
            <td>@item.Qty</td> 
        </tr>
    }    
</table>

8) Once Step 7 partial view is returned, the AJAX call in $.get (Step 5) goes to resultTable.fnOpen(nTr, details, 'Details') code to insert the partial view html snippet under the (nTr) tr tag. This simulates drill-down (or drill-in) behavior.

As a side note, what if we want to add a click event onto dynamically generated OrderItems detail table section? For example, the second column in order detail table (see Step 7) has a link tag. If user clicks this ProductId, we want to display a dialog box containing some product info. To add click event, normally we adds click() event onto a tag directly. So we can code this way.

$('a.productLink').click(...);

However, this code will not work because the event a tag whose class is productLink does not exist when the jQuery runs (aka when the document is ready). The drill down section is dynamically generated only when user clicks the expand icon.

In order to make this work, we attach the event onto any parent (or any ancestor that already exists during .on() method call) by using .on() method as shown in Step 5 script.

 $("#ResultTable").on('click', 'a.productLink', function ()...)

Selected element #ResultTable table does exist when .on() method is called. We put click event there with inner target element (a.productLink) specified in 2nd parameter. So when and only when user clicks on the inner target element (a tag), click event bubbles up to #ResultTable and executes delegated event handler. If 2nd parameter is null or omitted, it's called direct event handler where the event fires whenever the selected element (#ResultTable) or its descendant element is clicked.


Friday, September 27, 2013

ASP.NET MVC - using tr tag in if statement within a loop (Razor)

When rows to display are unknown, one can use for loop and display one row per line. For example, if 4 rows are to be displayed, they can be shown as follows.
Item1
Item2
Item3
Item4

But what if we want to display multiple items in one line? What if we want to display 2 columns in a row like this?
Item1 Item2
Item3 Item4

The following example shows how to display multiple items in a line. First, (model) data has unknown number of rows and we use foreach loop to display all data (Names in this example). COLUMN_SIZE is chosen to be 3, which means 3 columns in a row.
@{
    List<string> Names = new List<string>();
    Names.Add("Item1");
    Names.Add("Item2");
    Names.Add("Item3");
    Names.Add("Item4");
    Names.Add("Item5");

    int count = 0;
    int COLUMN_SIZE = 3;
}

<div>
    <table>  
        <tr>
        @foreach (var name in Names)
        {                                         
            <th>
                @Html.Label(name)
            </th>
            <td>                
                @Html.TextBox(name)
            </td>
                
            if (++count % COLUMN_SIZE == 0) 
            {                     
                @:</tr> 
                @:<tr> 
            }
        }
    </table>    
</div>

foreach loop gets data one by one and put it into td tag. Please note that we put tr tag before foreach to initiate tr open tag. For tr close tag, keep counting the counter and once it reaches column size boundary, we put tr close tag and then initiate another tr open tag. Razor engine keeps tracking of open/close tag match and implicitly adds tr open / close tag during its rendering.
In Razor, @: represents the next text is considered as a plain text.

Tuesday, July 9, 2013

How to enumerate all virtual directories from WMI (IIS 7.0+)

Here is a simple way of enumerating all IIS 7.0 (or IIS 8.0) virtual directories. IIS 7.0+ exposes WMI namespace called WebAdministration . One of simple access to the WMI data is using Powershell. Sure, other approaches such as VBScript, C++, C# will work but if Powershell might be simplest one, I guess.

So the steps are:
1. Run Powershell.
2. In Powershell, run the following command.

     PS> gwmi -namespace root\WebAdministration -class VirtualDirectory

IIS 7.0+ uses root\WebAdministration namespace and to check virtual directory read data from VirtualDirectory class.

3. If you want to check Web Application, run this:

     PS> gwmi -namespace root\WebAdministration -class Application

Friday, May 31, 2013

WCF - SendTimeout setting in CustomBinding

Timeout in WCF can occur when WCF communication takes longer than default timeout time. There are open/close/send/receive timeout but in many cases most important timeout is SendTimeout. If wait time is expected to be long, sometimes you might want to adjust SendTimeout property. This can be adjusted in code or web.config (or app.config in case of self hosting). In this example, we have self hosting console application that hosts a simple WCF service. Typically we use BasicHttpBinding or WsHttpBinding but this example uses a CustomBinding (for some reason, not important here).
static void Main(string[] args)
{
    Uri baseAddress = new Uri("http://localhost:8899/");
    ServiceHost host = new ServiceHost(typeof(SimpleService), baseAddress);

    BasicHttpBinding basicBinding = new BasicHttpBinding("MyBasicBinding");
    BindingElementCollection bec = basicBinding.CreateBindingElements();
    bec.Find().KeepAliveEnabled = false;

    CustomBinding customBinding = new CustomBinding(bec);

    // By default, sendTimeout is 1 min
    // customBinding does not automatically inherit basicBinding attr settings
    //customBinding.SendTimeout = basicBinding.SendTimeout;            

    host.AddServiceEndpoint(typeof(ISimpleService), customBinding, "ISimple");

    Console.WriteLine("SendTimeout" + customBinding.SendTimeout);
    Console.WriteLine("ReceiveTimeout" + customBinding.ReceiveTimeout);

    host.Open();

    Console.WriteLine("WCF Service Started. Press ENTER to stop.");
    Console.ReadLine();

    host.Close();
}

app.config
<configuration>
    <system.serviceModel>
      <bindings>
        <basicHttpBinding>
          <binding name="MyBasicBinding" maxReceivedMessageSize="40960" sendTimeout="00:05:00" receiveTimeout="00:05:00">
            <readerQuotas maxStringContentLength="40960"/>
          </binding>
        </basicHttpBinding>
      </bindings>
      
    </system.serviceModel>
</configuration>

The example gets BasicHttpBinding settings from app.config, and pass bindingElements to CustomBinding. In app.config, MyBasicBinding has sendTimeout and receiveTimeout attributes. I expected that those values were copied to custom binding but it turned out that those attributes are not. By default, sendTimeout is set to 1 minute and receiveTimeout is set to 10 mins. Unless explicitly set in the code, the self hosting service above will keep default value. So in this example, the code customBinding.SendTimeout = basicBinding.SendTimeout; should be uncommented to set timeout value.