I'm using ASP.NET MVC, and I'm trying to build a voting system, similar to stackoverflow.
I want that when I click on voteup button, to make a post on an action, make some checks there, but to remain on my initial page, and increment the vote (with js), if the checks passed (just like SO).
The items that I want to vote are populated by Index Action
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div><input type="submit" name="Vote" value="" class="fa fa-angle-up"/>
</div>
<div>#Html.DisplayFor(modelItem => item.Votes)</div>
<div><input type="submit" name="Vote" value="" class="fa fa-angle-down" /></div>
}
Action
public ActionResult SendVote(string vote)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<VoteLogViewModel, VoteLog>());
var mapper = config.CreateMapper();
switch (vote)
{
case "":
if (ModelState.IsValid)
{
//Send to db
VoteLogViewModel voteLogViewModel = new VoteLogViewModel
{
DateAdded = DateTime.Now,
Id = Guid.NewGuid().ToString(),
PlaceId = id,
UserId = User.Identity.GetUserId(),
Vote = 1
};
db.VoteLogs.Add(mapper.Map<VoteLog>(voteLogViewModel));
db.SaveChanges();
}
else
{
return RedirectToAction("Index");
}
break;
case "":
if (ModelState.IsValid)
{
//Send to db
}
else
{
return RedirectToAction("Index");
}
break;
}
return new EmptyResult();
}
How do I vote, without reloading the whole page?
Should I just make some links under my voting icons and somehow handle this with routes?
What you need to do is use Ajax
Example:
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div><input type="submit" name="Vote" value="true" class="fa fa-angle-up"/>
</div>
<div>#Html.DisplayFor(modelItem => item.Votes)</div>
<div><input type="submit" name="Vote" value="false" class="fa fa-angle-down" /></div>
}
<script>
$(function(){
$('input[name="Vote"]').click(function(e){
e.preventDefault();
var result = e.data.value;
$.ajax({
type: "POST",
url: url // ActionURL,
data: result,
success: function(data) { //Success here },
});
});
});
</script>
Controller
public ActionResult SendVote(bool vote)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<VoteLogViewModel, VoteLog>());
var mapper = config.CreateMapper();
if(!ModelState.IsValid)
{
return RedirectToAction("Index");
}
if(vote)
{
//Send to db
VoteLogViewModel voteLogViewModel = new VoteLogViewModel
{
DateAdded = DateTime.Now,
Id = Guid.NewGuid().ToString(),
PlaceId = id,
UserId = User.Identity.GetUserId(),
Vote = 1
};
db.VoteLogs.Add(mapper.Map<VoteLog>(voteLogViewModel));
db.SaveChanges();
}
else
{
//Send to db
}
return new EmptyResult();
}
Please note it might not be syntactically correct since I wrote it in outside of an IDE. But this should get you going.
I also refactored your controller to use a boolean rather than switching on a string.
You're currently performing a full postback as in essence the HTML helper in your view is rending a standard HTML form.
You will need to trigger an AJAX post to the server if you don't want the whole page to refresh. If you are already including jQuery on your page, something like the following should work:
$('form').on('submit', function(e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '#Url.Action(your action method)',
data: {
//form variables here
},
dataType: 'JSON',
contentType: 'application/json',
success: function(result) {
//handle success
},
error: function(result) {
//handle error
}
});
});
Related
What am trying to do is to post on form submit to a action and catch response and that works fine.Problem is when RedirectToAction gets called, it just stays on that same view(it doesn't redirect),or in case model is not valid, it doesn't show model validation. So i guess the problem is with url, but how can I correct it?
jQuery
$("form").on("submit", function (e) {
e.preventDefault();
var form = $(this);
var formURL = form.attr("action");
$.ajax({
type: "POST",
url: formURL,
data: $(this).serialize(),
success: function (response) {
if (response !== null && response.success == false) {
alert(response.responseText);
}
}
});
});
c# asp mvc
public ActionResult Add(SomeModel model) {
if (ModelState.IsValid) {
if (true) {
return Json(new { success = false, responseText = "Some error" }, JsonRequestBehavior.AllowGet);
}
return RedirectToAction("Details", new { id = id });
}
//gets called but it doesn't show validation.
return View(model);
}
public ActionResult Details(int id) {
//gets called but it doesn't show the view.
return view(model);
}
Because you're posting your form with an Ajax POST and your in your success function you have alert(response.responseText), you are NOT going to receive a View.
What you need to do is in success function, take the response from Details action and place it inside an HTML element on the page. Like below:
success: function (response) {
$("#div").html(response);
}
On another note, since you're not using a standard FORM and your posting with JavaScript, you wont get built in validation the models provide.
When I click the button that calls the JavaScript, it executes the script which calls the controller that inserts the record using Entity Framework. But it doesn't always reload the page even when I have the location.reload().
What happens is that it successfully submits the record but doesn't reflect in the grid (the list of records), when I submit another record again the first and 2nd inserted record will now show up. Am I doing something wrong with how I'm handling this?
PS, can someone also review my codes as I'm still quite new to web and MVC. Another question of mine is that my form is set like this: #using (Html.BeginForm("Create", "CableSystemType", FormMethod.Get)) but my submit button is declared POST in formmethod, I'm just wondering if this is good practice and if this has a bearing to my issue.
View
<input type="submit" id="btnSave" value="Save" class="btn btn-success" formmethod="post" onclick="return SaveUser();" />
Javascript
function SaveUser() {
debugger;
var json = {
usrId: $('#usrId').val(),
usrDesc: $('#usrDesc').val(),
usrStatus: $('input[name=usrStatus]:checked').val()
};
var chckUsr = document.getElementById("usrDesc").value;
var chckStat = document.querySelector('input[name="usrStatus"]:checked').value;
if (chckUsr === "") {
alert("Description is required.");
return false;
}
if (chckStat === "") {
alert("Status is required.");
return false;
}
$.ajax({
url: '/User/Save_User',
type: 'POST',
dataType: 'json',
data: JSON.stringify(json),
contentType: 'application/json; charset=utf-8',
success: function (response) {
if (response != null && response.success) {
//User is unique
alert(response.responseText);
} else {
//User already exists
alert("response.responseText);
}
//When I put alert in this area it shows up, but page doesn't refresh. I use Chrome
location.reload();
},
});
};
Controller
public JsonResult Save_User(UserViewModel model)
{
string _message = string.Empty;
using (var _odb = new DBEntities())
{
try
{
if (_odb.USR_MSTR.Any(o => o.USR_DESC == model.usrDesc))
{
return Json(new { success = false, responseText = "User already exists." }, JsonRequestBehavior.AllowGet);
}
else {
USR_MSTR usr_master = new USR_MSTR();
string id = Guid.NewGuid().ToString();
usr_master.USR_ID = Guid.NewGuid().ToString().Remove(5);
usr_master.USR_DESC = model.usrDesc;
usr_master.CREA_DT = DateTime.Now;
if (model.usrStatus == "Active")
{
usr_master.INACTV_DT = null;
}
else if (model.usrStatus == "Inactive")
{
usr_master.INACTV_DT = DateTime.Now;
}
_odb.USR_MSTR.Add(usr_master);
_odb.SaveChanges();
return Json(new { success = true, responseText = "User sucessfully saved." }, JsonRequestBehavior.AllowGet);
}
}
catch (Exception ex)
{
_message = "An error occured.";
_message = ex.InnerException.Message;
}
}
return Json(true);
}
I have a situation where I need to call ParentView from its partial view. Like I have list of ToDos in Partial view where Ajax actionlink is used to edit the data in parent by passing its id. The same is working without using Ajax as it is manipulating url by putting querystring. But we would like to have internal call with Ajax which is not firing.
The code we are using is like that:
<li>#Ajax.ActionLink(#item.ToDoTitle, "Index", new { tdid = #item.ToDoId }, new AjaxOptions { UpdateTargetId = "saved", InsertionMode = InsertionMode.Replace, HttpMethod="POST" })</li>
and controller is like that:
public ActionResult Index(int tdid =0)
{
if (tdid !=0)
{
ToDo t = new ToDo();
t.ToDoTitle = "Ramlal";
t.ToDoDesc = "Shyamlal";
t.ToDoId = tdid;
return Json(t);
}
else
{
return View();
}
}
We have tried the same with returning a view but nothing have happened, so we have tried to use Json as it returning model.
Correct me if I am wrong?
FYI, this is the parent view:
#model myTask.Models.ToDo
#{
ViewBag.Title = "My Task";
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
#using (Ajax.BeginForm("Save", new AjaxOptions() { InsertionMode = InsertionMode.Replace, UpdateTargetId = "MyTaskList" }))
{
#Html.AntiForgeryToken()
<div class="MyTask">
#Html.HiddenFor(t => t.ToDoId)
<div class="row">
<div class="col-lg-12">
#Html.LabelFor(t => t.ToDoTitle)<br />
#Html.TextBoxFor(t => t.ToDoTitle)
</div>
<div class="col-lg-12">
#Html.LabelFor(t => t.ToDoDesc)<br />
#Html.TextAreaFor(t => t.ToDoDesc)
</div>
</div>
<script>
$( "#ToDoTitle" ).blur( function() {
$.ajax({
url: "Save",
type: "POST",
data: $("#form0").serialize(),
dataType: "json"
}).done(function (model) {
$("#ToDoId").val(model.ToDoId);
$("#ToDoTitle").val(model.ToDoTitle);
$("#ToDoDesc").val(model.ToDoDesc);
});
});
$("#ToDoDesc").blur(function () {
if ($("#ToDoId").val() > 0) {
$.ajax({
url: "Save",
type: "POST",
data: $("#form0").serialize(),
dataType: "json"
}).done(function (model) {
$("#ToDoId").val(model.ToDoId);
$("#ToDoTitle").val(model.ToDoTitle);
$("#ToDoDesc").val(model.ToDoDesc);
});
}});
</script>
</div>
<div class="ObjectList" id="MyTaskList">
#Html.Action("TodoList")
</div>
<div class="clearfix"></div>
}
This is partial view which is being called using #Html.Action("TodoList")
#using System.Linq
#model myTask.Models.ToDoList
#{
ViewBag.Title = "Index";
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<div class="row">
#if (Model.ToDos.Count > 0)
{
<ul>
#foreach (var item in Model.ToDos)
{
#*<li>#Html.ActionLink(#item.ToDoTitle, "Index", new { tdid = #item.ToDoId })</li>*#
<li>#Ajax.ActionLink(#item.ToDoTitle, "/Index", new { tdid = #item.ToDoId }, new AjaxOptions { UpdateTargetId = "MyTask", InsertionMode = InsertionMode.Replace, HttpMethod = "POST" })</li>
}
</ul>
}
</div>
Controller:
using myTask.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
namespace myTask.Controllers
{
public class TasktdController : Controller
{
// GET: Tasktd
public ActionResult Index(int tdid =0)
{
if (tdid !=0)
{
ToDo t = new ToDo();
t.ToDoTitle = "Ramlal";
t.ToDoDesc = "Shyamlal";
t.ToDoId = tdid;
return PartialView(t);
}
else
{
return View();
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Save(ToDo td)
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
ModelState.Remove("ToDoId");
if (ModelState.IsValid)
{
var t = await td.Save(td);
return Json(td);
}
return Json("Oops! Error Occured");
}
[ChildActionOnly]
public PartialViewResult TodoList()
{
ToDoList tdl = new ToDoList();
List <ToDo> tds= new List<ToDo>();
ToDo t = new ToDo();
t.ToDoId = 1;
t.ToDoTitle = "XXX";
tds.Add(t);
tdl.ToDos = tds;
return PartialView(tdl);
}
}
}
There a number of problems with your code. First your submitting the form using .ajax() - and the form does not even have a submit button anyway! Replace
#using (Ajax.BeginForm("Save", new AjaxOptions() { ... })
{
....
}
with
<form id="form">
.... // add controls for properties
<button id="save" type="button">Save</button>
</form>
and remove the scripts for $( "#ToDoTitle" ).blur() and $("#ToDoDesc").blur() and replace with
<script>
var form = $('#form');
var saveUrl = '#Url.Action("Save")';
$.post(saveUrl, form.serialize(), function(data) {
if(data) {
// save successful - give the user some feedback
}
}).fail(function (result) {
// Oops and error occurred - give the user some feedback
});
.... // add script for editing here (see below0
</script>
Your current scripts result in poor performance and are the result of a bad UI design. The user should decide when to save (i.e by clicking a button), not you. Consider what happens if the user edits the title and tabs to the description then realizes that all was correct and nothing needed to be changed afterall (you code has already altered the data in the database and the user would not even know it)
Note the script should be immediately before the closing </body> tag (not inline). And the Save() method should return Json(true) if the objects was successfully saved.
To handle clicking the 'links', change the partial to (note the #if block it not required)
<ul>
#foreach (var item in Model.ToDos)
{
<li>
<span>#item.ToDoTitle</span>
<button class="edit" type="button" data-id="#item.#item.ToDoId">Edit</button>
</li>
}
</ul>
And if you want the button to look like a link, then use css to style it
Then add another script in the main view
var editUrl = '#Url.Action("Get")';
$('.edit').click(function() {
var id = $(this).data('id');
var title = $(this).prev('span').text();
$.getJSON(editUrl, { id: id, function(data) {
// Populate form fields
$('#ToDoId').val(id);
$('#ToDoTitle').val(title);
$('#ToDoDesc').val(data);
});
});
And the controller method would be
public JsonResult Get(int id)
{
// Look up the Description based on the id, for example
string description = db.ToDo.Where(x => x.ToDoId == id).Select(x => x.ToDoDesc);
return Json(description, JsonRequestBehavior.AllowGet);
}
Note, you already have the ID and Title properties in the view so its only necessary to return the Description (and I suggest you change your property names as suggested here - i.e. ID, Title and Description)
I am tryng to implement a search function in my index page using java script. I hav got a form to enter the name and when apply serach, the index page will get updated and load the new index page with the search results
Here is the form in my index page
<div id="content">
<form id="myForm" action="{{path('index_search')}}" method="POST" >
Write your name here:
<input type="text" name="name" id="name_id" value="" /><br />
<input type="submit" value="Send" />
</form>
</div>
<div id="output">#current index</div>
Here is the action exexcuted
public function searchAction()
{
$request = $this->get('request');
$name=$request->request->get('formName');
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('SystemVmsBundle:VisitorsDetails')->findOneByfirstname($name);
$view = $this->render('SystemVmsBundle:VisitorsDetails:index.html.twig',array(
'entities' => $entities,
));
$return=array("responseCode"=>200, "view"=>$view);
$return=json_encode($return);//jscon encode the array
return new Response($return,200,array('Content-Type'=>'application/json'));
}
Here is the js
$(document).ready(function() {
//listen for the form beeing submitted
$("#myForm").submit(function(){
//get the url for the form
var url=$("#myForm").attr("action");
$.post(url,{
formName:$("#name_id").val(),
other:"attributes"
},function(data){
//the response is in the data variable
if(data.responseCode==200 ){
$('#output').html(data.view);
$('#output').css("color","red");
}
else{
alert("An unexpeded error occured.");
}
});
return false;
});
});
However my js is working,but can not pass data as view to the js.How to pass the view 'index.html.twig' to the js?
When inspects with firebug,i got like
{"responseCode":200,"view":{"headers":{}}}
Any ideas?Thanks in advance!
Try to specify the dataType on your $.post function, like this:
$.post(url, {formName: $("#name_id").val(), other: "attributes"},
function(data) {
if(data.responseCode == 200){
$('#output')
.html(data.view)
.css("color","red");
} else {
alert("An unexpeded error occured.");
}
}, 'json');
But If you only need the html, not other data inside the json, you should change the logic, render a normal template and use dataType 'html'. goes like this:
// ...Controller.php
$view = $this->render('SystemVmsBundle:VisitorsDetails:index.html.twig',array(
'entities' => $entities,
));
return new Response($view);
// index.html.twig
$.ajax({
url: url,
dataType: 'html',
type: 'POST',
data: {formName: $("#name_id").val(), other:"attributes"},
success: function (data) {
$('#output').html(data).css("color","red");
}
});
I am writing ASP.NET web application. I am using views generated by Visual Studio (Create/Delete/Details/Edit/Index). In my application I have Articles which can be commented by logged users. Everything works fine (adding comment and viewing it), but to view newly added comment I have to manually refresh page.
Structure of Article/Details View looks like:
#if (Request.IsAuthenticated)
{
using (Ajax.BeginForm("Create", "Comment", new { articleId = Model._article.ArticleId }, new AjaxOptions
{
HttpMethod = "post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "myDisplayID"
}))
{
#Html.ValidationSummary(true)
<fieldset>
<h4>Dodaj komentarz</h4>
<p>
#Html.TextArea("komentarz", new { #class = "form-control" })
</p>
<p>
<input type="submit" value="Dodaj" class="btn btn-primary btn-large" />
</p>
</fieldset>
}
}
else
{
<p>
Zaloguj się aby dodać komentarz.
</p>
}
<hr />
<div id="myDisplayID">
#Html.Partial("_Comment", Model._comment)
</div>
EDIT
Comment/Create GET method
public ActionResult Create(int articleId)
{
var komentarz = new Comment();
komentarz.ArticleId = articleId;
return View(komentarz);
}
Comment/Create POST
[HttpPost]
public ActionResult Create(Comment comment, string komentarz, int articleId)
{
if (komentarz != String.Empty)
{
if (ModelState.IsValid)
{
comment.UserId = (int)WebSecurity.GetUserId(User.Identity.Name);
comment.Content = komentarz;
comment.ArticleId = articleId;
db.Comments.Add(comment);
db.SaveChanges();
}
}
ArticleViewModel widok = new ArticleViewModel();
widok._comment = (from b in db.Comments where b.ArticleId == articleId select b).ToList();
return PartialView("_Comment", widok._comment);
}
Article/Details GET method
public ActionResult Details(int id = 0)
{
ArticleViewModel widok = new ArticleViewModel();
widok._comment = (from b in db.Comments where b.ArticleId == id select b).ToList();
widok._article = (from t in db.Articles where t.ArticleId == id select t).FirstOrDefault();
if (Request.IsAjaxRequest())
{
return PartialView("_Comment", widok._comment);
}
if (widok == null)
{
return HttpNotFound();
}
return View(widok);
}
If you are to use Ajax.BeginFrom like you have shown. (but with some modifications)
using (Ajax.BeginForm(new AjaxOptions {
HttpMethod= "get",
InsertionMode=InsertionMode.Replace,
UpdateTargetId = "myDisplayID"
}))
, there are 3 things you need.
your comment section should be in a partial view and strongly type it (for your solution it might be a list of comments)
you should check if the request is ajax within the Action, using 'Request.IsAjaxRequest()'
if it's an ajax request, then you should return a partial view not the view.
.
public ActionResult Details()
{
//what ever the data retrieve code you have
return View(alldata);
}
[HttpPost]
public ActionResult Create(Comment comment, string komentarz)
{
if (komentarz != String.Empty)
{
if (ModelState.IsValid)
{
comment.UserId = (int)WebSecurity.GetUserId(User.Identity.Name);
comment.Content = komentarz;
db.Comments.Add(comment);
db.SaveChanges();
}
}
ArticleViewModel widok = new ArticleViewModel();
widok._comment = (from b in db.Comments where b.ArticleId == id select b).ToList();
return PartialView("_Comments",widok._comment);
}
submit comment - view
using (Ajax.BeginForm("Create", new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "myDisplayID"
}))
{
#Html.ValidationSummary(true)
<fieldset>
<h4>Dodaj komentarz</h4>
<p>
#Html.TextArea("komentarz", new { #class = "form-control" })
</p>
<p>
<input type="submit" value="Dodaj" class="btn btn-primary btn-large" />
</p>
</fieldset>
}
in your view
instead of
<h4>Comments</h4>
<table>
// table with comments for this article
</table>
put
<div id="myDisplayID">
#Html.Partial("_Comments", Model.Comments)
</div>
_Comments partial view
#model IEnumerable<Comments>
<h4>Comments</h4>
<table>
// table with comments for this article
</table>
I think doing it this way will improve the user experience if you also happen to add new comments using ajax
You can refresh in javascript with window.location.href=window.location.href. Reload posts the data again.
Best option to do this send a post request with an ajax Call, like this:
var comment = $("#txtComment).val(); // change this id with your textarea id
var id = #Model._article.ArticleId;
$.ajax({
url: "/Comment/Create",
type: "POST",
dataType: "json",
data: JSON.stringify({ content: comment, articleId: id }),
contentType: "application/json; charset=utf-8",
success: function (data) {
// if you returning all your comment use this
$("table").html(data.allContent);
// or if you return just a row with this comment
$("table").prepend(data.Comment);
}
I assume your Action method like this:
[HttpPost]
public ActionResult Create(string content,int articleId)
{
...
return Json(new { allContent = comments }); // just for example
}
Problem solved with changing
return View()
in POST Create Action to
ControllerContext.HttpContext.Response.Redirect(ControllerContext.HttpContext.Request.Url.ToString());
without any Partial Views, AJAX and Jquery
Try this :
return RedirectToAction("Index", "Comments");
or however your controller is called. But..
The best solution for me is to add comment asynchronously with jquery ajax. Comment/Create should return html as response (partialview), and you should replace old table with new one.
$.ajax({
url: "Comments/Create",
data: {comment : "bla bla", articleId : 1},
success: function (response) {
if (response == 'True') {
$('table').html(response);
}
}
});
Check some jquery ajax tutorials, it's very useful for such things. You can also use javascript when you are firing the action, which will prepare some html code with another row, and append it to table. Both of these ways are very nice, because you are not forced to reload the page.