Materialize is a CSS framework built around Google’s Material Design principles. In general, I would describe material design as being very focused with a kind of minimalist aesthetic. Components feel sharp and generally stand out in a subtle yet noticeable way. User interaction such as clicks are accompanied by small yet engaging and visually appealing effects. You can read more about Material Design at Google’s website. Here I’ll walk you through a tutorial on how to use materialize with ruby on rails with a focus on simple form.
Set Up
Conveniently, there is a gem that bundles materialize with rails. In your gem file include:
gem ‘materialize-sass’
This gem is located at https://github.com/mkhairi/materialize-sass
Remember that since we are using the sass css precompiler, we will also need to include:
gem ‘sass-rails’
@import “materialize”;
You can then include other pages such as ‘home’ or ‘main’ by writing:
@import “home”;
@import “main”;
Finally, in your application.js you need to require jQuery and materialize-sprockets by writing:
//= require jquery
//= require materialize-sprockets
Using Materialize
Materialize uses some conventions that bootstrap users will find familiar. It comes with a 12 column grid layout that uses small, medium and large columns for different device sizes. It also comes with a responsive container class that you can use to wrap your body content.
Materialize Icons
In the head of your document use an external stylesheet linking to the google material icons.
= stylesheet_link_tag “https://fonts.googleapis.com/icon?family=Material+Icons"
You can then specify icons by name inside an “i” tag with class ‘material-icons’. The following would produce the materialize menu icon
<i class = “material-icons”>menu</i>
Materialize Navbar
You can use the following haml to create a mobile responsive navbar that includes a drop down menu
%nav.red
.nav-wrapper
%div.brand-logo
// Replace this with an image
My Logo
%a{href: "#", class: "button-collapse", data: {activates: "example"}}
%i.material-icons menu
// Normal NavBar that goes across the top
%ul.right.hide-on-med-and-down
%li
=link_to "Link 1"
%li
= link_to "Link 2"
%li
= link_to "Link 3"
// Drop Down Menu
%li
%a{href: "#", class: "dropdown-button", data: {activates: "myDropdown"}}
= "Drop Down Menu"
%i.material-icons.right arrow_drop_down
%ul{id: "myDropdown", class: "dropdown-content"}
%li
= link_to "Drop Down Link 1"
%li
= link_to "Drop Down Link 2"
%li
= link_to "Drop Down Link 3"
// Mobile Sidebar
%ul#example.side-nav
%li
= link_to "Link 1"
%li
= link_to "Link 2"
%li
= link_to "Link 3"
// Drow Down Menu on the mobile sidebar
%li
%a{href: "#", class: "dropdown-button", data: {activates: "myDropDownSide"}}
= "Admin Menu"
%i.material-icons.right.fix-line-height arrow_drop_down
%ul{id: "myDropDownSide", class: "dropdown-content dropdown-content-sidebar-fix"}
%li
= link_to "Drop Down Link 1"
%li
= link_to "Drop Down Link 2"
%li
= link_to "Drop Down Link 3”
Materialize and Rails Simple Form
Materialize form elements require certain wrapper elements and classes to work. These are not the same as what simple form defines for your inputs by default, meaning it can take some extra work to get materialize and simple form working nicely together.
Simple form defines the wrapper and other elements included with the input in the simple_form.rb initializer file located in config.
You can remove all wrapper elements and all companion elements inserted by simple form by using f.input_field instead of f.input. So for example, to ignore the simple form wrappers and make our own simple string input that works with materialize, we could write…
= simple_form_for @myModel do |f| .input-field =f.input_field :my_field %label{for: “my_field"} My Label Text
However this becomes somewhat tedious to do every time we need to write a form element. A better approach is to create our own wrapper in the simple form initializer. In the simple_form.rb we can write:
config.wrappers :materialize_form, class: 'input-field my-class‘, error_class: 'has_error' do |b| b.use :html5 b.use :placeholder b.use :input b.use :label b.use :error, wrap_with: { tag: 'p' , class: 'error-text'} # b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } end
The above creates a custom wrapper called materialize_form_class. By using this wrapper every input by default gets a label and both are contained within a div with two classes, input-field and my-class. Input-field is used by materialize and my-class is a custom class that we could use to control layout and other properties. This wrapper also tells simple form to append the class ‘has_error’ to the wrapper div when the input contains errrors. Furthermore, when an error is present, within the wrapper div a ‘p’ tag with class error-text gets added after the label. With this we can control how we want to display error messages.
We can tell simple form to use this wrapper by writing:
= simple_form_for @myModel, wrapper: :materialize_form_class
Now all elements within this form will have the :materialize_form_class wrapper.
We can tell simple form to use our wrapper by default by adding the following line into the simple_form.rb file.
config.default_wrapper = :materialize_form
We can also declare our wrapper for individual elements within a form.
=simple_form_for @myModel do |f| = f.input :my_field, wrapper: :materialize_form_class
When using our :materialize_form_class wrapper we can change the label or wrapper html by using
=f.input :my_field, label: “New label", wrapper_html: {class: “newclass”}
This adds a class of “newclass” to our wrapper div and changes the label to display “New label”.
Materialize CSS comes with button classes that we can use to give our buttons a nice effect when clicked. We can tell simple form to use a default button class. In simple_form.rb write:
config.button_class = “btn waves-effect waves-light”
We can use this in simple form by writing:
=f.button :button
This will create a button with type submit.
We can also use simple form to write our own custom input components. Let’s write a custom input component for the materialize switch. The markup for this switch is:
<div class="switch"> <label> “Off” <input type="checkbox"> <span class="lever"></span> “On” </label> </div>
We will create a custom input called my_switch which will output the above markup automatically.
Start by adding a new folder to your app directory called inputs if it does not already exist. In this folder create a new file called my_switch_input.rb and start by writing the following:
class MySwitchInput < SimpleForm::Inputs::Base def input end end
Within the input method we can start building up our template
def input template.content_tag(:div, class: 'switch') do template.content_tag(:label) end end
This would give us a label
class MySwitchInput < SimpleForm::Inputs::Base def input template.content_tag(:div, class: 'switch') do template.concat label_tag end end def label_tag template.content_tag(:label) do template.concat "Off" template.concat @builder.input_field(attribute_name, {type: :checkbox}) template.concat span_tag template.concat "On" end end def span_tag template.content_tag(:span, class: 'lever') do end end end
Within the outermost div tag with class switch we call our method label_tag to nest a label tag. Within this tag we nest some text, a simple form input field with type ‘checkbox’ and a span tag defined in our span_tag method.
The @builder allows us to declare simple form elements like a text_field or input_field within our custom input. Here I used input_field to make sure there is no additional wrapper markup added around our checkbox input element.
We can now use this element in our forms as follows
=f.input :my_field, as: :my_switch
Keep in mind that this will give the input whatever markup is defined in the default wrapper class.
Remember that we can get the input by itself with no wrapper by using:
=f.input_field :my_field, as: :my_switch
We can even tell simple form what wrapper to use for our new custom input by including the following in our simple_form.rb initializer:
config.wrapper_mappings = { :my_switch => :my_wrapper}
Now our :my_switch component will use the wrapper :my_wrapper by default. Pretty convenient!