First of all, I am a beginner at this. Have some experience in asp.net, but that i 8 years ago, and a lot has changed since then.
I am sure my problem is quite simple for the experienced developer, I just do not have the experience and knowledge to fix it.
I produce a viewmodel that is passed to a razor view. This view generates a dynamic form, and all that works well. It basically asks a number of questions in groups, and I need to verify that the user has selected an answer for each question before submitting.
A test form looks like this: test form
Each question is created as radio button list, and I need to ensure that all questions has an answer, before it can be submitted.
The current view, which contains a javascript function that works some of the way.. it prevents me from submitting at all.. so looking at something which is not quite right, but has most of it right. So I guess I need a little help to fix that part, or change the way it is done to something better?:
@using RefereeOnline.Models.FormsViewModels
@model RenderFormViewModel
@{
ViewData["Title"] = "Evaluate";
}
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script>
function ValidateForm() {
var isFormValid = true;
$("#evaluateform input,select").each(function () {
var FieldId = "span_" + $(this).attr("id");
if ($.trim($(this).val()).length == 0 || $.trim($(this).val()) == 0) {
$(this).addClass("highlight");
//Show required message along with the field
if ($("#" + FieldId).length == 0) {
$("<span class='error' id='" + FieldId + "'>Required</span>").insertAfter(this);
}
//If you fill and again make the field empty in that case again show the message
if ($("#" + FieldId).css('display') == 'none') {
$("#" + FieldId).fadeIn(500);
}
//$(this).focus();
isFormValid = false;
}
else {
$(this).removeClass("highlight");
if ($("#" + FieldId).length > 0) {
// Hide the message with the fade out effect
$("#" + FieldId).fadeOut(1000);
}
}
});
return isFormValid;
}
</script>
<h2>Evaluate (@Model.FormTitle @Model.FormVersion)</h2>
@using (Html.BeginForm("Evaluate", "Forms", FormMethod.Post, new { id = "evaluateform" }))
{
<div>
<hr />
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>@Model.PersonName</dd>
<dt>Match</dt>
<dd>@Model.ActivityInfo</dd>
<dt>Level</dt>
<dd>@Model.LevelName</dd>
</dl>
<hr />
@for (int g = 0; g < Model.Groups.Count; g++)
{
<table class="table">
@Html.Hidden("Model.Groups[" + @g + "].GroupId", Model.Groups[g].GroupId)
@if (Model.Groups[g].Answers.Any())
{
<tr>
<td></td>
@foreach (var answer in Model.Groups[g].Answers)
{
<td>@answer.Text</td>
}
@if (Model.Groups[g].Questions.Any(x => x.AllowComment))
{
<td>Comment</td>
}
</tr>
}
@for (int i = 0; i < Model.Groups[g].Questions.Count; i++)
{
<tr>
<td>@Model.Groups[g].Questions[i].Text</td>
@if (Model.Groups[g].Answers.Any() && !Model.Groups[g].Questions[i].Answers.Any())
{
foreach (var answer in Model.Groups[g].Answers)
{
<td>
@Html.RadioButton("Model.Groups[" + g + "].Questions[" + i + "].SelectedAnswer", answer.Id)
@Html.Label(answer.Value.ToString(), answer.Value.ToString())
@Html.Hidden("Model.Groups[" + g + "].Questions[" + i + "].FieldId", Model.Groups[g].Questions[i].FieldId)
</td>
}
}
else if (Model.Groups[g].Questions[i].Answers.Any()) //single question with answers
{
foreach (RenderAnswer answer in Model.Groups[g].Questions[i].Answers)
{
<td>
@Html.RadioButton("Model.Groups[" + g + "].Questions[" + i + "].SelectedAnswer", answer.Id)
@Html.Label(answer.Value.ToString(), answer.Text)
@Html.Hidden("Model.Groups[" + g + "].Questions[" + i + "].FieldId", Model.Groups[g].Questions[i].FieldId)
</td>
}
}
else //single question with textbox
{
<td>@Html.TextBox("Model.Groups[" + g + "].Questions[" + i + "].AnswerValue", Model.Groups[g].Questions[i].AnswerValue)</td>
}
@if (Model.Groups[g].Questions.Any(x => x.AllowComment))
{
<td>
@if (Model.Groups[g].Questions[i].AllowComment)
{
@Html.TextArea("Model.Groups[" + g + "].Questions[" + i + "].Comment", Model.Groups[g].Questions[i].Comment, 2, 40, null)
}
</td>
}
</tr>
}
</table>
}
</div>
@Html.Hidden("Model.CustomerId", Model.CustomerId)
@Html.Hidden("Model.FormId", Model.FormId)
@Html.Hidden("Model.UserId", Model.UserId)
@Html.Hidden("Model.ActivityId", Model.ActivityId)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" onclick="return ValidateForm();" /> |
@Html.ActionLink("Back", "PlannedEvaluations", "Person", new { userid = Model.UserId })
</div>
</div>
}
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
<!--onclick="return ValidateForm();" -->
My view model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace RefereeOnline.Models.FormsViewModels
{
public class RenderFormViewModel
{
public Guid CustomerId { get; set; }
public Guid FormId { get; set; }
public string UserId { get; set; }
public string FormTitle { get; set; }
public string FormVersion { get; set; }
public Guid ActivityId { get; set; }
public string PersonName { get; set; }
public string ActivityInfo { get; set; }
public string LevelName { get; set; }
public List<RenderGroup> Groups { get; set; } = new List<RenderGroup>();
}
public class RenderGroup
{
public string GroupId { get; set; }
public List<RenderQuestion> Questions { get; set; } = new List<RenderQuestion>();
/// <summary>
/// Contains a list of possible answers to limit to
/// If empty, no limited answers
/// </summary>
public List<RenderAnswer> Answers { get; set; } = new List<RenderAnswer>();
}
public class RenderQuestion
{
public Guid FieldId { get; set; }
public string Text { get; set; }
/// <summary>
/// Specific answers for this field
/// Used if in a group, but answers not re-used
/// </summary>
public List<RenderAnswer> Answers { get; set; } = new List<RenderAnswer>();
public string AnswerValue { get; set; }
public Guid SelectedAnswer { get; set; }
public bool AllowComment { get; set; }
public string Comment { get; set; }
}
public class RenderAnswer
{
public Guid Id { get; set; }
public string Text { get; set; }
public int Value { get; set; }
}
}
My controller (has a EF core context), have removed all irrelevant methods:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.EntityFrameworkCore;
using RefereeDb.Entities;
using RefereeOnline.Data;
using RefereeOnline.Data.Entities;
using RefereeOnline.Models.FormsViewModels;
using Activity = RefereeOnline.Data.Entities.Activity;
namespace RefereeOnline.Controllers
{
[Authorize]
[Route("[controller]/[action]")]
public class FormsController : Controller
{
private readonly RefereeContext _context;
public FormsController(
RefereeContext context)
{
_context = context;
}
public IActionResult Evaluate(Guid eventid, Guid activityid)
{
var eventData = _context.Events.Find(eventid);
var customer = _context.Customers.Find(eventData.CustomerId);
var activityData = _context.Activities.Include(m => m.MetaData).ThenInclude(f => f.Field)
.ThenInclude(mf => mf.MetaField).Include(l => l.Level).ThenInclude(t => t.Type)
.ThenInclude(r => r.CustomerReferences).First(x => x.Id == activityid);
EvaluationForm form = null;
try
{
form = _context.EvaluationForms.Include(f => f.Fields).ThenInclude(a => a.Answers)
.First(x => x.Id == activityData.Level.Type.CustomerReferences
.First(c => c.CustomerId == customer.Id &&
c.LicenseTypeId == activityData.Level.LicenseTypeId).EvaluationFormId);
}
catch (Exception ex)
{
return RedirectToAction("ShowMessage", "Site",
new
{
message = $"Evaluation forms not configured correctly for {activityData.Level.Name}",
returnurl = HttpUtility.HtmlEncode(Url.Action("Index", "Home"))
});
}
var model = BuildViewModel(form);
model.ActivityId = activityData.Id;
var user = _context.Users.First(x => x.Id == activityData.PersonId);
model.PersonName = user.FullName;
model.LevelName = activityData.Level.Name;
model.ActivityInfo =
$"{activityData.Date.ToShortDateString()} {activityData.Date.ToShortTimeString()} {activityData.Place}";
foreach (CustomerMetaFieldData data in activityData.MetaData.OrderBy(o => o.Field.MetaField.Order))
model.ActivityInfo += $" {data.FieldValue}";
return View(model);
}
[HttpPost]
public IActionResult Evaluate(RenderFormViewModel model)
{
var activity = _context.Activities.Include(l => l.Level).ThenInclude(r => r.Rules).Include(p => p.Person)
.ThenInclude(l => l.Licenses).ThenInclude(t => t.Type).First(x => x.Id == model.ActivityId);
_context.Entry(activity.Person).Collection(x => x.Batches).Load();
//batch id is assigned in post processing
Evaluation evaluation = new Evaluation { ActivityId = activity.Id, EvaluationFormId = model.FormId};
activity.EvaluationData = evaluation;
var customer = _context.Customers.Include(t => t.AssociatedTypes).ThenInclude(s => s.EvaluationSetup)
.First(x => x.Id == model.CustomerId);
var setups = customer.AssociatedTypes.First(t => t.LicenseTypeId == activity.Level.LicenseTypeId)
.EvaluationSetup.Where(x => x.LicenseLevelId == activity.LicenseLevelId);
_context.SaveChanges();
try
{
//load the form
_context.Entry(activity.EvaluationData).Reference(f => f.EvaluationForm).Load();
_context.Entry(activity.EvaluationData.EvaluationForm).Collection(f => f.Fields).Load();
foreach (EvaluationFormField field in activity.EvaluationData.EvaluationForm.Fields)
_context.Entry(field).Collection(a => a.Answers).Load();
Dictionary<string, int> points = new Dictionary<string, int>();
foreach (RenderGroup renderGroup in model.Groups.Where(x => !string.IsNullOrEmpty(x.GroupId)))
{
var groupSetup = setups.FirstOrDefault(x => x.Group == renderGroup.GroupId);
if (renderGroup.GroupId != null)
points.Add(renderGroup.GroupId, 0);
foreach (RenderQuestion question in renderGroup.Questions)
{
activity.EvaluationData.Data.Add(new EvaluationData
{
FieldId = question.FieldId,
AnswerId = question.SelectedAnswer,
EvaluationId = activity.EvaluationData.Id,
Comment = question.Comment
});
if (renderGroup.GroupId != null)
{
var currentField =
activity.EvaluationData.EvaluationForm.Fields.First(f => f.Id == question.FieldId);
FieldAnswer currentAnswer = null;
if (currentField.SameAnswersForAll)
{
var field = activity.EvaluationData.EvaluationForm.Fields.First(x =>
x.Answers.Any() && x.Group == renderGroup.GroupId);
var answers = field.Answers;
currentAnswer = answers.FirstOrDefault(a => a.Id == question.SelectedAnswer);
}
else
{
currentAnswer = currentField.Answers.First(a => a.Id == question.SelectedAnswer);
}
points[renderGroup.GroupId] += currentAnswer.Points;
}
}
if (renderGroup.GroupId != null)
{
var fields =
activity.EvaluationData.EvaluationForm.Fields.Where(x => x.Group == renderGroup.GroupId);
int max = 0;
if (fields.Any(x => x.SameAnswersForAll))
{
max = fields.First(x => x.Answers.Any()).Answers.Max(m => m.Points) * fields.Count();
}
else
{
max = fields.Sum(x => x.Answers.Max(a => a.Points));
}
EvaluationPointSums newPoints =
new EvaluationPointSums
{
GroupId = renderGroup.GroupId,
EvaluationId = evaluation.Id,
Points = points[renderGroup.GroupId],
Threshold = groupSetup?.PassThreshold ?? 0,
Maximum = max
};
evaluation.Points.Add(newPoints);
}
}
_context.Audit.Add(new Audit(User.Identity.Name, evaluation.Id, "Evaluation added"));
_context.SaveChanges();
}
catch (Exception)
{
//reverting the evaluation
_context.Evaluations.Remove(evaluation);
_context.SaveChanges();
//todo: go to message
}
//post processing the evaluation... should new license be created? or expired..
PostProcessEvaluation(activity, evaluation);
return RedirectToAction("EvaluationResult", new { evaluationid = evaluation.Id });
}
public IActionResult EvaluationDetails(Guid activityid, Guid evaluationid, bool score, bool data, string userid)
{
//getting event, activity, metadata, form etc...
var activity = _context.Activities.Include(e => e.EvaluationData).ThenInclude(f => f.EvaluationForm)
.ThenInclude(ff => ff.Fields).ThenInclude(fc => fc.Answers).Include(m => m.MetaData)
.ThenInclude(fd => fd.Field).ThenInclude(d => d.MetaField).Include(e => e.Event).Include(l => l.Level)
.First(x => x.Id == activityid);
_context.Entry(activity.EvaluationData).Collection(x => x.Data).Load();
_context.Entry(activity.EvaluationData).Collection(x => x.Points).Load();
foreach (var evaluationData in activity.EvaluationData.Data)
_context.Entry(evaluationData).Reference(x => x.Answer).Load();
DisplayEvaluationViewModel model = new DisplayEvaluationViewModel { UserId = userid };
model.Activity = activity;
model.RenderModel = BuildViewModel(activity.EvaluationData.EvaluationForm);
return View(model);
}
private void PostProcessEvaluation(Activity activity, Evaluation evaluation)
{
EvaluationRule rule = activity.Person.HasLicense(activity.LicenseLevelId)
? activity.Level.Rules.FirstOrDefault(x => x.Scope == LicenseScope.Licensed)
: activity.Level.Rules.FirstOrDefault(x => x.Scope == LicenseScope.Trainee);
if (rule != null) //if no rule, nothing happens
{
var batch = activity.Person.Batches.FirstOrDefault(x => x.LevelId == activity.LicenseLevelId);
if (batch == null)
{
//creating new batch, marking evaluation with it
batch = new EvaluationIdentityBatch { CurrentBatch = Guid.NewGuid(), LevelId = activity.LicenseLevelId, PersonId = activity.PersonId };
evaluation.BatchId = batch.Id;
activity.Person.Batches.Add(batch);
_context.SaveChanges();
}
//get all evaluations belonging to this batch
var evals = _context.Evaluations.Where(x => x.BatchId == batch.CurrentBatch);
if (evals.Count(x => x.IsPassed) == rule.Goal)
{
//target hit, all is good, execute passed action
ExecuteAction(rule.SuccessAction, activity);
}
else if (evals.Count() == rule.Tries)
{
//execute failed action
ExecuteAction(rule.FailAction, activity);
}
else
{
//log that nothing happens....
Trace.TraceError("Rule found, but not triggered");
}
}
}
private void ExecuteAction(EvalAction action, Activity activity)
{
switch (action)
{
case EvalAction.Issue:
License newLicense = new License
{
Assigned = DateTime.Now,
CustomerId = activity.Person.CustomerId,
LicenseLevelId = activity.LicenseLevelId,
LicenseTypeId = activity.Level.LicenseTypeId,
PersonId = activity.Person.Id,
Recorded = DateTime.Now
};
activity.Person.Licenses.Add(newLicense);
_context.Audit.Add(new Audit(User.Identity.Name, newLicense.Id, "Created by rule"));
break;
case EvalAction.Expire:
var license =
activity.Person.CurrentLicenses.First(x => x.LicenseLevelId == activity.LicenseLevelId);
license.LastActivity = DateTime.Now;
license.ForceExpiry = true;
_context.Audit.Add(new Audit(User.Identity.Name, license.Id, "Expired by rule"));
break;
}
var batch = activity.Person.Batches.First(x => x.LevelId == activity.LicenseLevelId);
activity.Person.Batches.Remove(batch);
_context.SaveChanges();
}
public IActionResult EvaluationResult(Guid evaluationid)
{
EvaluationResultViewModel model = new EvaluationResultViewModel();
var evaluation = _context.Evaluations.Include(p => p.Points).Include(a => a.Activity)
.ThenInclude(m => m.MetaData).First(x => x.Id == evaluationid);
_context.Entry(evaluation.Activity).Reference(p => p.Person).Load();
_context.Entry(evaluation.Activity).Reference(l => l.Level).Load();
_context.Entry(evaluation.Activity).Collection(r => r.Relations).Load();
model.Evaluation = evaluation;
return View(model);
}
private RenderFormViewModel BuildViewModel(EvaluationForm form)
{
ApplicationUser user = _context.Users.Include(m => m.AssociationMembers).First(x => x.UserName == User.Identity.Name);
RenderFormViewModel model = new RenderFormViewModel { CustomerId = form.CustomerId, FormId = form.Id, FormVersion = form.FormVersion, FormTitle = form.Name, UserId = user.Id };
foreach (EvaluationFormField field in form.Fields.OrderBy(x => x.SortOrder))
{
if (string.IsNullOrEmpty(field.Group))
{
//normal field
RenderGroup group = new RenderGroup();
RenderQuestion newQuestion = new RenderQuestion { Text = field.Text, FieldId = field.Id };
newQuestion.Answers.AddRange(field.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText, Value = x.Points }));
group.Questions.Add(newQuestion);
model.Groups.Add(group);
}
else
{
//grouped field
RenderGroup group = model.Groups.FirstOrDefault(x => x.GroupId == field.Group);
if (group == null)
{
//group does not exist... create + add answers
group = new RenderGroup { GroupId = field.Group };
if (field.SameAnswersForAll)
{
var answerfield = form.Fields.Where(x => x.Group == field.Group && x.Answers.Any())
.OrderBy(o => o.SortOrder).FirstOrDefault();
if (answerfield != null)
{
//adding general answers
group.Answers.AddRange(answerfield.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText, Value = x.Points }));
}
}
model.Groups.Add(group);
}
//creating the question
RenderQuestion newQuestion = new RenderQuestion { FieldId = field.Id, Text = field.Text, AllowComment = field.AddComment };
//adding specific answers
if (!field.SameAnswersForAll && field.Answers.Any())
newQuestion.Answers.AddRange(field.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText }));
group.Questions.Add(newQuestion);
}
}
return model;
}
#region create
}
}