I have the following in a select box
<div class="form-group">
<label for="machinename">Portails</label>
<select name='portalsChoice' multiple class="form-control">
<g:each in="${portals}" var="portal">
<option>${portal.name}</option>
</g:each>
</select>
</div>
Then, in a controller, I get all parameter like this :
def mymethod() {
List<String> portalsChoice = params.portalsChoice
...
}
If I select 2 elements, it works well.
I I select only 1 element, I have the following error : Cannot cast object 'my string' with class 'java.lang.String' to class 'java.util.List'
What is the best way to avoid this error ?
Thanks in advance,
Replace this
def mymethod() {
List<String> portalsChoice = params.portalsChoice
}
with
def mymethod() {
List<String> portalsChoice = params.list('portalsChoice')
}
The portalsChoice list will contain the selected elements, regardless of how many were selected.
You can provide a check in your controller:
List<String> portalsChoice = []
if(params.portalsChoice instanceof String){
//force it to be a list
portalsChoice = params.list('portalsChoice')
}else{
//for multiple
portalsChoice = params.portalsChoice
}
not so tricky but helps resolve your issue.
Related
I have a requirement to have different forms for different clients which can all be configured in the background (in the end in a database)
My initial idea is to create an object for "Form" which has a "Dictionary of FormItem" to describe the form fields.
I can then new up a dynamic form by doing the following (this would come from the database / service):
private Form GetFormData()
{
var dict = new Dictionary<string, FormItem>();
dict.Add("FirstName", new FormItem()
{
FieldType = Core.Web.FieldType.TextBox,
FieldName = "FirstName",
Label = "FieldFirstNameLabel",
Value = "FName"
});
dict.Add("LastName", new FormItem()
{
FieldType = Core.Web.FieldType.TextBox,
FieldName = "LastName",
Label = "FieldLastNameLabel",
Value = "LName"
});
dict.Add("Submit", new FormItem()
{
FieldType = Core.Web.FieldType.Submit,
FieldName = "Submit",
Label = null,
Value = "Submit"
});
var form = new Form()
{
Method = "Post",
Action = "Index",
FormItems = dict
};
return form;
}
Inside my Controller I can get the form data and pass that into the view
public IActionResult Index()
{
var formSetup = GetFormData(); // This will call into the service and get the form and the values
return View(formSetup);
}
Inside the view I call out to a HtmlHelper for each of the FormItems
#model Form
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#using FormsSpike.Core.Web
#{
ViewData["Title"] = "Home Page";
}
#using (Html.BeginForm(Model.Action, "Home", FormMethod.Post))
{
foreach (var item in Model.FormItems)
{
#Html.FieldFor(item);
}
}
Then when posting back I have to loop through the form variables and match them up again. This feels very old school I would expect would be done in a model binder of some sort.
[HttpPost]
public IActionResult Index(IFormCollection form)
{
var formSetup = GetFormData();
foreach (var formitem in form)
{
var submittedformItem = formitem;
if (formSetup.FormItems.Any(w => w.Key == submittedformItem.Key))
{
FormItem formItemTemp = formSetup.FormItems.Single(w => w.Key == submittedformItem.Key).Value;
formItemTemp.Value = submittedformItem.Value;
}
}
return View("Index", formSetup);
}
This I can then run through some mapping which would update the database in the background.
My problem is that this just feels wrong :o{
Also I have used a very simple HtmlHelper but I can't really use the standard htmlHelpers (such as LabelFor) to create the forms as there is no model to bind to..
public static HtmlString FieldFor(this IHtmlHelper html, KeyValuePair<string, FormItem> item)
{
string stringformat = "";
switch (item.Value.FieldType)
{
case FieldType.TextBox:
stringformat = $"<div class='formItem'><label for='item.Key'>{item.Value.Label}</label><input type='text' id='{item.Key}' name='{item.Key}' value='{item.Value.Value}' /></ div >";
break;
case FieldType.Number:
stringformat = $"<div class='formItem'><label for='item.Key'>{item.Value.Label}</label><input type='number' id='{item.Key}' name='{item.Key}' value='{item.Value.Value}' /></ div >";
break;
case FieldType.Submit:
stringformat = $"<input type='submit' name='{item.Key}' value='{item.Value.Value}'>";
break;
default:
break;
}
return new HtmlString(stringformat);
}
Also the validation will not work as the attributes (for example RequiredAttribute for RegExAttribute) are not there.
Am I having the wrong approach to this or is there a more defined way to complete forms like this?
Is there a way to create a dynamic ViewModel which could be created from the origional setup and still keep all the MVC richness?
You can do this using my FormFactory library.
By default it reflects against a view model to produce a PropertyVm[] array:
```
var vm = new MyFormViewModel
{
OperatingSystem = "IOS",
OperatingSystem_choices = new[]{"IOS", "Android",};
};
Html.PropertiesFor(vm).Render(Html);
```
but you can also create the properties programatically, so you could load settings from a database then create PropertyVm.
This is a snippet from a Linqpad script.
```
//import-package FormFactory
//import-package FormFactory.RazorGenerator
void Main()
{
var properties = new[]{
new PropertyVm(typeof(string), "username"){
DisplayName = "Username",
NotOptional = true,
},
new PropertyVm(typeof(string), "password"){
DisplayName = "Password",
NotOptional = true,
GetCustomAttributes = () => new object[]{ new DataTypeAttribute(DataType.Password) }
}
};
var html = FormFactory.RazorEngine.PropertyRenderExtension.Render(properties, new FormFactory.RazorEngine.RazorTemplateHtmlHelper());
Util.RawHtml(html.ToEncodedString()).Dump(); //Renders html for a username and password field.
}
```
Theres a demo site with examples of the various features you can set up (e.g. nested collections, autocomplete, datepickers etc.)
I'm going to put my solution here since I found this searching 'how to create a dynamic form in mvc core.' I did not want to use a 3rd party library.
Model:
public class IndexViewModel
{
public Dictionary<int, DetailTemplateItem> FormBody { get; set; }
public string EmailAddress { get; set; }
public string templateName { get; set; }
}
cshtml
<form asp-action="ProcessResultsDetails" asp-controller="home" method="post">
<div class="form-group">
<label asp-for=#Model.EmailAddress class="control-label"></label>
<input asp-for=#Model.EmailAddress class="form-control" />
</div>
#foreach (var key in Model.FormBody.Keys)
{
<div class="form-group">
<label asp-for="#Model.FormBody[key].Name" class="control-label">#Model.FormBody[key].Name</label>
<input asp-for="#Model.FormBody[key].Value" class="form-control" value="#Model.FormBody[key].Value"/>
<input type="hidden" asp-for="#Model.FormBody[key].Name"/>
</div>
}
<input type="hidden" asp-for="templateName" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
You can use JJMasterData, it can create dynamic forms from your tables at runtime or compile time. Supports both .NET 6 and .NET Framework 4.8.
After setting up the package, access /en-us/DataDictionary in your browser
Create a Data Dictionary adding your table name
Click on More, Get Scripts, Execute Stored Procedures and then click on Preview and check it out
To use your CRUD at runtime, go to en-us/MasterData/Form/Render/{YOUR_DICTIONARY}
To use your CRUD at a specific page or customize at compile time, follow the example below:
At your Controller:
public IActionResult Index(string dictionaryName)
{
var form = new JJFormView("YourDataDictionary");
form.FormElement.Title = "Example of compile time customization"
var runtimeField = new FormElementField();
runtimeField.Label = "Field Label";
runtimeField.Name = "FieldName";
runtimeField.DataType = FieldType.Text;
runtimeField.VisibleExpression = "exp:{pagestate}='INSERT'";
runtimeField.Component = FormComponent.Text;
runtimeField.DataBehavior = FieldBehavior.Virtual; //Virtual means the field does not exist in the database.
runtimeField.CssClass = "col-sm-4";
form.FormElement.Fields.Add(runtimeField);
return View(form);
}
At your View:
#using JJMasterData.Web.Extensions
#model JJFormView
#using (Html.BeginForm())
{
#Model.GetHtmlString()
}
I am working on a Grails form item that just populates a list of FaqCategories from an FaqCategory domain class. All I want the select dropdown to do is display the list. So far, this sort of works, but I'm not sure how to generate the list or what to put in the Value section if anything.
Form item:
<div class="col-sm-9">
<g:select name="Category" from="${FaqCategory.list()}" required="" class="form-control" aria-labelledby="faqCategory-label" value=""/>
</div>
Domain class #1
class Faq {
String question
String answer
FaqCategory faqCategory
static constraints ={
question nullable:false, maxSize:1000
answer nullable:false, maxSize:1000
faqCategory nullable:false
}
}
Domain class #2
class FaqCategory {
String categoryType
String toString(){
"$categoryType"
}
static constraints = {
categoryType nullable:true, maxSize:100, unique:true
}
}
Controller snippet
#Transactional(readOnly = true)
def create() {
respond new Faq(params)
}
Controller code :
yourAction(int id){
//do some
def faqInstance = Faq.get(id)
render[view:"form",model:[faqInstance:faqInstance]]
}
View code :
<g:select name="faqCategory"
from="${FaqCategory.list()}"
value="${faqInstance?.faqCategory?.categoryType}"
optionKey="id" />
Hope it helps..
You haven't provided enough information to show the context in which this select is being used so it is hard to say exactly what you need, but something like this should work:
<g:select name="faqCategory"
from="${FaqCategory.list()}"
value="${you only need this if you want to preselect a value}"
optionKey="id" />
See http://grails.org/doc/latest/ref/Tags/select.html for more details.
I hope that helps.
Case:
I have a list of items of Class X displayed using Editor Template for Class X.
Problem:
How can I get index of an item being processed on the inside of the Editor Template?
I've been using this HtmlExtension that returns only the needed id of an iteration. It's basically a regex on ViewData.TemplateInfo.HtmlFieldPrefix that's capturing the last number.
public static class HtmlExtensions
public static MvcHtmlString Index(this HtmlHelper html)
{
var prefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var m = Regex.Match(prefix, #".+\[(\d+)\]");
if (m.Success && m.Groups.Count == 2)
return MvcHtmlString.Create(m.Groups[1].Value);
return null;
}
}
Can be used in an EditorFor-template like this:
#Html.Index()
Use a for loop instead of for each and pass the indexer into the EditorFor extension; razor should handle the rest.
#for(var i = 0; i < Model.count(); i++)
{
#Html.EditorFor(m => Model.ToArray()[i], new { index = i })
}
Update:
pass in the the index of the item using view data as show above.
In your editor template access the item via the ViewBag
<span> Item Index: #ViewBag.index </span>
Using the EditorTemplate is the best solution when viewing models that contain a list of something.
In order to find the index for the sub-model being rendered you can use the property that Razor sets by default:
ViewData.TemplateInfo.HtmlFieldPrefix
Say, for example, you have the following view models:
public class ParagraphVM
{
public int ParagraphId { get; set; }
public List<LineVM> Lines { get; set; }
}
and
public class LineVM
{
public int Id { get; set; }
public string Text {get; set;}
}
and you want to be able to edit all the "LineVM" within a "ParagraphVM". Then you would use an Editor Template so you would create a view at the following folder (if it doesn't exist) with the same name as the sub-model Views/Shared/EditorTemplates/LineVM.cshtml:
#model MyProject.Web.MVC.ViewModels.Paragraphs.LineVM
#{
//this will give you the List's element like Lines[index_number]
var field = ViewData.TemplateInfo.HtmlFieldPrefix;
}
<div id="#field">
#Html.EditorFor(l => l.Text)
</div>
Assuming you have a Controller's ActionResult that is returning a View and passing a ParagrapghVM viewmodel to a view, for example Views/Paragraph/_Paragraph.cshtml:
#model MyProject.Web.MVC.ViewModels.Paragraphs.ParagraphVM
#using (Html.BeginForm("Details", "Paragraphs", FormMethod.Post))
{
#Html.EditorFor(p => p.Lines)
}
This view would render as many editors for the list Lines as items contains that list.
So if, for example, the property list ParagraphVM.Lines contains 3 items it would render something like:
<div id="#Lines[0]">
<input id="Lines_0__Text name="Lines[0].Text"/>
</div>
<div id="#Lines[1]">
<input id="Lines_1__Text name="Lines[1].Text"/>
</div>
<div id="#Lines[2]">
<input id="Lines_2__Text name="Lines[2].Text"/>
</div>
With that you can know exactly what position each items is within the list and for example use some javascript to create a carousel or whatever you want to do with it. But remember that to edit that list you don't really need to know the position as Razor takes care of it for you. If you post back the model ParagraphVM, the list Lines will have the values bound (if any) without any additional work.
How about:
#using System
#using System.Text.RegularExpressions
var i = Convert.ToInt32(Regex.Matches(
ViewData.TemplateInfo.HtmlFieldPrefix,
#"\[([0-9]+)?\]")[0].Groups[1].ToString());
I think the easiest way is:
#Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, #"(?!\[)\d+(?=\])")
Or as helper:
public static string Index(this HtmlHelper html)
{
Match m = Regex.Match(html.ViewData.TemplateInfo.HtmlFieldPrefix, #"(?!\[)\d+(?=\])");
return m.Success ? m.Value : null;
}
Inspired by #Jona and #Ryan Penfold
You can use #Html.NameFor(m => m.AnyField). That expression will output the full name property including the index. You could extract the index there...
I have just started to discover FluentHml and I'm stuck with the CheckBoxList Helper.
Here is the code
<ul>
<%=this.CheckBoxList(m=>m.Filter)
.Options(criteria.Choices, x => x.Code, x => x.DisplayText)
.Selected(Model.Filter)
.Label(criteria.Label).ItemFormat("<li> {0} </li>")
%>
</ul>
So, I have a checkboxlist based on "criteria.Choices" which is typed as List<ChoiceViewModel>.
Here is the code of a ChoiceViewModel
public class ChoiceViewModel
{
// Some stuff
public string Code { get{ return _code; } }
public string Label { get { return _label; }}
public string DisplayText { get { return _displayText;}
}
}
And my problem is :
I want to disable the checkbox under a condition.
Let's say that if the Code doesn't start with an "A", I want to disable the checkbox
How can I achieve that ?
Thanks,
Hasan
CheckboxList does not provide that. You can do it with CheckBox in loop. Something like this:
<label>criteria.Label</label>
<%foreach (var choice in criteria.Choices) {%>
<li>
<%=this.CheckBox(m => m.Filter)
.Value(choice.Code)
.Checked(choice == Model.Filter)
.Label(choice.Code.DisplayText)
.Disabled(choice.Code.StartsWith("A")%>
</li>
<%}%>
I am having hard time getting hasErrors to work with indexed properties. For example
class Order {
String prop1
String prop2
static hasMany = [items: Item]
}
class Item {
String name
static constraints = {
name(blank:false)
}
}
Validation works properly and on item.name being blank I do get an error with
<g:renderErrors bean="${orderInstance}"/>
However, I am trying to have input box highlighted using hasErrors :
<g:each in="${orderIntsance.items}" status="i" var="item">
<span class="field ${hasErrors(bean: orderInstance, field: ????????? , 'errors')}">
<g:textField name="items[${i}].name" value="${item?.name}"/>
</span>
</g:each>
Not sure how to get to it with a field: property, any ideas?
Thanks
Found it, got to implement a custom validator per Grails Validation doc page (duh):
"In some situations (unusual situations), you might need to know how to transfer an error from a nested child object to a parent domain object. In some circumstances, if you validate the children objects before the parent object, then the errors on the children objects will get reset before the object is sent to the JSP." (http://www.grails.org/Validation)
static constraints = {
children( nullable:true, validator: {val, obj, errors ->
def errorFound = false;
val.each{ child ->
if(!child .validate()){
errorFound = true;
child .errors.allErrors.each{ error->
obj.errors.rejectValue('children', "parent.child.invalid",
[child, error.getField(), error.getRejectedValue()] as Object[],
"For source [${child}],
field [${error.getField()}] with value [${error.getRejectedValue()}] is invalid.")
}
}
}
if(errorFound) return false;
})
}
I had a similar requirement and tried the following way and it worked. Just wanted to share it
${hasErrors(bean: orderInstance, field: 'items['+ i +'].name', 'errors')