File

src/lib/text/text.component.ts

Properties

handler
handler: () => void
Type : () => void
icon
icon: string
Type : string
name
name: string
Type : string
import { ViewChild, Component, OnInit, Input, Output, ElementRef, EventEmitter, AfterViewInit, Renderer2, Self, Optional } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { hasRequiredValidator } from '../core/validator.functions';

@Component({
    selector: 'plex-text',
    template: `
    <div class="form-group" [ngClass]="{'has-danger': hasDanger() }">

        <!-- Label -->
        <label *ngIf="label" class="form-control-label">
            {{label}}
            <span *ngIf="control.name && esRequerido" class="requerido"></span>
        </label>

        <!-- Simple text field -->
        <div [attr.aria-hidden]="multiline || html" [hidden]="multiline || html" [class.input-group]="prefix || suffix || prefixParent?.children.length > 0 || suffixParent?.children.length > 0">
            <span *ngIf="prefix" class="input-group-addon">
                <plex-icon type="default" size="md" [name]="prefix"></plex-icon>
            </span>
            <span #prefixParent [hidden]="prefixParent?.children.length === 0" class="input-group-addon">
                <ng-content select="[left]"></ng-content>
            </span>

            <input #input [attr.aria-label]="textLabel" [attr.aria-labelledby]="passwordLabel" [attr.aria-describedby]="ariaDescribedby.id" type="{{type}}" class="form-control form-control-{{size}}" [placeholder]="placeholder" [disabled]="disabled"
                [readonly]="readonly" (input)="onChange($event.target.value)" (change)="disabledEvent($event)" (focus)="onFocus()" (focusout)="onFocusout()">

            <plex-icon  *ngIf="!readonly && !multiline && !html && !isEmpty" size="sm" name="close-circle" class="clear-icon" (click)="clearInput()"></plex-icon>

            <span #suffixParent [hidden]="suffixParent?.children.length === 0" class="input-group-addon">
                <ng-content select="[right]"></ng-content>
            </span>
        </div>

        <!-- Multiline -->
        <textarea [attr.aria-label]="textLabel" [attr.aria-labelledby]="passwordLabel" [attr.aria-hidden]="!multiline || html" [hidden]="!multiline || html" #textarea class="form-control" [placeholder]="placeholder" [rows]="rows" [disabled]="disabled" [readonly]="readonly"
        (input)="onChange($event.target.value)" (change)="disabledEvent($event)" (focus)="onFocus()" (focusout)="onFocusout()">
        </textarea>

        <!-- HTML Editor -->
        <quill-editor #quillEditor [attr.aria-label]="textLabel" [attr.aria-hidden]="multiline || !html" [hidden]="multiline || !html" [modules]="quill" [style]="quillStyle" [readOnly]="readonly" [placeholder]="placeholder" (onContentChanged)="onChange($event.html)"></quill-editor>

        <!-- Validación / Descripción ARIA -->
        <plex-validation-messages [mensaje]="mensaje" id="{{ ariaDescribedby.id }}" *ngIf="hasDanger()" [control]="control"></plex-validation-messages>
    </div>
    `
})
export class PlexTextComponent implements OnInit, AfterViewInit, ControlValueAccessor {
    // private
    public quillStyle = {
        height: '200px'
    };

    // Public
    public isEmpty = true;
    public quill = {
        toolbar: {
            container: [
                ['bold', 'italic', 'underline', 'link'],
                [{ list: 'ordered' }, { list: 'bullet' }],
                [{ size: ['small', false, 'large', 'huge'] }],
                [{ align: [] }],
                ['clean'],
            ],
            handlers: {}
        }
    };

    @ViewChild('input', { static: true }) private input: ElementRef;
    @ViewChild('textarea', { static: true }) private textarea: ElementRef;
    @ViewChild('quillEditor', { static: true }) private quillEditor: ElementRef;

    public get esRequerido(): boolean {
        return hasRequiredValidator(this.control as any);
    }

    // Propiedades
    @Input() type: 'text' | 'password' | 'email' = 'text';
    @Input() label: string;
    @Input() ariaLabel: string;
    @Input() ariaDescribedby: any = { id: `label${Math.floor(Math.random() * 100000)}` };
    @Input() mensaje: string;
    @Input() size: 'sm' | 'md' | 'lg' = 'md';
    @Input() placeholder: string;
    @Input() prefix: string;
    @Input() suffix: string;
    @Input() disabled = false;
    @Input() readonly = false;
    @Input() multiline = false;
    @Input() rows: number;
    @Input() html = false;
    @Input() debounce = 0;
    @Input() qlToolbar: PlexTextToolBar[];

    @Input()
    set password(value) {
        if (value) {
            this.type = 'password';
        }
    }

    @Input()
    set height(value: number | string) {
        if (typeof value === 'number') {
            this.quillStyle.height = value + 'px';
        } else {
            this.quillStyle.height = value;
        }
    }
    @Input()
    set autoFocus(value: any) {
        // Cada vez que cambia el valor vuelve a setear el foco
        if (this.renderer) {
            const element = this.multiline ? this.textarea.nativeElement : this.input.nativeElement;
            element.focus();
        }
    }

    // Eventos
    @Output() change = new EventEmitter();
    @Output() focus = new EventEmitter();
    @Output() focusout = new EventEmitter();
    @Output() typing = new EventEmitter();

    public onFocus() {
        this.focus.emit();
    }

    public onFocusout() {
        this.focusout.emit();
    }

    // Funciones públicas
    public onChange = (_: any) => { };

    public disabledEvent(event: Event) {
        event.stopImmediatePropagation();
        return false;
    }

    private changeTimeout = null;

    constructor(
        private renderer: Renderer2,
        @Self() @Optional() public control: NgControl,
    ) {
        if (this.control) {
            this.control.valueAccessor = this;
        }
        this.placeholder = '';
        this.password = false;
    }

    // Inicialización
    ngOnInit() {
        if (this.html) {
            this.prepareQuillToolbar();
        }
        if (this.type === 'password') {
            this.mensaje = 'Ingrese caracteres alfanuméricos, sin espacios.';
        }
    }

    ngAfterViewInit() {
        if (this.autoFocus) {
            const element = this.multiline ? this.textarea.nativeElement : this.input.nativeElement;
            element.focus();
        }
        if (this.html) {
            this.createToolbarIcons();
        }
    }

    // Actualización Modelo -> Vista
    writeValue(value: any) {
        const element = this.multiline ? this.textarea.nativeElement : this.input.nativeElement;
        this.renderer.setProperty(element, 'value', typeof value === 'undefined' ? '' : value);
        if (this.multiline) {
            this.adjustTextArea();
        } else {
            if (this.html) {
                const component = (this.quillEditor as any);
                // Por el dinamismo de RUP hay una primera instancia que quillEditor es undefined
                if (component.quillEditor) {
                    component.quillEditor.setContents(component.valueSetter(component.quillEditor, typeof value === 'undefined' ? '' : value));
                }
            }
        }
        // Check empty
        this.isEmpty = !(value && value.toString().trim());
    }

    public hasDanger() {
        return (this.control as any).name && (this.control.dirty || this.control.touched) && !this.control.valid;
    }

    // Actualización Vista -> Modelo
    registerOnTouched() {
    }

    registerOnChange(fn: any) {
        this.onChange = (value) => {
            value = value || null;
            fn(value);
            // Check empty
            this.isEmpty = !(value && value.toString().trim());

            if (this.multiline) {
                this.adjustTextArea();
            }
            // jgabriel | 24/03/2017 | Esto es un por bug de Angular2 que a veces no actualiza la vista cuando cambia el modelo
            // this.change.emit({
            //   value: value
            // });
            if (this.changeTimeout) {
                clearTimeout(this.changeTimeout);
            }
            this.changeTimeout = setTimeout(() => {
                this.change.emit({
                    value
                });
            }, this.debounce);
            this.typing.emit();
        };
    }

    /**
     * Borra el contenido del input
     *
     * @memberof PlexTextComponent
     */
    clearInput() {
        if (!this.disabled && !this.isEmpty) {
            this.writeValue(null);
            this.onChange(null);
            this.input.nativeElement.focus();
        }
    }

    /**
     * Ajusta el alto del textarea al contenido
     *
     * @memberof PlexTextComponent
     */
    adjustTextArea() {
        this.textarea.nativeElement.style.overflow = 'auto';
        this.textarea.nativeElement.style.height = (this.rows) ? this.rows + 'em' : '4em';
    }


    private prepareQuillToolbar() {
        if (this.qlToolbar) {
            const toolBarItems: string[] = [];
            const handlers: any = {};

            this.qlToolbar.forEach(item => {
                toolBarItems.push(item.name);
                handlers[item.name] = item.handler;
            });

            this.quill.toolbar.container.push(toolBarItems);
            this.quill.toolbar.handlers = handlers;
        }
    }

    private createToolbarIcons() {
        if (this.qlToolbar) {
            const editor = (this.quillEditor as any).quillEditor;
            const toolbar = editor.getModule('toolbar').container;
            this.qlToolbar.forEach(item => {
                const qlItem = toolbar.getElementsByClassName(`ql-${item.name}`);
                if (qlItem.length > 0) {
                    qlItem[0].innerHTML = `<i class="adi adi-${item.icon || item.name}"></i>`;
                }
            });
        }
    }

    get textLabel() {
        // ARIA no permite aria-label en passwords (sólo aria-labelledby)
        if (this.type !== 'password') {
            return this.ariaLabel ? this.ariaLabel : this.label;
        } else {
            return null;
        }
    }

    get passwordLabel() {
        return this.type === 'password' ? this.ariaDescribedby.id : null;
    }

}

export interface PlexTextToolBar {
    name: string;
    icon?: string;
    handler: () => void;
}

results matching ""

    No results matching ""