drab v0.6.3 Drab.Commander

Drab Commander is a module to keep event handlers.

All the Drab functions (callbacks, event handlers) are placed in the module called Commander. Think about it as a controller for the live pages. Commanders should be placed in web/commanders directory. Commander must have a corresponding controller.

defmodule DrabExample.PageCommander do
  use Drab.Commander

  def click_button_handler(socket, dom_sender) do
    ...
  end
end

Remember the difference: controller renders the page while commander works on the live page.

Event handler functions

Event handler is the function which process the request coming from the browser. It is done by running JS method Drab.exec_elixir() or from the DOM object with drab-handler attribute. See Drab.Core for description.

The event handler function receives two parameters:

  • socket - the websocket used to communicate back to the page
  • argument - an argument used in JS Drab.exec_elixir() method; when lauching an event via drab-handler=function atrribute, it is a map describing the sender object

All public functions with arity of 2 in the commander module must be considered as handlers

Please keep in mind that all public functions with arity of 2 in the commander may be called remotely from the browser.

Shared commanders

By default, only the page rendered with the corresponding controller may run handler functions in the commander. But there is a possibility to create a shared commander, which is allowed to run from any page. For this, you must mark the specific handlers as public, using public/1 macro. Please notice it is your responsibility to make such functions safe.

If you want to restrict shared controller for only specified controller, you must use before_handler/1 callback with controller/1 and action/1 functions to check out where the function is calling from.

Callbacks

Callbacks are an automatic events which are launched by the system. They are defined by the macro in the Commander module:

defmodule DrabExample.PageCommander do
  use Drab.Commander

  onload :page_loaded
  onconnect :connected
  ondisconnect :dosconnected

  before_handler :check_status
  after_handler  :clean_up, only: [:perform_long_process]

  def page_loaded(socket) do
    ...
  end

  def connected(socket) do
    ...
  end

  def connected(store, session) do
    # notice that this callback receives store and session, not socket
    # this is because socket is not available anymore (Channel is closed)
    ...
  end

  def check_status(socket, sender) do
    # return false or nil to prevent event handler to be launched
  end

  def clean_up(socket, dom_sender, handler_return_value) do
    # this callback gets return value of the corresponding event handler
  end
end

onconnect

Launched every time client browser connects to the server, including reconnects after server crash, network broken etc

onload

Launched only once after page loaded and connects to the server - exactly the same like onconnect, but launches only once, not after every reconnect

ondisconnect

Launched every time client browser disconnects from the server, it may be a network disconnect, closing the browser, navigate back. Disconnect callback receives Drab Store as an argument

before_handler

Runs before the event handler. If any of before callbacks return false or nil, corresponding event will not be launched. If there are more callbacks for specified event handler function, all are processed in order or appearance, then system checks if any of them returned false.

Can be filtered by :only or :except options:

before_handler :check_status, except: [:set_status]
before_handler :check_status, only:   [:update_db]

after_handler

Runs after the event handler. Gets return value of the event handler function as a third argument. Can be filtered by :only or :except options, analogically to before_handler

Using callbacks to check user permissions

Callbacks are handy for security. You may retrieve controller name and action name from the socket with controller/1 and action/1.

before_handler :check_permissions
def check_permissions(socket, _sender) do
  if controller(socket) == MyApp.MyController && action(socket) == :index do
    true
  else
    false
  end
end

Broadcasting options

All Drab function may be broadcaster. By default, broadcasts are sent to browsers sharing the same page (the same url), but it could be override by broadcasting/1 macro.

Modules

Drab is modular. You my choose which modules to use in the specific Commander by using :module option in use Drab.Commander directive. There is one required module, which is loaded always and can’t be disabled: Drab.Code. By default, modules Drab.Live and Drab.Element are loaded. The following code:

use Drab.Commander, modules: [Drab.Query]

will override default modules, so only Drab.Core and Drab.Query will be available.

Every module has its corresponding JS template, which is loaded only when module is enabled.

Using templates

Drab injects function render_to_string/2 into your Commander. It is a shorthand for Phoenix.View.render_to_string/3 - Drab automatically chooses the current View.

Examples:

buttons = render_to_string("waiter_example.html", [])

Generate the Commander

There is a mix task (Mix.Tasks.Drab.Gen.Commander) to generate skeleton of commander:

mix drab.gen.commander Name

See also Drab.Controller

Link to this section Summary

Functions

Drab may allow an access to specified Plug Session values. For this, you must whitelist the keys of the session map. Only this keys will be available to Drab.Core.get_session/2

Retrieves action name in the controller, which rendered the page where handler is called from

Sets up the callback for after_handler. Receives handler function name as an atom and options

Sets up the callback for before_handler. Receives handler function name as an atom and options

Set up broadcasting listen subject for the current commander

Retrieves controller module, which generated the page the handler function is calling from, from the socket

Sets up the callback for onconnect. Receives handler function name as an atom

Sets up the callback for ondisconnect. Receives handler function name as an atom

Sets up the callback for onload. Receives handler function name as an atom

Make handler function public, so it can be called from any page

Link to this section Functions

Link to this macro access_session(session_keys) (macro)

Drab may allow an access to specified Plug Session values. For this, you must whitelist the keys of the session map. Only this keys will be available to Drab.Core.get_session/2

defmodule MyApp.MyCommander do
  user Drab.Commander

  access_session [:user_id, :counter]
end

Keys are whitelisted due to security reasons. Session token is stored on the client-side and it is signed, but not encrypted.

Retrieves action name in the controller, which rendered the page where handler is called from.

Link to this macro after_handler(event_handler, filter \\ []) (macro)

Sets up the callback for after_handler. Receives handler function name as an atom and options.

after_handler :event_handler_function

See Drab.Commander summary for details.

Link to this macro before_handler(event_handler, filter \\ []) (macro)

Sets up the callback for before_handler. Receives handler function name as an atom and options.

before_handler :event_handler_function

See Drab.Commander summary for details.

Link to this macro broadcasting(subject) (macro)

Set up broadcasting listen subject for the current commander.

It is used by broadcasting functions, like Drab.Element.broadcast_prop/3 or Drab.Query.insert!/2. When the browser connects to Drab page, it gets the broadcasting subject from the commander. Then, it will receive all the broadcasts coming to this subject.

Default is :same_path

Options:

  • :same_path (default) - broadcasts will go to the browsers rendering the same url
  • :same_controller - broadcasted message will be received by all browsers, which renders the page generated by the same controller
  • "topic" - any topic you want to set, messages will go to the clients sharing this topic

See Drab.Core.broadcast_js/2 for more.

Link to this function controller(socket)

Retrieves controller module, which generated the page the handler function is calling from, from the socket.

Link to this macro onconnect(event_handler) (macro)

Sets up the callback for onconnect. Receives handler function name as an atom.

onconnect :event_handler_function

See Drab.Commander summary for details.

Link to this macro ondisconnect(event_handler) (macro)

Sets up the callback for ondisconnect. Receives handler function name as an atom.

ondisconnect :event_handler_function

See Drab.Commander summary for details.

Link to this macro onload(event_handler) (macro)

Sets up the callback for onload. Receives handler function name as an atom.

onload :event_handler_function

See Drab.Commander summary for details.

Link to this macro public(handler) (macro)

Make handler function public, so it can be called from any page.

This allow you to create a shared commanders

defmodule MyApp.MyCommander
  use Drab.Commander
  public [:handler1, :handler2]

  def handler1(socket, sender) do
    ...
  end
end

Without marking as public, function may only be called from the page rendered with the corresponding controller. When whitelisted, functions may be called from any page using the dot syntax:

<button drab-click="MyApp.MyCommander.handler1">handler 1</button>

or from JS:

Drab.exec_elixir("MyApp.MyCommander.handler1", {click: "clickety-click"});

Running non-public functions with dot syntax raises the exception on Phoenix side.