Browse Source

nitro v6.6.1 init

221V 3 years ago
parent
commit
37a67a3168
127 changed files with 6322 additions and 1 deletions
  1. 1 0
      .gitignore
  2. 15 0
      LICENSE
  3. 25 1
      README.md
  4. 12 0
      include/calendar.hrl
  5. 19 0
      include/comboLookup.hrl
  6. 7 0
      include/comboLookupEdit.hrl
  7. 7 0
      include/comboLookupText.hrl
  8. 7 0
      include/comboLookupVec.hrl
  9. 8 0
      include/cx.hrl
  10. 8 0
      include/event.hrl
  11. 7 0
      include/koatuuControl.hrl
  12. 48 0
      include/n2o.hrl
  13. 189 0
      include/nitro.hrl
  14. 12 0
      include/proto.hrl
  15. 7 0
      include/sortable_item.hrl
  16. 7 0
      include/sortable_list.hrl
  17. 34 0
      lib/NITRO.ex
  18. 199 0
      lib/combo.ex
  19. 221 0
      priv/css/calendar.css
  20. 106 0
      priv/css/sortable.css
  21. 1178 0
      priv/js/calendar.js
  22. 110 0
      priv/js/comboLookup.js
  23. 80 0
      priv/js/nitro.js
  24. 232 0
      priv/js/sortable.js
  25. 20 0
      priv/js/validation.js
  26. 7 0
      src/actions/action_alert.erl
  27. 12 0
      src/actions/action_api.erl
  28. 18 0
      src/actions/action_bind.erl
  29. 10 0
      src/actions/action_confirm.erl
  30. 24 0
      src/actions/action_event.erl
  31. 23 0
      src/actions/action_jq.erl
  32. 20 0
      src/actions/action_manage.erl
  33. 12 0
      src/actions/action_transfer.erl
  34. 9 0
      src/actions/action_ui.erl
  35. 12 0
      src/actions/action_wire.erl
  36. 88 0
      src/elements/combo/element_calendar.erl
  37. 39 0
      src/elements/combo/element_comboLookup.erl
  38. 42 0
      src/elements/combo/element_comboLookupEdit.erl
  39. 28 0
      src/elements/combo/element_comboLookupText.erl
  40. 31 0
      src/elements/combo/element_comboLookupVec.erl
  41. 18 0
      src/elements/combo/element_koatuu.erl
  42. 37 0
      src/elements/combo/element_sortable_item.erl
  43. 24 0
      src/elements/combo/element_sortable_list.erl
  44. 29 0
      src/elements/edit/element_del.erl
  45. 29 0
      src/elements/edit/element_ins.erl
  46. 36 0
      src/elements/embed/element_area.erl
  47. 35 0
      src/elements/embed/element_audio.erl
  48. 29 0
      src/elements/embed/element_canvas.erl
  49. 31 0
      src/elements/embed/element_embed.erl
  50. 34 0
      src/elements/embed/element_iframe.erl
  51. 19 0
      src/elements/embed/element_image.erl
  52. 28 0
      src/elements/embed/element_map.erl
  53. 34 0
      src/elements/embed/element_object.erl
  54. 29 0
      src/elements/embed/element_param.erl
  55. 30 0
      src/elements/embed/element_source.erl
  56. 32 0
      src/elements/embed/element_track.erl
  57. 37 0
      src/elements/embed/element_video.erl
  58. 26 0
      src/elements/form/element_button.erl
  59. 39 0
      src/elements/form/element_fieldset.erl
  60. 44 0
      src/elements/form/element_form.erl
  61. 43 0
      src/elements/form/element_keygen.erl
  62. 14 0
      src/elements/form/element_label.erl
  63. 12 0
      src/elements/form/element_legend.erl
  64. 33 0
      src/elements/form/element_meter.erl
  65. 30 0
      src/elements/form/element_output.erl
  66. 29 0
      src/elements/form/element_progress.erl
  67. 48 0
      src/elements/form/element_select.erl
  68. 41 0
      src/elements/form/element_textarea.erl
  69. 13 0
      src/elements/group/element_blockquote.erl
  70. 20 0
      src/elements/group/element_dtl.erl
  71. 28 0
      src/elements/group/element_html.erl
  72. 12 0
      src/elements/group/element_li.erl
  73. 35 0
      src/elements/group/element_script.erl
  74. 46 0
      src/elements/input/element_checkbox.erl
  75. 45 0
      src/elements/input/element_color.erl
  76. 50 0
      src/elements/input/element_date.erl
  77. 50 0
      src/elements/input/element_datetime.erl
  78. 50 0
      src/elements/input/element_datetime_local.erl
  79. 29 0
      src/elements/input/element_dropdown.erl
  80. 52 0
      src/elements/input/element_email.erl
  81. 45 0
      src/elements/input/element_file.erl
  82. 32 0
      src/elements/input/element_hidden.erl
  83. 58 0
      src/elements/input/element_input.erl
  84. 42 0
      src/elements/input/element_input_button.erl
  85. 51 0
      src/elements/input/element_input_image.erl
  86. 50 0
      src/elements/input/element_input_time.erl
  87. 44 0
      src/elements/input/element_link.erl
  88. 15 0
      src/elements/input/element_list.erl
  89. 11 0
      src/elements/input/element_literal.erl
  90. 49 0
      src/elements/input/element_month.erl
  91. 51 0
      src/elements/input/element_number.erl
  92. 50 0
      src/elements/input/element_password.erl
  93. 37 0
      src/elements/input/element_radio.erl
  94. 24 0
      src/elements/input/element_radiogroup.erl
  95. 48 0
      src/elements/input/element_range.erl
  96. 43 0
      src/elements/input/element_reset.erl
  97. 52 0
      src/elements/input/element_search.erl
  98. 25 0
      src/elements/input/element_submit.erl
  99. 51 0
      src/elements/input/element_tel.erl
  100. 23 0
      src/elements/input/element_textbox.erl
  101. 51 0
      src/elements/input/element_url.erl
  102. 50 0
      src/elements/input/element_week.erl
  103. 32 0
      src/elements/interactive/element_command.erl
  104. 28 0
      src/elements/interactive/element_details.erl
  105. 29 0
      src/elements/interactive/element_menu.erl
  106. 26 0
      src/elements/interactive/element_summary.erl
  107. 24 0
      src/elements/interactive/element_upload.erl
  108. 32 0
      src/elements/meta/element_meta.erl
  109. 29 0
      src/elements/meta/element_meta_base.erl
  110. 33 0
      src/elements/meta/element_meta_link.erl
  111. 30 0
      src/elements/meta/element_style.erl
  112. 28 0
      src/elements/table/element_col.erl
  113. 28 0
      src/elements/table/element_colgroup.erl
  114. 34 0
      src/elements/table/element_table.erl
  115. 16 0
      src/elements/table/element_td.erl
  116. 14 0
      src/elements/table/element_th.erl
  117. 19 0
      src/elements/table/element_tr.erl
  118. 9 0
      src/nitro.app.src
  119. 225 0
      src/nitro.erl
  120. 207 0
      src/nitro_conv.erl
  121. 104 0
      src/nitro_n2o.erl
  122. 68 0
      src/nitro_static.erl
  123. 36 0
      src/render/wf_event.erl
  124. 15 0
      src/render/wf_render.erl
  125. 15 0
      src/render/wf_render_actions.erl
  126. 76 0
      src/render/wf_render_elements.erl
  127. 29 0
      src/render/wf_tags.erl

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+ebin

+ 15 - 0
LICENSE

@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2013—2021 Maxim Sokhatsky <maxim@synrc.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

+ 25 - 1
README.md

@@ -1,2 +1,26 @@
-# nitro
+NITRO: Nitrogen Web Framework  
+=============================
 
+https://github.com/synrc/nitro v6.6.1 fork for n2z  
+
+Features  
+--------
+
+* Full HTML5 and SVG Support
+* Support for N2O Erlang WebSocket Application Server
+* Native Erlang way for UI/UX controls as HTML/JS/CSS components
+* Tag Inheritance
+* Polymorphic Tuples
+* Compact size (2500 LOC)
+* As fast as DTL and EEX
+* Elixir, MIX and HEX support
+* Custom Tags, ARIA, Web Components
+
+Credits
+-------
+
+* Maxim Sokhatsky — adaptation
+* Rusty Klophaus — original author
+* Jesse Gumm — author of http://nitrogenproject.com/
+
+OM A HUM

+ 12 - 0
include/calendar.hrl

@@ -0,0 +1,12 @@
+-ifndef(CAL_HRL).
+-define(CAL_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+
+-record(calendar, {?ELEMENT_BASE(element_calendar),
+  autocomplete=true, autofocus=false, disabled=false, form=[], list=[], maxDate={2019,2,2},
+    minDate, format="DD.MM.YYYY", pattern=[], name=[], step=[], readonly=[], required=[],
+    value={2020,2,2}, placeholder=[], onSelect=[], disableDayFn=[], firstDay=0, position=[],
+    reposition=[], yearRange=100}).
+
+-endif.

+ 19 - 0
include/comboLookup.hrl

@@ -0,0 +1,19 @@
+-ifndef(COMBO_LOOKUP_HRL).
+-define(COMBO_LOOKUP_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+
+-record(comboKey,  { value=[], dom=[], feed=[], delegate=[]}).
+-record(comboKeyup,  { value=[], dom=[], feed=[], delegate=[]}).
+-record(comboSelect,  { value=[], dom=[], feed=[], delegate=[] }).
+-record(comboNext,   { pos=[],  count=[], feed=[]}).
+-record(comboLookup, { ?ELEMENT_BASE(element_comboLookup),
+    value=[],
+    disabled=false,
+    feed=[],
+    reader=[],
+    chunk=20 }).
+
+-record(process, {name=[]}).
+
+-endif.

+ 7 - 0
include/comboLookupEdit.hrl

@@ -0,0 +1,7 @@
+-ifndef(COMBO_LOOKUP_EDIT_HRL).
+-define(COMBO_LOOKUP_EDIT_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+-record(comboLookupEdit, {?ELEMENT_BASE(element_comboLookupEdit), input, disabled, form, values, multiple}).
+
+-endif.

+ 7 - 0
include/comboLookupText.hrl

@@ -0,0 +1,7 @@
+-ifndef(COMBO_LOOKUP_TEXT_HRL).
+-define(COMBO_LOOKUP_TEXT_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+-record(comboLookupText, {?ELEMENT_BASE(element_comboLookupText), input, disabled, textarea, values}).
+
+-endif.

+ 7 - 0
include/comboLookupVec.hrl

@@ -0,0 +1,7 @@
+-ifndef(COMBO_LOOKUP_VEC_HRL).
+-define(COMBO_LOOKUP_VEC_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+-record(comboLookupVec, {?ELEMENT_BASE(element_comboLookupVec), input, disabled, values}).
+
+-endif.

+ 8 - 0
include/cx.hrl

@@ -0,0 +1,8 @@
+-ifndef(NITRO_CX).
+-define(NITRO_CX, true).
+
+-record(cx, { handlers=[], actions=[], req=[], module=[], lang=[], path=[],
+              session=[], token=[], formatter=bert, params=[], node=[],
+              client_pid=[], state=[], from=[], vsn = [] }).
+
+-endif.

+ 8 - 0
include/event.hrl

@@ -0,0 +1,8 @@
+-ifndef(NITRO_EVENT).
+-define(NITRO_EVENT, true).
+
+-include_lib("nitro/include/nitro.hrl").
+
+-record(event,   {?ACTION_BASE(action_event), type=default, postback, delegate, validation=[]}).
+
+-endif.

+ 7 - 0
include/koatuuControl.hrl

@@ -0,0 +1,7 @@
+-ifndef(KOATUU_NX_HRL).
+-define(KOATUU_NX_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+-record(koatuu, {?ELEMENT_BASE(element_koatuu)}).
+
+-endif.

+ 48 - 0
include/n2o.hrl

@@ -0,0 +1,48 @@
+-ifndef(N2O_HRL).
+-define(N2O_HRL, true).
+
+-define(FORMAT(F), case F of F when is_binary(F) -> binary_to_list(F);
+                             F when is_atom(F) -> atom_to_list(F);
+                             F when is_list(F) -> F end).
+
+-ifdef(OTP_RELEASE).
+-include_lib("kernel/include/logger.hrl").
+-else.
+-define(LOG_INFO(F), io:format(?FORMAT(F)) end).
+-define(LOG_INFO(F,X), io:format(?FORMAT(F),X)).
+-define(LOG_ERROR(F), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,F])).
+-define(LOG_ERROR(F,X), io:format(?FORMAT(F),X)).
+-endif.
+
+-define(LOG_EXCEPTION(E,R,S), ?LOG_ERROR(#{exception => E, reason => R, stack => S})).
+
+-record(pi, { name     :: term(),
+              table    :: atom(),
+              sup      :: atom(),
+              module   :: atom(),
+              state    :: term()  }).
+
+-record(cx, { handlers  = [] :: list({atom(),atom()}),
+              actions   = [] :: list(tuple()),
+              req       = [] :: [] | term(),
+              module    = [] :: [] | atom() | list(),
+              lang      = [] :: [] | atom(),
+              path      = [] :: [] | binary(),
+              session   = [] :: [] | binary(),
+              token     = [] :: [] | binary(),
+              formatter = bert :: bert | json | atom(),
+              params    = [] :: [] | list(tuple()) | binary() | list(),
+              node      = [] :: [] | atom() | list(),
+              client_pid= [] :: [] | term(),
+              state     = [] :: [] | term(),
+              from      = [] :: [] | binary(),
+              vsn       = [] :: [] | binary() }).
+
+-define(CTX(ClientId), n2o:cache(ClientId)).
+-define(REQ(ClientId), (n2o:cache(ClientId))#cx.req).
+
+% Nitrogen Protocol
+
+-include_lib("nitro/include/proto.hrl").
+
+-endif.

+ 189 - 0
include/nitro.hrl

@@ -0,0 +1,189 @@
+-ifndef(NITRO_HRL).
+-define(NITRO_HRL, true).
+
+-ifndef(CTX).
+-define(CTX, (get(context))).
+-endif.
+
+-define(DEFAULT_BASE, {?ELEMENT_BASE([])}).
+-define(DEFAULT_BASE_TAG(Tag), {?ELEMENT_BASE([],Tag,[])}).
+-define(ELEMENT_BASE(Module), ?ELEMENT_BASE(Module,[],[])).
+-define(ELEMENT_BASE(Module,Tag,Delegate),
+        ancestor=element, id=[], module=Module, delegate=Delegate, validation=[],
+        validate=[], actions=[], class=[], style=[], source=[], onmouseover=[], onmouseout=[], onmousemove=[],
+        onkeypress=[], onchange=[], onkeyup=[], onkeydown=[], onclick=[],
+        data_fields=[], aria_states=[], body=[], role=[], tabindex=[], show_if=true,
+        html_tag=Tag, title=[], postback=[], accesskey=[], contenteditable=[],
+        contextmenu=[], dir=[], draggable=[], dropzone=[], hidden=[], lang=[],
+        spellcheck=[], translate=[], onblur=[], onerror=[], onfocus=[],
+        onmessage=[], onresize=[], bind=[]).
+
+-define(ACTION_BASE(Module), ancestor=action, trigger=[], target=[], module=Module, actions=[], source=[]).
+-define(CTRL_BASE(Module), ?ELEMENT_BASE(Module,[],Module)).
+
+-record(element, {?ELEMENT_BASE([])}).
+-record(literal, {?ELEMENT_BASE(element_literal), html_encode=true }).
+-record(dtl, {?ELEMENT_BASE(element_dtl), file="index", bindings=[], app=web, folder="priv/templates", ext="html", bind_script=true, js_escape=false }).
+-record(list, {?ELEMENT_BASE(element_list), numbered=false }).
+-record(dropdown, {?ELEMENT_BASE(element_dropdown), options, value, multiple=false, disabled=false, name}).
+-record(radiogroup, {?ELEMENT_BASE(element_radiogroup)}).
+-record(spinner, {?ELEMENT_BASE(element_spinner), image="/priv/static/spinner.gif"}).
+
+% HTML Document meta
+-record(base,       {?ELEMENT_BASE(element_meta_base), href=[], target=[]}).
+-record(head,       ?DEFAULT_BASE).
+-record(meta_link,       {?ELEMENT_BASE(element_meta_link), href=[], hreflang=[], media=[], rel=[], sizes=[], type=[]}).
+-record(meta,       {?ELEMENT_BASE(element_meta), charset=[], content=[], http_equiv=[], name=[], type=[]}).
+-record(style,       {?ELEMENT_BASE(element_style), media=[], scoped=[], type=[]}).
+-record(title,       ?DEFAULT_BASE).
+
+% HTML Edits
+-record('del',       {?ELEMENT_BASE(element_del), cite=[], datetime}).
+-record(ins,       {?ELEMENT_BASE(element_ins), cite=[], datetime}).
+
+% HTML Embedded
+-record(area,       {?ELEMENT_BASE(element_area), alt, coords, href, hreflang, media, target, rel, shape, type}).
+-record(audio,       {?ELEMENT_BASE(element_audio), autoplay, controls, loop, mediagroup, muted, preload, src, width}).
+-record(canvas,       {?ELEMENT_BASE(element_canvas), height, width}).
+-record(embed,       {?ELEMENT_BASE(element_embed), height, src, type, width}).
+-record(iframe,       {?ELEMENT_BASE(element_iframe), height, name, sandbox, seamless, src, srcdoc, width}).
+-record(image,       {?ELEMENT_BASE(element_image), alt, height, ismap, src, usemap, width, image}).
+-record(map,       {?ELEMENT_BASE(element_map), name}).
+-record(object,       {?ELEMENT_BASE(element_object), data, form, height, name, type, usemap, width}).
+-record(param,       {?ELEMENT_BASE(element_param), name, value}).
+-record(source,       {?ELEMENT_BASE(element_source), media, src, type}).
+-record(track,       {?ELEMENT_BASE(element_track), default, kind, label, src, srclang}).
+-record(video,       {?ELEMENT_BASE(element_video), autoplay, controls, height, loop, mediagroup, muted, poster, preload, src, width}).
+
+% HTML Form
+-record(button,       {?ELEMENT_BASE(element_button), autofocus=[], disabled=[], form=[], formaction=[], formenctype=[], formmethod=[], formtarget=[], formnovalidate=[], name=[], type= <<"button">>, value=[]}).
+-record(datalist,       ?DEFAULT_BASE).
+-record(fieldset,       {?ELEMENT_BASE(element_fieldset), disabled=[], form=[], name=[], legend=[]}).
+-record(form,       {?ELEMENT_BASE(element_form), accept_charset=[], action=[], autocomplete=[], enctype=[], method=[], name=[], novalidate=[], target=[]}).
+-record(keygen,       {?ELEMENT_BASE(element_keygen), autofocus=[], challenge=[], disabled=[], form=[], keytype=[], name=[]}).
+-record(legend,       ?DEFAULT_BASE).
+-record(label,       {?ELEMENT_BASE(element_label), for=[], form=[]}).
+-record(meter,       {?ELEMENT_BASE(element_meter), high=[], low=[], max=[], min=[], optimum=[], value=[]}).
+-record(optgroup,       {?ELEMENT_BASE(element_select), disabled=[], label=[]}).
+-record(option,       {?ELEMENT_BASE(element_select), disabled=[], label=[], selected=false, value=[]}).
+-record(output,       {?ELEMENT_BASE(element_output), for, form, name}).
+-record(progress,       {?ELEMENT_BASE(element_progress), max=[], value=[]}).
+-record(select,       {?ELEMENT_BASE(element_select), autofocus=[], disabled=[], form=[], multiple=[], name=[], required=[], size=[]}).
+-record(textarea,       {?ELEMENT_BASE(element_textarea), autofocus=[], cols=[], dirname=[], disabled=[], form=[], maxlength, name, placeholder, readonly=[], required=[], rows=[], wrap=[], value=[]}).
+
+% HTML Form inputs
+-record(input,       {?ELEMENT_BASE(element_input), required, autocomplete, autofocus, disabled, form, name, value, type=[], checked=false, placeholder, multiple, min, max, pattern, accept}).
+-record(input_button,       {?ELEMENT_BASE(element_input_button),  autofocus, disabled, form, name, value}).
+-record(checkbox,           {?ELEMENT_BASE(element_checkbox),  autofocus, checked=false, disabled, form, name, required, value}).
+-record(color,           {?ELEMENT_BASE(element_color),  autocomplete, autofocus, disabled, form, list, name, value}).
+-record(date,           {?ELEMENT_BASE(element_date),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
+-record(datetime,           {?ELEMENT_BASE(element_datetime),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
+-record(datetime_local,           {?ELEMENT_BASE(element_datetime_local),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
+-record(email,           {?ELEMENT_BASE(element_email),  autocomplete, autofocus, disabled, form, list, maxlength, multiple, name, pattern, placeholder, readonly, required, size, value}).
+-record(file,           {?ELEMENT_BASE(element_file),  accept, autofocus, disabled, form, multiple, name, required}).
+-record(hidden,           {?ELEMENT_BASE(element_hidden),  disabled, form, name, value, html_name}).
+-record(input_image,           {?ELEMENT_BASE(element_input_image),  alt, autofocus, disabled, form, formaction, formenctype, formmethod, formnovalue, formtarget, height, name, src, width}).
+-record(month,              {?ELEMENT_BASE(element_month),  alt, autocomplete, autofocus, disabled, form, list, min, max, name, readonly, required, step, value}).
+-record(number,              {?ELEMENT_BASE(element_number),  autocomplete, autofocus, disabled, form, list, max, min, name, placeholder, readonly, required, step, value}).
+-record(password,              {?ELEMENT_BASE(element_password),  autocomplete, autofocus, disabled, form, maxlength, name, pattern, placeholder, readonly, required, size, value}).
+-record(radio,              {?ELEMENT_BASE(element_radio),  autofocus, checked, disabled, form, name, required, value, html_name}).
+-record(range,              {?ELEMENT_BASE(element_range),  autocomplete, autofocus, disabled, form, list, max=100, min=0, name, step=1, value}).
+-record(reset,              {?ELEMENT_BASE(element_reset),  autofocus, disabled, form, name, value}).
+-record(search,              {?ELEMENT_BASE(element_search),  autocomplete, autofocus, dirname, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
+-record(submit,              {?ELEMENT_BASE(element_submit),  autofocus, disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, value, click}).
+-record(tel,              {?ELEMENT_BASE(element_tel),  autocomplete, autofocus, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
+-record(textbox,              {?ELEMENT_BASE(element_textbox),  autocomplete, autofocus, dirname, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
+-record(input_time,              {?ELEMENT_BASE(element_input_time),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
+-record(url,              {?ELEMENT_BASE(element_url),  autocomplete, autofocus, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
+-record(week,              {?ELEMENT_BASE(element_week),  autocomplete, autofocus, disabled, form, list, max, min, name, readonly, required, step, value}).
+
+% HTML Interactive
+-record(command,       {?ELEMENT_BASE(element_command),  checked, disabled, icon, label, radiogroup, type= <<"command">>}).
+-record(details,       {?ELEMENT_BASE(element_details),  open}).
+-record(menu,       {?ELEMENT_BASE(element_menu),  label, type}).
+-record(summary,       ?DEFAULT_BASE).
+
+% HTML Grouping content
+-record(blockquote,		{?ELEMENT_BASE(element_blockquote),  cite}).
+-record(br,       		?DEFAULT_BASE).
+-record(dd,       		?DEFAULT_BASE).
+-record('div',      	?DEFAULT_BASE_TAG(<<"div">>)).
+-record(dl,       		?DEFAULT_BASE).
+-record(dt,       		?DEFAULT_BASE).
+-record(figcaption,		?DEFAULT_BASE).
+-record(figure,       	?DEFAULT_BASE).
+-record(hr,       		?DEFAULT_BASE).
+-record(li,             {?ELEMENT_BASE(element_li),  value}).
+-record(ol,             ?DEFAULT_BASE).
+-record(p,       		?DEFAULT_BASE).
+-record(panel,          ?DEFAULT_BASE_TAG(<<"div">>)).
+-record(pre,       		?DEFAULT_BASE).
+-record(ul,       		?DEFAULT_BASE).
+
+% HTML Root
+-record(html,			{?ELEMENT_BASE(element_html), manifest}).
+
+% HTML Scripting
+-record(script,			{?ELEMENT_BASE(element_script),  async=[], charset=[], defer=[], src=[], type=[]}).
+-record(noscript,      	?DEFAULT_BASE).
+
+% HTML Sections
+-record(body,       	?DEFAULT_BASE).
+-record(section,    	?DEFAULT_BASE).
+-record(nav,        	?DEFAULT_BASE).
+-record(article,    	?DEFAULT_BASE).
+-record(aside,      	?DEFAULT_BASE).
+-record(h1,         	?DEFAULT_BASE).
+-record(h2,         	?DEFAULT_BASE).
+-record(h3,         	?DEFAULT_BASE).
+-record(h4,         	?DEFAULT_BASE).
+-record(h5,         	?DEFAULT_BASE).
+-record(h6,         	?DEFAULT_BASE).
+-record(header,     	?DEFAULT_BASE).
+-record(hgroup,     	?DEFAULT_BASE).
+-record(footer,     	?DEFAULT_BASE).
+-record(address,    	?DEFAULT_BASE).
+-record(main,       	?DEFAULT_BASE).
+
+% HTML Table
+-record(caption,       	?DEFAULT_BASE).
+-record(col,            {?ELEMENT_BASE(element_col),  span}).
+-record(colgroup,       {?ELEMENT_BASE(element_colgroup), col, span}).
+-record(table,          {?ELEMENT_BASE(element_table),  caption, colgroup, border, footer, header}).
+-record(tbody,          ?DEFAULT_BASE).
+-record(td, 			{?ELEMENT_BASE(element_td), colspan=1, headers, rowspan=1, scope, bgcolor}).
+-record(tfoot,       	?DEFAULT_BASE).
+-record(th, 			{?ELEMENT_BASE(element_th), colspan=1, headers, rowspan=1, scope}).
+-record(thead,       	?DEFAULT_BASE).
+-record(tr, 			{?ELEMENT_BASE(element_tr), cells}).
+
+% Text
+-record(link,           {?ELEMENT_BASE(element_link),  href, hreflang, media, rel, target, type, url="javascript:void(0);", download, name}).
+-record(mark,           ?DEFAULT_BASE).
+-record(code,           ?DEFAULT_BASE).
+-record(span,           ?DEFAULT_BASE).
+
+% Extras
+-record(upload,         {?CTRL_BASE(element_upload), name, value}).
+
+% HTML5 template
+-record(template,		?DEFAULT_BASE).
+-record(message,		?DEFAULT_BASE).
+-record(author,		    ?DEFAULT_BASE).
+
+-record(action,  {?ACTION_BASE(undefined)}).
+-record(wire,    {?ACTION_BASE(action_wire)}).
+
+-record(replace, {?ACTION_BASE(action_manage), elements}).
+-record(insert,  {?ACTION_BASE(action_manage), elements, position = beforeend}).
+-record(multi,   {?ACTION_BASE(action_manage)}).
+-record(focus,   {?ACTION_BASE(action_ui)}).
+
+-record(api,     {?ACTION_BASE(action_api), name, tag, delegate }).
+-record(bind,    {?ACTION_BASE(action_bind), type=click, postback}).
+-record(alert,   {?ACTION_BASE(action_alert), text}).
+-record(confirm, {?ACTION_BASE(action_confirm), text, postback, delegate}).
+-record(jq,      {?ACTION_BASE(action_jq), property, method, args=[], right, format="~s"}).
+-record(transfer,{?ACTION_BASE(action_transfer), state, events=[] }).
+
+-endif.

+ 12 - 0
include/proto.hrl

@@ -0,0 +1,12 @@
+-ifndef(NITRO_PROTO_HRL).
+-define(NITRO_PROTO_HRL, true).
+
+-record(client,  { data=[] }).
+-record(server,  { data=[] }).
+-record(init,    { token=[] }).
+-record(pickle,  { source=[], pickled=[], args=[] }).
+-record(flush,   { data=[] }).
+-record(direct,  { data=[] }).
+-record(ev,      { module=[], msg=[], trigger=[], name=[] }).
+
+-endif.

+ 7 - 0
include/sortable_item.hrl

@@ -0,0 +1,7 @@
+-ifndef(SORTABLE_ITEM_NX_HRL).
+-define(SORTABLE_ITEM_NX_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+-record(sortable_item, {?ELEMENT_BASE(element_sortable_item), list_id, value, closeable, disabled}).
+
+-endif.

+ 7 - 0
include/sortable_list.hrl

@@ -0,0 +1,7 @@
+-ifndef(SORTABLE_LIST_NX_HRL).
+-define(SORTABLE_LIST_NX_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+-record(sortable_list, {?ELEMENT_BASE(element_sortable_list), values, closeable, disabled}).
+
+-endif.

+ 34 - 0
lib/NITRO.ex

@@ -0,0 +1,34 @@
+defmodule NITRO do
+  require Record
+
+  files = ["calendar.hrl", "nitro.hrl", "comboLookup.hrl",
+           "comboLookupVec.hrl", "comboLookupEdit.hrl", "koatuuControl.hrl"]
+
+  hrl_files =
+    Enum.filter(files, fn f ->
+      !String.contains?(f, "/_") and Path.extname(f) == ".hrl"
+    end)
+
+  Enum.each(
+    hrl_files,
+    fn t ->
+      Enum.each(
+        Record.extract_all(from_lib: "nitro/include/" <> t),
+        fn {name, definition} ->
+#          IO.inspect({name, definition, t})
+          prev = :application.get_env(:kernel, :nitro_tables, [])
+
+          case :lists.member(name, prev) do
+            true ->
+              :skip
+
+            false ->
+              Record.defrecord(name, definition)
+              :application.set_env(:kernel, :nitro_tables, [name | prev])
+          end
+        end
+      )
+    end
+  )
+
+end

+ 199 - 0
lib/combo.ex

@@ -0,0 +1,199 @@
+defmodule NITRO.Combo do
+  require NITRO
+  require Record
+
+  def to_list(fld) when is_atom(fld), do: fld
+  def to_list(fld), do: :unicode.characters_to_list(fld, :unicode)
+
+  def new(_name, obj, module, options) do
+    dom = :proplists.get_value(:dom, options)
+    feed = :proplists.get_value(:feed, options)
+    dropDown(obj, dom, module, feed)
+  end
+
+  def proto(NITRO.comboKey(value: :all) = msg), do: keyUp(msg)
+  def proto(NITRO.comboKey(delegate: []) = msg), do: keyUp(msg)
+
+  def proto(NITRO.comboKey(delegate: module) = msg) do
+    case has_function(module, :keyUp) do
+      true -> module.keyUp(msg)
+      false -> keyUp(msg)
+    end
+  end
+
+  def proto(NITRO.comboSelect(delegate: module) = msg) do
+    case has_function(module, :select) do
+      true -> module.select(msg)
+      false -> :skip
+    end
+  end
+
+  def keyUp(NITRO.comboKey(value: :all, dom: field0, feed: feed, delegate: module)) do
+    field = :nitro.to_list(field0)
+    all = apply(:kvs,:all,[feed])
+    :nitro.clear(:nitro.atom([:comboContainer, field]))
+    dropDownList0(all, field, module, feed)
+  end
+
+  def keyUp(NITRO.comboKey(value: value0, dom: field0, feed: feed, delegate: module)) do
+    value = :string.lowercase(:unicode.characters_to_list(value0, :unicode))
+    field = :nitro.to_list(field0)
+    all = apply(:kvs,:all,[feed])
+    index = index(module)
+    :nitro.clear(:nitro.atom([:comboContainer, field]))
+    :nitro.wire("comboLookupChange('#{field0}');")
+
+    filtered =
+      Enum.filter(all, fn x ->
+        Enum.any?(index, fn i ->
+          fld0 =
+            if is_function(i) do
+              i.(x)
+            else
+              f0 = apply(:kvs,:field, [x, i])
+              tab = elem(x, 0)
+
+              if f0 == tab do
+                :nitro.to_list(f0)
+              else
+                f0
+              end
+            end
+
+          fld = to_list(fld0)
+          fld != elem(x, 0) and :string.rstr(:string.lowercase(:nitro.to_list(fld)), value) > 0
+        end)
+      end)
+
+    dropDownList0(filtered, field, module, feed)
+  end
+
+  def dropDownList0(filtered, field, module, feed) do
+    if has_function(module, :dropDownList) do
+      module.dropDownList(filtered, field, module, feed)
+    else
+      dropDownList(filtered, field, module, feed)
+    end
+  end
+
+  def dropDownList(filtered, field, module, feed) do
+    formId = :nitro.atom([field, "form"])
+
+    if filtered == [] do
+      :nitro.wire("activeCombo = undefined; currentItem = undefined;")
+      :nitro.hide(:nitro.atom([:comboContainer, field]))
+      :nitro.wire("comboOpenFormById('#{formId}');")
+    else
+      :nitro.display(:nitro.atom([:comboContainer, field]), :block)
+      :nitro.wire("comboCloseFormById('#{formId}');")
+    end
+
+    Enum.each(filtered, fn obj ->
+      form = dropDown0(obj, field, module, feed)
+
+      :nitro.insert_top(
+        :nitro.atom([:comboContainer, field]),
+        :nitro.render(form)
+      )
+    end)
+  end
+
+  def dropDown0(obj, dom0, module, feed) do
+    case has_function(module, :dropDown) do
+      true ->
+        module.dropDown(obj, dom0, feed)
+
+      false ->
+        dropDown(obj, dom0, module, feed)
+    end
+  end
+
+  def view_value(obj, _, _) when obj in ["", [], :undefined], do: []
+
+  def view_value(obj, module, feed) do
+    case :erlang.function_exported(module, :view_value, 2) do
+      true ->
+        module.view_value(obj, feed)
+
+      false ->
+        if has_function(module, :view_value) do
+          module.view_value(obj)
+        else
+          view_value(obj)
+        end
+    end
+  end
+
+  def view_value(obj) when obj in ["", [], :undefined], do: []
+  def view_value(obj), do: :nitro.jse(:erlang.iolist_to_binary(apply(:kvs,:field,[obj, hd(index([]))])))
+
+  def dropDown(obj, dom0, module, feed) do
+    view_value = view_value(obj, module, feed)
+    dom = :nitro.to_list(dom0)
+    id = :nitro.jse(:erlang.iolist_to_binary(:nitro.atom([dom, :erlang.element(2, obj)])))
+    item = :nitro.to_list(item(obj, module))
+    source = :erlang.iolist_to_binary(feed)
+    click = :nitro.jse("comboSelect('#{dom}', '#{view_value}', '#{source}', '#{module}', '#{id}')")
+    move = :nitro.jse("comboLookupMouseMove('#{dom}')")
+
+    NITRO.panel(
+      id: id,
+      class: ['dropdown-item'],
+      bind: :base64.encode(:erlang.term_to_binary(obj)),
+      onclick: click,
+      onmousemove: move,
+      body: [
+        NITRO.p(body: item)
+      ]
+    )
+  end
+
+  def index([]), do: [:name, :id]
+
+  def index(module) do
+    case has_function(module, :index) do
+      true -> module.index()
+      false -> index([])
+    end
+  end
+
+  def item(obj, []), do: view_value(obj)
+
+  def item(obj, module) do
+    case has_function(module, :item) do
+      true -> module.item(obj)
+      false -> item(obj, [])
+    end
+  end
+
+  def update_comboVec(_parent, dom, feed, module, default, elem) do
+    vector  = view_value(default, module, feed)
+    clear   = "createSortable('##{dom}_list');"
+    append  = :lists.map(fn emp -> bind = emp |> :erlang.term_to_binary |> :base64.encode
+                 "appendItemFromBind('#{dom}_list','#{module.cn(emp)}','#{bind}');" end, vector)
+              |> :erlang.iolist_to_binary
+    render  = :nitro.render elem
+    command = "elem = qi('#{dom}'); elem.outerHTML = '#{render}';"
+    :nitro.wire(command <> append <> clear) # reverse
+  end
+
+  def update_combo(id, feed, module), do: :nitro.update(:nitro.atom([:lookup, id]), NITRO.comboLookup(id: id, feed: feed, delegate: module))
+
+  def update_combo_value(dom, value, feed, module) do
+    view_value = view_value(value, module, feed)
+    update_combo_value(dom, value, feed, module, view_value)
+  end
+
+  def update_combo_value(dom, value, feed, module, view_value) do
+    bind = :base64.encode(:erlang.term_to_binary(value))
+    update_combo(dom, feed, module)
+    command = "elem = qi('#{dom}'); elem.setAttribute('data-bind', '#{bind}'); elem.value = '#{view_value}';"
+    :nitro.wire(command)
+  end
+
+  def has_function(m, f) do
+    functions = apply(m, :module_info, [:exports])
+    isF = Keyword.get(functions, f, -1)
+    isF != -1
+  end
+end

+ 221 - 0
priv/css/calendar.css

@@ -0,0 +1,221 @@
+@charset "UTF-8";
+
+/*!
+ * Pikaday
+ * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/
+ */
+
+.pika-single {
+    z-index: 9999;
+    display: block;
+    position: relative;
+    color: #333;
+    background: #fff;
+    border: 1px solid #ccc;
+    border-bottom-color: #bbb;
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+/*
+clear child float (pika-lendar), using the famous micro clearfix hack
+http://nicolasgallagher.com/micro-clearfix-hack/
+*/
+.pika-single:before,
+.pika-single:after {
+    content: " ";
+    display: table;
+}
+.pika-single:after { clear: both }
+.pika-single { *zoom: 1 }
+
+.pika-single.is-hidden {
+    display: none;
+}
+
+.pika-single.is-bound {
+    position: absolute;
+    box-shadow: 0 5px 15px -5px rgba(0,0,0,.5);
+}
+
+.pika-lendar {
+    float: left;
+    width: 240px;
+    margin: 8px;
+}
+
+.pika-title {
+    position: relative;
+    text-align: center;
+}
+
+.pika-label {
+    display: inline-block;
+    *display: inline;
+    position: relative;
+    z-index: 9999;
+    overflow: hidden;
+    margin: 0;
+    padding: 5px 3px;
+    font-size: 14px;
+    line-height: 20px;
+    font-weight: bold;
+    background-color: #fff;
+}
+.pika-title select {
+    cursor: pointer;
+    position: absolute;
+    z-index: 9998;
+    margin: 0;
+    left: 0;
+    top: 5px;
+    filter: alpha(opacity=0);
+    opacity: 0;
+}
+
+.pika-prev,
+.pika-next {
+    display: block;
+    cursor: pointer;
+    position: relative;
+    outline: none;
+    border: 0;
+    padding: 0;
+    width: 20px;
+    height: 30px;
+    /* hide text using text-indent trick, using width value (it's enough) */
+    text-indent: 20px;
+    white-space: nowrap;
+    overflow: hidden;
+    background-color: transparent;
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: 75% 75%;
+    opacity: .5;
+    *position: absolute;
+    *top: 0;
+}
+
+.pika-prev:hover,
+.pika-next:hover {
+    opacity: 1;
+}
+
+.pika-prev,
+.is-rtl .pika-next {
+    float: left;
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');
+    *left: 0;
+}
+
+.pika-next,
+.is-rtl .pika-prev {
+    float: right;
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');
+    *right: 0;
+}
+
+.pika-prev.is-disabled,
+.pika-next.is-disabled {
+    cursor: default;
+    opacity: .2;
+}
+
+.pika-select {
+    display: inline-block;
+    *display: inline;
+}
+
+.pika-table {
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+    border: 0;
+}
+
+.pika-table th,
+.pika-table td {
+    width: 14.285714285714286%;
+    padding: 0;
+}
+
+.pika-table th {
+    color: #999;
+    font-size: 12px;
+    line-height: 25px;
+    font-weight: bold;
+    text-align: center;
+}
+
+.pika-button {
+    cursor: pointer;
+    display: block;
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    outline: none;
+    border: 0;
+    margin: 0;
+    width: 100%;
+    padding: 5px;
+    color: #666;
+    font-size: 12px;
+    line-height: 15px;
+    text-align: right;
+    background: #f5f5f5;
+}
+
+.pika-week {
+    font-size: 11px;
+    color: #999;
+}
+
+.is-today .pika-button {
+    color: #33aaff;
+    font-weight: bold;
+}
+
+.is-selected .pika-button {
+    color: #fff;
+    font-weight: bold;
+    background: #33aaff;
+    box-shadow: inset 0 1px 3px #178fe5;
+    border-radius: 3px;
+}
+
+.is-inrange .pika-button {
+    background: #D5E9F7;
+}
+
+.is-startrange .pika-button {
+    color: #fff;
+    background: #6CB31D;
+    box-shadow: none;
+    border-radius: 3px;
+}
+
+.is-endrange .pika-button {
+    color: #fff;
+    background: #33aaff;
+    box-shadow: none;
+    border-radius: 3px;
+}
+
+.is-disabled .pika-button {
+    pointer-events: none;
+    cursor: default;
+    color: #999;
+    opacity: .3;
+}
+
+.pika-button:hover {
+    color: #fff;
+    background: #ff8000;
+    box-shadow: none;
+    border-radius: 3px;
+}
+
+/* styling for abbr */
+.pika-table abbr {
+    border-bottom: none;
+    cursor: help;
+}
+

+ 106 - 0
priv/css/sortable.css

@@ -0,0 +1,106 @@
+
+body {
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-tap-highlight-color: transparent;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+.title {
+  font-weight: 500;
+  text-align: center;
+  margin-bottom: 20px;
+  font-size: 20px;
+  color: #0E254E;
+}
+
+.list {
+  margin: 0 auto;
+  width: 100%;
+  max-width: 380px;
+  user-select: none;
+}
+.list__item {
+  transition: box-shadow 200ms ease-out, opacity 200ms ease-out;
+  border-radius: 6px;
+  background: #fff;
+  box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
+  display: flex;
+}
+.list__item:not(:last-child) {
+  margin-bottom: 7px;
+}
+.list__item.is-dragging {
+  box-shadow: 0 0 24px rgba(0, 0, 0, 0.1);
+  opacity: 0.8;
+}
+.list__item-content {
+  width: calc(100% - 40px - 40px);
+  padding: 10px 15px;
+}
+.list__item-title, .list__item-description {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+.list__item-title {
+  font-size: 15px;
+  color: #0E254E;
+}
+.list__item-description {
+  font-size: 13px;
+  color: #66748E;
+}
+.list__item-handle {
+  position: relative;
+  width: 40px;
+  cursor: pointer;
+}
+.list__item-handle:before, .list__item-handle:after {
+  content: "";
+  position: absolute;
+  left: 15px;
+  right: 15px;
+  top: 50%;
+  height: 1px;
+  background: #c2cada;
+}
+.list__item-handle:before {
+  transform: translateY(-4px);
+}
+.list__item-handle:after {
+  transform: translateY(4px);
+}
+
+.list__item-close {
+  position: relative;
+  width: 40px;
+  cursor: pointer;
+}
+.list__item-close:after {
+  content: "X";
+  position: absolute;
+  left: 15px;
+  right: 15px;
+  top: 30%;
+  height: 0px;
+  background: #c2cada;
+}
+.list__item-close:after {
+  transform: translateY(0px);
+}
+
+.list__item-close:hover {
+  background-color: #f44336;
+  color: white;
+}
+
+.triangle{
+  position: absolute;
+  top: 1px;
+  right: 5px;
+  cursor: default;
+}

+ 1178 - 0
priv/js/calendar.js

@@ -0,0 +1,1178 @@
+/*!
+ * Pikaday
+ *
+ * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday
+ */
+
+var pickers = {};
+
+var clLangs = {
+    ua: {
+        previousMonth : 'Попередній місяць',
+        nextMonth     : 'Наступний місяць',
+        months        : ['Січень','Лютий','Березень','Квітень','Травень','Червень','Липень','Серпень','Вересень','Жовтень','Листопад','Грудень'],
+        weekdays      : ['Неділя','Понеділок','Вівторок','Середа','Четвер','П’ятниця','Субота'],
+        weekdaysShort : ['Нд','Пн','Вв','Ср','Чт','Пт','Сб']
+    },
+    ru: {
+        previousMonth : 'Предыдущий месяц',
+        nextMonth     : 'Следующий месяц',
+        months        : ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
+        weekdays      : ['Воскресенье','Понедельник','Вторник','Среда','Четверг','Пятница','Суббота'],
+        weekdaysShort : ['Вс','Пн','Вт','Ср','Чт','Пт','Сб']
+    },
+    en: {
+        previousMonth : 'Previous Month',
+        nextMonth     : 'Next Month',
+        months        : ['January','February','March','April','May','June','July','August','September','October','November','December'],
+        weekdays      : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
+        weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
+    }
+};
+function getDateParamBySign(date,sign) {
+    sign = sign.toUpperCase();
+    var param;
+    switch(sign) {
+        case "YYYY":
+            param = date.getFullYear().toString();break;
+        case "YY":
+            param = date.getFullYear().toString().substr(2,2);break;
+        case "MM":
+            param = (date.getMonth()+1);
+            param = (param >= 10) ? param.toString() : ("0"+param.toString());
+            break;
+        case "DD":
+            param = (date.getDate() >= 10) ? date.getDate().toString() : ("0"+date.getDate().toString());
+            break;
+        default:
+            param = date.toDateString();
+    }
+    return param;
+}
+
+function formatter(date, format) {
+    date = date || new Date();
+    format = format || "DD.MM.YYYY";
+    var signs = format.match(/(Y{2,4})|(M{2})|(D{2})/g);
+    var params = [];
+    var reStr = '';
+    for(var i=0; i<signs.length; ++i) {
+        params.push(getDateParamBySign(date,signs[i]));
+        reStr += ((i+1) != signs.length) ? signs[i] + "(.)" : signs[i];
+    }
+    var re = new RegExp(reStr,'g');
+    var delimiters = re.exec(format);
+    delimiters.splice(0,1);
+    var value = "";
+    for(i=0; i<params.length; i++) {
+        value += ((i+1) != params.length) ? (params[i] + delimiters[i]) : params[i];
+    }
+    return value;
+}
+
+function parser(str, format) {
+    format = format || "DD.MM.YYYY";
+    var signs = format.match(/(Y{2,4})|(M{2})|(D{2})/g);
+    var reStr = "(";
+    for(var i=0; i<signs.length; ++i) {
+        reStr += ".".repeat(signs[i].length) + (((i+1) != signs.length) ? ").(" : ")");
+    }
+    var re = new RegExp(reStr,'g');
+    var values = re.exec(str);
+    var year, month, day;
+    if (values && signs.length+1 == values.length) {
+        values = values.slice(1);
+        for(var i=0; i<signs.length; ++i) {
+            switch(signs[i].slice(0,1)){
+                case "Y": year = values[i]; break;
+                case "M": month = values[i]; break;
+                case "D": day = values[i]; break;
+            }
+        }
+        const res = new Date(year, month-1, day);
+        return res;
+    }
+    return null;
+}
+
+// function parseDateFromInput(value) {
+//     if(isNaN(Date.parse(value))) {
+//         var res = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/.exec(value);
+//         if(res && res.length == 4) { return new Date(res[3],(res[2]-1),res[1]); }
+//         else { return null; }
+//     }else{ return new Date(Date.parse(value)); }
+// }
+
+
+(function (root, factory)
+{
+    'use strict';
+
+    var moment;
+    if (typeof exports === 'object') {
+        // CommonJS module
+        // Load moment.js as an optional dependency
+        try { moment = require('moment'); } catch (e) {}
+        module.exports = factory(moment);
+    } else if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define(function (req)
+        {
+            // Load moment.js as an optional dependency
+            var id = 'moment';
+            try { moment = req(id); } catch (e) {}
+            return factory(moment);
+        });
+    } else {
+        root.Pikaday = factory(root.moment);
+    }
+}(this, function (moment)
+{
+    'use strict';
+
+    /**
+     * feature detection and helper functions
+     */
+    var hasMoment = typeof moment === 'function',
+
+    hasEventListeners = !!window.addEventListener,
+
+    document = window.document,
+
+    sto = window.setTimeout,
+
+    addEvent = function(el, e, callback, capture)
+    {
+        if (hasEventListeners) {
+            el.addEventListener(e, callback, !!capture);
+        } else {
+            el.attachEvent('on' + e, callback);
+        }
+    },
+
+    removeEvent = function(el, e, callback, capture)
+    {
+        if (hasEventListeners) {
+            el.removeEventListener(e, callback, !!capture);
+        } else {
+            el.detachEvent('on' + e, callback);
+        }
+    },
+
+    fireEvent = function(el, eventName, data)
+    {
+        var ev;
+
+        if (document.createEvent) {
+            ev = document.createEvent('HTMLEvents');
+            ev.initEvent(eventName, true, false);
+            ev = extend(ev, data);
+            el.dispatchEvent(ev);
+        } else if (document.createEventObject) {
+            ev = document.createEventObject();
+            ev = extend(ev, data);
+            el.fireEvent('on' + eventName, ev);
+        }
+    },
+
+    trim = function(str)
+    {
+        return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
+    },
+
+    hasClass = function(el, cn)
+    {
+        return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
+    },
+
+    addClass = function(el, cn)
+    {
+        if (!hasClass(el, cn)) {
+            el.className = (el.className === '') ? cn : el.className + ' ' + cn;
+        }
+    },
+
+    removeClass = function(el, cn)
+    {
+        el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
+    },
+
+    isArray = function(obj)
+    {
+        return (/Array/).test(Object.prototype.toString.call(obj));
+    },
+
+    isDate = function(obj)
+    {
+        return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
+    },
+
+    isWeekend = function(date)
+    {
+        var day = date.getDay();
+        return day === 0 || day === 6;
+    },
+
+    isLeapYear = function(year)
+    {
+        // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
+        return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
+    },
+
+    getDaysInMonth = function(year, month)
+    {
+        return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+    },
+
+    setToStartOfDay = function(date)
+    {
+        if (isDate(date)) date.setHours(0,0,0,0);
+    },
+
+    compareDates = function(a,b)
+    {
+        // weak date comparison (use setToStartOfDay(date) to ensure correct result)
+        return a.getTime() === b.getTime();
+    },
+
+    extend = function(to, from, overwrite)
+    {
+        var prop, hasProp;
+        for (prop in from) {
+            hasProp = to[prop] !== undefined;
+            if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
+                if (isDate(from[prop])) {
+                    if (overwrite) {
+                        to[prop] = new Date(from[prop].getTime());
+                    }
+                }
+                else if (isArray(from[prop])) {
+                    if (overwrite) {
+                        to[prop] = from[prop].slice(0);
+                    }
+                } else {
+                    to[prop] = extend({}, from[prop], overwrite);
+                }
+            } else if (overwrite || !hasProp) {
+                to[prop] = from[prop];
+            }
+        }
+        return to;
+    },
+
+    adjustCalendar = function(calendar) {
+        if (calendar.month < 0) {
+            calendar.year -= Math.ceil(Math.abs(calendar.month)/12);
+            calendar.month += 12;
+        }
+        if (calendar.month > 11) {
+            calendar.year += Math.floor(Math.abs(calendar.month)/12);
+            calendar.month -= 12;
+        }
+        return calendar;
+    },
+
+    /**
+     * defaults and localisation
+     */
+    defaults = {
+
+        // bind the picker to a form field
+        field: null,
+
+        // automatically show/hide the picker on `field` focus (default `true` if `field` is set)
+        bound: undefined,
+
+        // position of the datepicker, relative to the field (default to bottom & left)
+        // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
+        position: 'bottom left',
+
+        // automatically fit in the viewport even if it means repositioning from the position option
+        reposition: true,
+
+        // the default output format for `.toString()` and `field` value
+        format: 'DD.MM.YYYY',
+
+        // the initial date to view when first opened
+        defaultDate: null,
+
+        // make the `defaultDate` the initial selected value
+        setDefaultDate: false,
+
+        // first day of week (0: Sunday, 1: Monday etc)
+        firstDay: 0,
+
+        // the minimum/earliest date that can be selected
+        minDate: null,
+        // the maximum/latest date that can be selected
+        maxDate: null,
+
+        // number of years either side, or array of upper/lower range
+        yearRange: 10,
+
+        // show week numbers at head of row
+        showWeekNumber: false,
+
+        // used internally (don't config outside)
+        minYear: 0,
+        maxYear: 9999,
+        minMonth: undefined,
+        maxMonth: undefined,
+
+        startRange: null,
+        endRange: null,
+
+        isRTL: false,
+
+        // Additional text to append to the year in the calendar title
+        yearSuffix: '',
+
+        // Render the month after year in the calendar title
+        showMonthAfterYear: false,
+
+        // how many months are visible
+        numberOfMonths: 1,
+
+        // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
+        // only used for the first display or when a selected date is not visible
+        mainCalendar: 'left',
+
+        // Specify a DOM element to render the calendar in
+        container: undefined,
+
+        // internationalization
+        i18n: clLangs.en,
+
+        // Theme Classname
+        theme: null,
+
+        // callback function
+        onSelect: null,
+        onOpen: null,
+        onClose: null,
+        onDraw: null
+    },
+
+
+    /**
+     * templating functions to abstract HTML rendering
+     */
+    renderDayName = function(opts, day, abbr)
+    {
+        day += opts.firstDay;
+        while (day >= 7) {
+            day -= 7;
+        }
+        return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
+    },
+
+    renderDay = function(opts)
+    {
+        if (opts.isEmpty) {
+            return '<td class="is-empty"></td>';
+        }
+        var arr = [];
+        if (opts.isDisabled) {
+            arr.push('is-disabled');
+        }
+        if (opts.isToday) {
+            arr.push('is-today');
+        }
+        if (opts.isSelected) {
+            arr.push('is-selected');
+        }
+        if (opts.isInRange) {
+            arr.push('is-inrange');
+        }
+        if (opts.isStartRange) {
+            arr.push('is-startrange');
+        }
+        if (opts.isEndRange) {
+            arr.push('is-endrange');
+        }
+        return '<td data-day="' + opts.day + '" class="' + arr.join(' ') + '">' +
+                 '<button class="pika-button pika-day" type="button" ' +
+                    'data-pika-year="' + opts.year + '" data-pika-month="' + opts.month + '" data-pika-day="' + opts.day + '">' +
+                        opts.day +
+                 '</button>' +
+               '</td>';
+    },
+
+    renderWeek = function (d, m, y) {
+        // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified.
+        var onejan = new Date(y, 0, 1),
+            weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7);
+        return '<td class="pika-week">' + weekNum + '</td>';
+    },
+
+    renderRow = function(days, isRTL)
+    {
+        return '<tr>' + (isRTL ? days.reverse() : days).join('') + '</tr>';
+    },
+
+    renderBody = function(rows)
+    {
+        return '<tbody>' + rows.join('') + '</tbody>';
+    },
+
+    renderHead = function(opts)
+    {
+        var i, arr = [];
+        if (opts.showWeekNumber) {
+            arr.push('<th></th>');
+        }
+        for (i = 0; i < 7; i++) {
+            arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>');
+        }
+        return '<thead>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</thead>';
+    },
+
+    renderTitle = function(instance, c, year, month, refYear)
+    {
+        var i, j, arr,
+            opts = instance._o,
+            isMinYear = year === opts.minYear,
+            isMaxYear = year === opts.maxYear,
+            html = '<div class="pika-title">',
+            monthHtml,
+            yearHtml,
+            prev = true,
+            next = true;
+
+        for (arr = [], i = 0; i < 12; i++) {
+            arr.push('<option value="' + (year === refYear ? i - c : 12 + i - c) + '"' +
+                (i === month ? ' selected': '') +
+                ((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled' : '') + '>' +
+                opts.i18n.months[i] + '</option>');
+        }
+        monthHtml = '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month" tabindex="-1">' + arr.join('') + '</select></div>';
+
+        if (isArray(opts.yearRange)) {
+            i = opts.yearRange[0];
+            j = opts.yearRange[1] + 1;
+        } else {
+            i = year - opts.yearRange;
+            j = 1 + year + opts.yearRange;
+        }
+
+        for (arr = []; i < j && i <= opts.maxYear; i++) {
+            if (i >= opts.minYear) {
+                arr.push('<option value="' + i + '"' + (i === year ? ' selected': '') + '>' + (i) + '</option>');
+            }
+        }
+        yearHtml = '<div class="pika-label">' + year + opts.yearSuffix + '<select class="pika-select pika-select-year" tabindex="-1">' + arr.join('') + '</select></div>';
+
+        if (opts.showMonthAfterYear) {
+            html += yearHtml + monthHtml;
+        } else {
+            html += monthHtml + yearHtml;
+        }
+
+        if (isMinYear && (month === 0 || opts.minMonth >= month)) {
+            prev = false;
+        }
+
+        if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
+            next = false;
+        }
+
+        if (c === 0) {
+            html += '<button class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">' + opts.i18n.previousMonth + '</button>';
+        }
+        if (c === (instance._o.numberOfMonths - 1) ) {
+            html += '<button class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">' + opts.i18n.nextMonth + '</button>';
+        }
+
+        return html += '</div>';
+    },
+
+    renderTable = function(opts, data)
+    {
+        return '<table cellpadding="0" cellspacing="0" class="pika-table">' + renderHead(opts) + renderBody(data) + '</table>';
+    },
+
+
+    /**
+     * Pikaday constructor
+     */
+    Pikaday = function(options)
+    {
+        var self = this,
+            opts = self.config(options);
+
+        self._onMouseDown = function(e)
+        {
+            if (!self._v) {
+                return;
+            }
+            e = e || window.event;
+            var target = e.target || e.srcElement;
+            console.log(target);
+            if (!target) {
+                return;
+            }
+
+            if (!hasClass(target.parentNode, 'is-disabled')) {
+                if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) {
+                    self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
+                    if (opts.bound) {
+                        sto(function() {
+                            self.hide();
+                            if (opts.field) {
+                                opts.field.blur();
+                            }
+                        }, 100);
+                    }
+                }
+                else if (hasClass(target, 'pika-prev')) {
+                    self.prevMonth();
+                }
+                else if (hasClass(target, 'pika-next')) {
+                    self.nextMonth();
+                }
+            }
+            if (!hasClass(target, 'pika-select')) {
+                // if this is touch event prevent mouse events emulation
+                if (e.preventDefault) {
+                    e.preventDefault();
+                } else {
+                    e.returnValue = false;
+                    return false;
+                }
+            } else {
+                self._c = true;
+            }
+        };
+
+        self._onChange = function(e)
+        {
+            e = e || window.event;
+            var target = e.target || e.srcElement;
+            if (!target) {
+                return;
+            }
+            if (hasClass(target, 'pika-select-month')) {
+                self.gotoMonth(target.value);
+            }
+            else if (hasClass(target, 'pika-select-year')) {
+                self.gotoYear(target.value);
+            }
+        };
+
+        self._onInputChange = function(e)
+        {
+            var date;
+
+            if (e.firedBy === self) {
+                return;
+            }
+            if (hasMoment) {
+                date = moment(opts.field.value, opts.format);
+                date = (date && date.isValid()) ? date.toDate() : null;
+            }
+            else {
+                // date = parseDateFromInput(opts.field.value);
+                date = parser(opts.field.value, opts.format);
+            }
+            if (isDate(date)) {
+                self.setDate(date);
+            }else {
+                self.setDate(null);
+            }
+            if (!self._v) {
+                self.show();
+            }
+        };
+
+        self._onInputFocus = function()
+        {
+            self.show();
+        };
+
+        self._onInputClick = function()
+        {
+            self.show();
+        };
+
+        self._onInputBlur = function()
+        {
+            // IE allows pika div to gain focus; catch blur the input field
+            var pEl = document.activeElement;
+            do {
+                if (hasClass(pEl, 'pika-single')) {
+                    return;
+                }
+            }
+            while ((pEl = pEl.parentNode));
+
+            if (!self._c) {
+                self._b = sto(function() {
+                    self.hide();
+                }, 50);
+            }
+            self._c = false;
+        };
+
+        self._onClick = function(e)
+        {
+            e = e || window.event;
+            var target = e.target || e.srcElement,
+                pEl = target;
+            if (!target) {
+                return;
+            }
+            if (!hasEventListeners && hasClass(target, 'pika-select')) {
+                if (!target.onchange) {
+                    target.setAttribute('onchange', 'return;');
+                    addEvent(target, 'change', self._onChange);
+                }
+            }
+            do {
+                if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
+                    return;
+                }
+            }
+            while ((pEl = pEl.parentNode));
+            if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
+                self.hide();
+            }
+        };
+
+        self.el = document.createElement('div');
+        self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
+
+        addEvent(self.el, 'mousedown', self._onMouseDown, true);
+        addEvent(self.el, 'touchend', self._onMouseDown, true);
+        addEvent(self.el, 'change', self._onChange);
+
+        if (opts.field) {
+            if (opts.container) {
+                opts.container.appendChild(self.el);
+            } else if (opts.bound) {
+                document.body.appendChild(self.el);
+            } else {
+                opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
+            }
+            addEvent(opts.field, 'change', self._onInputChange);
+
+            if (!opts.defaultDate) {
+                if (hasMoment && opts.field.value) {
+                    opts.defaultDate = moment(opts.field.value, opts.format).toDate();
+                } else {
+                    opts.defaultDate = new Date(Date.parse(opts.field.value));
+                }
+                opts.setDefaultDate = true;
+            }
+        }
+
+        var defDate = opts.defaultDate;
+
+        if (isDate(defDate)) {
+            if (opts.setDefaultDate) {
+                self.setDate(defDate, true);
+            } else {
+                self.gotoDate(defDate);
+            }
+        } else {
+            self.gotoDate(new Date());
+        }
+
+        if (opts.bound) {
+            this.hide();
+            self.el.className += ' is-bound';
+            addEvent(opts.trigger, 'click', self._onInputClick);
+            addEvent(opts.trigger, 'focus', self._onInputFocus);
+            addEvent(opts.trigger, 'blur', self._onInputBlur);
+        } else {
+            this.show();
+        }
+    };
+
+
+    /**
+     * public Pikaday API
+     */
+    Pikaday.prototype = {
+
+
+        /**
+         * configure functionality
+         */
+        config: function(options)
+        {
+            if (!this._o) {
+                this._o = extend({}, defaults, true);
+            }
+
+            var opts = extend(this._o, options, true);
+
+            opts.isRTL = !!opts.isRTL;
+
+            opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
+
+            opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
+
+            opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
+
+            opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
+
+            opts.disableWeekends = !!opts.disableWeekends;
+
+            opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
+
+            var nom = parseInt(opts.numberOfMonths, 10) || 1;
+            opts.numberOfMonths = nom > 4 ? 4 : nom;
+
+            if (!isDate(opts.minDate)) {
+                opts.minDate = false;
+            }
+            if (!isDate(opts.maxDate)) {
+                opts.maxDate = false;
+            }
+            if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
+                opts.maxDate = opts.minDate = false;
+            }
+            if (opts.minDate) {
+                this.setMinDate(opts.minDate);
+            }
+            if (opts.maxDate) {
+                setToStartOfDay(opts.maxDate);
+                opts.maxYear  = opts.maxDate.getFullYear();
+                opts.maxMonth = opts.maxDate.getMonth();
+            }
+
+            if (isArray(opts.yearRange)) {
+                var fallback = new Date().getFullYear() - 10;
+                opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
+                opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
+            } else {
+                opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
+                if (opts.yearRange > 100) {
+                    opts.yearRange = 100;
+                }
+            }
+
+            return opts;
+        },
+
+        /**
+         * return a formatted string of the current selection (using Moment.js if available or default formatter)
+         */
+        toString: function(format) {
+            return !isDate(this._d) ? '' : (hasMoment) ? moment(this._d).format(format || this._o.format) : formatter(this._d,this._o.format);
+        },
+
+        /**
+         * return a Moment.js object of the current selection (if available)
+         */
+        getMoment: function()
+        {
+            return hasMoment ? moment(this._d) : null;
+        },
+
+        /**
+         * set the current selection from a Moment.js object (if available)
+         */
+        setMoment: function(date, preventOnSelect)
+        {
+            if (hasMoment && moment.isMoment(date)) {
+                this.setDate(date.toDate(), preventOnSelect);
+            }
+        },
+
+        /**
+         * return a Date object of the current selection
+         */
+        getDate: function()
+        {
+            return isDate(this._d) ? new Date(this._d.getTime()) : null;
+        },
+
+        /**
+         * set the current selection
+         */
+        setDate: function(date, preventOnSelect)
+        {
+            if (!date) {
+                this._d = null;
+
+                if (this._o.field) {
+                    this._o.field.value = '';
+                    fireEvent(this._o.field, 'change', { firedBy: this });
+                }
+
+                return this.draw();
+            }
+            if (typeof date === 'string') {
+                date = new Date(Date.parse(date));
+            }
+            if (!isDate(date)) {
+                return;
+            }
+
+            var min = this._o.minDate,
+                max = this._o.maxDate;
+
+            if (isDate(min) && date < min) {
+                date = min;
+            } else if (isDate(max) && date > max) {
+                date = max;
+            }
+
+            this._d = new Date(date.getTime());
+            setToStartOfDay(this._d);
+            this.gotoDate(this._d);
+
+            if (this._o.field) {
+                this._o.field.value = this.toString();
+                fireEvent(this._o.field, 'change', { firedBy: this });
+            }
+            if (!preventOnSelect && typeof this._o.onSelect === 'function') {
+                this._o.onSelect.call(this, this.getDate());
+            }
+        },
+
+        /**
+         * change view to a specific date
+         */
+        gotoDate: function(date)
+        {
+            var newCalendar = true;
+
+            if (!isDate(date)) {
+                return;
+            }
+
+            if (this.calendars) {
+                var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
+                    lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
+                    visibleDate = date.getTime();
+                // get the end of the month
+                lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
+                lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
+                newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
+            }
+
+            if (newCalendar) {
+                this.calendars = [{
+                    month: date.getMonth(),
+                    year: date.getFullYear()
+                }];
+                if (this._o.mainCalendar === 'right') {
+                    this.calendars[0].month += 1 - this._o.numberOfMonths;
+                }
+            }
+
+            this.adjustCalendars();
+        },
+
+        adjustCalendars: function() {
+            this.calendars[0] = adjustCalendar(this.calendars[0]);
+            for (var c = 1; c < this._o.numberOfMonths; c++) {
+                this.calendars[c] = adjustCalendar({
+                    month: this.calendars[0].month + c,
+                    year: this.calendars[0].year
+                });
+            }
+            this.draw();
+        },
+
+        gotoToday: function()
+        {
+            this.gotoDate(new Date());
+        },
+
+        /**
+         * change view to a specific month (zero-index, e.g. 0: January)
+         */
+        gotoMonth: function(month)
+        {
+            if (!isNaN(month)) {
+                this.calendars[0].month = parseInt(month, 10);
+                this.adjustCalendars();
+            }
+        },
+
+        nextMonth: function()
+        {
+            this.calendars[0].month++;
+            this.adjustCalendars();
+        },
+
+        prevMonth: function()
+        {
+            this.calendars[0].month--;
+            this.adjustCalendars();
+        },
+
+        /**
+         * change view to a specific full year (e.g. "2012")
+         */
+        gotoYear: function(year)
+        {
+            if (!isNaN(year)) {
+                this.calendars[0].year = parseInt(year, 10);
+                this.adjustCalendars();
+            }
+        },
+
+        /**
+         * change the minDate
+         */
+        setMinDate: function(value)
+        {
+            setToStartOfDay(value);
+            this._o.minDate = value;
+            this._o.minYear  = value.getFullYear();
+            this._o.minMonth = value.getMonth();
+        },
+
+        /**
+         * change the maxDate
+         */
+        setMaxDate: function(value)
+        {
+            this._o.maxDate = value;
+        },
+
+        setStartRange: function(value)
+        {
+            this._o.startRange = value;
+        },
+
+        setEndRange: function(value)
+        {
+            this._o.endRange = value;
+        },
+
+        /**
+         * refresh the HTML
+         */
+        draw: function(force)
+        {
+            if (!this._v && !force) {
+                return;
+            }
+            var opts = this._o,
+                minYear = opts.minYear,
+                maxYear = opts.maxYear,
+                minMonth = opts.minMonth,
+                maxMonth = opts.maxMonth,
+                html = '';
+
+            if (this._y <= minYear) {
+                this._y = minYear;
+                if (!isNaN(minMonth) && this._m < minMonth) {
+                    this._m = minMonth;
+                }
+            }
+            if (this._y >= maxYear) {
+                this._y = maxYear;
+                if (!isNaN(maxMonth) && this._m > maxMonth) {
+                    this._m = maxMonth;
+                }
+            }
+
+            for (var c = 0; c < opts.numberOfMonths; c++) {
+                html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year) + this.render(this.calendars[c].year, this.calendars[c].month) + '</div>';
+            }
+
+            this.el.innerHTML = html;
+
+            if (opts.bound) {
+                if(opts.field.type !== 'hidden') {
+                    sto(function() {
+                        opts.trigger.focus();
+                    }, 1);
+                }
+            }
+
+            if (typeof this._o.onDraw === 'function') {
+                var self = this;
+                sto(function() {
+                    self._o.onDraw.call(self);
+                }, 0);
+            }
+        },
+
+        adjustPosition: function()
+        {
+            var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect;
+
+            if (this._o.container) return;
+
+            this.el.style.position = 'absolute';
+
+            field = this._o.trigger;
+            pEl = field;
+            width = this.el.offsetWidth;
+            height = this.el.offsetHeight;
+            viewportWidth = window.innerWidth || document.documentElement.clientWidth;
+            viewportHeight = window.innerHeight || document.documentElement.clientHeight;
+            scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
+
+            if (typeof field.getBoundingClientRect === 'function') {
+                clientRect = field.getBoundingClientRect();
+                left = clientRect.left + window.pageXOffset;
+                top = clientRect.bottom + window.pageYOffset;
+            } else {
+                left = pEl.offsetLeft;
+                top  = pEl.offsetTop + pEl.offsetHeight;
+                while((pEl = pEl.offsetParent)) {
+                    left += pEl.offsetLeft;
+                    top  += pEl.offsetTop;
+                }
+            }
+
+            // default position is bottom & left
+            if ((this._o.reposition && left + width > viewportWidth) ||
+                (
+                    this._o.position.indexOf('right') > -1 &&
+                    left - width + field.offsetWidth > 0
+                )
+            ) {
+                left = left - width + field.offsetWidth;
+            }
+            if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
+                (
+                    this._o.position.indexOf('top') > -1 &&
+                    top - height - field.offsetHeight > 0
+                )
+            ) {
+                top = top - height - field.offsetHeight;
+            }
+
+            this.el.style.left = left + 'px';
+            this.el.style.top = top + 'px';
+        },
+
+        /**
+         * render HTML for a particular month
+         */
+        render: function(year, month)
+        {
+            var opts   = this._o,
+                now    = new Date(),
+                days   = getDaysInMonth(year, month),
+                before = new Date(year, month, 1).getDay(),
+                data   = [],
+                row    = [];
+            setToStartOfDay(now);
+            if (opts.firstDay > 0) {
+                before -= opts.firstDay;
+                if (before < 0) {
+                    before += 7;
+                }
+            }
+            var cells = days + before,
+                after = cells;
+            while(after > 7) {
+                after -= 7;
+            }
+            cells += 7 - after;
+            for (var i = 0, r = 0; i < cells; i++)
+            {
+                var day = new Date(year, month, 1 + (i - before)),
+                    isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
+                    isToday = compareDates(day, now),
+                    isEmpty = i < before || i >= (days + before),
+                    isStartRange = opts.startRange && compareDates(opts.startRange, day),
+                    isEndRange = opts.endRange && compareDates(opts.endRange, day),
+                    isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
+                    isDisabled = (opts.minDate && day < opts.minDate) ||
+                                 (opts.maxDate && day > opts.maxDate) ||
+                                 (opts.disableWeekends && isWeekend(day)) ||
+                                 (opts.disableDayFn && opts.disableDayFn(day)),
+                    dayConfig = {
+                        day: 1 + (i - before),
+                        month: month,
+                        year: year,
+                        isSelected: isSelected,
+                        isToday: isToday,
+                        isDisabled: isDisabled,
+                        isEmpty: isEmpty,
+                        isStartRange: isStartRange,
+                        isEndRange: isEndRange,
+                        isInRange: isInRange
+                    };
+
+                row.push(renderDay(dayConfig));
+
+                if (++r === 7) {
+                    if (opts.showWeekNumber) {
+                        row.unshift(renderWeek(i - before, month, year));
+                    }
+                    data.push(renderRow(row, opts.isRTL));
+                    row = [];
+                    r = 0;
+                }
+            }
+            return renderTable(opts, data);
+        },
+
+        isVisible: function()
+        {
+            return this._v;
+        },
+
+        show: function()
+        {
+            if (!this._v) {
+                removeClass(this.el, 'is-hidden');
+                this._v = true;
+                this.draw();
+                if (this._o.bound) {
+                    addEvent(document, 'click', this._onClick);
+                    this.adjustPosition();
+                }
+                if (typeof this._o.onOpen === 'function') {
+                    this._o.onOpen.call(this);
+                }
+            }
+        },
+
+        hide: function()
+        {
+            var v = this._v;
+            if (v !== false) {
+                if (this._o.bound) {
+                    removeEvent(document, 'click', this._onClick);
+                }
+                this.el.style.position = 'static'; // reset
+                this.el.style.left = 'auto';
+                this.el.style.top = 'auto';
+                addClass(this.el, 'is-hidden');
+                this._v = false;
+                if (v !== undefined && typeof this._o.onClose === 'function') {
+                    this._o.onClose.call(this);
+                }
+            }
+        },
+
+        /**
+         * GAME OVER
+         */
+        destroy: function()
+        {
+            this.hide();
+            removeEvent(this.el, 'mousedown', this._onMouseDown, true);
+            removeEvent(this.el, 'touchend', this._onMouseDown, true);
+            removeEvent(this.el, 'change', this._onChange);
+            if (this._o.field) {
+                removeEvent(this._o.field, 'change', this._onInputChange);
+                if (this._o.bound) {
+                    removeEvent(this._o.trigger, 'click', this._onInputClick);
+                    removeEvent(this._o.trigger, 'focus', this._onInputFocus);
+                    removeEvent(this._o.trigger, 'blur', this._onInputBlur);
+                }
+            }
+            if (this.el.parentNode) {
+                this.el.parentNode.removeChild(this.el);
+            }
+        }
+
+    };
+
+    return Pikaday;
+
+}));

+ 110 - 0
priv/js/comboLookup.js

@@ -0,0 +1,110 @@
+function comboClear(dom) {
+    let elem = qi('comboContainer_' + dom)
+    if (elem) { elem.style.display = 'none' }
+    activeCombo = undefined; currentItem = undefined;
+}
+
+function comboSelect(dom, row, feed, mod, id) {
+    let elem = qi(dom); comboClear(dom);
+    if (qi(id)) elem.setAttribute("data-bind", qi(id).getAttribute('data-bind'));
+    elem.value = row;
+    elem.style.backgroundColor = 'white';
+    direct(tuple(atom('comboSelect'),
+                 string(dom),
+                 string(row),
+                 string(feed),
+                 atom(mod)));
+}
+
+function comboLookupChange(dom) {
+  let elem = qi(dom);
+  if (elem && elem.value == "" && elem.getAttribute("data-bind")) {
+    elem.removeAttribute("data-bind");
+  }
+}
+
+function comboLookupClick(dom, feed, mod) {
+  var char = event.which || event.keyCode;
+  if (char ==  1 && !activeCombo && qi(dom).value == '') {
+    activeCombo = dom;
+    currentItem = undefined;
+    direct(tuple(atom('comboKey'),
+                atom('all'),
+                string(dom),
+                string(feed),
+                atom(mod)));
+    return
+  } else if(char ==  1 && activeCombo == dom){comboClear(dom);}
+}
+
+function comboLookupKeydown(dom, feed, mod) {
+    var char = event.which || event.keyCode;
+    if (char == 40 && !activeCombo && qi(dom).value == '') {
+        activeCombo = dom;
+        currentItem = undefined;
+        direct(tuple(atom('comboKey'),
+                     atom('all'),
+                     string(dom),
+                     string(feed),
+                     atom(mod)));
+        return
+    }
+    if (activeCombo && [38, 40].includes(char)) {
+        event.preventDefault();
+        console.log('Keycode: ' + char + ", DOM: " + dom);
+        if (char == 40) { set_focus( currentItem && ( !cycleEnabled || currentItem.nextSibling)
+                        ? currentItem.nextSibling : qi('comboContainer_' + dom).firstChild, true) }
+        if (char == 38) { set_focus( currentItem && ( !cycleEnabled || currentItem.previousSibling)
+                        ? currentItem.previousSibling : qi('comboContainer_' + dom).lastChild, true) }
+    }
+}
+
+function comboLookupKeyup(dom, feed, mod) {
+    var char = event.which || event.keyCode;
+    if (char == 27 || (char == 8 || char == 46) && qi(dom).value == '') {
+        qi(dom).value = '';
+        comboClear(dom);
+        return
+    }
+    if (char == 13 && currentItem) { currentItem.click(); return }
+    if ([33, 34, 35, 36, 37, 39].includes(char)) { return }
+    if (activeCombo && [38, 40].includes(char)) { return }
+    else {
+        activeCombo = dom;
+        currentItem = undefined;
+        direct(tuple(atom('comboKey'),
+                     bin(qi(dom).value),
+                     string(dom),
+                     string(feed),
+                     atom(mod)));
+    }
+}
+
+function comboLookupMouseMove(dom) {
+  set_focus(event.target.closest('.dropdown-item'), false)
+}
+
+function set_focus(elem, scroll) {
+  if (elem) {
+    if(currentItem) {currentItem.className = "dropdown-item"}
+    elem.className = "dropdown-item focus"
+    if (scroll==true) {elem.scrollIntoView({block: "nearest", inline: "nearest"})}
+    currentItem = elem
+  }
+}
+
+document.addEventListener("click", () => {
+  if (activeCombo && event.target.className != 'triangle' &&
+    !event.target.closest('#comboContainer_' + activeCombo)) {
+    qi(activeCombo).value = '';
+    comboClear(activeCombo);
+  }
+})
+
+function comboLostFocus(e) { }
+function comboOnFocus(e) { }
+function comboLookupMouseOut(dom) { }
+
+var activeCombo = undefined
+var currentItem = undefined
+var cycleEnabled = false

+ 80 - 0
priv/js/nitro.js

@@ -0,0 +1,80 @@
+// Nitrogen Compatibility Layer
+
+function unbase64(base64) {
+    var binary_string = window.atob(base64);
+    var len = binary_string.length;
+    var bytes = new Uint8Array(len);
+    for (var i = 0; i < len; i++) bytes[i] = binary_string.charCodeAt(i);
+    return bytes.buffer;
+}
+
+// Nitrogen Compatibility Layer
+
+function direct(term) { ws.send(enc(tuple(atom('direct'),term))); }
+function validateSources() { return true; }
+function querySourceRaw(Id) {
+    var val, el = document.getElementById(Id);
+    if (!el) {
+       val = qs('input[name='+Id+']:checked'); val = val ? val.value : "";
+    } else switch (el.tagName) {
+        case 'FIELDSET':
+            val = qs('[id="'+Id+'"]:checked'); val = val ? val.value : ""; break;
+        case 'INPUT':
+            switch (el.getAttribute("type")) {
+                case 'radio': val = qs('input[name='+Id+']:checked'); val = val ? val.value : ""; break;
+                case 'checkbox': val = qs('input[id='+Id+']:checked'); val = val ? val.value : ""; break;
+                case 'date': val = Date.parse(el.value);  val = val && new Date(val) || ""; break;
+                case 'calendar': val = pickers[el.id]._d || ""; break;
+                case 'comboLookup': case 'hidden':
+                    if (el.hasAttribute('data-bind')) {
+                        val = { 'text' : el.value, 'bind' : el.getAttribute('data-bind')};
+                    } else {
+                        val = el.value;
+                    }
+                    break;
+                default: var edit = el.contentEditable;
+                    if (edit && edit === 'true') val = el.innerHTML;
+                    else val = el.value;
+            }
+            break;
+        default:
+            if(el.getAttribute('data-text-input')) {
+              val = querySourceRaw(el.children[1].children[0].id);
+            }
+            else if (el.getAttribute('data-vector-input')) {
+                val = querySourceRaw(el.children[1].id);
+            } else if (el.getAttribute('data-edit-input')) {
+                val = querySourceRaw(el.children[0].children[0].children[0].id);
+            } else if (el.getAttribute('data-sortable-list')) {
+                val = getSortableValues('#' + el.id);
+            } else if (el.contentEditable === 'true') {
+                val = el.innerHTML;
+            } else {
+                val = el.value;
+                switch (val) {
+                    case "true": val = new Boolean(true); break;
+                    case "false": val = new Boolean(false); break;
+                }
+            }
+    }
+    return val;
+}
+
+function querySourceConvert(qs) {
+    if (qs && qs.hasOwnProperty('text') && qs.hasOwnProperty('bind')) {
+        return dec(unbase64(qs.bind)); }
+    else if (qs instanceof Date) {
+        return tuple(number(qs.getFullYear()),
+                     number(qs.getMonth() + 1),
+                     number(qs.getDate())); }
+    else if (qs instanceof Boolean) {
+        return atom(qs.valueOf()); }
+    else if (qs instanceof Array) {
+        return list.apply(null, qs.map(querySourceConvert)); }
+    else { return bin(qs); }
+}
+
+function querySource(Id) {
+    var qs = querySourceRaw(Id);
+    return querySourceConvert(qs);
+}

+ 232 - 0
priv/js/sortable.js

@@ -0,0 +1,232 @@
+"use strict";
+
+function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
+
+function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+window.addEventListener('touchmove', function () {});
+
+var Sortable =
+/*#__PURE__*/
+function () {
+  function Sortable(list, options) {
+    _classCallCheck(this, Sortable);
+    this.list = typeof list === 'string' ? document.querySelector(list) : list;
+    this.items = Array.from(this.list.children);
+    this.animation = false;
+    this.options = Object.assign({
+      animationSpeed: 200,
+      animationEasing: 'ease-out'
+    }, options || {});
+    this.dragStart = this.dragStart.bind(this);
+    this.dragMove = this.dragMove.bind(this);
+    this.dragEnd = this.dragEnd.bind(this);
+    this.list.addEventListener('touchstart', this.dragStart, false);
+    this.list.addEventListener('mousedown', this.dragStart, false);
+  }
+
+  _createClass(Sortable, [{
+    key: "dragStart",
+    value: function dragStart(e) {
+      var _this = this;
+
+      if (this.animation) return;
+      if (e.type === 'mousedown' && e.which !== 1) return;
+      if (e.type === 'touchstart' && e.touches.length > 1) return;
+      this.handle = null;
+      var el = e.target;
+
+      while (el) {
+        if (el.hasAttribute('data-sortable-handle')) this.handle = el;
+        if (el.hasAttribute('data-sortable-item')) this.item = el;
+        if (el.hasAttribute('data-sortable-list')) break;
+        el = el.parentElement;
+      }
+
+      if (!this.handle) return;
+      this.list.style.position = 'relative';
+      this.list.style.height = this.list.offsetHeight + 'px';
+      this.item.classList.add('is-dragging');
+      this.itemHeight = this.items[1].offsetTop;
+      this.listHeight = this.list.offsetHeight;
+      this.startTouchY = this.getDragY(e);
+      this.startTop = this.item.offsetTop;
+      var offsetsTop = this.items.map(function (item) {
+        return item.offsetTop;
+      });
+      this.items.forEach(function (item, index) {
+        item.style.position = 'absolute';
+        item.style.top = 0;
+        item.style.left = 0;
+        item.style.width = '100%';
+        item.style.transform = "translateY(".concat(offsetsTop[index], "px)");
+        item.style.zIndex = item == _this.item ? 2 : 1;
+      });
+      setTimeout(function () {
+        _this.items.forEach(function (item) {
+          if (_this.item == item) return;
+          item.style.transition = "transform ".concat(_this.options.animationSpeed, "ms ").concat(_this.options.animationEasing);
+        });
+      });
+      this.positions = this.items.map(function (item, index) {
+        return index;
+      });
+      this.position = Math.round(this.startTop / this.listHeight * this.items.length);
+      this.touch = e.type == 'touchstart';
+      window.addEventListener(this.touch ? 'touchmove' : 'mousemove', this.dragMove, {
+        passive: false
+      });
+      window.addEventListener(this.touch ? 'touchend' : 'mouseup', this.dragEnd, false);
+    }
+  }, {
+    key: "dragMove",
+    value: function dragMove(e) {
+      var _this2 = this;
+
+      if (this.animation) return;
+      var top = this.startTop + this.getDragY(e) - this.startTouchY;
+      var newPosition = Math.round(top / this.listHeight * this.items.length);
+      this.item.style.transform = "translateY(".concat(top, "px)");
+      this.positions.forEach(function (index) {
+        if (index == _this2.position || index != newPosition) return;
+
+        _this2.swapElements(_this2.positions, _this2.position, index);
+
+        _this2.position = index;
+      });
+      this.items.forEach(function (item, index) {
+        if (item == _this2.item) return;
+        item.style.transform = "translateY(".concat(_this2.positions.indexOf(index) * _this2.itemHeight, "px)");
+      });
+      e.preventDefault();
+    }
+  }, {
+    key: "dragEnd",
+    value: function dragEnd(e) {
+      var _this3 = this;
+
+      this.animation = true;
+      this.item.style.transition = "all ".concat(this.options.animationSpeed, "ms ").concat(this.options.animationEasing);
+      this.item.style.transform = "translateY(".concat(this.position * this.itemHeight, "px)");
+      this.item.classList.remove('is-dragging');
+      setTimeout(function () {
+        _this3.list.style.position = '';
+        _this3.list.style.height = '';
+
+        _this3.items.forEach(function (item) {
+          item.style.top = '';
+          item.style.left = '';
+          item.style.right = '';
+          item.style.position = '';
+          item.style.transform = '';
+          item.style.transition = '';
+          item.style.width = '';
+          item.style.zIndex = '';
+        });
+
+        _this3.positions.map(function (i) {
+          return _this3.list.appendChild(_this3.items[i]);
+        });
+
+        _this3.items = Array.from(_this3.list.children);
+        _this3.animation = false;
+      }, this.options.animationSpeed);
+      window.removeEventListener(this.touch ? 'touchmove' : 'mousemove', this.dragMove, {
+        passive: false
+      });
+      window.removeEventListener(this.touch ? 'touchend' : 'mouseup', this.dragEnd, false);
+    }
+  }, {
+    key: "swapElements",
+    value: function swapElements(array, a, b) {
+      var temp = array[a];
+      array[a] = array[b];
+      array[b] = temp;
+    }
+  }, {
+    key: "getDragY",
+    value: function getDragY(e) {
+      return e.touches ? (e.touches[0] || e.changedTouches[0]).pageY : e.pageY;
+    }
+  }, {
+    key: "removeItem",
+    value: function removeItem(item) {
+      this.items.splice(this.items.indexOf(item),1);
+      item.remove();
+    }
+  }, {
+    key: "addItemFrom",
+    value: function addItemFrom(input) {
+      var value = querySourceRaw(input);
+      var bind = '';
+      if (value && value.hasOwnProperty('text') && value.hasOwnProperty('bind')) {
+        bind = value.bind;
+        value = value.text;
+      }
+      var inputElement = document.getElementById(input);
+      if (bind !== '' && bind !== 'null') {
+        if (inputElement) {
+          inputElement.value = '';
+          inputElement.removeAttribute("data-bind");
+        }
+        appendItemFromBind(this.list.id,value,bind);
+      }
+    }
+  }, {
+    key: "getValues",
+    value: function getValues() {
+      return Array.from(this.items.map(function(item) {
+        let text = item.children[1].firstChild.innerHTML;
+        let bind = item.getAttribute('data-bind');
+        if (bind) return { 'text': text, 'bind': bind };
+        return text;
+      }));
+    }
+  }]);
+
+  return Sortable;
+}();
+
+var SortableMap = new Map([]);
+
+function createSortable(list) {
+  SortableMap.set(list, new Sortable(list));
+}
+
+function removeSortableItem(list, item) {
+  SortableMap.get(list).removeItem(item);
+}
+
+function addSortableItemFrom(list, input) {
+  var value = querySourceRaw(input);
+  var isAdded = SortableMap.get(list).items.map(el => el.textContent).includes(value.text || value);
+  if(qi(input).value != '' & !isAdded)
+    SortableMap.get(list).addItemFrom(input);
+}
+
+function getSortableValues(list) {
+  let sortable = SortableMap.get(list)
+  if(sortable) {
+    return sortable.getValues();
+  } else {
+    return Array.from([]);
+  }
+}
+
+function appendItemFromBind(dom,value,bind) {
+  var sortable = SortableMap.get('#'+dom);
+  var template = document.createElement('template');
+  template.innerHTML =
+    '<div class="list__item" data-sortable-item="data-sortable-item" style="" data-bind="'+ bind + '">' +
+       '<div class="list__item-close" onclick="removeSortableItem(\'#' + sortable.list.id + '\', this.parentNode);"></div>' +
+       '<div class="list__item-content"><div class="list__item-title">' + value + '</div></div>' +
+       '<div class="list__item-handle" data-sortable-handle="data-sortable-handle"></div>' +
+    '</div>'
+  var new_item = template.content.firstChild;
+  sortable.list.appendChild(new_item);
+  sortable.items.push(new_item);
+}

+ 20 - 0
priv/js/validation.js

@@ -0,0 +1,20 @@
+
+function validateSources(list) {
+    return list.reduce(function(acc,x) {
+        var event = new CustomEvent('validation');
+            event.initCustomEvent('validation',true,true,querySourceRaw(x));
+        var el = qi(x),
+            listener = el && el.validation,
+            res = !listener || listener && el.dispatchEvent(event);
+        if (!res) { console.log("Validation failed:" + x); }
+        if (el) el.style.background = res ? '' : 'pink';
+        return res && acc; },true); }
+
+(function () {
+   function CustomEvent ( event, params ) {
+       params = params || { bubbles: false, cancelable: false, detail: undefined };
+       var evt = document.createEvent( 'CustomEvent' );
+       evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+       return evt;  };
+  CustomEvent.prototype = window.Event.prototype;
+  window.CustomEvent = CustomEvent; })();

+ 7 - 0
src/actions/action_alert.erl

@@ -0,0 +1,7 @@
+-module(action_alert).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_action(#alert{text=T}) -> ["alert(\"",nitro:js_escape(T),"\");"].

+ 12 - 0
src/actions/action_api.erl

@@ -0,0 +1,12 @@
+-module(action_api).
+-author('Maxim Sokhatsky').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+-define(B(E), nitro:to_binary(E)).
+
+render_action(#api{name=Name,delegate=Delegate}) ->
+    Data = "string(JSON.stringify(data))",
+    PostbackScript = wf_event:new(Name, "document", Delegate, api_event, Data, []),
+    iolist_to_binary(["document.",?B(Name),"=function(data){",PostbackScript,"};"]).

+ 18 - 0
src/actions/action_bind.erl

@@ -0,0 +1,18 @@
+-module(action_bind).
+-author('Maxim Sokhatsky').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+-define(B(E), nitro:to_binary(E)).
+-define(T(T), wf_event:target(T)).
+
+render_action(#bind{postback=Code,target={g,_}=Control,type=Type,source=Src}) ->
+    % rebind same control to same handler on same target
+    G = ?T(Control), V = ?B(Src), E = ?B(Type),
+
+    ["{ if('",V,"' in ",G,"){",G,".removeEventListener('",E,"',",V,");delete ",G,"[",V,"]};",
+        V,"={handleEvent:(event) => { if(event.type === '",E,"'){",?B(Code),"}}};",
+        G,".addEventListener('",E,"',", V,");", G,".",V,"=",V,";}"];
+render_action(#bind{postback=Code,target=Control,type=Type}) ->
+    ["{var x=",?T(Control),"; x && x.addEventListener('",?B(Type),"',function(event){",?B(Code),"});}"].

+ 10 - 0
src/actions/action_confirm.erl

@@ -0,0 +1,10 @@
+-module(action_confirm).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_action(#confirm{target=Control,text=Text,postback=Postback,delegate=Delegate}) -> 
+    PostbackScript = wf_event:new(Postback, Control, Delegate, event, "[]", []),
+    ["if (confirm(\"",nitro:js_escape(Text),"\")) {",PostbackScript,"}"].
+

+ 24 - 0
src/actions/action_event.erl

@@ -0,0 +1,24 @@
+-module(action_event).
+-author('Maxim Sokhatsky').
+-author('Andrey Martemyanov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+-define(B(E), nitro:to_binary(E)).
+
+render_action(#event{source=undefined}) -> [];
+render_action(#event{postback=Postback,actions=_A,source=Source,target=Control,type=Type,delegate=D,validation=V}) ->
+    E = ?B(Control),
+    ValidationSource = [ S || S <- Source, not is_tuple(S) ],
+    PostbackBin = wf_event:new(Postback, E, D, event, data(E,Source), ValidationSource, V),
+    ["{var x=qi('",E,"'); x && x.addEventListener('",?B(Type),
+     "',function (event){ event.preventDefault(); ",PostbackBin,"});};"].
+
+data(E,SourceList) ->
+    Type=fun(A) when is_atom(A)   -> [ "atom('",atom_to_list(A),"')" ];
+            (A) when is_binary(A) -> [ "bin('",binary_to_list(A),"')" ];
+                              (A) -> [ "string('",A,"')" ] end,
+    list_to_binary(["[tuple(tuple(string('",E,"'),bin('detail')),[])",
+        [ case S of {Id,Code} -> [ ",tuple(",Type(Id),",",Code,")" ];
+                            _ -> [ ",tuple(",Type(S),",querySource('",?B(S),"'))" ]
+          end || S <- SourceList ],"]"]).

+ 23 - 0
src/actions/action_jq.erl

@@ -0,0 +1,23 @@
+-module(action_jq).
+-author('Rusty Klophaus').
+-author('Andrey Martemyanov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+-define(B(E), nitro:to_binary(E)).
+-define(R(E), nitro:render(E)).
+-define(T(T), wf_event:target(T)).
+-define(U, undefined).
+-define(M, {".map(i=>i.", ");"}).
+-define(P, {".", ";"}).
+
+render_action(#jq{property=?U,target=T,method=Methods,args=Args0,format=F}) ->
+    Args = case F of "'~s'" -> [ ?R(Args0) ]; _ -> Args0 end,
+    Format = fun(A) when is_tuple(A) orelse is_integer(A) -> ?R(A); (A) -> nitro:to_list(A) end,
+    RenderedArgs = string:join([ Format(A) || A <- Args], ","),
+    {Op,Op2} = case T of {qa,_} ->  ?M;{_,{qa,_},_} -> ?M; _ -> ?P end,
+    [[?T(T),Op,?B(M),"(",nitro:f(F,[RenderedArgs]),")", Op2] || M <- Methods];
+render_action(#jq{target=T,method=?U,property=P,right=R,args=simple}) -> [?B(T),".",?B(P),"='",?R(R),"';"];
+render_action(#jq{target=T,method=?U,property=P,right=?U})            -> [?T(T),".",?B(P),";"];
+render_action(#jq{target=T,method=?U,property=P,right=#jq{}=R})       -> [?T(T),".",?B(P),"=", ?R(R), ";"];
+render_action(#jq{target=T,method=?U,property=P,right=R})             -> [?T(T),".",?B(P),"='",?R(R),"';"].

+ 20 - 0
src/actions/action_manage.erl

@@ -0,0 +1,20 @@
+-module(action_manage).
+-author('Andrey Martemyanov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+-define(B(E), nitro:to_binary(E)).
+-define(R(E), nitro:render(E)).
+-define(T(T), wf_event:target(T)).
+
+render_action(#replace{target=T,elements=E}) -> ?R(#jq{target=T,property=outerHTML,right=E});
+render_action(#insert{target=T,elements=E,position=P}) ->
+    {Rendered,Actions}=render_element(E),
+    [?T(T),".insertAdjacentHTML('",?B(P),"','",Rendered,"');",?R(Actions)];
+render_action(#multi{actions=A}) -> ["window.requestAnimationFrame(function(timestamp){",?R(A),"});"].
+
+render_element(E) ->
+    Pid = self(),
+    Ref = make_ref(),
+    spawn(fun() -> Pid ! {?R(E),Ref,wf:actions()} end),
+    receive {Rendered, Ref, Actions} -> {Rendered,Actions} end.

+ 12 - 0
src/actions/action_transfer.erl

@@ -0,0 +1,12 @@
+-module(action_transfer).
+-author('Andrey Martemyanov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_action(Record) ->
+    case Record#transfer.state of
+        undefined -> ok;
+        List when is_list(List) -> [ erlang:put(K,V) || {K,V} <- List ];
+        Single -> erlang:put(state,Single) end,
+    Events = case Record#transfer.events of E when is_list(E) -> E; E -> [E] end,
+    [ self() ! M || M <- Events ], ok.

+ 9 - 0
src/actions/action_ui.erl

@@ -0,0 +1,9 @@
+-module(action_ui).
+-author('Andrey Martemyanov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+-define(T(T), wf_event:target(T)).
+
+render_action(#focus{target=T}) -> ["window.setTimeout(function(){var x=",?T(T),"; x && x.focus();},4);"].

+ 12 - 0
src/actions/action_wire.erl

@@ -0,0 +1,12 @@
+-module(action_wire).
+-author('Maxim Sokhatsky').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_action(#wire{actions=Actions}) -> nitro:render(Actions);
+render_action(S) when is_list(S) -> S;
+render_action(_) -> [].
+
+wire(A) -> Actions = case get(actions) of undefined -> []; E -> E end,
+           put(actions,Actions++[#wire{actions=A}]), [].

+ 88 - 0
src/elements/combo/element_calendar.erl

@@ -0,0 +1,88 @@
+-module(element_calendar).
+-include_lib("nitro/include/calendar.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-export([render_element/1]).
+
+render_element(Record) when Record#calendar.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#calendar.postback of
+        [] -> Record#calendar.id;
+        Postback ->
+          ID = case Record#calendar.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#calendar.source, delegate=Record#calendar.delegate }),
+          ID end,
+
+    init(Id,Record),
+
+    List = [
+      %global
+      {<<"accesskey">>, Record#calendar.accesskey},
+      {<<"class">>, Record#calendar.class},
+      {<<"contenteditable">>, case Record#calendar.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#calendar.contextmenu},
+      {<<"dir">>, case Record#calendar.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#calendar.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#calendar.dropzone},
+      {<<"hidden">>, case Record#calendar.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"spellcheck">>, case Record#calendar.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#calendar.style},
+      {<<"tabindex">>, Record#calendar.tabindex},
+      {<<"title">>, Record#calendar.title},
+      {<<"translate">>, case Record#calendar.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},
+      % spec
+      {<<"autocomplete">>, case Record#calendar.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#calendar.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#calendar.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#calendar.form},
+      {<<"list">>,Record#calendar.list},
+      {<<"name">>,Record#calendar.name},
+      {<<"readonly">>,if Record#calendar.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#calendar.required == true -> "required"; true -> [] end},
+      {<<"step">>,Record#calendar.step},
+      {<<"type">>, <<"calendar">>},
+      {<<"pattern">>,Record#calendar.pattern},
+      {<<"placeholder">>,Record#calendar.placeholder},
+      {<<"onkeypress">>, Record#calendar.onkeypress} | Record#calendar.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#calendar.body), List).
+
+init(Id,#calendar{minDate=Min,maxDate=Max,lang=Lang,format=Form,
+        value=Value,onSelect=SelectFn,disableDayFn=DisDayFn,firstDay=FirstDay,
+        position=Pos,reposition=Repos,yearRange=YearRange} = Calendar) ->
+    ID = nitro:to_list(Id),
+    I18n =        "clLangs.ua",
+    DefaultDate = case Value of
+       {Yv,Mv,Dv} -> nitro:f("new Date(~s,~s,~s)",[nitro:to_list(Yv),nitro:to_list(Mv-1),nitro:to_list(Dv)]);
+        _ -> "''" end,
+    MinDate =     case Min   of {Y,M,D}    -> nitro:f("new Date(~s,~s,~s)",[nitro:to_list(Y), nitro:to_list(M-1), nitro:to_list(D)]);   _ -> "new Date(2009, 3, 4)" end,
+    MaxDate =     case Max   of {Y1,M1,D1} -> nitro:f("new Date(~s,~s,~s)",[nitro:to_list(Y1),nitro:to_list(M1-1),nitro:to_list(D1)]);  _ -> "new Date(2189, 4, 1)" end,
+    OnSelect =    "null",
+    DisDay =      "null",
+    Position =    "bottom left",
+    Reposition =  "true",
+    WeekFirstDay = nitro:to_list(FirstDay),
+    nitro:wire(nitro:f(
+        "pickers['~s'] = new Pikaday({
+            field: document.getElementById('~s'),
+            i18n: ~s,
+            defaultDate: ~s,
+            setDefaultDate: true,
+            firstDay: ~s,
+            minDate: ~s,
+            maxDate: ~s,
+            format: '~s',
+            onSelect: ~s,
+            disableDayFn: ~s,
+            position: '~s',
+            reposition: ~s,
+            yearRange: ~s
+        });",
+        [ID,ID,I18n,DefaultDate,WeekFirstDay,MinDate,MaxDate,Form,OnSelect,DisDay,
+         Position,Reposition,nitro:to_list(YearRange)]
+    )).
+

+ 39 - 0
src/elements/combo/element_comboLookup.erl

@@ -0,0 +1,39 @@
+-module(element_comboLookup).
+-include_lib("nitro/include/comboLookup.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-export([render_element/1,proto/1]).
+
+proto(#comboKey{delegate=Module}=Msg)    -> Module:proto(Msg);
+proto(#comboKeyup{delegate=Module}=Msg)  -> Module:proto(Msg);
+proto(#comboSelect{delegate=Module}=Msg) -> Module:proto(Msg).
+
+render_element(#comboLookup{id=Id, style=Style, value = Val, bind = Object,
+  feed = Feed, disabled = Disabled, delegate = Module} = Data) ->
+  nitro:render(
+    #panel{id=form:atom([lookup, Id]), class=[dropdown],
+           body=[#input{id=Id, disabled = Disabled, type="comboLookup",
+                        autocomplete = "off",
+                        onkeyup = nitro:jse("comboLookupKeyup('"
+                               ++ nitro:to_list(Id) ++ "','"
+                               ++ nitro:to_list(Feed) ++ "','"
+                               ++ nitro:to_list(Module) ++ "')"),
+                        onkeydown= nitro:jse("comboLookupKeydown('"
+                               ++ nitro:to_list(Id) ++ "','"
+                               ++ nitro:to_list(Feed) ++ "','"
+                               ++ nitro:to_list(Module) ++ "')"),
+                        onchange= nitro:jse("comboLookupChange('"
+                               ++ nitro:to_list(Id) ++ "')"),
+                        bind = Object,
+                        value = Val, style = Style, class = column},
+                 #panel{class=['triangle'],
+                        body="&blacktriangledown;",
+                        onclick =
+                          case Disabled of
+                            true -> [];
+                            _ -> nitro:jse("comboLookupClick('"
+                              ++ nitro:to_list(Id) ++ "','"
+                              ++ nitro:to_list(Feed) ++ "','"
+                              ++ nitro:to_list(Module) ++ "')")
+                          end},
+                 #panel{id=form:atom([comboContainer, Id]),
+                        class = ['dropdown-content']}]}).

+ 42 - 0
src/elements/combo/element_comboLookupEdit.erl

@@ -0,0 +1,42 @@
+-module(element_comboLookupEdit).
+-include_lib("nitro/include/comboLookupEdit.hrl").
+-include_lib("nitro/include/comboLookup.hrl").
+-include_lib("nitro/include/sortable_list.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-export([render_element/1]).
+
+render_element(#comboLookupEdit{id=Id, input=Input, disabled=Disabled, validation=Validation, form=Form, values=Values, multiple=Multiple}) ->
+  ListId = form:atom([Id, "list"]),
+  InputId = element(#element.id, Input),
+  nitro:render(
+    #panel{
+      id = Id,
+      validation = Validation,
+      data_fields = [{<<"data-edit-input">>,<<"data-edit-input">>}],
+      body = [
+        #panel{
+          style = "display: flex; position: relative; width: 100%; justify-content: center;",
+          body =
+            [ Input,
+              case Multiple of
+                true ->
+                  #link{
+                    class = [button, sgreen],
+                    style = "min-width: 40px; text-align: center; height: fit-content; margin-left: 5px;",
+                    onclick = nitro:jse("addSortableItemFrom('#" ++ ListId ++ "', '" ++ InputId ++ "')"),
+                    body = <<"+">>};
+                false -> []
+              end,
+              case Disabled of
+                true -> [];
+                _ ->
+                  #panel{
+                    id = form:atom([InputId, "form"]),
+                    class = ['dropdown-content'],
+                    body = #panel{class = ['dropdown-item'], body = Form}
+                  } end ]},
+            case Multiple of
+              true -> #sortable_list{id = ListId, values = Values, closeable = true, disabled = Disabled};
+              false -> []
+            end ]}).

+ 28 - 0
src/elements/combo/element_comboLookupText.erl

@@ -0,0 +1,28 @@
+-module(element_comboLookupText).
+-include_lib("nitro/include/comboLookupText.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-export([render_element/1]).
+
+render_element(#comboLookupText{id=Id, input=Input, disabled=Disabled, validation=Validation, textarea=Textarea, values=Values}) ->
+  InputId = element(#element.id, Input),
+  LookupId = "wrap_" ++ Id ++ "_lookup",
+  TextareaId = "wrap_" ++ Id ++ "_textarea",
+  WrapId = "wrap_" ++ Id ++ "_comboLookupText",
+  nitro:render(
+        #panel{
+          id = WrapId,
+          validation = Validation,
+          data_fields = [{<<"data-text-input">>,<<"data-text-input">>}],
+          body = [
+            #panel{
+              id = LookupId,
+              style = "display: flex; width: 100%; justify-content: center;",
+              body =
+                case Disabled of
+                  true -> [];
+                  _ -> [Input]
+                end},
+            #panel{
+              id = TextareaId,
+              style = "display: flex; width: 100%; justify-content: center;",
+              body = [Textarea]}]}).

+ 31 - 0
src/elements/combo/element_comboLookupVec.erl

@@ -0,0 +1,31 @@
+-module(element_comboLookupVec).
+-include_lib("nitro/include/comboLookupVec.hrl").
+-include_lib("nitro/include/sortable_list.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-export([render_element/1]).
+
+render_element(#comboLookupVec{id=Id, input=Input, disabled=Disabled, validation=Validation, values=Values}) ->
+  ListId = form:atom([Id, "list"]),
+  InputId = element(#element.id, Input),
+  nitro:render(
+    #panel{
+      id = Id,
+      validation = Validation,
+      data_fields = [{<<"data-vector-input">>,<<"data-vector-input">>}],
+      body = [
+        #panel{
+          style = "display: flex; width: 100%; justify-content: center;",
+          body =
+            case Disabled of
+              true -> [];
+              _ ->
+                [ Input,
+                  #link{
+                    class = [button, sgreen],
+                    style = "min-width: 40px; text-align: center; height: fit-content; margin-left: 5px;",
+                    onclick = nitro:jse("addSortableItemFrom('#" ++ ListId ++ "', '" ++ InputId ++ "')"),
+                    body = <<"+">>} ] end },
+        % TODO: Add validation for each list_item and/or "+" button
+        % TODO?: Maybe show message "Empty list" when Values == []
+        #sortable_list{id = ListId, values = Values, closeable = true, disabled = Disabled}]}).

+ 18 - 0
src/elements/combo/element_koatuu.erl

@@ -0,0 +1,18 @@
+-module(element_koatuu).
+-include_lib("nitro/include/comboLookup.hrl").
+-include_lib("nitro/include/koatuuControl.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-export([render_element/1]).
+
+render_element(#koatuu{id=Id, style=Style, postback = Postback,delegate = Module} = Data) ->
+  Options = [ #option{ value = <<"Хмельницька"/utf8>>,
+                       body = <<"Хмельницька"/utf8>>,
+                      selected = true}], % 25 regions from const feed
+  nitro:render(
+    #panel{id=form:atom([koatuu, Id]),
+           body=[ #select{ id=form:atom([koatuu_select, Id]), postback=Postback,
+                           body=Options},
+
+                  #comboLookup{ } ]}).
+

+ 37 - 0
src/elements/combo/element_sortable_item.erl

@@ -0,0 +1,37 @@
+-module(element_sortable_item).
+-include_lib("nitro/include/sortable_item.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-export([render_element/1]).
+
+render_element(#sortable_item{list_id=ListId, value=Value, bind=Bind, closeable=Close, disabled=Disabled}) ->
+  Item = case Disabled of
+    true ->
+      #panel{
+        class = <<"list__item">>,
+        body = #panel{
+            class = <<"list__item-content">>,
+            style = <<"width:100%">>,
+            body = #panel{ class = <<"list__item-title">>, body = Value}}};
+    _ ->
+      #panel{
+        class = <<"list__item">>,
+        data_fields = [ {<<"data-sortable-item">>,<<"data-sortable-item">>} |
+          case Bind of
+            [] -> [];
+            _ -> [{<<"data-bind">>, base64:encode(term_to_binary(Bind))}] end ],
+        body = [
+          case Close of
+            true -> 
+              #panel{
+                class = <<"list__item-close">>,
+                onclick = nitro:jse("removeSortableItem('#" ++ ListId ++ "', this.parentNode);")};
+            _ -> [] end,
+          #panel{
+            class = <<"list__item-content">>,
+            style = case Close of true -> []; _ -> <<"width:100% - 40px">> end,
+            body = #panel{ class = <<"list__item-title">>, body = Value}},
+          #panel{
+            class = <<"list__item-handle">>,
+            data_fields = [{<<"data-sortable-handle">>,<<"data-sortable-handle">>}]}]} end,
+  nitro:render(Item).

+ 24 - 0
src/elements/combo/element_sortable_list.erl

@@ -0,0 +1,24 @@
+-module(element_sortable_list).
+-include_lib("nitro/include/sortable_list.hrl").
+-include_lib("nitro/include/sortable_item.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-export([render_element/1]).
+
+render_element(#sortable_list{id = Id, values = Values, closeable = Close, disabled = Disabled}) ->
+  case Disabled of
+    true ->
+      Closeable = false;
+    _ ->
+      nitro:wire("createSortable('#" ++ Id ++ "');"),
+      Closeable = Close end,
+  ProtoItem = #sortable_item{ list_id = Id, closeable = Closeable, disabled = Disabled},
+  Body = case Values of
+          {view_value_pairs, List} ->
+            [ ProtoItem#sortable_item{value = Val, bind = Bind} || {Val, Bind} <- List ];
+          _ ->
+            [ ProtoItem#sortable_item{value = Val} || Val <- Values ] end,
+  nitro:render(
+    #panel{ id = Id,
+            data_fields = [{<<"data-sortable-list">>, <<"data-sortable-list">>}],
+            body = Body}).

+ 29 - 0
src/elements/edit/element_del.erl

@@ -0,0 +1,29 @@
+-module(element_del).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#del.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#del.accesskey},
+      {<<"class">>, Record#del.class},
+      {<<"contenteditable">>, case Record#del.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#del.contextmenu},
+      {<<"dir">>, case Record#del.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#del.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#del.dropzone},
+      {<<"hidden">>, case Record#del.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#del.id},
+      {<<"lang">>, Record#del.lang},
+      {<<"spellcheck">>, case Record#del.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#del.style},
+      {<<"tabindex">>, Record#del.tabindex},
+      {<<"title">>, Record#del.title},
+      {<<"translate">>, case Record#del.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"cite">>, Record#del.cite},
+      {<<"datetime">>, Record#del.datetime} | Record#del.data_fields
+    ],
+    wf_tags:emit_tag(<<"del">>, nitro:render(case Record#del.body of [] -> []; B -> B end), List).

+ 29 - 0
src/elements/edit/element_ins.erl

@@ -0,0 +1,29 @@
+-module(element_ins).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#ins.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#ins.accesskey},
+      {<<"class">>, Record#ins.class},
+      {<<"contenteditable">>, case Record#ins.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#ins.contextmenu},
+      {<<"dir">>, case Record#ins.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#ins.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#ins.dropzone},
+      {<<"hidden">>, case Record#ins.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#ins.id},
+      {<<"lang">>, Record#ins.lang},
+      {<<"spellcheck">>, case Record#ins.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#ins.style},
+      {<<"tabindex">>, Record#ins.tabindex},
+      {<<"title">>, Record#ins.title},
+      {<<"translate">>, case Record#ins.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"cite">>, Record#ins.cite},
+      {<<"datetime">>, Record#ins.datetime} | Record#ins.data_fields
+    ],
+    wf_tags:emit_tag(<<"ins">>, nitro:render(case Record#ins.body of [] -> []; B -> B end), List).

+ 36 - 0
src/elements/embed/element_area.erl

@@ -0,0 +1,36 @@
+-module(element_area).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#area.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#area.accesskey},
+      {<<"class">>, Record#area.class},
+      {<<"contenteditable">>, case Record#area.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#area.contextmenu},
+      {<<"dir">>, case Record#area.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#area.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#area.dropzone},
+      {<<"hidden">>, case Record#area.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#area.id},
+      {<<"lang">>, Record#area.lang},
+      {<<"spellcheck">>, case Record#area.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#area.style},
+      {<<"tabindex">>, Record#area.tabindex},
+      {<<"title">>, Record#area.title},
+      {<<"translate">>, case Record#area.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"alt">>,Record#area.alt},
+      {<<"coords">>,Record#area.coords},
+      {<<"href">>,Record#area.href},
+      {<<"hreflang">>,Record#area.hreflang},
+      {<<"media">>,Record#area.media},
+      {<<"rel">>,Record#area.rel},
+      {<<"shape">>, case Record#area.shape of "rect" -> "rect"; "circle" -> "circle"; "poly" -> "poly"; "default" -> "default"; _ -> [] end},
+      {<<"target">>,Record#area.target},
+      {<<"type">>,Record#area.type} | Record#area.data_fields
+    ],
+    wf_tags:emit_tag(<<"area">>, List).

+ 35 - 0
src/elements/embed/element_audio.erl

@@ -0,0 +1,35 @@
+-module(element_audio).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#audio.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#audio.accesskey},
+      {<<"class">>, Record#audio.class},
+      {<<"contenteditable">>, case Record#audio.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#audio.contextmenu},
+      {<<"dir">>, case Record#audio.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#audio.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#audio.dropzone},
+      {<<"hidden">>, case Record#audio.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#audio.id},
+      {<<"lang">>, Record#audio.lang},
+      {<<"spellcheck">>, case Record#audio.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#audio.style},
+      {<<"tabindex">>, Record#audio.tabindex},
+      {<<"title">>, Record#audio.title},
+      {<<"translate">>, case Record#audio.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autoplay">>, case Record#audio.autoplay of true -> "autoplay"; _ -> [] end},      
+      {<<"controls">>, case Record#audio.controls of true -> "controls"; _ -> [] end},      
+      {<<"loop">>, case Record#audio.loop of true -> "loop"; _ -> [] end},            
+      {<<"mediagroup">>, Record#audio.mediagroup},      
+      {<<"muted">>, case Record#audio.muted of true -> "muted"; _ -> [] end},
+      {<<"preload">>, case Record#audio.preload of "auto" -> "auto"; "none" -> "none"; "metadata" -> "metadata"; _ -> [] end},
+      {<<"src">>, Record#audio.src},
+      {<<"width">>, Record#audio.width} | Record#audio.data_fields
+    ],
+    wf_tags:emit_tag(<<"audio">>, nitro:render(case Record#audio.body of [] -> []; B -> B end), List).

+ 29 - 0
src/elements/embed/element_canvas.erl

@@ -0,0 +1,29 @@
+-module(element_canvas).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#canvas.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#canvas.accesskey},
+      {<<"class">>, Record#canvas.class},
+      {<<"contenteditable">>, case Record#canvas.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#canvas.contextmenu},
+      {<<"dir">>, case Record#canvas.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#canvas.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#canvas.dropzone},
+      {<<"hidden">>, case Record#canvas.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#canvas.id},
+      {<<"lang">>, Record#canvas.lang},
+      {<<"spellcheck">>, case Record#canvas.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#canvas.style},
+      {<<"tabindex">>, Record#canvas.tabindex},
+      {<<"title">>, Record#canvas.title},
+      {<<"translate">>, case Record#canvas.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"height">>,Record#canvas.height},
+      {<<"width">>,Record#canvas.width} | Record#canvas.data_fields
+    ],
+    wf_tags:emit_tag(<<"canvas">>, nitro:render(case Record#canvas.body of [] -> []; B -> B end), List).

+ 31 - 0
src/elements/embed/element_embed.erl

@@ -0,0 +1,31 @@
+-module(element_embed).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#embed.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#embed.accesskey},
+      {<<"class">>, Record#embed.class},
+      {<<"contenteditable">>, case Record#embed.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#embed.contextmenu},
+      {<<"dir">>, case Record#embed.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#embed.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#embed.dropzone},
+      {<<"hidden">>, case Record#embed.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#embed.id},
+      {<<"lang">>, Record#embed.lang},
+      {<<"spellcheck">>, case Record#embed.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#embed.style},
+      {<<"tabindex">>, Record#embed.tabindex},
+      {<<"title">>, Record#embed.title},
+      {<<"translate">>, case Record#embed.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"height">>,Record#embed.height},      
+      {<<"src">>,Record#embed.src},
+      {<<"type">>,Record#embed.type},
+      {<<"width">>,Record#embed.width} | Record#embed.data_fields
+    ],
+    wf_tags:emit_tag(<<"embed">>, List).

+ 34 - 0
src/elements/embed/element_iframe.erl

@@ -0,0 +1,34 @@
+-module(element_iframe).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#iframe.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#iframe.accesskey},
+      {<<"class">>, Record#iframe.class},
+      {<<"contenteditable">>, case Record#iframe.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#iframe.contextmenu},
+      {<<"dir">>, case Record#iframe.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#iframe.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#iframe.dropzone},
+      {<<"hidden">>, case Record#iframe.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#iframe.id},
+      {<<"lang">>, Record#iframe.lang},
+      {<<"spellcheck">>, case Record#iframe.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#iframe.style},
+      {<<"tabindex">>, Record#iframe.tabindex},
+      {<<"title">>, Record#iframe.title},
+      {<<"translate">>, case Record#iframe.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"height">>,Record#iframe.height},      
+      {<<"sandbox">>,Record#iframe.sandbox},      
+      {<<"seamless">>, if Record#iframe.seamless == true -> "seamless"; true -> [] end},
+      {<<"src">>,Record#iframe.src},
+      {<<"srcdoc">>,Record#iframe.srcdoc},            
+      {<<"name">>,Record#iframe.name},
+      {<<"width">>,Record#iframe.width} | Record#iframe.data_fields
+    ],
+    wf_tags:emit_tag(<<"iframe">>, [], List).

+ 19 - 0
src/elements/embed/element_image.erl

@@ -0,0 +1,19 @@
+-module(element_image).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#image.show_if==false -> [<<>>];
+render_element(Record) ->
+  Attributes = [
+    {<<"id">>, Record#image.id},
+    {<<"class">>, Record#image.class},
+    {<<"title">>, Record#image.title},
+    {<<"style">>, Record#image.style},
+    {<<"alt">>, Record#image.alt},
+    {<<"width">>, Record#image.width},
+    {<<"height">>, Record#image.height},
+    {<<"src">>, nitro:coalesce([Record#image.src, Record#image.image])} | Record#image.data_fields
+  ],
+
+  wf_tags:emit_tag(<<"img">>, Attributes).

+ 28 - 0
src/elements/embed/element_map.erl

@@ -0,0 +1,28 @@
+-module(element_map).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#map.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#map.accesskey},
+      {<<"class">>, Record#map.class},
+      {<<"contenteditable">>, case Record#map.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#map.contextmenu},
+      {<<"dir">>, case Record#map.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#map.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#map.dropzone},
+      {<<"hidden">>, case Record#map.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#map.id},
+      {<<"lang">>, Record#map.lang},
+      {<<"spellcheck">>, case Record#map.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#map.style},
+      {<<"tabindex">>, Record#map.tabindex},
+      {<<"title">>, Record#map.title},
+      {<<"translate">>, case Record#map.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"name">>,Record#map.name} | Record#map.data_fields
+    ],
+    wf_tags:emit_tag(<<"map">>, nitro:render(case Record#map.body of [] -> []; B -> B end), List).

+ 34 - 0
src/elements/embed/element_object.erl

@@ -0,0 +1,34 @@
+-module(element_object).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#object.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#object.accesskey},
+      {<<"class">>, Record#object.class},
+      {<<"contenteditable">>, case Record#object.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#object.contextmenu},
+      {<<"dir">>, case Record#object.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#object.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#object.dropzone},
+      {<<"hidden">>, case Record#object.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#object.id},
+      {<<"lang">>, Record#object.lang},
+      {<<"spellcheck">>, case Record#object.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#object.style},
+      {<<"tabindex">>, Record#object.tabindex},
+      {<<"title">>, Record#object.title},
+      {<<"translate">>, case Record#object.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"data">>,Record#object.data},      
+      {<<"form">>,Record#object.form},      
+      {<<"height">>,Record#object.height},      
+      {<<"name">>,Record#object.name},            
+      {<<"type">>,Record#object.type},
+      {<<"usemap">>,Record#object.usemap},            
+      {<<"width">>,Record#object.width} | Record#object.data_fields
+    ],
+    wf_tags:emit_tag(<<"object">>, nitro:render(case Record#object.body of [] -> []; B -> B end), List).

+ 29 - 0
src/elements/embed/element_param.erl

@@ -0,0 +1,29 @@
+-module(element_param).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#param.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#param.accesskey},
+      {<<"class">>, Record#param.class},
+      {<<"contenteditable">>, case Record#param.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#param.contextmenu},
+      {<<"dir">>, case Record#param.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#param.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#param.dropzone},
+      {<<"hidden">>, case Record#param.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#param.id},
+      {<<"lang">>, Record#param.lang},
+      {<<"spellcheck">>, case Record#param.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#param.style},
+      {<<"tabindex">>, Record#param.tabindex},
+      {<<"title">>, Record#param.title},
+      {<<"translate">>, case Record#param.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"name">>,Record#param.name},
+      {<<"value">>,Record#param.value} | Record#param.data_fields
+    ],
+    wf_tags:emit_tag(<<"param">>, List).

+ 30 - 0
src/elements/embed/element_source.erl

@@ -0,0 +1,30 @@
+-module(element_source).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#source.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#source.accesskey},
+      {<<"class">>, Record#source.class},
+      {<<"contenteditable">>, case Record#source.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#source.contextmenu},
+      {<<"dir">>, case Record#source.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#source.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#source.dropzone},
+      {<<"hidden">>, case Record#source.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#source.id},
+      {<<"lang">>, Record#source.lang},
+      {<<"spellcheck">>, case Record#source.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#source.style},
+      {<<"tabindex">>, Record#source.tabindex},
+      {<<"title">>, Record#source.title},
+      {<<"translate">>, case Record#source.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"media">>,Record#source.media},
+      {<<"type">>,Record#source.type},
+      {<<"src">>,Record#source.src} | Record#source.data_fields
+    ],
+    wf_tags:emit_tag(<<"source">>, List).

+ 32 - 0
src/elements/embed/element_track.erl

@@ -0,0 +1,32 @@
+-module(element_track).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#track.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#track.accesskey},
+      {<<"class">>, Record#track.class},
+      {<<"contenteditable">>, case Record#track.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#track.contextmenu},
+      {<<"dir">>, case Record#track.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#track.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#track.dropzone},
+      {<<"hidden">>, case Record#track.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#track.id},
+      {<<"lang">>, Record#track.lang},
+      {<<"spellcheck">>, case Record#track.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#track.style},
+      {<<"tabindex">>, Record#track.tabindex},
+      {<<"title">>, Record#track.title},
+      {<<"translate">>, case Record#track.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"default">>, case Record#track.default of true -> "default"; _ -> [] end},
+      {<<"kind">>, case Record#track.kind of "subtitles" -> "subtitles"; "captions" -> "captions"; "descriptions" -> "descriptions"; "chapters" -> "chapters"; "metadata" -> "metadata"; _ -> [] end},
+      {<<"label">>, Record#track.label},
+      {<<"src">>, Record#track.src},
+      {<<"srclang">>, Record#track.srclang} | Record#track.data_fields
+    ],
+    wf_tags:emit_tag(<<"track">>, List).

+ 37 - 0
src/elements/embed/element_video.erl

@@ -0,0 +1,37 @@
+-module(element_video).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#video.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#video.accesskey},
+      {<<"class">>, Record#video.class},
+      {<<"contenteditable">>, case Record#video.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#video.contextmenu},
+      {<<"dir">>, case Record#video.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#video.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#video.dropzone},
+      {<<"hidden">>, case Record#video.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#video.id},
+      {<<"lang">>, Record#video.lang},
+      {<<"spellcheck">>, case Record#video.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#video.style},
+      {<<"tabindex">>, Record#video.tabindex},
+      {<<"title">>, Record#video.title},
+      {<<"translate">>, case Record#video.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autoplay">>, case Record#video.autoplay of true -> "autoplay"; _ -> [] end},      
+      {<<"controls">>, case Record#video.controls of true -> "controls"; _ -> [] end},      
+      {<<"height">>, Record#video.height},      
+      {<<"loop">>, case Record#video.loop of true -> "loop"; _ -> [] end},            
+      {<<"mediagroup">>, Record#video.mediagroup},      
+      {<<"muted">>, case Record#video.muted of true -> "muted"; _ -> [] end},
+      {<<"poster">>, Record#video.poster},      
+      {<<"preload">>, case Record#video.preload of "auto" -> "auto"; "none" -> "none"; "metadata" -> "metadata"; _ -> [] end},
+      {<<"src">>, Record#video.src},     
+      {<<"width">>, Record#video.width} | Record#video.data_fields
+    ],
+    wf_tags:emit_tag(<<"video">>, nitro:render(case Record#video.body of [] -> []; B -> B end), List).

+ 26 - 0
src/elements/form/element_button.erl

@@ -0,0 +1,26 @@
+-module(element_button).
+-author('Andrew Zadorozhny').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#button.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#button.postback of
+        [] -> Record#button.id;
+        undefined -> Record#button.id;
+        Postback ->
+          ID = case Record#button.id of [] -> nitro:temp_id(); I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#button.source, delegate=Record#button.delegate }),
+          ID end,
+    wf_tags:emit_tag(<<"button">>, nitro:render(Record#button.body), [
+        {<<"id">>, Id},
+        {<<"type">>, Record#button.type},
+        {<<"name">>, Record#button.name},
+        {<<"class">>, Record#button.class},
+        {<<"style">>, Record#button.style},
+        {<<"onchange">>, Record#button.onchange},
+        {<<"onclick">>, Record#button.onclick},
+        {<<"disabled">>, if Record#button.disabled == true -> "disabled"; true -> [] end},
+        {<<"value">>, Record#button.value}  | Record#button.data_fields ]).

+ 39 - 0
src/elements/form/element_fieldset.erl

@@ -0,0 +1,39 @@
+-module(element_fieldset).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#fieldset.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#fieldset.accesskey},
+      {<<"class">>, Record#fieldset.class},
+      {<<"contenteditable">>, case Record#fieldset.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#fieldset.contextmenu},
+      {<<"dir">>, case Record#fieldset.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#fieldset.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#fieldset.dropzone},
+      {<<"hidden">>, case Record#fieldset.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#fieldset.id},
+      {<<"lang">>, Record#fieldset.lang},
+      {<<"spellcheck">>, case Record#fieldset.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#fieldset.style},
+      {<<"tabindex">>, Record#fieldset.tabindex},
+      {<<"title">>, Record#fieldset.title},
+      {<<"translate">>, case Record#fieldset.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"disabled">>, if Record#fieldset.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#fieldset.form},
+      {<<"name">>,Record#fieldset.name} | Record#fieldset.data_fields
+    ],
+    wf_tags:emit_tag(
+      <<"fieldset">>,
+      [
+        case Record#fieldset.legend of 
+          [] -> [];
+          B -> wf_tags:emit_tag(<<"legend">>, nitro:render(B), [])
+        end, 
+        nitro:render(case Record#fieldset.body of [] -> []; B -> B end)
+      ], 
+      List).

+ 44 - 0
src/elements/form/element_form.erl

@@ -0,0 +1,44 @@
+-module(element_form).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#form.show_if==false -> [<<>>];
+render_element(Record) ->
+    ID = case Record#form.id of [] -> nitro:temp_id(); I->I end,
+    case Record#form.postback of
+         [] -> skip;
+         Postback -> nitro:wire(#event { type=submit,
+                                         target=ID,
+                                         postback=Postback,
+                                         delegate=Record#form.delegate,
+                                         source=Record#form.source }) end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#form.accesskey},
+      {<<"class">>, Record#form.class},
+      {<<"contenteditable">>, case Record#form.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#form.contextmenu},
+      {<<"dir">>, case Record#form.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#form.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#form.dropzone},
+      {<<"hidden">>, case Record#form.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, ID},
+      {<<"lang">>, Record#form.lang},
+      {<<"spellcheck">>, case Record#form.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#form.style},
+      {<<"tabindex">>, Record#form.tabindex},
+      {<<"title">>, Record#form.title},
+      {<<"translate">>, case Record#form.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},
+      % spec
+      {<<"accept-charset">>, Record#form.accept_charset},
+      {<<"action">>, Record#form.action},
+      {<<"autocomplete">>, case Record#form.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"enctype">>, case Record#form.enctype of "application/x-www-form-urlencoded" -> "application/x-www-form-urlencoded"; "multipart/form-data" -> "multipart/form-data"; "text/plain" -> "text/plain"; _ -> [] end},
+      {<<"method">>, case Record#form.method of "post" -> "post"; _ -> "get" end},
+      {<<"name">>,Record#form.name},
+      {<<"novalidate">>, case Record#form.novalidate of true -> "novalidate"; _ -> [] end},
+      {<<"target">>, Record#form.target} | Record#form.data_fields
+    ],
+    wf_tags:emit_tag(<<"form">>, nitro:render(Record#form.body), List).

+ 43 - 0
src/elements/form/element_keygen.erl

@@ -0,0 +1,43 @@
+-module(element_keygen).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#keygen.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#keygen.postback of
+        [] -> Record#keygen.id;
+        Postback ->
+          ID = case Record#keygen.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#keygen.source, delegate=Record#keygen.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#keygen.accesskey},
+      {<<"class">>, Record#keygen.class},
+      {<<"contenteditable">>, case Record#keygen.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#keygen.contextmenu},
+      {<<"dir">>, case Record#keygen.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#keygen.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#keygen.dropzone},
+      {<<"hidden">>, case Record#keygen.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#keygen.lang},
+      {<<"spellcheck">>, case Record#keygen.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#keygen.style},
+      {<<"tabindex">>, Record#keygen.tabindex},
+      {<<"title">>, Record#keygen.title},
+      {<<"translate">>, case Record#keygen.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autofocus">>,if Record#keygen.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"challenge">>,Record#keygen.challenge},      
+      {<<"disabled">>, if Record#keygen.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#keygen.form},
+      {<<"keytype">>,<<"rsa">>},
+      {<<"name">>,Record#keygen.name} | Record#keygen.data_fields
+    ],
+    wf_tags:emit_tag(<<"keygen">>, nitro:render(Record#keygen.body), List).

+ 14 - 0
src/elements/form/element_label.erl

@@ -0,0 +1,14 @@
+-module(element_label).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#label.show_if==false -> [<<>>];
+render_element(Record) -> 
+  wf_tags:emit_tag(<<"label">>, nitro:render(Record#label.body), [
+    {<<"id">>, Record#label.id},
+    {<<"class">>, Record#label.class},
+    {<<"style">>, Record#label.style},
+    {<<"for">>, Record#label.for},
+    {<<"onclick">>, Record#label.onclick} | Record#label.data_fields
+  ]).

+ 12 - 0
src/elements/form/element_legend.erl

@@ -0,0 +1,12 @@
+-module(element_legend).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#legend.show_if==false -> [<<>>];
+render_element(Record) -> 
+  wf_tags:emit_tag(<<"legend">>, nitro:render(Record#legend.body), [
+    {<<"id">>, Record#legend.id},
+    {<<"class">>, Record#legend.class},
+    {<<"style">>, Record#legend.style} | Record#legend.data_fields
+  ]).

+ 33 - 0
src/elements/form/element_meter.erl

@@ -0,0 +1,33 @@
+-module(element_meter).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#meter.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#meter.accesskey},
+      {<<"class">>, Record#meter.class},
+      {<<"contenteditable">>, case Record#meter.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#meter.contextmenu},
+      {<<"dir">>, case Record#meter.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#meter.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#meter.dropzone},
+      {<<"hidden">>, case Record#meter.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#meter.id},
+      {<<"lang">>, Record#meter.lang},
+      {<<"spellcheck">>, case Record#meter.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#meter.style},
+      {<<"tabindex">>, Record#meter.tabindex},
+      {<<"title">>, Record#meter.title},
+      {<<"translate">>, case Record#meter.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"high">>,Record#meter.high},
+      {<<"low">>,Record#meter.low},
+      {<<"max">>,Record#meter.max},
+      {<<"min">>,Record#meter.min},
+      {<<"optimum">>,Record#meter.optimum},
+      {<<"value">>, Record#meter.value} | Record#meter.data_fields
+    ],
+    wf_tags:emit_tag(<<"meter">>, nitro:render(case Record#meter.body of [] -> []; B -> B end), List).

+ 30 - 0
src/elements/form/element_output.erl

@@ -0,0 +1,30 @@
+-module(element_output).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#output.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#output.accesskey},
+      {<<"class">>, Record#output.class},
+      {<<"contenteditable">>, case Record#output.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#output.contextmenu},
+      {<<"dir">>, case Record#output.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#output.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#output.dropzone},
+      {<<"hidden">>, case Record#output.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#output.id},
+      {<<"lang">>, Record#output.lang},
+      {<<"spellcheck">>, case Record#output.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#output.style},
+      {<<"tabindex">>, Record#output.tabindex},
+      {<<"title">>, Record#output.title},
+      {<<"translate">>, case Record#output.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"for">>,Record#output.for},
+      {<<"form">>,Record#output.form},
+      {<<"name">>,Record#output.name} | Record#output.data_fields
+    ],
+    wf_tags:emit_tag(<<"output">>, nitro:render(case Record#output.body of [] -> []; B -> B end), List).

+ 29 - 0
src/elements/form/element_progress.erl

@@ -0,0 +1,29 @@
+-module(element_progress).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#progress.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#progress.accesskey},
+      {<<"class">>, Record#progress.class},
+      {<<"contenteditable">>, case Record#progress.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#progress.contextmenu},
+      {<<"dir">>, case Record#progress.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#progress.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#progress.dropzone},
+      {<<"hidden">>, case Record#progress.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#progress.id},
+      {<<"lang">>, Record#progress.lang},
+      {<<"spellcheck">>, case Record#progress.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#progress.style},
+      {<<"tabindex">>, Record#progress.tabindex},
+      {<<"title">>, Record#progress.title},
+      {<<"translate">>, case Record#progress.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"max">>,Record#progress.max},
+      {<<"value">>,Record#progress.value} | Record#progress.data_fields
+    ],
+    wf_tags:emit_tag(<<"progress">>, nitro:render(case Record#progress.body of [] -> []; B -> B end), List).

+ 48 - 0
src/elements/form/element_select.erl

@@ -0,0 +1,48 @@
+-module(element_select).
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#select.show_if==false -> [<<>>];
+render_element(Record = #select{}) ->
+  ID = case Record#select.id of [] -> nitro:temp_id(); I->I end,
+  case Record#select.postback of
+    [] -> skip;
+    Postback -> nitro:wire(#event{ type=change,
+                                target=ID,
+                                postback=Postback,
+                                source=[nitro:to_atom(ID)|Record#select.source],
+                                delegate=Record#select.delegate }) end,
+  Props = [
+    {<<"id">>, ID},
+    {<<"class">>, Record#select.class},
+    {<<"style">>, Record#select.style},
+    {<<"name">>, Record#select.name},
+    {<<"onchange">>, Record#select.onchange},
+    {<<"title">>, Record#select.title},
+    {<<"required">>, case Record#select.required of true -> <<"required">>; _-> [] end},
+    {<<"disabled">>, case Record#select.disabled of true -> <<"disabled">>; _-> [] end},
+    {<<"multiple">>, case Record#select.multiple of true -> <<"multiple">>; _-> [] end} | Record#select.data_fields
+  ],
+  wf_tags:emit_tag(<<"select">>, nitro:render(Record#select.body),
+                                  Props);
+render_element(Group = #optgroup{}) ->
+  wf_tags:emit_tag(<<"optgroup">>, nitro:render(Group#optgroup.body), [
+    {<<"disabled">>, case Group#optgroup.disabled of true-> <<"disabled">>; _-> [] end},
+    {<<"label">>, Group#optgroup.label}
+  ]);
+render_element(O = #option{}) ->
+  wf_tags:emit_tag(<<"option">>, nitro:render(O#option.body), lists:flatten([get_attrs(O) | O#option.data_fields])).
+
+get_attrs(O) ->
+  ValueAttr = case {O#option.selected, O#option.disabled} of
+                {true, _} -> <<"selected value">>;
+                {true, true} -> <<"selected disabled value">>;
+                {_, true} -> <<"disabled value">>;
+                _ -> <<"value">>
+              end,
+  [{<<"id">>, O#option.id},
+    {<<"label">>, O#option.label},
+    {<<"title">>, O#option.title},
+    {ValueAttr, O#option.value}
+  ].

+ 41 - 0
src/elements/form/element_textarea.erl

@@ -0,0 +1,41 @@
+-module(element_textarea).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#textarea.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#textarea.accesskey},
+      {<<"class">>, Record#textarea.class},
+      {<<"contenteditable">>, case Record#textarea.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#textarea.contextmenu},
+      {<<"dir">>, case Record#textarea.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#textarea.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#textarea.dropzone},
+      {<<"hidden">>, case Record#textarea.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#textarea.id},
+      {<<"lang">>, Record#textarea.lang},
+      {<<"spellcheck">>, case Record#textarea.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#textarea.style},
+      {<<"tabindex">>, Record#textarea.tabindex},
+      {<<"title">>, Record#textarea.title},
+      {<<"translate">>, case Record#textarea.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autofocus">>,if Record#textarea.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"cols">>,Record#textarea.cols},
+      {<<"dirname">>,Record#textarea.dirname},      
+      {<<"disabled">>, if Record#textarea.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#textarea.form},
+      {<<"maxlength">>,Record#textarea.maxlength},      
+      {<<"name">>,Record#textarea.name},
+      {<<"placeholder">>,Record#textarea.placeholder},
+      {<<"readonly">>,if Record#textarea.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#textarea.required == true -> "required"; true -> [] end},
+      {<<"rows">>,Record#textarea.rows},      
+      {<<"form">>,Record#textarea.wrap},
+      {<<"value">>,Record#textarea.value},
+      {<<"wrap">>, case Record#textarea.wrap of "hard" -> "hard"; "soft" -> "soft"; _ -> [] end} | Record#textarea.data_fields
+    ],
+    wf_tags:emit_tag(<<"textarea">>, nitro:render(case Record#textarea.body of [] -> []; B -> B end), List).

+ 13 - 0
src/elements/group/element_blockquote.erl

@@ -0,0 +1,13 @@
+-module (element_blockquote).
+-author('Andrew Zadorozhny').
+-include("nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#blockquote.show_if==false -> [<<>>];
+render_element(Record) ->
+  wf_tags:emit_tag(<<"blockquote">>, nitro:render(Record#blockquote.body), [
+      {<<"id">>, Record#blockquote.id},
+      {<<"class">>, Record#blockquote.class},
+      {<<"style">>, Record#blockquote.style},
+      {<<"cite">>, Record#blockquote.cite}  | Record#blockquote.data_fields
+  ]).

+ 20 - 0
src/elements/group/element_dtl.erl

@@ -0,0 +1,20 @@
+-module(element_dtl).
+-author('Maxim Sokhatsky').
+-include("nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#dtl.show_if==false -> [<<>>];
+render_element(Record=#dtl{}) ->
+    M = list_to_atom(nitro:to_list(Record#dtl.file) ++ "_view"),
+    %File = case code:lib_dir(nitro:to_atom(Record#dtl.app)) of
+                %{error,bad_name} -> nitro:to_list(Record#dtl.app);
+                %A -> A end ++ "/" ++ nitro:to_list(Record#dtl.folder)
+         %++ "/" ++ nitro:to_list(Record#dtl.file) ++ "." ++ nitro:to_list(Record#dtl.ext),
+    {ok,R} = render(M, Record#dtl.js_escape, [{K,nitro:render(V)} || {K,V} <- Record#dtl.bindings] ++
+        if Record#dtl.bind_script==true -> [{script,nitro:script()}]; true-> [] end),
+    R.
+
+render(M, true, Args) ->
+    {ok, R} = M:render(Args),
+    {ok, nitro:js_escape(R)};
+render(M, _, Args) -> M:render(Args).

+ 28 - 0
src/elements/group/element_html.erl

@@ -0,0 +1,28 @@
+-module(element_html).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#html.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#html.accesskey},
+      {<<"class">>, Record#html.class},
+      {<<"contenteditable">>, case Record#html.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#html.contextmenu},
+      {<<"dir">>, case Record#html.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#html.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#html.dropzone},
+      {<<"hidden">>, case Record#html.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#html.id},
+      {<<"lang">>, Record#html.lang},
+      {<<"spellcheck">>, case Record#html.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#html.style},
+      {<<"tabindex">>, Record#html.tabindex},
+      {<<"title">>, Record#html.title},
+      {<<"translate">>, case Record#html.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"manifest">>, Record#html.manifest} | Record#html.data_fields
+    ],
+    wf_tags:emit_tag(<<"html">>, nitro:render(case Record#html.body of [] -> []; B -> B end), List).

+ 12 - 0
src/elements/group/element_li.erl

@@ -0,0 +1,12 @@
+-module(element_li).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#li.show_if==false -> [<<>>];
+render_element(Record) -> 
+  wf_tags:emit_tag(<<"li">>, nitro:render(Record#li.body), [
+    {<<"class">>, Record#li.class},
+    {<<"id">>, Record#li.id},
+    {<<"style">>, Record#li.style} | Record#li.data_fields
+  ]).

+ 35 - 0
src/elements/group/element_script.erl

@@ -0,0 +1,35 @@
+-module(element_script).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#script.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#script.accesskey},
+      {<<"class">>, Record#script.class},
+      {<<"contenteditable">>, case Record#script.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#script.contextmenu},
+      {<<"dir">>, case Record#script.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#script.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#script.dropzone},
+      {<<"hidden">>, case Record#script.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#script.id},
+      {<<"lang">>, Record#script.lang},
+      {<<"spellcheck">>, case Record#script.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#script.style},
+      {<<"tabindex">>, Record#script.tabindex},
+      {<<"title">>, Record#script.title},
+      {<<"translate">>, case Record#script.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"async">>, if Record#script.async == true -> "async"; true -> [] end},
+      {<<"charset">>,Record#script.charset},
+      {<<"defer">>, if Record#script.defer == true -> "defer"; true -> [] end},
+      {<<"src">>,Record#script.src},
+      {<<"type">>,Record#script.type} | Record#script.data_fields
+    ],
+    wf_tags:emit_tag(<<"script">>,
+      case Record#script.src of
+           [] -> nitro:render(case Record#script.body of [] -> []; B -> B end);
+           _ -> [] end, List).

+ 46 - 0
src/elements/input/element_checkbox.erl

@@ -0,0 +1,46 @@
+-module(element_checkbox).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#checkbox.show_if==false -> [<<>>];
+render_element(Record) -> 
+    Id = case Record#checkbox.id of [] -> nitro:temp_id(); I->I end,
+    case Record#checkbox.postback of
+        [] -> ignore;
+        Postback -> nitro:wire(#event { type=change, postback=Postback, target=Id, source=[Id|Record#checkbox.source], delegate=Record#checkbox.delegate })
+    end,
+   Label = [ wf_tags:emit_tag(<<"input">>, [], [
+      % global
+      {<<"accesskey">>, Record#checkbox.accesskey},
+      {<<"class">>, Record#checkbox.class},
+      {<<"contenteditable">>, case Record#checkbox.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#checkbox.contextmenu},
+      {<<"dir">>, case Record#checkbox.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#checkbox.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#checkbox.dropzone},
+      {<<"hidden">>, case Record#checkbox.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#checkbox.lang},
+      {<<"spellcheck">>, case Record#checkbox.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#checkbox.style},
+      {<<"tabindex">>, Record#checkbox.tabindex},
+      {<<"title">>, Record#checkbox.title},
+      {<<"translate">>, case Record#checkbox.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autofocus">>,Record#checkbox.autofocus},
+      {<<"checked">>, if Record#checkbox.checked==true -> <<"checked">>; true -> [] end},
+      {<<"data-toggle">>, <<"checkbox">>},
+      {<<"disabled">>, if Record#checkbox.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>, Record#checkbox.form},
+      {<<"name">>, Record#checkbox.name},            
+      {<<"required">>, if Record#checkbox.required == true -> "required"; true -> [] end},
+      {<<"type">>, <<"checkbox">>},
+      {<<"value">>, Record#checkbox.value} | Record#checkbox.data_fields
+      ]),
+      case Record#checkbox.body of [] -> []; B -> B end ],
+    wf_tags:emit_tag(<<"label">>, nitro:render(Label), [
+        {<<"class">>, Record#checkbox.class},
+        {<<"style">>, Record#checkbox.style},
+        {<<"for">>, Id} ]).

+ 45 - 0
src/elements/input/element_color.erl

@@ -0,0 +1,45 @@
+-module(element_color).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#color.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#color.postback of
+        [] -> Record#color.id;
+        Postback ->
+          ID = case Record#color.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#color.source, delegate=Record#color.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#color.accesskey},
+      {<<"class">>, Record#color.class},
+      {<<"contenteditable">>, case Record#color.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#color.contextmenu},
+      {<<"dir">>, case Record#color.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#color.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#color.dropzone},
+      {<<"hidden">>, case Record#color.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#color.lang},
+      {<<"spellcheck">>, case Record#color.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#color.style},
+      {<<"tabindex">>, Record#color.tabindex},
+      {<<"title">>, Record#color.title},
+      {<<"translate">>, case Record#color.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>,case Record#color.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#color.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#color.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#color.form},
+      {<<"list">>,Record#color.list},      
+      {<<"name">>,Record#color.name},
+      {<<"type">>, <<"color">>},
+      {<<"value">>, Record#color.value} | Record#color.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#color.body), List).

+ 50 - 0
src/elements/input/element_date.erl

@@ -0,0 +1,50 @@
+-module(element_date).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#date.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#date.postback of
+        [] -> Record#date.id;
+        Postback ->
+          ID = case Record#date.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#date.source, delegate=Record#date.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#date.accesskey},
+      {<<"class">>, Record#date.class},
+      {<<"contenteditable">>, case Record#date.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#date.contextmenu},
+      {<<"dir">>, case Record#date.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#date.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#date.dropzone},
+      {<<"hidden">>, case Record#date.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#date.lang},
+      {<<"spellcheck">>, case Record#date.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#date.style},
+      {<<"tabindex">>, Record#date.tabindex},
+      {<<"title">>, Record#date.title},
+      {<<"translate">>, case Record#date.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#date.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#date.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#date.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#date.form},
+      {<<"list">>,Record#date.list},
+      {<<"max">>,Record#date.max},
+      {<<"min">>,Record#date.min},
+      {<<"name">>,Record#date.name},
+      {<<"readonly">>,if Record#date.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#date.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#date.step},
+      {<<"type">>, <<"date">>},
+      {<<"value">>, Record#date.value} | Record#date.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#date.body), List).

+ 50 - 0
src/elements/input/element_datetime.erl

@@ -0,0 +1,50 @@
+-module(element_datetime).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#datetime.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#datetime.postback of
+        [] -> Record#datetime.id;
+        Postback ->
+          ID = case Record#datetime.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#datetime.source, delegate=Record#datetime.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#datetime.accesskey},
+      {<<"class">>, Record#datetime.class},
+      {<<"contenteditable">>, case Record#datetime.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#datetime.contextmenu},
+      {<<"dir">>, case Record#datetime.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#datetime.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#datetime.dropzone},
+      {<<"hidden">>, case Record#datetime.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#datetime.lang},
+      {<<"spellcheck">>, case Record#datetime.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#datetime.style},
+      {<<"tabindex">>, Record#datetime.tabindex},
+      {<<"title">>, Record#datetime.title},
+      {<<"translate">>, case Record#datetime.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#datetime.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#datetime.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#datetime.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#datetime.form},
+      {<<"list">>,Record#datetime.list},
+      {<<"max">>,Record#datetime.max},
+      {<<"min">>,Record#datetime.min},
+      {<<"name">>,Record#datetime.name},
+      {<<"readonly">>,if Record#datetime.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#datetime.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#datetime.step},
+      {<<"type">>, <<"datetime">>},
+      {<<"value">>, Record#datetime.value} | Record#datetime.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#datetime.body), List).

+ 50 - 0
src/elements/input/element_datetime_local.erl

@@ -0,0 +1,50 @@
+-module(element_datetime_local).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#datetime_local.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#datetime_local.postback of
+        [] -> Record#datetime_local.id;
+        Postback ->
+          ID = case Record#datetime_local.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#datetime_local.source, delegate=Record#datetime_local.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#datetime_local.accesskey},
+      {<<"class">>, Record#datetime_local.class},
+      {<<"contenteditable">>, case Record#datetime_local.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#datetime_local.contextmenu},
+      {<<"dir">>, case Record#datetime_local.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#datetime_local.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#datetime_local.dropzone},
+      {<<"hidden">>, case Record#datetime_local.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#datetime_local.lang},
+      {<<"spellcheck">>, case Record#datetime_local.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#datetime_local.style},
+      {<<"tabindex">>, Record#datetime_local.tabindex},
+      {<<"title">>, Record#datetime_local.title},
+      {<<"translate">>, case Record#datetime_local.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#datetime_local.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#datetime_local.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#datetime_local.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#datetime_local.form},
+      {<<"list">>,Record#datetime_local.list},
+      {<<"max">>,Record#datetime_local.max},
+      {<<"min">>,Record#datetime_local.min},
+      {<<"name">>,Record#datetime_local.name},
+      {<<"readonly">>,if Record#datetime_local.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#datetime_local.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#datetime_local.step},
+      {<<"type">>, <<"datetime-local">>},
+      {<<"value">>, Record#datetime_local.value} | Record#datetime_local.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#datetime_local.body), List).

+ 29 - 0
src/elements/input/element_dropdown.erl

@@ -0,0 +1,29 @@
+-module(element_dropdown).
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#dropdown.show_if==false -> [<<>>];
+render_element(Record = #dropdown{}) -> 
+    ID = case Record#dropdown.id of [] -> nitro:temp_id(); I->I end,
+    case Record#dropdown.postback of
+         [] -> skip;
+         Postback -> nitro:wire(#event { type=change, postback=Postback, target=ID,
+                        source=Record#dropdown.source, delegate=Record#dropdown.delegate } ) end,
+
+    Opts = [wf_tags:emit_tag(<<"option">>, [O#option.label], [
+      {<<"disabled">>, O#option.disabled},
+      {<<"label">>, O#option.label},
+      {<<"selected">>, case O#option.selected of true -> <<"selected">>; _-> [] end},
+      {<<"value">>, O#option.value}
+    ])|| O = #option{show_if=Visible} <- Record#dropdown.options, Visible == true],
+
+    wf_tags:emit_tag(<<"select">>, Opts, [
+        {<<"id">>, Record#dropdown.id},
+        {<<"class">>, Record#dropdown.class},
+        {<<"style">>, Record#dropdown.style},
+        {<<"name">>, Record#dropdown.name},
+        {<<"disabled">>, case Record#dropdown.disabled of true -> <<"disabled">>; _-> [] end},
+        {<<"multiple">>, case Record#dropdown.multiple of true -> <<"multiple">>; _-> [] end}|
+        Record#dropdown.data_fields
+    ]).

+ 52 - 0
src/elements/input/element_email.erl

@@ -0,0 +1,52 @@
+-module(element_email).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#email.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#email.postback of
+        [] -> Record#email.id;
+        Postback ->
+          ID = case Record#email.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#email.source, delegate=Record#email.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#email.accesskey},
+      {<<"class">>, Record#email.class},
+      {<<"contenteditable">>, case Record#email.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#email.contextmenu},
+      {<<"dir">>, case Record#email.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#email.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#email.dropzone},
+      {<<"hidden">>, case Record#email.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#email.lang},
+      {<<"spellcheck">>, case Record#email.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#email.style},
+      {<<"tabindex">>, Record#email.tabindex},
+      {<<"title">>, Record#email.title},
+      {<<"translate">>, case Record#email.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#email.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#email.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#email.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#email.form},
+      {<<"list">>,Record#email.list},
+      {<<"maxlength">>,Record#email.maxlength},
+      {<<"multiple">>,if Record#email.multiple == true -> "multiple"; true -> [] end},
+      {<<"name">>,Record#email.name},
+      {<<"pattern">>,Record#email.pattern},
+      {<<"placeholder">>,Record#email.placeholder},
+      {<<"readonly">>,if Record#email.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#email.required == true -> "required"; true -> [] end}, 
+      {<<"size">>,Record#email.size},
+      {<<"type">>, <<"email">>},
+      {<<"value">>, Record#email.value} | Record#email.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#email.body), List).

+ 45 - 0
src/elements/input/element_file.erl

@@ -0,0 +1,45 @@
+-module(element_file).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#file.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#file.postback of
+        [] -> Record#file.id;
+        Postback ->
+          ID = case Record#file.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#file.source, delegate=Record#file.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#file.accesskey},
+      {<<"class">>, Record#file.class},
+      {<<"contenteditable">>, case Record#file.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#file.contextmenu},
+      {<<"dir">>, case Record#file.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#file.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#file.dropzone},
+      {<<"hidden">>, case Record#file.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#file.lang},
+      {<<"spellcheck">>, case Record#file.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#file.style},
+      {<<"tabindex">>, Record#file.tabindex},
+      {<<"title">>, Record#file.title},
+      {<<"translate">>, case Record#file.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"accept">>,Record#file.accept},
+      {<<"autofocus">>,if Record#file.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#file.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#file.form},
+      {<<"multiple">>,if Record#file.multiple == true -> "multiple"; true -> [] end},
+      {<<"name">>,Record#file.name},
+      {<<"required">>,if Record#file.required == true -> "required"; true -> [] end}, 
+      {<<"type">>, <<"file">>} | Record#file.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#file.body), List).

+ 32 - 0
src/elements/input/element_hidden.erl

@@ -0,0 +1,32 @@
+-module(element_hidden).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#hidden.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#hidden.accesskey},
+      {<<"class">>, Record#hidden.class},
+      {<<"contenteditable">>, case Record#hidden.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#hidden.contextmenu},
+      {<<"dir">>, case Record#hidden.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#hidden.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#hidden.dropzone},
+      {<<"hidden">>, case Record#hidden.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#hidden.id},
+      {<<"lang">>, Record#hidden.lang},
+      {<<"spellcheck">>, case Record#hidden.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#hidden.style},
+      {<<"tabindex">>, Record#hidden.tabindex},
+      {<<"title">>, Record#hidden.title},
+      {<<"translate">>, case Record#hidden.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"disabled">>, if Record#hidden.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#hidden.form},
+      {<<"name">>,Record#hidden.name},
+      {<<"type">>, <<"hidden">>},
+      {<<"value">>, Record#hidden.value} | Record#hidden.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#hidden.body), List).

+ 58 - 0
src/elements/input/element_input.erl

@@ -0,0 +1,58 @@
+-module(element_input).
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#input.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#input.postback of
+        [] -> Record#input.id;
+        undefined -> Record#input.id;
+        Postback ->
+          ID = case Record#input.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#input.source, delegate=Record#input.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#input.accesskey},
+      {<<"class">>, Record#input.class},
+      {<<"contenteditable">>, case Record#input.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#input.contextmenu},
+      {<<"dir">>, case Record#input.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#input.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#input.dropzone},
+      {<<"hidden">>, case Record#input.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#input.lang},
+      {<<"spellcheck">>, case Record#input.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#input.style},
+      {<<"tabindex">>, Record#input.tabindex},
+      {<<"title">>, Record#input.title},
+      {<<"translate">>, case Record#input.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},
+      % spec
+      {<<"autocomplete">>,Record#input.autocomplete},
+      {<<"autofocus">>,Record#input.autofocus},
+      {<<"disabled">>, if Record#input.disabled == true -> "disabled"; true -> [] end},
+      {<<"name">>,Record#input.name},
+      {<<"type">>, Record#input.type},
+      {<<"accept">>, Record#input.accept},
+      {<<"max">>, Record#input.max},
+      {<<"checked">>, if Record#input.checked == true -> true; true -> [] end},
+      {<<"aria-states">>, Record#input.aria_states},
+      {<<"placeholder">>,Record#input.placeholder},
+      {<<"min">>, Record#input.min},
+      {<<"multiple">>, Record#input.multiple},
+      {<<"pattern">>, Record#input.pattern},
+      {<<"value">>, Record#input.value},
+      {<<"data-bind">>, case Record#input.bind of [] -> []; X -> base64:encode(term_to_binary(X)) end},
+      {<<"onkeypress">>, Record#input.onkeypress},
+      {<<"onkeyup">>, Record#input.onkeyup},
+      {<<"onkeydown">>, Record#input.onkeydown},
+      {<<"onclick">>, Record#input.onclick},
+      {<<"required">>, if Record#input.required == true -> "required"; true -> [] end},
+      {<<"onchange">>, Record#input.onchange} | Record#input.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#input.body), List).

+ 42 - 0
src/elements/input/element_input_button.erl

@@ -0,0 +1,42 @@
+-module(element_input_button).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#input_button.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#input_button.postback of
+        [] -> Record#input_button.id;
+        Postback ->
+          ID = case Record#input_button.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#input_button.source, delegate=Record#input_button.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#input_button.accesskey},
+      {<<"class">>, Record#input_button.class},
+      {<<"contenteditable">>, case Record#input_button.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#input_button.contextmenu},
+      {<<"dir">>, case Record#input_button.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#input_button.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#input_button.dropzone},
+      {<<"hidden">>, case Record#input_button.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#input_button.lang},
+      {<<"spellcheck">>, case Record#input_button.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#input_button.style},
+      {<<"tabindex">>, Record#input_button.tabindex},
+      {<<"title">>, Record#input_button.title},
+      {<<"translate">>, case Record#input_button.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autofocus">>,Record#input_button.autofocus},
+      {<<"disabled">>, if Record#input_button.disabled == true -> "disabled"; true -> [] end},
+      {<<"name">>,Record#input_button.name},
+      {<<"type">>, <<"button">>},
+      {<<"value">>, Record#input_button.value} | Record#input_button.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#input_button.body), List).

+ 51 - 0
src/elements/input/element_input_image.erl

@@ -0,0 +1,51 @@
+-module(element_input_image).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#input_image.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#input_image.postback of
+        [] -> Record#input_image.id;
+        Postback ->
+          ID = case Record#input_image.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#input_image.source, delegate=Record#input_image.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#input_image.accesskey},
+      {<<"class">>, Record#input_image.class},
+      {<<"contenteditable">>, case Record#input_image.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#input_image.contextmenu},
+      {<<"dir">>, case Record#input_image.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#input_image.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#input_image.dropzone},
+      {<<"hidden">>, case Record#input_image.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#input_image.lang},
+      {<<"spellcheck">>, case Record#input_image.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#input_image.style},
+      {<<"tabindex">>, Record#input_image.tabindex},
+      {<<"title">>, Record#input_image.title},
+      {<<"translate">>, case Record#input_image.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"alt">>,Record#input_image.alt},
+      {<<"autofocus">>,if Record#input_image.autofocus == true -> "autofocus"; true -> [] end},      
+      {<<"disabled">>, if Record#input_image.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#input_image.form},
+      {<<"formaction">>,Record#input_image.formaction},
+      {<<"formenctype">>,Record#input_image.formenctype},
+      {<<"formmethod">>,Record#input_image.formmethod},
+      {<<"formnovalue">>,Record#input_image.formnovalue},
+      {<<"formtarget">>,Record#input_image.formtarget},
+      {<<"height">>,Record#input_image.height},
+      {<<"name">>,Record#input_image.name},
+      {<<"src">>,Record#input_image.src},
+      {<<"type">>, <<"image">>},
+      {<<"width">>,Record#input_image.width} | Record#input_image.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#input_image.body), List).

+ 50 - 0
src/elements/input/element_input_time.erl

@@ -0,0 +1,50 @@
+-module(element_input_time).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#input_time.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#input_time.postback of
+        [] -> Record#input_time.id;
+        Postback ->
+          ID = case Record#input_time.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#input_time.source, delegate=Record#input_time.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#input_time.accesskey},
+      {<<"class">>, Record#input_time.class},
+      {<<"contenteditable">>, case Record#input_time.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#input_time.contextmenu},
+      {<<"dir">>, case Record#input_time.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#input_time.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#input_time.dropzone},
+      {<<"hidden">>, case Record#input_time.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#input_time.lang},
+      {<<"spellcheck">>, case Record#input_time.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#input_time.style},
+      {<<"tabindex">>, Record#input_time.tabindex},
+      {<<"title">>, Record#input_time.title},
+      {<<"translate">>, case Record#input_time.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#input_time.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#input_time.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#input_time.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#input_time.form},
+      {<<"list">>,Record#input_time.list},
+      {<<"max">>,Record#input_time.max},
+      {<<"min">>,Record#input_time.min},
+      {<<"name">>,Record#input_time.name},
+      {<<"readonly">>,if Record#input_time.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#input_time.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#input_time.step},
+      {<<"type">>, <<"time">>},
+      {<<"value">>, Record#input_time.value} | Record#input_time.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#input_time.body), List).

+ 44 - 0
src/elements/input/element_link.erl

@@ -0,0 +1,44 @@
+-module(element_link).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#link.show_if==false -> [<<>>];
+render_element(Record) -> 
+    Id = case Record#link.postback of
+        [] -> Record#link.id;
+        Postback ->
+            ID = case Record#link.id of [] -> nitro:temp_id(); I -> I end,
+            nitro:wire(#event{ type=click,postback=Postback,target=ID,
+                            source=Record#link.source,delegate=Record#link.delegate,validation=Record#link.validate}),
+            ID end,
+    List = [
+      % global
+      {<<"accesskey">>, Record#link.accesskey},
+      {<<"class">>, Record#link.class},
+      {<<"contenteditable">>, case Record#link.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#link.contextmenu},
+      {<<"dir">>, case Record#link.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#link.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#link.dropzone},
+      {<<"hidden">>, case Record#link.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#link.lang},
+      {<<"spellcheck">>, case Record#link.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#link.style},
+      {<<"tabindex">>, Record#link.tabindex},
+      {<<"title">>, Record#link.title},
+      {<<"translate">>, case Record#link.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"href">>, nitro:coalesce([Record#link.href,Record#link.url])},
+      {<<"hreflang">>, Record#link.hreflang},
+      {<<"target">>, Record#link.target},
+      {<<"media">>, Record#link.media},
+      {<<"rel">>, Record#link.rel},
+      {<<"type">>, Record#link.type},
+      {<<"download">>, Record#link.download},
+      {<<"name">>, Record#link.name},
+      {<<"onclick">>, Record#link.onclick},
+      {<<"onmouseover">>, Record#link.onmouseover} | Record#link.data_fields ],
+    wf_tags:emit_tag(<<"a">>, nitro:render(Record#link.body), List).

+ 15 - 0
src/elements/input/element_list.erl

@@ -0,0 +1,15 @@
+-module(element_list).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#list.show_if==false -> [<<>>];
+render_element(Record = #list{}) -> 
+  Tag = case Record#list.numbered of true -> <<"ol">>; _ -> <<"ul">> end,
+
+  wf_tags:emit_tag(Tag, nitro:render(Record#list.body), [
+    {<<"id">>, Record#list.id},
+    {<<"class">>, Record#list.class},
+    {<<"style">>, Record#list.style} | Record#list.data_fields
+  ]).
+

+ 11 - 0
src/elements/input/element_literal.erl

@@ -0,0 +1,11 @@
+-module(element_literal).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#literal.show_if==false -> [<<>>];
+render_element(Record = #literal{}) ->
+	case Record#literal.html_encode of
+		true -> nitro:html_encode(Record#literal.body);
+		_    -> Record#literal.body
+	end.

+ 49 - 0
src/elements/input/element_month.erl

@@ -0,0 +1,49 @@
+-module(element_month).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#month.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#month.postback of
+        [] -> Record#month.id;
+        Postback ->
+          ID = case Record#month.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#month.source, delegate=Record#month.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#month.accesskey},
+      {<<"class">>, Record#month.class},
+      {<<"contenteditable">>, case Record#month.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#month.contextmenu},
+      {<<"dir">>, case Record#month.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#month.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#month.dropzone},
+      {<<"hidden">>, case Record#month.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#month.lang},
+      {<<"spellcheck">>, case Record#month.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#month.style},
+      {<<"tabindex">>, Record#month.tabindex},
+      {<<"title">>, Record#month.title},
+      {<<"translate">>, case Record#month.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"alt">>,Record#month.alt},
+      {<<"autofocus">>,if Record#month.autofocus == true -> "autofocus"; true -> [] end},            
+      {<<"disabled">>, if Record#month.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#month.form},
+      {<<"max">>,Record#month.max},
+      {<<"min">>,Record#month.min},      
+      {<<"name">>,Record#month.name},
+      {<<"readonly">>,if Record#month.readonly == true -> "readonly"; true -> [] end},      
+      {<<"required">>,if Record#month.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#month.step},
+      {<<"type">>, <<"month">>},
+      {<<"value">>, Record#month.value} | Record#month.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#month.body), List).

+ 51 - 0
src/elements/input/element_number.erl

@@ -0,0 +1,51 @@
+-module(element_number).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#number.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#number.postback of
+        [] -> Record#number.id;
+        Postback ->
+          ID = case Record#number.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#number.source, delegate=Record#number.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#number.accesskey},
+      {<<"class">>, Record#number.class},
+      {<<"contenteditable">>, case Record#number.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#number.contextmenu},
+      {<<"dir">>, case Record#number.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#number.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#number.dropzone},
+      {<<"hidden">>, case Record#number.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#number.lang},
+      {<<"spellcheck">>, case Record#number.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#number.style},
+      {<<"tabindex">>, Record#number.tabindex},
+      {<<"title">>, Record#number.title},
+      {<<"translate">>, case Record#number.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec      
+      {<<"autocomplete">>, case Record#number.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#number.autofocus == true -> "autofocus"; true -> [] end},            
+      {<<"disabled">>, if Record#number.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#number.form},
+      {<<"list">>,Record#number.list},
+      {<<"max">>,Record#number.max},
+      {<<"min">>,Record#number.min},      
+      {<<"name">>,Record#number.name},
+      {<<"placeholder">>,Record#number.placeholder},
+      {<<"readonly">>,if Record#number.readonly == true -> "readonly"; true -> [] end},      
+      {<<"required">>,if Record#number.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#number.step},
+      {<<"type">>, <<"number">>},
+      {<<"value">>, Record#number.value} | Record#number.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#number.body), List).

+ 50 - 0
src/elements/input/element_password.erl

@@ -0,0 +1,50 @@
+-module(element_password).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#password.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#password.postback of
+        [] -> Record#password.id;
+        Postback ->
+          ID = case Record#password.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#password.source, delegate=Record#password.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#password.accesskey},
+      {<<"class">>, Record#password.class},
+      {<<"contenteditable">>, case Record#password.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#password.contextmenu},
+      {<<"dir">>, case Record#password.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#password.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#password.dropzone},
+      {<<"hidden">>, case Record#password.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#password.lang},
+      {<<"spellcheck">>, case Record#password.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#password.style},
+      {<<"tabindex">>, Record#password.tabindex},
+      {<<"title">>, Record#password.title},
+      {<<"translate">>, case Record#password.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec 
+      {<<"autocomplete">>, case Record#password.autocomplete of true -> "on"; false -> "off"; _ -> [] end},      
+      {<<"autofocus">>,if Record#password.autofocus == true -> "autofocus"; true -> [] end},            
+      {<<"disabled">>, if Record#password.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#password.form},
+      {<<"maxlength">>,Record#password.maxlength},
+      {<<"name">>,Record#password.name},
+      {<<"pattern">>,Record#password.pattern},
+      {<<"placeholder">>, Record#password.placeholder},
+      {<<"readonly">>,if Record#password.readonly == true -> "readonly"; true -> [] end},      
+      {<<"required">>,if Record#password.required == true -> "required"; true -> [] end},      
+      {<<"size">>,Record#password.size},
+      {<<"type">>, <<"password">>},
+      {<<"value">>, Record#password.value} | Record#password.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#password.body), List).

+ 37 - 0
src/elements/input/element_radio.erl

@@ -0,0 +1,37 @@
+-module(element_radio).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#radio.show_if==false -> [<<>>];
+render_element(Record) ->
+    ID = case Record#radio.id of
+        [] -> nitro:temp_id();
+        RadioID -> RadioID
+    end,
+
+    case Record#radio.postback of
+        [] -> ignore;
+        Postback -> nitro:wire(#event{type=change, postback=Postback, target=ID, delegate=Record#radio.delegate })
+    end,
+
+    Content = nitro:render(Record#radio.body),
+    TypeChecked = case Record#radio.checked of
+         true -> [{<<"checked">>, <<"">>},{<<"type">>, <<"radio">>}];
+            _ -> [{<<"type">>, <<"radio">>}] end ++ case Record#radio.disabled of
+         true -> [{<<"disabled">>, <<"disabled">>}];
+            _ -> [] end,
+
+    [
+        wf_tags:emit_tag(<<"input">>, Content, TypeChecked ++ [
+            {<<"id">>, ID},
+            {<<"value">>, Record#radio.value},
+            {<<"name">>, nitro:coalesce([Record#radio.html_name,Record#radio.name])},
+            {<<"class">>, Record#radio.class},
+            {<<"style">>, Record#radio.style},
+            {<<"onclick">>, Record#radio.onclick},
+            {<<"required">>,if Record#radio.required == true -> "required"; true -> [] end}
+        ])
+
+    ].

+ 24 - 0
src/elements/input/element_radiogroup.erl

@@ -0,0 +1,24 @@
+-module(element_radiogroup).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#radiogroup.show_if==false -> [<<>>];
+render_element(Record) -> 
+    ID = Record#radiogroup.id,
+    Body = apply_name(ID, Record#radiogroup.body),
+    wf_render_elements:render_element(#panel {
+        id=ID,
+        class=[radiogroup, Record#radiogroup.class],
+        style=Record#radiogroup.style,
+        body=Body
+    }).
+
+%% TODO: This whole thing needs to be replaced with a smarter recursive search.
+%% As it is, it won't dive into the bodies of subelements. A recursive map (ie: nitro:map_body) would be
+%% ideal
+
+apply_name(Name, Terms) -> [do_apply(Name, X) || X <- Terms].
+do_apply(Name, X) when is_record(X, radio) -> X#radio {name = Name};
+do_apply(Name, List) when is_list(List) -> apply_name(Name,List);
+do_apply(_Name, X) -> X.

+ 48 - 0
src/elements/input/element_range.erl

@@ -0,0 +1,48 @@
+-module(element_range).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#range.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#range.postback of
+        [] -> Record#range.id;
+        Postback ->
+          ID = case Record#range.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#range.source, delegate=Record#range.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#range.accesskey},
+      {<<"class">>, Record#range.class},
+      {<<"contenteditable">>, case Record#range.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#range.contextmenu},
+      {<<"dir">>, case Record#range.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#range.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#range.dropzone},
+      {<<"hidden">>, case Record#range.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#range.lang},
+      {<<"spellcheck">>, case Record#range.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#range.style},
+      {<<"tabindex">>, Record#range.tabindex},
+      {<<"title">>, Record#range.title},
+      {<<"translate">>, case Record#range.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#range.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#range.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#range.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#range.form},
+      {<<"list">>,Record#range.list},
+      {<<"max">>,Record#range.max},
+      {<<"min">>,Record#range.min},
+      {<<"name">>,Record#range.name},
+      {<<"step">>,Record#range.step},
+      {<<"type">>, <<"range">>},
+      {<<"value">>, Record#range.value} | Record#range.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#range.body), List).

+ 43 - 0
src/elements/input/element_reset.erl

@@ -0,0 +1,43 @@
+-module(element_reset).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#reset.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#reset.postback of
+        [] -> Record#reset.id;
+        Postback ->
+          ID = case Record#reset.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#reset.source, delegate=Record#reset.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#reset.accesskey},
+      {<<"class">>, Record#reset.class},
+      {<<"contenteditable">>, case Record#reset.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#reset.contextmenu},
+      {<<"dir">>, case Record#reset.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#reset.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#reset.dropzone},
+      {<<"hidden">>, case Record#reset.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#reset.lang},
+      {<<"spellcheck">>, case Record#reset.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#reset.style},
+      {<<"tabindex">>, Record#reset.tabindex},
+      {<<"title">>, Record#reset.title},
+      {<<"translate">>, case Record#reset.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autofocus">>,if Record#reset.autofocus == true -> "autofocus"; true -> [] end},            
+      {<<"disabled">>, if Record#reset.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#reset.form},
+      {<<"name">>,Record#reset.name},
+      {<<"type">>, <<"reset">>},
+      {<<"value">>, Record#reset.value} | Record#reset.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#reset.body), List).

+ 52 - 0
src/elements/input/element_search.erl

@@ -0,0 +1,52 @@
+-module(element_search).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#search.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#search.postback of
+        [] -> Record#search.id;
+        Postback ->
+          ID = case Record#search.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#search.source, delegate=Record#search.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#search.accesskey},
+      {<<"class">>, Record#search.class},
+      {<<"contenteditable">>, case Record#search.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#search.contextmenu},
+      {<<"dir">>, case Record#search.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#search.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#search.dropzone},
+      {<<"hidden">>, case Record#search.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#search.lang},
+      {<<"spellcheck">>, case Record#search.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#search.style},
+      {<<"tabindex">>, Record#search.tabindex},
+      {<<"title">>, Record#search.title},
+      {<<"translate">>, case Record#search.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#search.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#search.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"dirname">>,Record#search.dirname},
+      {<<"disabled">>, if Record#search.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#search.form},
+      {<<"list">>,Record#search.list},
+      {<<"maxlength">>,Record#search.maxlength},
+      {<<"name">>,Record#search.name},
+      {<<"pattern">>,Record#search.pattern},
+      {<<"placeholder">>,Record#search.placeholder},
+      {<<"readonly">>,if Record#search.readonly == true -> "readonly"; true -> [] end},      
+      {<<"required">>,if Record#search.required == true -> "required"; true -> [] end},      
+      {<<"size">>,Record#search.size},
+      {<<"type">>, <<"search">>},
+      {<<"value">>, Record#search.value} | Record#search.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#search.body), List).

+ 25 - 0
src/elements/input/element_submit.erl

@@ -0,0 +1,25 @@
+-module (element_submit).
+-author('Andrew Zadorozhny').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#submit.show_if==false -> [<<>>];
+render_element(Record) ->
+    ID = case Record#submit.id of [] -> nitro:temp_id(); I->I end,
+    case Record#submit.postback of
+         [] -> skip;
+         Postback -> nitro:wire(#event { type=click, 
+                                      target=ID,
+                                      postback=Postback,
+                                      source=Record#submit.source }) end,
+    case Record#submit.click of
+         [] -> ignore;
+         ClickActions -> nitro:wire(#event { target=ID, type=click, actions=ClickActions }) end,
+  wf_tags:emit_tag(<<"input">>, [
+      {<<"id">>, ID},
+      {<<"type">>, <<"submit">>},
+      {<<"class">>, Record#submit.class},
+      {<<"style">>, Record#submit.style},
+      {<<"value">>, Record#submit.body}  | Record#submit.data_fields
+  ]).

+ 51 - 0
src/elements/input/element_tel.erl

@@ -0,0 +1,51 @@
+-module(element_tel).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#tel.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#tel.postback of
+        [] -> Record#tel.id;
+        Postback ->
+          ID = case Record#tel.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#tel.source, delegate=Record#tel.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#tel.accesskey},
+      {<<"class">>, Record#tel.class},
+      {<<"contenteditable">>, case Record#tel.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#tel.contextmenu},
+      {<<"dir">>, case Record#tel.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#tel.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#tel.dropzone},
+      {<<"hidden">>, case Record#tel.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#tel.lang},
+      {<<"spellcheck">>, case Record#tel.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#tel.style},
+      {<<"tabindex">>, Record#tel.tabindex},
+      {<<"title">>, Record#tel.title},
+      {<<"translate">>, case Record#tel.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#tel.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#tel.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#tel.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#tel.form},
+      {<<"list">>,Record#tel.list},
+      {<<"maxlength">>,Record#tel.maxlength},
+      {<<"name">>,Record#tel.name},
+      {<<"pattern">>,Record#tel.pattern},
+      {<<"placeholder">>,Record#tel.placeholder},
+      {<<"readonly">>,if Record#tel.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#tel.required == true -> "required"; true -> [] end},      
+      {<<"size">>,Record#tel.size},
+      {<<"type">>, <<"tel">>},
+      {<<"value">>, Record#tel.value} | Record#tel.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#tel.body), List).

+ 23 - 0
src/elements/input/element_textbox.erl

@@ -0,0 +1,23 @@
+-module(element_textbox).
+-author('Rusty Klophaus').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#textbox.show_if==false -> [<<>>];
+render_element(Record) -> 
+    List = [
+      {<<"id">>, Record#textbox.id},
+      {<<"type">>, <<"text">>},
+      {<<"maxlength">>,Record#textbox.maxlength},
+      {<<"style">>,Record#textbox.style},
+      {<<"name">>,Record#textbox.name},
+      {<<"placeholder">>,Record#textbox.placeholder},
+      {<<"value">>, Record#textbox.value},
+      {<<"disabled">>,Record#textbox.disabled},
+      {<<"autofocus">>,Record#textbox.autofocus},
+      {<<"readonly">>,if Record#textbox.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#textbox.required == true -> "required"; true -> [] end}, 
+      {<<"class">>,Record#textbox.class} | Record#textbox.data_fields
+  ] ++ case Record#textbox.disabled of [] -> []; _ -> [{<<"disabled">>,<<"disabled">>}] end,
+  wf_tags:emit_tag(<<"input">>, nitro:render(Record#textbox.body), List).
+

+ 51 - 0
src/elements/input/element_url.erl

@@ -0,0 +1,51 @@
+-module(element_url).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#url.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#url.postback of
+        [] -> Record#url.id;
+        Postback ->
+          ID = case Record#url.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#url.source, delegate=Record#url.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#url.accesskey},
+      {<<"class">>, Record#url.class},
+      {<<"contenteditable">>, case Record#url.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#url.contextmenu},
+      {<<"dir">>, case Record#url.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#url.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#url.dropzone},
+      {<<"hidden">>, case Record#url.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#url.lang},
+      {<<"spellcheck">>, case Record#url.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#url.style},
+      {<<"tabindex">>, Record#url.tabindex},
+      {<<"title">>, Record#url.title},
+      {<<"translate">>, case Record#url.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#url.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#url.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#url.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#url.form},
+      {<<"list">>,Record#url.list},
+      {<<"maxlength">>,Record#url.maxlength},
+      {<<"name">>,Record#url.name},
+      {<<"pattern">>,Record#url.pattern},      
+      {<<"placeholder">>,Record#url.placeholder},      
+      {<<"readonly">>,if Record#url.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#url.required == true -> "required"; true -> [] end},      
+      {<<"size">>,Record#url.size},
+      {<<"type">>, <<"url">>},
+      {<<"value">>, Record#url.value} | Record#url.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#url.body), List).

+ 50 - 0
src/elements/input/element_week.erl

@@ -0,0 +1,50 @@
+-module(element_week).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#week.show_if==false -> [<<>>];
+render_element(Record) ->
+    Id = case Record#week.postback of
+        [] -> Record#week.id;
+        Postback ->
+          ID = case Record#week.id of
+            [] -> nitro:temp_id();
+            I -> I end,
+          nitro:wire(#event{type=click, postback=Postback, target=ID,
+                  source=Record#week.source, delegate=Record#week.delegate }),
+          ID end,
+    List = [
+      %global
+      {<<"accesskey">>, Record#week.accesskey},
+      {<<"class">>, Record#week.class},
+      {<<"contenteditable">>, case Record#week.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#week.contextmenu},
+      {<<"dir">>, case Record#week.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#week.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#week.dropzone},
+      {<<"hidden">>, case Record#week.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Id},
+      {<<"lang">>, Record#week.lang},
+      {<<"spellcheck">>, case Record#week.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#week.style},
+      {<<"tabindex">>, Record#week.tabindex},
+      {<<"title">>, Record#week.title},
+      {<<"translate">>, case Record#week.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"autocomplete">>, case Record#week.autocomplete of true -> "on"; false -> "off"; _ -> [] end},
+      {<<"autofocus">>,if Record#week.autofocus == true -> "autofocus"; true -> [] end},
+      {<<"disabled">>, if Record#week.disabled == true -> "disabled"; true -> [] end},
+      {<<"form">>,Record#week.form},
+      {<<"list">>,Record#week.list},
+      {<<"max">>,Record#week.max},
+      {<<"min">>,Record#week.min},
+      {<<"name">>,Record#week.name},
+      {<<"readonly">>,if Record#week.readonly == true -> "readonly"; true -> [] end},
+      {<<"required">>,if Record#week.required == true -> "required"; true -> [] end},      
+      {<<"step">>,Record#week.step},
+      {<<"type">>, <<"week">>},
+      {<<"value">>, Record#week.value} | Record#week.data_fields
+    ],
+    wf_tags:emit_tag(<<"input">>, nitro:render(Record#week.body), List).

+ 32 - 0
src/elements/interactive/element_command.erl

@@ -0,0 +1,32 @@
+-module(element_command).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#command.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#command.accesskey},
+      {<<"class">>, Record#command.class},
+      {<<"contenteditable">>, case Record#command.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#command.contextmenu},
+      {<<"dir">>, case Record#command.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#command.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#command.dropzone},
+      {<<"hidden">>, case Record#command.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#command.id},
+      {<<"lang">>, Record#command.lang},
+      {<<"spellcheck">>, case Record#command.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#command.style},
+      {<<"tabindex">>, Record#command.tabindex},
+      {<<"title">>, Record#command.title},
+      {<<"translate">>, case Record#command.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},
+      % spec
+      {<<"disabled">>, if Record#command.disabled == true -> "disabled"; true -> [] end},
+      {<<"icon">>, Record#command.icon},
+      {<<"label">>, Record#command.label},
+      {<<"radiogroup">>, Record#command.radiogroup},
+      {<<"type">>, case Record#command.type of "command" -> "command"; "radio" -> "radio"; "checkbox" -> "checkbox"; _ -> [] end} | Record#command.data_fields
+    ],
+    wf_tags:emit_tag(<<"command">>, List).

+ 28 - 0
src/elements/interactive/element_details.erl

@@ -0,0 +1,28 @@
+-module(element_details).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#details.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#details.accesskey},
+      {<<"class">>, Record#details.class},
+      {<<"contenteditable">>, case Record#details.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#details.contextmenu},
+      {<<"dir">>, case Record#details.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#details.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#details.dropzone},
+      {<<"hidden">>, case Record#details.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#details.id},
+      {<<"lang">>, Record#details.lang},
+      {<<"spellcheck">>, case Record#details.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#details.style},
+      {<<"tabindex">>, Record#details.tabindex},
+      {<<"title">>, Record#details.title},
+      {<<"translate">>, case Record#details.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"open">>, case Record#details.open of true -> "open"; _ -> [] end} | Record#details.data_fields
+    ],
+    wf_tags:emit_tag(<<"details">>, nitro:render(case Record#details.body of [] -> []; B -> B end), List).

+ 29 - 0
src/elements/interactive/element_menu.erl

@@ -0,0 +1,29 @@
+-module(element_menu).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#menu.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#menu.accesskey},
+      {<<"class">>, Record#menu.class},
+      {<<"contenteditable">>, case Record#menu.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#menu.contextmenu},
+      {<<"dir">>, case Record#menu.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#menu.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#menu.dropzone},
+      {<<"hidden">>, case Record#menu.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#menu.id},
+      {<<"lang">>, Record#menu.lang},
+      {<<"spellcheck">>, case Record#menu.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#menu.style},
+      {<<"tabindex">>, Record#menu.tabindex},
+      {<<"title">>, Record#menu.title},
+      {<<"translate">>, case Record#menu.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"label">>, Record#menu.label},
+      {<<"type">>, case Record#menu.type of "toolbar" -> "toolbar"; "context" -> "context"; _ -> [] end} | Record#menu.data_fields
+    ],
+    wf_tags:emit_tag(<<"menu">>, nitro:render(case Record#menu.body of [] -> []; B -> B end), List).

+ 26 - 0
src/elements/interactive/element_summary.erl

@@ -0,0 +1,26 @@
+-module(element_summary).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#summary.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#summary.accesskey},
+      {<<"class">>, Record#summary.class},
+      {<<"contenteditable">>, case Record#summary.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#summary.contextmenu},
+      {<<"dir">>, case Record#summary.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#summary.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#summary.dropzone},
+      {<<"hidden">>, case Record#summary.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#summary.id},
+      {<<"lang">>, Record#summary.lang},
+      {<<"spellcheck">>, case Record#summary.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#summary.style},
+      {<<"tabindex">>, Record#summary.tabindex},
+      {<<"title">>, Record#summary.title},
+      {<<"translate">>, case Record#summary.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end} | Record#summary.data_fields
+    ],
+    wf_tags:emit_tag(<<"summary">>, nitro:render(case Record#summary.body of [] -> []; B -> B end), List).

+ 24 - 0
src/elements/interactive/element_upload.erl

@@ -0,0 +1,24 @@
+-module(element_upload).
+-compile(export_all).
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+
+render_element(Record) when Record#upload.show_if==false -> [<<>>];
+render_element(#upload{id=Id}) ->
+    Uid = case Id of [] -> nitro:temp_id(); I -> I end,
+    nitro:wire("ftp_file=undefined;"),
+    bind(ftp_open,  click,  nitro:f("qi('~s').click(); event.preventDefault();", [nitro:to_list(Uid)])),
+    bind(ftp_start, click,  "ftp.start(ftp_file);"),
+    bind(ftp_stop,  click,  "ftp.stop(ftp_file);"),
+    bind(nitro:to_atom(Uid), change, "ftp_file=ftp.init(this.files[0]);"),
+    Upload = #span  { id=upload, body = [
+             #input  { id   = Uid,         type    = <<"file">>, style = "display:none" },
+             #span   { id   = ftp_status,  body    = [] },
+             #span   { body = [
+             #button { id   = ftp_open,    body = "Browse" },
+             #button { id   = ftp_start,   body = "Upload" },
+             #button { id   = ftp_stop,    body = "Stop" }
+    ] } ] }, nitro:render(Upload).
+
+bind(Control,Event,Code) ->
+    nitro:wire(#bind{target=Control,type=Event,postback=Code}).

+ 32 - 0
src/elements/meta/element_meta.erl

@@ -0,0 +1,32 @@
+-module(element_meta).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#meta.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#meta.accesskey},
+      {<<"class">>, Record#meta.class},
+      {<<"contenteditable">>, case Record#meta.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#meta.contextmenu},
+      {<<"dir">>, case Record#meta.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#meta.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#meta.dropzone},
+      {<<"hidden">>, case Record#meta.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#meta.id},
+      {<<"lang">>, Record#meta.lang},
+      {<<"spellcheck">>, case Record#meta.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#meta.style},
+      {<<"tabindex">>, Record#meta.tabindex},
+      {<<"title">>, Record#meta.title},
+      {<<"translate">>, case Record#meta.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"charset">>, Record#meta.charset},
+      {<<"content">>, Record#meta.content},
+      {<<"http_equiv">>, Record#meta.http_equiv},
+      {<<"name">>, Record#meta.name},
+      {<<"type">>, Record#meta.type} | Record#meta.data_fields
+    ],
+    wf_tags:emit_tag(<<"meta">>, List).

+ 29 - 0
src/elements/meta/element_meta_base.erl

@@ -0,0 +1,29 @@
+-module(element_meta_base).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#base.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#base.accesskey},
+      {<<"class">>, Record#base.class},
+      {<<"contenteditable">>, case Record#base.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#base.contextmenu},
+      {<<"dir">>, case Record#base.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#base.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#base.dropzone},
+      {<<"hidden">>, case Record#base.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#base.id},
+      {<<"lang">>, Record#base.lang},
+      {<<"spellcheck">>, case Record#base.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#base.style},
+      {<<"tabindex">>, Record#base.tabindex},
+      {<<"title">>, Record#base.title},
+      {<<"translate">>, case Record#base.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"href">>,Record#base.href},
+      {<<"target">>,Record#base.target} | Record#base.data_fields
+    ],
+    wf_tags:emit_tag(<<"base">>, List).

+ 33 - 0
src/elements/meta/element_meta_link.erl

@@ -0,0 +1,33 @@
+-module(element_meta_link).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#meta_link.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#meta_link.accesskey},
+      {<<"class">>, Record#meta_link.class},
+      {<<"contenteditable">>, case Record#meta_link.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#meta_link.contextmenu},
+      {<<"dir">>, case Record#meta_link.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#meta_link.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#meta_link.dropzone},
+      {<<"hidden">>, case Record#meta_link.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#meta_link.id},
+      {<<"lang">>, Record#meta_link.lang},
+      {<<"spellcheck">>, case Record#meta_link.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#meta_link.style},
+      {<<"tabindex">>, Record#meta_link.tabindex},
+      {<<"title">>, Record#meta_link.title},
+      {<<"translate">>, case Record#meta_link.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"href">>,Record#meta_link.href},
+      {<<"hreflang">>,Record#meta_link.hreflang},
+      {<<"media">>,Record#meta_link.media},
+      {<<"rel">>,Record#meta_link.rel},
+      {<<"sizes">>,Record#meta_link.sizes},
+      {<<"type">>,Record#meta_link.type} | Record#meta_link.data_fields
+    ],
+    wf_tags:emit_tag(<<"link">>, List).

+ 30 - 0
src/elements/meta/element_style.erl

@@ -0,0 +1,30 @@
+-module(element_style).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#style.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#style.accesskey},
+      {<<"class">>, Record#style.class},
+      {<<"contenteditable">>, case Record#style.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#style.contextmenu},
+      {<<"dir">>, case Record#style.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#style.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#style.dropzone},
+      {<<"hidden">>, case Record#style.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#style.id},
+      {<<"lang">>, Record#style.lang},
+      {<<"spellcheck">>, case Record#style.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#style.style},
+      {<<"tabindex">>, Record#style.tabindex},
+      {<<"title">>, Record#style.title},
+      {<<"translate">>, case Record#style.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"media">>, Record#style.media},
+      {<<"scoped">>, case Record#style.scoped of true -> "scoped"; _ -> [] end},      
+      {<<"type">>, Record#style.type} | Record#style.data_fields
+    ],
+    wf_tags:emit_tag(<<"style">>, nitro:render(case Record#style.body of [] -> []; B -> B end), List).

+ 28 - 0
src/elements/table/element_col.erl

@@ -0,0 +1,28 @@
+-module(element_col).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#col.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#col.accesskey},
+      {<<"class">>, Record#col.class},
+      {<<"contenteditable">>, case Record#col.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#col.contextmenu},
+      {<<"dir">>, case Record#col.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#col.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#col.dropzone},
+      {<<"hidden">>, case Record#col.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#col.id},
+      {<<"lang">>, Record#col.lang},
+      {<<"spellcheck">>, case Record#col.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#col.style},
+      {<<"tabindex">>, Record#col.tabindex},
+      {<<"title">>, Record#col.title},
+      {<<"translate">>, case Record#col.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"span">>,Record#col.span} | Record#col.data_fields
+    ],
+    wf_tags:emit_tag(<<"col">>, nitro:render(case Record#col.body of [] -> []; B -> B end), List).

+ 28 - 0
src/elements/table/element_colgroup.erl

@@ -0,0 +1,28 @@
+-module(element_colgroup).
+-author('Vladimir Galunshchikov').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#colgroup.show_if==false -> [<<>>];
+render_element(Record) ->
+    List = [
+      %global
+      {<<"accesskey">>, Record#colgroup.accesskey},
+      {<<"class">>, Record#colgroup.class},
+      {<<"contenteditable">>, case Record#colgroup.contenteditable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"contextmenu">>, Record#colgroup.contextmenu},
+      {<<"dir">>, case Record#colgroup.dir of "ltr" -> "ltr"; "rtl" -> "rtl"; "auto" -> "auto"; _ -> [] end},
+      {<<"draggable">>, case Record#colgroup.draggable of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"dropzone">>, Record#colgroup.dropzone},
+      {<<"hidden">>, case Record#colgroup.hidden of "hidden" -> "hidden"; _ -> [] end},
+      {<<"id">>, Record#colgroup.id},
+      {<<"lang">>, Record#colgroup.lang},
+      {<<"spellcheck">>, case Record#colgroup.spellcheck of true -> "true"; false -> "false"; _ -> [] end},
+      {<<"style">>, Record#colgroup.style},
+      {<<"tabindex">>, Record#colgroup.tabindex},
+      {<<"title">>, Record#colgroup.title},
+      {<<"translate">>, case Record#colgroup.contenteditable of "yes" -> "yes"; "no" -> "no"; _ -> [] end},      
+      % spec
+      {<<"span">>,Record#colgroup.span} | Record#colgroup.data_fields
+    ],
+    wf_tags:emit_tag(<<"colgroup">>, nitro:render(case Record#colgroup.body of [] -> []; B -> B end), List).

+ 34 - 0
src/elements/table/element_table.erl

@@ -0,0 +1,34 @@
+-module(element_table).
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#table.show_if==false -> [<<>>];
+render_element(Record = #table{}) -> 
+  Header = case Record#table.header of
+    [] -> "";
+    H when is_tuple(H) -> H;
+    _ -> wf_tags:emit_tag(<<"thead">>, nitro:render(Record#table.header), [])
+  end,
+  Footer = case Record#table.footer of
+    [] -> "";
+    _ -> wf_tags:emit_tag(<<"tfoot">>, nitro:render(Record#table.footer), [])
+  end,
+  Bodies = case Record#table.body of
+    #tbody{} = B -> B;
+    [] -> #tbody{};
+    unndefined -> #tbody{};
+    Rows -> [case B of #tbody{}=B1 -> B1; _-> #tbody{body=B} end  || B <- Rows]
+  end,
+  Caption = case Record#table.caption of
+    [] -> "";
+    _ -> wf_tags:emit_tag(<<"caption">>, nitro:render(Record#table.caption), [])
+  end,
+  Colgroup = case Record#table.colgroup of
+    [] -> "";
+    _ -> wf_tags:emit_tag(<<"colgroup">>, nitro:render(Record#table.colgroup), [])
+  end,
+  wf_tags:emit_tag( <<"table">>, nitro:render([Caption, Colgroup, Header, Footer, Bodies]), [
+      {<<"id">>, Record#table.id},
+      {<<"class">>, Record#table.class},
+      {<<"style">>, Record#table.style} | Record#table.data_fields
+  ]).

+ 16 - 0
src/elements/table/element_td.erl

@@ -0,0 +1,16 @@
+-module(element_td).
+-include("nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#td.show_if==false -> [<<>>];
+render_element(Record) ->
+  wf_tags:emit_tag(<<"td">>, nitro:render(Record#td.body), [
+    {<<"id">>, Record#td.id},
+    {<<"class">>, Record#td.class},
+    {<<"style">>, Record#td.style},
+    {<<"onclick">>, Record#td.onclick},
+    {<<"rowspan">>, Record#td.rowspan},
+    {<<"bgcolor">>, Record#td.bgcolor},
+    {<<"colspan">>, Record#td.colspan},
+    {<<"scope">>, Record#td.scope} | Record#td.data_fields
+  ]).

+ 14 - 0
src/elements/table/element_th.erl

@@ -0,0 +1,14 @@
+-module(element_th).
+-include("nitro.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#th.show_if==false -> [<<>>];
+render_element(Record) ->
+  wf_tags:emit_tag(<<"th">>, nitro:render(Record#th.body), [
+    {<<"id">>, Record#th.id},
+    {<<"class">>, Record#th.class},
+    {<<"style">>, Record#th.style},
+    {<<"rowspan">>, Record#th.rowspan},
+    {<<"colspan">>, Record#th.colspan},
+    {<<"scope">>, Record#th.scope} | Record#th.data_fields
+  ]).

+ 19 - 0
src/elements/table/element_tr.erl

@@ -0,0 +1,19 @@
+-module(element_tr).
+-include("nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_element(Record) when Record#tr.show_if==false -> [<<>>];
+render_element(Record = #tr{postback= Postback}) ->
+  Id = case Record#tr.id of [] -> nitro:temp_id(); I->I end,
+  Cursor = case Postback of [] -> "";
+    P -> nitro:wire(#event {type=click, postback=P, target=Id, delegate=Record#tr.delegate}), "cursor:pointer;"
+  end,
+  wf_tags:emit_tag(<<"tr">>, nitro:render(Record#tr.cells), [
+    {<<"id">>, Id},
+    {<<"onclick">>, Record#tr.onclick},
+    {<<"class">>, Record#tr.class},
+    {<<"onmouseover">>, Record#tr.onmouseover},
+    {<<"onmouseout">>, Record#tr.onmouseout},
+    {<<"style">>, [Record#tr.style, Cursor]} | Record#tr.data_fields
+  ]).

+ 9 - 0
src/nitro.app.src

@@ -0,0 +1,9 @@
+{application, nitro, [
+    {description,  "NITRO Nitrogen Web Framework"},
+    {vsn,          "6.6.0"},
+    {applications, [kernel, stdlib]},
+    {modules, []},
+    {registered,   []},
+    {mod, { nitro, []}},
+    {env, []}
+]}.

+ 225 - 0
src/nitro.erl

@@ -0,0 +1,225 @@
+-module(nitro).
+-include_lib("nitro/include/cx.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+-behaviour(application).
+-export([start/2, stop/1, init/1]).
+
+atom(List) when is_list(List) -> string:join([ nitro:to_list(L) || L <- List], "_");
+atom(Scalar) -> nitro:to_list(Scalar).
+
+q(Key) -> q(Key, []).
+q(Key, Def) -> case get(Key) of undefined -> Def; Val -> Val end.
+
+qc(Key) -> CX = get(context), qc(Key,CX#cx.req).
+qc(Key,Req) -> proplists:get_value(nitro:to_binary(Key),cowboy_req:parse_qs(Req)).
+
+start(_StartType, _StartArgs) -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+stop(_State) -> ok.
+init([]) -> {ok, {{one_for_one, 5, 10}, []}}.
+
+f(S) -> f(S, []).
+f(S, Args) -> lists:flatten(io_lib:format(S, Args)).
+
+coalesce([]) -> undefined;
+coalesce([H]) -> H;
+coalesce([undefined|T]) -> coalesce(T);
+coalesce([[]|T]) -> coalesce(T);
+coalesce([H|_]) -> H.
+
+jse(X) -> js_escape(X).
+hte(X) when is_binary(X) -> nitro:to_binary(nitro_conv:html_encode(X));
+hte(X) -> nitro_conv:html_encode(X).
+
+js_escape(Value) -> nitro_conv:js_escape(Value).
+
+-define(IS_STRING(Term), (is_list(Term) andalso Term /= [] andalso is_integer(hd(Term)))).
+
+to_list(L) when ?IS_STRING(L) -> L;
+to_list(L) when is_list(L) -> SubLists = [inner_to_list(X) || X <- L], lists:flatten(SubLists);
+to_list(A) -> inner_to_list(A).
+inner_to_list(A) when is_atom(A) -> atom_to_list(A);
+inner_to_list(B) when is_binary(B) -> binary_to_list(B);
+inner_to_list(I) when is_integer(I) -> integer_to_list(I);
+inner_to_list(L) when is_tuple(L) -> lists:flatten(io_lib:format("~p", [L]));
+inner_to_list(L) when is_list(L) -> L;
+inner_to_list(F) when is_float(F) -> float_to_list(F,[{decimals,9},compact]).
+
+to_atom(A) when is_atom(A) -> A;
+to_atom(B) when is_binary(B) -> to_atom(binary_to_list(B));
+to_atom(I) when is_integer(I) -> to_atom(integer_to_list(I));
+to_atom(F) when is_float(F) -> to_atom(float_to_list(F,[{decimals,9},compact]));
+to_atom(L) when is_list(L) -> list_to_atom(binary_to_list(list_to_binary(L))).
+
+to_binary(A) when is_atom(A) -> atom_to_binary(A,latin1);
+to_binary(B) when is_binary(B) -> B;
+to_binary(I) when is_integer(I) -> to_binary(integer_to_list(I));
+to_binary(F) when is_float(F) -> float_to_binary(F,[{decimals,9},compact]);
+to_binary(L) when is_list(L) ->  iolist_to_binary(L);
+to_binary(X) when is_tuple(X) ->  term_to_binary(X).
+
+-ifndef(PICKLER).
+-define(PICKLER, (application:get_env(n2o,pickler,nitro_conv))).
+-endif.
+
+pickle(Data) -> ?PICKLER:pickle(Data).
+depickle(SerializedData) -> ?PICKLER:depickle(SerializedData).
+
+prolongate() -> case application:get_env(n2o,session) of {ok, M} -> M:prolongate(); undefined -> false end.
+authenticate(I, Auth) -> (application:get_env(n2o,session,n2o_session)):authenticate(I, Auth).
+
+render(X) -> wf_render:render(X).
+wire(Actions) -> action_wire:wire(Actions).
+
+unique_integer() -> erlang:unique_integer().
+temp_id() -> "auto" ++ integer_to_list(unique_integer() rem 1000000).
+
+html_encode(L,Fun) when is_function(Fun) -> Fun(L);
+html_encode(L,EncType) when is_atom(L) -> html_encode(nitro:to_list(L),EncType);
+html_encode(L,EncType) when is_integer(L) -> html_encode(integer_to_list(L),EncType);
+html_encode(L,EncType) when is_float(L) -> html_encode(float_to_list(L,[{decimals,9},compact]),EncType);
+html_encode(L, false) -> L;
+html_encode(L, true) -> L;
+html_encode(L, whites) -> html_encode_whites(nitro:to_list(lists:flatten([L]))).
+html_encode(<<>>) -> [];
+html_encode([]) -> [];
+html_encode([H|T]) ->
+    case H of
+        $< -> "&lt;" ++ html_encode(T);
+        $> -> "&gt;" ++ html_encode(T);
+        $" -> "&quot;" ++ html_encode(T);
+        $' -> "&#39;" ++ html_encode(T);
+        $& -> "&amp;" ++ html_encode(T);
+        BigNum when is_integer(BigNum) andalso BigNum > 255 ->
+        [$&,$# | nitro:to_list(BigNum)] ++ ";" ++ html_encode(T);
+        Tup when is_tuple(Tup) -> throw({html_encode,encountered_tuple,Tup});
+        _ -> [H|html_encode(T)]
+    end.
+
+html_encode_whites([]) -> [];
+html_encode_whites([H|T]) ->
+    case H of
+        $\s -> "&nbsp;" ++ html_encode_whites(T);
+        $\t -> "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" ++ html_encode_whites(T);
+        $< -> "&lt;" ++ html_encode_whites(T);
+        $> -> "&gt;" ++ html_encode_whites(T);
+        $" -> "&quot;" ++ html_encode_whites(T);
+        $' -> "&#39;" ++ html_encode_whites(T);
+        $& -> "&amp;" ++ html_encode_whites(T);
+        $\n -> "<br>" ++ html_encode_whites(T);
+        _ -> [H|html_encode_whites(T)]
+    end.
+
+script() -> get(script).
+script(Script) -> put(script,Script).
+
+% Update DOM nitro:update
+
+update(Target, Elements) ->
+    nitro:wire(#jq{target=Target,property=outerHTML,right=Elements,format="`~s`"}).
+
+insert_top(Tag,Target, Elements) ->
+    {Render, _Ref, Actions} = render_html(Elements),
+    nitro:wire(nitro:f(
+        "qi('~s').insertBefore("
+        "(function(){var div = qn('~s'); div.innerHTML = `~s`; return div.firstChild; })(),"
+        "qi('~s').firstChild);",
+        [Target,Tag,Render,Target])),
+    nitro:wire(nitro:render(Actions)).
+
+insert_bottom(Tag, Target, Elements) ->
+    {Render, _Ref, Actions} = render_html(Elements),
+    nitro:wire(nitro:f(
+        "(function(){ var div = qn('~s'); div.innerHTML = `~s`;"
+                     "qi('~s').appendChild(div.firstChild); })();",
+        [Tag,Render,Target])),
+    nitro:wire(nitro:render(Actions)).
+
+insert_before(Target, Elements) -> insert_adjacent(beforebegin, Target, Elements).
+insert_after(Target, Elements) -> insert_adjacent(afterend, Target, Elements).
+
+insert_adjacent(Command, Target, Elements) -> insert_adjacent(Command, Target, Elements, "qi").
+insert_adjacent(Command, Target, Elements, Q) ->
+    {Render, _Ref, Actions} = render_html(Elements),
+    nitro:wire(nitro:f("~s('~s').insertAdjacentHTML('~s', `~s`);",[Q,Target,Command,Render])),
+    nitro:wire(nitro:render(Actions)).
+
+
+render_html(Elements) ->
+    Pid = self(),
+    Ref = make_ref(),
+    spawn(fun() -> R = nitro:render(Elements), Pid ! {R, Ref, nitro:actions()} end),
+    {Render, Ref, Actions} = receive {_, Ref, _} = A -> A end, 
+    {Render, Ref, Actions}.
+    
+
+actions() -> get(actions).
+actions(Ac) -> put(actions,Ac).
+
+insert_top(Target, Elements) when element(1,Elements) == tr -> insert_top(tbody,Target, Elements);
+insert_top(Target, Elements) -> insert_top('div',Target, Elements).
+insert_bottom(Target, Elements) when element(1,Elements) == tr -> insert_bottom(tbody, Target, Elements);
+insert_bottom(Target, Elements) -> insert_bottom('div', Target, Elements).
+
+
+clear(Target) ->
+    nitro:wire("var x = qi('"++nitro:to_list(Target)++"'); while (x && x.firstChild) x.removeChild(x.firstChild);").
+
+remove(Target) ->
+    nitro:wire("var x=qi('"++nitro:to_list(Target)++"'); x && x.parentNode.removeChild(x);").
+
+% Wire JavaScript nitro:wire
+
+state(Key) -> erlang:get(Key).
+state(Key,Value) -> erlang:put(Key,Value).
+
+% Redirect and purge connection nitro:redirect
+
+redirect(Url) -> nitro:wire(#jq{target='window',property=location,args=simple,right=Url}).
+%header(K,V) -> nitro:context((?CTX)#cx{req=cowboy_req:set_resp_header(K,V,?CTX#cx.req)}).
+
+setAttr(Element, Attr, Value) -> 
+    nitro:wire("{ var x = qi('"++ 
+    nitro:to_list(Element)++"'); if (x) x.setAttribute('"++nitro:to_list(Attr)++"', '"++nitro:to_list(Value)++"'); }").
+
+style(Element, Style) -> setAttr(Element, "style", Style).
+style(Element, Style, Value) -> 
+            nitro:wire("{ var x = qi('"++ 
+                nitro:to_list(Element)++"'); if (x) x.style."++nitro:to_list(Style)++" = '"++nitro:to_list(Value)++"'; }").
+
+display(Element,Status) -> style(Element, "display", Status).
+
+show(Element) -> display(Element,block).
+hide(Element) -> display(Element,none).
+
+compact([]) -> "[]";
+compact("\n") -> "[]";
+compact([X|_]=Y) when is_tuple(X) -> [ compact(F) || F <- Y ];
+compact(Tuple) when is_binary(Tuple) -> unicode:characters_to_binary(Tuple);
+compact(Tuple) when is_tuple(Tuple) ->
+     Min = erlang:min(9,size(Tuple)),
+     Fields = lists:zip(lists:seq(1,Min),lists:sublist(tuple_to_list(Tuple),1,Min)),
+     "{" ++ string:join([ io_lib:format("~s",[compact(F)]) || {_,F}<- Fields ],",") ++ "}";
+compact(Tuple) -> nitro:jse(nitro:to_list(Tuple)).
+
+meg(X) -> integer_to_list(X div 1000000) ++ "M".
+rev(X) -> lists:reverse(X).
+num(S) -> case rev(S) of
+               [$K|K] -> list_to_integer(rev(K)) * 1000;
+               [$M|M] -> list_to_integer(rev(M)) * 1000 * 1000;
+               [$G|G] -> list_to_integer(rev(G)) * 1000 * 1000 * 1000;
+               [$T|T] -> list_to_integer(rev(T)) * 1000 * 1000 * 1000 * 1000 end.
+
+cookie_expire(SecondsToLive) ->
+    Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
+    DateTime = calendar:gregorian_seconds_to_datetime(Seconds + SecondsToLive),
+    cow_date:rfc2109(DateTime).
+
+cookie(Id, Value) -> cookie(Id, Value, 2147483647). % expire never
+cookie(Id, Value, Expire) ->
+    Format = "document.cookie='~s=~s; path=/; expires=~s';",
+    nitro:wire(nitro:f(Format,[nitro:to_list(Id),nitro:to_list(Value), cookie_expire(Expire)])).
+
+cookies() -> cowboy_req:parse_cookies((get(context))#cx.req).
+cookie(Key) -> case lists:keyfind(Key, 1, cowboy_req:parse_cookies((get(context))#cx.req)) of false -> undefined; {_, Value} -> Value end.

+ 207 - 0
src/nitro_conv.erl

@@ -0,0 +1,207 @@
+-module(nitro_conv).
+-description('N2O Formatter: JSON, BERT').
+-author('Maxim Sokhatsky').
+-compile(export_all).
+-include_lib("nitro/include/nitro.hrl").
+
+% WF to_atom to_list to_binary
+
+-define(IS_STRING(Term), (is_list(Term) andalso Term /= [] andalso is_integer(hd(Term)))).
+
+-ifndef(N2O_JSON).
+-define(N2O_JSON, (application:get_env(n2o,json,jsone))).
+-endif.
+
+
+to_list(L) when ?IS_STRING(L) -> L;
+to_list(L) when is_list(L) -> SubLists = [inner_to_list(X) || X <- L], lists:flatten(SubLists);
+to_list(A) -> inner_to_list(A).
+inner_to_list(A) when is_atom(A) -> atom_to_list(A);
+inner_to_list(B) when is_binary(B) -> binary_to_list(B);
+inner_to_list(I) when is_integer(I) -> integer_to_list(I);
+inner_to_list(L) when is_tuple(L) -> lists:flatten(io_lib:format("~p", [L]));
+inner_to_list(L) when is_list(L) -> L;
+inner_to_list(F) when is_float(F) -> float_to_list(F,[{decimals,9},compact]).
+
+to_atom(A) when is_atom(A) -> A;
+to_atom(B) when is_binary(B) -> to_atom(binary_to_list(B));
+to_atom(I) when is_integer(I) -> to_atom(integer_to_list(I));
+to_atom(F) when is_float(F) -> to_atom(float_to_list(F,[{decimals,9},compact]));
+to_atom(L) when is_list(L) -> list_to_atom(binary_to_list(list_to_binary(L))).
+
+to_binary(A) when is_atom(A) -> atom_to_binary(A,latin1);
+to_binary(B) when is_binary(B) -> B;
+to_binary(T) when is_tuple(T) -> term_to_binary(T);
+to_binary(I) when is_integer(I) -> to_binary(integer_to_list(I));
+to_binary(F) when is_float(F) -> float_to_binary(F,[{decimals,9},compact]);
+to_binary(L) when is_list(L) ->  iolist_to_binary(L).
+
+to_integer(A) when is_atom(A) -> to_integer(atom_to_list(A));
+to_integer(B) when is_binary(B) -> to_integer(binary_to_list(B));
+to_integer(I) when is_integer(I) -> I;
+to_integer([]) -> 0;
+to_integer(L) when is_list(L) -> list_to_integer(L);
+to_integer(F) when is_float(F) -> round(F).
+
+% HTML encode/decode
+
+html_encode(L,Fun) when is_function(Fun) -> Fun(L);
+html_encode(L,EncType) when is_atom(L) -> html_encode(nitro:to_list(L),EncType);
+html_encode(L,EncType) when is_integer(L) -> html_encode(integer_to_list(L),EncType);
+html_encode(L,EncType) when is_float(L) -> html_encode(float_to_list(L,[{decimals,9},compact]),EncType);
+html_encode(L, false) -> L;
+html_encode(L, true) -> L;
+html_encode(L, whites) -> html_encode_whites(nitro:to_list(lists:flatten([L]))).
+html_encode(<<>>) -> <<>>;
+html_encode([]) -> [];
+html_encode(B) when is_binary(B) -> html_encode(binary_to_list(B));
+html_encode([$\n|T]) -> "<br>" ++ html_encode(T);
+html_encode([H|T]) ->
+	case H of
+		$< -> "&lt;" ++ html_encode(T);
+		$> -> "&gt;" ++ html_encode(T);
+		$" -> "&quot;" ++ html_encode(T);
+		$` -> "&#39;" ++ html_encode(T);
+		$' -> "&#39;" ++ html_encode(T);
+		$& -> "&amp;" ++ html_encode(T);
+		BigNum when is_integer(BigNum) andalso BigNum > 255 ->
+			%% Any integers above 255 are converted to their HTML encode equivilant,
+			%% Example: 7534 gets turned into &#7534;
+			[$&,$# | nitro:to_list(BigNum)] ++ ";" ++ html_encode(T);
+		Tup when is_tuple(Tup) -> 
+			throw({html_encode,encountered_tuple,Tup});
+		_ -> [H|html_encode(T)]
+	end.
+
+html_encode_whites([]) -> [];
+html_encode_whites([H|T]) ->
+	case H of
+		$\s -> "&nbsp;" ++ html_encode_whites(T);
+		$\t -> "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" ++ html_encode_whites(T);
+		$< -> "&lt;" ++ html_encode_whites(T);
+		$> -> "&gt;" ++ html_encode_whites(T);
+		$" -> "&quot;" ++ html_encode_whites(T);
+		$' -> "&#39;" ++ html_encode_whites(T);
+		$` -> "&#39;" ++ html_encode_whites(T);
+		$& -> "&amp;" ++ html_encode_whites(T);
+		$\n -> "<br>" ++ html_encode_whites(T);
+		_ -> [H|html_encode_whites(T)]
+	end.
+
+%% URL encode/decode
+
+url_encode(S) -> quote_plus(S).
+url_decode(S) -> unquote(S).
+
+-define(PERCENT, 37).  % $\%
+-define(FULLSTOP, 46). % $\.
+-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
+    (C >= $a andalso C =< $f) orelse
+    (C >= $A andalso C =< $F))).
+-define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse
+    (C >= $A andalso C =< $Z) orelse
+    (C >= $0 andalso C =< $9) orelse
+    (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse
+        C =:= $_))).
+
+quote_plus(Atom) when is_atom(Atom) -> quote_plus(atom_to_list(Atom));
+quote_plus(Int) when is_integer(Int) -> quote_plus(integer_to_list(Int));
+quote_plus(Bin) when is_binary(Bin) -> quote_plus(binary_to_list(Bin));
+quote_plus(String) -> quote_plus(String, []).
+
+quote_plus([], Acc) -> lists:reverse(Acc);
+quote_plus([C | Rest], Acc) when ?QS_SAFE(C) -> quote_plus(Rest, [C | Acc]);
+quote_plus([$\s | Rest], Acc) -> quote_plus(Rest, [$+ | Acc]);
+quote_plus([C | Rest], Acc) -> <<Hi:4, Lo:4>> = <<C>>, quote_plus(Rest, [digit(Lo), digit(Hi), ?PERCENT | Acc]).
+
+unquote(Binary) when is_binary(Binary) -> unquote(binary_to_list(Binary));
+unquote(String) -> qs_revdecode(lists:reverse(String)).
+
+unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
+unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
+unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.
+
+qs_revdecode(S) -> qs_revdecode(S, []).
+qs_revdecode([], Acc) -> Acc;
+qs_revdecode([$+ | Rest], Acc) -> qs_revdecode(Rest, [$\s | Acc]);
+qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) -> qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
+qs_revdecode([C | Rest], Acc) -> qs_revdecode(Rest, [C | Acc]).
+
+%% JavaScript encode/decode
+
+js_escape(undefined) -> [];
+js_escape(Value) when is_list(Value) -> binary_to_list(js_escape(iolist_to_binary(Value)));
+js_escape(Value) -> js_escape(Value, <<>>).
+js_escape(<<"\\", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\\\">>);
+js_escape(<<"\r", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\r">>);
+js_escape(<<"\n", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\n">>);
+js_escape(<<"\"", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\\"">>);
+js_escape(<<"'",Rest/binary>>,Acc) -> js_escape(Rest, <<Acc/binary, "\\'">>);
+js_escape(<<"`",Rest/binary>>,Acc) -> js_escape(Rest, <<Acc/binary, "\\`">>);
+js_escape(<<"<script", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "<script">>);
+js_escape(<<"script>", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "script>">>);
+js_escape(<<C, Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, C>>);
+js_escape(<<>>, Acc) -> Acc.
+
+% JOIN
+
+join([],_) -> [];
+join([Item],_Delim) -> [Item];
+join([Item|Items],Delim) -> [Item,Delim | join(Items,Delim)].
+
+% Fast HEX
+
+digit(0) -> $0;
+digit(1) -> $1;
+digit(2) -> $2;
+digit(3) -> $3;
+digit(4) -> $4;
+digit(5) -> $5;
+digit(6) -> $6;
+digit(7) -> $7;
+digit(8) -> $8;
+digit(9) -> $9;
+digit(10) -> $a;
+digit(11) -> $b;
+digit(12) -> $c;
+digit(13) -> $d;
+digit(14) -> $e;
+digit(15) -> $f.
+
+hex(Bin) -> << << (digit(A1)),(digit(A2)) >> || <<A1:4,A2:4>> <= Bin >>.
+unhex(Hex) -> << << (erlang:list_to_integer([H1,H2], 16)) >> || <<H1,H2>> <= Hex >>.
+
+f(S) -> f(S, []).
+f(S, Args) -> lists:flatten(io_lib:format(S, Args)).
+
+replace([], _, _) -> [];
+replace(String, S1, S2) when is_list(String), is_list(S1), is_list(S2) ->
+    Length = length(S1),
+    case string:substr(String, 1, Length) of
+        S1 -> S2 ++ replace(string:substr(String, Length + 1), S1, S2);
+        _ -> [hd(String)|replace(tl(String), S1, S2)]
+    end.
+
+coalesce({_,Y}) -> Y;
+coalesce(false) -> undefined;
+coalesce([]) -> undefined;
+coalesce([H]) -> H;
+coalesce([undefined|T]) -> coalesce(T);
+coalesce([[]|T]) -> coalesce(T);
+coalesce([H|_]) -> H.
+
+indexof(Key, Fields) -> indexof(Key, Fields, 2).
+indexof(_Key, [], _N) -> undefined;
+indexof(Key, [Key|_T], N) -> N;
+indexof(Key, [_|T], N) -> indexof(Key, T, N + 1).
+
+config(App, Key, Default) -> application:get_env(App,Key,Default).
+
+os_env(Key) -> os_env(Key, "").
+os_env(Key, Default) -> case os:getenv(Key) of false -> Default; V -> V end.
+
+% base64 encode/decode
+pickle(Data) -> base64:encode(term_to_binary({Data, os:timestamp()}, [compressed])).
+depickle(PickledData) ->
+    try {Data, _PickleTime} = binary_to_term(base64:decode(nitro:to_binary(PickledData))), Data
+    catch _:_ -> undefined end.

+ 104 - 0
src/nitro_n2o.erl

@@ -0,0 +1,104 @@
+-module(nitro_n2o).
+-description('N2O Nitrogen Web Framework Protocol').
+-include_lib("nitro/include/n2o.hrl").
+-export([info/3,render_actions/1,io/1,io/2,event/1]).
+
+% Nitrogen pickle handler
+
+info({text,<<"N2O,",Auth/binary>>}, Req, State) ->
+    info(#init{token=Auth},Req,State);
+
+info(#init{token=Auth}, Req, State) ->
+    {'Token', Token} = nitro:authenticate([], Auth),
+    Sid = case nitro:depickle(Token) of {{S,_},_} -> S; X -> X end,
+    New = State#cx{session = Sid, token = Auth},
+    put(context,New),
+    {reply,{bert,case io(init, State) of
+                      {io,_,{stack,_}} = Io -> Io;
+                      {io,Code,_} -> {io,Code,{'Token',Token}} end},
+            Req,New};
+
+info(#client{data=Message}, Req, State) ->
+    nitro:actions([]),
+    {reply,{bert,io(#client{data=Message},State)},Req,State};
+
+info(#pickle{}=Event, Req, State) ->
+    nitro:actions([]),
+    {reply,{bert,html_events(Event,State)},Req,State};
+
+info(#flush{data=Actions}, Req, State) ->
+    nitro:actions(Actions),
+    {reply,{bert,io(<<>>)},Req,State};
+
+info(#direct{data=Message}, Req, State) ->
+    nitro:actions([]),
+    {reply,{bert,case io(Message, State) of
+                      {io,_,{stack,_}} = Io -> Io;
+                      {io,Code,Res} -> {io,Code,{direct,Res}} end},
+            Req,State};
+
+info(Message,Req,State) -> {unknown,Message,Req,State}.
+
+% double render: actions could generate actions
+
+render_actions(Actions) ->
+    nitro:actions([]),
+    First  = nitro:render(Actions),
+    Second = nitro:render(nitro:actions()),
+    nitro:actions([]),
+    nitro:to_binary([First,Second]).
+
+% n2o events
+
+html_events(#pickle{source=Source,pickled=Pickled,args=Linked}, State=#cx{token = Token}) ->
+    Ev  = nitro:depickle(Pickled),
+    L   = nitro:prolongate(),
+    Res = case Ev of
+          #ev{} when L =:= false -> render_ev(Ev,Source,Linked,State), <<>>;
+          #ev{} -> render_ev(Ev,Source,Linked,State), nitro:authenticate([], Token);
+          _CustomEnvelop -> %?LOG_ERROR("EV expected: ~p~n",[CustomEnvelop]),
+                           {error,"EV expected"} end,
+    io(Res).
+
+% calling user code in exception-safe manner
+
+-ifdef(OTP_RELEASE).
+
+render_ev(#ev{module=M,name=F,msg=P,trigger=T},_Source,Linked,State) ->
+    try case F of
+         api_event -> M:F(P,Linked,State);
+             event -> [erlang:put(K,V) || {K,V} <- Linked], M:F(P);
+                 _ -> M:F(P,T,State) end
+    catch E:R:S -> ?LOG_EXCEPTION(E,R,S), {stack,S} end.
+
+io(Event, #cx{module=Module}) ->
+    try X = Module:event(Event), {io,render_actions(nitro:actions()),X}
+    catch E:R:S -> ?LOG_EXCEPTION(E,R,S), {io,[],{stack,S}} end.
+
+io(Data) ->
+    try {io,render_actions(nitro:actions()),Data}
+    catch E:R:S -> ?LOG_EXCEPTION(E,R,S), {io,[],{stack,S}} end.
+
+-else.
+
+render_ev(#ev{module=M,name=F,msg=P,trigger=T},_Source,Linked,State) ->
+    try case F of
+         api_event -> M:F(P,Linked,State);
+             event -> [erlang:put(K,V) || {K,V} <- Linked], M:F(P);
+                 _ -> M:F(P,T,State) end
+    catch E:R -> S = erlang:get_stacktrace(), ?LOG_EXCEPTION(E,R,S), {stack,S} end.
+
+io(Event, #cx{module=Module}) ->
+    try X = Module:event(Event), {io,render_actions(nitro:actions()),X}
+    catch E:R -> S = erlang:get_stacktrace(), ?LOG_EXCEPTION(E,R,S), {io,<<>>,{stack,S}} end.
+
+io(Data) ->
+    try {io,render_actions(nitro:actions()),Data}
+    catch E:R -> S = erlang:get_stacktrace(), ?LOG_EXCEPTION(E,R,S), {io,<<>>,{stack,S}} end.
+
+-endif.
+
+% event Nitrogen Web Framework protocol
+
+event(_) -> [].
+

+ 68 - 0
src/nitro_static.erl

@@ -0,0 +1,68 @@
+-module(nitro_static).
+-description('NITRO Static bridge for MAD containers').
+-author('Maxim Sokhatsky').
+-compile(export_all).
+-include_lib("kernel/include/file.hrl").
+
+init(_, _, _) -> {upgrade, protocol, cowboy_rest}.
+
+rest_init(Req, {dir, Path, Extra}) when is_binary(Path) -> rest_init(Req, {dir, binary_to_list(Path), Extra});
+rest_init(Req, {dir, Path, Extra}) ->
+    {PathInfo, Req2} = cowboy_req:path_info(Req),
+    Info = {ok, #file_info{type=regular,size=0}},
+    FileName = filename:join([Path|PathInfo]),
+    {ok, Req2, {FileName, Info, Extra}}.
+
+malformed_request(Req, State) -> {State =:= error, Req, State}.
+
+forbidden(Req, State={_, {ok, #file_info{type=directory}}, _}) -> {true, Req, State};
+forbidden(Req, State={_, {error, eacces}, _}) -> {true, Req, State};
+forbidden(Req, State={_, {ok, #file_info{access=Access}}, _}) when Access =:= write; Access =:= none -> {true, Req, State};
+forbidden(Req, State) -> {false, Req, State}.
+
+
+
+fixpath(Path) -> {Module,Fun} = application:get_env(n2o,fixpath,{nitro_static,fix}),
+                 Module:Fun(Path).
+
+fix(A) -> A.
+
+content_types_provided(Req, State={Path, _, Extra}) ->
+    case lists:keyfind(mimetypes, 1, Extra) of
+         false -> {[{cow_mimetypes:web(Path), get_file}], Req, State};
+         {mimetypes, Module, Function} ->
+            {[{Module:Function(fixpath(Path)), get_file}], Req, State};
+         {mimetypes, Type} -> {[{Type, get_file}], Req, State}
+    end.
+
+resource_exists(Req, State={_, {ok, #file_info{type=regular}}, _}) -> {true, Req, State};
+resource_exists(Req, State) -> {false, Req, State}.
+
+generate_etag(Req, State={Path, {ok, #file_info{size=Size, mtime=Mtime}}, Extra}) ->
+    case lists:keyfind(etag, 1, Extra) of
+         false -> {generate_default_etag(Size, Mtime), Req, State};
+         {etag, Module, Function} -> {Module:Function(Path, Size, Mtime), Req, State};
+         {etag, false} -> {undefined, Req, State}
+    end.
+
+generate_default_etag(Size, Mtime) ->
+    {strong, list_to_binary(integer_to_list(erlang:phash2({Size, Mtime}, 16#ffffffff)))}.
+
+last_modified(Req, State={_, {ok, #file_info{mtime=Modified}}, _}) -> {Modified, Req, State}.
+
+get_file(Req, State={P, {ok, #file_info{size=_Size}}, _}) ->
+    Path = fixpath(P),
+    StringPath = nitro:to_list(unicode:characters_to_binary(Path,utf8,utf8)),
+    [_Type,Name|RestPath] = filename:split(StringPath),
+    FileName = filename:absname(StringPath),
+    Raw = case file:read_file(FileName) of
+          {ok,Bin} -> Bin;
+          {error,_} ->
+              case mad_repl:load_file(StringPath) of
+                   {ok,ETSFile} -> ETSFile;
+                   {error,_} ->
+                        case file:read_file(filename:join([ code:lib_dir(list_to_atom(Name))|RestPath])) of
+                             {ok,ReleaseFile} -> ReleaseFile;
+                             {error,_} -> <<>> end end end,
+    Sendfile = fun (Socket, Transport) -> Transport:send(Socket, Raw) end,
+    {{stream, size(Raw), Sendfile}, Req, State}.

+ 36 - 0
src/render/wf_event.erl

@@ -0,0 +1,36 @@
+-module(wf_event).
+-author('Maxim Sokhatsky').
+-author('Andrey Martemyanov').
+-include_lib("nitro/include/cx.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+-record(ev,      { module, msg, trigger, name }).
+
+-define(B(E), nitro:to_binary(E)).
+-define(L(E), nitro:to_list(E)).
+target({ps, {qa, Id}, Ps}) ->
+  T = target({qa,Id}), P = nitro:js_escape(Ps),
+  ["var t=",T,";t.map(ts => '",P,"'.split('.').reduce((a,p)=>(a&&a[p]?a[p]:null),ts)).filter(o=>o)"];
+target({ps,Id,Ps}) ->
+  T = target(Id), P = nitro:js_escape(Ps),
+  ["var ts=",T,",ps = '",P,"'.split('.').reduce((a,p)=>(a&&a[p]?a[p]:null),ts);","ps&&ps"];
+target({qs,S}) -> ["qs('",nitro:js_escape(?L(S)), "')"];
+target({qa,S}) -> ["Array.from(qa('",nitro:js_escape(?L(S)), "'))"];
+target({g,T})  -> nitro:js_escape(?L(T));
+target(Id)     -> ["qi('",nitro:js_escape(?L(Id)),"')"].
+
+new(P,E,D,N,Data,Source) -> new(P,E,D,N,Data,Source,<<>>).
+
+new(bin,Data) -> <<"ws.send(enc(tuple(atom('bin'),bin('",(nitro:pickle(Data))/binary,"'))));">>.
+new([], _, _, _, _, _, _) -> <<>>;
+new(undefined, _, _, _, _, _, _) -> <<>>;
+new(Postback, Element, Delegate, Name, Data, Source, Validation) ->
+    Module = nitro:coalesce([Delegate, ?CTX#cx.module]),
+    Join=fun([]) -> [];
+           ([E]) -> [$'|E]++[$'];
+         ([H|T]) -> [[$'|H]++[$']] ++ [ [$,,$'|E]++[$'] || E <- T ] end,
+    Event = #ev{name=Name, module=Module, msg=Postback, trigger=Element},
+    list_to_binary(["{ if (validateSources([",Join([ nitro:to_list(S) || S <- Source, S =/= []]),
+        "])) { ",?B(Validation)," ws.send(enc(tuple(atom('",?B(application:get_env(n2o,event,pickle)),"'),bin('",
+        Element,"'),bin('",nitro:pickle(Event),"'),",Data,"))); } else console.log('Validation Error'); }"]).

+ 15 - 0
src/render/wf_render.erl

@@ -0,0 +1,15 @@
+-module(wf_render).
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+
+render_item([]) -> <<>>;
+render_item(undefined) -> <<>>;
+render_item(E) when element(2,E) =:= element -> wf_render_elements:render_element(E);
+render_item(E) when element(2,E) =:= action  -> wf_render_actions:render_action(E);
+render_item(E) -> E.
+
+render([]) -> <<>>;
+render(undefined) -> <<>>;
+render(<<E/binary>>) -> E;
+render(Elements) when is_list(Elements) -> [ render_item(E) || E <- Elements, E /= undefined ];
+render(Elements) -> render_item(Elements).

+ 15 - 0
src/render/wf_render_actions.erl

@@ -0,0 +1,15 @@
+-module(wf_render_actions).
+-author('Andrew Zadorozhny').
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+-compile(export_all).
+
+render_action(Action) ->
+    Module = element(#action.module,Action),
+    Res = Module:render_action(Action),
+    case Res of
+         Res when is_tuple(Res) -> render_action(Res);
+         Bin when is_binary(Bin) -> Bin;
+         Str when is_list(Str) -> Str;
+         _ -> [] end.
+

+ 76 - 0
src/render/wf_render_elements.erl

@@ -0,0 +1,76 @@
+-module (wf_render_elements).
+-author('Maxim Sokhatsky').
+-include_lib ("nitro/include/nitro.hrl").
+-include_lib ("nitro/include/comboLookupEdit.hrl").
+-compile(export_all).
+
+render_element(E) when is_list(E) -> E;
+render_element(Element) when is_tuple(Element) ->
+    Id = case element(#element.id,Element) of
+        [] -> [];
+        undefined -> undefined;
+        L when is_list(L) -> L;
+        Other -> nitro:to_list(Other) end,
+    case element(#element.actions,Element) of [] -> skip; Actions -> nitro:wire(Actions) end,
+    Tag = case element(#element.html_tag,Element) of [] -> nitro:to_binary(element(1, Element)); T -> T end,
+    case element(1, Element) of
+      comboLookupEdit ->
+        lists:map(fun (El) ->
+          Input =
+            case element(#element.body, El) of
+              Body when not (Body == []) and is_list(Body) ->
+                case element(#element.body, hd(lists:flatten(tl(Body)))) of
+                  X when is_tuple(X)-> X;
+                  _ -> El
+                end;
+              _ -> El
+            end,
+          InputId = element(#element.id, Input),
+          case element(#element.validation, Input) of
+            [] -> skip;
+            InputCode ->
+              nitro:wire(nitro:f("{var name='~s'; qi(name) && qi(name).addEventListener('validation',"
+                                "function(e) { if (!(~s)) e.preventDefault(); });"
+                                "if(qi(name)) qi(name).validation = true;}",[InputId,InputCode]))
+          end
+        end, lists:flatten(tl(element(#element.body, element(#comboLookupEdit.form, Element)))));
+      _ ->
+        case element(#element.validation,Element) of
+         [] -> skip;
+         Code ->
+         nitro:wire(nitro:f("{var name='~s'; qi(name) && qi(name).addEventListener('validation',"
+                                "function(e) { if (!(~s)) e.preventDefault(); });"
+                                "if(qi(name)) qi(name).validation = true;}",[Id,Code])) end
+    end,
+    case element(#element.module,Element) of
+        [] -> default_render(Tag, Element);
+        undefined -> default_render(Tag, Element);
+        Module -> nitro:to_binary(Module:render_element(setelement(#element.id,Element,Id))) end;
+render_element(Element) -> io:format("Unknown Element: ~p~n\r",[Element]).
+
+default_render(Tag, Record) ->
+    wf_tags:emit_tag(Tag, nitro:render(lists:flatten([element(#element.body,Record)])),
+        lists:append([
+           [{<<"id">>,              element(#element.id, Record)},
+            {<<"data-bind">>,       element(#element.bind, Record)},
+            {<<"class">>,           element(#element.class, Record)},
+            {<<"style">>,           element(#element.style, Record)},
+            {<<"title">>,           element(#element.title, Record)},
+            {<<"accesskey">>,       element(#element.accesskey, Record)},
+            {<<"contenteditable">>, element(#element.contenteditable, Record)},
+            {<<"contextmenu">>,     element(#element.contextmenu, Record)},
+            {<<"dir">>,             element(#element.dir, Record)},
+            {<<"draggable">>,       element(#element.draggable, Record)},
+            {<<"dropzone">>,        element(#element.dropzone, Record)},
+            {<<"hidden">>,          element(#element.hidden, Record)},
+            {<<"lang">>,            element(#element.lang, Record)},
+            {<<"onclick">>,         element(#element.onclick, Record)},
+            {<<"spellcheck">>,      element(#element.spellcheck, Record)},
+            {<<"translate">>,       element(#element.translate, Record)},
+            {<<"tabindex">>,        element(#element.tabindex, Record)},
+            {<<"onmouseout">>,      element(#element.onmouseout, Record)},
+            {<<"onmouseover">>,     element(#element.onmouseover, Record)},
+            {<<"onmousemove">>,     element(#element.onmousemove, Record)},
+            {<<"role">>,            element(#element.role, Record)}],
+        element(#element.data_fields, Record),
+        element(#element.aria_states, Record)])).

+ 29 - 0
src/render/wf_tags.erl

@@ -0,0 +1,29 @@
+-module(wf_tags).
+-author('Maxim Sokhatsky').
+-include_lib("nitro/include/nitro.hrl").
+-compile(export_all).
+-define(VOID(Tag),  (Tag == <<"br">>     orelse Tag == <<"hr">>
+              orelse Tag == <<"link">>   orelse Tag == <<"img">>
+              orelse Tag == <<"input">>  orelse Tag == <<"link">>
+              orelse Tag == <<"meta">>   orelse Tag == <<"param">>
+              orelse Tag == <<"base">>   orelse Tag == <<"area">>
+              orelse Tag == <<"col">>    orelse Tag == <<"command">> orelse Tag == <<"option">>
+              orelse Tag == <<"keygen">> orelse Tag == <<"source">>)).
+
+emit_tag(TagName, Props) -> [<<"<">>,TagName] ++ write_props(Props) ++ [<<"/>">>].
+emit_tag(TagName, undefined, Props) -> emit_tag(TagName, [], Props);
+emit_tag(TagName, [[]], Props) -> emit_tag(TagName, [], Props);
+emit_tag(TagName, [undefined], Props) -> emit_tag(TagName, [], Props);
+emit_tag(TagName, <<>>, Props) when ?VOID(TagName) -> emit_tag(TagName, Props);
+emit_tag(TagName, [], Props) -> [<<"<">>,TagName,write_props(Props),<<">">>,<<"</">>,TagName,<<">">>];
+emit_tag(TagName, Content, Props) -> [<<"<">>,TagName,write_props(Props),<<">">>, Content,<<"</">>,TagName,<<">">>].
+write_props(Props) -> lists:map(fun display_property/1, Props).
+display_property({_, undefined}) -> [];
+display_property({_, []}) -> [];
+display_property({<<"class">>=Id, Value}) -> prop({Id,Value});
+display_property({<<"data-toggle">>=Id, Value}) -> prop({Id,Value});
+display_property({Prop, Value}) -> [<<" ">>, nitro:to_binary(Prop), <<"=\"">>, nitro:to_binary(Value), <<"\"">>].
+
+prop({Id, Value}) when is_atom(Value) -> [<<" ">>,Id,<<"=\"">>, nitro:to_binary(Value), <<"\"">>];
+prop({Id, Value}) when is_binary(Value) -> [<<" ">>,Id,<<"=\"">>, Value, <<"\"">>];
+prop({Id, Value}) -> [<<" ">>,Id,<<"=\"">>, string:join([nitro:to_list(V) || V <-Value ]," "), <<"\"">>].