This is a migrated thread and some comments may be shown as answers.

[Solved] Dynamic data binding

9 Answers 197 Views
Grid
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Kemal
Top achievements
Rank 1
Kemal asked on 03 Nov 2011, 01:06 PM
Hi there,

I have a requirement for displaying different type of entities on the same MVC page depending on the productType value inside the page request. To achive this, on the initial request I am genereting a DataTable in the controller action and providing this as the Grid Model (Model.ProductSchemaDataTable), see below:

<%= Html.Telerik().Grid(Model.ProductSchemaDataTable)
        .Name("ProductGrid")
        .Columns(columnFactory => ProductGridHelper.FormatColumns(columnFactory, Model.ProductMetaDataType))
        .DataBinding(binding => binding.Ajax().Select("Search", "Product", Model.DefaultSearchRequest)).Scrollable(scrolling => scrolling.Height(500))
        .ClientEvents(events => events.OnLoad("onLoad"))
        .ClientEvents(events => events.OnDataBinding("onDataBinding"))
        .Footer(false)
%>

On the initial page request, DataTable will be empty. I am just using it to generate the columns in a dynamic way. On the consequent ajax request I simply query related products (Product/Search action) and return them as below:

[GridAction]
public ActionResult Search(ProductSearchRequest searchRequest)
{
    var productConfig = ServiceContainer.ProductConfigService.Get(searchRequest.SystemId);
     
    var products = ServiceContainer.ProductService.GetProducts(searchRequest.FromDate,
                                                                        searchRequest.ToDate,
                                                                        searchRequest.Currency,
                                                                        searchRequest.UserName,
                                                                        productConfig.ProductConfigId);
 
    var gridModel = new GridModel { Data = products, Total = products.Count() };
 
    return View(gridModel);
}

The problem of this approach, I won't be able to bind any property that have a custom type. To give an example if related product has Category object which has Id and Name fields, I won't be able to bind Name property of the Category property on that product type as I originally used a DataTable to create the columns of the grid.

So my question is, is there any way I can bind sub properties even if I create the grid through a DataTable? Or is there any other approach that I can bind different types to same grid depending on the page request values?

9 Answers, 1 is accepted

Sort by
0
James
Top achievements
Rank 1
answered on 03 Nov 2011, 04:24 PM
In my experience, I try to keep complex objects out of any datasource that I know I will be binding to a grid.  What I usually do is create an object that essentially encapsulates the value for a given row, and then assign values as needed in my controller so that, in the end, I have a collection of objects that really only have scalar values.  This might not be possible for your situation, but I hope that helps.

I am curious, however, as to your solution of a custom GridColumnFactory.  I'm referring specifically to this snippet of code:

ProductGridHelper.FormatColumns(columnFactory, Model.ProductMetaDataType)

I am looking to implement something very similar on my end but I have been unable to get it to work.  Any tips or suggestions?  Thanks.  :)

James
0
Kemal
Top achievements
Rank 1
answered on 04 Nov 2011, 04:20 PM
Hi James,

I managed to solve this by looking at this link: http://www.telerik.com/community/code-library/aspnet-mvc/grid/binding-to-a-collection-of-dynamic-objects-with-mvc3-razor.aspx and I must say it worked quite well.

I am no  longer passing a DataTable as a model it is just and IEnumerable<dynamic> and that makes sure that product gets resolved at the runtime.

For different products I need to display different columns and I needed something dynamic for this. So I created below attribue:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class GridColumnAttribute: Attribute
{
    public GridColumnAttribute()
    {
        IsKey = false;
        Width = 100;
    }
 
    public bool IsKey { get; set; }
    public int Width { get; set; }
    public string DataBindProperty { get; set; }
    public string DisplayName { get; set; }
    public string DisplayFormat { get; set; }
    public GridColumnTextAlign TextAlign { get; set; }
}
 
public enum GridColumnTextAlign
{
    Left,
    Center,
    Right
}

and also created a ProductMedata class for each different product classes and decorated the properties with this attribute. So in way this provided me a quick way of configuring my gris for different products by just decorating them with an attribute.

Below is the code for the custom column generator:

public static void GenerateColumns(GridColumnFactory<dynamic> columnFactory, Type productMetadaType)
       {
           var properties = productMetadaType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
           var keyProperty = GetPropertyMarkedAsKey(properties);
 
           if (keyProperty == null)
           {
               var errMessage = string.Format("Metadata type {0} must have at least one property marked with GridColumnAttribute(IsKey=true)", productMetadaType.GetType().Name);
               throw new MissingMemberException(errMessage);
           }
 
           AddCheckBoxColumn(columnFactory, keyProperty.Name);
            
           foreach (var property in properties)
           {
               var customAttributes = property.GetCustomAttributes(typeof(GridColumnAttribute), false);
                
               if (customAttributes.Length > 0)
               {
                   var attribute = (GridColumnAttribute) customAttributes[0];
                   var textAlignHtmlValue = GetTextAlignHtmlValue(attribute.TextAlign);
                   var propertyName = property.Name;
                    
                   if(!string.IsNullOrEmpty(attribute.DataBindProperty))
                   {
                       propertyName += "." + attribute.DataBindProperty;
                   }
 
                   var builder = columnFactory.Bound(propertyName)
                       .Width(attribute.Width + "px")
                       .HeaderHtmlAttributes(new { @style = "text-align: " + textAlignHtmlValue })
                       .HtmlAttributes(new {@style = "text-align: " + textAlignHtmlValue})
                       .HtmlAttributes(new {@id = "column_" + property.Name});
 
                   if(!string.IsNullOrEmpty(attribute.DisplayName))
                   {
                       builder.Title(attribute.DisplayName);
                   }
 
                   if (!string.IsNullOrEmpty(attribute.DisplayFormat))
                   {
                       builder.Format(attribute.DisplayFormat);
                   }
               }
           }
       }
private static PropertyInfo GetPropertyMarkedAsKey(IEnumerable<PropertyInfo> properties)
{
    PropertyInfo keyProperty = null;
 
    foreach (var property in properties)
    {
        var customAttributes = property.GetCustomAttributes(typeof (GridColumnAttribute), false);
        if (customAttributes.Length > 0)
        {
            var attribute = (GridColumnAttribute) customAttributes[0];
            if (attribute.IsKey)
            {
                if (keyProperty == null)
                {
                    keyProperty = property;
                }
                else
                {
                    throw new InvalidDataException("There cannot be more than one GridColumnAttribute with IsKey=true in the Product Medata");
                }
            }
        }
    }
 
    return keyProperty;
}

Hope it helps, any question let me know.

0
James
Top achievements
Rank 1
answered on 04 Nov 2011, 09:24 PM
Kemal, thank you so much for posting those code samples.  After some work and some meetings with my boss and fellow developer, a lot of the dynamic binding we want to be able to do has been pushed into future sprints as we just want to get something functional right now, so I'm strongly typing quite a bit.  However, this information will be exceptionally helpful in the future, so I thank you very much.  If I end up using any of this, I will be sure to give credit to you as well as a link to this thread in any class comments.
0
James
Top achievements
Rank 1
answered on 08 Nov 2011, 12:26 AM
Kemal, I was able to get your sample code integrated (and credited properly, of course), and it works fabulously.  I do wonder, however, if you've noticed any odd behavior concerning column reordering, grouping and scrolling?  I am experiencing this myself (see this thread), so I'm curious as to if you've seen this, and if so, how you have solved it.  Thanks again.

James
0
James
Top achievements
Rank 1
answered on 10 Nov 2011, 08:57 PM
Kemal, how did you do your AddCheckBoxColumn() method?  I can't for the life of me figure it out, and I've been banging my head against this for most of the day, hehe.  Kindly respond at your earliest convenience.  Thanks again for all the help.

James
0
Kemal
Top achievements
Rank 1
answered on 11 Nov 2011, 12:42 PM
Hi James,

Here is the latest version of the helper, hope it helps.

public class ProductGridHelper
   {
       public static void GenerateColumns(GridColumnFactory<dynamic> columnFactory, Type ProductMetadaType)
       {
           var properties = ProductMetadaType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
           var keyProperty = GetPropertyMarkedAsKey(properties);
 
           if (keyProperty == null)
           {
               var errMessage = string.Format("Metadata type {0} must have at least one property marked with GridColumnAttribute(IsKey=true)", ProductMetadaType.GetType().Name);
               throw new MissingMemberException(errMessage);
           }
 
           AddCheckBoxColumn(columnFactory, keyProperty.Name);
            
           AddFieldColumns(columnFactory, properties);
 
           AddActionLinks(columnFactory);
       }
 
       private static void AddFieldColumns(GridColumnFactory<object> columnFactory, IEnumerable<PropertyInfo> properties)
       {
           foreach (var property in properties)
           {
               var customAttributes = property.GetCustomAttributes(typeof(GridColumnAttribute), false);
                
               if (customAttributes.Length > 0)
               {
                   var attribute = (GridColumnAttribute) customAttributes[0];
                   var textAlignHtmlValue = GetTextAlignHtmlValue(attribute.TextAlign);
                   var propertyName = property.Name;
                    
                   if(!string.IsNullOrEmpty(attribute.DataBindProperty))
                   {
                       propertyName += "." + attribute.DataBindProperty;
                   }
 
                   var builder = columnFactory.Bound(propertyName)
                       .Width(attribute.Width)
                       .HeaderHtmlAttributes(new { @style = "text-align: " + textAlignHtmlValue })
                       .HtmlAttributes(new {@style = "text-align: " + textAlignHtmlValue})
                       .HtmlAttributes(new {@id = "column_" + property.Name});
 
                   if(!string.IsNullOrEmpty(attribute.DisplayName))
                   {
                       builder.Title(attribute.DisplayName);
                   }
 
                   if (!string.IsNullOrEmpty(attribute.DisplayFormat))
                   {
                       builder.Format(attribute.DisplayFormat);
                   }
               }
           }
       }
 
       private static void AddActionLinks(GridColumnFactory<dynamic> columnFactory)
       {
           const string actionButtonsHtml =
               "<a href='javascript:displayProductEdit(<#= ProductId #>);' class='ProductEdit'>Edit</a>"
               + "  <a href='javascript:deleteProduct(<#= ProductId #>);' class='ProductDelete'>Delete</a>"
               + "  <a href='javascript:displayProductCopy(<#= ProductId #>);' class='ProductCopy'>Copy</a>";
 
           columnFactory
               .Bound("ProductId")
               .Title("")
               .Width(90)
               .ClientTemplate(actionButtonsHtml);
       }
 
       private static void AddCheckBoxColumn(GridColumnFactory<dynamic> columnFactory, string keyPropertyName)
       {
           columnFactory.Bound("ProductId")
                       .Width("25px")
                       .HtmlAttributes(new { @style = "text-align: center" })
                       .HtmlAttributes(new { @id = "column_Id" })
                       .ClientTemplate(string.Format("<input type='checkbox' class='value-checkbox' value='{0}'></input>", keyPropertyName))
                       .HeaderTemplate("<input type='checkbox' id='checkbox-select-all'></input>");
       }
 
       private static PropertyInfo GetPropertyMarkedAsKey(IEnumerable<PropertyInfo> properties)
       {
           PropertyInfo keyProperty = null;
 
           foreach (var property in properties)
           {
               var customAttributes = property.GetCustomAttributes(typeof (GridColumnAttribute), false);
               if (customAttributes.Length > 0)
               {
                   var attribute = (GridColumnAttribute) customAttributes[0];
                   if (attribute.IsKey)
                   {
                       if (keyProperty == null)
                       {
                           keyProperty = property;
                       }
                       else
                       {
                           throw new InvalidDataException("There cannot be more than one GridColumnAttribute with IsKey=true in the Product Medata");
                       }
                   }
               }
           }
 
           return keyProperty;
       }
 
       private static string GetTextAlignHtmlValue(GridColumnTextAlign textAlign)
       {
           switch (textAlign)
           {
               case GridColumnTextAlign.Left:
                   return "left";
               case GridColumnTextAlign.Center:
                   return "center";
               case GridColumnTextAlign.Right:
                   return "right";
               default:
                   return "left";
           }
       }
   }

I've replaced some of the company business specific variable names so you might have to correct some variables starting with capitals etc.. Also there are some action buttons now which works with other elements on my page, you can remove those...

Checked the other post you mentioned and saw that it is sorted out.

Regards,
Kemal
0
James
Top achievements
Rank 1
answered on 11 Nov 2011, 04:11 PM
Kemal, you're a saint, and I can't thank you enough.  You know, it might be worth it to make a sample project with some of this logic showing how to bind a grid to an IEnumerable<dynamic> and post it to the Code Library.  The sample there right now is nowhere near this complete (helpful though it is).  Might score some Telerik points, because I'd be surprised if functionality like this becomes more common.

Thank you again.

James
0
Kemal
Top achievements
Rank 1
answered on 11 Nov 2011, 05:15 PM
I am glad that I was able to help. I will try to make it a sample project once this is finished and working fine in production.

Regards,
Kemal
0
Gaurav
Top achievements
Rank 1
answered on 18 Jan 2013, 04:55 AM
Hi Kemal,
I am new to MVC as well as Telerik. But that was just amazing stuff though I was not able to digest all.
We have the same requirement i.e. to create grid dynamically ofcourse with dynamic columns and having editing functionality. Can you please let me know if your implementation support grid editing. If it does which I think it does then can you please provide the sample code which I can understand by debugging.
I did post a thread last week (here) but no one replied to that yet. We are really behind the schedule and need to complete this immediately. So can you please reply at the earliest whenever you will get the time.

Hi James,
If you have working solution of this and can share that then it will be really great.

If I make some additions to your solutions then I will definitely send them back. Will be really grateful if any one of you could share the sample code.
Tags
Grid
Asked by
Kemal
Top achievements
Rank 1
Answers by
James
Top achievements
Rank 1
Kemal
Top achievements
Rank 1
Gaurav
Top achievements
Rank 1
Share this question
or