Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file interactive.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323openImportopenCore_kernelopenVdom(* We hit the default height limit of 128 fairly quickly. *)let()=letmax_height=Incr.State.max_height_allowedIncr.State.tin(* 2057 is an arbitrary choice, increase if necessary *)letmax_height=max2057max_heightinIncr.State.set_max_height_allowedIncr.State.tmax_height;;(* An [Interactive.t] consists of:
- A [value], which incrementally updates as the user changes it
- A [render] function, which constructs a list of Virtual_dom nodes
[render] takes in an [inject] function that specifies how to convert updates into
Virtual_dom events, which is used in the event handlers of the Virtual_dom nodes.
It's more natural to think of the type of [inject] as [('a -> Event.t)] rather than
[(unit -> Event.t)], since its purpose is to convert a value update into an [Event.t].
The reason it's not implemented this way is that if a complex ['a Interactive.t]
is created by composing simpler [_ Interactive.t]s, then when the value of a simpler
part changes, the resulting [Event.t] should reflect the new value of ['a], not the new
value of the simpler part.
So, what we actually do is:
- Update the Incr.Var.t for the simple part
- Incr.stabilize ()
- Read out the new value of ['a]
The [inject] function is defined in [render].
*)type'at={value:'aIncr.t;render:(unit->Event.t)->Node.tlistIncr.t}[@@derivingfields]letmake_counter()=letcounter=ref0infun()->let()=incrcounterin!counter;;letnext_key=letnext_id=make_counter()infun()->"form_"^Int.to_string(next_id());;letof_incrvalue=letrender_=Incr.return[]inFields.create~value~render;;letreturnx=of_incr(Incr.returnx)letbind(typeab)(x:at)~(f:a->bt):bt=letopenIncr.Let_syntaxinletbti:btIncr.t=let%mapvalue=x.valueinfvalueinletvalue:bIncr.t=let%bindbt=btiinbt.valueinletrenderinject=letnodesx=x.renderinjectinlet%mapouter_nodes=nodesxandinner_nodes=bti>>=nodesinouter_nodes@inner_nodesinFields.create~value~render;;letrendert~on_input~inject=letobserver=Incr.observet.valueinletinject()=let()=Incr.stabilize()ininject(on_input(Incr.Observer.value_exnobserver))inIncr.map(t.renderinject)~f:(funnodes->Node.div[]nodes);;letcurrent_valuet=letobserver=Incr.observet.valueinlet()=Incr.stabilize()inIncr.Observer.value_exnobserver;;letmapt~f=letvalue=Incr.mapt.value~finletrender=t.renderinFields.create~value~render;;letmap_nodest~f=letopenIncr.Let_syntaxinletrenderinject=let%mapnodes=t.renderinjectinfnodesinFields.create~value:t.value~render;;letmap_nodes_value_dependentt~f=letopenIncr.Let_syntaxinletrenderinject=let%mapnodes=t.renderinjectandvalue=t.valueinfvaluenodesinFields.create~value:t.value~render;;letbothab=letvalue=Incr.map2a.valueb.value~f:(funab->a,b)inletrenderinject=Incr.map2(a.renderinject)(b.renderinject)~f:List.appendinFields.create~value~render;;letwrap_in_div?(attrs=[])t=map_nodest~f:(funnodes->[Node.divattrsnodes])modulePrimitives=structletcreate~init~render=letvar=Incr.Var.createinitinletvalue=Incr.Var.watchvarinletrenderinject=letinjectx=let()=Incr.Var.setvarxininject()inrender~inject~valueinFields.create~value~render;;type'aprimitive=?attrs:Attr.tlist->?id:string->unit->'atletbootstrap_text_attrs=[]letbootstrap_text_area_attrs=[Attr.class_"textarea"]letbootstrap_button_attrs=[Attr.classes["btn";"btn-primary"]]letbootstrap_dropdown_attrs=[Attr.classes["btn";"btn-outline-primary";"btn-sm";"dropdown-toggle"]];;letdefault_text_attrs=[]letdefault_text_area_attrs=[]letdefault_button_attrs=[]letdefault_dropdown_attrs=[]letshared_setup~id=letkey=next_key()inletid=Option.valueid~default:keyinkey,id;;letof_nodesnodes=letvalue=Incr.return()inletnodes=Incr.returnnodesinletrender_=nodesinFields.create~value~render;;lettext_or_text_area~which_one?init~attrs?id()=letopenIncr.Let_syntaxinletinit=Option.valueinit~default:""inletkey,id=shared_setup~idincreate~init~render:(fun~inject~value->let%mapvalue=valueinleton_input=Attr.on_input(fun_evtext->injecttext)inletattrs=Attr.idid::on_input::attrsin[(matchwhich_onewith|`Text->Node.input~key(Attr.type_"text"::Attr.valuevalue::attrs)[]|`Text_area->Node.textarea~keyattrs[Node.textvalue])]);;lettext?init?(attrs=default_text_attrs)=text_or_text_area~which_one:`Text?init~attrs;;lettext_area?init?(attrs=default_text_area_attrs)=text_or_text_area~which_one:`Text_area?init~attrs;;moduleButton_state=structtypet=|Pressed|Not_pressedendletbutton~text?(attrs=default_button_attrs)?id()=letinit=Button_state.Not_pressedinletkey,id=shared_setup~idincreate~init~render:(fun~inject~value:(_:Button_state.tIncr.t)->leton_click=Attr.on_click(fun_->Event.Many[injectButton_state.Pressed;injectButton_state.Not_pressed])inletattrs=Attr.idid::Attr.type_"button"::on_click::attrsinIncr.return[Node.button~keyattrs[Node.texttext]]);;letdisabled_button~text?(attrs=default_button_attrs)?id()=letkey=next_key()inletid=Option.valueid~default:keyinletattrs=[Attr.idid;Attr.type_"button";Attr.disabled]@attrsinletnodes=[Node.button~keyattrs[Node.texttext]]inof_nodesnodes;;letdropdown_exn~options?(init=0)?(attrs=default_dropdown_attrs)?id()=letnames,meanings=List.unzipoptionsinletopenIncr.Let_syntaxinletkey,id=shared_setup~idinlett=create~init~render:(fun~inject~value:selected_idx->let%mapselected_idx=selected_idxinletselect_options=List.mapinames~f:(funidxtext->letselected_attr=ifselected_idx=idxthen[Attr.create"selected""selected"]else[]inletoption_attr=selected_attr@[Attr.value(Int.to_stringidx)]inNode.optionoption_attr[Node.texttext])inleton_input=Attr.on_input(fun_evtext->inject(Int.of_stringtext))inletattrs=Attr.idid::on_input::attrsin[Node.select~keyattrsselect_options])inmapt~f:(funselected_index->List.nth_exnmeaningsselected_index);;letdropdown_with_blank_exn~options?init?attrs?id()=letoptions=List.mapoptions~f:(fun(label,value)->label,Somevalue)inletoptions=("",None)::optionsinletinit=Option.mapinit~f:(funx->x+1)indropdown_exn~options?init?attrs?id();;letcheckbox?(init=false)?(attrs=[])?id()=letopenIncr.Let_syntaxinletkey,id=shared_setup~idincreate~init~render:(fun~inject~value->let%mapvalue=valueinletattrs=(ifvaluethen[Attr.checked]else[])@attrsin(* jjackson: I couldn't figure out how to obtain the current state of the checkbox
directly from the event, so we have to find the checkbox in the DOM and look at
its state, which we avoid in the other primitives as it creates more room for
error.
*)leton_click_ev=letelement=Dom_html.document##getElementById(Js.stringid)inletchecked=matchDom_html.opt_taggedelementwith|Some(Dom_html.Inputel)->Js.to_boolel##.checked|_->let()=Async_js.log_s[%message"Couldn't determine the state of the checkbox. The form might not \
work properly."(id:string)]invalueininjectcheckedinletattrs=Attr.type_"checkbox"::Attr.idid::Attr.on_clickon_click::attrsin[Node.input~keyattrs[]]);;letmessagemsg=of_nodes[Node.textmsg]letline_break=of_nodes[Node.div[][]]letnodes=of_nodesendmoduleT=structincludeMonad.Make(structtypenonrec'at='atletreturn=returnletmap=mapletmap=`Custommapletbind=bindend)endletall_ignore=T.all_unitletall=T.allletall_unit=T.all_unitletignore_m=T.ignore_mletjoin=T.joinlet(>>|)=T.(>>|)let(>>=)=T.(>>=)moduleMonad_infix=T.Monad_infixmoduleLet_syntax=structletreturn=returnlet(>>|)=(>>|)let(>>=)=(>>=)moduleLet_syntax=structletreturn=returnletbind=bindletmap=mapletboth=bothmoduleOpen_on_rhs=Primitivesendend