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