I have a form with and one of the fields for the model is complex, so I created a custom editor for that field.
Here is the part of the editor in the main form:
<div class="form-group">
<label class="col-md-3 control-label">#i18n.Translations</label>
<div class="control-group col-md-9">
#Html.EditorFor(m => m.Translations)
</div>
</div>
Here is the Custom Editor:
#using XXX.i18n
#using XXX.ViewModels
#model XXX.ViewModels.ServiceTranslation
#{
bool translationNameValid = Html.ViewData.ModelState.IsValidField(Html.IdFor(m => m.TranslationName).ToString());
bool translationDescriptionValid = Html.ViewData.ModelState.IsValidField(Html.IdFor(m => m.TranslationDescription).ToString());
}
<div class="form-group">
#Html.HiddenFor(m => m.EntityLanguageId)
#Html.HiddenFor(m => m.Language)
#Html.HiddenFor(m => m.TranslationId.Left)
#Html.HiddenFor(m => m.TranslationId.Right)
<div class="col-md-6">
<div class="input-group #(translationNameValid ? null : "state-error")">
<span class="input-group-addon">#Model.Language</span>
#Html.TextBoxFor(m => m.TranslationName, new { #placeholder = i18n.Name, #class = "form-control" })
</div>
<div class="#(translationNameValid ? null : "state-error")">
<em for="#Html.IdFor(m => m.TranslationName)" class="invalid">#Html.ValidationMessage("TranslationName")</em>
</div>
</div>
<div class="col-md-6 #(translationDescriptionValid ? null : "state-error")">
#Html.TextBoxFor(m => m.TranslationDescription, new { #placeholder = i18n.Description, #class = "form-control" })
<em for="#Html.IdFor(m => m.TranslationDescription)" class="invalid">#Html.ValidationMessage("TranslationDescription")</em>
</div>
</div>
Also, I should mention that the main form is inside a partial and the TranslationName and TranslationDescription are 2 required fields (and are annotated with [Required] in the ViewModel)
The #Html.ValidationMessage return the required field message with the textboxes are empty.
My problem is that the translationDescriptionValid and the translationNameValid are always true and they should return false when the fields are blank.
I've used this method in other forms with success.
What am I doing wrong here?
Related
I have a button which add parent partial view and parent partial view also have a add button which adds child partial view in it.My problem is that when i press main button to add 2 parent partial view and press add child partial button in second parent partial view, then child partial view create in first parent partial view.I want to add child partial view according to parent partial view. How can i do this?
Coding
//parent partial
<div id="individual-details" class="card">
<div class="card-header">
#(Model.SearchFirstName + Model.SearchLastName == "" ? "New Search Individual" : Model.SearchFirstName + Model.SearchLastName)
#if (ViewData["hideRemove"] == null || (bool?)ViewData["hideRemove"] == false)
{
<a id="individual-remove" href="#" onclick="removeIndividual(this)" class="btn btn-danger pull-right">Remove</a>
}
</div>
<div class="form-horizontal">
<div class="card-block">
<div class="form-group">
#Html.LabelFor(m => m.SearchPostcode, "Search Post Code", new { #class = "form-control-label" })
#Html.TextBoxFor(m => m.SearchPostcode, null, new { #class = "form-control" })
</div>
</div>
</div>
<div class="card-block">
<div id="Characteristics" class="mb-3">
#if (Model?.Characteristics != null)
{
for (var i = 0; i < Model?.Characteristics.Count; i++)
{
<div class="form-group">
#{ Html.RenderPartial("IndividualSearchCharacterisiticPartial", Model.Characteristics[i], new ViewDataDictionary()); }
#* #Html.EditorFor(m => m.Characteristics);*#
</div>
}
}
</div>
<button id="add-characteristics" onclick="add(this)" type="button" class="btn btn-success">Add Characteristics</button>
</div>
</div>
//Button
function add(element){
var action = ' #Url.Action("NewIndividualSearchCharacteristic", "Blended")';
$.post(action)
.done(function (partialView) {
$('#Characteristics').append(partialView);
});
}
//child partial
#model ABC.Core.Models.DTOs.Characteristic
#using (Html.BeginCollectionItem("Characteristics"))
{
<div id="characteristic-details" class="card">
<div class="form-horizontal">
<div class="card-block">
<div class="container">
<div class="row">
<div class="col-*-*">
#Html.LabelFor(m => m.Name, "Name", new { #class = "form-control-label" })
</div>
<div class="col">
#Html.TextBoxFor(m => m.Name, null, new { #class = "form-control" })
</div>
<div class="col-*-*">
#Html.LabelFor(m => m.Value, "Value", new { #class = "form-control-label" })
</div>
<div class="col">
#Html.TextBoxFor(m => m.Value, null, new { #class = "form-control" })
</div>
<div class="col-*-*">
<a id="characteristic-remove" href="#" onclick="removeCharacteristic(this)" class="btn btn-danger pull-right">Remove</a>
</div>
</div>
</div>
</div>
</div>
</div>
}````
$('#Characteristics').append(partialView);
If you use Id selector, it will always select the first element with the id Characteristics.
Try the below codes:
function add(element){
var action = ' #Url.Action("NewIndividualSearchCharacteristic", "Blended")';
$.post(action)
.done(function (partialView) {
element.previousElementSibling.append(partialView);
});
}
Have been trying to get a member profile management area working with ajax as each section of the page is hidden within a show hide div.
I have used ajax before in MVC applications but have never used it with umbraco surface controllers before. I'm unsure why returning a partial view in the controller is outputting the whole page and not just the partial view that I am giving to it.
Controller:
[HttpPost]
[ActionName("MvcMemberEditProfileDetails")]
public ActionResult MvcMemberEditProfileDetails(MvcMemberEditProfileDetailsModel model)
{
var memberService = Services.MemberService;
var currentUser = Membership.GetUser();
var member = memberService.GetByEmail(currentUser.Email);
bool result = false;
if (ModelState.IsValid)
{
...
}
if (result)
{
...
}
return PartialView("MvcMemberEditProfileDetails", model);
}
View:
#model Umbraco714.Models.MvcMemberEditProfileDetailsModel
#using (Ajax.BeginForm("MvcMemberEditProfileDetails", "MvcMember", new AjaxOptions() { HttpMethod = "POST", UpdateTargetId = "MvcMemberEditProfileDetails", InsertionMode = InsertionMode.Replace }))
{
if (Model.Result != null)
{
if (Model.Result == true)
{
<div id="result" class="alert alert-success">
#Html.Raw(Model.ResultMessage)
</div>
}
else
{
<div id="result" class="alert alert-danger">
#Html.Raw(Model.ResultMessage)
</div>
}
}
<div class="form-default">
<div class="row">
<div class="col-md-6">
<div class="form-group">
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
</div>
</div>
<div class="col-md-6">
<div class="form-group">
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
#Html.ValidationMessageFor(m => m.LastName)
</div>
</div>
<div class="col-md-6">
<div class="form-group">
#Html.LabelFor(m => m.CompanyName)
#Html.TextBoxFor(m => m.CompanyName)
#Html.ValidationMessageFor(m => m.CompanyName)
</div>
</div>
<div class="col-md-6">
<div class="form-group">
#Html.LabelFor(m => m.CompanyPosition)
#Html.TextBoxFor(m => m.CompanyPosition)
#Html.ValidationMessageFor(m => m.CompanyPosition)
</div>
</div>
<div class="col-xs-12">
<div class="form-group">
#Html.LabelFor(m => m.CompanyBio)
<span class="bs-tooltip" data-toggle="tooltip" data-placement="top" title="Max 1000 characters long"><i class="fa fa-info-circle" aria-hidden="true"></i></span>
#Html.TextAreaFor(m => m.CompanyBio, new { #rows = "4", #maxlength = "1000" })
#Html.ValidationMessageFor(m => m.CompanyBio)
</div>
#TempData["Status"]
<div class="form-group nomargin">
<div class="text-right">
<button class="button" type="submit"><i class="fa fa-floppy-o" aria-hidden="true"></i> Update</button>
</div>
</div>
</div>
</div>
</div>
}
I have everything that needs to be included (as far as I'm aware) well before the form and there are no console errors.
<script src="/scripts/jquery.js"></script>
<script src="/scripts/bootstrap.min.js"></script>
<script src="/scripts/jquery-val.js"></script>
<script src="/scripts/jquery.unobtrusive-ajax.min.js"></script>
I have also made sure that UnobtrusiveJavaScriptEnabled is set to true in the web.config but I'm still getting a full page rendered when I post the form.
Initially:
When the page loads and the form shows
After:
When the form is submitted and the correct partial view is returned but inside of an entire
Feeling pretty dumbfounded that I've spent more that a couple of hours looking into this even though it's clearly working in a sort of way.
Is it possible / a known thing for this to happen? I searched around but couldn't find any topics with a similar issue, unless I was just wording things wrong.
Just looking for a nudge in the right direction if anybody has any ideas?
Umbraco can be funny with partials. Try returning CurrentUmbracoPage() in the controller.
As for the message, you could use TempData as it only lasts for one request, rather than the model.
So I have a ViewModel and the properties are all validated by having required attributes on them. When I submit the form it doesn't throw any error messages, i.e. name field is required.
I think I have figured where the problem is. Its in the submit button, how do I get the button to hit the HttpPost method of the ActionList because at the moment its not hitting that, hence the modelstate is not being validated.
View:
<h2>#ViewBag.Title</h2>
<hr>
<div class="well">
#using (Html.BeginForm("BookingDetails", "Booking"))
{
#Html.ValidationSummary(true)
<div class="form-group">
#Html.DisplayNameFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.FirstName)
</div>
<div class="form-group">
#Html.DisplayNameFor(m => m.Surname)
#Html.TextBoxFor(m => m.Surname, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Surname)
</div>
<div class="form-group">
#Html.DisplayNameFor(m => m.EmailAddress)
#Html.TextBoxFor(m => m.EmailAddress, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.EmailAddress)
</div>
<div class="form-group">
#Html.DisplayNameFor(m => m.MobileNumber)
#Html.TextBoxFor(m => m.MobileNumber, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.MobileNumber)
</div>
<div class="form-group">
#Html.DisplayNameFor(m => m.NumberOfPeople)
#Html.TextBoxFor(m => m.NumberOfPeople, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.NumberOfPeople)
</div>
<div class="form-group">
#Html.DisplayNameFor(m => m.Date)
#Html.TextBoxFor(m => m.Date, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Date)
</div>
<div>
#Html.DisplayNameFor(m => m.Time)
#Html.TextBoxFor(m => m.Time, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Time)
</div>
}
</div>
<div class="form-group">
#Html.ActionLink("Book my table", "BookingDetails", "Booking" new { #class ="btn btn-primary" })
</div>
Controller:
// GET:
public ActionResult BookingDetails()
{
return View();
}
// Post
[HttpPost]
public ActionResult BookingDetails(BookingDetailsViewModel model)
{
if(ModelState.IsValid)
{
return RedirectToAction("BookingConfirmation");
}
return View(model);
}
You have two problems that I can see.
Firstly, your button isn't a submit button. Its a link that sits outside of the form. You must move it into your form and make it a submit button (or write some javascript that submits the form on click):
<div>
#Html.DisplayNameFor(m => m.Time)
#Html.TextBoxFor(m => m.Time, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Time)
</div>
<input type="submit" class="btn btn-primary" /> <!-- move it inside the form -->
#* ^^^^ submit *#
} <!-- end of your form is here -->
Also, Html.ValidationSummary(true) will hide all of your property errors from your Validation Summary. This may not be what you want. If you run into that issue.. remove true from the call.
Having faced this issue (I wanted to allow an edit by using a bootstrap modal window, i'm using MVC4 and entity framework), when I want to save my changes, I have this error message since I'm using the modal window :
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
Here are my actions :
[HttpGet]
public ActionResult EditPerson(long id)
{
var person = db.Persons.Single(p => p.Id_Person == id);
ViewBag.Id_ProductPackageCategory = new SelectList(db.ProductPackageCategories, "Id_ProductPackageCategory", "Name", person.Id_ProductPackageCategory);
return PartialView("_EditPerson", person);
}
[HttpPost]
public ActionResult EditPerson(Person person)
{
ViewBag.Id_ProductPackageCategory = new SelectList(db.ProductPackageCategories, "Id_ProductPackageCategory", "Name", person.Id_ProductPackageCategory);
if (ModelState.IsValid)
{
ModelStateDictionary errorDictionary = Validator.isValid(person);
if (errorDictionary.Count > 0)
{
ModelState.Merge(errorDictionary);
return View(person);
}
db.Persons.Attach(person);
db.ObjectStateManager.ChangeObjectState(person, EntityState.Modified);
db.SaveChanges();
return View("Index");
}
return View(person);
}
My partial view :
#model BuSIMaterial.Models.Person
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Edit</h3>
</div>
<div>
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "table"
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<div class="modal-body">
<div class="editor-label">
First name :
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.FirstName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
Last name :
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
National number :
</div>
<div class="editor-field">
#Html.EditorFor(model => model.NumNat, new { maxlength = 11 })
#Html.ValidationMessageFor(model => model.NumNat)
</div>
<div class="editor-label">
Start date :
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.StartDate, new { #class = "datepicker", #Value = Model.StartDate.ToString("yyyy/MM/dd") })
#Html.ValidationMessageFor(model => model.StartDate)
</div>
<div class="editor-label">
End date :
</div>
<div class="editor-field">
#if (Model.EndDate.HasValue)
{
#Html.TextBoxFor(model => model.EndDate, new { #class = "datepicker", #Value = Model.EndDate.Value.ToString("yyyy/MM/dd") })
#Html.ValidationMessageFor(model => model.EndDate)
}
else
{
#Html.TextBoxFor(model => model.EndDate, new { #class = "datepicker" })
#Html.ValidationMessageFor(model => model.EndDate)
}
</div>
<div class="editor-label">
Distance House - Work (km) :
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HouseToWorkKilometers)
#Html.ValidationMessageFor(model => model.HouseToWorkKilometers)
</div>
<div class="editor-label">
Category :
</div>
<div class="editor-field">
#Html.DropDownList("Id_ProductPackageCategory", "Choose one ...")
#Html.ValidationMessageFor(model => model.Id_ProductPackageCategory) <a href="../ProductPackageCategory/Create">
Add a new category?</a>
</div>
<div class="editor-label">
Upgrade? :
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Upgrade)
#Html.ValidationMessageFor(model => model.Upgrade)
</div>
</div>
<div class="modal-footer">
<button class="btn btn-inverse" type="submit">Save</button>
</div>
}
Any idea on what's going on?
Try this first, just above #Html.ValidationSummary() in the partial view where you have the modal head, body and footer, place:
#Html.HiddenFor(model => model.PersonId) // or.Id whatever's in your model
This creates a hidden field in your view and sets model ID i.e. PK.
i have added extra three input fields to my view to enable the system admin to submit four objects at the same time instead of one object at a time; the view looks as the following:-
#model Elearning.Models.Answer
#{
ViewBag.Title = "Create";
}
<div id = "partialWrapper">
#using (Ajax.BeginForm("Create", "Answer", new AjaxOptions
{
HttpMethod = "Post",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "incrementanswer",
OnSuccess = "removePartial",
LoadingElementId = "progress2"
}))
{
<div id = "returnedquestion">
#Html.ValidationSummary(true)
<fieldset>
<legend>Answer here</legend>
<ol>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[0].Description")
#Html.ValidationMessageFor(model => model.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div>
</li>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[1].Description")
#Html.ValidationMessageFor(model => model.Description)
</div> <div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div> </li>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[2].Description")
#Html.ValidationMessageFor(model => model.Description)
</div> <div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div> </li>
<li> <div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.TextBox("answer[3].Description")
#Html.ValidationMessageFor(model => model.Description)
</div> <div class="editor-label">
#Html.LabelFor(model => model.IsRight)
</div>
<div class="editor-field">
#Html.DropDownList("IsRight", String.Empty)
#Html.ValidationMessageFor(model => model.IsRight)
</div> </li>
<ol>
</fieldset>
<input type= "hidden" name = "questionid" value = #ViewBag.questionid>
<input type= "hidden" name = "assessmentid" value = #ViewBag.assessmentid>
<input type="submit" value="Add answer" />
</div>
}
</div>
and the following Post Ation Method:-
[HttpPost]
public ActionResult Create(int questionid, ICollection<Answer> answer)
{
if (ModelState.IsValid)
{
foreach (var a in answer){
repository.AddAnswer(a);
repository.Save();
}
return PartialView("_details2",answer);
}
return View("_details2",answer);}
and last thing the _details2 partial view which contains the newly added objects:-
#model IEnumerable<Elearning.Models.Answer>
#{
ViewBag.Title = "Details";
}
#foreach (var m in Model)
{
<tr id = #m.AnswersID>
<td>
#Html.DisplayFor(modelItem => m.Description)
</td>
<td>
#*#Html.DisplayFor(modelItem => Model.Answer_Description.description)*#
#ViewBag.Answerdesription
</td>
<td>
#Ajax.ActionLink("Delete", "Delete", "Answer",
new { id = m.AnswersID },
new AjaxOptions
{
Confirm = "Are You sure You want to delete this Answer ?",
HttpMethod = "Post",
UpdateTargetId = #m.AnswersID.ToString(),
OnSuccess = "removePartial2"
})
</td>
</tr>
}
but the above is not working nethier the objects will be added nor the partial view will be returned , so how i can solve this issue???
BR
You bind your view to a single Elearning.Models.Answer object, how are you expecting to get a collection of Answers as a parameter in your Action? The default model binder will try to bind your view fields to the parameter in the Action but it won't be able to as it's a collection.
What you could try to do is to bind your View to a List<Elearning.Models.Answer> and feed it 4 empty Answer objects, then you can create a strongly typed Partial view that expects one Elearning.Models.Answer, add the Partial in a foreach and, when posting the form, expect that the default model binder does it work and fill your action method with a brand new List of Answer objects.
As an alternative, you can create a View Model object that contains the fields in your View, including those 4 description fields. You add them as Html.TextboxFor to bind each of them to a different property in the View Model. Then you can collect them in your action, provided you change it to public ActionResult Create(int questionid, ViewModelAnswer answer)
Does it make sense?
Your model should contain a list and code like this:
#for (int i=0; i < Model.FavouriteMovies.Count; i++) {
#Html.LabelFor(model => model.YourList[i].Field)
#Html.EditorFor(model => model.YourList[i].Field)
#Html.ValidationMessageFor(model => model.YourList[i].Field)
}
which will print something like:
<label for="YourList_0__Field">Field Name</label>
The Field Name field is required.
And receive the model back in your controller:
public ActionResult MyAction(MyModel model)
{
// First element?
model.YourList[0].
}