Handle simple remote modal dialogs in rails applications

I will show you how to handle simple remote modal dialog rendering in rails apps.

Sometimes we do not want to write big javascript applications to have working remote modals in our rails applications. And rendering modals on the client is to bit of a hassle? Why not let the server (fastest html renderer in town) respond with complete dialogs?

Iam using Bootstrap Modals for this example. But it will work for any JS modal library.

Step 1 – Add a new modal layout.

<!-- app/views/layouts/modal.html.erb -->
<div class="modal fade" tabindex="-1" role="dialog">
  <div class="modal-dialog">
    <div class="modal-content">
      <%= yield %>
    </div><!-- /.modal-content -->
  </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

We also need to define a place where the dialogs will be rendered. Add the following to the application layout file.

<!-- app/views/layouts/application.html.erb -->
<div id="modal-holder"></div>

Step 2 – Add modal javascript

This will enable links with data-behavior=”modal” attribute to be rendered in modal windows.

$ ->
  modal_holder_selector = "#modal-holder"

  $(document).on "click", "[data-behavior='modal']", ->
    $('body').modalmanager('loading')
    location = $(this).attr("href")
    $.get location, (data)->
      $(modal_holder_selector).html(data).find(".modal").modal("show")
    false

  $(document).on "ajax:success", "[data-behavior='modal-form']", (event, data, status, xhr) ->
    # make sure this event is observered by document and not by dom node itself.
    url = xhr.getResponseHeader("Location")
    # Navigates to given location header in http response or closes the modal handler.
    if url
      window.location = url
    else
      # FUTURE TODO: make sure only the modal which was called last will be destroyed.
      $(".modal").modal("hide")
    false

Step 3 – Modify controller action

Now change the render layout for the controller action like so:

class ApplesController < ApplicationController
  def new
    @apples = Apple
    render layout: 'modal'
  end
end

Step 4 – Use it

<%= link_to 'Create apple', new_apple_path, data: { behavior: 'modal' } %>

Please make sure the rendered form will have a data-behavior=”modal-form” attribute to successfully close the dialog after valid ajax response.

I18n Rails – How to organize your locales in larger projects

As our rails projects grow bigger and bigger, the i18n locale files easily get messy and will loose their structure after a while. Especially when you have multiple software developers work on the same project.

So we develped a solution for us that is easier to handle and to maintain. The starting point is from the ruby on rails guides combined with this blog post:

We separated our yml files to the folders given by the rails structure. The yml files themself are also structured that way. This is a bit more efford in the beginning, but it is very nice to maintain, because you always know, where you have to put your locales and always know where to look for them!

|-defaults
|---de.rb
|---en.rb
|-models
|---survey
|-----de.rb
|-----en.rb
|-views
|---defaults
|-----de.rb
|-----en.rb
|---surveys
|-----de.rb
|-----en.rb
|---users
|-----de.rb
|-----en.rb

Each file now only handles the content of one logical controller unit. The views/surveys files now have the content for each action. We group them in the action to logical groups that always look the same. We have a title, the breadcrumbs and then we have a unit for each form.

en:
  views:
    surveys:
      index:
        title:
          text: :defaults.surveys
        breadcrumbs:
          surveys:
            text: :defaults.surveys
        form_q:
          fields:
            search_field:
              placeholder: 'Search surveys...'
              legend:
                text: 'Search'
              topics_title_cont_any:
                label: 'Topics'
                input:
                  placeholder: ''
              title_cont:
                label: 'Title'
                input:
                  placeholder: ''
        submit:
          text: :views.defaults.search
          disable_with: :views.defaults.form.searching

The problem with a solution like that is, that you have lots of redundant data. Take breadcrumbs for example. In each views folder there are different actions that have the same breadcrumb navigation. But there is a nice feature that you can use for that:

We put all business dependend locals into the defaults or views/defaults folders and reference them with e.g. :defaults.surveys. That makes it easy to have on each page every element, that can be translated to each language, but also removes redundant translations. But if you come to a language that make it harder to translate everything with the same translation you can translate that one separately!! Which can be quite important for example for languages like chinese.