Â# Welcome to actioncomponent gem
Frontend components for Ruby on Rails: group your view logic, html, css and javascript files in components to be used in views or directly rendered from controllers.
How to use?
Gemfile
gem 'actioncomponent'
What do you get?
Organize your frontend code
Your can group your frontend stuff by domain and organize the UI of your Rails app like this:
app
├── components
│ └── user
│ └── filter
│ └── template.html.erb
│ └── view_model.rb
│ └── style.sass
│ └── list
│ └── template.html.erb
│ └── style.sass
And, in your views, or in your components, your can render it!
app/view/users/index.html.erb
<%
import_action_component 'Filter', path: 'user/filter'
import_action_component 'List', path: 'user/list'
%>
<%= Filter() %>
<%= List(users: @users) %>
Or, you can completely drop your view and tell your controller to render a User Index Compoment
app
├── components
│ └── user
│ └── filter
│ └── template.html.erb
│ └── list
│ └── template.html.erb
│ └── index
│ └── template.html.erb
app/components/user/index/template.html.erb
<%
import_action_component 'Filter', path: 'user/filter'
import_action_component 'List', path: 'user/list'
%>
<%= Filter() %>
<%= List(users: @users) %>
app/controllers/users_controller.rb
# ...
# TODO
# ...
Pass data to your components
When rendering your can pass data in a hash/named parameters format. The data will be exposed in your template through a view model.
Supose that you have a header component
app
├── components
│ └── header
│ └── template.html.erb
And you want to render this component in your layout file.
app/views/layouts/application.html.erb
<%
import_action_component 'Header', path: 'header'
%>
<%= Header(my_user: current_user) %>
You can access the user attribute in your template like this:
app/components/header/template.html.erb
<header>
Hi, <%= my_user.name %>
</header>
View Models
The methods available inside the template will be those defined in your view model. If no view model is defined for your component then our ActionComponent::Component::ViewModel will be used. The view model is instantiated with the arguments that you provide when calling your component.
ActionComponent::Component::ViewModel
It takes all the constructor arguments (it must be a hash/named args) and creates a getter for each one of them. Example:
vm = ActionComponent::Component::ViewModel.new(name: 'John', age: 12)
vm.name # John
vm.age # 12
Create your own ViewModel: handle complex view logic
We only use our own view model if there is no view_model.rb file inside your component's folder. This file should declare a class following all the Rails naming conventions.
So, imagine that we want our vm to have a random_greeting method. We can can create a view model like this:
Use helpers inside your components
When initializing the view model we also provide two additionals parameters (:h and :helper) so you can have access to rails helpers.
As all view model methods are available to your template your will have access to a h
or helper
like this:
Example of a component's templatefile
<div class="child">
<div class="date">
<%= helper.l(Date.new(2019, 01, 03)) %>
</div>
<div class="routes">
<%= h.users_path %>
</div>
<div class="translation">
<%= h.t('hello')%>
</div>
</div>
Your can create custom view models that inherits from ours
app/components/header/view_model.rb
class Header::ViewModel < ActionComponent::Component::ViewModel
def random_greeting
hi_text = ['Hi', 'Yo'].sample
"#{hi_text}, #{user.name}"
end
end
Now the template can access the method like this:
app/components/header/template.html.erb
<header>
<%= random_greeting %>
</header>
Use helpers inside a ViewModel
A helper
and h
attribute are passed when instantiating a ViewModel.
class Header::ViewModel < ActionComponent::Component::ViewModel
def random_greeting
hi_text = ['Hi', 'Yo'].sample
"#{hi_text}, #{user.name}."
end
def formated_date
h.l(Date.today)
end
end
app/components/header/template.html.erb
<header>
<%= random_greeting %> Today is <%= formated_date %>
</header>
Access your controller in your VM or Template
When rendering a component, a c
or controller
parameter is passed through so you can have access to all your request data inside your VM or template.
view_model
class ControllerData::ViewModel < ActionComponent::Component::ViewModel
def formated_page
"Current page: #{c.params[:page]}"
end
def formated_search
"Searching for: #{controller.params[:search]}"
end
end
template.erb
<%= formated_search %>
<%= formated_page %>
Style namespacing
Each rendered component will be wrapped inside a div with a dynamic data attribute according to the component path. This means that you can create custom css for each component. Example:
app
├── components
│ └── user_page
│ └── header
│ └── template.html.erb
If we render the header inside a component it will generate a HTML like this
<div class='action-component' data-action-component-id='user_page-header'>
...
...
</div>
Then you can customize the component with the following css:
[data-action-component-id=project-index] {
background: red;
}
Where do I put my CSS files?
Where it belongs: in your component folder. It doens't matter the name or de number of css/sass/less files that you have... Just don't forget to namespace it!
Also in your application.css file you should require all the css from the component folder. You can do that with a relative require_tree
. Like this:
application.sass
//*=require_tree ../../../components
@import "fullcalendar.min"
@import "bootstrap"
@import "datepicker"
// ...
Configuration
You can change some parameters by creating a initializer on your app
config/initializers/action_component.rb
ActionComponent.configure do |config|
# Folder path to look for components
config.components_path = 'app/components'
# Default name for the html/erb/slim/etc template file inside the component folder
config.template_file_name = 'template'
# Default name for the view model file inside the component folder
config.view_model_file_name = 'view_model'
end