ASP.Net MVC Submit Post Data for Survey - asp.net-mvc

I'm having a hard time figuring out how to collect the data from a FormCollection when submitted for my form which collects the answers for a survey. Specifically my problem is with questions that have multiple choice options(radio buttons) and an other textbox field if the options did not apply.
My survey has the following structure:
QUESTION : [ QuestionId, Text, QuestionType, OrderIndex]
MULTIPLE_CHOICE_OPTIONS: [ MC_OptionId, QuestionId, OrderIndex, MC_Text]
ANSWER: [AnswerId,QuestionId, MC_OptionId (can be null), UserTextAnswer]
QUESTION_TYPES are: [Multiple_Choice, Multiple_Choice_wOtherOption, FreeText or Checkbox]
My view is rendering the form as follows(pseudo code to simplify):
//Html.BeginForm
foreach( Question q in Model.Questions)
{
q.Text //display question text in html
if (q.QuestionType == Multiple_Choice)
{
foreach( MultipleChoice_Option mc in Model.MULTIPLE_CHOICE_OPTIONS(opt => opt.QuestionId == q.QuestionId)
{
<radio name=q.QuestionId value=mc.MC_OptionId />
// All OK, can use the FormCollectionKey to get the
// QuestionId and its value to get the selected MCOptionId
}
}
else if (q.QuestionType == Multiple_Choice_wOtherOption)
{
foreach( MultipleChoice_Option mc in Model.MULTIPLE_CHOICE_OPTIONS(opt => opt.QuestionId == q.QuestionId)
{
<radio name=q.QuestionId value=mc.MC_OptionId />
}
<textbox name=q.QuestionId />
// ****Problem - I can get the QuestionId from the FormCollection Key, but
// I don't know if the value is from the user entered
// textbox or from a MCOptionId***
}
}
<button type="submit">Submit Survey</button>
// Html.EndForm
I was doing it this way so back in the controller action that handles the post back I could read over the FormCollection by key to get the questionId, and the value for each index to get the MCOptionID.
But in the case of the question with radio buttons and a textbox all with the same name key how would I determine if the form data is from the radio button or the text box.
I can see the way I'm doing this breaks because their could be the case where a question (id=1) has a MCOption w/ Id=5 so the radio button has value of 5 and the user enters 5 in the Other textbox. When the form submits I see the formcollection[key="1"] has the value 5 and I can't tell if that is from usertext or the radioButton value referencing a MCOptionId.
Is there a better way to approach this problem, either the db structure, view rendering code or the way the form controls are named? Maybe form collection is not the way to go but I was stumped how to post back and make the model binding work .
Thanks for any help, been going around in circles for something that seems pretty simple.

Consider this small refactoring...
//you're always rendering the radios, it seems?
RenderPartial("MultipleChoice", Model.MULTIPLE_CHOICE_OPTIONS.Where(x =>
x.QuestionId == q.QuestionId));
if (q.QuestionType == Multiple_Choice_wOtherOption)
{
<textbox name="Other|" + q.QuestionId />
}
and within that strongly typed partial view:
//Model is IEnumerable<MultipleChoice_Option >
foreach (MultipleChoice_Option mc in Model )
{
<radio name=mc.Question.QuestionId value=mc.MC_OptionId />
}
It seems your question was around the textbox name; being tied to the question by ID. In your controller, you'll have to explicitly know when to look for any value in the textbox.
string userAnswer = Request.Form["OtherEntry|" + someQuestionID].ToString();

Related

MVC 5 Add 'data-' attribute with Custom Data Annotation

I have an input text field, and I want to be able to restore the initial value after the user edits the text.
So, I would say to add a data-{something} attribute, for instance data-init, that will hold the initial value, so it can be restored later.
Result should look like:
<input type="text" val="Some value..." data-init="Some value..." />
Now, I know I can achieve this by using:
#Html.TextBoxFor(m => m.InputText , new { data_init = Model.InputText })
But it's awful, and I have a lot of input fields with this behavior.
Also, I can't create a custom HtmlHelper because I have many input types with this behavior, it will be quite messy if I go this way...
So, what I think should be the practical solution is to use Data Annotations.
public class ExampleVM
{
[HoldInitialValue]
public string InputText { get; set; }
}
The [HoldInitialValue] attribute will be responsible to add the data-init="{value}" to the input tag. The {value} will be taken from the property's value, with also an option to override it.
So, how do I implement this behavior?
Thanks for the helpers.
Even if you were to create a custom attribute, which would need to implement MetadataAware in order to add the value to the AdditionalValues property of ModelMetadata, you still then need to create your own extension methods to read that value and add it to the htmlAttributes.
For an example of how to implement it, refer CustomAttribute reflects html attribute MVC5.
However, that is unnecessary since HTML inputs already store the initial value. For a <input> (other than a checkbox) or <textarea> its defaultValue. For a <input type="checkbox" /> its defaultChecked and for <select> its defaultSelected.
So in the case of your #Html.TextBoxFor(m => m.InputText), your could use for example
using javascript
var element = document.getElementById ("InputText");
var initialValue = elem.defaultValue;
// reset to initial value
element.value = initialValue;
or using jQuery
var element = $('#InputText');
var initialValue = element.prop('defaultValue');
// reset to initial value
element.val(initialValue);
try this one , using htmlAttributes as IDictionary
#Html.TextBoxFor(m => m.InputText , new Dictionary<string,object>()
{
{ "data-init", Model.InputText}
})

Free Text Entry in Angular Material mdAutoComplete

I want my angular material autocomplete to be a list of suggestions but not requirements. However I'm not sure how to implement as their is no clear example from the Angular Material docs.
In the example below my model is $ctrl.item.category
Clearly the example below is wrong, as my model is linked to md-selected-item, but this only works if I select an item. I want the user to be able to free enter the text if the item is not in the list. Basically how autocomplete already works in most browsers.
I see plenty of questions on how to disable this, but they are not trying to disable so much as clean up the left over text when an item is not selected. In these cases when an item is not selected then the model value is null, but text is left in the input.
I want the text left int he input to be the model value if the person does not select (or a match is not made).
md-autocomplete(
md-floating-label="Category Name"
flex="50"
md-input-name="category"
md-selected-item="$ctrl.item.category"
md-search-text="catSearch"
md-items="category in $ctrl.categories"
md-item-text="category"
md-min-length="0"
md-select-on-match=""
md-match-case-insensitive=""
required=""
)
md-item-template
span(md-highlight-text="catSearch" md-highlight-flags="^i") {{category}}
My options ($ctrl.categories) is an array of strings ['Food','Liqour'] and I wan the user to be able to use one of those or free enter Tables as their choice.
In this case you should link md-search-text to your model.
If you want to implement fuzzy search you have to write the filter method yourself. Look at this example:
template:
<md-autocomplete
md-items="item in $ctrl.itemsFilter()"
md-item-text="item.label"
md-search-text="$ctrl.query"
md-selected-item="$ctrl.selected"
>
<md-item-template>
<span md-highlight-text="$ctrl.query">{{item.label}}</span>
</md-item-template>
<md-not-found>
No item matching "{{$ctrl.query}}" were found.
</md-not-found>
<div ng-messages="$ctrl.myValidator($ctrl.query)">
<div ng-message="short">Min 2 characters</div>
<div ng-message="required">Required value</div>
</div>
</md-autocomplete>
controller:
var items = [ ... ];
ctrl.itemsFilter = function itemsFilter() {
return ctrl.query ? filterMyItems(ctrl.query) : items;
};
ctrl.myValidator = function (value) {
return {
short: value && value.length < 2,
required : value && value.length < 1,
};
};
then you just need to add filterMyItems method to filter your items
To improve the answer of #masitko, I have implemented the filter in a way, that it adds the query to the filtered list. So it becomes selectable and a valid option. So it's possible to make the autocomplete a suggestion box.
I'm using ES6 in my projects. But it should be easily adaptable to ES5 code.
myFilter() {
if (!this.query) return this.items;
const
query = this.query.toLowerCase(),
// filter items where the query is a substing
filtered = this.items.filter(item => {
if (!item) return false;
return item.toLowerCase().includes(query);
});
// add search query to filtered list, to make it selectable
// (only if no exact match).
if (filtered.length !== 1 || filtered[0].toLowerCase() !== query) {
filtered.push(this.query);
}
return filtered;
}

Aspx to Razor Select List MVC5

I have converted my MVC3 application to MVC5, I had to change all views to razor. Having a challenge with a select list:
In ASPX view that works I am using the following:
<select id="Profession" name="Profession" style="width: 235px; background-color: #FFFFCC;">
<% List<string> allProfessions = ViewBag.AllProfessions;
string selectedProfession;
if (Model != null && !String.IsNullOrEmpty(Model.Profession))
selectedProfession = Model.Profession;
else
selectedProfession = allProfessions[0];
foreach (var aProfession in allProfessions)
{
string selectedTextMark = aProfession == selectedProfession ? " selected=\"selected\"" : String.Empty;
Response.Write(string.Format("<option value=\"{0}\" {1}>{2}</option>", aProfession, selectedTextMark, aProfession));
}%>
</select>
In Razor I am using:
<select id="Profession" name="Profession" style="width: 235px; background-color: #FFFFCC;">
#{List<string> allProfessions = ViewBag.AllProfessions;
string selectedProfession;}
#{if (Model != null && !String.IsNullOrEmpty(Model.Profession))
{selectedProfession = Model.Profession;}
else {selectedProfession = allProfessions[0];}
}
#foreach (var aProfession in allProfessions)
{
string selectedTextMark = aProfession == selectedProfession ?
"selected=\"selected\"" : String.Empty;
Response.Write(string.Format("<option value=\"{0}\" {1}>{2}</option>",
aProfession, selectedTextMark, aProfession));
}
</select>
The list shows up at the top of the page, I can't figure out where is the problem. Would appreciate your assistance.
Don't create your dropdown manually like that. Just use:
#Html.DropDownListFor(m => m.Profession, ViewBag.AllProfessions, new { style = "..." })
UPDATE
I tried your solution but got this error: Extension method cannot by dynamically dispatched
And, that's why I despise ViewBag. I apologize, as my answer was a little generic. Html.DropDownList requires the list of options parameter to be an IEnumerable<SelectListItem>. Since ViewBag is a dynamic, the types of its members cannot be ascertained, so you must cast explicitly:
(IEnumerable<SelectListItem>)ViewBag.AllProfessions
However, your AllProfessions is a simple array, so that cast won't work when the value gets inserted at run-time, but that can be easily fixed by casting it to a List<string> and then converting the items with a Select:
((List<string>)ViewBag.AllProfessions).Select(m => new SelectListItem { Value = m, Text = m })
There again, you see why dynamics are not that great, as that syntax is rather awful. The way you should be handling this type of stuff is to use your model or, preferably, view model to do what it should do: hold domain logic. Add a property to hold your list of profession choices:
public IEnumerable<SelectListItem> ProfessionChoices { get; set; }
And then, in your controller action, populate this list before rendering the view:
var model = new YourViewModel();
...
model.ProfessionChoices = repository.GetAllProfessions().Select(m => new SelectListItem { Value = m.Name, Text = m.Name });
return View(model);
repository.GetAllProfessions() is shorthand for whatever you're using as the source of your list of professions, and the Name property is shorthand for how you get at the text value of the profession: you'll need to change that appropriately to match your scenario.
Then in your view, you just need to do:
#Html.DropDownListFor(m => m.Profession, Model.ProfessionChoices)
Given that you don't have this infrastructure already set up, it may seem like a lot to do just for a drop down list, and that's a reasonable thing to think. However, working in this way will keep your view lean, make maintenance tons easier, and best of all, keep everything strongly-typed so that if there's an issue, you find out at compile-time instead of run-time.
I believe it's happening because of the Response.Write. Try this:
#Html.Raw(string.Format("<option value=\"{0}\" {1}>{2}</option>", aProfession,
selectedTextMark, aProfession))

Wix populate listbox

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();
}

Multiple Dropdowns all need null value if value hasnt changed... Also Need selected value to be default

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.

Resources