diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5ff77dd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,29 @@
+
+
+OPA=opa
+FILES=$(shell find src -name '*.opa')
+EXE=main.exe
+
+all: $(FILES)
+ $(OPA) $^ -o main.exe
+
+run:
+ ./$(EXE) --db-local db/db
+
+new-db:
+ ./$(EXE) --db-local db/db --db-force-upgrade
+
+clean-db:
+ rm -rf db/*
+
+clean:
+ rm -rf *.opx *.opx.broken
+ rm -f *.exe
+ rm -rf doc
+ rm -rf _build _tracks
+ rm -f *.log
+ rm -f *.apix
+ rm -f src/*.api
+ rm -rf *.opp
+ rm -f src/*.api-txt
+
diff --git a/db/db_config b/db/db_config
new file mode 100644
index 0000000..6f3c55c
Binary files /dev/null and b/db/db_config differ
diff --git a/db/db_db_state b/db/db_db_state
new file mode 100644
index 0000000..077a5b9
Binary files /dev/null and b/db/db_db_state differ
diff --git a/db/db_flags_file b/db/db_flags_file
new file mode 100644
index 0000000..7690f96
Binary files /dev/null and b/db/db_flags_file differ
diff --git a/db/db_lock b/db/db_lock
new file mode 100644
index 0000000..e69de29
diff --git a/db/db_node_file b/db/db_node_file
new file mode 100644
index 0000000..8e75334
Binary files /dev/null and b/db/db_node_file differ
diff --git a/db/db_timestamps_file b/db/db_timestamps_file
new file mode 100644
index 0000000..f0e97fd
Binary files /dev/null and b/db/db_timestamps_file differ
diff --git a/db/db_trans_file b/db/db_trans_file
new file mode 100644
index 0000000..e69de29
diff --git a/db/db_uid_file b/db/db_uid_file
new file mode 100644
index 0000000..98cf75d
Binary files /dev/null and b/db/db_uid_file differ
diff --git a/db/db_uid_rev_file b/db/db_uid_rev_file
new file mode 100644
index 0000000..e901d19
Binary files /dev/null and b/db/db_uid_rev_file differ
diff --git a/src/example.opa b/src/example.opa
new file mode 100644
index 0000000..46781ea
--- /dev/null
+++ b/src/example.opa
@@ -0,0 +1,129 @@
+
+
+import bddc.tablebuilder
+
+/**
+ *
+ *
+ * DATAS
+ *
+ *
+ *
+*/
+
+type vtest = {str : string i : int}
+type vtest_k = {k : int v : vtest}
+db /test : intmap(vtest)
+
+init() =
+ if not(Db.exists(@/test[0])) then
+ do /test[0] <- {str="coucou" i=10}
+ do /test[1] <- {str="salut" i=1}
+ void
+
+do init()
+
+get_values() : list(vtest_k) =
+ Db.intmap_fold_range(
+ @/test,
+ (acc,k -> List.add({~k v=/test[k]},acc)),
+ [],0,none,(_->true)
+ )
+add(new_v : vtest) : int =
+ key = Db.fresh_key(@/test)
+ do save(key, new_v)
+ key
+save(key : int, new_v : vtest) =
+ /test[key] <- new_v
+rm(key : int) =
+ Db.remove(@/test[key])
+
+
+/**
+ *
+ *
+ * TABLE
+ *
+ *
+ *
+*/
+
+
+
+@client
+mk_columns() = [
+ TableBuilder.mk_column(
+ <>String>,
+ (r,_chan -> <>{r.v.str}>),
+ some(r1, r2 -> String.ordering(r1.v.str, r2.v.str)),
+ none
+ ),
+ TableBuilder.mk_column(
+ <>Int>,
+ (r,_chan -> <>{r.v.i}>),
+ some(r1, r2 -> Int.ordering(r1.v.i, r2.v.i)),
+ none
+ ),
+ TableBuilder.mk_column(
+ <>Tool>,
+ (r,chan ->
+
+ ),
+ none,
+ none
+ )
+ ]
+
+onready() =
+ values = get_values()
+ spec = TableBuilder.mk_spec(
+ mk_columns(),
+ values
+ )
+ table = TableBuilder.make(spec)
+ key() = String.to_int(Dom.get_value(#key))
+ str() = Dom.get_value(#str)
+ i() = String.to_int(Dom.get_value(#int))
+ row_k() = {k=key() v={str=str() i=i()}}
+ row() = {str=str() i=i()}
+ // add
+ onadd(_) =
+ k=add(row())
+ row_k={~k v=row()}
+ Session.send(table.channel, {add=row_k})
+ // delete by key
+ ondelkey(_) =
+ do rm(key())
+ Session.send(table.channel, {del_key=key()})
+ // edit by key
+ oneditkey(_) =
+ do save(key(), row())
+ Session.send(table.channel, {edit_key={key=key() row=row_k()}})
+ xhtml =
+ <>
+ str :
+ int :
+ key :
+
+
+
+ {table.xhtml}
+ >
+ Dom.transform([#onready <- xhtml])
+
+
+/**
+ *
+ *
+ * SERVER
+ *
+ *
+ *
+*/
+
+
+main() =
+
onready()}>
+
+
+server = Server.one_page_server("Test", main)
diff --git a/src/tablebuilder.opa b/src/tablebuilder.opa
new file mode 100644
index 0000000..4c8adaf
--- /dev/null
+++ b/src/tablebuilder.opa
@@ -0,0 +1,206 @@
+/*
+ *
+ * @author Thomas Recouvreux
+ *
+ */
+
+package bddc.tablebuilder
+import stdlib.core.rpc.core
+import bddc.tools
+
+
+
+type TableBuilder.message('row) =
+ {show} /
+ {sort : int} /
+ {del_key : int} /
+ {del_filter : ('row->bool)} /
+ {edit_key : {key:int row:'row}} /
+ {edit_filter : ('row->'row)} /
+ {add : 'row}
+type TableBuilder.row_order('row) = ('row,'row -> Order.ordering)
+type TableBuilder.row_filter('row) = ('row -> bool)
+@abstract type TableBuilder.column('row) = {
+ label : xhtml
+ cell_maker : ('row,channel(TableBuilder.message('row)) -> xhtml)
+ order : option(TableBuilder.row_order('row))
+ sort_reverse : bool
+ filter : option(TableBuilder.row_filter('row))
+ filter_on : bool
+}
+@abstract type TableBuilder.spec('row) = {
+ id : string
+ columns : list(TableBuilder.column('row))
+ content : list('row)
+ sort_active : int
+}
+type TableBuilder.t('row) = {
+ xhtml : xhtml
+ channel : channel(TableBuilder.message('row))
+}
+
+
+
+
+
+TableBuilder = {{
+ mk_spec( columns : list(TableBuilder.column),
+ content : list('row)
+ ) : TableBuilder.spec('row) =
+ {
+ id = Dom.fresh_id()
+ ~columns
+ ~content
+ sort_active=0
+ }
+ mk_column( label : xhtml,
+ cell_maker : ('row,channel(TableBuilder.message('row)) -> xhtml),
+ order : option(TableBuilder.row_order('row)),
+ filter : option(TableBuilder.row_filter('row))
+ ) : TableBuilder.column =
+ {
+ ~label
+ ~cell_maker
+ ~order
+ ~filter
+ sort_reverse=true
+ filter_on=false
+ }
+
+ /**
+ Cré une session qui va contenir la spec du tableau, renvoi un record contenant
+ le channel pour communiquer et agir sur le tableau et le code xhtml necessaire
+ à l'affichage du tableau
+ @param spec
+ @return (TableBuilder.t)
+ */
+ @client
+ make(spec : TableBuilder.spec('row)) : TableBuilder.t =
+ rec val channel = Session.make(spec, callback)
+ and callback(spec,message) =
+ do jlog("{message}")
+ new_spec = match message with
+ | {show} -> spec
+ | {~sort} -> onclick_sort(sort, spec)
+ | {~del_key} -> {spec with content=List.remove_at(del_key,spec.content)}
+ | {~del_filter} -> {spec with content=List.filter(v->not(del_filter(v)),spec.content)}
+ | {~edit_key} -> {spec with content=Tools.list.replace(edit_key.key, edit_key.row, spec.content)}
+ | {~edit_filter} -> {spec with content=List.map(edit_filter, spec.content)}
+ | {~add} -> {spec with content=List.add(add,spec.content)}
+ end
+ new_spec = sort(new_spec)
+ do Dom.transform([#{spec.id} <- xhtml_table(new_spec, channel)])
+ {set=new_spec}
+ {~channel xhtml=xhtml(spec,channel)}
+
+
+ set_sort(i_col : int, spec : TableBuilder.spec('row)) : TableBuilder.spec('row) =
+ {spec with sort_active=i_col}
+
+ set_reverse(i_col : int, value : bool, spec : TableBuilder.spec('row)) : TableBuilder.spec('row) =
+ col = List.get(i_col,spec.columns)
+ if Option.is_some(col) then
+ col = Option.get(col)
+ col = {col with sort_reverse=value}
+ {spec with columns = Tools.list.replace(i_col, col, spec.columns)}
+ else spec
+
+ /**
+ Fonction appellée quand on clique pour trier le tableau,
+ elle va modifier la spec pour préciser la colonne de trie choisie
+ et inverser l'ordre de tri de cette colonne
+ @param i_col la colonne à utiliser pour le tri
+ @param spec
+ @return spec
+ */
+ @client
+ @private
+ onclick_sort(i_col : int, spec : TableBuilder.spec('row)) : TableBuilder.spec('row) =
+ Tools.option.perform_default(
+ (col -> set_sort(i_col, set_reverse(i_col, not(col.sort_reverse), spec))),
+ spec,
+ List.get(i_col,spec.columns)
+ )
+
+ /**
+ Trier la spec, la colonne à utiliséer pour le tri est précisée
+ dans la spec
+ @param spec
+ @return spec
+ */
+ @client
+ @private
+ sort(spec : TableBuilder.spec('row)) : TableBuilder.spec('row) =
+ Tools.option.perform_default(
+ (col -> Tools.option.perform_default(
+ (ord ->
+ if col.sort_reverse then
+ {spec with content = List.rev(List.sort_with(ord,spec.content))}
+ else
+ {spec with content = List.sort_with(ord,spec.content)}
+ ),
+ spec,
+ col.order
+ )),
+ spec,
+ List.get(spec.sort_active,spec.columns)
+ )
+
+ /**
+ Cré le tableau (seulement les balises exterieures)
+ @param spec
+ @param channel
+ @return (xhtml)
+ */
+ @client
+ @private
+ xhtml(spec : TableBuilder.spec('row), channel : channel(TableBuilder.message('row))) : xhtml =
+ Session.send(channel,{show})}>
+
+
+ /**
+ Cré la contenu du tableau
+ @param spec
+ @param channel
+ @return (xhtml)
+ */
+ @client
+ @private
+ xhtml_table(spec : TableBuilder.spec('row), channel : channel(TableBuilder.message('row))) : xhtml =
+ xhtml_header(s : TableBuilder.spec('row)) : xhtml =
+ List.foldi( (i,col,acc ->
+ <>{acc}{col.label}<>{
+ if Option.is_some(col.order) then
+
+ else
+ <>>
+ }> | >
+ ),
+ spec.columns,
+ <>>)
+ xhtml_body(s : TableBuilder.spec('row)) : xhtml =
+ List.fold( (row,acc ->
+ <>{acc}
+
+ {List.fold( (col,acc ->
+ <>{acc}
+ {col.cell_maker(row, channel)} |
+ >
+ ),
+ s.columns,
+ <>>
+ )}
+
+ >
+ ),
+ spec.content,
+ <>>)
+
+ {xhtml_header(spec)}
+
+
+ {xhtml_body(spec)}
+
+
+}}
+
diff --git a/src/tools.opa b/src/tools.opa
new file mode 100644
index 0000000..bbf670e
--- /dev/null
+++ b/src/tools.opa
@@ -0,0 +1,26 @@
+/*
+ * (c) Valdabondance.com - 2011
+ * @author Matthieu Guffroy - Thomas Recouvreux
+ *
+ */
+
+package bddc.tools
+
+Tools = {{
+
+ list = {{
+ replace(key : int, new_v : 'new_v, l : list('a)) : list('a) =
+ List.mapi((i,v -> if i==key then new_v else v), l)
+ }}
+
+
+ option = {{
+ perform_default(f : ('o -> 'r), default : 'r, o : option('o)) : 'r =
+ if Option.is_some(o) then
+ f(Option.get(o))
+ else
+ default
+ }}
+
+
+}}