123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401open!Coreopen!Js_of_ocamlmoduleInput=structmoduletypeS=sigtypettypesettingstyperesultvalmake:settings->document:Dom_html.documentJs.t->tvalelement:t->Dom_html.elementJs.tvalwait_for_input:?auto_focus:bool->t->unit->resultLwt.tendmoduletypeS1=sigtype'attype'asettingstype'aresultvalmake:'asettings->document:Dom_html.documentJs.t->'atvalelement:'at->Dom_html.elementJs.tvalwait_for_input:?auto_focus:bool->'at->unit->'aresultLwt.tendendmoduleHtml_input1(Element:sigtype'attype'asettingstype'avaluevalappend_element_as_child:'at->parent:#Dom.nodeJs.t->unitvalreset:'at->unit->unitLwt.tvalmake_readonly:'at->unit->unitLwt.tvalfocus:'at->unit->unitLwt.tvalread_value_result:'at->unit->('avalue,Error.t)Result.tvalmake:document:Dom_html.documentJs.t->settings:'asettings->unit->'atend)=structtype'at={element:Dom_html.elementJs.t;form:Dom_html.formElementJs.t;input_element:'aElement.t;submit_button:Dom_html.buttonElementJs.t;}letmakesettings~document:'at=letcontainer=Dom_html.createDivdocumentinletform=Dom_html.createFormdocumentinDom.appendChildcontainerform;(form##.className:=Class.(to_js_stringInput_container_form));letinput_element=Element.make~document~settings()inElement.append_element_as_childinput_element~parent:form;letsubmit_button=Dom_html.createButton~_type:(Js.string"submit")documentin(submit_button##.className:=Class.(to_js_stringInput_submit_button));submit_button##.innerText:=Js.string"Submit";Dom.appendChildformsubmit_button;{element=container;form;input_element;submit_button}letelementt=t.elementletset_submit_handlert~handler()=t.form##.onsubmit:=Dom.handler(funevent->handlerevent();Js._false)letclear_submit_handlert()=t.form##.onsubmit:=Dom.handler(Fn.constJs._true)letset_to_readonlyt()=let%lwt()=Element.make_readonlyt.input_element()int.submit_button##.disabled:=Js._true;Lwt.return()letwait_for_input?(auto_focus=true)t()=letget_input()=let%lwt()=Element.resett.input_element()inletinput_submit_promise,input_submit_handler=Lwt.task()inletsubmit_handler_()=matchElement.read_value_resultt.input_element()with|Okinput_parsed_value->Lwt.wakeupinput_submit_handlerinput_parsed_value|Errorerror->(* TODO-someday: since this is in the handler code given to the
submit event, and not part of the Lwt computation, this error isn't
caught by the error-handling logic and instead crashes the webapp.
It would be nice to fix this sometime *)failwith([%message"Failed to parse input"(error:Error.t)]|>Sexp.to_string_hum)inset_submit_handlert~handler:submit_handler();let%lwt()=ifauto_focusthenElement.focust.input_element()elseLwt.return()ininput_submit_promiseinleton_input_read()=(* Do cleanup once the input has been read *)let%lwt()=set_to_readonlyt()inclear_submit_handlert();Lwt.return()inlet%lwtinput=get_input()inlet%lwt()=on_input_read()inLwt.returninputend(** This should be just the same as [Html_input1] but ignoring the type
parameters. It's inconvenient I need to write the code like this but I can't
think of another way *)moduleHtml_input(Element:sigtypettypesettingstypevaluevalappend_element_as_child:t->parent:#Dom.nodeJs.t->unitvalreset:t->unit->unitLwt.tvalmake_readonly:t->unit->unitLwt.tvalfocus:t->unit->unitLwt.tvalread_value_result:t->unit->(value,Error.t)Result.tvalmake:document:Dom_html.documentJs.t->settings:settings->unit->tend)=structmoduleM=Html_input1(structincludeElementtype_t=Element.ttype_settings=Element.settingstype_value=Element.valueend)includeM(* Since I know that type parameter is ignored, I just give the empty type to it to make the signature work out *)typeempty=|typet=emptyM.tendmoduleSimple_html_input(M:sigtypetvalhtml_input_type:stringvalt_of_string_result:string->(t,Error.t)Result.tend)=Html_input(structtypet=Dom_html.inputElementJs.ttypesettings=unittypevalue=M.tletappend_element_as_childt~parent=Dom.appendChildparenttletresett()=t##.value:=Js.string"";Lwt.return()letmake_readonlyt()=t##.readOnly:=Js._true;Lwt.return()letfocust()=t##focus;Lwt.return()letget_contentt()=Js.to_stringt##.valueletread_value_resultelement()=letinput_element_content=get_contentelement()inM.t_of_string_resultinput_element_contentletmake~document~settings:()()=letinput_field_name_string=Js.string"text_input_field"inDom_html.createInputdocument~_type:(Js.stringM.html_input_type)~name:input_field_name_stringend)moduleText=Html_input(structtypet={container:Dom_html.divElementJs.t;input:Dom_html.inputElementJs.t;}typesettings=stringoptiontypevalue=stringletappend_element_as_child{container;_}~parent=Dom.appendChildparentcontainerletreset{input;_}()=input##.value:=Js.string"";Lwt.return()letmake_readonly{input;_}()=input##.readOnly:=Js._true;Lwt.return()letfocus{input;_}()=input##focus;Lwt.return()letread_value_result{input;_}()=Ok(Js.to_stringinput##.value)letmake~document~settings:prompt()=letcontainer=Dom_html.createDivdocumentinletinput_field_name_string=Js.string"text_input_field"inletinput=Dom_html.createInputdocument~_type:(Js.string"text")~name:input_field_name_stringin(matchpromptwith|None->()|Someprompt->letlabel=Dom_html.createSpandocumentin(label##.className:=Class.(to_js_stringText_prompt_label));label##.innerText:=Js.stringprompt;Dom.appendChildcontainerlabel);Dom.appendChildcontainerinput;{container;input}end)moduleInteger=Simple_html_input(structtypet=intlethtml_input_type="number"lett_of_string_results=matchInt.of_string_optswith|Somex->Okx|None->Error(Error.of_string"Unable to parse string as int")end)moduleSingle_selection=Html_input1(structtype'at={element:Dom_html.selectElementJs.t;options:'alist;option_to_string:'a->string;}type'asettings='alist*('a->string)type'avalue='aletappend_element_as_child{element;_}~parent=Dom.appendChildparentelementletreset{element;options;option_to_string}()=element##.value:=Js.string(option_to_string(List.hd_exnoptions));Lwt.return()letmake_readonly{element;_}()=element##.disabled:=Js._true;Lwt.return()letfocus{element;_}()=element##focus;Lwt.return()letread_value_result{element;options;option_to_string=_}()=letopenResult.Let_syntaxinlet%bindinput_index=Js.to_stringelement##.value|>Int.of_string_opt|>Result.of_option~error:(Error.of_string"Input value was not integer")inList.nthoptionsinput_index|>Result.of_option~error:(Error.of_string"Input index out of range")letmake~document~settings:(options,option_to_string)()=matchoptionswith|[]->failwith"No options provided"|_::_->letinput_field_name_string=Js.string"select_input"inletelement=Dom_html.createSelectdocument~name:input_field_name_stringinList.iterioptions~f:(funioption->letoption_element=Dom_html.createOptiondocumentinoption_element##.innerText:=Js.string(option_to_stringoption);option_element##.value:=Js.string(string_of_inti);Dom.appendChildelementoption_element);{element;options;option_to_string}end)moduleMulti_selection=Html_input1(structmoduleCheckbox=structtypet={element:Dom_html.elementJs.t;checkbox:Dom_html.inputElementJs.t;value_index:int;}letappend_element_as_child{element;_}~parent=Dom.appendChildparentelementletreset{checkbox;_}()=checkbox##.checked:=Js._falseletmake_readonly{checkbox;_}()=checkbox##.disabled:=Js._trueletis_checked{checkbox;_}()=Js.to_boolcheckbox##.checkedletvalue_index{value_index;_}=value_indexletmake~document~value_name~input_name~value_index()=letlabel_container=Dom_html.createLabeldocumentin(label_container##.className:=Class.(to_js_stringInput_multiselect_container));letcheckbox=Dom_html.createInputdocument~_type:(Js.string"checkbox")~name:(Js.stringinput_name)inDom.appendChildlabel_containercheckbox;letvalue_node=Dom_html.createPdocumentinvalue_node##.innerText:=Js.stringvalue_name;Dom.appendChildlabel_containervalue_node;{element=(label_container:>Dom_html.elementJs.t);checkbox;value_index;}endtype'at={element:Dom_html.fieldSetElementJs.t;checkboxes:Checkbox.tlist;options:'alist;option_to_string:'a->string;}type'asettings='alist*('a->string)type'avalue='alistletappend_element_as_child{element;_}~parent=Dom.appendChildparentelementletreset{checkboxes;_}()=List.itercheckboxes~f:(funcheckbox->Checkbox.resetcheckbox());Lwt.return()letmake_readonly{checkboxes;_}()=List.itercheckboxes~f:(funcheckbox->Checkbox.make_readonlycheckbox());Lwt.return()letfocus{checkboxes;_}()=(matchcheckboxeswith|[]->()|first_checkbox::_->first_checkbox.element##focus);Lwt.return()letread_value_result{element=_;checkboxes;options;option_to_string=_}()=List.fold_resultcheckboxes~init:[]~f:(funacccheckbox->ifCheckbox.is_checkedcheckbox()thenletvalue_index=Checkbox.value_indexcheckboxinlet%bind.Resultoption=List.nthoptionsvalue_index|>Result.of_option~error:(Error.of_string(sprintf"Checkbox index %d is not in range"value_index))inOk(option::acc)else(* Don't add as not checked *)Okacc)|>Result.map~f:List.revletmake~document~settings:(options,option_to_string)()=letfieldset=Dom_html.createFieldsetdocumentinletcheckboxes=List.mapioptions~f:(funioption->letcheckbox_name="input_checkboxes[]"(* The "[]" signs to browsers that this is a list, needed because the name will be reused *)inletcheckbox=Checkbox.make~document~value_name:(option_to_stringoption)~input_name:checkbox_name~value_index:i()inCheckbox.append_element_as_child~parent:fieldsetcheckbox;checkbox)in{element=fieldset;checkboxes;options;option_to_string}end)