Adding jQuery to forms with the same model in MVC3 - c#

I am trying to have two submission forms with similar functionality on the same view, where each form is strongly typed to the same model. I then am trying to add a datepicker to the same input on both forms, but I am unsure of how to do this. Here is my code:
#using (#Ajax.BeginForm(...
new { id = "editScheduleForm" }))
{
<div class="editor-label">
#Html.LabelFor(model => model.StartTime)
</div>
<div class="editor-field">
<input
#Html.EditorFor(model => model.StartTime)
#Html.ValidationMessageFor(model => model.StartTime)
</div>
}
...
#using (#Ajax.BeginForm(...
new { id = "addScheduleForm" }))
{
<div class="editor-label">
#Html.LabelFor(model => model.StartTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StartTime)
#Html.ValidationMessageFor(model => model.StartTime)
</div>
}
Both of these forms are in their own strongly-typed partial views. I naturally tried simply adding the datepicker in jQuery to both partial views, like so:
$(function () {
$("#StartTime").datepicker();
});
But it unsurprisingly only worked for one of the inputs. I have been trying to use the HTML id that I added to both Ajax form declarations (e.g. editScheduleForm and addScheduleForm), but am unsure of how to do this. Any suggestions?
Solution:
As scottm suggested, I looked through documentation for using a custom editor template. I read this, detailing functionality I didn't know existed:
http://msdn.microsoft.com/en-us/library/ee407399.aspx
Here, you can specify a template, and then add a parameter for the htmlFieldName, which is specifically designed to circumvent the problem I was happening.

You can add a class to the inputs in your editor template. Then you can then use the class as the jQuery selector.
Add a file called DateTime.cshtml under ~/Views/Shared/EditorTemplates, and add the following:
#model DateTime
#Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty), new { #class = "date" })
Then add this jQuery to the page.
$(function () {
$(".date").datepicker();
});

The ID must be unique in a document.
Never rely on multiple ids on a page working correctly. Matter of fact, despite what most examples in blogs do, you should hardly ever use ids as jquery selectors.
Like #scottm said. You should use classes to tag items which you want to be widget-ized.
Here's a simplistic bit of code so you can create jq ui widgets declaratively:
$(function() {
$('[data-usewidget]').each(function() {
var $this = $(this), widget = $this.data('usewidget');
$.fn[widget] && $this[widget]();
});
});
Then in your html
<input name=startTime data-usewidget=datepicker />

Related

view component data input validation

I've been trying to play around with view components in ASP.NET Core to create a more "widget" based system. What I currently have is a contact form that opens a modal dialog and displays a view component based on the selection they make. For simplicity sake, I invoke the viewcomponent as follows from my layout page (yes I'm not using the vc: convention atm):
#await Component.InvokeAsync("Inquiry")
It will then invoke the inquiry form through here:
public class InquiryViewComponent : ViewComponent
{
private readonly ILogger<InquiryViewComponent> _logger;
private readonly IServiceRepository _serviceRepository;
public InquiryViewComponent(ILogger<InquiryViewComponent> logger, IServiceRepository serviceRepository)
{
_logger = logger;
_serviceRepository = serviceRepository;
}
public async Task<IViewComponentResult> InvokeAsync()
{
// do stuff here like create a backing model and then return the view
return await Task.FromResult(View("~/Views/Inquiry/Inquiry.cshtml", _viewModel));
}
}
Everything loads correctly and the modal dialog opens (have a script to invoke the modal on open) with the following:
#model InquiryViewModel
//modal initialization here
//modal header here
#using (Html.BeginForm("Inquiry", "Inquiry"))
{
<div class="modal-body">
<div class="container">
<div class="row mb-2">
<div class="col-md-6">
#Html.LabelFor(m => m.Inquire.FirstName, new { #class = "required" })
#Html.TextBoxFor(m => m.Inquire.FirstName, new { #class="form-control", autocomplete="given-name", required="required" })
</div>
<div class="col-md-6">
#Html.LabelFor(m => m.Inquire.LastName, new { #class = "required" })
#Html.TextBoxFor(m => m.Inquire.LastName, new { #class = "form-control", autocomplete = "family-name", required = "required" })
</div>
</div>
//OTHER FIELDS HERE
</div>
//FOOTER
}
Once the user submits, if the model is invalidated, it posts back to the view Inquiry which I don't want. I do understand that it is trying to return the page on the form (I can see where this is happening). I also understand I could use client-side validation but I'm aiming to build more complex widgets and don't want a pile of JavaScript code. What I want to know: if I have a form based view component and it needs model validation from the server, can I have it return the result WITHOUT rendering the page? Is this implementation just completely incorrect for view components and what other ways are more suitable?

Client side validation issue when using a view model more than once on a page

I have a view model that has 4 properties in it, Username, Role, Application, and Reasons(Required). I am then using the following html helpers to show these in a view:
<form id="roleForm">
<fieldset>
<p>
#Html.LabelFor(model => model.RoleName)
#Html.DisplayWithIdFor(model => model.RoleName)
#Html.HiddenFor(model => model.RoleName)
</p>
<p>
#Html.LabelFor(model => model.UsersName)
#Html.DisplayFor(model => model.UsersName)
#Html.HiddenFor(model => model.UsersName)
</p>
<p>
#Html.LabelFor(model => model.Application)
#Html.DisplayFor(model => model.Application)
#Html.HiddenFor(model => model.Application)
</p>
<p>
#Html.LabelFor(model => model.Reasons)
#Html.TextAreaFor(model => model.Reasons, new { #cols = "80", #rows = "4", #class = "k-textbox" })
<span style="color:red;">#Html.ValidationMessageFor(model => model.Reasons)</span>
</p>
<button class="button-yes k-button" type="button">OK</button>
<button class="button-no k-button" type="button">Cancel</button>
<!-- Allow form submission with keyboard without duplicating the dialog button -->
<input type="submit" tabindex="-1" style="position:absolute; top:-1000px">
</fieldset>
</form>
However due to the nature of a particular system on one page I have 3 different things that I need to use this view model for on a single page. To do this I have 3 partial views and 3 Kendo UI windows, Again this part is working.
The issue then arises that JQuery always pics up the first Text Area defined on the page, this is expected as they all have the same ID's so I have looked to change the ID's on the other 2 Text Areas, see bellow:
#Html.TextAreaFor(model => model.Reasons, new { #id = "AddCompanyReasons", #cols = "80", #rows = "4", #class = "k-textbox" })
This now finds the correct text Area. However now the issue is the validation is always returning true on the client side despite getting the text or lack of text from the text box.
var isReasonValid = $('#AddCompanyReasons').valid();
I had thought that maybe creating an attribute to change the property name slightly every time the property appears on the page might solve this but it seems a bit extreme.
Has anyone come across this problem before and does any one have any ideas on how to solve this?
EDIT
Terleric Kendo Window Popups to show the three forms:
#(Html.Kendo().Window()
.Name("remove-role-window")
.Title("Update Roles")
.Visible(false)
.Content(#<text>
#Html.Partial("~/Areas/UserCurrentRoles/Views/UserCurrentRoles/_AXRemoveRole.cshtml")
</text>)
.Modal(true)
.Width(500)
.Events(e => e.Open("resetDialog"))
)
#(Html.Kendo().Window()
.Name("remove-company-window")
.Title("Update Roles")
.Visible(false)
.Content(#<text>
#Html.Partial("~/Areas/UserCurrentRoles/Views/UserCurrentRoles/_RemoveAXCompany.cshtml")
</text>)
.Modal(true)
.Width(500)
.Events(e => e.Open("resetCompanyDialog"))
)
#(Html.Kendo().Window()
.Name("add-company-window")
.Title("Update Roles")
.Visible(false)
.Content(#<text>
#Html.Partial("~/Areas/UserCurrentRoles/Views/UserCurrentRoles/_AddAXCompany.cshtml", Model)
</text>)
.Modal(true)
.Width(500)
.Events(e => e.Open("resetAddDialog"))
)
validation works on document ready. after document ready if html is dynamically changing. by any operation means any element has been reprinted or recreated will not be validated. you will have to run validation function again to validate all fields.

Issue with EditorTemplates

ASP.Net MVC 4.0 based Scenario:
I need to render a set of Widgets[aka User Control with sets of custom fields] on a screen.
The ViewModel for this Widget is something like this -
Public Class Widget
{
public string Header {get;set;}
//Note the data type here. It causes issues with rendering Widget itself.
public object ActualContent {get;set;}
public string Footer {get;set;}
}
At, run-time, say I want to render 2 widgets. Then all I need to do is create the instances as shown below :
Widget w1 = new Widget()
{
Header ="PatientDetails",
ActualContent = new Patient ()
{
FirstName = "ABC",
LastName = "XYZ"
}
Footer = "PatientDetails
};
Widget w2 = new Widget()
{
Header ="Address-Header",
ActualContent = new Address ()
{
ZIPCode = "123456",
Extn = "1234"
}
Footer = "Address-Footer"
};
The cshtmls are :
Main Page i.e Widget.cshtml
#using Widgets.Models
#model List<Widget>
...
<div class="widget">
<div class="header">
#Model.Header
</div>
<div class="body" >
#Html.EditorFor(model => Model.ActualContent)
</div>
<div class="footer">
#Model.Footer
</div>
</div>
...
Patient.cshtml in ~\Views\Shared\EditorTemplates
#model Widgets.Models.Patient
<div class="editor-label">
#Html.LabelFor(model => Model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.FirstName)
#Html.ValidationMessageFor(model => Model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => Model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => Model.LastName)
#Html.ValidationMessageFor(model => Model.LastName)
</div>
Address.cshtml has a similar implementation.
Note, the property ActualContent, is of type object. It can be assigned an instance of any UserControl [Patient,Address,etc] at runtime.
Also, each of the UserControls has a corresponding strongly typed cshtml view defined inside the EditorTemplates.
Issue :
Now the issue is when I try to load the Widgets, I can see both the widgets getting rendered but only displaying the Header & Footer values.
The portion correponding to the ActualContent property, as shown above is not rendered at all, despite the fact that, while debigging, I can see that
corresponding .cshtml for Patients & Address are getting accessed!
I feel there might be something that I may have missed out on.
It would be nice if someone can get this working for me. Thanks in advance.
Sandesh L.
Try to access your Editor template like this,
#Html.EditorFor(m => m.ActualContent, "_YourEditorTemplate")

re-display all form input before post

Before some people start yelling, I realize there is similar questions up, but their all dealing with single entries...
I have a long form and want to use a generic method of dealing with the re-displaying of data.
Please note : this is an asp.Net MVC 4 application using razor2 views
Example of one of the fields,
<div class="control-group">
<label class="control-label">#Html.LabelFor(m => m.Name)
<span class="required">*</span>
</label>
<div class="controls">
#Html.TextBoxFor(m => m.Name, new { #name = "nameInput" })
</div>
</div>
My think was to add an ID to each Textbox...
Example of how is might be displayed on the confirmation view..
<div class="control-group">
<label class="control-label">Name:</label>
<div class="controls">
<span class="text display-value" data-display="nameInput"></span>
</div>
</div>
Then render that value using data-display with the ID...
jQuery that I thought would deal with it...
var displayConfirm = function() {
$('.display-value', form).each(function(){
var input = $('[name="'+$(this).attr("data-display")+'"]', form);
if (input.is(":text") || input.is("textarea")) {
$(this).html(input.val());
} else if (input.is("select")) {
$(this).html(input.find('option:selected').text());
}
});
}
Unfortunately this does not appear to be working correctly....
Can anyone point out / re-solve the issue ?
Got it, I needed to use the name in the model rather then assigning names.
Got correct names from "page view source", and simply plugged that value into data-display tag.

Data Annotation doesn't not work if control generates from List View Model

I have below view model
public class QuestionarrieAnswersViewModel
{
public long QuestionID { get; set; }
public string Question { get; set; }
[Required(ErrorMessage="required")]
[StringLength(255, ErrorMessage = "Maximum 255 characters are allowed.")]
public string Answer { get; set; }
}
and i am generating view in below way
#model List<BusinessLayer.Models.ViewModel.QuestionarrieAnswersViewModel>
#using (Ajax.BeginForm("SaveQuestionarrie", "Member", FormMethod.Post, new AjaxOptions { OnBegin = "OnBegin", OnComplete = "OnComplete" }, new { #class = "form-horizontal" }))
{
for(int i=0;i<Model.Count;i++)
{
<div class="control-group">
<div class="head_form">
<label class="control-label">#Model[i].Question</label>
<div class="controls">
#Html.TextAreaFor(m=>m[i].Answer)
#Html.ValidationMessageFor(m => m[i].Answer)
#Html.HiddenFor(m=>m[i].QuestionID)
</div>
</div>
</div>
}
<div class="control-group">
<div class="controls">
<button class="btn" type="submit">Save</button>
</div>
</div>
}
I have set dataannotation on Answer field in above model but its not applying in above view while it works if i generate view in below way
#model BusinessLayer.Models.ViewModel.QuestionarrieAnswersViewModel
#using (Ajax.BeginForm("SaveQuestionarrie", "Member", FormMethod.Post, new AjaxOptions { OnBegin = "OnBegin", OnComplete = "OnComplete" }, new { #class = "form-horizontal" }))
{
#Html.TextAreaFor(m => m.Answer)
#Html.TextAreaFor(m => m.QuestionID)
<div class="control-group">
<div class="controls">
<button class="btn" type="submit">Save</button>
</div>
</div>
}
What's going wrong here...
In order to fire those validation rules, you'll need to use an EditorFor instead of a TextAreaFor.
It's because there's an outstanding issue with validation of TextArea's, see here: http://aspnet.codeplex.com/workitem/8576.
This is due to a bug in the version of jquery.validate.unobtrusive.js that was released with ASP.NET MVC3. This answer is on the same bug, the solution to this is to upgrade to the latest version of jquery.validate.unobtrusive.js - either grab it from an MVC4 project or update using NuGet.
The jquery.validate.unobtrusive.js script doesn't seem to have a version number so if you search in the script for a function called escapeAttributeValue, then this is a version of the script that has this bug fix.
The problem that is addressed in the bug fix is how to handle markup generated having name attributes containing characters that need escaping in a jQuery selector. In this case
<textarea cols="20" name="[0].Answer" rows="2"></textarea>
needs this selector
$('[name=\\[0\\]\\.Answer]')
The client-side DataAnnotation (validation) does not work for the Html.TextAreaFor() helper.
To make it work, you have to decorate the 'Answer' property with the [DataType(DataType.MultilineText)] attribute. And in the view, use Html.EditorFor() helper instead of the Html.TextAreaFor() helper mehthod.
See similar SO answer asp.net mvc TextAreaFor is not getting validated as a required field.

Categories