import { extend } from '../Helpers/Extend';
import { strToDOM } from '../DOM/StrToDOM';
import { insertAfter, append } from '../DOM/Manipulation';
import { on } from '../Events/EventsManager';
const defaultOptions: FLib.SkinFile.Options = {
"selector": ".file-skin",
"wrap": "
",
"fileInfoSelector": ".file-info",
"fileInfo": "",
"autoHideFileInfo": true,
"resetButtonSelector": ".file-reset",
"resetButton": "×",
"disabledClass": "disabled",
"invalidClass": "invalid",
"selectedClass": "selected"
}
/**
* Skin an HTML input file element.
*/
export default class SkinFile implements FLib.SkinFile.SkinFile {
#options: FLib.SkinFile.Options;
#$parent: FLib.SkinFile.CustomInputFileParent;
#$resetButton: HTMLElement | undefined;
#$fileInfo: HTMLElement | undefined;
#fileInfoId = '';
#$input: FLib.SkinFile.CustomInputFile;
#originalFileInfoText = '';
constructor( $input: HTMLInputElement, userOptions?: Partial ) {
this.#$input = $input;
this.#options = extend( defaultOptions, userOptions );
const $PARENT = this.#$input.closest( this.#options.selector );
// The html skin is already done. Just selecting elements for the initialistion
if ( $PARENT ) {
this.#$parent = $PARENT as FLib.SkinFile.CustomInputFileParent;
this.#$resetButton = this.#$parent.querySelector( this.#options.resetButtonSelector ) as HTMLElement;
if ( !this.#$resetButton ) {
throw `[SkinFile]: "${ this.#options.resetButtonSelector }" not found`;
}
this.#$fileInfo = this.#$parent.querySelector( this.#options.fileInfoSelector ) as HTMLElement;
if ( !this.#$fileInfo ) {
throw `[SkinFile]: "${ this.#options.fileInfoSelector }" not found`;
}
this.#originalFileInfoText = this.#$fileInfo.innerHTML;
this.#originalFileInfoText = this.#originalFileInfoText.trim();
}
// No HTML skin, just input:file alone. Create the whole skin here:
else {
this.#$parent = strToDOM( this.#options.wrap ) as HTMLElement;
insertAfter( this.#$parent, this.#$input );
append( this.#$input, this.#$parent );
if ( this.#$input.hasAttribute( 'data-file-info' ) ) {
this.#fileInfoId = this.#$input.getAttribute( 'data-file-info' ) || '';
this.#$fileInfo = document.getElementById( this.#fileInfoId ) as HTMLElement;
}
if ( this.#$fileInfo ) {
this.#originalFileInfoText = this.#$fileInfo.innerHTML;
this.#originalFileInfoText = this.#originalFileInfoText.trim();
}
else {
this.#$fileInfo = strToDOM( this.#options.fileInfo ) as HTMLElement;
append( this.#$fileInfo, this.#$parent );
}
if ( this.#options.resetButton ) {
this.#$resetButton = strToDOM( this.#options.resetButton ) as HTMLElement;
append( this.#$resetButton, this.#$parent );
}
}
on( this.#$input, {
"eventsName": "change",
"callback": this.#changeHandler
} );
on( this.#$fileInfo, {
"eventsName": "click",
"callback": this.#clickHandler
} );
if ( this.#$resetButton ) {
on( this.#$resetButton, {
"eventsName": "click",
"callback": this.#resetHandler
} );
}
this.#changeState();
this.#$input.__skinAPI = this.#$parent.__skinAPI = this;
}
#changeState = (): void => {
if ( !this.#$input.value ) {
( this.#$fileInfo as HTMLElement ).innerHTML = this.#originalFileInfoText;
if ( this.#options.autoHideFileInfo && !this.#originalFileInfoText ) {
( this.#$fileInfo as HTMLElement ).style.display = 'none';
}
this.#$parent.classList.remove( this.#options.selectedClass );
return;
}
const aValue = this.#$input.value.split( /(\\|\/)/ );
( this.#$fileInfo as HTMLElement ).innerHTML = aValue[ aValue.length - 1 ];
this.#$parent.classList.add( this.#options.selectedClass );
if ( this.#options.autoHideFileInfo ) {
( this.#$fileInfo as HTMLElement ).style.display = '';
}
}
#changeHandler = (): void => {
this.#changeState();
}
#clickHandler = (): void => {
this.#$input.click();
}
#resetHandler = (): void => {
this.#$input.value = '';
this.#changeState();
}
#enableDisable = ( fnName: string, disabled: boolean ): void => {
this.#$input.disabled = disabled;
this.#$parent.classList[ fnName ]( this.#options.disabledClass );
}
/**
* Force the select to be enable
*/
enable(): this {
this.#enableDisable( 'remove', false );
return this;
}
/**
* Force the select to be disable
*/
disable(): this {
this.#enableDisable( 'add', true );
return this;
}
#validInvalid = ( fnName: string ): void => {
this.#$parent.classList[ fnName ]( this.#options.invalidClass );
}
/**
* Force the state of the select to invalid
*/
setInvalid(): this {
this.#validInvalid( 'add' );
return this;
}
/**
* Force the state of the select to valid
*/
setValid(): this {
this.#validInvalid( 'remove' );
return this;
}
}
/**
* Skin an input file DOM element
*
*
* @example
* ```ts
* // Call with default options:
* skinInputFile( $input, {
* "selector": ".file-skin",
* "wrap": "<div class=\"file-skin\"></div>",
* "fileInfoSelector": ".file-info",
* "fileInfo": "<div class=\"file-info\"></div>",
* "autoHideFileInfo": true,
* "resetButtonSelector": ".file-reset",
* "resetButton": "<span class=\"file-reset\">×</span>",
* "disabledClass": "disabled",
* "invalidClass": "invalid",
* "selectedClass": "selected"
* } );
* ```
*/
export function skinInputFile( $input: HTMLInputElement, options: Partial ): SkinFile {
return new SkinFile( $input, options );
}
/**
* Skin all input file DOM element in a wrapper
*
* @example
* ```ts
* // Call with default options:
* skinInputFileAll( $wrapper, {
* "selector": ".file-skin",
* "wrap": "<div class=\"file-skin\"></div>",
* "fileInfoSelector": ".file-info",
* "fileInfo": "<div class=\"file-info\"></div>",
* "autoHideFileInfo": true,
* "resetButtonSelector": ".file-reset",
* "resetButton": "<span class=\"file-reset\">×</span>",
* "disabledClass": "disabled",
* "invalidClass": "invalid",
* "selectedClass": "selected"
* } );
* ```
*/
export function skinInputFileAll( $wrapper: HTMLElement, options: Partial = {} ): SkinFile[] {
const skinList: SkinFile[] = [];
const $inputs = $wrapper.querySelectorAll( options.selector || 'input[type="file"]' );
$inputs.forEach( $input => {
skinList.push( new SkinFile( $input as HTMLInputElement, options ) );
} );
return skinList;
}