Faster Page Through Reused HTML Options with Rails & JS

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

 

Leave a comment