Blazor Server - How to disable a control inside component? - blazor-server-side

I try to show and disable a button by adding "disabled" class in a component (pre-hidden) but failed - it's not working. Where I did wrong ?
Component Button :
<div class="form-group">
<button class="btn btn-primary #disabled">Disable Me</button>
</div>
#code {
string _password;
string disabled = "";
public void Disable()
{
disabled = "disabled";
}
}
Index.razor :
#page "/"
<h1>How to disable button in component ?</h1>
<button class="btn btn-primary" #onclick="ShowAndDisableButton">Show and disable button</button>
#if (show)
{
<ButtonComponent #ref="button"/>
}
#code
{
ButtonComponent button = new();
bool show = false;
void ShowAndDisableButton()
{
show = true;
button.Disable();
}
}
UPDATED : if I change the ShowAndDisableButton code to
async Task ShowAndDisableButton()
{
show = true;
await Task.Delay(TimeSpan.FromMilliseconds(10)); // add this, wait for a while
button.Disable();
}
and change button code in index.razor to
<button class="btn btn-primary" #onclick="()=>ShowAndDisableButton()">Show and disable button</button>
it works. but I don't know why and don't want to use such way, are there any proper way?

The problem is that button.Disable(); does not cause the normal rerendering.
And it is an overcomoplicated way of doing things.
In the page:
#if (show)
{
<ButtonComponent Disabled="!show" />
}
#code
{
//ButtonComponent button = new();
bool show = false;
void ShowAndDisableButton()
{
show = true;
//button.Disable();
}
}
and the button itself:
<div class="form-group">
<button class="btn btn-primary" disabled="Disabled">Disable Me</button>
</div>
#code {
string _password;
// string disabled = "";
[Parameter] public bool Disabled {get; set; }
}
But you won't be able to use this button.

Use disabled="#disabledState"
Where disabledState is a boolean

Related

Blazor server component isn't rerendered

I have a Blazor Server (.NETv5) application with a search page.
On this page I have a form to search by name.
On the form submit event I call the search method of my child component.
That component is doing the actual search. This is working fine.
Because the search might take a few seconds I want to show a spinner when the search starts and hide it when the search is done.
Also when I do a second search I want to hide the previous search results.
Hiding the spinner and showing the search results is working, but showing the spinner before the search doesn't work. The variable is set correctly but the page is not rerendered (I think).
My page:
<div class="container pt-2 mb-3">
<RadzenTemplateForm Data="#searchTerms" Submit="#((SearchTerms args) => { Submit(args); })">
<div class="row">
<div class="mb-2 col-6 pl-0">
<RadzenLabel Text="Name" />
<RadzenTextBox class="col-12" Name="Name" #bind-Value="searchTerms.Name"/>
</div>
</div>
<div class="row">
<div class="col-md-12 mt-3">
<RadzenButton ButtonType="ButtonType.Submit" Icon="search" Text="Search" Disabled="#isSearching" />
<RadzenButton ButtonStyle="ButtonStyle.Light" Icon="cancel" Text="Cancel" Click="#Cancel" class="ml-2"/>
</div>
</div>
</RadzenTemplateForm>
</div>
<SearchResultsComponent #ref="searchResultsComponent" />
protected SearchTerms searchTerms = new();
protected SearchResultsComponent searchResultsComponent;
protected bool isSearching;
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
// Disable submit button ==> NOT WORKING:
isSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
// Enable submit button again:
isSearching = false;
}
protected void Cancel()
{
// Reset form:
searchTerms = new SearchTerms();
}
My child component:
<div class="container">
#if (isSearching)
{
<div class="spinner-border text-primary mr-2" role="status">
<span class="sr-only">Searching...</span>
</div>
<b>Searching ...</b>
}
#if (noResults)
{
<div class="alert alert-warning" role="alert">
No results.
</div>
}
#if (getSearchResults != null && getSearchResults.Any())
{
<RadzenHeading Size="H2" Text=#($"Results({getSearchResults.Count})")></RadzenHeading>
<div class="row">
#foreach (var searchResult in getSearchResults)
{
<RadzenCard>
<b>#searchResult.Name</b>
</RadzenCard>
}
</div>
}
</div>
private IList<MultiShardSearchResultsWerknemer> _searchResults;
private bool _isSearching = true;
private bool _noResults;
protected bool noResults
{
get => _noResults;
set
{
if (Equals(_noResults, value)) return;
_noResults = value;
InvokeAsync(() => StateHasChanged());
}
}
protected bool isSearching
{
get => _isSearching;
set
{
if (Equals(_isSearching, value)) return;
_isSearching = value;
InvokeAsync(() => StateHasChanged());
}
}
protected IList<MultiShardSearchResultsWerknemer> getSearchResults
{
get => _searchResults;
set
{
if (Equals(_searchResults, value)) return;
_searchResults = value;
InvokeAsync(() => StateHasChanged());
}
}
public void Search(SearchTerms args)
{
Helpers.ConsoleLog(args);
if (string.IsNullOrEmpty(args.Name)) return;
// Reset ==> NOT WORKING:
isSearching = true;
noResults = false;
getSearchResults = null;
InvokeAsync(() => StateHasChanged());
getSearchResults = ShardingService.SearchForAllEmployees(args.Name, null).GetAwaiter().GetResult();
Helpers.ConsoleLog("Found results: " + getSearchResults.Count);
isSearching = false;
noResults = !getSearchResults.Any();
}
For debugging purposes, I've set _isSearching = true which shows me the spinner. The spinner is also hidden when the search is done, so that is working. But I can't get the spinner to show when I start searching.
I've tried all options I could find, without success.
I must be missing something. Please advice.
Have a look at your search handel method
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
// Disable submit button ==> NOT WORKING:
isSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
// Enable submit button again:
isSearching = false;
}
Keep in mind, that rendering will occur once the method has finished. So, before the call isSearching is false and after it is also false. That's why you don't see the spinner.
Blazor offers a method to kick off a new render cycle: StateHasChanged().
So, you could modify your submit method like
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
isSearching = true;
StateHasChanged()
// Call search method on child component
searchResultsComponent.Search(args);
isSearching = false;
StateHasChanged()
}
So, you click the search/submit button on this method is executed.
Or if you like, create a property instead
#code
{
private Boolean isSearching = false;
public Boolean IsSearching
{
get => isSearching;
private set
{
isSearching = value;
StateHasChanged();
}
}
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
IsSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
IsSearching = false;
}
}
I haven't tested it but faced a similar problems once.

How to implement the inline editing for this grid using devexpress blazor

I have a created a grid using devexpress blazor. I want to implement an inline editing for this grid, although devexpress haven't implemented any inline editing functionality.
Here is ht code i have used for this Grid.
#if (dischargeBoards == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="card demo-card-wide border-bottom-0">
<div class="card-header border-bottom-0">
ClientVisitGuid: <b>#(selectedRow == null ? (object)"(none)" : selectedRow.ClientVisitGuid)</b>
CurrentLocationGUID: <b>#(selectedRow == null ? (object)"(none)" : selectedRow.CurrentLocationGUID)</b>
PatientName: <b>#(selectedRow == null ? "(none)" : selectedRow.PatientName)</b>
</div>
</div>
<DxDataGrid Data="#dischargeBoards"
ShowFilterRow="false"
#bind-SingleSelectedDataRow="#SelectedRow"
        
        ShowPager="true"
        ShowGroupPanel="true"
        PageSize="19">
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.LocationGroup)" Caption="L/C Group" AllowGroup="true"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Location)" Width="100px"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.PatientName)" SortIndex="0" Width="240px"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Service)" Width="240px"></DxDataGridColumn>
<DxDataGridDateEditColumn Field="#nameof(DischargeBoardVisit.DischargeDateExp)"
                 DisplayFormatString="MM/dd/yyyy"
                 EditFormatString="d"></DxDataGridDateEditColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.DischargeTimeExp)" Caption="Time Exp"></DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Family)" Caption="Family">
<DisplayTemplate>
#{
var checkboxFamily = (context as DischargeBoardVisit).Family;
if (checkboxFamily)
{
<button class="btn btn-success btn-circle btn-circle-sm m-1">#*<i class="fa fa-check"></i>*#</button>
}
else
{
<button class="btn btn-danger btn-circle btn-circle-sm m-1"></button>
}
}
</DisplayTemplate>
</DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Rehab)">
<DisplayTemplate>
#{
var checkboxRehab = (context as DischargeBoardVisit).Rehab;
//<input type="checkbox" checked="#checkboxRehab" disabled readonly />
if (checkboxRehab)
{
<button class="btn btn-success btn-circle btn-circle-sm m-1">#*<i class="fa fa-check"></i>*#</button>
}
else
{
<button class="btn btn-danger btn-circle btn-circle-sm m-1"></button>
}
}
</DisplayTemplate>
</DxDataGridColumn>
<DxDataGridColumn Field="#nameof(DischargeBoardVisit.Comment)" Width="240px"></DxDataGridColumn>
</DxDataGrid>
}
#code {
DischargeBoardVisit[] dischargeBoards;
// readonly string _id = Guid.NewGuid().ToString();
[Parameter]
public string DisplayFormatString { get; set; }
protected override async Task OnInitializedAsync()
{
dischargeBoards = await DischageBoard.GetDischargeAsync();
SelectedRow = dischargeBoards.FirstOrDefault();
}
public DischargeBoardVisit context { get; set; }
DischargeBoardVisit selectedRow;
public DischargeBoardVisit SelectedRow
{
get
{
return selectedRow;
}
set
{
selectedRow = value;
InvokeAsync(StateHasChanged);
}
}
}
I haved tried in different ways. But i couldn't find any proper solution for that.
thanks in advance. All appreciation would be highly appreciated.
Currently, there is no straightforward way to do so. That's why I suggest that you wait until this feature is implemented since it is in the Backlog of the DevExpress Blazor team. Please follow the updates in the blogs.

Aurelia Dialog not returning response after using dropdown

I'm using the aurelia-dialog plugin to allow users to generate a set of objects, and want the dialog's response to return the chosen objects.
The workflow is that the list of options is generated from an API call using a promise when the activate() method is called on the dialog. The options are then displayed to the user, and selected from a dropdown. The user then clicks ok and the response should be sent back. Here is the code that is supposed to accomplish it:
this.ds.open({
viewModel: MyModal,
model: {
"title": "Select Objects",
"message": "I hate this modal"
}
}).then(response => {
console.log("closed modal");
console.log(response);
if (!response.wasCancelled) {
console.log('OK');
} else {
console.log('cancelled');
}
console.log(response.output);
});
And then in the modal.js:
import {inject} from 'aurelia-framework';
import {DialogController} from 'aurelia-dialog';
import {ModalAPI} from './../apis/modal-api';
//#inject(ModalAPI, DialogController)
export class MyModal {
static inject = [DialogController, ModalAPI];
constructor(controller, api){
this.controller = controller;
this.api = api;
controller.settings.centerHorizontalOnly = true;
}
activate(args){
this.title = args.title;
this.message = args.message;
this.returnedSet = null;
this.historicSetName = null;
this.reportHist = null;
return this.api.getReportHistory().then(reports => {
this.reportHist = reports;
});
}
selectHistoricReport() {
console.log(this.historicSetName);
if(this.historicSetName == "Select a report...") {
this.returnedSet = null;
} else {
var selectedReport = this.reportHist.filter(x => x.name == this.historicSetName)[0];
this.returnedSet = selectedReport.rsids;
}
console.log(this.returnedSet);
}
ok(returnedSet) {
console.log(returnedSet);
this.controller.ok(returnedSet);
}
}
And then the html:
<template>
<require from="../css/upload-set.css"></require>
<ai-dialog class="selector panel panel-primary">
<ai-dialog-header class="panel-heading">
<button type="button" class="close" click.trigger="controller.cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">${title}</h4>
</ai-dialog-header>
<ai-dialog-body class="panel-body container-fluid">
<div class="row">
<div class="col-sm-6">
<label>Report: </label>
<select value.bind="historicSetName" change.delegate="selectHistoricReport()" class="input-md form-control">
<option ref="historicSetPlaceholder">Select a report...</option>
<option repeat.for="historicReport of reportHist">${historicReport.name}</option>
</select>
</div>
</div>
</ai-dialog-body>
<ai-dialog-footer>
<button click.trigger="controller.cancel()">Cancel</button>
<button click.trigger="ok(returnedSet)">Save</button>
</ai-dialog-footer>
</ai-dialog>
</template>
As long as I don't touch the dropdown, the dialog will return a null (or any other value I initialize returnedSet to). However, as soon as I click on the dropdown, clicking either the Save or Cancel button leads to nothing being returned and the console.log lines at the end of my first code block just get skipped. I also tried removing the click.delegate line from my HTML, but that didn't change anything.
Anyone have any idea why this might be happening?
Also, I found this post(Aurelia Dialog and Handling Button Events) with an extremely similar problem, but can't seem to find any solution in there as to what I should do.
Thanks in advance.

Toggle button class using Directive in Angular

I want to toggle a buttons class depending on the state of a form in Angular. The template is based on Bootstrap.
I've setup a directive called IsDirty.
If the form has the class 'ng-valid', add the class 'btn-success' to the submit button.
Alternatively, if the form is dirty and has the class 'ng-dirty', remove the class 'btn-success' from the submit button.
So far this is what I have but it doesn't work.
var angular = require('angular');
angular.module('myApp')
.directive('isDirty', [function() {
return {
restrict: 'E',
link: function(scope, element, attrs, ctrl) {
var submitButton = element.find('.btn-primary');
if(element.hasClass('ng-valid')) {
submitButton.addClass('btn-success');
} else {
submitButton.removeClass('btn-success');
}
scope.$apply();
}
};
}]);
My form:
<form is-dirty class="form-horizontal" role="form" name="profileForm">
<!-- INPUT FIELDS HERE //-->
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="reset" class="btn btn-default">Cancel</button>
</form>
This should hopefully fix your problem
.directive('ngDirty', [function() {
return {
restrict: 'AE',
link: function(scope, element, attrs, ctrl) {
var submitButton = element[0].querySelector('.btn-primary');
if(element.hasClass('ng-valid')) {
submitButton.classList.add("btn-danger");
} else {
submitButton.classList.remove("btn-danger");
}
}
};
}]);
Plnkr Example
Update:
It's a little dirty but it seems to work and checks each input, you must bind each input to an ng-model though I have used $scope.input
New Plnkr
2nd Update
I have removed the function and brought in a $timeout you will see from the example how it works.
New update with a $timeout as function
Use ngClass for this (btw, I am confused with your class names. In your description you say add/remove the class .btn-success but in the code you are adding/removing .btn-danger. So in the code below, I am sticking with .btn-success):
<form is-dirty class="form-horizontal" role="form" name="profileForm">
<!-- INPUT FIELDS HERE //-->
<button type="submit"
class="btn btn-primary"
ng-class="{'btn-success' : isValid}">
Save Changes
</button>
<button type="reset" class="btn btn-default">Cancel</button>
</form>
And in your directive:
var angular = require('angular');
angular.module('myApp')
.directive('form', [function() {
return {
restrict: 'EA',
link: function(scope, element, attrs, ctrl) {
scope.isValid = element.hasClass('ng-valid');
}
};
}]);
I would further suggest that you actually make the class ng-valid itself with ng-class and use the variable scope.isValid to change between ng-valid and isDirty.

Hot Towel SPA Modal Dialog knockout bindings and validation

I'm building a SPA using the Hot Towel SPA template.
Here's my problem:
I have a view where I put the information related to a Venue (Name, Description, Address, etc.)
Associated with the Venue there are different Rooms with their own fields (Name, Description, Type, etc.)
I have a list of Rooms and a button "Add New Room".When I hit the button, a modal dialog pops up, I fill the form with the requested information then I save. After the dialog is closed the list gets updated.
I am able to retrieve the information from the dialog, but I'm not able to trigger the validation rules if the fields are left blank. Also the datacontext.HasChanges() returns always true.
Is it possible to use the modal dialog like any other view?
Here's part of the code I am using:
From Model.cs:
public class Venue
{
[Key]
public int VenueId { get; set; }
[Required(ErrorMessage = "Venue Name is required.")]
[Display(Name = "Venue Name")]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Room> Fields { get; set; }
...
}
public class Room
{
[Key]
public int RoomId { get; set; }
[Required(ErrorMessage = "Name is required.")]
[Display(Name = "Name")]
public string Name { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public int VenueId { get; set; }
public virtual Venue Venue { get; set; }
...
}
From venuedetail.js:
define(['services/datacontext',
'services/model',
'durandal/plugins/router',
'durandal/system',
'durandal/app',
'services/logger',
'viewmodels/roommodal'],
function (datacontext, model, router, system, app, logger, roommodal) {
...
var addRoom = function () {
var newRoom= datacontext.manager.createEntity("Room");
roommodal.room = newRoom;
app.showModal(roommodal).then(function (response) {
if (response) {
}
return true;
});
};
...
From roommodal.js:
define(['durandal/app', 'services/datacontext', 'durandal/system', 'durandal/plugins/router', 'services/logger'],
function (app, datacontext, system, router, logger) {
var isSaving = ko.observable(false);
var room= ko.observable();
activate = function(routeData) {
return true;
};
hasChanges = ko.computed(function() {
return datacontext.hasChanges(); // Always true ?
});
canSave = ko.computed(function() {
return hasChanges() && !isSaving();
});
canDeactivate = function () {
return true;
};
var save = function(dialogResult) {
this.modal.close(dialogResult);
};
var cancel = function() {
this.modal.close(false);
};
var vm = {
activate: activate,
save: save,
canSave: canSave,
cancel: cancel,
canDeactivate: canDeactivate,
room: room,
hasChanges: hasChanges,
title: 'Add room'
};
return vm;
From roommodal.html:
<div class="messageBox">
<div class="modal-header">
<h3 data-bind="text: title"></h3>
<i class="icon-asterisk" data-bind="visible: hasChanges"></i>
</div>
<div class="modal-body">
<div data-bind="with: room">
<label for="name">Name</label>
<input id="name" data-bind="value: name, valueUpdate: 'afterkeydown'"
placeholder="Enter name" />
<div>
<label>Description</label>
<textarea data-bind="value: description"
placeholder="Enter description"></textarea>
</div>
<div>
<label>Notes</label>
<textarea data-bind="value: notes"
placeholder="Enter notes"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-info"
data-bind="click: cancel, enable: canSave">
<i class="icon-undo"></i>Cancel</button>
<button class="btn btn-info"
data-bind="click: save, enable: canSave">
<i class="icon-save"></i>Save</button>
</div>
Any help will be greatly appreciated.
Thanks in advance.
Validation is triggered when you try to save using datacontext.saveChanges() which you don't do in this piece of code, instead, you are just closing the modal window.
Easiest way to see if saving was successful is to check HasChanges afterwards - it should be false if everything went well, otherwise it will be true.
datacontext.HasChanges() returns true when you enter modal window because you create an entity and place it in your context before opening the modal window. You can only ignore HasChanges and silently CancelChanges before closing the modal window.