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);