MVC 3 WebGrid with a dynamic source - asp.net-mvc

I have a dynamic list of data with a dynamic number of columns being created by a PIVOT function. Everything more or less works, but I wanted to apply some custom formatting to some of the columns. I figured out how to get a list of the columns by just taking the first row and casting it like so:
var columns = Model.Comparisons.Select(x => x).FirstOrDefault() as IDictionary<string, object>;
Next I decided to create my List by looping over the "columns", which works as long as I reference the dynamic fields in the "format:" clause by their dynamic field name directly for example:
foreach (var c in columns)
{
switch (c.Key)
{
case "Cost":
cols.Add(grid.Column(
columnName: c.Key,
header: c.Key,
format: (item) => Convert.ToDecimal(item.Cost).ToString("C")));
break;
default:
cols.Add(grid.Column(columnName: c.Key, header: c.Key, format: item => item[c.Key]));
break;
}
}
The "default" does not dynamically get the value of each record. I believe it has to do with the "item[c.Key]" vs item.Cost. The problem is I don't want to have to write different case for each field, primarily because I don't know them ahead of time as the data can change. There are about 6 fields that will always be present. I do know however the datatype, which is why I wanted to put a custom format on them.
EDIT
I managed to solve this by writing an extension method.
public static class DynamicDataHelper
{
public static WebGridColumn GetColumn(this HtmlHelper helper, string vendor)
{
return new WebGridColumn()
{
ColumnName = vendor,
Header = vendor,
Format = (item) => helper.ActionLink(
(string)Convert.ToDecimal(item[vendor]).ToString("C"),
"VendorSearch",
"Compare",
new { Vendor = vendor, mpn = item.MPN },
new { target = "_blank" })
};
}
}

I edited my post with the Html Helper that I wrote that will in effect build the custom WebGridColumn objects I was having problems with. The "vendor" is passed in from the View and is then resolved at runtime. It works great.

Related

How do you add an initial selection for Angular Material Table SelectionModel?

The Angular Material documentation gives a nice example for how to add selection to a table (Table Selection docs). They even provide a Stackblitz to try it out.
I found in the code for the SelectionModel constructor that the first argument is whether there can be multiple selections made (true) or not (false). The second argument is an array of initially selected values.
In the demo, they don't have any initially selected values, so the second argument in their constructor (line 36) is an empty array ([]).
I want to change it so that there is an initially selected value, so I changed line 36 to:
selection = new SelectionModel<PeriodicElement>(true, [{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}]);
This changes the checkbox in the header to an indeterminate state (as expected), but does not cause the row in the table to be selected. Am I setting the initial value incorrectly, or what am I missing here? How can I set an initially selected value?
Tricky one. You need to initialize the selection by extracting that particular PeriodicElement object from your dataSource input, and passing it to the constructor.
In this particular case, you could code
selection = new SelectionModel<PeriodicElement>(true, [this.dataSource.data[1]);
It's because of the way SelectionModel checks for active selections.
In your table markup you have
<mat-checkbox ... [checked]="selection.isSelected(row)"></mat-checkbox>
You expect this binding to mark the corresponding row as checked. But the method isSelected(row) won't recognize the object passed in here as being selected, because this is not the object your selection received in its constructor.
"row" points to an object from the actual MatTableDataSource input:
dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
But the selection initialization:
selection = new SelectionModel<PeriodicElement>(true, [{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}]);
happens with a new object you create on the fly. Your selection remembers THIS object as a selected one.
When angular evaluates the bindings in the markup, SelectionModel internally checks for object identity. It's going to look for the object that "row" points to in the internal set of selected objects.
Compare to lines 99-101 and 16 from the SelectionModel source code:
isSelected(value: T): boolean {
return this._selection.has(value);
}
and
private _selection = new Set<T>();
I was facing the same issue, I used dataSource to set the initial value manually in ngOnInit()
ngOnInit() {
this.dataSource.data.forEach(row => {
if (row.symbol == "H") this.selection.select(row);
});
}
If you do the following, it works too
selection = new SelectionModel<PeriodicElement>(true, [ELEMENT_DATA[1]])
To select all you can do
selection = new SelectionModel<PeriodicElement>(true, [...ELEMENT_DATA])
I hope the answer is helpful
Or more dynamically if you have a set of values and you want to filter them before:
selection = new SelectionModel<PeriodicElement>(true, [
...this.dataSource.data.filter(row => row.weight >= 4.0026)
]);
This gets more tricky if you have data loading asynchronously from an api. Here is how I did it:
Firstly I have implemented the DataSource from "#angular/cdk/table". I also have an RxJS Subject that fires whenever data is loaded (first time or when user changes page in the pagination section)
export abstract class BaseTableDataSource<T> implements DataSource<T>{
private dataSubject = new BehaviorSubject<T[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
private totalRecordsSubject = new BehaviorSubject<number>(null);
public loading$ = this.loadingSubject.asObservable();
public dataLoaded$ = this.dataSubject.asObservable();
public totalRecords$ = this.totalRecordsSubject.asObservable().pipe(filter(v => v != null));
constructor(){}
connect(collectionViewer: CollectionViewer): Observable<T[]>{
return this.dataSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.dataSubject.complete();
this.loadingSubject.complete();
this.totalRecordsSubject.complete();
}
abstract fetchData(pageIndex, pageSize, ...params:any[]) : Observable<TableData<T>>;
abstract columnMetadata(): {[colName: string]: ColMetadataDescriptor };
loadData(pageIndex, pageSize, params?:any[]): void{
this.loadingSubject.next(true);
this.fetchData(pageIndex, pageSize, params).pipe(
finalize(() => this.loadingSubject.next(false))
)
.subscribe(data => {
this.totalRecordsSubject.next(data.totalNumberOfRecords);
this.dataSubject.next(data.records)
});
}
}
Now when I want to pre-select a row, I can write a function like this in my component which hosts a table that uses an implementation of the above mentioned data source
selectRow(rowSelectionFn: (key: string) => boolean){
this.dataSource.dataLoaded$.pipe(takeUntil(this.destroyed$))
.subscribe(data => {
const foundRecord = data.filter(rec => rowSelectionFn(rec));
if(foundRecord && foundRecord.length >= 0){
this.selection.toggle(foundRecord[0]);
}
});
}

MVC drop down list is not picking up selected item?

My model contains an array of zip code items (IEnumerable<SelectListItem>).
It also contains an array of selected zip codes (string[]).
In my HTML page, I want to render each selected zip code as a drop down with all the zip code options. My first attempt did not work:
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes", Model.ZipCodeOptions )
}
I realized that although that would produce drop downs with the right "name" attribute, it wouldn't know which element of ZipCodes holds the value for that particular box, and might just default to the first one.
My second attempt is what really surprised me. I explicitly set the proper SelectListItem's Selected property to true, and it still rendered a control with nothing selected:
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes", Model.ZipCodeOptions.Select( x => (x.Value == zip) ? new SelectListItem() { Value = x.Value, Text = x.Text, Selected = true } : x ) )
}
There, it's returning a new IEnumerable<SelectListitem> that contains all the original items, unless it's the selected item, in which case that element is a new SelectListItem with it's Selected property set to true. That property is not honored at all in the final output.
My last attempt was to try to use an explicit index on the string element I wanted to use as the value:
#{int zipCodeIndex = 0;}
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes[" + (zipCodeIndex++) + "]", Model.ZipCodeOptions )
}
That doesn't work either, and probably because the name is no longer "ZipCodes", but "ZipCodes[x]". I also received some kind of read-only-collection error at first and had to change the type of the ZipCodes property from string[] to List<string>.
In a forth attempt, I tried the following:
#for (int zipCodeIndex = 0; zipCodeIndex < Model.ZipCodes.Count; zipCodeIndex++)
{
var zip = Model.ZipCodes[zipCodeIndex];
Html.DropDownListFor( x => x.ZipCodes[zipCodeIndex], Model.ZipCodeOptions )
}
That produces controls with id like "ZipCodes_1_" and names like "ZipCodes[1]", but does not select the right values. If I explicitly set the Selected property of the right item, then this works:
#for (int zipCodeIndex = 0; zipCodeIndex < Model.ZipCodes.Count; zipCodeIndex++)
{
var zip = Model.ZipCodes[zipCodeIndex];
Html.DropDownListFor( x => x.ZipCodes[zipCodeIndex], Model.ZipCodeOptions.Select( x => (x.Value == zip) ? new SelectListItem() { Value = x.Value, Text = x.Text, Selected = true } : x ) )
}
However, the problem with that approach is that if I add a new drop downs in JavaScript and give them all the name "ZipCodes", then those completely override all the explicitly indexed ones, which never make it to the server. It doesn't seem to like mixing the plain "ZipCodes" name with explicit array elements "ZipCodes[1]", even though they map to the same variable when either is used exclusively.
In the U.I., user's can click a button to add a new drop down and pick another zip code. They're all named ZipCodes, so they all get posted to the ZipCodes array. When rendering the fields in the loop above, I expect it to read the value of the property at the given index, but that doesn't work. I've even tried remapping the SelectListItems so that the proper option's "Selected" property is true, but it still renders the control with nothing selected. What is going wrong?
The reason you first 2 snippets do not work is that ZipCodes is a property in your model, and its the value of your property which determines what is selected (not setting the selected value in the SelectList constructor which is ignored). Since the value of ZipCodes is an array of values, not a single value that matches one of the option values, a match is not found and therefore the first option is selected (because something has to be). Note that internally, the helper method generates a new IEnumerable<SelectListItem> based on the one you provided, and sets the selected attribute based on the model value.
The reason you 3rd and 4th snippets do not work, is due to a known limitation of using the DropDownListFor() method, and to make it work, you need to use an EditorTemplate and pass the SelectList to the template using AdditionalViewData, or construct a new SelectList in each iteration of the loop (as per your last attempt). Note that all it needs to be is
for(int i = 0; i < Model.ZipCodes.Length; i++)
{
#Html.DropDownListFor(m => m.ZipCodes[i],
new SelectList(Model.ZipCodeOptions, "Value", "Text", Model.ZipCodes[i]))
}
If you want to use just a common name (without indexers) for each <select> element using the DropDownList() method, then it needs to be a name which does not match a model property, for example
foreach(var item in Model.ZipCodes)
{
#Html.DropDownList("SelectedZipCodes",
new SelectList(Model.ZipCodeOptions, "Value", "Text", item))
}
and then add an additional parameter string[] SelectedZipCodes in you POST method to bind the values.
Alternatively, use the for loop and DropDownListFor() method as above, but include a hidden input for the indexer which allows non-zero based, non consecutive collection items to be submitted to the controller and modify you script to add new items using the technique shown in this answer
Note an example of using the EditorTemplate with AdditionalViewData is shown in this answer

Static list of data for dropdown list MVC

I want to have a static list of data in a model that can be used in a viewmodel and dropdown on a view. I want to be able to use it in this way in my controller:
MaintenanceTypeList = new SelectList(g, "MaintenanceTypeID", "MaintenanceTypeName"),
and access it in my view like this:
#Html.LabelFor(model => model.MaintenanceTypeID)
#Html.DropDownListFor(x => x.MaintenanceTypeID, Model.MaintenanceTypeList, "-- Select --", new { style = "width: 150px;" })
#Html.ValidationMessageFor(x => x.MaintenanceTypeID)
I am currently using a repository pattern for data in the database, but don't want to put this data in the database because it will never change. I still want it in a model though. Basically, my dropdown list should offer the following:
Value Text
-------------------------------------
Calibration Calibration
Prevent Preventative Maintenance
CalibrationPrevent PM and Calibration
Any help or examples of static lists using models/oop is appreciated
You can use a list initializer:
public static SomeHelperClass{
public static List<SelectListItem> MaintenanceTypeList {
get {
return new List<SelectListItem>
{ new SelectListItem{Value = "Calibration", Text = "Calibration"}
,new SelectListItem{ Value = "Prevent", Text = "Preventative Maintenance" }
,etc.
};
}
}
}
Hopefully I didn't miss a curly brace somewhere. You can google "C# list initializer" for more examples. I don't remember off top of my head what the actual collection to is for a SelectListCollection is, but I know there is a overload of DropDownList that accepts List as I often just have a collection of keyvaluepairs or something else, and then in my view I convert it to SelectListItems: someList.Select(i => new SelectListItem { Value = i.Key, Text = i.Value })
Note that another option is to place your values in an enum. You can then use a Description attribute on each enum value:
enum MaintenanceType {
[Description("Calibration")]
Calibration = 1,
[Description("Preventative Maintenance")]
Prevent = 2
}
Then you can do things like
Enum.GetValues(typeof(MaintenanceType )).Select(m=>new SelectListItem{ Value = m, Text = m.GetDescription()} ).ToList()
That last line was a little off the top of the head, so hopefully I didn't make a mistake. I feel like an enum is more well structured for what you're trying to do.

Advanced ASP.NET WebGrid - Dynamic Columns and Rows

I'm trying to create a WebGrid which has to be very dynamic. The columns are defined in a list, which I've done like so:
#{
List<WebGridColumn> columns = new List<WebGridColumn>();
foreach (var column in Model.Columns)
{
columns.Add(new WebGridColumn() { ColumnName = column.Name, Header = column.Name });
}
}
#grid.GetHtml(
columns: columns)
All well and good, but the problem I have is with the rows. I'll try and explain...
For this question let's say we have two columns for Name and Address.
I have a collection of row objects, lets say SearchResult objects. A SearchResult contains a Dictionary of any number of attributes, such as Name, Address, Phone, Height, Bra Size, or anything (think of the EAV pattern). I need to access the attributes based on Column Name.
I figured I could do this using format, but I can't seem to figure it out. I want something like this:
columns.Add(new WebGridColumn() { ColumnName = column.Name, Header =
column.Header, Format = #<text>#item.Attributes[column.Name]</text> });
This sort of works but despite creating the format for the separate columns, the rows get populated with only the last column's format. i.e.:
Name Address
1 Main Street 1 Main Street
45 Paradise Av 45 Paradise Av
etc
I think it should work if you leave out the "ColumnName" (superfluous anyway), and also make the dynamic expression a bit more explicit:
columns.Add(
new WebGridColumn() {
Header = column.Header,
Format = (item) => #Html.Raw("<text>" + #item.Attributes[column.Name] + "</text>")
}
);
This issue is related to reference variables. You need to have the Format property in terms of the other properties of the WebGridColumn. This is how I would do it:
#{
List<WebGridColumn> columns = new List<WebGridColumn>();
foreach (var column in Model.Columns)
{
var col = new WebGridColumn();
col.Header = column.Name;
col.Format = (item) => #Html.Raw("<text>" + #item.Attributes[col.Header] + "</text>");
columns.Add(col);
}
}

How do I get Choice Values from a Document library's Choice column in code

I am fairly new to SharePoint development and as you may all know that it is very basic for one to know how to access fields in a choice column...
My problem:
I want to access the values of the Check Boxes from a Choice Column.
For Example:
I have a document library called Libe, this document library has a custom column with type Choice and has 4 checkboxes with the values:
Category 1
Category 2
Category 3
Category 4
How do I get the values like literally the text values of what is in the Check Box List: "Category 1", "Category 2" ... "Category 4".
Any ideas?
I can access the column fine and get the selected values, I just do not know how to get the values the user can choose from.
Answer
SPFieldMultiChoice Fld = (SPFieldMultiChoice)list.Fields["Column"];
List<string> fieldList = new List<string>();
foreach (string str in Fld.Choices)
{
fieldList.Add(str);
}
Above is the answer, I can't answer my own question until I have a 100 rep.
using (SPSite site = new SPSite("http://servername/"))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["ListName"];
string values = list["yourColumn"] as string;
string[] choices = null;
if (values != null)
{
choices = values.Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries);
}
}
}
You can try this code for getting choice field value from document library.

Resources