EditForm in Blazor app have multiple submit buttons - html

I have a simple Blazor Editform where i have multiple buttons with different navigations & toast notifications. I have OnValidSubmit attached to Editform. Now the validations are working for all the buttons. I have added onclick() to trigger button functions but I want onclick to be triggered only if user has entered all the details. Hope I have explained well. Please let me know for additional input.
Current output for Forward or Next buttons are : if No values entered -> Correct validation(asked to fill in details) -> forward notification displayed.
Expected output :
if No values entered -> Correct validation(asked to fill in details).
if All values entered -> Correct validation -> forward notification displayed.
Here is some code:
<EditForm EditContext="#editContext" OnValidSubmit="HandleValidSubmit" #onreset="HandleReset">
<DataAnnotationsValidator />
<div class="form-row">
<div class="form-group col">
<label>Role</label><br />
<InputRadioGroup #bind-Value="model.Role" class="form-control">
#foreach (var option in rdOptions)
{
<InputRadio Value="option" /> #option
<text> </text>
}
</InputRadioGroup>
<ValidationMessage For="#(() => model.Role)" />
</div>
<div class="form-group col">
<label>Company Name</label>
<InputSelect id="txtCompanyName" class="form-control" #bind-Value="#model.CompanyName">
<option selected value="-1">-Select-</option>
<option value="CompanyName1">CompanyName1</option>
<option value="CompanyName2">CompanyName2</option>
</InputSelect>
<ValidationMessage For="#(() => model.CompanyName)" />
</div>
</div>
<div class="form-row">
<div class="text-left col-3">
<button type="submit" class="btn btn-primary btn-success">Save</button>
</div>
<div class="text-right col-3">
<button type="submit" class="btn btn-primary" #onclick="#Forward">Forward</button>
</div>
<div class="text-right col-3">
<button type="submit" class="btn btn-primary" #onclick="#Review">Next</button>
</div>
<div class="text-right col-3">
<button type="reset" class="btn btn-secondary">Clear</button>
</div>
</div>
</EditForm>
code section:
#code {
private Model model = new Model();
private EditContext editContext;
List<Model> models = new();
protected override void OnInitialized()
{
editContext = new EditContext(model);
}
private void HandleValidSubmit()
{
var modelJson = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
JSRuntime.InvokeVoidAsync("alert", $"SUCCESS!! :-)\n\n{modelJson}");
toastService.ShowSuccess("saved successfully!");
}
private void Forward()
{
toastService.ShowInfo("Forwarded!!");
}
private void Review()
{
toastService.ShowInfo("Review!!");
}
private void HandleReset()
{
model = new Model();
editContext = new EditContext(model);
}
}

Change type="submit" to type="button"
Except maybe for the Save button.

You can do validation manually in your button event handlers and then not use the EditForm OnValidSubmit, and set the button types to button.
...
if (editContext.Validate())
go
else
alert
...
FYI - The relevant bit of code from EditForm looks like this:
private async Task HandleSubmitAsync()
{
Debug.Assert(_editContext != null);
if (OnSubmit.HasDelegate)
{
// When using OnSubmit, the developer takes control of the validation lifecycle
await OnSubmit.InvokeAsync(_editContext);
}
else
{
// Otherwise, the system implicitly runs validation on form submission
var isValid = _editContext.Validate(); // This will likely become ValidateAsync later
if (isValid && OnValidSubmit.HasDelegate)
{
await OnValidSubmit.InvokeAsync(_editContext);
}
if (!isValid && OnInvalidSubmit.HasDelegate)
{
await OnInvalidSubmit.InvokeAsync(_editContext);
}
}
}

In my opinion you will need to use JavaScript to check the inputs were filled or not and based on that you can disable or enable the submit button

Related

razor-pages Delete functional doesn't work, how to delete?

I'm currently learning RazorPages in codeacademy.com.
I did everything that was shown in the video Tutorial, and unfortunately is doesn't work:
The task for the project:
"Data Delete
The current project has a button on the Index page list that deletes the current Continent or Country. The button will be modified to call a discrete Delete.cshtml page. This page will display the current record for review and provide a Delete button. Once the deletion occurs, the user is redirected back to the list so they get visual feedback of a successful task.
The code and markup are easily copied from the existing Detail.cshtml page. After copying that page, we add a delete button and copy the necessary statements from the OnPostAsync() method in the Index.cshtml.cs page."
The Delete page was created. The problem is:
When I press button Delete on the Delete page I have redirection to this link in browser:
https://localhost/Continents/Delete/SA?Continent.ID=SA
Actually no Delete happends
No redirection
What mistakes maybe here?
The code Delete.cshtml:
#page "{id}"
#model DeleteModel
#{
ViewData["Title"] = "Continent Delete";
}
<div class="jumbotron p-3">
<div class="d-flex align-items-center">
<h1 class="display-4 flex-grow-1">
Continent Delete
</h1>
<a class="btn btn-primary btn-sm" asp-page="./Index">
Back to List
</a>
</div>
</div>
[enter image description here](https://i.stack.imgur.com/tFnrX.jpg)
<div class="d-flex">
<div class="p-2 bg-primary text-white text-right" style="flex:0 0 15%">
#Html.DisplayNameFor(model => model.Continent.ID)
</div>
<div class="p-2 border-top border-right border-bottom border-primary" style="flex:1 0 auto">
#Html.DisplayFor(model => model.Continent.ID)
</div>
</div>
<div class="d-flex">
<div class="p-2 bg-primary text-white text-right" style="flex:0 0 15%">
#Html.DisplayNameFor(model => model.Continent.Name)
</div>
<div class="p-2 border-right border-bottom border-primary" style="flex:1 0 auto">
#Html.DisplayFor(model => model.Continent.Name)
</div>
</div>
<form metod="post" class="d-flex flex-row-reverse mt-3">
<input type="hidden" asp-for="Continent.ID"/>
<input type="submit" value="Delete" class="btn btn-danger btn-sm"/>
</form>
Delete.cshtml.cs:
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorCountry.Models;
using RazorCountry.Data;
namespace RazorCountry.Pages.Continents
{
public class DeleteModel : PageModel
{
private readonly CountryContext _context;
public DeleteModel(CountryContext context)
{
_context = context;
}
public Continent Continent { get; set; }
public async Task<IActionResult> OnGetAsync(string id)
{
Continent = await _context.Continents
.Include(c => c.Countries)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Continent == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(string id)
{
if (id == null)
{
return NotFound();
}
// Find the continent
Continent Continent = await _context.Continents.FindAsync(id);
//Delete the continent
if (Continent != null)
{
_context.Continents.Remove(Continent);
}
//Persist Changes
await _context.SaveChangesAsync();
//Redirect to the user
return RedirectToPage("./Index");
}
}
}
Model Continent.cs:
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace RazorCountry.Models
{
public class Continent
{
[Required, StringLength(2, MinimumLength = 2), Display(Name = "Code")]
[RegularExpression(#"[A-Z]+", ErrorMessage = "Only upper case characters are allowed.")]
public string ID { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Country> Countries { get; set; }
}
}
Try to understand how RazorPages functional works, try to fix mistake in a correct way
The reason that no error occurs is because your OnPostAsync method is not being hit. You have mis-spelled the method attribute in your form (you have metod) so submitting it generates the default GET request and your OnGetAsync handler is executed instead.
Once you have corrected that error, you still have an unresolved issue.
Your OnPostAsync handler expects a parameter named id. The hidden field tag helper in your form generates a parameter with a name of Continent.ID. Consequently the value will not be bound to the id parameter. The simplest solution is to do away with the tag helper in the form a the bottom of your Delete page and replace it with a plain HTML input with a name attribute:
<form method="post" class="d-flex flex-row-reverse mt-3">
<input type="hidden" name="id" value="#Continent.ID"/>
<input type="submit" value="Delete" class="btn btn-danger btn-sm"/>
</form>
You can read more about model binding in general here: https://www.learnrazorpages.com/razor-pages/model-binding
Thanks to #MikeBrind finally, I changed the code
<form method="post" class="d-flex flex-row-reverse mt-3">
<input type="hidden" name="id" value="#Model.Continent.ID"/>
<input type="submit" value="Delete" class="btn btn-danger btn-sm"/>
</form>
And it works!

Angular 13 - How to open form without button and the button click

My angular component has a tree and several nodes. When I double click on a node the click event runs a web api and retrieves data for an id that will be used to create a dynamic form using npm package: #rxweb/reactive-dynamic-forms. Once the data request is 'completed' a button appears and when clicked it opens the form with appropriate fields for the id selected. I would like to eliminate the need for this secondary button click. I've tried several suggestions but just cannot get anything to work.
I'm using Infragistics controls and bootstrap for the form.
html:
<div class="column-layout my-pane-layout">
<div *ngIf = "isShowFormButton" >
<button #open igxButton="raised" igxRipple="white" (click)="form.open()">Run</button>
<igx-dialog #form [closeOnOutsideSelect]="true" >
<igx-dialog-title>
<div class="dialog-container">
<igx-icon>vpn_key</igx-icon>
<div class="dialog-title">Form</div>
</div>
</igx-dialog-title>
<form class="input-group-form" [formGroup]="dynamicForm.formGroup" (ngSubmit)="onSubmit()">
<div class="container">
<div class="controls" viewMode="horizontal" [rxwebDynamicForm]="dynamicForm" [uiBindings]="uiBindings">
</div>
<button igxButton="raised" type="submit" igxRipple class="button" [disabled]="!dynamicForm.formGroup.valid">
<igx-icon>
directions_run
</igx-icon>
<span>Submit</span>
</button>
</div>
</form>
<div igxDialogActions>
<!-- <button igxButton (click)="form.close()">CANCEL</button> -->
<button igxButton (click)="form.close()">Submit</button>
</div>
</igx-dialog>
</div>
<h6 class="h6">
Levels
</h6>
<igx-tree #tree class="tree" selection="None" >
<igx-tree-node *ngFor="let level1 of myData" [data]="level1">
{{ level1.Name }}
<igx-tree-node *ngFor="let level2 of level1.levels" [data]="level2">
{{ level2.Name }}
<igx-tree-node *ngFor="let level3 of level2.levelplus" [data]="level3" (dblclick)="onDoubleClick($event,level3)">
{{level3.Name }}
</igx-tree-node>
</igx-tree-node>
</igx-tree-node>
</igx-tree>
</div>
XYZ.component.ts:
export class XYZComponent implements OnInit {
#ViewChild('form') dialog: IgxDialogComponent;
myData: any[];
public tree: IgxTreeComponent;
public selectedNode;
public ID: number = 2;
isShowRunButton: boolean = false;
public dynamicForm!: DynamicFormBuildConfig;
public dynamicFormConfiguration!: DynamicFormConfiguration;
constructor(private dynamicFormBuilder:RxDynamicFormBuilder){}
ngOnInit() {
populate tree with data here ...
}
public onDoubleClick(event,node) {
console.log(node);
event.stopPropagation();
this.runParameters(node.Id);
}
public runParameters(Id) {
this.aSer.getApi(Id).subscribe({next:(data: any[]) => {this.myData = data;},
error: err => {console.log(err); },
complete: () => {
this.dynamicForm =
this.dynamicFormBuilder.formGroup(this.myData,this.dynamicFormConfiguration);
this.isShowFormButton = true;
//this.dialog.open();
}
});
}
public onSubmit() {
console.log(this.dynamicForm.formGroup);
this.isShowFormButton= false;
//this.dialog.open();
}
}
If I uncomment out the 'this.dialog.open()' the code throws the following error:
TypeError: Cannot read properties of undefined (reading 'open')
Many postings say that I need to use a #ViewChild but it seems that it cannot find that reference : #ViewChild('form') dialog: IgxDialogComponent;
Any help would be much appreciated. Code works fine with the 'Run' button click but I want to eliminate that extra step.

Modal ajax.form validation ASP.NET MVC, error message not showing, modal not closing after succesful submit

i'm new to the ASP.net and i get easilly confused when jquery and Ajax get in the middle of a code.
I have a popup bootstrap Modal that show when i click on a button, the Modal contains an Ajax.form that allows me to create projects, i'm using HTML.validation to check if my form is filled correctly. I have two main problems :
When I enter valid informations in my form and i submit the form, the project is created succesfully but the Modal doesn't close automaticlly.
When i enter non-valid informations when i submit no message error apear, nonetheless when I close the modal and open it again, the errors apear.
I need your help to :
Close the modal once the submit form is valid.
Have the error message show in my modal form.
*This is a part of my main view :
enter code here
<!--validation script -->
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<!-- Button trigger modal -->
<div>
<div>
<a class="btn btn-red ms-auto my-auto h-50"
href="#Url.Action("createProject", "ProjectSelection")" title="Nouveau projet"
data-bs-toggle="modal" data-bs-target="#Modal_NewProject"
ajax-target-id="Body_NewProject" data-bs-loader="Loader_NewProject">
<i class="feather-16" data-feather="plus"></i>
Créer projet
</a>
</div>
</div>
<br />
<!-- Modal View -->
<div class="modal fade" id="Modal_NewProject" tabindex="-1"
aria-labelledby="Label_NewProject" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="Label_NewProject">
Créer un Projet</h5>
<button type="button" class="btn-close"
data-bs-dismiss="modal" aria-label="Close"></button>
</div>
#using (Ajax.BeginForm("createProjectPost",
"ProjectSelection", new AjaxOptions { HttpMethod = "POST",
UpdateTargetId = "Body_NewProject" }, new { id = "projectForm" }))
{
<div id="Body_NewProject">
<div class="spinner-border" role="status"
id="Loader_NewProject">
<span class="visually-hidden">Loading...</span>
</div>
<!-- partial view "createProject" -->
</div>
}
</div>
</div>
</div>
<!---->
*And this is my partial view that containts the elements of the form
<div class="modal-body" id="frmProject">
<div class="input-group mb-3" id="">
#Html.DropDownListFor(m => m.Project.Domain,
new SelectList(Model.Domains, "IdDomain", "Name"),"Selectionner un domaine",
new { #class = "w-100", #id = "DomainSelection" })
#Html.ValidationMessageFor(m => m.Project.Domain, null,
new { style = "color: red;" })
</div>
<div class="input-group mb-3 mb-3 " id="teamSelection">
#Html.DropDownListFor(m => m.Project.Teams,
new SelectList(Model.Teams.Select(teamElement => teamElement.Name).ToList()),
"Selectionner une équipe", new { #class = "w-100" })
#Html.ValidationMessageFor(m => m.Project.Teams, null,
new { style = "color: red;" })
</div>
<div class="form-floating mb-3">
<input class="form-control " data-val-required="Le champ Projet est requis."
id="Project.Name" name="Project.Name" type="text" value="">
<label for="Project.Name" class="">Nom du projet</label>
#Html.ValidationMessageFor(m => m.Project.Name, null,
new { style = "color: red;" })
</div>
</div>
<div class="modal-footer mb-0">
<div class="panel-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal"
type="button">Annuler</button>
<button type="submit" form="projectForm"
class="btn btn-red">Valider</button>
<div class="col-xs-10" id="lblstatus"></div>
</div>
</div>
*This is my Controller code
public ActionResult createProject()
{
//initialization code
return PartialView();
}
[HttpPost]
[ValidateAntiForgeryToken]
[HandleError]
public ActionResult createProjectPost(Project project)
{
#region Variable verification
if (!ModelState.IsValid)
{
Session["Error"] = true;
if (project.Domain == null)
{
Session["DomainError"] = true;
}
if (project.Teams == null)
{
Session["TeamError"] = true;
}
if (project.Name == null)
{
Session["NameError"] = true;
}
ViewData["project"] = "create";
//return View("ProjectSelectionPage");
return PartialView(project);
}
#endregion
#region Initialisation
//initilization code
#endregion
#region Check possible copy of the project
if (projectDB.Exist(project.Name))
{
//Check that this project doesn't exist in the database
//Create a session variable that will trigger an error message
Session["Error"] = true;
Session["NameCopy"] = true;
ViewData["project"] = "create";
return PartialView(project);
}
#endregion
#region Project to database and generate name
//Add the project to the database
#endregion
return PartialView();
}
It's been several days since i'm searching for an answer, i either missed one or didn't use it correctly.
enter image description here
Thank you

taking input from button in thymeleaf

I am displaying a form to user with some details and then user clicks approve and reject. In the backend, I want to take this in one property - userAction, which can be "approve" or "reject".
How can I add input to the buttons that user clicks? And then this input is part of the object requestDto.
<form th:action="#{/mission/store/{uuid}(uuid=${uuid})}" th:object="${requestDto}" method="post" class="mission_form">
<div class="wizard-header">
<h3 class="wizard-title">
Approve Mission
</h3>
<h5>Should you chose to accept this mission, press approve.</h5>
</div>
<div class="wizard-footer">
<!--<div class="pull-right">-->
<input type='button' class="btn btn-success" name='approve' value='Approve' />
<!--</div>-->
<div class="pull-left">
<input type='button' th:field="*{}" class='btn btn-danger' name='previous' value='Decline' />
</div>
<div class="clearfix"></div>
</div>
</form>
First define a proper DataTransferObject:
public class MyRequestDto {
private String userAction;
// don't forget getters and setters
}
Then add a object of that class to your model
// if you are return a M&V-object:
ModelAndView mv = new ModelAndView("viewName")
ModelAndView.addObject("requestDto", new MyRequestDto());
// if you define a Model-Object as input-parameter:
Model.addAttribute("requestDto", new MyRequestDto());
Define a form like this (I used button-elements). The point is not to use the th:field attribute:
<form th:action="...." method="POST" th:object="${requestDto}">
<button name="userAction" value="approve" >Approve</button>
<button name="userAction" value="reject" >Reject</button>
</form>
Recieve the DataTransferObject by adding this to your controllers input-paramters (the controller that handels the post request):
... #Valid RequestDto requestDto, BindingResult bindingResult, ....
Now you can access requestDto's userAction-Attribute. The value is approve if you click the first button and it is reject if you click the second button. First you can check if there are binding errors by checking bindingResults.hasErrors().

How to change ng-include inside firebase firestore function

When I click on continue button it checks clients ID in firestore database and if ID does'nt exist then $scope.filePath = "createClient.htm" is called. everything is working fine but when I call this piece of code inside firebase function nothing happens
Inside HTML
<div ng-include="filePath" class="ng-scope">
<div class="offset-1 offset-sm-2 col-11 col-sm-7">
<h2>Enter Client ID</h2>
<br>
<div class="form-group">
<label for="exampleInputEmail1">ID</label>
<input id="client_id" type="text" class="form-control" placeholder="Client ID">
</div>
<div class="float-right">
<button type="button" class="btn btn-danger">Cancel</button>
<button type="button" ng-click="searchClient()" class="btn btn-success">Continue</button>
</div>
</div>
</div>
Internal Script
<script>
$scope.searchClient = function()
{
//$scope.filePath = "createClient.htm"; it works here
var id= document.getElementById('client_id').value;
db.collection("clients") // db is firestore reference
.where("c_id","==",id)
.get()
.then(function(querySnapshot)
{
querySnapshot.forEach(function(doc)
{
if(!doc.exists)
{
console.log("Client Does'nt Exist");
$scope.filePath = "createClient.htm"; // but doesnt works here inside this firebase function
}
}
);
}
);
}
};
</script>
when console.log is shown and firebase function are fully executed then if I click on "Continue" Button again it works fine and content of createClient.htm is shown inside ng-include="filePath". why i need to click twice ?