2020-07-01 02:12:33 +02:00
import { UIElement } from "../UIElement" ;
2020-07-20 15:54:50 +02:00
import { InputElement } from "./InputElement" ;
2020-07-21 00:38:03 +02:00
import Translations from "../i18n/Translations" ;
2020-08-17 17:23:15 +02:00
import { UIEventSource } from "../../Logic/UIEventSource" ;
2020-10-08 19:03:00 +02:00
import Combine from "../Base/Combine" ;
2020-08-22 02:12:46 +02:00
2020-09-25 12:44:04 +02:00
export class TextField extends InputElement < string > {
2020-08-30 01:13:18 +02:00
private readonly value : UIEventSource < string > ;
public readonly enterPressed = new UIEventSource < string > ( undefined ) ;
private readonly _placeholder : UIElement ;
2020-08-31 02:59:47 +02:00
public readonly IsSelected : UIEventSource < boolean > = new UIEventSource < boolean > ( false ) ;
2020-10-04 01:04:46 +02:00
private readonly _htmlType : string ;
2020-09-24 23:56:08 +02:00
private readonly _textAreaRows : number ;
2020-07-23 17:32:18 +02:00
2020-12-05 03:22:17 +01:00
private readonly _isValid : ( string , country ) = > boolean ;
2020-10-08 19:03:00 +02:00
private _label : UIElement ;
2020-09-26 03:02:19 +02:00
2020-09-25 12:44:04 +02:00
constructor ( options ? : {
2020-07-20 15:54:50 +02:00
placeholder? : string | UIElement ,
2020-09-25 12:44:04 +02:00
value? : UIEventSource < string > ,
2020-09-07 01:03:23 +02:00
textArea? : boolean ,
2020-10-04 01:04:46 +02:00
htmlType? : string ,
2020-10-08 19:03:00 +02:00
label? : UIElement ,
2020-09-24 23:56:08 +02:00
textAreaRows? : number ,
2020-12-05 03:22:17 +01:00
isValid ? : ( ( s : string , country ? : ( ) = > string ) = > boolean )
2020-07-20 13:28:45 +02:00
} ) {
2020-07-20 15:54:50 +02:00
super ( undefined ) ;
2020-07-20 21:39:07 +02:00
const self = this ;
2020-07-20 13:28:45 +02:00
this . value = new UIEventSource < string > ( "" ) ;
2020-09-25 12:44:04 +02:00
options = options ? ? { } ;
2020-10-04 01:04:46 +02:00
this . _htmlType = options . textArea ? "area" : ( options . htmlType ? ? "text" ) ;
2020-09-25 12:44:04 +02:00
this . value = options ? . value ? ? new UIEventSource < string > ( undefined ) ;
2020-07-20 13:28:45 +02:00
2020-10-08 19:03:00 +02:00
this . _label = options . label ;
2020-09-07 01:03:23 +02:00
this . _textAreaRows = options . textAreaRows ;
2020-09-26 21:00:03 +02:00
this . _isValid = options . isValid ? ? ( ( str , country ) = > true ) ;
2020-07-20 13:28:45 +02:00
2020-07-21 00:38:03 +02:00
this . _placeholder = Translations . W ( options . placeholder ? ? "" ) ;
this . ListenTo ( this . _placeholder . _source ) ;
2020-07-20 15:54:50 +02:00
2020-09-10 21:06:56 +02:00
this . onClick ( ( ) = > {
self . IsSelected . setData ( true )
} ) ;
2020-09-25 12:44:04 +02:00
this . value . addCallback ( ( t ) = > {
2020-09-26 03:02:19 +02:00
const field = document . getElementById ( "txt-" + this . id ) ;
2020-09-25 12:44:04 +02:00
if ( field === undefined || field === null ) {
2020-07-20 13:28:45 +02:00
return ;
}
2020-09-26 03:02:19 +02:00
field . className = self . IsValid ( t ) ? "" : "invalid" ;
2020-09-25 12:44:04 +02:00
if ( t === undefined || t === null ) {
2020-07-20 13:28:45 +02:00
return ;
}
2020-07-20 15:54:50 +02:00
// @ts-ignore
2020-09-25 12:44:04 +02:00
field . value = t ;
2020-08-22 02:12:46 +02:00
} ) ;
2020-09-25 17:57:01 +02:00
this . dumbMode = false ;
2020-09-25 12:44:04 +02:00
}
2020-07-05 18:59:47 +02:00
2020-09-25 12:44:04 +02:00
GetValue ( ) : UIEventSource < string > {
return this . value ;
2020-07-01 02:12:33 +02:00
}
2020-09-25 12:44:04 +02:00
2020-07-20 21:03:55 +02:00
InnerRender ( ) : string {
2020-09-25 12:44:04 +02:00
2020-10-04 01:04:46 +02:00
if ( this . _htmlType === "area" ) {
2020-09-25 17:57:01 +02:00
return ` <span id=" ${ this . id } "><textarea id="txt- ${ this . id } " class="form-text-field" rows=" ${ this . _textAreaRows } " cols="50" style="max-width: 100%; width: 100%; box-sizing: border-box"></textarea></span> `
2020-08-31 02:59:47 +02:00
}
2020-09-25 12:44:04 +02:00
2020-09-12 23:15:17 +02:00
const placeholder = this . _placeholder . InnerRender ( ) . replace ( "'" , "'" ) ;
2020-10-08 19:03:00 +02:00
let label = "" ;
if ( this . _label != undefined ) {
label = this . _label . Render ( ) ;
}
return new Combine ( [
2020-10-17 03:19:14 +02:00
` <span id=" ${ this . id } "> ` ,
2020-10-08 19:03:00 +02:00
` <form onSubmit='return false' class='form-text-field'> ` ,
label ,
` <input type=' ${ this . _htmlType } ' placeholder=' ${ placeholder } ' id='txt- ${ this . id } '/> ` ,
` </form> ` ,
2020-10-17 03:19:14 +02:00
` </span> `
2020-10-08 19:03:00 +02:00
] ) . Render ( ) ;
2020-09-25 17:57:01 +02:00
}
InnerUpdate() {
const field = document . getElementById ( "txt-" + this . id ) ;
2020-07-01 02:12:33 +02:00
const self = this ;
field . oninput = ( ) = > {
2020-09-26 21:00:03 +02:00
// How much characters are on the right, not including spaces?
2020-09-26 01:10:10 +02:00
// @ts-ignore
2020-09-26 21:00:03 +02:00
const endDistance = field . value . substring ( field . selectionEnd ) . replace ( / /g , '' ) . length ;
2020-07-05 18:59:47 +02:00
// @ts-ignore
2020-09-26 21:00:03 +02:00
let val : string = field . value ;
2020-09-26 03:02:19 +02:00
if ( ! self . IsValid ( val ) ) {
self . value . setData ( undefined ) ;
} else {
self . value . setData ( val ) ;
}
2020-09-26 01:10:10 +02:00
// Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change
// See https://github.com/pietervdvn/MapComplete/issues/103
2020-09-26 03:02:19 +02:00
// We reread the field value - it might have changed!
2020-09-26 21:00:03 +02:00
2020-09-26 03:02:19 +02:00
// @ts-ignore
2020-09-26 21:00:03 +02:00
val = field . value ;
2020-09-27 00:20:48 +02:00
let newCursorPos = val . length - endDistance ;
2020-09-26 21:00:03 +02:00
while ( newCursorPos >= 0 &&
// We count the number of _actual_ characters (non-space characters) on the right of the new value
// This count should become bigger then the end distance
2020-09-27 00:20:48 +02:00
val . substr ( newCursorPos ) . replace ( / /g , '' ) . length < endDistance
2020-09-26 21:00:03 +02:00
) {
newCursorPos -- ;
}
// @ts-ignore
self . SetCursorPosition ( newCursorPos ) ;
2020-07-01 02:12:33 +02:00
} ;
2020-09-25 12:44:04 +02:00
if ( this . value . data !== undefined && this . value . data !== null ) {
// @ts-ignore
field . value = this . value . data ;
}
2020-08-31 02:59:47 +02:00
field . addEventListener ( "focusin" , ( ) = > self . IsSelected . setData ( true ) ) ;
field . addEventListener ( "focusout" , ( ) = > self . IsSelected . setData ( false ) ) ;
2020-09-10 21:06:56 +02:00
2020-07-01 02:12:33 +02:00
field . addEventListener ( "keyup" , function ( event ) {
if ( event . key === "Enter" ) {
2020-07-05 18:59:47 +02:00
// @ts-ignore
2020-07-01 02:12:33 +02:00
self . enterPressed . setData ( field . value ) ;
}
} ) ;
2020-07-05 18:59:47 +02:00
2020-07-01 02:12:33 +02:00
}
2020-09-11 00:23:19 +02:00
public SetCursorPosition ( i : number ) {
2020-10-04 01:04:46 +02:00
if ( this . _htmlType !== "text" && this . _htmlType !== "area" ) {
return ;
}
2020-09-26 01:10:10 +02:00
const field = document . getElementById ( 'txt-' + this . id ) ;
2020-09-11 00:23:19 +02:00
if ( field === undefined || field === null ) {
return ;
}
2020-09-25 12:44:04 +02:00
if ( i === - 1 ) {
2020-09-11 00:23:19 +02:00
// @ts-ignore
i = field . value . length ;
}
field . focus ( ) ;
// @ts-ignore
field . setSelectionRange ( i , i ) ;
2020-09-26 01:10:10 +02:00
2020-09-11 00:23:19 +02:00
}
2020-09-25 12:44:04 +02:00
IsValid ( t : string ) : boolean {
2020-09-26 03:02:19 +02:00
if ( t === undefined || t === null ) {
return false
}
2020-09-26 21:00:03 +02:00
return this . _isValid ( t , undefined ) ;
2020-07-20 15:54:50 +02:00
}
2020-09-27 00:20:48 +02:00
}