Adding class to date_select elements

How can I add a class to each select generated by date_select? I already created a custom builder but I can’t figure out how to add a class name to each select element;

builder = fn b ->
  ~e"""
  <div class="flex">
    <%= b.(:day, []) %>
    <%= b.(:month, []) %>
    <%= b.(:year, []) %>
  </div>
  """
end

Thanks in advance for any help!

It should be able to accept a class: "some-class" keyword option.

date_select(form, field, class: "some-class")

If I’m reading the code correctly that class goes through select/4 and then content_tag/3 - so you are restricted to the same class on every select generated.

Are you guys sure? I tried it in several combinations and none of them adds the class to the generated html output (<%= date_select f, :date, class: “select” %>).

builder = fn b ->
  ~e"""
  <div class="flex">
    <%= b.(:day, [class: "xyz"]) %>
    <%= b.(:month, [class: "abc"]) %>
    <%= b.(:year, [class: "def"]) %>
  </div>
  """
end
4 Likes

Thanks @voughtdq, that did the trick!

Note to self - trying stuff out …

some_phoenix_project$ iex -S mix
Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import Phoenix.HTML
Phoenix.HTML
iex(2)> import Phoenix.HTML.Form
Phoenix.HTML.Form
iex(3)> builder = fn select_for ->
...(3)>   ~E"""
...(3)>   <div class="flex">
...(3)>     <%= select_for.(:day, [class: "xyz"]) %>
...(3)>     <%= select_for.(:month, [class: "abc"]) %>
...(3)>     <%= select_for.(:year, [class: "def"]) %>
...(3)>   </div>
...(3)>   """
...(3)> end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex(4)> content = ~E"""
...(4)> <%= date_select :user, :born_at,
...(4)>   builder: builder,
...(4)>   year: [options: 2018..2018],
...(4)>   month: [options: 12..12],
...(4)>   day: [options: 13..13]
...(4)> %>
...(4)> """
{:safe,
 [
   [
     "",
     [
       [
         [
           [
             [
               ["" | "<div class=\"flex\">\n  "],
               60,
               "select",
               [
                 [32, "class", 61, 34, "xyz", 34],
                 [32, "id", 61, 34, "user_born_at_day", 34],
                 [32, "name", 61, 34, "user[born_at][day]", 34]
               ],
               62,
               [
                 "",
                 60,
                 "option",
                 [[32, "value", 61, 34, "13", 34]],
                 62,
                 "13",
                 60,
                 47,
                 "option",
                 62
               ],
               60,
               47,
               "select",
               62
             ] |
             "\n  "
           ],
           60,
           "select",
           [
             [32, "class", 61, 34, "abc", 34],
             [32, "id", 61, 34, "user_born_at_month", 34],
             [32, "name", 61, 34, "user[born_at][month]", 34]
           ],
           62,
           [
             "",
             60,
             "option",
             [[32, "value", 61, 34, "12", 34]],
             62,
             "12",
             60,
             47,
             "option",
             62
           ],
           60,
           47,
           "select",
           62
         ] |
         "\n  "
       ],
       60,
       "select",
       [
         [32, "class", 61, 34, "def", 34],
         [32, "id", 61, 34, "user_born_at_year", 34],
         [32, "name", 61, 34, "user[born_at][year]", 34]
       ],
       62,
       [
         "",
         60,
         "option",
         [[32, "value", 61, 34, "2018", 34]],
         62,
         "2018",
         60,
         47,
         "option",
         62
       ],
       60,
       47,
       "select",
       62
     ] |
     "\n</div>\n"
   ] |
   "\n"
 ]}
iex(5)> safe_to_string(content)
"<div class=\"flex\">\n  <select class=\"xyz\" id=\"user_born_at_day\" name=\"user[born_at][day]\"><option value=\"13\">13</option></select>\n  <select class=\"abc\" id=\"user_born_at_month\" name=\"user[born_at][month]\"><option value=\"12\">12</option></select>\n  <select class=\"def\" id=\"user_born_at_year\" name=\"user[born_at][year]\"><option value=\"2018\">2018</option></select>\n</div>\n\n"
iex(6)> 
2 Likes

I am using the builder option instead of the built in helper function (shown below) to control the styling and order of the select fields. However, I’m trying to add a default option for the year to the builder, but have not found a solution.

 <%= date_select f, :date, default: {2018, 12, 25} %>

To get a data range, I’m doing

<%= b.(:year, [options: (NaiveDateTime.utc_now.year - 10)..(NaiveDateTime.utc_now.year + 10), 
               class: "def"]) %>

but the following default option is not working.

<%= b.(:year, [default: NaiveDateTime.utc_now.year, options: (NaiveDateTime.utc_now.year - 10)..(NaiveDateTime.utc_now.year + 10), 
               class: "def"]) %>

How can I add a default year to the builder while also supplying a range (shown above)?

sorry for the post bump, but does anyone know a solution?

An HTML input element has no notion of default - that concept exists only in the context of the helper function. On the input level you have to set the value attribute.

When specified in the HTML, this is the initial value, and from then on it can be altered or retrieved at any time using JavaScript to access HTMLInputElement.value The value attribute is always optional.

For HTMLInputElement.defaultValue:

string : Returns / Sets the default value as originally specified in the HTML that created this object.

So presumably the input element “remembers” the value that was set when it was created as the “default”.

Similiarly:
<select>: The HTML Select element - HTML: HyperText Markup Language | MDN
HTMLSelectElement - Web APIs | MDN
<option>: The HTML Option element - HTML: HyperText Markup Language | MDN
HTMLOptionElement - Web APIs | MDN

$ iex -S mix
Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import Phoenix.HTML
Phoenix.HTML
iex(2)> import Phoenix.HTML.Form
Phoenix.HTML.Form
iex(3)> year_range = fn radius -> (NaiveDateTime.utc_now.year - radius)..(NaiveDateTime.utc_now.year + radius) end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex(4)> year_default = fn -> NaiveDateTime.utc_now.year end
#Function<20.128620087/0 in :erl_eval.expr/5>
iex(5)> builder = fn select_for ->
...(5)>     ~E"""
...(5)>       <%= select_for.(:year, [value: year_default.(), options: year_range.(1), class: "def"]) %>
...(5)>     """
...(5)>   end
#Function<6.128620087/1 in :erl_eval.expr/5>
iex(6)> content = ~E"""
...(6)>   <%= date_select :user, :date,
...(6)>     builder: builder,
...(6)>     year: []
...(6)>   %>
...(6)>   """
{:safe,
 [
   [
     "",
     [
       ["" | "  "],
       60,
       "select",
       [
         [32, "class", 61, 34, "def", 34],
         [32, "id", 61, 34, "user_date_year", 34],
         [32, "name", 61, 34, "user[date][year]", 34]
       ],
       62,
       [
         [
           [
             "",
             60,
             "option",
             [[32, "value", 61, 34, "2017", 34]],
             62,
             "2017",
             60,
             47,
             "option",
             62
           ],
           60,
           "option",
           [[32, "value", 61, 34, "2018", 34], [32, "selected"]],
           62,
           "2018",
           60,
           47,
           "option",
           62
         ],
         60,
         "option",
         [[32, "value", 61, 34, "2019", 34]],
         62,
         "2019",
         60,
         47,
         "option",
         62
       ],
       60,
       47,
       "select",
       62
     ] |
     "\n"
   ] |
   "\n"
 ]}
iex(7)> 
1 Like

I appreciate the detailed response, thank you. I tried using the value: option key in the builder function for the :year, but it always returns current year, even on an edit action when a value other than current year was set. With your version using the ~E sigil, is it using the default year in value: if one was not already set (i.e., empty list)?

Thank you again.

<%= b.(:year, [value: NaiveDateTime.utc_now.year, options: (NaiveDateTime.utc_now.year - 10)..(NaiveDateTime.utc_now.year + 10), 
               class: "def"]) %>

You provided this as your reference case:

My purpose was to attempt to generate equivalent HTML output with a builder function.

So if we start out with (in iex):

import Phoenix.HTML
import Phoenix.HTML.Form

default_date = {2020, 12, 25}
content =
  ~E"""
    <%= date_select :user, :date, default: default_date %>
  """

safe_to_string(content)

We’ll get this output:

<select id=\"user_date_year\" name=\"user[date][year]\">
  <option value=\"2014\">2014</option>
  <option value=\"2015\">2015</option>
  <option value=\"2016\">2016</option>
  <option value=\"2017\">2017</option>
  <option value=\"2018\">2018</option>
  <option value=\"2019\">2019</option>
  <option value=\"2020\" selected>2020</option>
  <option value=\"2021\">2021</option>
  <option value=\"2022\">2022</option>
  <option value=\"2023\">2023</option>
  <option value=\"2024\">2024</option>
</select> /
<select id=\"user_date_month\" name=\"user[date][month]\">
  <option value=\"1\">January</option>
  <option value=\"2\">February</option>
  <option value=\"3\">March</option>
  <option value=\"4\">April</option>
  <option value=\"5\">May</option>
  <option value=\"6\">June</option>
  <option value=\"7\">July</option>
  <option value=\"8\">August</option>
  <option value=\"9\">September</option>
  <option value=\"10\">October</option>
  <option value=\"11\">November</option>
  <option value=\"12\" selected>December</option>
</select> /
<select id=\"user_date_day\" name=\"user[date][day]\">
  <option value=\"1\">01</option>
  <option value=\"2\">02</option>
  <option value=\"3\">03</option>
  <option value=\"4\">04</option>
  <option value=\"5\">05</option>
  <option value=\"6\">06</option>
  <option value=\"7\">07</option>
  <option value=\"8\">08</option>
  <option value=\"9\">09</option>
  <option value=\"10\">10</option>
  <option value=\"11\">11</option>
  <option value=\"12\">12</option>
  <option value=\"13\">13</option>
  <option value=\"14\">14</option>
  <option value=\"15\">15</option>
  <option value=\"16\">16</option>
  <option value=\"17\">17</option>
  <option value=\"18\">18</option>
  <option value=\"19\">19</option>
  <option value=\"20\">20</option>
  <option value=\"21\">21</option>
  <option value=\"22\">22</option>
  <option value=\"23\">23</option>
  <option value=\"24\">24</option>
  <option value=\"25\" selected>25</option>
  <option value=\"26\">26</option>
  <option value=\"27\">27</option>
  <option value=\"28\">28</option>
  <option value=\"29\">29</option>
  <option value=\"30\">30</option>
  <option value=\"31\">31</option>
</select>\n"

Note how the “2020”, “12” and “25” option elements have the selected attribute.

Now a builder version:

import Phoenix.HTML
import Phoenix.HTML.Form

make_builder = fn {year, month, day} ->
  fn b ->
    ~E"""
      <%= b.(:year, [value: year, options: 2014..2024, class: "def"]) %> / <%= b.(:month, [value: month]) %> / <%= b.(:day, [value: day]) %>
    """
  end
end

default_date = {2020, 12, 25}
builder = make_builder.(default_date)
content =
  ~E"""
    <%= date_select :user, :date, builder: builder %>
  """

safe_to_string(content)

which produces the same output.

EDIT:

Now having seen this code:

This works as expected:

import Phoenix.HTML
import Phoenix.HTML.Form

builder =
  fn b ->
    ~E"""
      <%= b.(:year, [options: 2014..2024, class: "def"]) %> / <%= b.(:month, []) %> / <%= b.(:day, []) %>
    """
  end

default_date = {2020, 12, 25}
content =
  ~E"""
    <%= date_select :user, :date, builder: builder, default: default_date %>
  """

safe_to_string(content)

If you look at the date_select code it should become obvious that the :default value has already been extracted and is being handed to the builder function as the value to format.

By the time builder runs it’s too late to influence the :default because

value = Keyword.get(opts, :value, input_value(form, field) || Keyword.get(opts, :default))

has already happened.

2 Likes

thank you so much for your detailed response! that explains it perfectly.

1 Like