SLDS-Input for SLDS-UI Module

One of the important and big steps in custom UI elements developent is Custom Form Component. We should be able to create our own UI components that must behave as standard component from Angular FormModule. In this case we need to implement ControlValueAccessor interface with few some additional hacks. I want to start with simple input that I will wrap with SLDS input form-element styles.

slds-input.component.html
<div class="slds-form-element">
    <label class="slds-form-element__label" [for]="idPrefix+'_input-id-1'">
        <abbr *ngIf="required" class="slds-required" title="required">* </abbr>{{label}}</label>
    <div class="slds-form-element__control">
        <input
            [type]="type"
            [ngModel]="value"
            (ngModelChange)="onValueChange($event)"
            [id]="idPrefix+'_input-id-1'"
            [name]="name"
            [required]="required"
            [disabled]="disabled"
            class="slds-input" />
    </div>
</div>

slds-input.component.ts
import { Component, OnInit, Input, Output, EventEmitter, forwardRef } from '@angular/core';
import { getRandomString } from '../../utils';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';


@Component({
    selector: 'slds-input',
    templateUrl: './slds-input.component.html',
    styleUrls: ['./slds-input.component.scss'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => SldsInputComponent),
            multi: true
        }
    ]
})
export class SldsInputComponent implements ControlValueAccessor {

    @Input() type = 'text';
    @Input() label = '<No label>';
    @Input() name: string;
    @Input() required = false;
    @Input() disabled = false;
    @Output() valueChange = new EventEmitter();

    idPrefix = '';
    value: string|number;
    oldValue: string|number;

    constructor() {
        this.idPrefix = getRandomString();
    }

    writeValue(value: any): void {
        this.value = value;
        this.oldValue = value;
    }

    propagateChange = (_: any) => {};

    registerOnChange(fn: any): void {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any): void {

    }

    onValueChange(newValue: any) {
        this.value = newValue;
        if (this.type === 'number') {
            this.value = parseInt(this.value as string, 10);
        }
        this.propagateChange(this.value);
    }

}

Small hack - idPrefix. To make standard label+input tags work together we need to set label[for] and input[id] as unique value across the whole page.  To automate it we can generate some random value in contructor. I've added getRandomString to new utils.ts file.

export function getRandomString() {
    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}

Also interesting moment is input type check in onValueChange method to return value in proper format (string or integer).

And usage example:
...
<slds-modal
    *ngIf="showNewTaskModal"
    title="New Task">
    <modal-content>

        <slds-input
            [(ngModel)]="newTask.name"
            label="Name"
            required="true"></slds-input>

    </modal-content>
    <modal-actions>
        <button class="slds-button slds-button_neutral" (click)="cancelCreateEmptyTask()">Cancel</button>
        <button class="slds-button slds-button_brand" (click)="createEmptyTask()">Create</button>
    </modal-actions>
</slds-modal>

If all changes was good you should see this modal



And final updates to finish with Task creation here is few new methods in

task.component.ts
...
openNewTaskModal() {
    this.newTask = {
        id: null,
        name: '',
        status: 'stop',
        config: null,
        user_id: null
    };
    this.showNewTaskModal = true;
}

createEmptyTask() {
    this.remoteService.createEmptyTask(this.newTask)
    .then((data) => {
        this.showNewTaskModal = false;
        if (data && data.id) {
            this.router.navigate(['/tasks', data.id]);
        }
    });
}

cancelCreateEmptyTask() {
    this.showNewTaskModal = false;
}
...

and in the backend

controllers/task.py
...
@task_bp.route('/create', methods=['POST'])
@login_required
def create_task():

    task_dto = request.get_json()

    task = Task(
        name=task_dto['name'],
        status='stop',
        user_id=current_user.id,
        config={}
    )
    db.session.add(task)
    db.session.commit()

    return jsonify(task)
...

If new Task is created successfully you should see Details page for this task



Comments

Popular posts from this blog

HTTPS in local environment for Angular + Flask project.

Salesforce Authentication 2 (Token Validation and Refresh)

User authentification with Flask-Login