POST <ul> with <li> elements from MVC view page - c#

So I have a list, where useer can add any number of items. In other words, I have a <ul> and variable length of <li> elements.
Variable length <li> elements means, user adds <li> elements in runtime, if she/he is done, she/he submits the form. So I don't know how many <li> elements are there.
Something like this :
<form asp-controller="MyController" asp-action="AddList" method="post">
<ul id="myUL">
<li>item1</li>
<li>item2</li>
/*...*/
<li>itemN</li>
</ul>
<button type="submit" class="btn btn-default">New List</button>
</form>
I add <li> elements dynamically with JavaScript if user clicks a button, like this:
function newElement() {
var li = document.createElement("li");
var inputValue = document.getElementById("myInput").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
document.getElementById("myUL").appendChild(li);
}
My controller's method (AddList) looks like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> NewShoppingList(object list)
{
// I don't know how to accept that list
}
But this way my parameter list is null. How can I get that <ul> with some <li> elements?

ul and li are not submittable form elements.You can try the following.
I'm not sure what your view model looks like, but it seems like a list of string values? So then it will look like this in your view model:
public class YourViewModel
{
public List<string> Items { get; set; }
}
In your view try the following:
<ul id="myUL">
<li>item1</li>
<li>item2</li>
/*...*/
<li>itemN</li>
</ul>
function addHidden(theLi, key, value) {
// Create a hidden input element, and append it to the li:
var input = document.createElement('input');
input.type = 'hidden';
input.name = key;'name-as-seen-at-the-server';
input.value = value;
theLi.appendChild(input);
}
function newElement() {
var li = document.createElement("li");
var inputValue = document.getElementById("myInput").value;
var t = document.createTextNode(inputValue);
li.appendChild(t);
addHidden(li, 'your-model-propertyName' + li-Count+1, inputValue );
document.getElementById("myUL").appendChild(li);
}
When you post then these items should still be in the list.
[HttpPost]
[AllowAnonymous]
//[ValidateAntiForgeryToken]
public async Task<IActionResult> NewShoppingList(List<string> list)
{
// I don't know how to accept that list
}
If you dont like add hidden field for each li , you can get all li items and send thems with Ajax.
like this:
function sendData() {
var items = [];
$("#myUL li").map(function () {
items.push(this.innerText);
});
$.ajax({
type: "POST",
data: {
list: items
},
url: "/Home/NewShoppingList",
success: function (res) {
}
}
I hope this helps.

You should avoid posting the html, after all you'll probably need to parse it, save to db etc. As Jeremy Thomson suggested use some client side library - jQuery, Angular or what ever you want and do the posts of items using Ajax. You can post them on each user input, or keep them in mvc model, javascript object etc. and post them at once.
example just to get the idea:
MVC - Binding dynamically added controls to a List<T> in a Model

Related

Route Blog id with view of Blog in MVC

I want to be able to click on a blog link and open the blog showing the page of that specific blog. My route config accepts optional id parameters. I am doing this with AJAX as well so hoping to click on a blog link and return to BlogController. I have searched around but cannot find something that helps me..
Here is my code
CSHTML
#foreach (var blodID in blogCont)
{
<a href="??" id="blogHREF" blog-id="#blodID.blogID">
Text
</a>
}
AJAX
$(document).on("click", "#blogHREF", function () {
var retBlogID = $(this).attr("blog-id");
var blogData = {
blogID: retBlogID
}
$.ajax({
type: "GET",
url: "Blog/getBlog",
data: blogData
});
});
C#
public class BlogController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult getBlog(blogTable blogged)
{
return View(blogged.blogAuthor);
}
}
If you are simply navigating to a new page (such as your question suggests) I would simply use a html helper.
#Html.Action("getBlog", "Blog", new {blogged = blodID })
So this:
<a href="??" id="blogHREF" blog-id="#blodID.blogID">
Text
</a>
would be this:
<a href="#Html.Action("getBlog", "Blog", new {blogged = blodID })" id="// THIS HAS TO BE UNIQUE">
Text
</a>
This is assuming that blodID is a blogTable.
Remove the [HttpGet], we don't need it.
Also worth noting that the id on your <a> tag doesn't look to me to be a unique value. These have to be unique. No matter where you use Id's in HTML.

How can i send back a model containing two lists from the view to my controller

I have a cshtml view, for which I am sending a viewmodel. That viewmodel consist of two list of albums (music albums). I then ask the user to check up to 3 of these albums (a checkbox next to the title) to vote for his favorite music. I use javascript to ensure he doesn't check anymore than 3 (The security is a detail right now, I'm more concerned about getting it to work, but I'm open to suggestion if people have a better solution).
Since all albums are displayed in a table, I would love to send back to the controller through the submit button, the same model after updating it.
Basically, one of the list contain the current vote the user has made before loading the page (can be empty), and the second one should be empty until sent back to the controller containing the list of votes that are currently selected. I then use these two lists to compare them and update the database, removing the votes he removed, and adding the vote he added.
But I am unable to create a proper form to return these informations as I am not used to forms.
I tried to put the whole list in a form, but it didn't work. My reserach when I look for "sending model back to controller" usually do just that and get it to work.
View model
public class CategoryVotesUserViewModels
{
public CategoryVoteViewModels categoryVoteViewModels;
public List<int> listVotesEntry = new List<int>();
public List<int> listVotesOutput = new List<int>();
}
Relevant CSHTML and javascript
#section Header{
<script>
var MAX_VOTES = 3;
function checkNumberVotes($this) {
console.log($("input[name='listVoteOutput']:checked"));
if ($("input[name='listVoteOutput']:checked").length > MAX_VOTES) {
$this.checked = false;
}
}
</script>
}
#using (Html.BeginForm("VoteInCategory", "Votes", new { SearchModel = Model }, FormMethod.Post))
{
<ul>
#foreach (var av in Model.categoryVoteViewModels.listVotes)
{
<li>
#av.album.Title | #av.votes |
<input type="checkbox"
name="listVoteOutput"
value=#av.album.ID
onclick="checkNumberVotes(this)"
#if (Model.listVotesEntry.Contains(av.album.ID))
{ <text> checked </text> } />
</li>
}
</ul>
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
}
Controller
[HttpPost]
public ActionResult VoteInCategory(CategoryVotesUserViewModels categoryVotesUserViewModels)
{
if (ModelState.IsValid)
{
List<int> toAdd = categoryVotesUserViewModels.listVotesOutput.Except(categoryVotesUserViewModels.listVotesEntry).ToList();
List<int> toRemove = categoryVotesUserViewModels.listVotesEntry.Except(categoryVotesUserViewModels.listVotesOutput).ToList();
VoteService.updateVoteUserCategory(User.Identity.GetUserId(), toRemove, toAdd, categoryVotesUserViewModels.categoryVoteViewModels.categoryID);
//TODO Redirect to success
return RedirectToAction("Index", "Home");
}
return View(categoryVotesUserViewModels);
}
If the user already had voted, all album whose ID is in "ListVotesEntry" should begin checked. If the user hasn't voted, or voted for nothing previously, "ListVotesEntry" should be empty.
When the User press the submit button, if an album is checked, the album's id should be added to the "ListVotesOutput" list. Also, both "ListVotesEntry" and "ListVotesOutput" should be sent back to the controller. The list with the names of the albums and their titles/ID is no longer necessary for the rest of the treatment
Found the solution. The problem was that my model needed to use "{get; set;}" on its attributes, otherwise the binding doesn't work, which mean that it send back an empty model.

Get the name of view who called controller in ASP.NET MVC

Is there any way to get the name of View that called method in controller and save it for example in some custom variable inside that controller's method?
For example:
I have one View that uses Ajax to get to InfinateScroll method in controller:
<div class="container-post">
<div id="postListDiv">
#{Html.RenderAction("PostList", "Posts", new { Model = Model });}
</div>
<div id="loadingDiv" style="text-align: center; display: none; margin-bottom: 20px;">
<img alt="Loading" src="#Url.Content("~/images/ajax-loader.gif")" />
</div>
</div>
<script src="#Url.Content("~/Scripts/jquery-1.10.2.min.js")"></script>
<script type="text/javascript">
var BlockNumber = 2;
var NoMoreData = false;
var inProgress = false;
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height() && !NoMoreData && !inProgress) {
inProgress = true;
$("#loadingDiv").show();
$.post("#Url.Action("InfinateScroll", "Posts")", { "BlockNumber": BlockNumber },
function (data) {
BlockNumber = BlockNumber + 1;
NoMoreData = data.NoMoreData;
$("#postListDiv").append(data.HTMLString);
$("#loadingDiv").hide();
inProgress = false;
});
}
});
</script>
I use this View on two pages. In one case I'm using it to show only posts from specific user (user who is logged in), and on the other view I'm showing posts from all users in database(similar to Facebook wall where you can see only your post, and NewsFeed where you can not only your's but also posts from your frineds).
For some reason I would like to know which page was active when call for InfinateScroll method was made.
This is the method where I would like to make some differences between those two pages so I can do some check out's later.
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber)
{
int BlockSize = 5;
var posts = PostManager.GetPosts(BlockNumber, BlockSize);
JsonModel jsonModel = new JsonModel();
jsonModel.NoMoreData = posts.Count < BlockSize;
jsonModel.HTMLString = RenderPartialViewToString("PostList", posts);
return Json(jsonModel);
}
This method gets posts using helper method GetPosts and it's used for showing more posts on scroll.
You can get the name of the current View from inside the view using the following:
#Path.GetFileNameWithoutExtension(Server.MapPath(VirtualPath))
Source: How to get the current view name in asp.net MVC 3?
so you could add this as a routevalue into your #Url.Action like so:
#Url.Action(
"InfinateScroll",
"Posts",
new{callingView=Path.GetFileNameWithoutExtension(Server.MapPath(VirtualPath))})
Then you could add a parameter to your controller method
public ActionResult InfinateScroll(int BlockNumber, string callingView)
You can create a hidden variable in the html like this -
<input type="hidden" id="pageName" value="myPage1" />
Add an extra parameter to your Action -
public ActionResult InfiniteScroll(int BlockNumber, int pageName)
And then, in your jquery code, when you post, send in pageName as well.
$.post("#Url.Action("InfinateScroll", "Posts")", { "BlockNumber": BlockNumber, "pageName": $('#pageName').val() },
Hope this helps.
In one case I'm using it to show only posts from specific user... and
on the other view I'm showing posts from all users in database...
Putting your desired logic on the view is unsafe, especially if showing data is user-based or user-specific. However, if you insists on having the logic on the view then you should pass along another variable to the controller like so:
$.post("#Url.Action("InfinateScroll", "Posts")",
{ "BlockNumber": BlockNumber, "UserId": userId },
// rest of your code goes here...
});
You then should have another parameter in your controller:
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber, int userId)
{
//filter your data based on the "userId" parameter
}
But like I mentioned this is unsafe because someone can easily pass in a valid "userId" and get to the data when you don't want them to. So the safest (or safer) way is to have the "filtering logic" in your controller like so:
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber)
{
// a context based logic
var userId = GetLoggedInUserId();
// that method could return null or zero
// and depending on how you approach it
//filter your data based on the "userId"
}

Ajax form in partial view validation

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);
}

Reloading Partial View with JQuery

I have a page with a video at the top and a list of videos you can choose from. Currently, clicking a link in the video list will reload the entire page. I need it to only refresh the partial view I have containing the video at the top of the page.
I saw several posts here on SO showing how to reload partial views with JQuery, but couldn't get it to work correctly in my situation. I'm unsure how to pass the correct id of the video along.
Controller:
public ActionResult Videos(int topVideo = 0)
{
VideosModel model = new VideosModel();
model.Videos = StatsVideoService.GetEntityList(new Lookup(TableStatsVideo.IsDeleted, false)).OrderByDescending(x => x.DateCreated).ToList();
if (topVideo == 0)
model.TopVideo = model.Videos.First();
else
{
model.TopVideo = model.Videos.Where(x => x.StatsVideoId == topVideo).FirstOrDefault();
if (model.TopVideo == null)
model.TopVideo = model.Videos.First();
}
return View(model);
}
View:
#model Project.Models.VideosModel
<section class="videos">
<div id="top_video">
#{Html.RenderPartial("StatsVideo", Model.TopVideo);}
</div>
<ul>
#foreach (var item in Model.Videos)
{
<li>
<div class="videoList">
<a href ="#Url.Action("Videos", "Home", new { topVideo = item.StatsVideoId })">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
</li>
}
</ul>
</section>
If there's any more information needed, please let me know.
After several hours of bashing my head against the wall, I got it to work! Just as a reference to anyone else in the future who's viewing this article, here's how I got it to work:
I set the onclick of the link to point to a javascript method, passing in the id of the video as a parameter:
#foreach (var item in Model.Videos)
{
<li>
<div class="videoList">
<a href ="#" onclick="updateTopVideo(#item.StatsVideoId)">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
</li>
}
And then I included this script in the view at the bottom:
<script>
var updateTopVideo = function (itemId) {
var url = '#Url.Content("~/Home/StatsVideo/")';
url = url + itemId;
$.get(url, "", callBack, "html");
};
var callBack = function (response) {
$('#top_video').html(response);
};
</script>
Finally, I added a method to my controller that would return the partial view needed for the video at the top of the screen:
public ActionResult StatsVideo(int Id)
{
IStatsVideo vid = StatsVideoService.GetEntity(new Lookup(TableStatsVideo.StatsVideoId, Id));
if (vid == null)
vid = StatsVideoService.GetEntityList(new Lookup(TableStatsVideo.IsDeleted, false)).OrderByDescending(x => x.DateCreated).FirstOrDefault();
return PartialView(vid);
}
This code should be fairly easy to understand. Basically, the onclick calls the first javascript method, which then calls the controller. The controller builds the partial view and returns it. The first javascript method passes it to the second javascript method which sets the html of the div "top_video" to be the returned partial view.
If anything doesn't make sense, or anyone's having trouble with this in the future, let me know and I'll do my best to offer some help.
I think there may be several confusing and inconsistent elements here.
First, you are returning a full view instead of a partial view. This reloads all containing elements, not just the part that is relevant to your partial view.
Second, you are using Url.Action, which only generates the url. I would recommend using Ajax.ActionLink, which allows you to do fully ajax calls, refreshing the content of your partial div and updating a target div element.
instead of:
<div class="videoList">
<a href ="#Url.Action("Videos", "Home", new { topVideo = item.StatsVideoId })">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
try the more modern solution
<div class="videoList">
#Ajax.ActionLink(
"Videos",
"Home",
"new { topVideo = item.StatsVideoId },
new AjaxOptions {
HttpMethod = "GET",
OnSuccess = "handleSuccess"
}
)
</div>
This way you can be very specific on what you want each link to do, and you can pass along multiple parameters as well as define a callback function. You can also use "UpdateTargetId" in your ajax options to load your newly refreshed partial view into a DOM element.
You can remove the around the image and just store the url generated by the Url.Action in a data-href attribute.
Then you can use the jquery load method to load the data:
$(".videolist>img").click(function () {
$("#content").load($(this).data("href"));
});
I created a fiddle that loads content dynamically here, so you can play with it if you want: http://jsfiddle.net/bTsLV/1/

Categories

Resources