Packages
Nitrogen Web Framework Core Library
Current section
Files
Jump to
Current section
Files
src/elements/forms/element_dropdown.erl
% vim: ts=4 sw=4 et
% Nitrogen Web Framework for Erlang
% Copyright (c) 2008-2010 Rusty Klophaus
% Copyright (c) 2014 Jesse Gumm
% See MIT-LICENSE for licensing information.
-module (element_dropdown).
-include("wf.hrl").
-export([
reflect/0,
render_element/1,
create_option/1
]).
-define(DEFAULT_MULTISELECT_SIZE, 5).
-define(DEFAULT_SINGLESELECT_SIZE, 1).
-spec reflect() -> [atom()].
reflect() -> record_info(fields, dropdown).
-spec render_element(#dropdown{}) -> body().
render_element(Record) ->
wire_postback(Record),
action_event:maybe_wire_next(Record#dropdown.anchor, Record#dropdown.next),
Options = format_options(Record),
MultipleAttribute = ?WF_IF(Record#dropdown.multiple,multiple,undefined),
DisabledAttribute = ?WF_IF(Record#dropdown.disabled,disabled,undefined),
Size = provided_or_default_size(Record),
wf_tags:emit_tag(select, Options, [
{id, Record#dropdown.html_id},
{class, [dropdown, Record#dropdown.class]},
{title, Record#dropdown.title},
{style, Record#dropdown.style},
{name, Record#dropdown.html_name},
{size, Size},
MultipleAttribute,
DisabledAttribute,
{data_fields, Record#dropdown.data_fields}
]).
provided_or_default_size(#dropdown{size=auto, multiple=true}) ->
?DEFAULT_MULTISELECT_SIZE;
provided_or_default_size(#dropdown{size=auto, multiple=false}) ->
?DEFAULT_SINGLESELECT_SIZE;
provided_or_default_size(#dropdown{size=Size}) ->
Size.
wire_postback(Dropdown) when Dropdown#dropdown.postback==undefined ->
ignore;
wire_postback(Dropdown) ->
wf:wire(Dropdown#dropdown.anchor, #event {
type=change,
postback=Dropdown#dropdown.postback,
handle_invalid=Dropdown#dropdown.handle_invalid,
on_invalid=Dropdown#dropdown.on_invalid,
validation_group=Dropdown#dropdown.id,
delegate=Dropdown#dropdown.delegate
}).
format_options(Dropdown) when Dropdown#dropdown.options==undefined ->
"";
format_options(#dropdown{options=Opts, value=Value0, html_encode=HtmlEncode}) ->
Value = binary_or_undefined(Value0),
create_options(Value, HtmlEncode, Opts).
binary_or_undefined(undefined) ->
undefined;
binary_or_undefined(V) ->
wf:to_binary(V).
create_option(Option) ->
create_options(undefined, true, [Option]).
create_options(_,_,[]) ->
[];
create_options(Selected,HtmlEncode, [{Value, Text} | Rest]) ->
[create_option_from_tuple(Selected, HtmlEncode, {Value, Text}) | create_options(Selected, HtmlEncode, Rest)];
create_options(Selected,HtmlEncode, [OG = #option_group{show_if=true} | Rest]) ->
[create_option_group(Selected, HtmlEncode, OG) | create_options(Selected, HtmlEncode, Rest)];
create_options(Selected,HtmlEncode,[#option_group{show_if=false} | Rest]) ->
create_options(Selected,HtmlEncode,Rest);
create_options(Selected,HtmlEncode,[X=#option{show_if=true} | Rest]) ->
[create_option_full(Selected, HtmlEncode, X) | create_options(Selected,HtmlEncode,Rest)];
create_options(Selected,HtmlEncode,[#option{show_if=false} | Rest]) ->
create_options(Selected,HtmlEncode,Rest);
create_options(Selected,HtmlEncode,[Other | Rest]) when ?IS_STRING(Other); is_binary(Other); is_integer(Other); is_atom(Other) ->
[create_option_simple(Selected, HtmlEncode, Other) | create_options(Selected,HtmlEncode, Rest)];
create_options(_,_,[Other | _]) ->
throw({unknown_option_provided_to_dropdown_element,Other}).
create_option_group(Selected, HtmlEncode, #option_group{text=Text, options=Options, disabled=Disabled}) ->
OptionTags = create_options(Selected, HtmlEncode, Options),
LabelProp = {label, wf:html_encode(Text, HtmlEncode)},
DisabledProp = ?WF_IF(Disabled, disabled, undefined),
Props = [
LabelProp,
DisabledProp
],
wf_tags:emit_tag(optgroup, OptionTags, Props).
create_option_simple(Selected, HtmlEncode, Value) ->
create_option_from_tuple(Selected, HtmlEncode, {Value, Value}).
create_option_from_tuple(Selected, HtmlEncode, {Value, Text}) ->
Option = #option{text=Text, value=Value},
create_option_full(Selected, HtmlEncode, Option).
create_option_full(Selected, HtmlEncode, Opt = #option{text=Text, value=Value, disabled=Disabled}) ->
Content = wf:html_encode(Text, HtmlEncode),
SelectedProp = ?WF_IF(is_selected(Selected, Opt), selected, undefined),
DisabledProp = ?WF_IF(Disabled, disabled, undefined),
ValueProp = ?WF_IF(Value =:= undefined, [], {value, wf:html_encode(Value, HtmlEncode)}),
Props = [
SelectedProp,
DisabledProp,
ValueProp
],
wf_tags:emit_tag(option, Content, Props).
-spec is_selected(DropDownValue :: binary()|undefined, X :: #option{}) -> boolean().
is_selected(_DropDownValue, #option{selected=true}) ->
%% If the #option.selected=true, then short-circuit and return true.
true;
is_selected(DropDownValue, #option{selected=OptSelected, value=OptValue})
when OptSelected=:=false;
OptValue=:=undefined;
DropDownValue=:=undefined ->
false;
is_selected(DropDownValue, #option{value=OptValue}) ->
%% Finally, if none of the above short-circuits trip, then we can convert
%% #option.value to binary and compare directly. If they match, then it's
%% selected.
wf:to_binary(OptValue) =:= DropDownValue.
-include_lib("eunit/include/eunit.hrl").
html_encode_test() ->
?assertEqual(true, is_selected(<<"anything">>, #option{selected=true})),
?assertEqual(false, is_selected(<<"anything">>, #option{selected=false})),
?assertEqual(true, is_selected(<<"apple">>, #option{value=apple})),
?assertEqual(false, is_selected(<<"other_thing">>, #option{value=apple})),
?assertEqual(true, is_selected(<<"apple">>, #option{value=apple, selected=true})),
?assertEqual(false, is_selected(<<"apple">>, #option{value=apple, selected=false})).