I read a lot of questions here on stacoverflow, but it is still not clear to me how should I use a partialview within a main view with an action method. What's probably wrong is my aproach in general. With what I have so far I am not sure how to continue with my code.
I will start with the main view :
#{
ViewBag.Title = "getRolesByYear";
}
</script>
<script type="text/javascript">
getRolesForYear(parseInt(#DateTime.Now.Year));
$(function () {
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
//console.log(year);
function getRolesForYear(year) {
console.log(year);
$.ajax({
type: "POST",
url: '#Url.Action("getRolesByYear", "WorkRoles")',
dataType: "json",
data: {
year: year
},
success: successFunc,
error: errorFunc
});
function successFunc(data, status) {
console.log('x');
}
}
function errorFunc() {
alert('error');
}
}
</script>
<div id = "years" class="btn-group btn-group-justified timeline">
#DateTime.Now.Year
#DateTime.Now.AddYears(-1).Year
#DateTime.Now.AddYears(-2).Year
</div>
<div id"partial"></div>
In this view I have three buttons with different year for each button. On page load or on button click I make an ajax call to an action method with the an int year as a parameter.
This is a simplified version of my action method :
public ActionResult getRolesByYear(int year)
{
// a couple of queries here
var list = list of RoleViewModel objects;
return PartialView(list);
And here is the partialView :
#model IEnumerable<eksp.Models.RoleViewModel>
#foreach (var item in Model)
{
<div class="jumbotron">
<h2>item.Role.RoleName</h2>
<h1> item.Role.RoleDescription</h1>
<p class="lead">Focus start : item.Role.FocusStart</p>
<p>Focus end : item.Role.FocusStart </p>
</div>
}
Obviously, a lot of thins aren't clear to me. How can I use this partial view with the action method i have and the main view? Do I need a separate method for the partial view? Any tips?
Your ajax call will invoke the action method which returns the partial view result (markup generated by executing the partial view). I guess you simply need to use the response of the ajax call to update your DOM.
If you want to update the content of the div with id partial with the response, you can do that in the success event handler of your ajax call.
success : function(response)
{
$("#partial").html(response);
}
I would also recommend to call the getRolesForYear method on document ready event.
$(function () {
getRolesForYear(parseInt(#DateTime.Now.Year));
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
Also, If your main view is also the result of action method getRolesByYear, you probably want to return the partial view result only on the ajax call, the other calls,you can return the partial view
public ActionResult getRolesByYear(int year)
{
var list = list of RoleViewModel objects;
if(Request.IsAjaxRequest())
{
return PartialView(list);
}
else
{
return View(list);
}
}
Here the same action method handles the request for main view and ajax call. It returns the same view for ajax call, but using PartialView call, so layout will be ignored. But If you have a specific view you want to return, you can do that as well.
if(Request.IsAjaxRequest())
{
return PartialView("_yearlyRoles",list);
}
One of the reasons I like using partial views for rendering data via Ajax calls. For example if I want to start searching in order to avoid the server call i Just use an ajax call to the controller which returns the search results through a partial view. In your example yoy need to load the results in partial div.
function successFunc(data, status) {
$("#partial").html(data);
}
Related
I have a question regarding the calling method from view.
Basically on my view I have 2 links:
1 link : When I click on it, some method should be called and executed, but nothing should change on webpage, so no postback.
2 link: When I click on it, some method should happen and postback can happen, on the same page
In controller I have:
public ActionResult FirstMethod(){ return View();}
public ActionResult SecondMethod(){ return View();}
In view:
#Html.ActionLink("Action 1", "FirstMethod", "Controller");
#Html.ActionLink("Action 2", "SecondMethod", "Controller");
So when I click on both action happens but then i get an error saying cannot find FirstMethod.chtml ..
So is this possible to have one method with postback and another one without? And how to return to the same page ... and not try to get FirstMethod.chtml ..
Following solution is based on AJAX -
Controller -
public class DemoController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult CallMe()
{
return new ContentResult() { Content = "This is Demo " };
}
}
Index.cshtml -
<h2>Index</h2>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
$(function () {
$("#Click").click(function () {
$.ajax({
url: "/Demo/CallMe",
type: "GET",
error: function (response) {
alert(response);
},
success: function (response) {
alert(response);
}
});
});
})
</script>
<input type="button" value="Click" id="Click" />
First navigate to /demo/Index, that will display the page with above markup with a button in the page. And when we click on the Click button, we have -
The #Html.ActionLink method basically just forwards you to the specified controller-action, you cannot change this, since this is the purpose of the method.
You have to handle the click client-side, and bind a specific action to it (post some data to a url, and do nothing afterwards). One fairly easy way to do this, is to use jQuery.Post
Example from the above jquery link.
Example: Request the test.php page, but ignore the return results.
$.post("test.php");
Actually, there is no postback concept in asp.net mvc. all interactions with server should via the controller/action.
#Html.ActionLink() method just generate a link(tag a in html) and do nothing. everything happens after you send a request(such as click the link) to controller/action, if you want do nothing when click the link, you'd better use AJAX method like this
#Html.ActionLink("Action 1", "FirstMethod", "Controller", null/*routeValues*/, new { id = "link1Id" });
<script type="text/javascript">
$(function () {
$("#link1Id").click(function () {
$.get("/Contoller/FirstMethod", function () {
//do nothing or alert(something)
});
return false;
});
})
</script>
You can simply return another view after you've done what you wanted in your controller action:
public ActionResult SecondMethod()
{
//do something
return View("FirstMethod");
}
After you've seen this you will most probably be disgusted by the use of magic strings to reference views or controllers and that disgust is completely understandable :)
Then you should look whether something like T4MVC could fit your needs.
View:
#if (Model.IsEventActive)
{
<div id="GameEvent">
//SomeCode
</div>
}
else
{
<div id="GameNonEvent">
//SomeCode
</div>
}
JS file:
$('#btnNextEvent').click(function () {
$.ajax({
type: "POST",
url: "Game/UpdateUserEventInfo"
//success: function () {
// $("#GameEvent").hide();
//}
});
});
Controller:
[HttpPost]
public ActionResult UpdateUserEventInfo()
{
var user = _user;
_instance.Users.UpdateUserEventInfo(user);
return RedirectToAction("Index");
}
public ActionResult Index()
{
if (!_instance.Users.CheckUserSkillsExist(WebSecurity.CurrentUserName))
{
return RedirectToAction("CreateChar");
}
_instance.GameBase.GetBaseData();
var userModel = GetPlayerDisplayStats();
return View(userModel);
}
If my beginning IsEventActive = true;
The FullEvent View at a certain moment calls my JS method which triggers the ajax call to UpdateUserEventInfo.
So, basically what is supposed to happen is when the Controller method UpdateUserEventInfo is fired, it updates the DB and then calls my index view again. Index view rebuilds the model and launches the view.
The view checks for IsEventActive and builds the divs based on that.
The page shows the GameEvent Div in the beginning because IsEventActive is true, but when the index view rebuilds again via the ajax call. The if-else loops follows correctly and goes to the GameNonEvent div and creates it. But I do not see that on the page. The page still shows GamEvent Div. Even though the view didn't go into the if statement.
If i refresh the page, then it shows correctly.
Wrap the part of the view that you showed in another container:
#if (Model.IsEventActive)
{
<div id="container">
<div id="GameEvent">
<div class="col-lg-10 col-lg-offset-1">
<div class="row">
#{ Html.RenderAction("FullEvent", "Game", new { ActiveEventGrpId = Model.ActiveEventGrpId }); }
</div>
</div>
</div>
</div>
}
else
{
...
}
Then, in success handler of ajax call do:
success: function (data) {
$("#container").html(data);
}
if i get your code right it looks like you are not doing anything with result of ajax call to "Game/UpdateUserEventInfo",
so nothing will happen. Where you have "success" commented out you should have code that updates view client side. Which could be a lot of code, it is possible that ajax call may not be even needed there, href to action will do just fine from functional point. The simple way
to fix this (since you say page refresh works) would be to put a page reload on ajax success :
$.ajax({
type: "POST",
url: "Game/UpdateUserEventInfo"
success: function () {
window.location.reload();
}
});
but this will also successfully cancel the whole point of using ajax, you may need review your approach for this.
Ey!
How I could refresh a Partial View with data out of the Model?
First time, when the page loads it's working properly, but not when I call it from the Action.
The structure I've created looks like:
Anywhere in my View:
#{ Html.RenderAction("UpdatePoints");}
My PartialView "UpdatePoints":
<h3>Your points are #ViewBag.points </h3>
At the Controller I have:
public ActionResult UpdatePoints()
{
ViewBag.points = _Repository.Points;
return PartialView("UpdatePoints");
}
Thanks for your help!
UPDATE
Thanks all for your help! Finally I used JQuery/AJAX as you suggested, passing the parameter using model.
So, in JS:
$('#divPoints').load('/Schedule/UpdatePoints', UpdatePointsAction);
var points= $('#newpoints').val();
$element.find('PointsDiv').html("You have" + points+ " points");
In Controller:
var model = _newPoints;
return PartialView(model);
In View
<div id="divPoints"></div>
#Html.Hidden("newpoints", Model)
So, say you have your View with PartialView, which have to be updated by button click:
<div class="target">
#{ Html.RenderAction("UpdatePoints");}
</div>
<input class="button" value="update" />
There are some ways to do it. For example you may use jQuery:
<script type="text/javascript">
$(function(){
$('.button').on("click", function(){
$.post('#Url.Action("PostActionToUpdatePoints", "Home")').always(function(){
$('.target').load('/Home/UpdatePoints');
})
});
});
</script>
PostActionToUpdatePoints is your Action with [HttpPost] attribute, which you use to update points
If you use logic in your action UpdatePoints() to update points, maybe you forgot to add [HttpPost] attribute to it:
[HttpPost]
public ActionResult UpdatePoints()
{
ViewBag.points = _Repository.Points;
return PartialView("UpdatePoints");
}
You can also try this.
$(document).ready(function () {
var url = "#(Html.Raw(Url.Action("ActionName", "ControllerName")))";
$("#PartialViewDivId").load(url);
setInterval(function () {
var url = "#(Html.Raw(Url.Action("ActionName", "ControllerName")))";
$("#PartialViewDivId").load(url);
}, 30000); //Refreshes every 30 seconds
$.ajaxSetup({ cache: false }); //Turn off caching
});
It makes an initial call to load the div, and then subsequent calls are on a 30 second interval.
In the controller section you can update the object and pass the object to the partial view.
public class ControllerName: Controller
{
public ActionResult ActionName()
{
.
. // code for update object
.
return PartialView("PartialViewName", updatedObject);
}
}
Thanks all for your help!
Finally I used JQuery/AJAX as you suggested, passing the parameter using model.
So, in JS:
$('#divPoints').load('/Schedule/UpdatePoints', UpdatePointsAction);
var points= $('#newpoints').val();
$element.find('PointsDiv').html("You have" + points+ " points");
In Controller:
var model = _newPoints;
return PartialView(model);
In View
<div id="divPoints"></div>
#Html.Hidden("newpoints", Model)
Controller :
public ActionResult Refresh(string ID)
{
DetailsViewModel vm = new DetailsViewModel(); // Model
vm.productDetails = _product.GetproductDetails(ID);
/* "productDetails " is a property in "DetailsViewModel"
"GetProductDetails" is a method in "Product" class
"_product" is an interface of "Product" class */
return PartialView("_Details", vm); // Details is a partial view
}
In yore index page you should to have refresh link :
Refresh
This Script should be also in your index page:
<script type="text/javascript">
$(function () {
$('a[id=refreshItem]:last').click(function (e) {
e.preventDefault();
var url = MVC.Url.action('Refresh', 'MyController', { itemId: '#(Model.itemProp.itemId )' }); // Refresh is an Action in controller, MyController is a controller name
$.ajax({
type: 'GET',
url: url,
cache: false,
success: function (grid) {
$('#tabItemDetails').html(grid);
clientBehaviors.applyPlugins($("#tabProductDetails")); // "tabProductDetails" is an id of div in your "Details partial view"
}
});
});
});
I reallly have a simple set of code to bring back a set of data that is triggered off a drop down.
this is the script:
function () {
$('#ProviderID').change(function () {
$.ajax({
url: '/servicesDisplay/Index',
type: 'Get',
data: { id: $(this).attr('value') },
success: function (result) {
// The AJAX request succeeded and the result variable
// will contain the partial HTML returned by the action
// we inject it into the div:
$('#serLocations').html(result);
}
});
});
This is the controller:
public ActionResult Index(string id)
{
int prid = Int32.Parse(id.Substring(0, (id.Length-1)));
string mulitval = id.Substring((id.Length-1), 1).ToString();
System.Data.Objects.ObjectResult<getProviderServiceAddress_Result> proList = theEntities.getProviderServiceAddress(prid);
List<getProviderServiceAddress_Result> objList = proList.ToList();
SelectList providerList = new SelectList(objList, "AddressID","Address1");
//ViewBag.providerList = providerList;
return PartialView("servicesDisplay/Index", providerList);
}
This is the view:
#model OCS_CS.Models.servicesDisplay
<div>
#Html.DropDownList(model => model.ServiceAdderssID, (IEnumerable<SelectListItem>)model)
</div>
When the drop down passes the in the value. The apps does hit the controller. But it highlightes the drop down in a light red and the view never displays.
Try this short version which uses the jquery load method.
$(function(){
$('#ProviderID').change(function () {
$('#serLocations').load("#Url.Action("Index","ServicesDisplay")?id="
+$(this).val());
});
});
If you want to avoid caching of result, you may send a unique timestamp along with the querystring to avoid caching.
$('#serLocations').load("#Url.Action("Index","ServicesDisplay")?id="
+$(this).val()+"&t="+$.now());
You are doing a GET, thats no meaning to pass data to ajax, you may pass data for POST:
First, put the value at the URL:
function () {
$('#ProviderID').change(function () {
$.ajax({
url: '/servicesDisplay/Index/' + $(this).attr('value'),
type: 'Get',
success: function (result) {
// The AJAX request succeeded and the result variable
// will contain the partial HTML returned by the action
// we inject it into the div:
$('#serLocations').html(result);
}
});
});
Second, mark the method as GET
[HttpGet]
public ActionResult Index(string id)
Hopes this help you!
You have quite a few problems with your code. First the model defined for your view is:
#model OCS_CS.Models.servicesDisplay
but in your action your're invoking the call to this view by passing in a SelectList:
SelectList providerList = new SelectList(objList, "AddressID","Address1");
return PartialView("servicesDisplay/Index", providerList);
this is not going to fly because the models do not match by type. Seconds problem is you are casting this SelectList into an IEnumerable. This is also not going to work. You need to cast to SelectList:
#Html.DropDownList(model => model.ServiceAdderssID, (SelectList)model)
but again until you match the type of your model in your action with the model on your view none of this will work. I suggest you install Fiddler to help you determine what sort of error are you getting.
I have a form inside a partial view, it works but in case a server side validation error is raised it displays only the partial view. So I decided to use ajax for the submission (actually it makes sense as it is inserting a contact and there's a list in the main view).
Thing is, with the posted code if one of these errors comes it's properly displayed in the view (I'd need to make the partial visible again, but that's another thing), but if there's no error it'll display the list only in the partial view. I can do the other way round, displaying properly when there's no error but then not achieving the proper displaying of validation errors.
I would like to understand what's the best approach, or at least which are the possibilities: maybe change the code in the controller or do some kind of check in the success callback...
I edit what I had before, because when no error I should return the list, not the whole view as I posted before, but anyway I still have the doubt on how to tell one from another as both are succesfull calls to the post action
Thanks
The view is this one
#model ContactListViewModel
#{
ViewBag.Title = " My Contacts"
}
<div id="ContactList">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
#{ Html.RenderAction("AddContact"); }
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
#foreach (var group in Model.Contacts)
{
<div class="PlanContacts">
<div class="PlanName">#group.Key</div>
#foreach (var contact in group.Values)
{
<div class="Preview">
#Html.DisplayFor(m => contact, "Contact")
</div>
}
</div>
}
</div>
#section PageJavascript
{
<script src="~/Scripts/AMPContacts.js"></script>
}
The controller post action
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
// Here I should return the updated list
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}
And the ajax submission code inside the longer AMPContact.js
$('#addForm').submit(function (e) {
e.preventDefault();
var addContainer = $(document.getElementById('addContainer'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
addContainer.html(result);
}
});
});
I understand this answer it's far from being good but maybe it's useful for somebody in terms of narrowing a possible search. As it would ended up being huge I rather post this update as an answer to my own question, not sure if it complies with good manners.
The explained one wasn't the only problem I faced, hard times as well with the reset of the form and specially the successive error submissions (once I got an error trying to submit it with errors again), so I ended up making a mess out of different solutions for different problems. Hopefully I will be able to clean it up
Thanks
In the view I use now
<div id="myContacts">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
<div class="toggler">
Add Contact
</div>
<div id="addToggling" class="initiallyHidden">
#{ Html.RenderAction("AddContact"); }
</div>
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
<div id="list">
#{ Html.RenderPartial("_ContactList", Model); }
</div>
In the .js
$('#addContainer').on('submit', '#addForm', ajaxCall);
function ajaxCall(e) {
e.preventDefault();
var addToggling = $(document.getElementById('addToggling'));
var contactList = $(document.getElementById('contactList'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
if (result.passedValidation == true) {
// Json is returned with flag, we get the list from the server and update the list element
$.get(result.action, function (partial) {
contactList.html(partial);
});
// Add some effects and clear the form
$(document).scrollTop(0);
setTimeout(function () {
addToggling.slideUp(300, resetAddForm);
}, 500);
setTimeout(function () {
contactList.effect("highlight", {}, 3000);
}, 1000);
}
else {
// The form partial view is returned and displayed in the same element when there are validation errors
$(document).scrollTop(0);
addToggling.html(result);
$.validator.unobtrusive.parse('#addForm');
}
}
});
}
function resetAddForm() {
var addForm = $(document.getElementById('addForm'));
// Hhide the error messages
addForm.find("span.field-validation-error").hide();
addForm.find("div.validation-summary-errors").hide();
// Removes the class associated to errors
addForm[0].reset();
// Clear the inputs
addForm.find('input:text, input:password, input:file, select, textarea').val('');
addForm.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
}
Controller with the existing action method slightly changed and a new one
public ActionResult ContactList()
{
var partyId = (int)Session["PartyId"];
var viewModel = _displayBuilder.Build(partyId);
return PartialView("_ContactList", viewModel);
}
[HttpGet]
public ActionResult AddContact()
{
var partyId = (int) Session["PartyId"];
var viewModel = _createBuilder.Build(partyId);
return PartialView("_AddContact", viewModel);
}
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
if (Request.IsAjaxRequest())
return Json(new { passedValidation = true, action = Url.Action("ContactList")});
return RedirectToAction("Index");
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}