Viewplex
A convenience library that allows you to further separate your views into scoped components.
Installation
Add viewplex
to your list of dependencies in mix.exs
:
def deps do
[
{:viewplex, "~> 0.2.0"}
]
end
Documentation can be be found at https://hexdocs.pm/viewplex.
Usage
Configuration
Configure where your components are located with:
config :viewplex,
path: "lib/my_app_web/components"
Components
Create new components by use
-ing the Viewplex.Component
module and creating a corresponding template with the same name.
lib/my_app_web/components/label_component.ex:
defmodule LabelComponent do
use Viewplex.Component
end
lib/my_app_web/components/label_component.html.eex:
Hello World!
You can also use inline templates:
defmodule LabelComponent do
use Viewplex.Component
def call(_module, assigns), do: ~E"Hello World"
end
Then, call your component inside a template:
<%= component LabelComponent %>
Remember to
import Viewplex.Helpers
in your views.
Naming Conventions
Components should follow the same convention as Elixir modules.
So, if you have a MyAppWeb.Components.LabelComponent
module, the file should be located at: my_app_web/components/label_component.ex
. We also support (and encourage) prefixing the component module name with "Component", following the existant naming convention for Phoenix's views and controllers.
Feature folders
Intead of placing your components in the root folder, you can also group your components like this:
my_app_web/components/label
my_app_web/components/label/label_component.ex
my_app_web/components/label/label_component.html.eex
Filtering assigns
Components can also define fields:
defmodule LabelComponent do
use Viewplex.Component, [:message]
end
<%= component LabelComponent, message: "Hello World" %>
Fields defined in the component will be available as an assign in your template:
<label><%= @message %></label>
This should allow you to simply pass down a map of assigns that will be filtered before actually invoking the component:
<%= component LabelComponent, assigns %>
Content blocks and slots
You can also pass content to your components:
Inline:
<%= component LabelComponent, "Hello World" %>
Or using do blocks:
<%= component LabelComponent do %>
Hello World
<% end %>
This will be available as an assign under the :content
key:
<label><%= @content ></label>
Using named slots:
<%= component LabelComponent do %>
<% slot(:message, "Hello World") %>
<% end %>
<label><%= @slots.message ></label>
Notice that we are using
<%
instead of<%=
. This is necessary because if you output the return of theslot/2
function, you'll actually be rendering its content inside the component's block.
This happens because Viewplex will scan the component block forslot/2
function calls and then extract the slot content so it is available in the template.
Mouting
You can also customize how components are mounted by overriding the mount/1
function:
defmodule LabelComponent do
use Viewplex.Component, [:message]
def mount(assigns) do
user = Users.get_user(assigns.user_id)
{:ok, Map.put(assigns, :user, user)}
end
end
Or disabling the component entirely by returning an {:error, reason}
tuple:
defmodule LabelComponent do
use Viewplex.Component, [:message]
def mount(assigns) do
case Users.get_user(assigns.user_id) do
nil -> {:error, "could not fetch user"}
user -> {:ok, Map.put(assigns, :user, user)}
end
end
end
Todo's
- [ ] Improve documentation
- [ ] Add real use-cases as examples
- [ ] Improve function typespecs
- [x] Support defining components inside group folder (aka "feature folder")
- [x] Publish to Hex
- [ ] Scaffold and setup tasks
- [ ] Component documentation generation
- [ ] Get default component path based on app name
- [ ] Add test samples on README
- [ ] Log mount errors using Logger