drab v0.10.5 Drab.Core View Source
Drab module providing the base of communication between the browser and the server.
Drab.Core
defines the method to declare client-side events, which are handled server-side in
the commander module. Also provides basic function for running JS code directly from Phoenix
on the browser.
Commander
Commander is the module to keep your Drab functions (event handlers) in. See Drab.Commander
for more info, and just for this part of docs let’s assume you have the following one defined:
defmodule DrabExample.PageCommander do
use Drab.Commander, modules: []
defhandler button_clicked(socket, payload) do
socket |> console("You've sent me this: #{payload |> inspect}")
end
end
Events
Events are defined directly in the HTML by adding the drab
attribute with the following pattern:
<button drab='event_name#options:event_handler_function_name(argument)'>clickme</button>
event_name
is the DOM event name, eg. “click”, “blur”event_handler_function_name
- the name of the event handler function in the commander on the server sideoptions
- optional, so far the only available option is “debounce(milliseconds)” for “keyup” eventargument
- optional, additional argument to be passed to the event handler function as a third argument
Example:
<button drab='click:button_clicked'>clickme</button>
Clicking above button launches DrabExample.PageCommander.button_clicked/2
on the server side.
<button drab='click:button_clicked(42)'>clickme</button>
Clicking the button above launches DrabExample.PageCommander.button_clicked/3
on the server
side, with third argument of value 42. This is evaluated on the client side, so it could be
any valid JS expression:
<button drab='click:button_clicked({the_answer: 42})'>
<button drab='click:button_clicked(window.location)'>
You may have multiple events defined for a DOM object, but the specific event may appear there
only once (can’t define two handlers for one event). Separate event:handler
pairs with
whitespaces:
<button drab='click:button_clicked mouseover:prepare_button'>clickme</button>
Shortcut form
There are few shortcuts for the most popular events: click
, keyup
, keydown
, change
.
For those events an attribute drab-EVENTNAME
must be set. The following is an equivalent
for the previous one:
<button drab-click='button_clicked'>clickme</button>
As above, there is a possibility to define multiple event handlers for one DOM object, but the only one handler for the event. The following form is valid:
<button drab-click='button_clicked' drab-mouseover='prepare_button(42)'>clickme</button>
But the next one is prohibited:
<button drab-click='handler1' drab-click='handler2'>INCORRECT</button>
In this case you may provide options with drab-options
attribute, but only when you have
the only one event defined.
There is a possibility to configure the shortcut list:
config :drab, MyAppWeb.Endpoint,
events_shorthands: ["click", "keyup", "blur"]
Please keep this list short, as it affects client script performance.
Defining optional argument in multiple nodes with drab-argument
attribute
If you add drab-argument
attribute to any tag, all children of this tag will use this as
an optional attribute. Notice that the existing arguments are not overwritten, so this:
<div drab-argument='42'>
<button drab-click='button_clicked'>
<button drab-click='button_clicked(43)'>
</div>
is the equivalent to:
<button drab-click='button_clicked(42)'>
<button drab-click='button_clicked(43)'>
Handling event in any commander (Shared Commander)
By default Drab runs the event handler in the commander module corresponding to the controller, which rendered the current page. But it is possible to choose the module by simply provide the full path to the commander:
<button drab-click='MyAppWeb.MyCommander.button_clicked'>clickme</button>
Notice that the module must be a commander module, ie. it must be marked with
use Drab.Commander
, and the function must be marked as public with Drab.Commander.public/1
macro.
Form values
If the sender object is inside a <form>
tag, it sends the “form” map, which contains values
of all the inputs found withing the form. Keys of that map are “name” attribute of the input or,
if not found, an “id” attribute. If neither “name” or “id” is given, the value of the form is
not included.
Control of element enabled/disabled state of element
By default, Drab takes control of enabled/disabled state of the Drab element. It disables the element when the handler is still running, to prevent multiple clicks. Element is back to the previous (enabled) state after the handler finish. Also in case of disconnection, Drab-controlled elements are disabled.
You may turn off this behaviour globally using the config options, see Drab.Config
.
There is also a possibility to turn it off individually, using drab-no-disable
attribute:
<button drab-click="clickety" drab-no-disable>Button</button>
Running Elixir code from the Browser
There is the Javascript method
Drab.exec_elixir()
in the global Drab
object, which allows you to run the Elixir function defined in the Commander.
Store
Analogically to Plug, Drab can store the values in its own session. To avoid confusion with
the Plug Session session, it is called a Store. You can use functions: put_store/3
and
get_store/2
to read and write the values in the Store. It works exactly the same way as
a “normal”, Phoenix session.
- By default, Drab Store is kept in browser Local Storage. This means it is gone when you close
the browser or the tab. You may set up where to keep the data with
drab_store_storage
config entry, see Drab.Config - Drab Store is not the Plug Session! This is a different entity. Anyway, you have an access to the Plug Session (details below).
- Drab Store is stored on the client side and it is encrypted.
Session
Although Drab Store is a different entity than Plug Session (used in Controllers), there is a way
to access the Session. First, you need to whitelist the keys you want to access in
access_session/1
macro in the Commander (you may give it a list of atoms or a single atom).
Whitelisting is due to security: it is kept in Token, on the client side, and it is signed
but not encrypted.
defmodule DrabPoc.PageCommander do
use Drab.Commander
onload :page_loaded,
access_session :drab_test
def page_loaded(socket) do
socket
|> update(:val, set: get_session(socket, :drab_test), on: "#show_session_test")
end
end
There is no way to update the session from Drab. Session is read-only.
Broadcasting
Normally Drab operates on the user interface of the browser which generared the event, but you may use it for broadcasting changes to all connected browsers. Drab uses a topic for distinguishing browsers, which are allowed to receive the change.
Broadcasting function receives socket
or topic
as the first argument. If socket
is used,
function derives the topic
from the commander configuration. See
Drab.Commander.broadcasting/1
to learn how to configure the broadcasting options. It is also
possible to subscribe to the external topic in a runtime, using Drab.Commander.subscribe/2
.
Broadcasting functions may be launched without the socket
given. In this case, you need
to define it manually, using helper functions: Drab.Core.same_path/1
, Drab.Core.same_topic/1
and Drab.Core.same_controller/1
. See broadcast_js/3
for more.
List of broadcasting functions:
-
Drab.Core.broadcast_js/3
Drab.Core.broadcast_js!/3
Link to this section Summary
Types
Return value of broadcast_js/2
Input: binary string or safe
Types returned from the browser
Returned status of all Core operations
Subject for broadcasting
Functions
Asynchronously executes the javascript on all the browsers listening on the given subject
Exception raising version of exec_js/2
Synchronously executes the given javascript on the client side
Returns the value of the Plug Session represented by the given key
Returns the value of the Plug Session represented by the given key or default
,
when key not found
Returns the value of the Drab store represented by the given key
Returns the value of the Drab store represented by the given key or default
when key not found
Saves the key => value in the Store. Returns unchanged socket
Helper for broadcasting functions, returns topic for a given controller and action
Helper for broadcasting functions, returns topic for a given endpoint, controller and action
Helper for broadcasting functions, returns topic for a given controller
Helper for broadcasting functions, returns topic for a given endpoint and controller
Helper for broadcasting functions, returns topic for a given URL path
Helper for broadcasting functions, returns topic for a given endpoint and URL path
Helper for broadcasting functions, returns topic for a given topic string
Helper for broadcasting functions, returns topic for a given topic string
Like this/1
, but returns selector of the object ID
Returns the selector of object, which triggered the event. To be used only in event handlers
Returns the unique selector of the DOM object, which represents the shared commander of the event triggerer
Link to this section Types
Return value of broadcast_js/2
Input: binary string or safe
Return value of exec_js/2
Types returned from the browser
Returned status of all Core operations
subject() :: Phoenix.Socket.t() | String.t() | {atom(), String.t()} | list()
Subject for broadcasting
Link to this section Functions
broadcast_js(subject(), input(), Keyword.t()) :: bcast_result()
Asynchronously executes the javascript on all the browsers listening on the given subject.
The subject is derived from the first argument, which could be:
socket - in this case broadcasting option is derived from the setup in the commander. See
Drab.Commander.broadcasting/1
for the broadcasting optionssame_path(string) - sends the JS to browsers sharing (and configured as listening to same_path in
Drab.Commander.broadcasting/1
) the same urlsame_commander(atom) - broadcast goes to all browsers configured with :same_commander
same_topic(string) - broadcast goes to all browsers listening to this topic; notice: this is internal Drab topic, not a Phoenix Socket topic
First argument may be a list of the above.
The second argument is a JavaScript string.
See Drab.Commander.broadcasting/1
to find out how to change the listen subject.
iex> Drab.Core.broadcast_js(socket, "alert('Broadcasted!')")
{:ok, :broadcasted}
iex> Drab.Core.broadcast_js(same_path("/drab/live"), "alert('Broadcasted!')")
{:ok, :broadcasted}
iex> Drab.Core.broadcast_js(same_controller(MyApp.LiveController), "alert('Broadcasted!')")
{:ok, :broadcasted}
iex> Drab.Core.broadcast_js(same_topic("my_topic"), "alert('Broadcasted!')")
{:ok, :broadcasted}
iex> Drab.Core.broadcast_js([same_topic("my_topic"), same_path("/drab/live")],
"alert('Broadcasted!')")
{:ok, :broadcasted}
Returns {:ok, :broadcasted}
exec_js!(Phoenix.Socket.t(), input(), Keyword.t()) :: return() | no_return()
Exception raising version of exec_js/2
Examples
iex> socket |> exec_js!("2 + 2")
4
iex> socket |> exec_js!("nonexistent")
** (Drab.JSExecutionError) nonexistent is not defined
(drab) lib/drab/core.ex:100: Drab.Core.exec_js!/2
iex> socket |> exec_js!("for(i=0; i<1000000000; i++) {}")
** (Drab.JSExecutionError) timeout
(drab) lib/drab/core.ex:100: Drab.Core.exec_js!/2
iex> socket |> exec_js!("for(i=0; i<10000000; i++) {}", timeout: 1000)
** (Drab.JSExecutionError) timeout
lib/drab/core.ex:114: Drab.Core.exec_js!/3
exec_js(Phoenix.Socket.t(), input(), Keyword.t()) :: result()
Synchronously executes the given javascript on the client side.
Returns tuple {status, return_value}
, where status could be :ok
or :error
,
and return value contains the output computed by the Javascript or the error message.
Options
timeout
in milliseconds
Examples
iex> socket |> exec_js("2 + 2")
{:ok, 4}
iex> socket |> exec_js("not_existing_function()")
{:error, "not_existing_function is not defined"}
iex> socket |> exec_js("for(i=0; i<1000000000; i++) {}")
{:error, :timeout}
iex> socket |> exec_js("alert('hello from IEx!')", timeout: 500)
{:error, :timeout}
get_session(Phoenix.Socket.t(), atom()) :: term()
Returns the value of the Plug Session represented by the given key.
counter = get_session(socket, :userid)
You must explicit which session keys you want to access in :access_session
option in
use Drab.Commander
or globally, in config.exs
:
config :drab, MyAppWeb.Endpoint,
access_session: [:user_id]
get_session(Phoenix.Socket.t(), atom(), term()) :: term()
Returns the value of the Plug Session represented by the given key or default
,
when key not found.
counter = get_session(socket, :userid, 0)
See also get_session/2
.
get_store(Phoenix.Socket.t(), atom()) :: term()
Returns the value of the Drab store represented by the given key.
uid = get_store(socket, :user_id)
get_store(Phoenix.Socket.t(), atom(), term()) :: term()
Returns the value of the Drab store represented by the given key or default
when key not found
counter = get_store(socket, :counter, 0)
put_store(Phoenix.Socket.t(), atom(), term()) :: Phoenix.Socket.t()
Saves the key => value in the Store. Returns unchanged socket.
put_store(socket, :counter, 1)
Helper for broadcasting functions, returns topic for a given controller and action.
iex> same_action(DrabTestApp.LiveController, :index)
"controller:Elixir.DrabTestApp.LiveController#index"
Helper for broadcasting functions, returns topic for a given endpoint, controller and action.
To be used in multiple endpoint environments only.
iex> same_action(DrabTestApp.Endpoint, DrabTestApp.LiveController, :index)
{DrabTestApp.Endpoint, "controller:Elixir.DrabTestApp.LiveController#index"}
Helper for broadcasting functions, returns topic for a given controller.
iex> same_controller(DrabTestApp.LiveController)
"__drab:controller:Elixir.DrabTestApp.LiveController"
Helper for broadcasting functions, returns topic for a given endpoint and controller.
To be used in multiple endpoint environments only.
iex> same_controller(DrabTestApp.Endpoint, DrabTestApp.LiveController)
{DrabTestApp.Endpoint, "__drab:controller:Elixir.DrabTestApp.LiveController"}
Helper for broadcasting functions, returns topic for a given URL path.
iex> same_path("/test/live")
"__drab:same_path:/test/live"
Helper for broadcasting functions, returns topic for a given endpoint and URL path.
To be used in multiple endpoint environments only.
iex> same_path(DrabTestApp.Endpoint, "/test/live")
{DrabTestApp.Endpoint, "__drab:same_path:/test/live"}
Helper for broadcasting functions, returns topic for a given topic string.
Drab broadcasting topics are different from Phoenix topic - they begin with “__drab:”. This is because you can share Drab socket with you own one.
iex> same_topic("mytopic")
"__drab:mytopic"
Helper for broadcasting functions, returns topic for a given topic string.
To be used in multiple endpoint environments only.
Drab broadcasting topics are different from Phoenix topic - they begin with “__drab:”. This is because you can share Drab socket with you own one.
iex> same_topic(DrabTestApp.Endpoint, "mytopic")
{DrabTestApp.Endpoint, "__drab:mytopic"}
Like this/1
, but returns selector of the object ID.
def button_clicked(socket, sender) do
socket |> update!(:text, set: "alread clicked", on: this!(sender))
socket |> update!(attr: "disabled", set: true, on: this!(sender))
end
Raises exception when being used on the object without an ID.
Returns the selector of object, which triggered the event. To be used only in event handlers.
def button_clicked(socket, sender) do
set_prop socket, this(sender), innerText: "already clicked"
set_prop socket, this(sender), disabled: true
end
Returns the unique selector of the DOM object, which represents the shared commander of the event triggerer.
In case the even was triggered outside the Shared Commander, returns “” (empty string).
To be used only in event handlers. Allows to create reusable Drab components.
<div drab-commander="DrabTestApp.Shared1Commander">
<div class="spaceholder1">Nothing</div>
<button drab-click="button_clicked">Shared 1</button>
</div>
def button_clicked(socket, sender) do
set_prop socket, this_commander(sender) <> " .spaceholder1", innerText: "changed"
end