I'm trying to use a WebGrid to display data in my model and am having a huge number of issues. My model contains among other things this:
public IEnumerable<Auction> Auctions { get; set; }
What I have done is:
#{
var grid = new WebGrid(Model.Auctions, rowsPerPage: Model.PagingInfo.ItemsPerPage, defaultSort: "Title", canPage: true, canSort: true)
{SortDirection = SortDirection.Ascending};
grid.Pager(WebGridPagerModes.NextPrevious);
}
I want to display some text in the first column depending on the type of the auction in the current row, so I have written a method in the model:
public string GetAuctionType(Auction auction)
{
var type = string.Empty;
if (auction is LubAuction)
{
type = "Lowest unique wins";
}
else if (auction is EsfAuction)
{
type = "Highest wins";
}
return type;
}
Now, my view also contains:
#grid.GetHtml(
columns: grid.Columns(
grid.Column("OwnerReference", header: "Owner reference")
)
);
Question is how do I add a grid.Columns line in the above to display the text in GetAuctionType?
Also, the other issue is that there is no pager appearing and sorting does not work.
I would appreciate all help.
Thanks,
Sachin
I would move GetAuctionType logic to a Partial Class so you can access it like a normal property on each object in your collection. You may also want to take a look at question ASP.NET MVC3 WebGrid format: parameter that is covering usage of WebGrid's column format syntax.
Regarding your other issues, do you see any errors in javascript console ?
Related
I am trying to set a value for DataGridViewRow.Tag when data binding but I don't know how to do it?
I tried with DataRow row = table.NewRow(); But row doesn't have tag.
How to set a value for DataGridViewRow.Tag when binding DataGridView to table (for example)? Or it isn't possible?
Edit1:
Here is the code i am using:
var table = new DataTable();
table.Columns.Add("Title", typeof(string));
table.Columns.Add("URL", typeof(string));
table.Columns.Add("Read Later", typeof(bool));
foreach (XElement node in nodes)
{
Helper.CheckNode(node);
var id = node.Attribute("id").Value;
var url = node.Element("path").Value;
var comment = node.Element("title").Value;
var readlater = node.Attribute("readLater")?.Value.ToString() == "1";
var row = table.NewRow();
row.ItemArray = new object[] { url, comment, readlater };
table.Rows.Add(row);//Edit2
}
dataGridView1.DataSource = table;
I am trying to set a tag for the row to use it in CellClick event:
var cRow = dataGridView1.Rows[e.RowIndex];
var id = cRow.Tag.ToString();
Separate the data from how it is displayed
When using a DataGridView, it is seldom a good idea to access the cells and the columns directly. It is way more easier to use DataGridView.DataSource.
In modern programming there is a tendency to separate your data (= model) from the way your data is displayed (= view). To glue these two items together an adapter class is needed, which is usually called the viewmodel. Abbreviated these three items are called MVVM.
Use DataGridView.DataSource to display the data
Apparently, if the operator clicks a cell, you want to read the value of the tag of the row of the cell, to get some extra information, some Id.
This displayed row is the display of some data. Apparently part of the functionality of this data is access to this Id. You should not put this information in the view, you should put it in the model.
class MyWebPage // TODO: invent proper identifier
{
public int Id {get; set;}
public string Title {get; set;}
public string Url {get; set;}
public bool ReadLater {get; set;}
... // other properties
}
Apparently you have a method to fetch the data that you want to display from a sequence of nodes. Separate fetching this data (=model) from displaying it (= view):
IEnumerable<MyWebPage> FetchWebPages(...)
{
...
foreach (XElement node in nodes)
{
Helper.CheckNode(node);
bool readLater = this.CreateReadLater(node);
yield return new MyWebPage
{
Id = node.Attribute("id").Value,
Url = node.Element("path").Value,
Title = node.Element("title").Value,
ReadLater = this.CreateReadLater(node),
};
}
}
I don't know what is in node "ReadLater", apparently you know how to convert it to a Boolean.
bool CreateReadLater(XElement node)
{
// TODO: implement, if null return true; if not null ...
// out of scope of this question
}
For every property that you want to display you create a DataGridViewColumn. Property DataPropertyName defines which property should be shown in the column. Use DefaultCellStyle if a standard ToString is not enough to display the value properly, for instance, to define the number of digits after the decimal point, or to color negative values red.
You can do this using the visual studio designer, or you can do this in the constructor:
public MyForm
{
InitializeComponents();
this.dataGridViewColumnTitle.DataPropertyName = nameof(MyWebPage.Title);
this.dataGridViewColumnUrl.DataPropertyName = nameof(MyWebPage.Url);
...
}
You don't want to display the Id, so there is no column for this.
Now to display the data, all you have to do is assign the list to the datasource:
this.dataGrieViewWebPages.DataSource = this.FetchWebPages().ToList();
This is display only. If the operator can change the displayed values, and you want to access the changed values, you should put the items in an object that implements interface IBindingList, for instance, using class (surprise!) BindingList<T>:
private BindingList<MyWebPage> DisplayedWebPages
{
get => (BindingList<MyWebPage>)this.dataGrieViewWebPages.DataSource;
set => this.dataGrieViewWebPages.DataSource = value;
}
Initialization:
private void DisplayWebPages()
{
this.DisplayedWebPages = new BindingList<MyWebPage>(this.FetchWebPages.ToList());
}
And presto! All webpages are displayed. Every change that the operator makes: add / remove / edit rows are automatically updated in the DisplayedWebPages.
If you want to access the currently selected WebPages:
private MyWebPage CurrentWebPage =>(MyWebPage)this.dataGrieViewWebPages.CurrentRow?.DataBoundItem;
private IEnumerable<MyWebPage> SelectedWebPages =>
this.dataGrieViewWebPages.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<MyWebPage>();
Now apparently whenever the operator clicks a cell, you want to do something with the Id of the WebPage that is displayed in the Row of the cell.
View: Displayed Cell and Row
ViewModel: React when operator clicks a cell
Model Action that must be done
React on Cell Click: get the Id
We've handled the View above. ViewModel is the event handler:
void OnDatGridViewCellClicked(object sender, DataGridViewCellEventArgs e)
{
// use the eventArgs to fetch the row, and thus the WebPage:
MyWebPage webPage = (MyWebPage)this.dataGridViewWebPages.Rows[e.RowIndow].DataBoundItem;
this.ProcessWebPage(webPage);
}
ProcessWebPage is typically a method in your Model class:
public void ProcessWebPage(MyWebPage webPage)
{
// Do what you need to do if the operator clicks the cell, for example:
int webPageId = webPage.Id;
...
}
Conclusion: advantages of separating model from view
By the way, did you see that all ViewModel methods are one-liners? Only your Model methods FetchWebPages and ProcessWebPage contain several lines.
Because you separated the View from the Model, changes to your Model or your View will be fairly simple:
If you want to store your data in Json format, or in a database instead of in an XML, your View won't change
If you don't want to react on cell click, but on Button OK click, then your Model won't change. Your model also doesn't have to change if you decide to show more or less columns
Because you separated your Model from your View, the Model can be unit tested without a form. You can also test the View with a Model filled with only test values.
I need to save the Contact Number in a registration form wherein i'm having 2 TextBox, one for Country Code and another for Number. Now i need to combine both and bind into a single property which i have in my class. How can i do that?
View:
<div class="form-group">
#Html.LabelFor(m => m.Phone, "Contact Number:")
#Html.TextBoxFor(m => m.xxxx,new { #class = "form-control", #id = "txtContactCode", required="required", type ="number" })-
#Html.TextBoxFor(m => m.Phone,new { #class = "form-control", #id = "txtContactNumber", required="required", type ="number" })
</div>
Property,
public string Phone { get; set; }
Now what should i bind the property in the code of the phone number field to concatenate as one? Is there any way or i should declare another property named Code and then proceed?
In general, you should not try to split/join things. You're just introducing a potential point of failure into your application. If you only care about storing a single combined Phone, then let the user enter their phone number directly in a field for Phone. If you care about ensuring that every phone number has a country code, you can use a phone number validation library like this port of Google's libphonenumber library, to parse the user entered phone numbers and standardize how they're stored in the database.
The problem with trying to combine two fields into one is that you then have to split that one field back into two. Especially with something like a country code that can be variable length, that's going to be really difficult to do reliably. However, if you insist on going down this path, I'd recommend using a view model like:
public string Phone
{
get { return String.Format("{0} {1}", CountryCode, Number); }
set
{
CountryCode = null;
Number = null;
if (value != null)
{
var parts = value.Split(new[] { ' ' }, 2);
if (parts.Length == 2)
{
CountryCode = parts[0];
Number = parts[1];
}
}
}
}
public string CountryCode { get; set; }
public string Number { get; set; }
Then, you would bind to CountryCode and Number, respectively, in your view. The Phone custom getter and setter will take care of translating back and forth between combined and constituent parts.
You can easily do this by using the BindModel() method which is available in the IModelBinder interface.
IModelBinder.BindModel: Binds the model to a value by using the specified controller context and binding context.
Also see this explanation with real-time example.
Note: In the above example, first_name, middle_name and last_name were bound to the full name property. You can bind your required two properties to the one property in the same way.
I'm developing a project with ASP MVC 5, Kendo UI, and some layers. The main idea is that after I chose a value from a dropdown column in a Kendo Grid for example:
columns.Bound(b => b.Country).ClientTemplate("#=Country.Name#");
It should update a second and third column based on the previous selection:
columns.Bound(b => b.Category).ClientTemplate("#=Category.Name#");
columns.Bound(b => b.Client).ClientTemplate("#=Client.Name#");
I haven't been able to find any example or idea in the Telerik documentation or forums:
Grid/Events
Grid / Editing custom editor
Refresh/Replace DataSource of Foreignkey DropDown List
I read this example too with a normal dropdown:
Kendo UI DropDownList on a change to trigger event
Has anyone experienced something like this? My current idea is to create N number of Editor Templates:
#model Kendo.Mvc.Examples.Models.CategoryViewModel
#(Html.Kendo().DropDownListFor(m => m)
.DataValueField("CategoryID")
.DataTextField("CategoryName")
.BindTo((System.Collections.IEnumerable)ViewData["categories"])
)
With each of the possible Countries, however, it could be really inefficient and I still don't know how to trigger the on Change event.
After a long research, I was able to find a solution in this example:
Grid InLine and PopUp Editing using Cascading DropDownLists
However, it wasn't just copy and paste, I still don't know why this example is not available in the official FAQ Telerik page, but I'd like to provide the key point in order to do it:
1) You must select the InLine or PopUp edit mode:
.Editable(editable => editable.Mode(GridEditMode.InLine))
Why? Because when you are going to edit or add the line:
The cascade Drop downs are fully linked to the ID, for example:
2) Next, your new column in the grid is going to look this one:
columns.Bound(b => b.CategoryID).ClientTemplate("#=Category.Name#");
Be careful, before I we used the class as Category instead of the CategoryID, the ID is the crucial point.
3) You need to change the previous approach from adding the hint to the class to the ID of the it, for example:
Non-cascade approach:
[UIHint("ClientStatus")]
public Statuses Status { get; set; }
public int StatusID { get; set; }
Cascade approach:
public Statuses Status { get; set; }
[UIHint("ClientStatus")]
public int StatusID { get; set; }
3) The editor template from the cascade approaches should look like this:
Basic one:
#model int
#(Html.Kendo().DropDownListFor(m => m)
.AutoBind(false)
.DataValueField("CategoriesID")
.DataTextField("Name")
.DataSource(dataSource =>
{
dataSource.Read(read => read.Action("PopulateCategories", "FullView"))
.ServerFiltering(true);
})
)
#Html.ValidationMessageFor(m => m)
Cascade ones:
#model int
#(Html.Kendo().DropDownListFor(m => m)
.AutoBind(false)
.DataValueField("ID")
.DataTextField("Name")
.DataSource(dataSource =>
{
dataSource.Read(read => read.Action("PopulateStatuses", "FullView").Data("filterCategories"))
.ServerFiltering(true);
})
.CascadeFrom("CategoriesID")
)
#Html.ValidationMessageFor(m => m)
4) The cascade is calling a JavaScript function that looks like this:
function filterCategories() {
return {
categoriesID: $("#CategoriesID").data("kendoDropDownList").value()
};
}
Where CategoriesID is the ID of the first drop down, which is generated when we edit or add a new line.
4) Finally, we need to share a JSON as a result:
First drop down:
public JsonResult PopulateCategories()
{
return Json(CategoriesData.GetCategories(), JsonRequestBehavior.AllowGet);
}
Second and further drop downs:
public JsonResult PopulateStatuses(int categoryID)
{
return Json(StatusesData.GetStatuses(categoryID), JsonRequestBehavior.AllowGet);
}
I used the following approach for styling individual cells of grid with a template. I think you can apply this logic in order to change the values in DropDownList.
UI for Javascript:
{
field: "EmployeeName", type: "string", width: "55px", title: "Employee Name",
template: "#= GetEditTemplate(data) #"
}
UI for MVC:
...
columns.Bound(t => t.EmployeeName).Title("Status Name").Template(#<text></text>)
.ClientTemplate("#= GetEditTemplate(data)#").Width("55px");
...
Example: Here data parameter is passed to the Javascript method and used in condition:
<script>
//Change the color of the cell value according to the given condition
function GetEditTemplate(data) {
var html;
if (data.StatusID == 1) {
html = kendo.format(
"<span class='text-success'>" +
data.EmployeeName
+ "</span>"
);
}
else {
html = kendo.format(
"<span class='text-danger'>Cancel</span>"
);
}
return html;
}
</script>
For more information you might have a look at How Do I Have Conditional Logic in a Column Client Template?. Hope this helps...
I can't figure this one out. I'm creating a new ElasticSearch index using the ElasticProperty attributes/decorators. Here's how I create the index:
client = ClientFactory(indexName);
if (!client.IndexExists(indexName).Exists)
{
var set = new IndexSettings() { NumberOfShards = 1 };
var an = new CustomAnalyzer() { Tokenizer = "standard" };
an.Filter = new List<string>();
an.Filter.Add("standard");
an.Filter.Add("lowercase");
an.Filter.Add("stop");
an.Filter.Add("asciifolding");
set.Analysis.Analyzers.Add("nospecialchars", an);
client.CreateIndex(c => c
.Index(indexName)
.InitializeUsing(set)
.AddMapping<ItemSearchable>(m => m.MapFromAttributes())
);
}
And all of my fields are being created properly from the attributes specified on the class, except these two. They are C# enumerations, which is maybe part of the problem. I'm trying to save them in the index as numeric fields...
[Required, ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, IncludeInAll = false)]
public Enums.PlatformType PlatformType { get; set; }
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, OmitNorms = true, IncludeInAll = false)]
public Enums.ItemType ItemType { get; set; }
When I set up the index and check via Kibana, I don't see PlatformType or ItemType at all in the list of fields in the empty index.
When I insert a record, I can see the values in the source (JSON) as numbers (as expected), but the "Fields" are not there.
So I'm thinking it's either because they're C# enum type, or because I'm trying to store it as a number. But I'm stumped on why Elasticsearch is skipping these fields. Many thanks for any ideas you may have.
UPDATE 1 ... The search still works (even without those fields being shown in the Fields section). I'm thinking it might just be a Kibana problem. In the Table view, it shows my two fields like this...
and hovering over those triangle exclamation marks says "No cache mapping for this field. Refresh your mapping from the Settings > Indices page". But of course I can't find such a page within Kibana.
So I might be fine behind the scenes and this is a non-issue. Does anyone else have any insight on what might fix this, make it clearer, or whether this is known behaviour and I should just move on? Thanks.
I'm using MVC's WebGrid throughout my application and it's been working great. I'm setting the columns for the grid in my controller code and then passing that to my view. I add a new column like this where columns is a List<MvcWebGridColumn>:
columns.Add(new WebGridColumn { ColumnName = "Path", Header = "Full Path", Filter = ... });
And then in my view, I set the grid columns from my view model like this:
#grid.GetHtml(
footerStyle: "hide",
mode: WebGridPagerModes.All,
htmlAttributes: new { id = #Model.Id },
columns: #Model.Columns)
Up until this point, setting the columns in the code has worked great as the columns have been known, first-level properties of my models. Now things are more complicated and I don't know how to handle it with the WebGrid. Here is an example model:
public class Requirement
{
public string Id { get; set; }
public string Path { get; set; }
public Dictionary<string, string> Fields { get; set; }
}
I want to be able to set a WebGrid's column in the code using the value from the Field dictionary property's key. I'm trying this, but it's not working:
columns.Add(new WebGridColumn { ColumnName = String.Format("Fields[\"{0}\"]", key), Header = label });
I tried also putting a method GetFieldValue(string key) method on my Requirement model, but it doesn't like that either. It seems like it can only handle exactly matching an object's property identifier.
I'm not sure if this is possible using WebGrid out of the box or if I have to extend the functionality. Thanks in advance.
I believe I just came up with a way to handle this that doesn't seem too hacky.
To restate the problem, I believe the MVC WebGrid uses reflection to set the ColumnName property so the string you enter for that needs to be directly accessible via the object's API eg. "Id", "Field.Address.Name", etc. The problem in my case is that the value I need for my column's row comes from a Dictionary and it doesn't seem like you can pass a parameter (key, index, etc) into the ColumnName to get the value I need.
My solution is to avoid using the ColumnName property of the WebGrid entirely when resolving the value to be displayed. I simply enter a value for the grid's model that I know exists for the ColumnName, in my case "Id". This is just used so the WebGrid will render. Then, since the Format of the WebGridColumn uses a dynamic function, I can define my column value using whatever I want:
columns.Add(new WebGridColumn
{
ColumnName = "Id",
Header = field.Label,
Format = x => new HtmlString(x.Value.GetEntityFieldValue(field.Field))
});
I soon realized that this will mess up sorting. MVC WebGrid's black-box sort simply uses the ColumnName in the sort query so every column for me now has "?sort=Id". If you're not sorting, then you're OK. If you are, you'll have to implement a custom sorting solution like I'm currently doing.