I would like to confirm if this limitation is by design or if I'm doing something wrong:
I have a View with two RenderPartials:
#model Heelp.ViewModels.CompanyIndexViewModel
#{ Html.RenderPartial(MVC.Company.Views.IndexSearch, Model.SearchViewModel); }
#{ Html.RenderPartial(MVC.Company.Views.IndexMap, Model.MapViewModel); }
In the first Partial View I have an Ajax.BeginForm:
#model Heelp.ViewModels.CompanyIndexSearchViewModel
#using (Ajax.BeginForm(MVC.Company.CategoryGetAllBySearch(), new AjaxOptions { UpdateTargetId = "searchCompanyResults", InsertionMode = InsertionMode.Replace }, new { #id = "searchBoxWrap" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.IsCenterFromUser)
#Html.HiddenFor(m => m.CenterLat)
#Html.HiddenFor(m => m.CenterLng)
#Html.HiddenFor(m => m.Zoom)
#Html.HiddenFor(m => m.SearchRadius)
#Html.TextBoxFor(m => m.Search, new { #placeholder = #HeelpResources.CompanyIndexViewSearchPlaceholder })
<input type="button" value="«" id="clearKeywords"/>
#Html.TextBoxFor(m => m.Location, new { #placeholder = #HeelpResources.CompanyIndexViewLocationPlaceholder })
<input type="button" value="«" id="clearLocation"/>
<input type="button" value="X" id="hereButton"/>
<input type="submit" value="#HeelpResources.CompanyIndexViewSearchButtonLabel"/>
}
<div id="searchCompanyResults" class="clearfix" style="z-index: 10; position: absolute; width: 400px;"></div>
The Ajax.BeginForm generates a PartialView in the searchCompanyResults div with a list of Ajax.ActionLink's:
#model Heelp.ViewModels.CategoryGetAllBySearchListViewModel
<p class="float-left margin-top align-left"><span>Encontrámos <em>#Model.TotalSearchCount</em> resultados nas categorias:</span></p>
<div class="clear-both">
<div id="searchResultsList" class="float-left">
<ul>
#foreach (var item in Model.CategoryGetAllBySearch)
{
<li>
#Ajax.ActionLink(
String.Format("{0} {1} ver »", item.SearchCount, item.Name),
MVC.Company.GetAllByCategory(item.Id, Model.Search, Model.Location, Model.IsCenterFromUser, Model.CenterLat, Model.CenterLng, Model.SearchRadius),
new AjaxOptions { OnBegin = "CompanyGetAllByCategoryOnBegin(" + item.Id + ")", OnSuccess = "CompanyGetAllByCategoryOnSuccess" })
</li>
}
</ul>
</div>
</div>
The problem here is that, if I don't include a link to "< script src="~/Scripts/jquery.unobtrusive-ajax.min.js" >" in the PartialView the Action.Link returns the Json text.
EDIT: One I detected is that when I click the Action.Link, the submit is made 2 times the first time, and 4 the second, and on and on growing, why?
Do I have to do this?
If you want use Ajax.BeginForm, Ajax.ActionLink and others from Ajax you should include jquery.unobtrusive-ajax.js file in your layout. It contains code that intercept click on link and submit of the form by cancel action and make it over AJAX.
You don't need include that file in partial views twice.
Related
I have a page to add outcomes to a areas on a form. I utilise Editor templates to group the outcomes into areas and allow the outcome to be selected from a drop down list. I'd like to show an optional comments box for two of the 3 possible outcomes.
The main page is the following:
#model DBS.ViewModels.OutcomeQuestionnaireVM
#{
ViewBag.Title = "Outcomes";
}
<h2>Add Outcomes</h2>
#if (Model.Error == true)
{
<h3 class="danger">You MUST select an outcome for at least 1 area.</h3>
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.HiddenFor(x => x.DebriefId)
<hr />
#Html.EditorFor(m => m.Groups, new { outcomes = Model.Outcomes })
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Next" class="btn btn-default" />
</div>
</div>
</div>
}
The group editor template is:
#model DBS.ViewModels.OutcomeQuestionGroupVM
#{
Layout = null;
}
<h4>#Html.DisplayFor(m => m.Name)</h4>
#Html.EditorFor(m => m.Questions, new { outcomes = ViewData["outcomes"] })
The final editor template for the outcome is:
#model DBS.ViewModels.OutcomeQuestionVM
#{
Layout = null;
}
<div class="form-group">
<div class="row">
<div class="col-md-4">
#Html.DisplayFor(m => m.Question)
</div>
<div class="col-md-4">
#Html.HiddenFor(m => m.ID)
#Html.DropDownListFor(m => m.OutcomeId, (SelectList)ViewData["outcomes"], "Please Select if applicable", new { #class = "form-control", #id = "OutcomeId" })
#Html.ValidationMessageFor(m => m.OutcomeId, "", new { #class = "text-danger" })
</div>
<div class="col-md-4" id="Comments">
#Html.HiddenFor(m => m.Comments)
#Html.TextAreaFor(model => Model.Comments, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Comments, "", new { #class = "text- danger" })
</div>
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(document).ready(function ()
{
document.getElementById('Comments').hide;
});
$(function () {
$(".DropChange").change(function () {
var valone = $('#OutcomeId').val();
if (valone == 1 || valone == 2)
{
$('#Comments').show();
}
else if (valone == 3)
{
$('#Comments').hide();
}
else
{
$('#Comments').hide();
}
});
});
</script>
}
However javascript doesn't do anything.
Sections are not supported in partial views, which is fortunate in your case or you would be adding multiple inline copies of each script in the jqueryval bundle as well as your own script (one each time the template is added).
Your also generating invalid html because of the duplicate id attributes generated by new { id = "OutcomeId" } and <div class="col-md-4" id="Comments"> which means the script would never have worked anyway.
You are also generating a hidden input for Comments before the textarea for the same property, which means that when the form is submitted, the value of Comments will be the initial value (as generated by #Html.HiddenFor(m => m.Comments)) and the value of the <textarea> would be ignored.
Its the responsibility of the view to include the scripts, not the partial, so move the scripts to the main view (or layout) and use class names and relative selectors.
Your html in the template should be
<div class="row">
<div class="col-md-4">
#Html.DisplayFor(m => m.Question)
</div>
<div class="col-md-4">
#Html.HiddenFor(m => m.ID)
// add class name to handle the .change() event
#Html.DropDownListFor(m => m.OutcomeId, (SelectList)ViewData["outcomes"], "Please Select if applicable", new { #class = "form-control outcome" })
#Html.ValidationMessageFor(m => m.OutcomeId, "", new { #class = "text-danger" })
</div>
<div class="col-md-4" class="comments"> // use class name
#Html.TextAreaFor(model => Model.Comments, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Comments, "", new { #class = "text- danger" })
</div>
</div>
then include css to initially hide all comments
.comments {
display: none;
}
and the script in the main view
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(".outcome").change(function () {
var valone = $(this).val();
// get the associated comment
var comment = $(this).closest('.row').find('.comments');
if (valone == 1 || valone == 2) {
comment.show();
} else {
comment.hide();
}
});
</script>
}
Note it was not clear what $(".DropChange") was referring to but I assume its the dropdownlist in your template (which I gave class="outcome")
I am loading a partial view depeding on dropdownlist selection just like how it is described here: Load partial view depending on dropdown selection in MVC3. My code looks like this:
View:
#....ParentModel
#using (Html.BeginForm("Method", "Controller", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div id="info">
<div id="info-selection">
#Html.LabelFor(m => m.SavedInfo)
#Html.DropDownListFor(m => m.SavedInfo , infoListItems, new { #id = "info-dropdown" })
</div>
<div id="info-describtion">
#Html.EditorFor(m => m.ChildModel)
</div>
</div>
<input type="submit" id="confirm_btn" value="Go!" />
}
<script>
$('#info-dropdown').change(function () {
var id = $(this).val();
$.get('/Cart/SelectInfo/' + id, function (data) {
$('#info-describtion').html(data);
//$('#info-describtion').fadeIn('fast');
});
});
</script>
PartialView:
#model ...Info
#{
List<SelectListItem> listMetroItems = new List<SelectListItem>();
listMetroItems.Add(new SelectListItem
{
Text = "no metro",
Value = "NoMetroAvailable",
Selected = true
});
}
<ul id="info-left">
<li>
#Html.LabelFor(m => m.Undegraund)
#Html.DropDownListFor(m => m.Undegraund, listMetroItems)
</li>
<li>
#Html.LabelFor(m => m.House)
#Html.TextBoxFor(m => m.House)
</li>
<li>
#Html.LabelFor(m => m.Flat)
#Html.TextBoxFor(m => m.Flat)
</li>
<li>
#Html.LabelFor(m => m.Floor)
#Html.TextBoxFor(m => m.Floor)
</li>
</ul>
<ul id="info-right">
<li>
#Html.LabelFor(m => m.Street)
#Html.TextBoxFor(m => m.Street)
</li>
<li>
#Html.LabelFor(m => m.Building)
#Html.TextBoxFor(m => m.Building)
</li>
<li>
#Html.LabelFor(m => m.Porch)
#Html.TextBoxFor(m => m.Porch)
</li>
<li>
#Html.LabelFor(m => m.IntercomCode)
#Html.TextBoxFor(m => m.IntercomCode)
</li>
</ul>
Controller:
public ActionResult SelectInfo(int id)
{
Info partialViewModel = new Info(id);
return PartialView("~/Views/Shared/EditorTemplates/InfoModel.cshtml", partialViewModel);
}
One thing I can't get to work properly: the partial view I load as you can see is the part of the form. The form content updates on dropdown change perfectly. But when I click the submit button of the form it posts the obsolete data not the one replaced with SelectInfo() method! I think its due to the fact that the ParentModel of the whole view doesnt get updated when partial view updates. What are the choices to resolve this?
This is what I believe is happening. First, before selecting any drop down, load your page and view the source. Note the Name property of your different inputs. They will be something like
<input class="text-box single-line" id="Info_House" name="Info.House" type="text" value="">
Note the name. Info.House. The name is what drives model binding in MVC ASP.net. Now note that your partial view takes an Info model directly. Not a parent model. So once you select a drop down item and your partial view populates you now have inputs like so :
<input id="House" name="House" type="text" value="House">
Now note the new name. The html produced is attempting to bind DIRECTLY to a property called "House". So it assumes your model has something like Model.House. But your Parent model does not. It has Model.Info.House
So what you need to do is the following. In your controller
Controller
public ActionResult SelectInfo(int id)
{
Info info = new Info(id);
var parentModel = new ParentModel { Info = info};
return PartialView("~/Views/Shared/EditorTemplates/InfoModel.cshtml", parentModel);
}
Now in your partial view you would have
Partial View (note the partial view now takes the ParentModel. Not the child model.
#model MvcApplication1.Models.ParentModel
#{
List<SelectListItem> listMetroItems = new List<SelectListItem>();
listMetroItems.Add(new SelectListItem
{
Text = "no metro",
Value = "NoMetroAvailable",
Selected = true
});
}
<ul id="info-left">
<li>
#Html.LabelFor(m => m.Info.Undergraund)
#Html.DropDownListFor(m => m.Info.Undergraund, listMetroItems)
</li>
<li>
#Html.LabelFor(m => m.Info.House)
#Html.TextBoxFor(m => m.Info.House)
</li>
<li>
#Html.LabelFor(m => m.Info.Flat)
#Html.TextBoxFor(m => m.Info.Flat)
</li>
<li>
#Html.LabelFor(m => m.Info.Floor)
#Html.TextBoxFor(m => m.Info.Floor)
</li>
</ul>
<ul id="info-right">
<li>
#Html.LabelFor(m => m.Info.Street)
#Html.TextBoxFor(m => m.Info.Street)
</li>
<li>
#Html.LabelFor(m => m.Info.Building)
#Html.TextBoxFor(m => m.Info.Building)
</li>
<li>
#Html.LabelFor(m => m.Info.Porch)
#Html.TextBoxFor(m => m.Info.Porch)
</li>
<li>
#Html.LabelFor(m => m.Info.IntercomCode)
#Html.TextBoxFor(m => m.Info.IntercomCode)
</li>
</ul>
I can't seem to get a jQuery AJAX post to update a div with the returned partial view. The partial view is returned with the correct html and displays in its entirety within the browser but I cannot get it to display in the div I would like it to. I'm sure it is something rather simple but I can't seem to figure it out after numerous hours of trying to solve this.
Ultimately what I am trying to achieve is when the form is posted that the results are display in another div and I would then, somehow, update another div with another partial view via another ajax call. I have set this test up to get familiar with how things work but I am struggling to get this right.
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="id,Name")] Test test)
{
if (ModelState.IsValid)
{
db.Test.Add(test);
db.SaveChanges();
return PartialView("DetailsPartial", test);
}
return PartialView("CreatePartial");
}
Main page:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
<button id="Create">Create</button>
</p>
<div id="CreatePlaceholder">
</div>
<div id="DetailsPlaceholder">
</div>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
<script>
$(document).ready(function () {
$('#Create').click(function (event) {
event.preventDefault();
$.ajax({
type: 'get',
url: '/Test/Create'
}).done(function (data) {
$('#CreatePlaceholder').html(data)
})
})
$('#CreateForm').submit(function (event) {
event.preventDefault();
var $form = $(this);
var formData = $form.serialize;
alert(formData);
formData.__RequestVerificationToken = $('input[name=__RequestVerificationToken]').val();
$.ajax({
type: 'POST',
url: '/Test/Create',
data: formData,
success: function (result) {
$('#DetailsPlaceholder').html(result);
}
})
});
})
</script>
Create partial view:
#model PerformanceTools.Models.Test
<form id="CreateForm" method="post" action="#Url.Action("Create","Test")">
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Test</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
Details partial view:
#model PerformanceTools.Models.Test
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.id)
</dt>
<dd>
#Html.DisplayFor(model => model.id)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
#Html.DisplayFor(model => model.Name)
</dd>
</dl>
However you are rendering your partial view put it inside a div.
<div id="partialSummaryDiv">#{Html.RenderPartial("YourPartial");}</div>
On the success of the Ajax call call .html() on the div not on the partial view.
success: function (data) {
$('#partialSummaryDiv).html(data);
}
It is not displaying in the Div because you did not define the partial view in the div. If you want the partial view hidden until the ajax success comes through, you will need to add additional logic
The problem was that the event was never being hooked up to the forms submit due to it being added to the page after the DOM was ready.
I was able to fix the issue by using .on on the empty placeholder div.
$('#CreatePlaceholder').on('submit', '#CreateForm', function (event) {
event.preventDefault();
var formData = $(this).serialize();
formData.__RequestVerificationToken = $('input[name=__RequestVerificationToken]').val();
$.ajax({
type: 'POST',
url: '/Test/Create',
data: formData
}).done(function (data) {
$('#DetailsPlaceholder').html(data);
})
});
I have the following:
_ImageGalleryPartial
#if (Model != null)
{
foreach (var item in Model)
{
#Html.Partial("_ImageItem", item);
}
}
_ImageItemPartial
#model WebsiteEngine.Models.PortfolioImage
#{
string url = VirtualPathUtility.ToAbsolute(String.Format("~/{0}", Html.DisplayFor(m => m.FileName)));
}
#using (Html.BeginForm("DeleteImage", "Portfolio", FormMethod.Post, new { #class = "deleteImageForm" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true);
#Html.HiddenFor(m => m.Name)
#Html.HiddenFor(m => m.FileName)
#Html.HiddenFor(m => m.Order)
#Html.HiddenFor(m => m.PortfolioID)
#Html.HiddenFor(m => m.PortfolioImageID)
<div class="span3">
<div class="item">
<a class="fancybox-button" data-rel="fancybox-button" title="Photo" href="#url">
<div class="zoom">
<img src="#url" alt="Photo" />
<div class="zoom-icon"></div>
</div>
</a>
<div class="details">
<i class="icon-remove"></i>
</div>
</div>
</div>
}
When I inspect the page elements using Chrome dev tools, the first element is missing the surrounding form. If I inspect the page source (using right) click then the form is there. This lead me to believe some JS was removing the form so I disabled JS but it was still missing.
If change _ImageGalleryPartial so it's like this (notice the addition of an empty model):
#if (Model != null)
{
#Html.Partial("_ImageItem", new WebsiteEngine.Models.PortfolioImage());
foreach (var item in Model)
{
#Html.Partial("_ImageItem", item);
}
}
Then all the "correct" elements get a form but again, this first item doesn't.
I'm still inclined to think this is a JS issue but I'm a little stumped as disabling JS doesn't fix it. Is this some off behaviour with MVC?
Just to note, I have simplified my layout above, I do actually have one or 2 nested forms but assume that's Ok as everything else is Ok, it's just this first partial that's broken.
Any ideas?
Html forms can't be nested.
Chrome will ignore illegal tags, thus they are not showing.
You can read this post for further information.
I'm having trouble with a new MVC4 site I'm working on. I can't seem to get the javascript to run at all. It doesn't throw an error, it just does nothing.
What am I missing?
Here's my page code:
#model IEnumerable<WiseGalleriesEntities.Medium>
#{
ViewBag.Title = "ManageMedium";
}
<div class="title">
<h1>Mediums...</h1>
</div>
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#using (Ajax.BeginForm("Create", "Medium", new AjaxOptions() { UpdateTargetId = "MediumListDiv" }, new { id = "frmAddMedium" }))
{
<div class="span12">
<div>
<div class="editor-field">
#Html.TextBox("txtDescription")
</div>
</div>
Add Medium
</div>
}
<div id="MediumListDiv">
#Html.Partial("Medium.List", Model)
</div>
Instead of using the href attribute, use the onclick attribute.
<a onclick="$('#frmAddMedium').submit();" class="btn-primary btn-small">Add Medium</a>