Triggering a ActionResult method off of a checkbox toggle - javascript

I'm a semi-competent Winforms/WPF/MVVM/c#/vb.Net dev, attempting to teach myself ASP.Net with MVC and i'm a little confused as to how you "raise events" (i know MVC doesn't do events but that's what i'm equating it to) off of anything that's not a form's submit button. I have a View, Controller and a Model for a simple to-do list style application and i'm wondering how i can trigger some code in the controller off of the toggling of the check box. This is my code:
View (Views/ToDo/Index.cshtml):
#{
ViewBag.Title = "Index";
}
#model List<ToDo.Models.ToDoListItem>
<h2>To Do List</h2>
<form action="/ToDo/Create" method="post">
<div>
<input name="ToDoItem" />
<input type="submit" value="Add Task" />
</div>
</form>
<div>
<ul>
#if (Model != null)
{
foreach (var item in Model)
{
<li>
<form action="/ToDo/Delete/#item.ItemId " method="post">
<input type="checkbox" checked="#item.isChecked" />
#if (item.isCompleted)
{
<label style="text-decoration-line:line-through">#item.ItemText</label>
}
else
{
<label>#item.ItemText</label>
}
<input type="submit" value="Delete" />
</form>
</li>
}
}
</ul>
</div>
Controller (Controllers/ToDoController.cs):
namespace ToDo.Controllers
{
public class ToDoController : Controller
{
// GET: ToDo
public ActionResult Index()
{
return View(Models.ToDoListItem.GetAll());
}
[HttpPost]
public ActionResult Create(string toDoItem)
{
Models.ToDoListItem.Create(toDoItem, Models.ToDoListItem.GetNextID());
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Delete(string id)
{
int itemIdentifier = Convert.ToInt32(id);
Models.ToDoListItem.Delete(itemIdentifier);
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult CheckBoxToggle(string id)
{
int itemIdentifier = Convert.ToInt32(id);
Models.ToDoListItem.CompleteToggeled(itemIdentifier);
return RedirectToAction("Index");
}
}
}
Model (Models/ToDiListItem.cs):
namespace ToDo.Models
{
public class ToDoListItem
{
#region Fields
private int _itemId;
private string _itemText;
private bool _isCompleted;
#endregion
#region Events
#endregion
#region Properties
public int ItemId
{
get
{
return _itemId;
}
set
{
_itemId = value;
}
}
public string ItemText
{
get
{
return _itemText;
}
set
{
_itemText = value;
}
}
public bool isCompleted
{
get
{
return _isCompleted;
}
set
{
_isCompleted = value;
}
}
public string isChecked
{
get
{
if (isCompleted)
return "checked";
else
return string.Empty;
}
}
#endregion
#region Public Methods
public static void Create(string toDoItem, int itemId)
{
var item = new ToDoListItem();
item.ItemText = toDoItem;
item.ItemId = itemId;
GlobalVariables.Tasks.Add(item);
}
public static void Delete(int id)
{
foreach (ToDoListItem item in GlobalVariables.Tasks)
{
if (item.ItemId == id)
{
GlobalVariables.Tasks.Remove(item);
break;
}
}
}
public static void CompleteToggeled(int id)
{
foreach (ToDoListItem item in GlobalVariables.Tasks)
{
if (item.ItemId == id)
{
item.isCompleted = !item.isCompleted;
}
}
}
public static List<ToDoListItem> GetAll()
{
return GlobalVariables.Tasks;
}
public static int GetNextID()
{
return ++GlobalVariables.CurrentID;
}
#endregion
}
}
So, what i am looking to do, is to be able to toggle the "checked" state one of the checkboxes on the form and call the "CheckBoxToggle" method in my controller, passing in the ID of the item (similarly to how i did it on the delete button). I've seen something to do with Javascript (which i know nothing about) being mentioned, but i have no real idea what i am doing with it and nothing i've seen so far explains it particularly clearly.
If anyone knows the best way for me to go about this, some assistance would be much appreciated.

You can listen to the change event on the checkbox and post the form which includes an input form element with same name as your HttpPost action method parameter.
Update your markup so that it will have a css class which we will use as our jQuery selector for wiring up the event listener for the checbox toggle event. Also you can keep the Id of item in an input hidden field with same name as your parameter (Id)
#using (Html.BeginForm("CheckBoxToggle", "Todo"))
{
<input type="checkbox" class="myChk" name="isChecked" checked="checked"/>
<input type="hidden" name="id" value="#item.Id" />
}
Now basically you need to listen to the click event on the check box.
$(function(){
$(".myChk").click(function() {
$(this).closest("form").submit();
});
});
Assuming you have jQuery library loaded to your page.
I also see you are receiving the parameter value in string and converting it back to int. Why not use the int type param ? You may also add one more parameterr named isChecked to know whether user checked the checkbox or unchecked. When it is checked the parameter value will be not null
[HttpPost]
public ActionResult CheckBoxToggle(int id,string isChecked)
{
Models.ToDoListItem.CompleteToggeled(id);
return RedirectToAction("Index");
}

Related

How to read multiple choice answers from a dynamic form in razor pages?

As a follow up on my previous question related to this issue, the solution provided there works well enough for basic answers to basic questions in my questionnaire.
Background
I'm allowing the user to create custom questionnaires by specifying question details including a question type which determines the UI elements that will be rendered dynamically when their customer fills out the questionnaire.
The proposed solution of binding in a list per learnrazorpages.com works well for questions for which only one response may be given, however, it seems less apt for handling a multiple choice type of question where the user may provide any number of responses.
The following code does not inject my selections on the multiple choice question into the form submission.
<form method="post">
#for (int i = 0; i <= Model.Questions.Count - 1; i++)
{
var question = Model.Questions[i];
var questionId = Model.Questions[i].Id;
var questionType = Model.Questions[i].Type;
<input type="hidden" id="[#i].QuestionId" name="[#i].QuestionId" value="#questionId" />
#if (questionType == Enums.QuestionType.MultipleChoice)
{
var options = Model.Options.Where(x => x.Question.Id == questionId);
var answers = Model.Answers.WHere(x => x.Question.Id == questionId);
<div class="mb-3">
<p class="mb-0 mt-2">#question.Text</p>
#for (int j = 0; j <= Model.Options.Count() - 1; j++)
{
if (Model.Options[j].Question.Id == questionId)
{
// Determine based on the answer whether or not to check the checkbox.
var option = Model.Options[j];
var isChecked = answers.Any(x => x.Value == option.Id.ToString());
var _checked = isChecked ? "checked" : "";
<div>
<input type="hidden" id="[#j].OptionId" name="[#j].OptionId" value="#option.Id" />
<input class="form-check-input" type="checkbox" id="[#j].Value" name="[#j].Value" value="#option.Id" #_checked />
<label class="form-check-label" for="[#j].Value">#option.Text</label>
</div>
}
}
</div>
}
</form>
I structured it this way thinking it would simply inject the options list into the method parameter value of my OnPost method, since the viewmodel I built for that method accepts the list as a parameter:
public void OnPost(List<AnswerViewmodel> answers)
{
// I haven't got any logic here yet to save the answers so this is just for a breakpoint.
var answered = answers.Where(x => x.Value is not null);
}
public class AnswerViewmodel
{
public int QuestionId { get; set; }
public string Value { get; set; }
public string Comment { get; set; }
public List<OptionViewmodel> Options { get; set; }
}
public class OptionViewmodel
{
public int OptionId { get; set; }
public string Value { get; set; }
}
If the option is selected (checked) then Value will be "true", otherwise "false".
However, the Options list is always null in the value.
How can I get my options to post through to the backend with the rest of the form?
You name attribute of your inputs should be [#i].Options[#j].Value to bind correctly with the List<OptionViewmodel> Options:
<input type="hidden" id="[#j].OptionId" name="[#i].Options[#j].OptionId" value="#option.Id" />
<input class="form-check-input" type="checkbox" id="[#j].Value" name="[#i].Options[#j].Value" value="#option.Id" #_checked />

How to keep the existing image if no new image selected?

I know there are similar questions. But I spend a whole day and couldn't fix my problem, Because I am completely noob. So, I'd appreciate it if someone provide an specific solution for my ASP.Net Core project.
If you need more info, Just ask.
Thanks.
So, My project is about Directors:
public class Director
{
public Director()
{
Movies = new List<Movie>();
}
public int DirectorId { get; set; }
[Required]
public string Name { get; set; }
public string Country { get; set; }
public string Bio { get; set; }
public List<Movie> Movies { get; set; }
public string PhotoURL { get; set; } // This field holds only the name of the photo, Not its URL.
}
My project save images in "wwwroot/uploads". Each director has an image. I can select a new image from my hard disk for each director.
Problem:
I can update a director image. But If I don't select a new image, the existing image will be deleted. I want to prevent it. I want it to keep the existing image if I don't select a new image.
Edit.cshtl.cs
public class EditModel : PageModel
{
private readonly Context _context;
private readonly IWebHostEnvironment hostingEnvironment;
public EditModel(Context context, IWebHostEnvironment environment)
{
_context = context;
this.hostingEnvironment = environment;
}
[BindProperty]
public Director Director { get; set; }
[BindProperty]
public IFormFile Image { set; get; }
public async Task<IActionResult> OnGetAsync(int? directorId)
{
if (directorId == null)
{
return NotFound();
}
Director = await _context.Director.FirstOrDefaultAsync(m => m.DirectorId == directorId);
if (Director == null)
{
return NotFound();
}
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync(int? directorId)
{
if (!ModelState.IsValid)
{
return Page();
}
if (this.Image != null)
{
var fileName = GetUniqueName(this.Image.FileName);
var uploads = Path.Combine(hostingEnvironment.WebRootPath, "uploads");
var filePath = Path.Combine(uploads, fileName);
this.Image.CopyTo(new FileStream(filePath, FileMode.Create));
this.Director.PhotoURL = fileName; // Set the file name
}
_context.Attach(Director).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!DirectorExists(Director.DirectorId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool DirectorExists(int id)
{
return _context.Director.Any(e => e.DirectorId == id);
}
private string GetUniqueName(string fileName)
{
fileName = Path.GetFileName(fileName);
return Path.GetFileNameWithoutExtension(fileName)
+ "_" + Guid.NewGuid().ToString().Substring(0, 4)
+ Path.GetExtension(fileName);
}
}
Edit.cshtml
<form method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Director.DirectorId" />
<div class="form-group">
<label asp-for="Director.Name" class="control-label"></label>
<input asp-for="Director.Name" class="form-control" />
<span asp-validation-for="Director.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Director.Country" class="control-label"></label>
<input asp-for="Director.Country" class="form-control" />
<span asp-validation-for="Director.Country" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Director.Bio" class="control-label"></label>
<input asp-for="Director.Bio" class="form-control" />
<span asp-validation-for="Director.Bio" class="text-danger"></span>
</div>
<div>
<img id="blah" src="~/uploads/#Model.Director.PhotoURL" />
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="form-group" runat="server">
<label asp-for="Director.PhotoURL" class="control-label"></label>
<input type="file" asp-for="Image" class="form-control" id="imgInp" value="~/uploads/#Model.Director.PhotoURL" />
<span asp-validation-for="Director.PhotoURL" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
site.js:
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#blah').attr('src', e.target.result);
}
reader.readAsDataURL(input.files[0]); // convert to base64 string
}
}
$("#imgInp").change(function () {
if ($('#imgInp').get(0).files.length !== 0) {
readURL(this);
}
});
Edit page:
The simplest way to modify your code would be to do a get on the database and set the image path to the original image path if the new image path is null. This does have the disadvantage of an extra database call though:
if (this.Image != null)
{
...
}
else
{
Director.PhotoURL = (await _context.Director.FirstOrDefaultAsync(m => m.DirectorId == directorId))?PhotoURL;
}
Another option would be to store the original image path somewhere when you retrieve it from the database and then use it if the new image path is null. This would not involve an extra get. You could do something like:
[BindProperty]
public string OriginalImage { set; get; }
...
OriginalImage = Director.PhotoURL
...
<input type="hidden" asp-for="OriginalImage"/>
...
if (this.Image != null)
{
...
}
else
{
Director.PhotoURL = OriginalImage;
}
The simplest thing to do is to use an else clause in your OnPostAsync call here: if (this.Image != null)
Since you are setting the context to changed and doing an update, you are writing a null to the image. With an else you can do something like this:
else {
this.Director.PhotoURL = LastImage;
}
As for how you get a value for LastImage, the easiest way is to pull it via a linq query using the DirectorId that was passed in (the same way you get the Director object a little above).

Validation message is not displaying for custom attribute in .Net Core

I am creating custom attribut for validation to override RegularExpressionAttribute in .Net Core, and Implemented IClientModelValidator for client side validation. validation is apply on field but didn't display Error message for it. ModelState.IsValid is also giving Invalid that field but validation message is not displaying.
ViewModel
[Required]
[Display(Name = "First Name")]
[RestrictSplCharacters]
public string FirstName { get; set; }
Override Attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class RestrictSplCharactersAttribute : RegularExpressionAttribute, IClientModelValidator
{
private string errorMessage= "Special characters or blank space is not allowed in {0}";
public RestrictSplCharactersAttribute()
: base(#"[_A-z0-9]*((-|\s)*[_A-z0-9])*$")
{
this.ErrorMessage = this.errorMessage;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-restrictSplCharacters", errorMessage);
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
In Html Control is like
<div class="oneditshow">
<input autocomplete="off" class="k-textbox valid k-valid" data-val="true" data-val-required="The First Name field is required." data-val-restrictSplCharacters="Special characters or blank space is not allowed in First Name" id="FirstName" name="FirstName" placeholder="First Name" required="required" style="width: 100%" value="" aria-required="true" aria-describedby="FirstName-error">
<span class="text-danger field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true" style="display: none;"></span>
</div>
Javascript function
<script>
var $jQval = $.validator;
$jQval.addMethod("restrictSplCharacters",
function (value, element, parameters) {
var regExp = "/[_A-z0-9]*((-|\s)*[_A-z0-9])*$/";
if (value.match(regExp)) {
return true;
} else {
return false;
}
});
var adapters = $jQval.unobtrusive.adapters;
adapters.addBool("restrictSplCharacters");
</script>
Thank you, Client Side validation is not fired because it's kendo UI.
I replace my JavaScript with Below javascript for kendo custom validation Rule.
//register custom validation rules
(function ($, kendo) {
$.extend(true, kendo.ui.validator, {
rules: { // custom rules
restrictSpecialCharacters: function (input, params) {
//check for the rule attribute
if (input.filter("[data-val-restrictSpecialCharacters]").length && input.val()) {
return /[_A-z0-9]*((-|\s)*[_A-z0-9])*$/.test(input.val());
}
return true;
}
},
messages: { //custom rules messages
restrictSpecialCharacters: function (input) {
// return the message text
return input.attr("data-val-restrictSpecialCharacters");
}
}
});
})(jQuery, kendo);
Try with adding following code after public void AddValidation in RestrictSpecialCharactersAttribute.
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule mvr = new ModelClientValidationRule();
mvr.ErrorMessage = this.eRRORMESSAGE;
mvr.ValidationType = "restrictSpecialCharacters";
return new[] { mvr };
}
You can find more details here.

Pass value from Javascript function to MVC Model

I have the following ViewModel:
namespace SimpleModel.ViewModels
{
public class ClientSearch
{
public int Type { get; set; }
public string String { get; set; }
}
}
The following is the HTML code snippet:
<div id="clientSelect">
<input type="radio" name="clientSelect" value="1" />Account Number<br />
<input type="radio" name="clientSelect" value="2" />ID Number / Company Number<br />
<input type="radio" name="clientSelect" value="3" />Surname / Company Name<br />
#Html.EditorFor(model => model.String)
</div>
<p>
<input type="submit" value="Search" onclick="clientSearch('clientSelect')" />
</p>
I have the following JavaScript function:
<script type="text/javascript">
function clientSearch(strGroupName) {
var selectedValue = 0;
var arrInputs = document.getElementsByTagName("input");
for (var i = 0; i < arrInputs.length; i++) {
var oCurInput = arrInputs[i];
if (oCurInput.type == "radio" && oCurInput.name == strGroupName && oCurInput.checked)
selectedValue = oCurInput.value;
}
}
</script>
I need to update ClientSearch model Type field with selectedValue from within the Javascript function so I may pass the model back to the Controller for processing.
Any help would be appreciated.
First of all this object is not ok, you can not have a property that is a c# keyword
public class ClientSearch
{
public int Type { get; set; }
public string String { get; set; } // This right here is a reserved c# keyword
}
So change your ClientSearch class to something like
public class ClientSearch
{
public int Type { get; set; }
public string SearchString { get; set; }
}
Then your View will look something like:
<div id="clientSelect">
#Html.RadioButtonFor(x => x.Type, 1) #:AccountNumber<br/>
#Html.RadioButtonFor(x => x.Type, 2) #:ID Number / Company Number<br/>
#Html.RadioButtonFor(x => x.Type, 3) #:Surname / Company Name<br />
#Html.TextBoxFor(model => model.SearchString)
</div>
<p>
<input type="submit" value="Search" />
</p>
No javascript needed... imagine that :)

How to set value to a hidden property with a button?

I have the following files:
view.jsp
<# page import=...
<bean:define id="mForm" name="myForm" type="MyForm"/>
<html:form action="MyFoo" method="post" styleId="myForm" enctype="multipart/form-data">
<html:hidden property="boo"/>
<input type="button" value="Press me" onclick="javascript:changeBoo()"/>
</html:form>
MyForm.java
class MyForm {
private boolean boo;
public void setBoo(boolean boo){
this.boo = boo;
}
public boolean getBoo(){
return this.boo;
}
}
MyFooAction.java
public class MyFooAction extends BaseAction {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
ActionForward aForward = null;
String forward = "success";
try {
MyForm myForm = (MyForm) form;
String boo = (String)request.getParameter("boo");
if(boo.equals("true")){
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>DONE");
}
else {
//some code here
}
aForward = mapping.findForward(forward);
}
catch (Exception e) {
throw new Exception();
}
return aForward;
}
}
The question is how to implement changeBoo() in Javascript in order to change the value of boo and to invoke MyFooAction with correct value of boo?
First, change your button to type="submit". That will take care of submitting the form for you. Notice how changeBoo() now returns a value for your onclick attribute. This will submit the form if your function returns true.
Also, you'll need to add an id attribute to your hidden field so that you can easily get a reference to it from javascript:
<html:hidden property="boo" id="booId" />
<input type="submit" value="Press me" onclick="return changeBoo();"/>
Then it's just a matter of creating the javascript function:
function changeBoo(){
var boo = document.getElementById('booId');
boo.value = 'The new value';
return true;
}
PS On your <html:form>...</html:form>, make sure you have a way to submit a form. This is usually done by adding <html:submit>.
Now, to come back to your question, your Javascript function will be like this (assuming that your ActionForm name specified on struts-config.xml is "myForm").
fumction changeBoo() {
var boo = document.myForm.boo;
if ("true" == boo.value.toLowerCase() || "yes" == boo.value.toLowerCase() || "1" == boo.value.toLowerCase()) {
boo.value = "false";
} else {
boo.value = "true";
}
}
Bear in mind that Struts converts boolean values to "true" or "false", "yes" or "no", "0" or "1".

Categories

Resources