Phlexible
A bunch of helpers and goodies intended to make life with Phlex even easier!
Table of Contents
Installation
Install the gem and add to the application's Gemfile by executing:
bundle add phlexible
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install phlexible
Usage
AliasView
Create an alias at a given element, to the given view class.
So instead of:
class MyView < Phlex::HTML
def view_template
div do
render My::Awesome::Component.new
end
end
end
You can instead do:
class MyView < Phlex::HTML
extend Phlexible::AliasView
alias_view :awesome, -> { My::Awesome::Component }
def view_template
div do
awesome
end
end
end
Callbacks
While Phlex does have before_template, after_template, and around_template hooks, they must be defined as regular Ruby methods, meaning you have to always remember to call super when redefining any hook method.
This module provides a more Rails-like interface for defining callbacks in your Phlex views, using ActiveSupport::Callbacks. It implements the same before_template, after_template, and around_template hooks as callbacks.
class Views::Users::Index < Views::Base
include Phlexible::Callbacks
before_template :set_title
def view_template
h1 { @title }
end
private
def set_title
@title = 'Users'
end
end
You can still use the regular before_template, after_template, and around_template hooks as well, but I recommend that if you include this module, that you use callbacks instead.
When used with Rails::AutoLayout, layout callbacks (before_layout, after_layout, around_layout) are also available. See the Rails::AutoLayout section below.
ElementVariants
Declare named variants for one or more HTML elements, then apply them by passing the variant name as a Symbol (or Symbols) in the first argument to the element method. By default, each variant adds a data-variant-<name> attribute to the element.
Extend your view class with Phlexible::ElementVariants and declare variants with variant:
class MyView < Phlex::HTML
extend Phlexible::ElementVariants
variant :divider, on: [:h1, :h2, :h3, :h4, :h5, :h6]
def view_template
h1(:divider) { 'My Title' }
end
end
This will output:
<h1 data-variant-divider>My Title</h1>
Underscored names are rendered as dashes in the data attribute (e.g. :super_big becomes data-variant-super-big).
Modifying attributes
Pass a block to variant to modify the element's attributes when the variant is used. The block receives the attributes hash:
class MyView < Phlex::HTML
extend Phlexible::ElementVariants
variant :external, on: :a do |attributes|
attributes[:target] = '_blank'
end
def view_template
a(:external, href: 'https://example.com') { 'Link' }
end
end
Multiple variants
You can apply multiple variants to a single element by passing more than one Symbol:
a(:external, :primary, href: '/foo') { 'Link' }
Passing an unregistered variant raises ArgumentError.
PageTitle
Helper to assist in defining page titles within Phlex views. Also includes support for nested views, where each desendent view class will have its title prepended to the page title. Simply include Phlexible::PageTitle module and assign the title to the page_title class variable:
class MyView
include Phlexible::PageTitle
self.page_title = 'My Title'
end
Then call the page_title method in the <head> of your page.
ProcessAttributes
This functionality is already built in to Phlex >= 1. This module is only needed for Phlex >= 2.
Allows you to intercept and modify HTML element attributes before they are rendered. This is useful for adding default attributes, transforming values, or conditionally modifying attributes based on other attributes.
Extend your view class with Phlexible::ProcessAttributes and define a process_attributes instance method that receives the attributes hash and returns the modified hash.
class MyView < Phlex::HTML
extend Phlexible::ProcessAttributes
def process_attributes(attrs)
# Add a default class to all elements
attrs[:class] ||= 'my-default-class'
attrs
end
def view_template
div(id: 'container') { 'Hello' }
end
end
This will output:
<div id="container" class="my-default-class">Hello</div>
The process_attributes method is called for all standard HTML elements and void elements, as well as any custom elements registered with register_element.
class MyView < Phlex::HTML
extend Phlexible::ProcessAttributes
register_element :my_custom_element
def process_attributes(attrs)
attrs[:data_processed] = true
attrs
end
def view_template
my_custom_element(name: 'test') { 'Custom content' }
end
end
Rails::ActionController::ImplicitRender
Adds support for default and action_missing rendering of Phlex views. So instead of this:
class UsersController
def index
render Views::Users::Index.new
end
def show
render Views::Users::Show.new
end
end
You can do this:
class UsersController
include Phlexible::Rails::ActionController::ImplicitRender
end
View Resolution
By default, views are resolved using the phlex_view_path method, which constructs a path based on the controller and action name. For example, UsersController#index will look for Views::Users::IndexView.
You can customize this behavior by overriding phlex_view_path in your controller:
class UsersController
include Phlexible::Rails::ActionController::ImplicitRender
private
def phlex_view_path(action_name)
"views/#{controller_path}/#{action_name}"
end
end
This would resolve UsersController#index to Views::Users::Index instead.
Rails::ControllerVariables
NOTE: Prior to 1.0.0, this module was called
ControllerAttributeswith a very different API. This is no longer available since 1.0.0.
Include this module in your Phlex views to get access to the controller's instance variables. It provides an explicit interface for accessing controller instance variables from within the view.
class Views::Users::Index < Views::Base
include Phlexible::Rails::ControllerVariables
controller_variable :first_name, :last_name
def view_template
h1 { "#{@first_name} #{@last_name}" }
end
end
Options
controller_variable accepts one or many symbols, or a hash of symbols to options.
as:- If set, the given attribute will be renamed to the given value. Helpful to avoid naming conflicts.allow_undefined:- By default, if the instance variable is not defined in the controller, an exception will be raised. If this option is totrue, an error will not be raised.
You can also pass a hash of attributes to controller_variable, where the key is the controller
attribute, and the value is the renamed value, or options hash.
class Views::Users::Index < Views::Base
include Phlexible::Rails::ControllerVariables
controller_variable last_name: :surname, first_name: { as: :given_name, allow_undefined: true }
def view_template
h1 { "#{@given_name} #{@surname}" }
end
end
Please note that defining a variable with the same name as an existing variable in the view will be overwritten.
Rails::AutoLayout
Automatically wraps Phlex views in a layout component based on namespace conventions. Include this module in your view classes to enable automatic layout resolution.
class Views::Admin::Index < Phlex::HTML
include Phlexible::Rails::AutoLayout
def view_template
span { 'admin index' }
end
end
Layout Resolution
Layouts are resolved by mapping the view's namespace to a layout class under Views::Layouts::. For example:
| View class | Layout resolved |
|---|---|
Views::Admin::Index |
Views::Layouts::Admin |
Views::Admin::Users::Show |
Views::Layouts::Admin::Users (falls back to Views::Layouts::Admin if not found) |
Views::Dashboard::Index |
Views::Layouts::Application (default fallback) |
Layout classes receive the view instance as a constructor argument and yield the view content:
class Views::Layouts::Admin < Phlex::HTML
def initialize(view)
@view = view
end
def view_template(&block)
div(id: 'admin-layout', &block)
end
end
Controller-Assigned Layouts
You can override automatic resolution by setting a @layout instance variable in your controller. The view must also include ViewAssigns (which AutoLayout includes automatically):
class AdminController < ApplicationController
def index
@layout = Views::Layouts::Custom
end
end
Configuration
Three class attributes control layout resolution:
| Attribute | Default | Description |
|---|---|---|
auto_layout_view_prefix |
'Views::' |
Only views matching this prefix get auto-layout. Set to nil to match all view classes. |
auto_layout_namespace |
'Views::Layouts::' |
Namespace where layout classes are looked up. |
auto_layout_default |
'Views::Layouts::Application' |
Fallback layout when no namespace match is found. Set to nil to render without a layout. |
class Views::Base < Phlex::HTML
include Phlexible::Rails::AutoLayout
self.auto_layout_view_prefix = 'Views::'
self.auto_layout_namespace = 'Views::Layouts::'
self.auto_layout_default = 'Views::Layouts::Application'
end
Layout Callbacks
When Rails::AutoLayout is included, before_layout, after_layout, and around_layout callbacks become available (provided by the Callbacks module):
class Views::Admin::Index < Phlex::HTML
include Phlexible::Rails::AutoLayout
before_layout :log_render
def view_template
span { 'admin index' }
end
private
def log_render
Rails.logger.info "Rendering with layout"
end
end
Caching
Layout resolution is cached per class in production. In development (when Rails.configuration.enable_reloading is enabled), layouts are resolved fresh on each render. You can manually clear the cache with reset_resolved_layout!.
Rails::AElement
No need to call Rails link_to helper, when you can simply render an anchor tag directly with Phlex. But unfortunately that means you lose some of the magic that link_to provides. Especially the automatic resolution of URL's and Rails routes.
The Phlexible::Rails::AElement module passes through the href attribute to Rails url_for helper. So you can do this:
Rails.application.routes.draw do
resources :articles
end
class MyView < Phlex::HTML
include Phlexible::Rails::AElement
def view_template
a(href: :articles) { 'View articles' }
end
end
You can also pass :back as the href value, which will use the referrer URL if available, or fall back to javascript:history.back():
a(href: :back) { 'Go back' }
Rails::ButtonTo
Generates a form containing a single button that submits to the URL created by the set of options.
It is similar to Rails button_to helper, which accepts the URL or route helper as the first argument, and the value/content of the button as the block.
Phlexible::Rails::ButtonTo.new(:root) { 'My Button' }
The url argument accepts the same options as Rails url_for.
The form submits a POST request by default. You can specify a different HTTP verb via the :method option.
Phlexible::Rails::ButtonTo.new(:root, method: :patch) { 'My Button' }
Options
:class- Specify the HTML class name of the button (not the form).:form_attributes- Hash of HTML attributes for the form tag.:data- This option can be used to add custom data attributes.:params- Hash of parameters to be rendered as hidden fields within the form.:method- Symbol of the HTTP verb. Supported verbs are :post (default), :get, :delete, :patch, and :put.
Rails::MetaTags
A super simple way to define and render meta tags in your Phlex views. Just render the
Phlexible::Rails::MetaTagsComponent component in the head element of your page, and define the
meta tags using the meta_tag method in your controllers.
class MyController < ApplicationController
:description, 'My description'
:keywords, 'My keywords'
end
class MyView < Phlex::HTML
def view_template
html do
head do
render Phlexible::Rails::MetaTagsComponent
end
body do
# ...
end
end
end
end
Rails::Responder
If you use Responders, Phlexible provides a responder to support implicit rendering similar to Rails::ActionController::ImplicitRender above. It will render the Phlex view using respond_with if one exists, and fall back to default rendering.
Just include it in your ApplicationResponder:
class ApplicationResponder < ActionController::Responder
include Phlexible::Rails::ActionController::ImplicitRender
include Phlexible::Rails::Responder
end
Then simply respond_with in your action method as normal:
class UsersController < ApplicationController
def new
respond_with User.new
end
def index
respond_with User.all
end
end
This responder requires the use of ActionController::ImplicitRender, so don't forget to include that in your ApplicationController.
If you use Rails::ControllerVariables in your view, and define a resource attribute, the responder will pass that to your view.
Development
After checking out the repo, install dependencies with:
bin/setup
This gem supports varios major Phlex versions, as defined in the Appraisal file.
To run tests for all supported Phlex versions:
bundle exec appraisal rails t
To run tests for a specific Phlex versions, call:
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/phlexible. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Phlexible project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.