#104014
Patrick Hume
Participant

I thought I would update this with a complete solution with a few weeks for a better UXI experience.

This ensures the password field is not red on page load, then if the user types in it and then deletes its contents it will then appear red to indicate that it is required the same behaviour as other required fields, also fix the styling so that the password *’s are indented and appears the same as the default blazor form-control class, also adds the field highlight when password field is selected. The submit button is only enabled when the complexity is ‘Strong’, and when the submit is clicked it displays a spinning bootstrap icon on the submit button and disables it to prevent re-clicking. Assumes the submit action will call a service that returns a result object or response, this can then be used to display the smart blazor badge to give feedback on the Ui to the user.

I hope this helps someone one day

complexityCheck.js

const symbols = /['<>@!#$%^&*()_+\]\[\{}?:;|'"\\,.\/~`\-=']+/;
const numerical = /.*[0-9].*/;
const upperCase = /.*[A-Z].*/;
const lowerCase = /.*[a-z].*/;

(function (global) {
    global.customStrength = function (dotNetObject) {
        const allowedSymbols = '<>@!#$%^&*()_+[]{}?:;|\'"\\,./~`-=';
        const passwordField = document.querySelectorAll("smart-password-text-box")[0];
        const passwordConfirm = document.querySelectorAll("smart-password-text-box")[1];
        // Remove the red border on the page load
        passwordConfirm.style.border = '';
        // Set the border to red if the field is cleared and outline green if the field has a value
        // Assumes that any value will be the same is the same as the first password field & relies on 
        // MVC data attestations [Compare( to enforce this at time of form submit
        passwordConfirm.addEventListener('change', function (event) {
            if (event.detail.value) {
                this.style.border = "#26b050 solid 1.5px";
            } else {
                this.style.border = "red solid 3px";
            }
        });
        passwordField.messages = {
            'en': {
                'passwordStrength': 'Password strength',
                'short': 'Short',
                'init': 'init',
                'weak': 'complexity Week - missing at least 1 symbol, ' + allowedSymbols,
                'far': 'complexity Fair - missing at least 1 numerical value, 0 - 9',
                'better': 'complexity Better - missing at least 1 upper case characture, A- Z',
                'good': 'complexity Good - missing at least 1 lower case characture, a - z',
                'strong': 'Strong =)',
                'showPassword': 'Show password'
            }
        };
        // indent password text to match the styling of other from controls 
        passwordField.style.textIndent = '0.7rem'
        setTimeout(function () {
            passwordField.passwordStrength = function (password, allowableSymbols) {
                const passwordLength = password.length;
                let message = null;
                //debugger;                
                passwordField.children[0].classList.remove("smart-password-better");
                if (passwordLength < 7) {
                    message = 'short';
                } else if (!new RegExp(symbols).exec(password)) {
                    message = 'weak';
                } else if (!new RegExp(numerical).exec(password)) {
                    message = 'far';
                } else if (!new RegExp(upperCase).exec(password)) {
                    message = 'better';
                } else if (!new RegExp(lowerCase).exec(password)) {
                    message = 'good';
                }
                //console.log(message);
                if (message) {
                    dotNetObject.invokeMethodAsync('CallbackComplexityCheck', false)
                    return message;
                }
                dotNetObject.invokeMethodAsync('CallbackComplexityCheck', true)
                return 'strong';
            }
        }, 0);
        // remove red border on page load, the timer allows js to wire up before removing the class that's added by the js
        setTimeout(function () {
            passwordField.children[0].classList.remove("smart-password-short");
        }, 10);
        return true;
    }
})(window);

index.html

    
<script src="js/complexityCheck.js"></script>
    <script src="_content/Smart.Blazor/js/smart.blazor.js"></script>
    <script src="_content/Smart.Blazor/js/smart.elements.js"></script>

razor page example

<EditForm Model="SomeModel" OnValidSubmit="ProcessMyForm">
        <DataAnnotationsValidator />
        <div class="mb-3">
            <label for="password">New Password</label>
            <template id="tooltip-password">
                <span>{{value}}</span>
            </template>
            <PasswordTextBox Required id="password" TooltipArrow TooltipTemplate="tooltip-password" Placeholder="Enter password" @bind-Value="SomeModel.Password" ShowPasswordIcon ShowPasswordStrength Name="password"></PasswordTextBox>
            <ValidationMessage For="@(() => SomeModel.Password)" />
        </div>
        <div class="mb-3">
            <label for="confirmPassword">Confirm New Password</label>
            <PasswordTextBox Class="form-control" Required Placeholder="Confirm password" @bind-Value="SomeModel.ConfirmPassword" ShowPasswordIcon Name="confirmPassword"></PasswordTextBox>
            <ValidationMessage For="@(() => SomeModel.ConfirmPassword)" />
        </div>
        <button type="submit" id="submit" disabled="@(!isComplexityValid || loading)" class="btn btn-primary">
            @if (loading)
            {
                <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                <span class="sr-only">Processing...</span>
            }
            else
            {
                <span class="sr-only">Submit</span>
            }
        </button>
    </EditForm>

<div>
    <span class="@($"smart-badge {messageCssClass}")">@message</span>
</div>
<br />
@code {

    private string message = string.Empty;
    private bool isComplexityValid = false;
    private bool loading = false;
    private string messageCssClass = string.Empty;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeAsync<bool>("customStrength", DotNetObjectReference.Create(this));
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    private async Task ProcessMyForm()
    {
        loading = true;
        var result = await // call some service here !

        if (result.Success)
        {
            messageCssClass = "smart-badge-success";
  
        }
        else
        {
            messageCssClass = "smart-badge-danger";
        }
        loading = false;
        message = result.Message;
        StateHasChanged();
    }

    [JSInvokable, EditorBrowsable(EditorBrowsableState.Never)]
    public void CallbackComplexityCheck(bool valid)
    {
        if (isComplexityValid != valid)
        {
            isComplexityValid = valid;
            StateHasChanged();
        }
    }