I am trying to fill in a ListBox with CustomAction and it's not going well.
I try to figure out the session.Database.Tables but have no idea how to start.
I've created a listbox like this
<Control Id="ListBox1" Type="ListBox" Sorted="no" Indirect="no" Property="LISTBOXVALUESONE" X="10" Y="50" Width="150" Height="180">
<ListBox Property="LISTBOXVALUESONE">
<ListItem Text="ARGHH!" Value="1"/>
</ListBox>
</Control>
But I cant see the property in my verbrose log or anything about an table so I guess I have to create an table in customAction and populate it?
I see my ARGHH! in the list so it should exsist but how do I access the values? And add new ones?
Found more examples and stuff in C++ but i would like to make the CustomAction in C#
EDIT
Database db = session.Database;
string sqlInsertTemp = db.Tables["ListBox"].SqlInsertString + " TEMPORARY";
View view = db.OpenView(sqlInsertTemp );
view.Execute( new Record( new object[] { "LISTBOXVALUESONE", 2, "2", "One" } ));
view.Close();
Thanks to Christopher I got it to work with adding an value.
db.Tables["ListBox"] should remain the same and name the type not the id as i taught
And on this line view.Execute( new Record( new object[] { "LISTBOXVALUESONE", 2, "2", "One" } ));
you put your Listbox Property and then the placement of the value "one" we insert
The two "2"s is what I figure the placement we want it on and I already have an test value on 1
my "ARGHH!" so I put the new on 2 and dont know the details but...
I got an Table Update error and, one dublicate value error if i put 2,1 or 1,2 in the customaction!
I wrote a blog article about 5 years ago that might help you:
How DTF is going to help me become a better .NET Developer
You want to make sure your built MSI has a ListBox table otherwise the SQL won't work when it tries to generate the temp rows dynamically at runtime. If the ListBox element doesn't do this for you, the EnsureTable element will.
The actual C# looks something like:
Database db = session.Database;
string sqlInsertTemp = db.Tables["ListBox"].SqlInsertString + " TEMPORARY";
View view = db.OpenView(sqlInsertTemp );
view.Execute( new Record( new object[] { "TESTPROP", 1, "1", "One" } ));
view.Close();
Note this is an old code example and doesn't properly take advantage of using statements and IDisposable.
Add one record to list box:
private void AddRecordToListBox(string listBoxPropertyName, int index, string text, string value)
{
View view = session.Database.OpenView("SELECT * FROM ListBox");
view.Execute();
Record record = session.Database.CreateRecord(4);
record.SetString(1, listBoxPropertyName);
record.SetInteger(2, index);
record.SetString(3, value);
record.SetString(4, text);
view.Modify(ViewModifyMode.InsertTemporary, record);
view.Close();
}
Fill ListBox:
private void FillListBox()
{
var dict = SomeDict();
int index = 1;
foreach (var element in dict)
{
AddRecordToListBox(ListBoxName, index, element.Key, element.Value);
index++;
}
}
Clear ListBox
private void ClearListBox(string listBoxPropertyName)
{
var command = String.Format("DELETE FROM ListBox WHERE ListBox.Property='{0}'", listBoxPropertyName);
View view = session.Database.OpenView(command);
view.Execute();
view.Close();
}
Related
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]);
}
});
}
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
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.
MVC 3 VB.NET razor. I have a view that has 4 dropdown boxes in it.. This is for setting up a staff member.. If that staff member is to work certain classes then that class will be set for each day. If he is not then the value needs to stay null. This is a edit view so it may have to be accessed multiple times and still keep the original selectlist values if none changed. The below is what I have right now that is working only on its face. The Old selected value is shown first. However this isnt being returned on the save... The only way that it will save correctly is if I select the value that was set in each box then click save. The next problem is that not all staff members will have classes to work on every one of the 4 days. So how do I set a value to null and keep it that way unless a class Is actually selected..
Dim _staff As confstaff = db.confstaffs.Single(Function(a) a.id = id)
ViewBag.role = _staff.Conf_Role.ToString
ViewBag.confRole = db.conf_roles.ToList
ViewData("tue_Class") = New SelectList(db.courses.ToList.Where(Function(r) r.course_day = "Tuesday").Select(Function(r) r.course_ref), New With {.value = _staff.tue_class})
ViewData("wed_Class") = New SelectList(db.courses.ToList.Where(Function(r) r.course_day = "Wednesday").Select(Function(r) r.course_ref), New With {.value = _staff.wed_class})
ViewData("thur_Class") = New SelectList(db.courses.ToList.Where(Function(r) r.course_day = "Thursday").Select(Function(r) r.course_ref), New With {.value = _staff.thur_class})
ViewData("fri_Class") = New SelectList(db.courses.ToList.Where(Function(r) r.course_day = "Friday").Select(Function(r) r.course_ref), New With {.value = _staff.fri_class})
Return View(_staff)
And the view is:
<label>Tuesday Class</label>
#Html.DropDownList("tue_class", "Select One")
<label class="small_spacing">Wednesday Class</label>
#Html.DropDownList("wed_class", "Select One")
<label class="small_spacing">Thursday Class</label>
#Html.DropDownList("thur_class", "Select One")
<label class="small_spacing">Friday Class</label>
#Html.DropDownList("fri_class", "Select One")
I already expect someone to point out that I should use a view model instead of viewbag but I dont see how a view model would be practical with there being over 100 different courses but I am open for ideas...
Any Ideas???????
if you look at your page source after the page is loaded you will see your top item set from your viewbag value has no value only text so when it is submitted it thinks you are submitting blank. I ran into this before.
What you need to do is to manually create each of your dropdown lists by iterating through the collection and setting the one that matches your viewbag item to selected, that way you can be sure a selected item has a selected value. I have some Razr code around here somewhere. Will update when I find it.
EDIT
<select name="Type1" id="Type1">
<option value=""></option>
#foreach (var name in ViewBag.BackOfficeTypes)
{
if (name == ViewBag.SelectedType1Value)
{
<option value="#name" selected="selected">#name</option>
}
else
{
<option value="#name">#name</option>
}
}
</select>
I have several of these on the same page building from different sets of items. Hope this helps.
EDIT 2
If you want to do it all from codebehind I am not a VB guy but here is a way to do it but you need to change your linq statement to manually create the list items instead of dumping from the toList Method.
var courses = db.getCourses();
IEnumerable<SelectListItem> selectList =
from c in courses
where c.course_day = "Tuesday"
select new SelectListItem
{
Selected = (c.CourseID == selectedCourseID),
Text = c.Name,
Value = c.CourseID.ToString()
};
If you can translate this into the VB equivalent it might solve your issue instead of building them in the Razor end.
All,
I've read through a lot of posts about Checkboxes and ASP.MVC but I'm not that much wiser.
My scenario:
I have a strongly typed View where I pass a collection of summary objects to the view for rendering in a for-each. This summary object contains label data based on a unique id. I also add a checkbox to the row so do so via:
<td>
<%= Html.CheckBox("markedItem", Model.MarkedItem, new { TrackedItemId = Model.Id })%>
</td>
When I perform a POST to get the submitted results my action method takes the strongly typed ViewModel back but the original summary object that I used to create the list is not populated.
Ok, this is annoying, but I can understand why so I'll live with it.
What I then do is to add a new property to my ViewModel called "MarkedItem" which is a string collection.
On postback this marked item is filled with the before and after states if the checkbox has changed but nothing to tell me which key they were for. Just to clarify, if I send this
TrackedItemId = A, Value = false
TrackedItemId = B, Value = true
TrackedItemId = C, Value = false
and set the page to this:
TrackedItemId = A, Value = true
TrackedItemId = B, Value = true
TrackedItemId = C, Value = false
I will get back this:
MarkedItem[0] = true
MarkedItem[1] = false
MarkedItem[2] = true
MarkedItem[3] = false
in other words [0] is the new value and [1] is the old value, [2] and [3] represent values that haven't changed.
My questions are:
Is this right - that I get before and after in this way? Is there any way to only send the latest values?
How can I get hold of the custom attribute (TrackedItemId) that I've added so that I can add meaning to the string array that is returned?
So far I like MVC but it not handling simple stuff like this is really confusing. I'm also a javascript noob so I really hope that isn't the answer as I'd like to return the data in my custom viewmodel.
Please make any explanations/advice simple :)
<p>
<label>
Select project members:</label>
<ul>
<% foreach (var user in this.Model.Users)
{ %>
<li>
<%= this.Html.CheckBox("Member" + user.UserId, this.Model.Project.IsUserInMembers(user.UserId)) %><label
for="Member<%= user.UserId %>" class="inline"><%= user.Name%></label></li>
<% } %></ul>
and in the controller:
// update project members
foreach (var key in collection.Keys)
{
if (key.ToString().StartsWith("Member"))
{
int userId = int.Parse(key.ToString().Replace("Member", ""));
if (collection[key.ToString()].Contains("true"))
this.ProjectRepository.AddMemberToProject(id, userId);
else
this.ProjectRepository.DeleteMemberFromProject(id, userId);
}
}
With thanks to Pino :)
ok, one hack I've come up with - I really hate that I have to do this but I don't see another way round it and I'm sure it will break at some point.
I've already implemented by own ModelBinder to get round some other issues (classes as properties for example) so have extended it to incorporate this code. We use Guid's for all our keys.
If there are any alternatives to the below then please let me know.
Html
<%= Html.CheckBox("markedItem" + Model.Id, false)%>
C#
(GuidLength is a const int = 36, Left and Right are our own string extensions)
//Correct checkbox values - pull all the values back from the context that might be from a checkbox. If we can parse a Guid then we assume
//its a checkbox value and attempt to match up the model. This assumes the model will be expecting a dictionary to receive the key and
//boolean value and deals with several sets of checkboxes in the same page
//TODO: Model Validation - I don't think validation will be fired by this. Need to reapply model validation after properties have been set?
Dictionary<string, Dictionary<Guid, bool>> checkBoxItems = new Dictionary<string, Dictionary<Guid, bool>>();
foreach (var item in bindingContext.ValueProvider.Where(k => k.Key.Length > GuidLength))
{
Regex guidRegEx = new Regex(#"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
if (guidRegEx.IsMatch(item.Key.Right(GuidLength)))
{
Guid entityKey = new Guid(item.Key.Right(GuidLength));
string modelKey = item.Key.Left(item.Key.Length - GuidLength);
Dictionary<Guid, bool> checkedValues = null;
if (!checkBoxItems.TryGetValue(modelKey, out checkedValues))
{
checkedValues = new Dictionary<Guid, bool>();
checkBoxItems.Add(modelKey, checkedValues);
}
//The assumption is that we will always get 1 or 2 values. 1 means the contents have not changed, 2 means the contents have changed
//and, so far, the first position has always contained the latest value
checkedValues.Add(entityKey, Convert.ToBoolean(((string[])item.Value.RawValue).First()));
}
}
foreach (var item in checkBoxItems)
{
PropertyInfo info = model.GetType().GetProperty(item.Key,
BindingFlags.IgnoreCase |
BindingFlags.Public |
BindingFlags.Instance);
info.SetValue(model, item.Value, null);
}