Telerik Blazor Grid: GridAutoGeneratedColumns set the width of a column in the model using Data annotation - telerik

I am using Telerik Blazor Grid and GridAutoGeneratedColumns feature to generate a grid and its columns regarding the properties of the model.
here's the question:
I remember I saw something like adding a Data annotation to the model's property which defines the ColumnWidth for instance to indicate a specific width for a column. But I cannot find it anymore.
So, generally, is there any way to define a specific column width for a property in the model so the auto generated columns can render it automatically and dynamically?
Have a look on the code so it'll be more clear:
#page "/test"
#using System.ComponentModel.DataAnnotations;
<TelerikGrid Data=#GridData
AutoGenerateColumns="true"
Pageable="true"
Sortable="true"
Groupable="true"
OnUpdate="#UpdateItem"
OnDelete="#DeleteItem"
OnCreate="#CreateItem">
<GridToolBar>
<GridCommandButton Command="Add" Icon="add">Add Employee</GridCommandButton>
</GridToolBar>
<GridColumns>
<GridColumn Field="#nameof(Employee.EmployeeId)" Title="Employee Id" Width="120px" Editable="false" />
<GridAutoGeneratedColumns />
<GridCommandColumn>
<GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="delete">Delete</GridCommandButton>
<GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
<GridCommandButton Command="Cancel" Icon="cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
#code {
public class Employee
{
public Employee()
{
HireDate = MeetingDate = DateTime.Now;
}
[Display(AutoGenerateField = false, Name = "Employee #")]
public int? EmployeeId { get; set; }
[Editable(false)]
[Display(Name = "Employee Name")]
public string Name { get; set; }
[Display(Name = "Age In Years")]
public int? AgeInYears { get; set; }
[Display(Name = "Graduate Grade")]
public decimal? GraduateGrade { get; set; }
[Display(Name = "HireDate")]
public DateTime HireDate { get; set; }
[Display(AutoGenerateField = false, Name = "Meeting Date")]
public DateTime MeetingDate { get; set; }
}
public List<Employee> GridData { get; set; }
protected override void OnInitialized()
{
GridData = new List<Employee>();
var rand = new Random();
for (int i = 0; i < 100; i++)
{
GridData.Add(new Employee()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
AgeInYears = rand.Next(10, 80),
HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20)),
MeetingDate = DateTime.Now.Date.AddDays(rand.Next(20, 40)),
GraduateGrade = i % 4 + 3
});
}
}
private void CreateItem(GridCommandEventArgs args)
{
var argsItem = args.Item as Employee;
argsItem.EmployeeId = GridData.Count + 1;
argsItem.Name = "Employee " + argsItem.EmployeeId;
GridData.Insert(0, argsItem);
}
private void DeleteItem(GridCommandEventArgs args)
{
var argsItem = args.Item as Employee;
GridData.Remove(argsItem);
}
private void UpdateItem(GridCommandEventArgs args)
{
var argsItem = args.Item as Employee;
var index = GridData.FindIndex(i => i.EmployeeId == argsItem.EmployeeId);
if (index != -1)
{
GridData[index] = argsItem;
}
}
}
And the result is something like:
And what I expect is setting a specific column width for instance, something like this:
[Editable(false)]
[Display(Name = "Employee Name")]
[ColumnWidth="200px"]
public string Name { get; set; }
Thank you in advance and stay healty and productive.

The column width feature of the autogenerated columns is the ColumnWidth parameter that lets you set the same width for all of them. You can read more about it in the AutoGenerated Columns - Customization section
There is no "column width" attribute out-of-the-box in C# and so it would be strange for the grid to use it. What I can suggest you consider is that you create a custom attribute (see an example here) and use a loop to create the columns in a fashion similar to this example (you can use reflection to get the fields from the model if you don't use an expando object), and then read their attributes and set the Width parameter - essentially, make your own column generation.

Related

Dynamic LINQ: Comparing Nested Data With Parent Property

I've a class with following structure:
public class BestWayContext
{
public Preference Preference { get; set; }
public DateTime DueDate { get; set; }
public List<ServiceRate> ServiceRate { get; set; }
}
public class ServiceRate
{
public int Id { get; set; }
public string Carrier { get; set; }
public string Service { get; set; }
public decimal Rate { get; set; }
public DateTime DeliveryDate { get; set; }
}
and I've dynamic linq expression string
"Preference != null && ServiceRate.Any(Carrier == Preference.Carrier)"
and I want to convert above string in Dynamic LINQ as follows:
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, null).Compile();
But it showing following error:
Please correct me what am I doing wrong?
It looks like you wanted to do something like this:
var bwc = new BestWayContext
{
Preference = new Preference { Carrier = "test" },
DueDate = DateTime.Now,
ServiceRate = new List<ServiceRate>
{
new ServiceRate
{
Carrier = "test",
DeliveryDate = DateTime.Now,
Id = 2,
Rate = 100,
Service = "testService"
}
}
};
string condition = "Preference != null && ServiceRate.Any(Carrier == #0)";
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, bwc.Preference.Carrier).Compile();
bool res = expression(bwc); // true
bwc.ServiceRate.First().Carrier = "test1"; // just for testing this -> there is only one so I've used first
res = expression(bwc); // false
You want to use Preference which belong to BestWayContext but you didn't tell the compiler about that. If i write your expression on Linq i will do as follows:
[List of BestWayContext].Where(f => f.Preference != null && f.ServiceRate.Where(g => g.Carrier == f.Preference.Carrier)
);
As you see i specified to use Preference of BestWayContext.

Getting an Enum to display on client side

I'm having hard time understanding how to convert an Enum value to it's corresponding name. My model is as follows:
public class CatalogRule
{
public int ID { get; set; }
[Display(Name = "Catalog"), Required]
public int CatalogID { get; set; }
[Display(Name = "Item Rule"), Required]
public ItemType ItemRule { get; set; }
public string Items { get; set; }
[Display(Name = "Price Rule"), Required]
public PriceType PriceRule { get; set; }
[Display(Name = "Value"), Column(TypeName = "MONEY")]
public decimal PriceValue { get; set; }
[Display(Name = "Exclusive?")]
public bool Exclude { get; set; }
}
public enum ItemType
{
Catalog,
Category,
Group,
Item
}
public enum PriceType
{
Catalog,
Price_A,
Price_B,
Price_C
}
A sample result from .net API:
[
{
$id: "1",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 1,
CatalogID: 501981,
ItemRule: 0,
Items: "198",
PriceRule: 1,
PriceValue: 0.5,
Exclude: false
},
{
$id: "2",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 2,
CatalogID: 501981,
ItemRule: 2,
Items: "9899",
PriceRule: 2,
PriceValue: 10.45,
Exclude: false
}
]
So in this example, I need to get Catalog for results[0].ItemRule & Price A for results[0].PriceRule. How can I accomplish this in BreezeJS??
This is easy to do in ASP.NET Web API, because it is an out-of-box feature in the default JSON serializer (Json.NET).
To see strings instead of enum numbers in JSON, just add an instance of StringEnumConverter to JSON serializer settings during app init:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
UPDATE: Yep, you right, this is not help with Breeze.js. Ok, you can anyway do a little magic to make enums work like strings (while new version with fix is not released).
Create a custom ContextProvider which updates all integer enum values in metadata to strings. Here it is:
public class StringEnumEFContextProvider<T> : EFContextProvider<T>
where T : class, new()
{
protected override string BuildJsonMetadata()
{
XDocument xDoc;
if (Context is DbContext)
{
xDoc = GetCsdlFromDbContext(Context);
}
else
{
xDoc = GetCsdlFromObjectContext(Context);
}
var schemaNs = "http://schemas.microsoft.com/ado/2009/11/edm";
foreach (var enumType in xDoc.Descendants(XName.Get("EnumType", schemaNs)))
{
foreach (var member in enumType.Elements(XName.Get("Member", schemaNs)))
{
member.Attribute("Value").Value = member.Attribute("Name").Value;
}
}
return CsdlToJson(xDoc);
}
}
And use it instead of EFContextProvider in your Web API controllers:
private EFContextProvider<BreezeSampleContext> _contextProvider =
new StringEnumEFContextProvider<BreezeSampleContext>();
This works well for me with current Breeze.js version (1.1.3), although I haven't checked other scenarios, like validation...
UPDATE: To fix validation, change data type for enums in breeze.[min|debug].js, manually (DataType.fromEdmDataType function, dt = DataType.String; for enum) or replace default function during app init:
breeze.DataType.fromEdmDataType = function (typeName) {
var dt = null;
var parts = typeName.split(".");
if (parts.length > 1) {
var simpleName = parts[1];
if (simpleName === "image") {
// hack
dt = DataType.Byte;
} else if (parts.length == 2) {
dt = DataType.fromName(simpleName);
if (!dt) {
if (simpleName === "DateTimeOffset") {
dt = DataType.DateTime;
} else {
dt = DataType.Undefined;
}
}
} else {
// enum
dt = DataType.String; // THIS IS A FIX!
}
}
return dt;
};
Dirty, dirty hacks, I know... But that's the solution I found
There will be a new release out in the next few days where we "change" breeze's enum behavior ( i.e. break existing code with regards to enums). In the new release enums are serialized and queried by their .NET names instead of as integers. I will post back here when the new release is out.

Insertion of a new item in grid fails due to client template existence

I have a Kendo UI grid which I need to be editable. I also need a client template for one of the columns.
The part from the kendo ui grid that bugs me:
.Columns(columns =>
{
columns.Bound(p => p.Author).Filterable(false).Width(100);
columns.Bound(p => p.Name).Filterable(false).Title("Idea Name");
columns.Bound(p => p.Description).Filterable(false);
columns.Bound(p => p.BeginDate).Format("{0:d}");
columns.Bound(p => p.ReleaseDate).Format("{0:d}");
columns.Bound(p => p.NextAvailableStates).ClientTemplate
(
"#for(var i = 0; i < NextAvailableStates.length; i++) {" +
"# <li>" +
" #=NextAvailableStates[i].StepName # " +
"</li> #" +
"} #"
)
.Title("Actions").IncludeInMenu(false).Visible(true)
.Sortable(false).Filterable(false);
})
My controller:
public partial class IdeaController : Controller
{
private StateMachineHelper helper = new StateMachineHelper();
public ActionResult Index(int? state)
{ //must ad to Model the next available states
var model = new List<Idea>();
model.AddRange(helper.GetIdeasByState(helper.GetStateByID(state)));
foreach (var item in model)
{
item.NextAvailableStates = helper.GetNextStates(helper.GetStateMachineInstancesByOrderID(item.ID));
}
return View(model);
}
public ActionResult NextState()
{
return View();
}
public ActionResult Read([DataSourceRequest]DataSourceRequest request, DateTime start, DateTime end)
{
var ideas = helper.GetIdeasBetweenDates(start, end);
DataSourceResult result = ideas.ToDataSourceResult(request);
return Json(result);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([DataSourceRequest] DataSourceRequest request, Idea product)
{
if (product != null && ModelState.IsValid)
{
var helper = new StateMachineHelper();
var machine = new StateMachineInstance() {
ParentID = 0,
//1 -> machineID
CurrentStateID = helper.GetFirstStateOfMachine(1).ID,
MachineID =1,
ParentStateID =0,
//OrderType: idea/command
OrderType = "Idea"
};
helper.AddNewOrder(product, machine);
product.NextAvailableStates = helper.GetNextStates(machine);
}
return Json(new[] { product }.ToDataSourceResult(request, ModelState));
}
}
public Idea()
{
this.StateMachineInstances = new HashSet<StateMachineInstance>();
}
[ScaffoldColumn(false)]
[DataMember]
public int ID { get; set; }
[Required]
[DataMember]
public string Name { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
[Required]
public string Author { get; set; }
[DataMember]
public System.DateTime BeginDate { get; set; }
[DataMember]
public Nullable<System.DateTime> ReleaseDate { get; set; }
[DataMember]
public List<State> NextAvailableStates
{
get
{
return nextAvailableStates;
}
set
{
nextAvailableStates = value;
}
}
private List<State> nextAvailableStates = new List<State>();
public virtual ICollection<StateMachineInstance> StateMachineInstances { get; set; }
}
The grid renders just fine, with every column filled with the correct information. The problem appears when I need to add a new item to the grid. The popup window doesn't show up and I get Uncaught ReferenceError: NextAvailableStates is not defined. Which actually makes sense because the grid tries to draw before the new item gets returned.
My question: is there a way to achieve the insertion of a new item while using custom client templates?
You can try modifying your client template to check for the existence of NextAvailableStates before using them:
"# if (NextAvailableStates) {" +
" for(var i = 0; i < NextAvailableStates.length; i++) {" +
"# <li>" +
" #=NextAvailableStates[i].StepName # " +
"</li> #" +
" } " +
"} #"

RadGrid client side binding

I've got a question regarding the Telerik RadGrid control client side binding. I want to populate grid with the cities on the Client Side. I've got a object City, which has a property Country:
[DataContract]
[KnownType(typeof(Country))]
public class City
{
[DataMember]
public virtual string CityCode { get; set; }
[DataMember]
public virtual string CityName { get; set; }
[DataMember]}
public virtual Country Country { get; set;
}
}
[DataContract]
public class Country
{
[DataMember]
public virtual string CountryCode { get; set; }
[DataMember]
public virtual string Iso2Code { get; set; }
[DataMember]
public virtual string CountryName { get; set; }
[DataMember]
public virtual char RDC { get; set; }
}
I retrieve this data as a JSON object to the client side using the JQuery Ajax and WCF.
and then I bind it to the grid:
rgCity.set_dataSource(dataItem);
rgCity.dataBind();
Here are the Columns definition for the grid:
<Columns>
<telerik:GridBoundColumn HeaderText="City Code" DataField="CityCode" MaxLength="3"> </telerik:GridBoundColumn>
<telerik:GridBoundColumn HeaderText="City Name" DataField="CityName"></telerik:GridBoundColumn>
<telerik:GridBoundColumn HeaderText="Country Code" DataField="CountryCode" MaxLength="2"></telerik:GridBoundColumn>
</Columns>
The problem is I'm not getting the Country Code column populated with the data. I think the problem is in data binding, but I'm not sure if is it possible to bind a complex objects.
I think should be something like that:
<telerik:GridBoundColumn HeaderText="Country Code" DataField="**City.CountryCode**" MaxLength="2"></telerik:GridBoundColumn>
I appreciate any help solving that issue!
You can just overwrite the
Telerik.Web.UI.GridTableView.dataBind function by replacing
this snippet from the original minified telerik script:
var J = r[v].get_uniqueName();
var n = this.getCellByColumnUniqueName(H, J);
if (!n) {
continue;
}var D = r[v]._data.DataField;
if (typeof (D) == "undefined") {
D = J;
} var h = this._dataSource[u][D];
if (h == null) {
h = "";
with something, for example, like this:
var J = r[v].get_uniqueName();
var n = this.getCellByColumnUniqueName(H, J);
if (!n) {
continue;
}var D = r[v]._data.DataField;
if (typeof (D) == "undefined") {
D = J;
}
//change here to eval the dataField
var h = AvoidEvalDueToPerformance(this._dataSource[u], D);
if (h == null) {
h = "";
AvoidEvalDueToPerformance function is defined as follows:
function AvoidEvalDueToPerformance(object, propertyString) {
var k = propertyString.split(".");
for (var i = 0; i < k.length; i++)
if (object[k[i]]) object = object[k[i]];
else return object;
return object;
}
Hope this helps someone as this was the first result i stumbled upon searching for the answer of question "How to bind RadGrid to complex object clientside"
P.S. to overwrite a function you could write
Telerik.Web.UI.GridTableView.dataBind.prototype = function(){
//copy-paste the Telerik.Web.UI.GridTableView.dataBind.prototype contents
//from your favorite javascript debugger output
//(or grep output or w/e you prefer) here :)<br />
}
I don't think you can do complex databinding like that. Instead, I'd make a new property that returned the Country Code directly, then bind to that. Example:
[DataContract]
[KnownType(typeof(Country))]
public class City
{
...
[DataMember]}
public virtual CountryCode{ get Country.CountryCode; }
}
Declarative databinding for the grid then is what you had:
<Columns>
...
<telerik:GridBoundColumn HeaderText="Country Code" DataField="CountryCode" MaxLength="2"></telerik:GridBoundColumn>

Automatically display the age in my "CREATE FORM"

I have birthdate and age field in my CREATE FORM and i want to automatically calculate and DISPLAY the age once the birthdate is filled in.
So I use CodeFirst of EntityFramework and MVC 3 Razor View
So here's My Model:
namespace Payroll_System.Models
{
public class Employee
{
[DataType(DataType.Date)]
[DisplayName("Date of Birth:")]
[Required(ErrorMessage = "Birth Date is Required.")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0;dd/MM/yyyy}"
, NullDisplayText = "No Date of Birth is Selected.")]
public DateTime BirthDate { get; set; }
[Integer]
[Min(1, ErrorMessage = "Unless you are Benjamin Button.")]
public int Age
{
get
{
DateTime now = DateTime.Today;
int age = now.Year - BirthDate.Year;
if (BirthDate > now.AddYears(-age)) age--; ;
return age;
}
}
And after I created the model I create my controller with CRUD options using MVC Scaffolding. Then in the partialview of Create there is no textbox for age. So Please provide some code.
You can't enter values, and let alone validate, a read only property.
You may want to validate your data by implementing the IValidatableObject interface.
Model:
public class PersonFromSO : IValidatableObject
{
[DataType(DataType.Date)]
[DisplayName("Date of Birth:")]
[Required(ErrorMessage = "Birth Date is Required.")]
[DisplayFormat(ApplyFormatInEditMode = true,
DataFormatString = "{0;dd/MM/yyyy}",
NullDisplayText = "No Date of Birth is Selected.")]
public DateTime BirthDate { get; set; }
public int Age
{
get
{
DateTime now = DateTime.Today;
int age = now.Year - BirthDate.Year;
if (BirthDate > now.AddYears(-age)) age--; ;
return age;
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if (Age < 1)
yield return new ValidationResult("You were born in the future.",
new[] { "Age" });
}
}
View:
#model PersonFromSO
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary()
#Html.LabelFor(m => m.BirthDate)
#Html.TextBoxFor(m => m.BirthDate)
<time>#Model.Age</time>
<input type="submit" />
}
Here's also the controller I've used, if needed:
public class PersonFromSOController : Controller
{
public ActionResult Create()
{
return View(new PersonFromSO());
}
[HttpPost]
public ActionResult Create(PersonFromSO person)
{
return View(person);
}
}
I would note that that is a little convoluted, and you are probably better off validating BirthDate directly.
get
{
DateTime now = DateTime.Today;
int age = now.Year - BirthDate.Year;
if (BirthDate > now.AddYears(-age)) age--; ;
return age;
}
This is wrong if you also store the time it should be the code below
get
{
DateTime now = DateTime.Now;
int age = now.Year - BirthDate.Year;
if (BirthDate > now.AddYears(-age)) age--; ;
return age;
}

Resources