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 + }} + + +}}