I want to do a grid of checkboxes that contain products/categories. The products and categories could be dynamic (I mean, the count of products/categories can change). I'm able to create the grid and save the data when I check a box. My problem is to use ajax properly.
Here is my model :
public class ProductModel
{
public List<List<ProductItemGrid>> ProductItemGrid { get; set; }
public List<string> ProductNameList { get; set; }
public List<string> CategoryNameList { get; set; }
}
public class ProductItemGrid
{
public int ProductID { get; set; }
public int CategoryID { get; set; }
public bool ProductInCategory { get; set; }
}
Part of my view, (I use the list of list to populate it) :
#for (int i = 0; i < Model.ProductNameList.Count(); i++)
{
<tr class=#(i % 2 == 0 ? "even" : "odd")>
<td style="font-weight: bold;">
#Html.DisplayFor(x => x.ProductNameList[i])
</td>
#foreach (var result in Model.ProductItemGrid[i])
{
string ckBoxName = result.ProductID.ToString() + result.CategoryID.ToString();
<td id='<%=ckBoxName%>'>
#using (Ajax.BeginForm("UpdateProductItem", "Product", new AjaxOptions() { InsertionMode = InsertionMode.Replace, UpdateTargetId = ckBoxName }))
{
#Html.Hidden("p_CategoryID", result.CategoryID)
#Html.Hidden("p_ProductID", result.ProductID)
#Html.CheckBox("<%=ckBoxName%>", result.ProductInCategory, new { onclick = "test" })
}
</td>
}
</tr>
}
Right now the view contain some error, but Im sure you get the main idea. With the ajax form, Im able to update my database, but my main problem is to update the checkbox itself after doing the C# part. Also, I'm trying to name the < td> to be able to update the ckbox by giving him a name as ProductID_CategoryID (so it would be easier for me to know which one Im updating). Thks.
Have you checkout http://knockoutmvc.com it has a very nice integration with ASP.NET MVC3 and it looks like the type of library that can help you easily achieve what you need.
Related
I'm desperately trying to find out a solution towards solving this feature, but I haven't really got any useful help anywhere so far.
I'm working on a ASP.NET Framework MVC project, where I try to implement a simple rating feature for a Quiz class. But I'm kind of lost when it comes to the AJAX part and the Controller.
I can't figure it out how to implement this so every votes related to a specific Quiz gets registered in the Notification table.
I created a Notification class inside Model to store the results of Votes:
public class Notification
{
public int Id { get; set; }
[Required]
public Quiz Quiz { get; set; }
public int UpVoteCount { get; private set; }
public int DownVoteCount { get; private set; }
public int Score => UpVoteCount - DownVoteCount;
public void UpVote()
{
UpVoteCount++;
}
public void DownVote()
{
DownVoteCount++;
}
}
Then in the folder under Controller/API/QuizsController.cs I have implemented this action method, which I'm unsure of it's implementation. I'm kind of lost in this part! :
[HttpPost]
public IHttpActionResult Vote(int id)
{
var userId = User.Identity.GetUserId();
var quiz = _context.Guizzes.Single(m => m.Id == id && m.AuthorId == userId);
var notification = new Notification
{
Quiz = quiz, UpVote, DownVote, Score
};
_context.SaveChanges(); // save to the database
return Ok();
}
Then in the View I created MyQuiz.cshtml file and in there I have implemented the below html and AJAX code, but again this part certainly lacks some basic constructions to connect the controller to register the votes in the database.:
#model IEnumerable<VikingNotes.Models.Quiz>
#{
ViewBag.Title = "Home Page";
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.2/css/all.css" integrity="sha384-/rXc/GQVaYpyDdyxK+ecHPVYJSN9bmVFBvjA/9eOB+pb3F2w2N6fc5qB9Ew5yIns" crossorigin="anonymous">
<ul class="quizs verOffset7">
#foreach (var quiz in Model)
{
#*<li> #quiz.Creation - #quiz.Author.UserName </li>*#
<li>
<!-- Star Rating with font awesome-->
<div class="rating-star ">
#for (int i = 0; i < 5; i++)
{
<i class="fas fa-star"></i>
}
</div>
<!-- up-down arrow with font awesome-->
<div class="article">
<a href="voteUp" i class='fas fa-caret-up' style='font-size:40px;color:darkgrey'></a> <br>
<a href="votedown" class='fas fa-caret-down' style='font-size:40px;color:darkgrey'></a>
</div>
</li>
}
</ul>
#section scripts
{
<script>
jQuery(function ($) {
// Hook up our vote handlers
$("a.vote").live('click', voteClick);
function voteClick(event) {
var voteLink, voteType, item, itemId;
// Regardless of the below, we handle the event, so "consume" it
event.stopPropagation();
event.preventDefault();
// Get the anchor element, wrapped in a jQuery instance
voteLink = $(this);
// See if the vote has already been done or is in progress
if (voteLink.hasClass('done') || voteLink.hasClass('inprogress')) {
// Ignore the click, possibly tell the user why
return;
}
// Get the vote type
voteType = voteLink.hasClass('up') ? 'up' : 'down';
// Get the item we're voting on
item = voteLink.closest('.article');
// Get its ID
itemId = item.attr('data-itemid');
// If we didn't get an ID...
if (!itemId) {
// ...report error
return;
}
// Mark "in progress" and initiate the vote; action continues
// in our callbacks below
voteLink.addClass('inprogress');
$.ajax({
url: 'savevote',
data: { itemId: itemId, voteType: voteType },
type: 'POST',
success: votePostSuccess,
error: votePostError
});
// Called when the POST is successful
function votePostSuccess(response) {
// The POST worked
voteLink.removeClass('inprogress');
// Did things work on the server?
if (response === "ok") { // Or whatever
// Yes, the vote was successfully recorded
voteLink.addClass('done');
}
else {
// Report an error to the user, the server couldn't record the vote
}
}
// Called when the POST fails for some reason (HTTP errors)
function votePostError(xhr, statusText, err) {
// Not in progress anymore
voteLink.removeClass('inprogress');
// Report error to user
}
}
});
</script>
}
The Quiz Model looks like this:
public class Quiz
{
public int Id { get; set; }
public ApplicationUser User { get; set; }
[Required]
public string UserId{ get; set; }
[Required, MaxLength(200)]
public string Title { get; set; }
[Required]
[StringLength(Int32.MaxValue)]
public string Description { get; set; }
public DateTime Creation { get; set; }
public Genre Genre { get; set; }
public IEnumerable Genres { get; set; }
[Required]
public int GenreId { get; set; }
}
I also tried another approach:
I tried to adopt this tutorial [https://www.jqueryscript.net/other/Awesome-Rating-System.html][1]
with my project, but I couldn't get any results. I applied the instructions on the link and I downloaded the libraries and added to the project.
Please, some helpful souls!
(I've to add that it's my first project with mvc, razor and AJAX)
If what you want to do is simply send the star rating to the controller. You can do the following.
#for (int i = 1; i <= 5; i++)
{
<a onclick"javascript: voteClick(#i, //ID of the quiz)">
<i class="fas fa-star"></i>
</a>
}
That should send the vote rating and the id of the quiz to the function. From there you can simply use an ajax post to post it to the controller method.
EDIT: Note that you will need to change the parameters of your javascript function to:
function voteClick(vote, id)
{
//Function stuff
}
I want to post a List of items to controller from Razor view , but i am getting a List of objects as null
My class structre is
Model:
List<Subjects> modelItem
class Subjects
{
int SubId{get;set;}
string Name{get;set;}
List<Students> StudentEntires{get;set;}
}
class StudentEntires
{
int StudId{get;set;}
string Name{get;set;}
int Mark{get;set;}
}
The model itself is a list of items and every items contain List of child items as well. Example model is a list of Subjects and every subject contains a List of Students, and i want to input mark for every student
My View is like
#model IList<Subjects>
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (Model.Count > 0)
{
#for (int item = 0; item < Model.Count(); item++)
{
<b>#Model[item].Name</b><br />
#foreach (StudentEntires markItem in Model[item].StudentEntires)
{
#Html.TextBoxFor(modelItem => markItem.Mark)
}
}
<p style="text-align:center">
<input type="submit" class="btn btn-primary" value="Update" />
</p>
}
}
And in controller
[HttpPost]
public ActionResult OptionalMarks(int Id,ICollection<Subjects> model)
{
//BUt my model is null. Any idea about this?
}
You're finding this difficult because you're not utilising the full power of the MVC framework, so allow me to provide a working example.
First up, let's create a view model to encapsulate your view's data requirements:
public class SubjectGradesViewModel
{
public SubjectGradesViewModel()
{
Subjects = new List<Subject>();
}
public List<Subject> Subjects { get; set; }
}
Next, create a class to represent your subject model:
public class Subject
{
public int Id { get; set; }
public string Name { get; set; }
public List<Student> StudentEntries { get; set; }
}
Finally, a class to represent a student:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Grade { get; set; }
}
At this point, you have all the classes you need to represent your data. Now let's create two controller actions, including some sample data so you can see how this works:
public ActionResult Index()
{
var model = new SubjectGradesViewModel();
// This sample data would normally be fetched
// from your database
var compsci = new Subject
{
Id = 1,
Name = "Computer Science",
StudentEntries = new List<Student>()
{
new Student { Id = 1, Name = "CompSci 1" },
new Student { Id = 2, Name = "CompSci 2" },
}
};
var maths = new Subject
{
Id = 2,
Name = "Mathematics",
StudentEntries = new List<Student>()
{
new Student { Id = 3, Name = "Maths 1" },
new Student { Id = 4, Name = "Maths 2" },
}
};
model.Subjects.Add(compsci);
model.Subjects.Add(maths);
return View(model);
}
[HttpPost]
public ActionResult Index(SubjectGradesViewModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Success");
}
// There were validation errors
// so redisplay the form
return View(model);
}
Now it's time to construct the views, and this part is particularly important when it comes to sending data back to a controller. First up is the Index view:
#model SubjectGradesViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Subjects) <br />
<input type="submit" />
}
You'll notice I'm simply using Html.EditorFor, whilst passing Subjects as the parameter. The reason I'm doing this is because we're going to create an EditorTemplate to represent a Subject. I'll explain more later on. For now, just know that EditorTemplates and DisplayTemplates are special folder names in MVC, so their names, and locations, are important.
We're actually going to create two templates: one for Subject and one for Student. To do that, follow these steps:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
Create a strongly-typed view in that directory with the name that matches your model (i.e. in this case you would make two views, which would be called Subject.cshtml and Student.cshtml, respectively (again, the naming is important)).
Subject.cshtml should look like this:
#model Subject
<b>#Model.Name</b><br />
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.StudentEntries)
Student.cshtml should look like this:
#model Student
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.DisplayFor(m => m.Name): #Html.EditorFor(m => m.Grade)
<br />
That's it. If you now build and run this application, putting a breakpoint on the POST index action, you'll see the model is correctly populated.
So, what are EditorTemplates, and their counterparts, DisplayTemplates? They allow you to create reusable portions of views, allowing you to organise your views a little more.
The great thing about them is the templated helpers, that is Html.EditorFor and Html.DisplayFor, are smart enough to know when they're dealing with a template for a collection. That means you no longer have to loop over the items, manually invoking a template each time. You also don't have to perform any null or Count() checking, because the helpers will handle that all for you. You're left with views which are clean and free of logic.
EditorTemplates also generate appropriate names when you want to POST collections to a controller action. That makes model binding to a list much, much simpler than generating those names yourself. There are times where you'd still have to do that, but this is not one of them.
Change the action method signature to
public ActionResult OptionalMarks(ICollection<Subjects> model)
Since in your HTML, it does not look like there is anything named Id in there. This isn't your main issue though.
Next, do the following with the foor loop
#for(int idx = 0; idx < Model[item].StudentEntires.Count();idx++)
{
#Html.TextBoxFor(_ => Model[item].StudentEntries[idx])
}
Possibly due to the use of a foreach loop for the StudentEntries, the model binder is having trouble piecing everything together, and thus a NULL is returned.
EDIT:
Here's an example:
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var viewModel = new IndexViewModel();
var subjects = new List<Subject>();
var subject1 = new Subject();
subject1.Name = "History";
subject1.StudentEntires.Add(new Student { Mark = 50 });
subjects.Add(subject1);
viewModel.Subjects = subjects;
return View(viewModel);
}
[HttpPost]
public ActionResult Index(IndexViewModel viewModel)
{
return new EmptyResult();
}
}
View
#model SOWorkbench.Controllers.IndexViewModel
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (Model.Subjects.Any())
{
int subjectsCount = Model.Subjects.Count();
for (int item = 0; item < subjectsCount; item++)
{
<b>#Model.Subjects[item].Name</b><br />
int studentEntriesCount = Model.Subjects[item].StudentEntires.Count();
for(int idx = 0;idx < studentEntriesCount;idx++)
{
#Html.TextBoxFor(_ => Model.Subjects[item].StudentEntires[idx].Mark);
}
}
<p style="text-align:center">
<input type="submit" class="btn btn-primary" value="Update" />
</p>
}
}
When you post the form, you should see the data come back in the viewModel object.
I am populating DropDownList in View and getting this error on POST.
Error:
There is no ViewData item of type 'IEnumerable' that has the key 'SelectedCityName'.
Controller:
public ViewResult Register()
{
var Cities = ILocaRepo.Cities.ToList();
var Wards = ILocaRepo.Wards.ToList() ;
var model = new RegisterViewModel
{
City = Cities.Select(x => new SelectListItem
{
Value = x.CityID.ToString(),
Text = x.CityName
}),
Ward = Wards.Select(x => new SelectListItem
{
Value = x.WardID.ToString(),
Text = x.WardName
})
};
return View(model);
}
//
// POST: /Account/Register
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
MembershipService.CreateUser(model.User.Username, model.User.Password, model.User.Name, model.SelectedCityName, model.SelectedWardName, model.User.Address, model.User.Phone, model.User.Email, "Member");
FormsAuthentication.SetAuthCookie(model.User.Username, false);
return RedirectToAction("ListProduct", "Product");
}
catch (ArgumentException ae)
{
ModelState.AddModelError("", ae.Message);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
View :
<tr>
<td class="info_label">City</td>
<td>#Html.DropDownListFor(m=>m.SelectedCityName,Model.City,"-- Chọn thành phố --",new { #class = "dropdown" })</td>
</tr>
<tr>
<td class="info_label">Ward</td>
<td>#Html.DropDownListFor(m => m.SelectedWardName, Model.Ward, "-- Chọn quận --", new { #class = "dropdown" })</td>
</tr>
And the ViewModel:
public User User { get; set; }
public string SelectedCityName { get; set; }
public string SelectedWardName { get; set; }
public IEnumerable<SelectListItem> City { get; set; }
public IEnumerable<SelectListItem> Ward { get; set; }
How can i get SelectedCityName to pass as parameter of RegisterUser method?. Any help is appreciated.
Inside your POST action you are rendering the same view in case of error (return View(model);). But you forgot to assign the City and Ward properties, the same way you did in your GET action and they will be null when this view is rendered.
So:
// If we got this far, something failed, redisplay form
// and don't forget to rebind City and Ward
model.City = ILocaRepo.Cities.ToList().Select(x => new SelectListItem
{
Value = x.CityID.ToString(),
Text = x.CityName,
});
model.Ward = ILocaRepo.Wards.ToList().Select(x => new SelectListItem
{
Value = x.WardID.ToString(),
Text = x.WardName,
});
// Now we can safely redisplay the same view
return View(model);
The reason why those 2 properties are null inside your POST action is pretty simple and lies into the design of HTML forms. In HTML when you submit a form containing a <select> element (which is what those Html.DropDownListFor helpers are generating), only the selected value is sent to the server. The list of available values is not sent to the server simply because it is assumed that you already have it somewhere on the server (because you rendered this form in the first place). So it is your responsibility, if you decide to redisplay the form in your POST action, to re-populate those collections in order to be able to display the dropdown lists properly.
I am developing a page for rating questions.
In the view, I have a list of questions and 5 radio buttons in front of each one of them.
<input name="evalId" type="hidden" value="#Model.Evaluation.EvalId" />
foreach (var question in questionList)
{
<input name="questionId" type="hidden" value="#question.QuestionId" />
<div class="row_star" style="border-bottom : 0 none; background: none;">
#if (!String.IsNullOrEmpty(question.QuestionTitre))
{
<p>#question.QuestionTitre.TrimEnd()</p>
}
#* here goes the code for 5 radio buttons*#
}
Now, in my controller I want to be able to know which radio button was checked for each question.
How can I do that ?
Here is my ViewModel
public class EvaluationViewModel
{
/// <summary>
///
/// </summary>
public EvalEvaluation Evaluation
{
get;
set;
}
/// <summary>
///
/// </summary>
public Dictionary<EvalQuizz, List<EvalQuestion>> EvalQuizzQuestionList
{
get;
set;
}
}
Assuming your ViewModel is like this
public class Question
{
public int ID { set; get; }
public string QuestionText { set; get; }
public List<Answer> Answers { set; get; }
public int SelectedAnswer { set; get; }
public Question()
{
Answers = new List<Answer>();
}
}
public class Answer
{
public int ID { set; get; }
public string AnswerText { set; get; }
}
public class Evaluation
{
public List<Question> Questions { set; get; }
public Evaluation()
{
Questions = new List<Question>();
}
}
And in your GET action method, you will return the viewmodel back to the view with some questions and answers filled in it. In the code below I've hardcoded the questions and answers. You may get it from your Repositary/Service layer.
public ActionResult Index()
{
var evalVM = new Evaluation();
//the below is hardcoded for DEMO. you may get the data from some
//other place and set the questions and answers
var q1=new Question { ID=1, QuestionText="What is your favourite language"};
q1.Answers.Add(new Answer{ ID=12, AnswerText="PHP"});
q1.Answers.Add(new Answer{ ID=13, AnswerText="ASP.NET"});
q1.Answers.Add(new Answer { ID = 14, AnswerText = "Java" });
evalVM.Questions.Add(q1);
var q2=new Question { ID=2, QuestionText="What is your favourite DB"};
q2.Answers.Add(new Answer{ ID=16, AnswerText="SQL Server"});
q2.Answers.Add(new Answer{ ID=17, AnswerText="MySQL"});
q2.Answers.Add(new Answer { ID=18, AnswerText = "Oracle" });
evalVM.Questions.Add(q2);
return View(evalVM);
}
Now we will create an Editor Template to render our Question. so go to your View Folder and create a folder called EditorTemplates under the folder with your current controller name.
Add a view to the EditorTemplates folder and give the same name as the class name we want to represent. ie : Question.cshtml
Now put this code in the editor tempalte
#model YourNameSpace.Question
<div>
#Html.HiddenFor(x=>x.ID)
#Model.QuestionText
#foreach (var a in Model.Answers)
{
<p>
#Html.RadioButtonFor(b=>b.SelectedAnswer,a.ID) #a.AnswerText
</p>
}
</div>
Now go to our main view and use EditorTemplate html helper method to bring the EditorTemplate we created to the main view.
#model YourNameSpace.Evaluation
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.EditorFor(x=>x.Questions)
<input type="submit" />
}
Now in your HttpPost you can check the posted model and get the selected radio button (SelectedAnswer) value there
[HttpPost]
public ActionResult Index(Evaluation model)
{
if (ModelState.IsValid)
{
foreach (var q in model.Questions)
{
var qId = q.ID;
var selectedAnswer = q.SelectedAnswer;
//Save
}
return RedirectToAction("ThankYou"); //PRG Pattern
}
//reload questions
return View(model);
}
If you use visual studio breakpoints, you can see the values posted. Thanks to MVC Model binding :)
You can read about it and download a working sample here.
I have the following code that allows a teacher to see a drop-down list of available courses to teach, listed by name. When a teacher selects a dropdown option I would like a form on the view to auto-populate with default values representing the selected course. What is the most efficient way to populate the fields?
note: When "Custom" is selected in the drop-down, I want the form that is displayed below the dropdown to have nothing but blank spaces.
CourseController
// GET: /Course/ApplyToTeach
public ActionResult ApplyToTeach()
{
var course = db.CourseTemplates;
var model = new ApplyCourseViewModel
{
Courses = course.Select(x => new SelectListItem
{
Value = x.Title,
Text = x.Title,
})
};
return View(model);
}
ApplyToTeachViewModel
public class ApplyToTeachViewModel
{
[Display(Name = "selected course")]
public string SelectedCourse { get; set; }
public IEnumerable<SelectListItem> Courses { get; set; }
}
ApplyToTeach (view) - note that all I have here currently is the drop-down menu, I am looking for the most efficient way to add the auto-populating form below this drop-down.
<h2>ApplyToTeach</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Apply To Teach</legend>
<div class="editor-label">
Which class would you like to teach? (select "Custom" if you would like to submit a customized class)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SelectedCourse, Model.Courses, "Custom")
#Html.ValidationMessageFor(model => model.Courses)
</div>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
}
The data for the drop-down fields comes from the following model -
CourseTemplates
public class CourseTemplates
{
public int CourseTemplatesID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public int AttendingDays { get; set; } // 10, 8, 3, or custom
public int AttendanceCap { get; set; } // default of 30
public string Location { get; set; }
public string Description { get; set; }
}
The form is actually going to be submitted as a "Course" model, not the "CourseTemplates" model, the "Course" model has more data fields than the "CourseTemplates" model - such as the following:
public DateTime StartDate { get; set; }
public bool Approved { get; set; }
etc. . .
What I have in mind as far as user-experience is that an administrator will go through beforehand and add in a number of possible course options, simply to ease the application process for most teachers (so they don't have to type every detail for every class they apply to teach), but I want the teacher to be able to edit any information before submitting the course for review by an administrator.
Any tips?