We render an edit page the repeats many long option lists, so we made rails only render it’s options once and made javascript reuse them, resulting in reduced render ~70% time, ~90% page size.
It resets options when a users reload a partially filled out page, but otherwise supports keyboard and mouse navigation nicely.
# app/views/projects/_form.html.erb <% list = [["Foo", "foo"], ["Bar", "bar"], ["Name", @project.name]] %> <%= form.reused_select :name, list %> <%= form.reused_select :name, list %> <%= form.reused_select :name, list %> # config/initializers/reused_select.rb ActionView::Helpers::FormBuilder.class_eval do def reused_select(column, values, options={}) value = object.public_send(column) options_id = "options_id-#{values.object_id}" options[:html] = (options[:html] || {}).merge("data-selected": value, "data-options-id": options_id) placeholder_values = [values.detect { |_, v| v == value }] # make select look normal rendered = select column, placeholder_values, options # render the real values only once and reuse them via js if @template.instance_eval { (@reused_select ||= Set.new).add? options_id } rendered << @template.tag(:span, id: options_id, style: "display: none", data: {options: [["", ""]] + values}) end rendered end end # app/assets/javascripts/application.js function reuseSelect(e){ var select = e.target; var $select = $(select); var options_id = $select.data("options-id"); var selected = $select.data("selected"); // values come from json, so be careful to match the type select.innerHTML = ''; // clear out fake options $($("#" + options_id).data("options")).each(function(_, e){ var name = e[0]; var value = e[1]; var option = document.createElement("option"); option.innerText = name; option.value = value; if(value === selected) { option.selected = "selected"; } select.appendChild(option); }); } $("select[data-options-id]").one("mousedown", reuseSelect).one("focus", reuseSelect);