snowhite v2.1.3 Snowhite.Builder.Module View Source
The module builder is the one responsible for building modules. It's basically a wrapper over Phoenix's LiveView but with some extra sugar.
It is meant to provide a simple way to declare new modules and interact with server. It automatically assigns default assign (params
and options
) to your socket and support some extra features such as timed callbacks.
By convention it is highly recommended to scope you modules under Snowhite.Modules.*
to avoid collision with other existing modules.
Mounting
Most of the time, your module will have some extra assigns coming from either computed values or a Server. These has to be defined at mount of the module. To do so, all you need to do is override the mount/1
function and perform your assignations.
Examples
defmodule Snowhite.Modules.MyModule do
use Snowhite.Builder.Module
def mount(socket) do
assign(socket, :message, "Hello world!") # Only has to return the updated socket,
end
end
Assign @message
will now be available in templates. At mount time, it's also important to note that the module will subscribe to Snowhite's PubSub under a topic matching is module name. For instance Snowhite.Modules.Clock
subscribes on "snowhite:modules:clock"
Module options
To make sure you pass in the good options to a module, it is required that you declared them by overriding module_options/0
. It follows a simple pattern of [{option_name, type}]
where type is either :required
or {:optional, fallback}
.keyword()
This has three advantages. It raises if you miss a required option, it raises if you add an unsupported option and put in a fallback if you omit an optional option.
For instance, for weather modules, the city_id is required as you can't have the weather of a place you don't know. But you also might want to set the locale to "fr" if you wish to, but otherwise, english should do the job.
Examples
defmodule Snowhite.Modules.MyModule do
use Snowhite.Builder.Module
def module_options do
[
message: :required, # it requires a message
color: {:optional, :white} # it can be of any color but defaults to white
]
end
end
Extra applications
The best way to keep data consistent around multiple Snowhite pages, is to have them centralized in a GenServer or another type of Server. This is where extra applications comes in handy.
Examples use case
Before moving to Snowhite.Modules.Clock.Server
, the clock was handled by the Live view itself. Even though it works, it has flaws. The clocks were not in sync since the pages weren't open at the exact same time. So we decided to use a Server.
The Snowhite.Modules.Clock.Server
is responsible for controlling clock in an atomic way. When it gets updated, it broadcasts an event so every pages updates at the exact same time. All default modules includes this concept, it might be interesting to take a look at those for inspiration.
Examples
defmodule Snowhite.Modules.MyModule do
use Snowhite.Builder.Module
def applications(options) do # applications/1 receives options of the module for the application supervision
[
{Snowhite.Modules.MyModule.Server, []} # They are declared as {module, args}.
]
end
end
By now, every Server is uniquely launched. It is sufficient for most use case, however, this would prevent you to have, for instance, multiple weather.
Accessing module's config
The builder also includes convenient functions to reach module's config when it is needed. For instance, to use the weather you need an API key for OpenWeather. As this value should not be commited, it's better to have it in an env variable.
You can register it that way
config :snowhite, :modules,
my_module: [
api_key: System.get_env("SOME_API_KEY")
]
# In your module
defmodule Snowhite.Modules.MyModule do
use Snowhite.Builder.Module
def mount(socket) do
assign(socket, :api_key, config(:api_key))
end
end
Note: It is not required to have the config put in the assign, this is just an example on how to use the config/1 function.
Link to this section Summary
Functions
This macro is required as it will make scheduled events from every/3
working.
The builder macro supports the following options
Sets default assign in a module. The templates is rendered with the request params so you can use them in you Modules. Same for options that you pass when declaring a module.
Broadcasts an event on the Snowhite's PubSub server. All modules subscribes on it so they can communicate with each other.
The every/3
macro comes in handy when you want to perform action every x ms.
Same as get_options/3
but with nil
as fallback value
Get an option from the assigns or the socket with an optional fallback. As the options are matched against your module options, the default value should be the one declared in module_options/0
.
Gets a configuration key for a specific module. By default, any module registers it's configuration under snowhite.modules.[module_name]
.
Link to this section Types
Specs
Link to this section Functions
This macro is required as it will make scheduled events from every/3
working.
Specs
__using__([builder_option()]) :: Macro.t()
The builder macro supports the following options
:config_key
, key to use when fetching configs. Fallbacks to the modules name (MyModule
->my_module
):topic
, the topic to register on Snowhite's PubSub, it fallbacks tosnowhite:modules:my_module
Specs
assign_session(Phoenix.LiveView.Socket.t(), map()) :: Phoenix.LiveView.Socket.t()
Sets default assign in a module. The templates is rendered with the request params so you can use them in you Modules. Same for options that you pass when declaring a module.
Specs
Broadcasts an event on the Snowhite's PubSub server. All modules subscribes on it so they can communicate with each other.
Specs
every(non_neg_integer() | atom(), atom(), function()) :: Macro.t()
The every/3
macro comes in handy when you want to perform action every x ms.
It either supports an integer (for a given amount of seconds, it is recommended to use the ~d()
sigil from Snowhite.Helpers.Timing
to inscrease readability) or an atom to get the amount of ms from the options. It also needs an unique name so every callbacks has it's own name and a function to be called.
Examples
defmodule Snowhite.Modules.MyModule do
use Snowhite.Builder.Module # Imports the ~d sigil by default
every(~d(1m), :count, &count/1)
def count(socket) do # called every minutes
assign(sockey, :counter, socket.assigns.counter + 1)
end
end
Specs
get_option(Phoenix.Socket.t() | %{options: keyword()}, atom()) :: any()
Same as get_options/3
but with nil
as fallback value
Specs
get_option(Phoenix.Socket.t() | %{options: keyword()}, atom(), any()) :: any()
Get an option from the assigns or the socket with an optional fallback. As the options are matched against your module options, the default value should be the one declared in module_options/0
.
Specs
Gets a configuration key for a specific module. By default, any module registers it's configuration under snowhite.modules.[module_name]
.
i.e. For the Snowhite.Modules.Clock
module, it's configuration key is :clock
so you can configure it that way
config :snowhite, :modules, clock: [format: ""]