% vim: sw=4 ts=4 et ft=erlang -ifndef(wf_inc). -define(wf_inc, ok). -include("compat.hrl"). -include("wf_test.hrl"). -compile(nowarn_export_all). %% This is the parse_transform to allow extending fields -compile({parse_transform, rekt}). -define(WF_EXTEND(OrigRec, NewRec, Module, Fields), -extend({OrigRec, NewRec, [{module, Module} | Fields]})). %%% TYPES FOR DIALYZER %%% %% Allow Dialzer to be run on the .ebin files -compile(debug_info). -type nitrogen_element() :: tuple(). -type template_script_element() :: script | mobile_script. -type body_element() :: nitrogen_element() | binary() | string() | iolist() | template_script_element(). -type body() :: body_element() | [body_element()]. -type action_element() :: undefined | tuple() | string() | binary() | iolist(). -type actions() :: action_element() | [action_element()]. -type validator_element() :: nitrogen_element(). -type validators() :: validator_element() | [validator_element()]. -type tag() :: term(). -type id() :: atom() | string() | binary(). -type proplist() :: [{term(), term()}]. -type data_field_name() :: atom() | text(). -type data_field_value() :: atom() | text(). -type data_fields() :: [data_field_name() | {data_field_name()} | {data_field_name(),data_field_value()}]. -type wire_priority() :: eager | normal | defer. -type class() :: string() | binary() | atom(). -type text() :: string() | binary() | iolist(). -type html_encode() :: boolean() | whites | fun((term()) -> text()). -type html() :: string() | binary() | iolist(). -type script() :: string() | binary() | iolist(). -type url() :: text() | atom(). -type path() :: string() | binary(). -type html_name() :: string() | binary() | atom(). -type mobile_theme() :: string() | binary() | atom(). -type comet_name() :: term(). -type comet_restart_msg() :: term(). -type comet_function() :: pid() | function() | {comet_name(), function()} | {comet_name(), function(), comet_restart_msg()}. -type handler_config() :: any(). -type handler_state() :: any(). -type google_chart_type() :: line | sparkline | stacked_horizontal_bar | stacked_vertical_bar | grouped_horizontal_bar | grouped_vertical_bar | pie | pie3d. -type color() :: string() | binary() | atom(). -type google_chart_position() :: top | left | bottom | right. -type module_function() :: {atom(), atom()}. -type encoding_function() :: module_function() | fun((iolist()) -> iolist()). -type encoding() :: none | unicode | auto | encoding_function(). -type context_data() :: iolist() | {file, Filename :: path()} | {stream, Size :: integer(), fun()} | {sendfile, integer(), Size :: integer(), Path :: any()}. -type context_type() :: first_request | postback_request | static_file | postback_websocket | comet | undefined. -type mermaid_code() :: binary() | string() | iolist(). -type mermaid_diagram() :: flowchart | sequence | gantt. -type mermaid_diagram_options(Diagram) :: {Diagram, proplist()}. %%% CONTEXT %%% % Page Request Information. -record(page_context, { series_id, % A unique ID assigned to the first request which stays constant on repeated requests. module, % The requested page module entry_point=main, % The entry point for the module. Either an atom name % (for Module:Atom/0) or can be a function arity 0 path_info, % Any extra info passed with the request async_mode= comet % {poll, Interval}, comet, or {websocket, WebsocketConnectionPid} }). % Event Information. A serialized version of this record % is sent by the browser when an event is called. -record(event_context, { module, % The module that should handle the event tag, % An Erlang term that is passed along with the event type, % The type of event postback, comet, continuation, upload anchor, % The element id to which trigger and target are relative. validation_group, % The validation group that should be run when this event is fired. handle_invalid % When an invalidation evaluates to false, instead of silently not % issuing a postback event, call Delegate:event_invalid with the same tag. }). % Handlers Context- % Handlers are used to encapsulate Nitrogen's behaviour, and % to allow other frameworks to substitute their own behaviour. % These are set in wf_context:make_context/1 -record(handler_context, { name, % The name of a handler. See wf_context for a list. module, % A module that provides the logic for a handler. This can be substituted by your app. config, % The config of the handler, set at the beginning of each request. state % The state of the handler, serialized and maintained between postbacks in a series. %serialize=true % whether or not to serialize and deserialize this handler during requests. Usually, just the state handler needs to be serialized. }). -record(context, { % Transient Information type :: context_type(), bridge :: undefined | simple_bridge:bridge(), %% will only be undefined when "not in a request" anchor=undefined :: id(), data=[] :: context_data(), encoding=auto :: encoding(), action_queue=undefined :: wf_action_queue:action_queue() | undefined, % These are all serialized, sent to the browser % and de-serialized on each request. page_context :: undefined | #page_context{}, event_context :: undefined | #event_context{}, handler_list :: undefined | list() }). %%% LOGGING %%% -ifndef(debug_print). -define(debug_print, true). -define(PRINT(Var), error_logger:info_msg("DEBUG: ~p~n~p:~p~n~p~n ~p~n", [self(), ?MODULE, ?LINE, ??Var, Var])). -define(LOG(Msg, Args), error_logger:info_msg(Msg, Args)). -define(DEBUG, error_logger:info_msg("DEBUG: ~p:~p~n", [?MODULE, ?LINE])). -endif. %%% GUARDS %%% -define (IS_STRING(Term), (is_list(Term) andalso Term /= [] andalso is_integer(hd(Term)))). -define (IS_ACTION_PRIORITY(Priority), (Priority=:=normal orelse Priority=:=eager orelse Priority=:=defer)). -define (PRIORITY_WIRE(Priority), (case Priority of eager -> eager; defer -> defer; normal -> wire; wire -> wire end) ). %%% TERNARY IF AND VARIATIONS %%% -define(WF_IF(Term,IfTrue,IfFalse), fun() -> %% This is wrapped in a fun to contain the leaky-case expression. It %% allows for nesting ?WF_IF calls. Though it obviously does incur the %% minor overhead of creating an anonymous function then executing it. case Term of %% We use the long "WF_IF_VALUE" variable to prevent the likelyhood %% of a variable naming clash. This will throw some matching or %% "shadowing" errors if, in a function, you define a variable %% called "WF_IF_VALUE" before calling ?WF_IF. Given that this is %% unlikely to happen, this is an acceptable compromise. WF_IF_VALUE when WF_IF_VALUE==false; WF_IF_VALUE==undefined; WF_IF_VALUE==[] -> IfFalse; _ -> IfTrue end end()). -define(WF_IF(Term,IfTrue), ?WF_IF(Term,IfTrue,"")). %%% FRAMEWORK %%% %%% Elements %%% -define(ELEMENT_BASE(Module), %% attribute is_element should only ever be the atom `is_element` but %% because Nitrogen copies the base element when it needs to, it's %% possible it might accidentally copy the wrong kind of element that %% it may think is an element but its not. In such an event, we allow %% any() here, so that it passes the dialyzer warning, and will throw %% an error appropriately during runtime. %% %% If we kept it as just `is_element`, that may be just fine, but then %% users would probably have to use dialyzer to debug the "is not an %% element" error is_element=is_element :: is_element | any(), module=Module :: atom(), id :: id(), anchor :: id(), actions :: actions(), show_if=true :: boolean(), class="" :: class() | [class()], style="" :: text(), html_id="" :: id(), title="" :: undefined | text(), data_fields=[] :: data_fields() ). -record(elementbase, {?ELEMENT_BASE(undefined)}). -record(template, {?ELEMENT_BASE(element_template), file :: string(), to_type=html :: string() | atom() | binary(), from_type=html :: string() | atom() | binary(), options=[] :: [{atom(), string() | atom()}], module_aliases=[] :: [{atom(), atom()}], bindings=[] :: proplist() }). -record(function_el, {?ELEMENT_BASE(element_function), function=fun() ->[]end :: fun() | [fun()] }). -record(body, {?ELEMENT_BASE(element_body), body=[] :: body() }). -define(H_ELEMENT(Size), {?ELEMENT_BASE(element_h), text="" :: text(), body=[] :: body(), html_encode=true :: html_encode(), size=Size :: undefined | integer() }). -record(h, ?H_ELEMENT(undefined)). -record(h1, ?H_ELEMENT(1)). -record(h2, ?H_ELEMENT(2)). -record(h3, ?H_ELEMENT(3)). -record(h4, ?H_ELEMENT(4)). -record(h5, ?H_ELEMENT(5)). -record(h6, ?H_ELEMENT(6)). -record(list, {?ELEMENT_BASE(element_list), numbered=false :: boolean(), body=[] :: body() }). -record(listitem, {?ELEMENT_BASE(element_listitem), body=[] :: body(), text="" :: text(), html_encode=true :: html_encode(), role="" :: term() }). -record(br, {?ELEMENT_BASE(element_br) }). -record(hr, {?ELEMENT_BASE(element_hr) }). -record(p, {?ELEMENT_BASE(element_p), body=[] :: body(), text="" :: text(), html_encode=true :: html_encode() }). -record(label, {?ELEMENT_BASE(element_label), body=[] :: body(), text="" :: text(), html_encode=true :: html_encode(), for="" :: id() }). -record(pre, {?ELEMENT_BASE(element_pre), text="" :: text(), html_encode=true :: html_encode() }). -record(strong, {?ELEMENT_BASE(element_strong), body="" :: body(), text="" :: text(), html_encode=true :: html_encode() }). -record(em, {?ELEMENT_BASE(element_em), body="" :: body(), text="" :: text(), html_encode=true :: html_encode() }). -record(value, {?ELEMENT_BASE(element_value), text="" :: text(), html_encode=true :: html_encode() }). -record(link, {?ELEMENT_BASE(element_link), text="" :: text(), body="" :: body(), image=undefined :: undefined | url(), new=false :: boolean(), html_encode=true :: html_encode(), mobile_target=false :: boolean(), mobile_dialog=false :: boolean(), url="javascript:" :: script() | url(), click :: actions(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module() }). -record(email_link, {?ELEMENT_BASE(element_email_link), text="" :: text(), body="" :: body(), html_encode=true :: html_encode(), email="" :: text() }). -record(error, {?ELEMENT_BASE(element_error), text="" :: text(), html_encode=true :: html_encode() }). -record(span, {?ELEMENT_BASE(element_span), body="" :: body(), text="" :: text(), html_encode=true :: html_encode() }). -record(delay_body, {?ELEMENT_BASE(element_delay_body), delegate :: module(), tag=undefined :: term(), placeholder :: body(), delay=0 :: integer() %% milliseconds to wait to populate }). -record(button, {?ELEMENT_BASE(element_button), text="" :: text(), body="" :: body(), image=undefined :: undefined | url(), html_encode=true :: html_encode(), next :: id(), click :: actions(), enter_clicks=[] :: [id()], postback :: term(), disabled=false :: boolean(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module() }). -record(literal, {?ELEMENT_BASE(element_literal), text="" :: text(), html_encode=true :: html_encode() }). -record(textbox, {?ELEMENT_BASE(element_textbox), text="" :: text(), maxlength="" :: integer() | string(), size="" :: integer() | string(), placeholder="" :: text(), html_encode=true :: html_encode(), disabled=false :: boolean(), readonly=false :: boolean(), next :: id(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module(), html_name :: html_name(), type=text :: string() | atom(), autocomplete="off" :: string() | atom() }). -record(datepicker_textbox, {?ELEMENT_BASE(element_datepicker_textbox), text="" :: text(), maxlength="" :: integer() | string(), size="" :: integer() | string(), placeholder="" :: text(), html_encode=true :: html_encode(), readonly=false :: boolean(), disabled=false :: boolean(), next :: id(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), html_name :: html_name(), validators=[] :: validators(), options = [{dateFormat, "yy-mm-dd"}] :: proplist() }). -record(date_dropdown, {?ELEMENT_BASE(element_date_dropdown), value="" :: text() | erlang:date() | {erlang:date(),erlang:time()}, format=iso :: ymd | iso | mdy | usa | dmy, min_year=undefined :: undefined | integer(), max_year=undefined :: undefined | integer(), month_names=true :: boolean(), month_fun={element_date_dropdown, months} :: {module(), atom()} | fun(), wrapperid=undefined :: id(), allow_blank=false :: boolean() }). -record(textbox_autocomplete, {?ELEMENT_BASE(element_textbox_autocomplete), tag :: term(), text="" :: text(), minLength=2 :: integer(), size="" :: integer() | string(), html_encode=true :: html_encode(), placeholder="" :: text(), disabled=false :: boolean(), readonly=false :: boolean(), delay=300 :: integer(), next :: id(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module() }). -record(hidden, {?ELEMENT_BASE(element_hidden), text="" :: text(), html_encode=true :: html_encode(), html_name :: html_name(), disabled=false :: boolean() }). -record(textarea, {?ELEMENT_BASE(element_textarea), text="" :: text(), placeholder="" :: text(), disabled=false :: boolean(), readonly=false :: boolean(), trap_tabs=false :: boolean(), next :: id(), columns :: undefined | integer(), rows :: undefined | integer(), html_encode=true :: html_encode(), html_name :: html_name() }). -record(range, {?ELEMENT_BASE(element_range), min=0 :: integer(), max=100 :: integer(), step=1 :: integer(), value=0 :: integer(), next :: id(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module() }). -record(option, { text="" :: text(), value=undefined :: text() | atom() | integer(), selected :: boolean() | undefined, show_if=true :: boolean(), disabled=false :: boolean() }). -type short_option() :: {text() | atom() | integer(), text()} | text(). -record(option_group, { text="" :: text(), options=[] :: [#option{} | short_option()], show_if=true :: boolean(), disabled=false :: boolean() }). -type options() :: [#option{} | short_option()] | [#option_group{}]. -record(dropdown, {?ELEMENT_BASE(element_dropdown), options=[] :: undefined | options(), size=auto :: auto | integer(), html_encode=true :: html_encode(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module(), value :: atom() | text() | integer(), next :: id(), multiple=false :: boolean(), disabled=false :: boolean(), html_name :: html_name() }). -record(checkbox, {?ELEMENT_BASE(element_checkbox), text="" :: text(), label_position='after' :: 'after' | before | none, html_encode=true :: html_encode(), checked=false :: boolean(), value="on" :: text(), next :: id(), postback :: term(), handle_invalid=false :: boolean(), disabled=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module(), html_name :: html_name() }). -record(radiogroup, {?ELEMENT_BASE(element_radiogroup), body=[] :: body() }). -record(radio, {?ELEMENT_BASE(element_radio), body="" :: body(), text="" :: text(), html_encode=true :: html_encode(), value :: text(), label_class :: class() | [class()], next :: id(), name :: html_name(), checked=false :: boolean(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module(), html_name :: html_name(), disabled=false :: boolean() }). -record(password, {?ELEMENT_BASE(element_password), text="" :: text(), maxlength="" :: integer() | string(), size="" :: integer | string(), placeholder="" :: text(), html_encode=true :: html_encode(), disabled=false :: boolean(), readonly=false :: boolean(), next :: id(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module(), html_name :: html_name() }). -record(restful_form, {?ELEMENT_BASE(element_restful_form), method="POST" :: string() | atom(), action :: url() | undefined, html_name :: html_name(), target :: string() | atom(), enctype :: text() | undefined, body=[] :: body() }). -record(restful_submit, {?ELEMENT_BASE(element_restful_submit), text="Submit" :: text(), body=[] :: body(), html_encode=true :: html_encode(), html_name :: html_name() }). -record(restful_reset, {?ELEMENT_BASE(element_restful_reset), text="Cancel" :: text(), html_encode=true :: html_encode(), html_name :: html_name() }). -record(restful_upload, {?ELEMENT_BASE(element_restful_upload), html_encode=true :: html_encode(), html_name :: html_name() }). -record(panel, {?ELEMENT_BASE(element_panel), body=[] :: body(), text="" :: text(), html_encode=true :: html_encode() }). -record(sync_panel, {?ELEMENT_BASE(element_sync_panel), body_fun :: undefined | fun(), triggers=[] :: [term()], pool=sync_panel :: atom() }). -record(fieldset, {?ELEMENT_BASE(element_fieldset), body=[] :: body(), text="" :: text(), html_encode=true :: html_encode(), legend_body=[] :: body(), legend_text="" :: text(), legend_html_encode=true :: html_encode() }). -record(spinner, {?ELEMENT_BASE(element_spinner), image="/nitrogen/spinner.gif" :: url() }). -record(image, {?ELEMENT_BASE(element_image), image="" :: url(), alt="" :: text(), width :: undefined|integer(), height :: undefined|integer() }). -record(video, {?ELEMENT_BASE(element_video), url="" :: url(), poster_url="" :: url(), width=560 :: integer(), height=315 :: integer(), preload=metadata :: auto | metadata | none, loop=false :: boolean(), muted=false :: boolean(), autoplay=false :: boolean(), controls=false :: boolean(), body_no_support="Your browser does not support the video tag" :: body() }). -record(youtube, {?ELEMENT_BASE(element_youtube), width=560 :: integer(), height=315 :: integer(), key="" :: text(), allowfullscreen=true :: boolean() }). -record(lightbox, {?ELEMENT_BASE(element_lightbox), body=[] :: body() }). -record(table, {?ELEMENT_BASE(element_table), rows :: body(), border = 0 :: integer(), header=[] :: body(), footer=[] :: body() }). -record(tablerow, {?ELEMENT_BASE(element_tablerow), cells :: body() }). -record(tableheader, {?ELEMENT_BASE(element_tableheader), text="" :: text(), html_encode=true :: html_encode(), body=[] :: body(), align="left" :: text() | atom(), valign="middle" :: text() | atom(), colspan=1 :: integer(), rowspan=1 :: integer() }). -record(tablecell, {?ELEMENT_BASE(element_tablecell), text="" :: text(), html_encode=true :: html_encode(), body="" :: body(), align="left" :: text() | atom(), valign="middle" :: text() | atom(), colspan=1 :: integer(), rowspan=1 :: integer() }). -record(singlerow, {?ELEMENT_BASE(element_singlerow), cells :: body() }). -record(file, {?ELEMENT_BASE(element_file), file :: path(), include_panel=true :: boolean(), html_encode=false :: boolean() }). -record(flash, {?ELEMENT_BASE(element_flash)}). -record(placeholder, {?ELEMENT_BASE(element_placeholder), body=[] :: body() }). -record(bind, {?ELEMENT_BASE(element_bind), data=[] :: list(), map=[] :: list() | tuple(), transform :: fun() | undefined, acc=[] :: term(), body=[] :: body(), empty_body=[] :: body() }). -record(sortblock, {?ELEMENT_BASE(element_sortblock), tag :: term(), items=[] :: body(), group :: atom() | string() | binary(), connect_with_groups=none :: atom() | string() | binary() | [atom() | string() | binary()], handle :: class(), placeholder="" :: class(), force_placeholder_size=false :: boolean(), delegate :: module() }). -record(sortitem, {?ELEMENT_BASE(element_sortitem), tag :: term(), body=[] :: body() }). -record(draggable, {?ELEMENT_BASE(element_draggable), tag :: term(), body=[] :: body(), group :: atom() | string() | binary(), handle :: class(), clone=true :: boolean(), revert=true :: boolean() | valid | invalid, scroll=true :: boolean(), container :: atom() | binary() | string(), zindex :: integer() | undefined, distance=3 :: integer(), options=[] :: proplist() }). -record(droppable, {?ELEMENT_BASE(element_droppable), tag :: term(), body=[] :: body(), accept_groups=all :: atom() | string() | binary() | [atom() | string() | binary()], active_class=active :: class(), hover_class=hover :: class(), delegate :: module() }). -record(gravatar, {?ELEMENT_BASE(element_gravatar), email="" :: text(), size="80" :: integer() | text(), rating="g" :: text(), default="" :: text() }). -record(inplace_textarea, {?ELEMENT_BASE(element_inplace_textarea), tag :: term(), text="" :: text(), html_encode=true :: html_encode(), start_mode=view :: view | edit, validators=[] :: validators(), delegate :: module() }). -record(inplace_textbox, {?ELEMENT_BASE(element_inplace_textbox), tag :: term(), text="" :: text(), html_encode=true :: html_encode(), start_mode=view :: view | edit, validators=[] :: validators(), delegate :: module() }). -record(inplace, {?ELEMENT_BASE(element_inplace), tag :: term(), text="" :: text(), delegate :: module(), view :: body(), edit :: body(), start_mode=view :: view | edit }). -record(upload, {?ELEMENT_BASE(element_upload), delegate :: module(), tag :: term(), show_button=true :: boolean(), file_text="Select file" :: text(), button_text="Upload" :: text(), button_class="" :: class() | [class()], droppable=false :: boolean(), droppable_text="Drop Files Here" :: text(), multiple=false :: boolean(), overall_progress=auto :: boolean() | undefined | auto }). -record(wizard, {?ELEMENT_BASE(element_wizard), tag :: term(), titles :: undefined | [text()], steps=[] :: [body()], next="Next" :: text(), back="Back" :: text(), finish="Finish" :: text(), show_progress=true :: boolean(), progress_text="(Step ~p of ~p)" :: text(), next_class="" :: text() | [text()], back_class="" :: text() | [text()], finish_class="" :: text() | [text()] }). -record(sparkline, {?ELEMENT_BASE(element_sparkline), type=line :: line | bar | tristate | bullet | discrete | pie | box, values=[] :: [integer() | float() | null | undefined], options=[] :: proplist() }). -record(recaptcha, {?ELEMENT_BASE(element_recaptcha), captcha_opts=[] :: proplist(), button_id :: id(), button_label="Check!" :: text(), button_class :: class(), delegate :: module(), tag :: term(), fail_body="Please try again!" :: body(), public_key :: string() | undefined, private_key :: string() | undefined, challenge_url :: url() | undefined, verify_url :: url() | undefined }). %% Mobile Shortcut elements -record(mobile_list, {?ELEMENT_BASE(element_mobile_list), body=[] :: body(), theme :: mobile_theme(), inset=true :: boolean() }). -record(mobile_list_divider, {?ELEMENT_BASE(element_mobile_list_divider), theme :: mobile_theme(), text="" :: text(), body=[] :: body(), role=heading :: atom() | string() }). -record(mobile_listitem, {?ELEMENT_BASE(element_mobile_listitem), text="" :: text(), body=[] :: body(), theme="" :: atom() | string() }). -record(mobile_toggle, {?ELEMENT_BASE(element_mobile_toggle), on_text="On" :: text(), on_value="on" :: text(), off_text="Off" :: text(), off_value="off" :: text(), selected="on" :: atom() | text(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), delegate :: module(), width :: integer() | undefined, theme="" :: mobile_theme() }). -record(mobile_collapsible, {?ELEMENT_BASE(element_mobile_collapsible), header_theme :: mobile_theme(), content_theme :: mobile_theme(), header_text="" :: text(), content_body=[] :: body(), content_text="" :: text(), header_size=3 :: integer(), mini=false :: boolean(), collapsed=true :: boolean() }). -record(mobile_collapsible_set, {?ELEMENT_BASE(element_mobile_collapsible_set), header_theme :: mobile_theme(), content_theme :: mobile_theme(), mini=false :: boolean(), body=[] :: body() }). -record(mobile_grid_block, {?ELEMENT_BASE(element_mobile_grid_block), text="" :: text(), body=[] :: body(), new_row=default :: atom() }). -record(mobile_grid, {?ELEMENT_BASE(element_mobile_grid), columns=2 :: integer(), blocks=[] :: [#mobile_grid_block{}] }). -record(mobile_panel, {?ELEMENT_BASE(element_mobile_panel), mini=false :: boolean(), position=left :: left | right, dismissible=true :: boolean(), theme :: mobile_theme(), display_mode=reveal :: reveal | overlay | push, body=[] :: body() }). -record(iframe, {?ELEMENT_BASE(element_iframe), align :: text() | atom(), frameborder :: integer() | undefined, height :: integer(), name="" :: text(), sandbox="" :: text(), seamless :: atom() | text(), src :: url(), srcdoc="" :: text(), width :: integer(), allowfullscreen=true :: boolean() }). %% HTML5 semantic elements -record(section, {?ELEMENT_BASE(element_section), body="" :: body(), role="" :: text() }). -record(nav, {?ELEMENT_BASE(element_nav), body="" :: body(), role="" :: text() }). -record(article, {?ELEMENT_BASE(element_article), body="" :: body(), role="" :: text() }). -record(aside, {?ELEMENT_BASE(element_aside), body="" :: body(), role="" :: text() }). -record(html5_header, {?ELEMENT_BASE(element_html5_header), body="" :: body(), role="" :: text() }). -record(html5_footer, {?ELEMENT_BASE(element_html5_footer), body="" :: body(), role="" :: text() }). -record(time, {?ELEMENT_BASE(element_time), datetime="" :: text(), body="" :: body(), text="" :: text(), html_encode=true :: html_encode(), role="" :: text() }). -record(mark, {?ELEMENT_BASE(element_mark), body="" :: body(), text="" :: text(), html_encode=true :: html_encode(), role="" :: text() }). -record(main, {?ELEMENT_BASE(element_main), body="" :: body(), role="" :: text() }). %% 960.gs Grid -define(GRID_ELEMENT(Type, Columns), {?ELEMENT_BASE(element_grid), type=Type :: undefined | container | grid | clear, columns=Columns :: undefined | integer(), alpha=false :: boolean(), omega=false :: boolean(), push :: integer() | undefined, pull :: integer() | undefined, prefix :: integer() | undefined, suffix :: integer() | undefined, body=[] :: body() }). -record(grid, ?GRID_ELEMENT(undefined, undefined)). -record(container_12, ?GRID_ELEMENT(container,12)). -record(container_16, ?GRID_ELEMENT(container,16)). -record(grid_1, ?GRID_ELEMENT(grid, 1)). -record(grid_2, ?GRID_ELEMENT(grid, 2)). -record(grid_3, ?GRID_ELEMENT(grid, 3)). -record(grid_4, ?GRID_ELEMENT(grid, 4)). -record(grid_5, ?GRID_ELEMENT(grid, 5)). -record(grid_6, ?GRID_ELEMENT(grid, 6)). -record(grid_7, ?GRID_ELEMENT(grid, 7)). -record(grid_8, ?GRID_ELEMENT(grid, 8)). -record(grid_9, ?GRID_ELEMENT(grid, 9)). -record(grid_10, ?GRID_ELEMENT(grid, 10)). -record(grid_11, ?GRID_ELEMENT(grid, 11)). -record(grid_12, ?GRID_ELEMENT(grid, 12)). -record(grid_13, ?GRID_ELEMENT(grid, 13)). -record(grid_14, ?GRID_ELEMENT(grid, 14)). -record(grid_15, ?GRID_ELEMENT(grid, 15)). -record(grid_16, ?GRID_ELEMENT(grid, 16)). -record(grid_clear, ?GRID_ELEMENT(clear, undefined)). -record(progress_bar, {?ELEMENT_BASE(element_progress_bar), value=undefined :: integer() | undefined, max=100 :: integer(), color = <<"909090">> :: color(), label :: undefined | percent | ratio | both | string() }). %% Google Charts -record(chart_axis, { position :: google_chart_position(), labels :: undefined | [text()], color = <<"909090">> :: color(), font_size=10 :: integer() }). -record(chart_data, { color :: color(), legend :: text(), values :: [text() | integer()], min_value=0 :: integer(), max_value=100 :: integer(), line_width=1 :: integer(), line_length=1 :: integer(), blank_length=0 :: integer() }). -record(google_chart, {?ELEMENT_BASE(element_google_chart), type=line :: google_chart_type(), color = <<"909090">> :: color(), font_size=10 :: integer(), width=300 :: integer(), height=150 :: integer(), axes=[] :: undefined | [#chart_axis{}], data=[] :: undefined | [#chart_data{}], grid_x=undefined :: undefined | integer(), grid_y=undefined :: undefined | integer(), grid_line_length=1 :: integer(), grid_blank_length=5 :: integer(), background_color=ffffff :: color(), chart_color=ffffff :: color(), legend_location=bottom :: google_chart_position(), bar_space=3 :: integer(), bar_group_space=7 :: integer() }). -record(qr, {?ELEMENT_BASE(element_qr), data=undefined :: any(), size=200 :: integer() }). -record(mermaid, {?ELEMENT_BASE(element_mermaid), code=[] :: mermaid_code(), options=[] :: proplist(), diagram_options=undefined :: undefined | mermaid_diagram_options(mermaid_diagram()) }). %%% Actions %%% -define(AV_BASE(Module,Type), is_action=Type :: is_action | is_validator, module=Module :: module(), anchor :: id(), trigger :: id(), target :: id(), actions :: actions(), show_if=true :: boolean(), dependency_js :: url() | undefined ). -define(ACTION_BASE(Module), ?AV_BASE(Module,is_action)). -record(actionbase, {?ACTION_BASE(undefined)}). -record(wire, {?ACTION_BASE(action_wire)}). -record(click, {?ACTION_BASE(action_click)}). -define(ACTION_UPDATE(Type), {?ACTION_BASE(action_update), type=Type :: atom(), elements=[] :: body() }). %% #update records and its derivitives should all use the same template to %% ensure simple conversion performed by action_update:render_action/1 -record(update, ?ACTION_UPDATE(update)). -record(replace, ?ACTION_UPDATE(replace)). -record(insert_top, ?ACTION_UPDATE(insert_top)). -record(insert_bottom, ?ACTION_UPDATE(insert_bottom)). -record(insert_before, ?ACTION_UPDATE(insert_before)). -record(insert_after, ?ACTION_UPDATE(insert_after)). -record(remove, ?ACTION_UPDATE(remove)). -record(comet, {?ACTION_BASE(action_comet), pool=undefined :: term(), scope=local :: local | global, function :: comet_function(), dying_message :: term(), reconnect_actions :: actions() }). -record(continue, {?ACTION_BASE(action_continue), function :: undefined | fun(), delegate :: module(), tag :: term(), timeout :: integer() | infinity | undefined }). -record(api, {?ACTION_BASE(action_api), name :: string() | binary() | atom(), tag :: term(), delegate :: module() }). -record(function, {?ACTION_BASE(action_function), function :: fun() | undefined }). -record(js_fun, {?ACTION_BASE(action_js_fun), function :: atom() | text(), args=[] :: [text()] }). -record(set, {?ACTION_BASE(action_set), value="" :: text() | integer() }). -record(set_multiple, {?ACTION_BASE(action_set_multiple), values=[] :: [text()] }). -record(redirect, {?ACTION_BASE(action_redirect), url="" :: url(), login=false :: boolean() | url() }). -record(open_window, {?ACTION_BASE(action_open_window), url="" :: url(), height :: integer() | undefined, width :: integer() | undefined, left :: integer() | undefined, top :: integer() | undefined, menubar=true :: boolean(), statusbar=true :: boolean(), titlebar=true :: boolean(), name='_blank' :: '_blank' | '_parent' | '_self' | '_top' | text(), options=[] }). -record(event, {?ACTION_BASE(action_event), type=default :: atom(), keycode=undefined :: integer() | undefined, shift_key=false :: boolean(), delay=0 :: integer(), postback :: term(), handle_invalid=false :: boolean(), on_invalid :: undefined | actions(), validation_group :: string() | binary() | atom(), delegate :: module(), extra_param :: string() | binary() | undefined }). -record(before_postback, {?ACTION_BASE(action_before_postback), script="" :: string() }). %% we want validation assignments to happen last, so we use AV_BASE and set deferral to zero first -record(validate, {?ACTION_BASE(action_validate), on=submit :: atom(), success_text=" " :: text(), group :: string() | binary() | atom(), validators :: validators(), attach_to :: id() }). -record(validation_error, {?ACTION_BASE(action_validation_error), text="" :: text(), attach_to :: id() }). -record(clear_validation, {?ACTION_BASE(action_clear_validation), validation_trigger :: id(), validation_target :: id(), validation_all :: id() }). -record(alert, {?ACTION_BASE(action_alert), text="" :: text() }). -record(confirm, {?ACTION_BASE(action_confirm), text="" :: text(), postback :: term(), delegate :: module() }). -record(console_log, {?ACTION_BASE(action_console_log), text="" :: any() }). -record(script, {?ACTION_BASE(action_script), script :: text() }). -record(disable_selection, {?ACTION_BASE(action_disable_selection)}). -record(disable_option, {?ACTION_BASE(action_toggle_option), value :: atom() | string() | binary() | integer() }). -record(enable_option, {?ACTION_BASE(action_toggle_option), value :: atom() | string() | binary() | integer() }). -record(add_option, {?ACTION_BASE(action_add_option), option :: #option{} | short_option(), location=bottom :: top | bottom }). -record(remove_option, {?ACTION_BASE(action_remove_option), value :: atom() | string() | binary() | integer() }). -record(jquery_effect, {?ACTION_BASE(action_jquery_effect), type :: atom() | string() | binary(), effect :: atom() | string() | binary(), speed :: integer(), options=[] :: proplist(), class :: class(), easing :: atom() }). -record(show, {?ACTION_BASE(action_show), effect=none :: atom(), options=[] :: proplist(), speed=500 :: integer() }). -record(hide, {?ACTION_BASE(action_hide), effect=none :: atom(), options=[] :: proplist(), speed=500 :: integer() }). -record(appear, {?ACTION_BASE(action_appear), speed=500 :: integer() }). -record(fade, {?ACTION_BASE(action_fade), speed=500 :: integer() }). -record(slide_down, {?ACTION_BASE(action_slide_down), speed=500 :: integer() }). -record(slide_up, {?ACTION_BASE(action_slide_up), speed=500 :: integer() }). -record(effect, {?ACTION_BASE(action_effect), effect=none :: atom(), options=[] :: proplist(), speed=500 :: integer() }). -record(toggle, {?ACTION_BASE(action_toggle), effect=none :: atom(), options=[] :: proplist(), speed=500 :: integer() }). -record(toggle_mobile_panel, {?ACTION_BASE(action_toggle_mobile_panel)}). -record(add_class, {?ACTION_BASE(action_add_class), class=none :: class(), speed=0 :: integer() }). -record(remove_class, {?ACTION_BASE(action_remove_class), class=none :: class(), speed=0 :: integer() }). -record(animate, {?ACTION_BASE(action_animate), options=[] :: proplist(), speed=500 :: integer(), easing=swing :: atom() }). -record(buttonize, {?ACTION_BASE(action_buttonize)}). -record(disable, {?ACTION_BASE(action_disable)}). -record(enable, {?ACTION_BASE(action_enable)}). -record(make_readonly, {?ACTION_BASE(action_make_readonly)}). -record(make_writable, {?ACTION_BASE(action_make_writable)}). -record(set_cookie, {?ACTION_BASE(action_set_cookie), cookie :: atom() | text(), value="" :: atom() | text(), path="/" :: text(), domain=undefined :: undefined | text(), minutes_to_live=20 :: integer(), secure=false :: boolean(), http_only=false :: boolean() }). %%% Validators %%% %%% %% TODO: Switch this from is_action to is_validator once deferred is implemented %%% This will allow users to bind validators directly, instead of needing the #validate{} action -define(VALIDATOR_BASE(Module), ?AV_BASE(Module,is_action), text="Failed." :: text(), attach_to :: undefined | id() ). -record(validatorbase, {?VALIDATOR_BASE(undefined)}). -record(is_required, {?VALIDATOR_BASE(validator_is_required), unless_has_value :: undefined | [id()] }). -record(is_email, {?VALIDATOR_BASE(validator_is_email)}). -record(is_integer, {?VALIDATOR_BASE(validator_is_integer), min :: undefined | integer(), max :: undefined | integer(), allow_blank=false :: boolean() }). -record(is_number, {?VALIDATOR_BASE(validator_is_number), min :: undefined | integer(), max :: undefined | integer(), allow_blank=false :: boolean() }). -record(min_length, {?VALIDATOR_BASE(validator_min_length), length :: undefined | integer() }). -record(max_length, {?VALIDATOR_BASE(validator_max_length), length :: undefined | integer() }). -record(confirm_password, {?VALIDATOR_BASE(validator_confirm_password), password :: id() }). -record(confirm_same, {?VALIDATOR_BASE(validator_confirm_same), confirm_id :: id() }). -record(custom, {?VALIDATOR_BASE(validator_custom), function :: fun(), tag :: term() }). -record(js_custom, {?VALIDATOR_BASE(validator_js_custom), function :: atom() | script(), args="{}" :: text(), when_empty=false :: boolean() }). -record(if_value, {?ACTION_BASE(action_if_value), value :: atom() | text(), map :: undefined | [{atom() | text(), actions()}], else=[] :: actions() }). -endif.