How Can I Get the Controller to Register the Value of Votes to the Database? - ajax

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
}

Related

How to update hierarchical ViewModel?

I am stuck with this problem.
I have a model AssessmentModel defined like this:
public class AssessmentModel
{
public Respondent Respondent { get; set; }
public List<CompetencyModel> Competencies { get; set; }
}
public class CompetencyModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ResultModel> Results { get; set; }
}
public class ResultModel
{
public int Id { get; set; }
public int Score { get; set; }
}
All I need is to set value to the Score property of ResultModel.
Score is the only editable property here.
And I have just 1 View only, this view has a #model List, it displays a list of CompetencyModel items with Edit button for each one.
When I click the Edit button, the Id of CompetencyModel is passed to the same View, and the View draws an Edit form for ResultModel items that belong to the selected CompetencyModel.
However the form for ResultModel items exists on the same View, and the model of the View is still #model List.
How can I get to the Score property by using bindable Html.EditorFor(m=>m.Score) helper for each ResultModel item?
The View is defined like this:
#model List<CompetencyModel>
#foreach(var comp in Model)
{
<p>#comp.Name</p>
Edit
}
In the controller I set ViewBag.CurrentId = comp.Id, and at the bottom of the View:
if(ViewBag.CurrentId != null) //draw a form for ResultModel items
{
// What should I do now?
// how cant I use Html.EditorFor(m=>...) if the Model is still List<CompetencyModel>
}
I need to get to a single ResultModel entity to set a value to a Score property.
Thank you.
You should be able to get this done using Linq. Consider having the following code segment in the your last if statement
var result = Model.Results.FirstOrDefault(r => r.Id == ViewBag.CurrentId);
I dont have a IDE with me, so watchout for syntext errors

Getting a list of radio button values in ASP MVC 3

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.

MVC3 Ajax Checkboxes grid

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.

MVC 3 Unobtrusive validation of a list

Question
I have created a server-side property level validation attribute. But instead of applying it to an individual field I've applied it to a List. This allows me to validate the model as a whole.
I now need to know how to convert this to work using the unobtrusive client-side validation built into MVC 3.
My current code is below to illustrate my issue...
Scenario
The basic scenario was the ability total up all the Quantity values for every row in a List grouped by the GroupNo field. If the sum of any of the groups was more than 10 then an error should be displayed.
I was kindly given an answer in a previous post to make this work server-side using a validation attribute against a List...
The model:
public class ItemDetails
{
public int SerialNo { get; set; }
public string Description { get; set; }
public int GroupNo { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class MyViewModel
{
[EnsureMaxGroupItems(10, ErrorMessage = "You cannot have more than 10 items in each group")]
public IList<ItemDetails> Items { get; set; }
}
and the validation attribute itself:
[AttributeUsage(AttributeTargets.Property)]
public class EnsureMaxGroupItemsAttribute : ValidationAttribute
{
public int MaxItems { get; private set; }
public EnsureMaxGroupItemsAttribute(int maxItems)
{
MaxItems = maxItems;
}
public override bool IsValid(object value)
{
var items = value as IEnumerable<ItemDetails>;
if (items == null)
{
return true;
}
return items
.GroupBy(x => x.GroupNo)
.Select(g => g.Sum(x => x.Quantity))
.All(quantity => quantity <= MaxItems);
}
}
and finally your controller actions will work with the view model:
public ActionResult ListItems()
{
var model = new MyViewModel
{
Items = ItemsRepository.GetItems()
};
return View(model);
}
[HttpPost]
public ActionResult ListItems(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
...
}
and next the corresponding strongly typed view:
#model MyViewModel
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Items)
<button type="submit">Go go go</button>
}
and the last bit is the corresponding editor template that will automatically be rendered for each element of the Items collection so that you don't even need to write for loops (~/Views/Shared/EditorTemplates/ItemDetails.cshtml):
#model ItemDetails
#Html.HiddenFor(x => x.SerialNo)
#Html.LabelFor(x => x.Description)
#Html.HiddenFor(x => x.GroupNo)
#Html.LabelFor(x => x.Price)
#Html.TextBoxFor(x => x.Quantity)
Client-side unobtrusive validation possible?
I would like it all to validate using unobtrusive MVC validation. But I cannot figure out how to unobtrusively validate the EnsureMaxGroupItemsAttribute attribute against the list as a whole.
I've implemented IClientValidatable in this way:
Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules
Dim result = New List(Of ModelClientValidationRule)
Dim rule = New ModelClientValidationRule() With { _
.ErrorMessage = "You cannot have more than 10 items in each group", _
.ValidationType = "itemscheck"}
result.Add(rule)
Return result
End Function
Note: the mix of VB and C# is only because the previous question I asked was answered in C#. The project is in VB but I don't mind an answer in C#.
I've created the adaptor in my JS file:
jQuery.validator.unobtrusive.adapters.addBool("itemscheck");
... and ...
jQuery.validator.addMethod("itemscheck", function (value, element, params) {
// The check has been omitted for the sake of saving space.
// However this method never gets called
return false;
});
Is there a way to hook this up to work unobtrusively?
This is not possible because your custom attribute is placed in the collection property and there are no HTML5 data-* attributes emitted at all. It is not a supported scenario by the unobtrusive client validation framework. You could write directly a custom jquery validate rule to handle this scenario if you need client validation for it.

How does one populate a displayed form using data from an associated database entry that is selected via a drop-down

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?

Resources