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.
Related
I am attempting to implement a Dragula Drag and Drop feature to the part of my application that allows an Admin to assign users to certain roles. The .cshtml is implemented and displaying correctly but am having trouble figuring out how to write the Ajax call so that I can pass parameters to my method in the controller that utilizes a helper class to add and remove. Can parameters be passed into the ajax $.post(url) that are derived from the location where it was dropped? Included below should be all relevant code. I also understand Viewbags aren't the best way to do this, but if i can get it functioning i will write a viewmodel and pass the data through that way instead.
.cshtml Code
there are 4 of these, one for each of the 3 assignable roles, and one for those not assigned to any role. They're populating correctly currently.
<div class="col-3 h-100">
<div class="bg-secondary p-4">
<h6 class="card-title">Developers</h6>
<div id="dragula-developers" class="py-2">
#foreach (var user in ViewBag.Developers)
{
<div class="card rounded mb-2">
<div class="card-body p-3">
<div class="media">
<div class="media-body">
<h6 class="mb-1">#user.FirstName #user.LastName</h6>
<p class="mb-0 text-muted"> #user.Email </p>
</div>
</div>
</div>
</div>
}
Controller Method
public JsonResult ManageRoles(string id, string role)
{
var message = "User Not Assigned To Role";
if (id != null)
{
userRolesHelper.RemoveUserFromRole(id, role);
if (!string.IsNullOrEmpty(role))
{
userRolesHelper.AddUserToRole(id, role);
message = "User Assigned To Role Successfully!";
}
}
return Json(message);
}
and finally the bare bones of my dragula script where my ajax should go i believe, after the .on('drop').
(function ($) {
'use strict';
dragula([document.getElementById("dragula-noRole"), document.getElementById("dragula-submitters"),
document.getElementById("dragula-developers"), document.getElementById("dragula-managers")])
.on('drop', function (el) {
console.log($(el));
})
})(jQuery);
There are 4 boxes, and ideally when a user's card is dropped into a dragula container, their id and the role associated with the dragula would be shot to the post and their role would be changed, returning a message. This isn't a feature that would be used heavily, so i shouldnt need websockets, but i can't figure out how to implement it. Any help would be greatly appreciated!
After much longer than I care to admit, I've finally figured it out. Just in case it helps anyone else in the future, I had to change my controller to accept an Array. It ended up looking like this before Abstraction.
[HttpPost]
public JsonResult ReassignToDev(Array userIds)
{
var message = "";
if (userIds == null)
{
message = "No Users to Change Roles Of";
}
if (userIds != null)
{
foreach (var user in userIds)
{
userRolesHelper.RemoveAllRoles(user.ToString());
userRolesHelper.AddUserToRole(user.ToString(), "Developer");
message = "Role Change Successful";
}
}
return Json(message);
}
and my ajax call finally wound up looking like this:
var developerIds = new Array();
$('.developer .hidden').each(function (i) {
developerIds.push($(this).text());
})
console.log(developerIds);
$.ajax({
type: "POST",
url: '#Url.Action("ReassignToDev", "Admin")',
dataType: 'JSON',
data: { userIds: developerIds },
success: function (response) {
alert(message);
},
error: function (response) {
alert(message);
}
});
4 different methods and 4 different calls, submitted through a button click that fires an ajax request. not the most elegant solution i'm sure, but it is functional and fast.
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);
}
iv read several posts about ajax calls and im still confused.
My HomeControler got methods
public async Task<ActionResult> Index(string srchterm)
public async Task Publish(TrendVM trendVm)
I want to call Publish it from index.cshtml
my view is like this
#model IEnumerable<Trend>
<div class="container-fluid post-container">
#if (Model != null)
{
foreach (var trend in #Model)
{
Html.RenderPartial("_Trend",trend);
//button that calls Publish and passes it trend without refreshing the page.
}
}
</div>
is the some razer helper that will generate the request?
Recommended approach
If you have a unique record id for each trend item you are printing, you should use that id to pass it back to your server via ajx.
foreach (var trend in #Model)
{
Html.RenderPartial("_Trend",trend);
#Html.ActionLink("Publish","Publish","Home",new { id=trend.Id},
new { #class="publishLink"})
}
Basically, the above code will render an anchor tag like this for each trend item
Publish
where 450 will be replaced with the actual unique Id you have for trend item. Clicking on the link will open the url in a new page usually. I don't think you want that to happen here. So we will override the default click behaviour and make an ajax call to server.
Add this script to your page
#section Scripts
{
<script>
$(function(){
$("a.publishLink").click(function(e){
e.preventDefault();
var url=$(this).attr("href");
$.post(url,function(response){
alert("Publish completed");
});
});
});
</script>
}
Now we need to make sure our publish method accepts an id and do the processing. So change the Publish method to /Create a new method (and use that method name in our earlier markup in Html.ActionLink call)
public async Task Publish(int id)
{
// using the Id value, do some processing.
}
But if you do not want to change your Publish method signature, what you should be doing is creating a form inside your foreach loop and serialize the form and send it. You need to keep the data you want to send in input form fields. We will keep those in hidden fields for now.
foreach (var trend in #Model)
{
Html.RenderPartial("_Trend",trend);
using(Html.BeginForm("Publish","Home"))
{
#Html.HiddenFor(s=>s.Name)
#Html.HiddenFor(s=>s.TrendCode)
#Html.ActionLink("Publish","Publish","Home",new { id=trend.Id},
new { #class="publishLink"})
}
}
Assuming Name and TrendCode are 2 properties of your TrendVM.
and the javascript will be
#section Scripts
{
<script>
$(function(){
$("a.publishLink").click(function(e){
e.preventDefault();
var _f=$(this).closest("form");
$.post(_f.attr("action"),_f.serialize(),function(response){
alert("Publish completed");
});
});
});
</script>
}
You should write some js code. And use $.ajax() function. Put a button on your View:
<button id="your-submit-button" type="submit">Ajax call</button>
Put empty div somewhere on page where you will put your PartialView:
<div id="your-partial-view-container"></div>
Then put some jquery (you also can use plain old js, but it's easier with jquery) on your page. It's better to put all your js code in #section script {} that defined in your _Layout:
$(document).ready(function() {
$("#your-submit-button").click(function(){
$.ajax({
url: #Url.Action("Publish","Home"), //here you put your controller adress
type: "POST",
dataType: 'html',
data: $("#your-form-with-model-data-id").serialize(), //that's how you get data from your form to send your TrendVM to controller
success: function(data) {
$("#your-partial-view-container").html(data);
}
});
});
});
Now when you click on button your js code should be call controller and response will be added inside your div.
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.
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);
}