Current section

Files

Jump to
form src form_backend.erl
Raw

src/form_backend.erl

-module(form_backend).
-compile(export_all).
-include_lib("nitro/include/calendar.hrl").
-include_lib("nitro/include/multipleInput.hrl").
-include_lib("nitro/include/comboLookup.hrl").
-include_lib("nitro/include/comboLookupEdit.hrl").
-include_lib("nitro/include/comboLookupVec.hrl").
-include_lib("nitro/include/comboLookupModify.hrl").
-include_lib("nitro/include/comboLookupGroup.hrl").
-include_lib("nitro/include/nitro.hrl").
-include_lib("form/include/formReg.hrl").
-include_lib("form/include/step_wizard.hrl").
-include_lib("form/include/meta.hrl").
fieldId(X, Object, Opt) ->
form:atom([X#field.id,
form:type(Object),
form:kind(Opt)]).
sources(Object, Options) ->
Fields = proplists:get_value(fields, Options, []),
M = lists:filtermap(fun (X) ->
Id = list_to_atom(form:atom([X,
form:type(Object),
form:kind(Options)])),
case Fields of
[] -> {true, Id};
_ -> lists:member(X, Fields) andalso {true, Id}
end
end,
element(5, kvs:table(form:type(Object)))),
% io:format("sources: ~p~n",[M]),
M.
type(O) -> element(1, O).
kind(Options) ->
case lists:filter(fun ({_Y, Z}) -> Z end,
[{X, proplists:get_value(X, Options, false)}
|| X <- [create, view, search, edit]])
of
[] -> none;
[{K, true} | _] -> K
end.
pos(Object, X) ->
Tab = element(1, Object),
Fields = element(5, kvs:table(Tab)),
P = string:rstr([Tab | Fields], [X#field.id]),
% io:format("pos: ~p~n",[{P,Object,X}]),
P.
extract(Object, X, Opt) ->
Pos = form:pos(Object, X),
Value = element(Pos, Object),
case form:kind(Opt) of
create -> val_or_def(Value, X, Object);
_ -> Value
end.
val_or_def([], #field{default = Def}, Object)
when is_function(Def) ->
Def(Object);
val_or_def([], #field{default = Def}, _) -> Def;
val_or_def(Value, _, _) -> Value.
extract_view_bind(Object, X, Opt) ->
get_view_bind(extract(Object, X, Opt), X).
get_view_bind(Value, X) ->
Module = X#field.module,
case has_function(Module, view_value) of
true -> {Module:view_value(Value), Value};
false -> {Value, []}
end.
has_function([], _F) -> false;
has_function(M, F) ->
Functions = apply(M, module_info, [exports]),
IsF = proplists:get_value(F, Functions, -1),
IsF /= -1.
evoke(Object, X, Value) ->
setelement(form:pos(Object, X), Object, Value).
translate_error(A, B) -> io_lib:format("~p", [{A, B}]).
translate(A, B) -> io_lib:format("~p", [{A, B}]).
translate(A) -> io_lib:format("~p", [A]).
dispatch(Object, Options) ->
Name = proplists:get_value(name, Options, false),
Row = proplists:get_value(row, Options, false),
Compact = proplists:get_value(compact, Options, false),
Registry = application:get_env(form, registry, []),
#formReg{vertical = V, horizontal = H, compact = C} =
lists:keyfind(element(1, Object),
2,
Registry:registry()),
Module = case Compact of
true -> C;
false ->
case Row of
true -> H;
false -> V
end
end,
form:new(Module:new(nitro:compact(Name),
Object,
Options),
Object,
Options).
new(Document = #document{}, Object, Opt) ->
% io:format("new options: ~p~n",[Opt]),
Name = Document#document.name,
#panel{id = form:atom([form, Name]), class = form,
body =
[BuildBlock(Document, Object, Opt)
|| BuildBlock
<- [fun buttons/3, fun caption/3, fun fields/3]]};
new(Document, _Object, _Opt) ->
Document. % pass HTML5 as NITRO records
new(A, B) -> new(A, B, []).
steps(Document, _Object, _Opt) ->
StepWizard = Document#document.steps,
case StepWizard of
#step_wizard{} ->
#panel{class = steps,
body =
["<b>",
nitro:f(translate(step_wizard),
[StepWizard#step_wizard.current_step,
StepWizard#step_wizard.steps]),
"</b>"]};
_ -> []
end.
caption(#document{sections=[]},_,_) -> [];
caption(Document, _Object, _Opt) ->
Name = Document#document.name,
SectionList = Document#document.sections,
#panel{id = form:atom([form, Name, caption]), class = caption,
body =
[[#h4{class = Section#sec.nameClass, body = Section#sec.name},
#panel{class = Section#sec.descClass, body = Section#sec.desc}]
|| Section <- SectionList]}.
fields(Document, Object, Opt) ->
% Name = Document#document.name,
Fields = Document#document.fields,
lists:foldr(fun (X, Acc) ->
form:fieldType(X, Acc, Object, Opt)
end,
[],
Fields).
field(Field) ->
Id = element(2, Field),
[_Name, Doc, Type] = string:tokens(Id, "_"),
field(Field,
{nitro_conv:to_atom(Doc)},
{nitro_conv:to_atom(Type)}).
field(Field, Object, Opt) ->
form:fieldType(Field, [], Object, Opt).
buttons(#document{buttons=[]},_,_) -> [];
buttons(Document, Object, _Opt) ->
% Name = Document#document.name,
Buttons = Document#document.buttons,
#panel{id = forpreload, class = buttons,
body =
lists:foldr(fun (#but{} = But, Acc) ->
[#link{id =
form:atom([But#but.id,
form:type(Object),
form:kind(_Opt)]),
class = But#but.class, validate = But#but.validation,
postback = But#but.postback, body = But#but.title,
onclick = But#but.onclick, href = But#but.href,
target = But#but.target, source = But#but.sources}
| Acc]
end,
[],
Buttons)}.
% GENERIC MATCH
fieldType(#field{type = empty} = X, Acc, Object, Opt) ->
[#panel{class = box, style = "display:none;",
id = fieldId(X, Object, Opt)}
| Acc];
fieldType(#field{type = dynamic} = X, Acc, _Object,
_Opt) ->
[X#field.raw | Acc];
fieldType(#field{type = comment} = X, Acc, Object,
Opt) ->
[#panel{id =
form:atom([commentBlock,
form:type(Object),
form:kind(Opt)]),
class = box,
body =
[case X#field.tooltips of
false -> [];
true ->
[#panel{class = label, body = "&nbsp;"},
#panel{class = field, body = "&nbsp;"},
#panel{class = tool,
body =
[#link{id = form:atom([commentlink, X#field.id]),
class = tooltips,
onclick = nitro:f("showComment(this);"),
body = [#image{src = []}]}]}]
end,
#panel{class = comment, id = fieldId(X, Object, Opt),
body = [X#field.desc],
style =
case X#field.tooltips of
false -> "";
true -> "display:none;"
end}]}
| Acc];
fieldType(#field{type = card} = X, Acc, Object, Opt) ->
[#panel{id = form:fieldId(X, Object, Opt),
class = [box, pad0],
body =
[#panel{class = label, body = X#field.title},
#panel{class = field, style = "width:66.63%",
body =
#panel{id = form:atom([X#field.id, 1]),
body =
[#panel{class = field, style = "width:90%;",
body =
#select{id = form:atom([X#field.id, 2]),
disabled = true,
validation = form:val(Opt, "Validation.card(e)"),
body =
#option{selected = true,
body = form:translate(loading)}}},
#panel{class = tool, body = [#image{src = []}]}]}}]}
| Acc];
fieldType(#field{type = bool} = X, Acc, Object, Opt) ->
Val = form:extract(Object, X, Opt),
Options = [#opt{name = <<"true">>,
title = <<"Так"/utf8>>, checked = Val == true},
#opt{name = <<"false">>, title = <<"Ні"/utf8>>,
checked = Val == false},
#opt{name = <<"">>, title = <<"Не вибрано"/utf8>>,
checked = not is_boolean(Val)}],
form:fieldType(X#field{type = select,
options = Options},
Acc,
Object,
Opt);
fieldType(#field{} = X, Acc, Object, Opt) ->
Panel = case X#field.id of
[] -> #panel{};
_ ->
#panel{id =
form:atom([wrap,
X#field.id,
form:type(Object),
form:kind(Opt)])}
end,
Visibility = case X#field.visible of
false -> "display: none;";
true -> ""
end,
Tooltips = [case Tx of
{N} -> #panel{id = form:atom([tooltip, N]), body = []};
_ ->
#link{class = tooltips, tabindex = "-1",
onmouseover = "setHeight(this);",
body =
[#image{src = "app/img/question.png"},
#span{body = Tx}]}
end
|| Tx <- lists:reverse(X#field.tooltips)],
Options = [case {X#field.type, O#opt.noRadioButton} of
% SELECT/OPTION
{select, _} ->
#option{value = O#opt.name, body = O#opt.title,
selected = O#opt.checked};
% COMBO/RADIO
{combo, _} ->
#panel{body =
#label{body =
#radio{name = fieldId(X, Object, Opt),
id =
form:atom([X#field.id, form:type(Object), O#opt.name]),
body = O#opt.title, checked = O#opt.checked,
disabled = O#opt.disabled, value = O#opt.name,
postback = {X#field.id, type(Object), O#opt.name}}}};
% CHECKBOX
{check, _} ->
#checkbox{id =
form:atom([O#opt.name,
form:type(Object),
form:kind(Opt)]),
source = [form:atom([O#opt.name])], body = O#opt.title,
disabled = O#opt.disabled, checked = O#opt.checked,
postback = {O#opt.name}};
{_, _} ->
#label{id = form:atom([label, O#opt.name]), body = []}
end
|| O <- X#field.options],
% io:format("O: ~p~n",[{X,Object}]),
[Panel#panel{class = X#field.boxClass,
style = Panel#panel.style ++ Visibility,
body =
[#panel{class = X#field.labelClass,
body = X#field.title},
#panel{class = X#field.fieldClass,
body =
form:fieldType(X#field.type, X, Options, Object, Opt)},
#panel{class = tool,
body =
case Tooltips of
[] -> [];
_ ->
Dom = case length(Tooltips) of
1 -> #br{};
_ -> #panel{style = "height:10px;"}
end,
tl(lists:flatten(lists:zipwith(fun (A2, B2) -> [A2, B2]
end,
lists:duplicate(length(Tooltips), Dom),
Tooltips)))
end}]}
| Acc].
% SECOND LEVEL MATCH
fieldType(text, X, _Options, Object, Opt) ->
#panel{id = fieldId(X, Object, Opt),
body = X#field.desc};
fieldType(integer, X, _Options, Object, _Opt) ->
nitro:f(X#field.format,
[case X#field.postfun of
[] -> pos(Object, X);
PostFun -> PostFun(pos(Object, X))
end]);
fieldType(money, X, _Options, Object, Opt) ->
[#input{id = form:fieldId(X, Object, Opt),
pattern = "[0-9]*", multiple = X#field.multiple,
validation =
form:val(Opt,
nitro:f("Validation.money(e, ~w, ~w, '~s')",
[X#field.min,
X#field.max,
form:translate({?MODULE, error})])),
onkeyup = "beautiful_numbers(event);",
value = nitro:to_list(form:extract(Object, X, Opt))},
#panel{class = pt10,
body =
[form:translate({?MODULE, warning}),
nitro:to_binary(X#field.min),
" ",
X#field.curr]}];
fieldType(pay, X, _Options, Object, Opt) ->
#panel{body =
[#input{id = fieldId(X, Object, Opt),
pattern = "[0-9]*", multiple = X#field.multiple,
validation = form:val(Opt, X#field.validation),
onkeypress =
nitro:f("return fieldsFilter(event, ~w, '~w');",
[X#field.length, X#field.type]),
onkeyup = "beautiful_numbers(event);",
value = nitro:to_list(form:extract(Object, X, Opt))},
<<" ">>,
#span{body = X#field.curr}]};
fieldType(ComboCheck, _X, Options, _Object, _Opt)
when ComboCheck == combo orelse ComboCheck == check ->
Dom = case length(Options) of
1 -> #br{};
_ -> #panel{}
end,
tl(lists:flatten(lists:zipwith(fun (A, B) -> [A, B] end,
lists:duplicate(length(Options), Dom),
Options)));
fieldType(select, X, Options, Object, Opt) ->
#select{id = fieldId(X, Object, Opt),
postback = X#field.postback,
disabled = X#field.disabled,
multiple = X#field.multiple,
update = X#field.update,
body = Options};
fieldType(string, X, _Options, Object, Opt) ->
#input{class = column, id = fieldId(X, Object, Opt),
disabled = X#field.disabled,
multiple = X#field.multiple,
validation =
if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)",
[X#field.min, X#field.max]))
end,
value = form:extract(Object, X, Opt)};
fieldType(number, X, _Options, Object, Opt) ->
#input{class = column, type = number,
id = fieldId(X, Object, Opt),
multiple = X#field.multiple,
validation =
if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)",
[X#field.min, X#field.max]))
end,
disabled = X#field.disabled,
value = form:extract(Object, X, Opt)};
fieldType(email, X, _Options, Object, Opt) ->
#input{class = column, type = email,
id = fieldId(X, Object, Opt),
multiple = X#field.multiple,
validation =
form:val(Opt,
nitro:f("Validation.email(e, ~w, ~w)",
[X#field.min, X#field.max])),
disabled = X#field.disabled,
value = form:extract(Object, X, Opt)};
fieldType(phone, X, _Options, Object, Opt) ->
#input{id = fieldId(X, Object, Opt), class = phone,
pattern = "[0-9]*", multiple = X#field.multiple,
onkeypress =
nitro:f("return fieldsFilter(event, ~w, '~w');",
[X#field.length, X#field.type]),
validation =
form:val(Opt,
nitro:f("Validation.phone(e, ~w, ~w)",
[X#field.min, X#field.max])),
value = form:extract(Object, X, Opt)};
fieldType(auth, X, _Options, Object, Opt) ->
[#input{id = fieldId(X, Object, Opt), class = phone,
type = password,
onkeypress = "return removeAllErrorsFromInput(this);",
onkeyup = "nextByEnter(event);",
multiple = X#field.multiple,
validation =
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)",
[X#field.min, X#field.max])),
placeholder = form:translate({auth, holder})},
#span{class = auth_link,
body =
[#link{href = form:translate({auth, lost_pass_link}),
target = "_blank", postback = undefined,
body = form:translate({auth, lost_pass})},
#br{},
#link{href = form:translate({auth, change_login_link}),
target = "_blank", postback = undefined,
body = form:translate({auth, change_login})}]}];
fieldType(otp, X, _Options, Object, Opt) ->
#input{class = [phone, pass], type = password,
id = fieldId(X, Object, Opt), placeholder = "(XXXX)",
multiple = X#field.multiple, pattern = "[0-9]*",
validation =
form:val(Opt, "Validation.nums(e, 4, 4, \"otp\")")};
fieldType(password,X,_Options,Object,Opt) ->
#input{ class = [phone, pass],
type = password,
id = fieldId(X, Object, Opt),
multiple = X#field.multiple,
validation =
if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)",
[X#field.min, X#field.max]))
end,
value = form:extract(Object,X,Opt)};
fieldType(multipleInput, X, Options, Object, Opt) ->
Id = form:atom([X#field.id,
form:type(Object),
form:kind(Opt)]),
Validation = if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)",
[X#field.min, X#field.max]))
end,
Input = case X#field.input of
calendar ->
#panel{body = [I]} = fieldType(X#field.input,
X,
Options,
Object,
Opt),
I;
_ -> fieldType(X#field.input, X, Options, Object, Opt)
end,
InputId = form:atom([element(#element.id, Input),
"input"]),
Delegate = X#field.module,
RawValues = form:extract(Object, X, Opt),
Values = if not is_list(RawValues) -> [];
true ->
case erlang:function_exported(Delegate, view_value, 2)
of
true ->
{view_value_pairs,
[{Delegate:view_value(V, X#field.bind), V}
|| V <- RawValues]};
false ->
case form_backend:has_function(Delegate, view_value) of
true ->
{view_value_pairs,
[{Delegate:view_value(V), V} || V <- RawValues]};
false -> RawValues
end
end
end,
#multipleInput{id = Id,
input = setelement(#element.id, Input, InputId),
disabled = X#field.disabled, validation = Validation,
values = Values};
fieldType(comboLookup, X, _Options, Object, Opt) ->
{Value, Bind} = extract_view_bind(Object, X, Opt),
#comboLookup{id = fieldId(X, Object, Opt),
disabled = X#field.disabled,
validation =
if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)",
[X#field.min, X#field.max]))
end,
feed = X#field.bind, value = Value, bind = Bind,
delegate = X#field.module, reader = [], chunk = 20, update = X#field.update};
fieldType(comboLookupEdit, X, _Options, Object, Opt) ->
Id = form:atom([X#field.id,
form:type(Object),
form:kind(Opt)]),
Bind = form:extract(Object, X, false, Opt),
Delegate = X#field.module,
RawValues = form:extract(Object, X, Opt),
Values = case erlang:function_exported(Delegate,
view_value,
2)
of
true ->
{view_value_pairs,
[{Delegate:view_value(V, X#field.bind), V}
|| V <- RawValues]};
false ->
case form_backend:has_function(Delegate, view_value) of
true ->
{view_value_pairs,
[{Delegate:view_value(V), V} || V <- RawValues]};
false -> RawValues
end
end,
Input = #comboLookup{id = form:atom([Id, "input"]),
feed = X#field.bind, disabled = X#field.disabled,
bind = Bind, delegate = X#field.module, reader = [],
postback = X#field.postback, update = X#field.update, chunk = 20},
#comboLookupEdit{id = Id, input = Input,
disabled = X#field.disabled,
validation =
if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)\n "
" \t\t\t&& Validation.isString(e)",
[X#field.min, X#field.max]))
end,
form = X#field.form, values = Values,
multiple = X#field.multiple};
fieldType(comboLookupVec, X, Options, Object, Opt) ->
Id = fieldId(X, Object, Opt),
Delegate = X#field.module,
Input = #comboLookup{id = form:atom([Id, "input"]),
feed = X#field.bind, delegate = Delegate, reader = [],
style =
"padding-bottom: 10px; margin: 0; background-c"
"olor: inherit;",
chunk = 20, update = X#field.update},
Disabled = X#field.disabled,
RawValues = form:extract(Object, X, Opt),
Values = case form_backend:has_function(Delegate,
view_value)
of
true ->
{view_value_pairs,
[get_view_bind(V, X) || V <- RawValues]};
false -> RawValues
end,
Min = X#field.min,
Max = X#field.max,
Validation = if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.length(e, ~w, ~w)", [Min, Max]))
end,
#comboLookupVec{id = Id, input = Input,
disabled = Disabled, validation = Validation,
values = Values};
fieldType(comboLookupGroup,X,_Options,Object,Opt) ->
Id = form:atom([X#field.id,form:type(Object),form:kind(Opt)]),
Delegate = X#field.module,
Disabled = X#field.disabled,
Subtitle = X#field.subtitle,
RawValues = form:extract(Object,X,Opt),
Values =
if not is_list(RawValues) -> [];
true ->
case erlang:function_exported(Delegate, view_value, 2) of
true ->
lists:map(fun(Vals) when is_list(Vals) -> {view_value_pairs, [{Delegate:view_value(V, X#field.bind), V} || V <- Vals]};
(V) -> {view_value_pairs, [{Delegate:view_value(V, X#field.bind), V}]} end, RawValues);
false ->
case form_backend:has_function(Delegate, view_value) of
true -> lists:map(fun(Vals) when is_list(Vals) -> {view_value_pairs, [{Delegate:view_value(V), V} || V <- Vals]};
(V) -> {view_value, pairs, [{Delegate:view_value(V), V}]} end, RawValues);
false -> RawValues
end
end
end,
Input = #comboLookup{
id = form:atom([Id, "input"]),
feed = X#field.bind,
delegate = Delegate,
reader = [],
style = "padding-bottom: 10px; margin: 0; background-color: inherit;",
update = X#field.update,
chunk = 20},
Min = X#field.min,
Max = X#field.max,
Validation = if not X#field.required -> [];
true -> form:val(Opt,nitro:f("Validation.comboLookupGroup(e, ~w, ~w)",[Min, Max])) end,
#comboLookupGroup{id = Id,
input = Input,
disabled = Disabled,
validation = Validation,
values = Values,
subtitle = Subtitle,
delegate = Delegate};
fieldType(comboLookupModify, X, Options, Object, Opt) ->
Id = fieldId(X, Object, Opt),
Delegate = X#field.module,
Input = #comboLookup{id = form:atom([Id, "input"]),
feed = X#field.bind, delegate = Delegate, reader = [],
style =
"padding-bottom: 10px; margin: 0; background-c"
"olor: inherit;",
update = X#field.update,
chunk = 20},
Disabled = X#field.disabled,
RawValues = form:extract(Object, X, Opt),
Values = case form_backend:has_function(Delegate,
view_value)
of
true ->
{view_value_pairs,
[get_view_bind(V, X) || V <- RawValues]};
false -> RawValues
end,
Min = X#field.min,
Max = X#field.max,
Validation = if not X#field.required -> [];
true ->
form:val(Opt,
nitro:f("Validation.comboLookupModify(e, ~w, ~w)", [Min, Max]))
end,
#comboLookupModify{id = Id, input = Input,
disabled = Disabled, validation = Validation,
values = Values, modify_pos = X#field.modify_pos,
modify_feed = X#field.modify_feed, modify_module = X#field.modify_module,
modify_default = X#field.modify_default};
fieldType(file, _X, _Options, _Object, _Opt) -> [];
fieldType(calendar, X, _Options, Object, Opt) ->
#panel{class = [field],
body =
[#calendar{value = form:extract(Object, X, Opt),
id = fieldId(X, Object, Opt),
disabled = X#field.disabled,
onkeypress = "return removeAllErrorsFromInput(this);",
validation =
if not X#field.required -> [];
true -> form:val(Opt, "Validation.calendar(e)")
end,
disableDayFn = "disableDays4Charge",
class = ['input-date'], minDate = X#field.min,
maxDate = X#field.max, multiple = X#field.multiple,
lang = ua}]}.
val(Options, Validation) ->
case proplists:get_value(search, Options, false) of
true -> [];
false -> Validation
end.
proto(#comboUpdate{module = Module} = X) ->
case form_backend:has_function(Module, update) of
true -> Module:update(X);
false -> update(X)
end.
update(#comboUpdate{id = Id, fields = Fields, value = DefValue, module = Module, action = Action}) ->
DefRec = Module:id(),
Value = case DefValue of [] -> nitro:q(list_to_atom(form:atom([Id, element(1, DefRec), Action]))); _ -> DefValue end,
Rec = kvs:setfield(DefRec, Id, Value),
Options = [{Action, true}],
#document{fields = F} = Module:new(update, Rec, Options),
lists:foreach(fun (Fid) ->
nitro:update(
form:atom([wrap, Fid, element(1, Rec), Action]),
form:field(lists:keyfind(Fid, 2, F), Rec, Options)
)
end, Fields).