Don't just use any parameters
Please be mindful that if you do choose to pass parameters to your commander methods, they can only accept keyword arguments.
A Rails-centric frontend framework running over a permanent WebSocket connection.
Commanders are to fie what controllers are to Ruby on Rails. For each controller in your application that you want to use fie with, you must create an accompanying commander. Therefore a controller TestController would need a commander TestCommander in the commanders folder.
The following is an example of the definition of a commander GuideCommander:
app/commanders/guide_commander.rb
class GuideCommander < Fie::Commander
end
You can define methods as you would in any other class in Ruby. Below is an example:
app/commanders/guide_commander.rb
class GuideCommander < Fie::Commander
def my_method1(argument1:)
puts argument1
end
def my_method2(argument1: 'default', argument2:)
puts "This is an optional argument: #{ argument1 }"
puts "This is a required argument: #{ argument2 }"
end
end
Please be mindful that if you do choose to pass parameters to your commander methods, they can only accept keyword arguments.
Method name | Description |
---|---|
state |
returns a Fie::State object you can use to manipulate the state (read more in
Three-way data binding).
|
execute_js_function(name, *arguments) |
Executes a Javascript function. It makes the following function call to your JS frontend:
name(*arguments) .
e.g. execute_js_function(my_function, argument1, argument2)
|
connected? |
Returns a boolean based on whether the client is still connected to the commander. Particularly useful in long running tasks. |
publish(subject, object) |
Calls Fie::Pools.publish_lazy with the publisher's connection uuid
(view Publishing to pools section).
e.g. publish(:chat, 'What is up people?')
|
Instance variable name | Description |
---|---|
@caller |
Parameters of the DOM element that called this method. The parameters are structured in the following way:
|
@controller_name |
The name of the controller whose view called this method. |
@action_name |
The name of the action (view) that called this method. |
@connection_uuid |
The identifying UUID of this fie connection. Use this variable to manipulate the state from outside of the commander (read more in How to manipulate the view and state from outside of the commander.) |
Javascript has event listeners, fie has HTML DOM bindings. HTML DOM bindings allow you to remotely call a method in your commander upon a DOM event.
HTML DOM bindings are simply attributes of elements within the DOM. The names of these attributes are prefixed
with "fie-
".
The attribute to call a method my_method
after a click event would be
fie-click="my_method"
.
Additionally, arguments can be sent to the method in the commander. The arguments should be a JSON
object set as the value of the DOM element attribute "fie-parameters
".
Below is an example where method my_method
is triggered after a click event on a link with arguments
string = 'string'
and integer = 123
.
<% parameters = {
string: 'string',
integer: 123
}.to_json %>
<a fie-click="my_method" fie-parameters="<%= parameters %>">Click Me!</a>
Below is a list of all the available HTML DOM bindings.
fie-parameters
fie-click
fie-submit
fie-scroll
fie-keyup
fie-keydown
fie-enter
fie-change
Three-way data binding means that if an instance variable is changed in the view (through a form input for example), the change will be reflected where the variable has been used elsewhere on the page and in the state object of the commander.
This works the other way around, if the variable is changed in the commander, the change will be reflected in the view.
The commander can make use of the state object to change the value of a variable throughout the framework.
The state object has getters and setters for each instance variable in your view. For example, if your
view contains a @var variable
, you will be able to manipulate it by calling
state.var = 'new value'
and read it value by calling state.var
in your commander.
Below is an example of three-way data binding where the change is instantiated by the backend.
Viewapp/views/guide/index.html.erb
<b>Array:</b><br>
<ul>
<% @array.each do |item| %>
<li><%= item %></li>
<% end %>
</ul>
<b>Press enter to add to array:</b> <input type="text" fie-enter="append_to_array" value="">
Commanderapp/commanders/guide_commander.rb
class GuideCommander < Fie::Commander
def append_to_array
state.array << @caller[:value]
end
end
Result:When creating a form for an active record object, fie automatically registers modifications and synchronizes with the commander state object and other variables in the view.
Below is an example of three-way data binding where the change is instantiated by a form in the view.
Viewapp/views/guide/index.html.erb
<b><%= @post_object.title %></b><br>
<%= @post_object.body %><br><br>
<%= form_with model: @post_object, scope: :post_object, url: '#' do |form| %>
Title:<br> <%= form.text_field :title %><br><br>
Body:<br> <textarea name="post_object[body]"><%= @post_object.body %></textarea>
<% end %>
Result:Text fields and text areas are the only HTML form elements used in this example, however they are all supported by fie.
You will notice that the scope property of form_with is set to the name of the instance variable the object is stored in. It is very important to ensure you do that as well, fie uses it to identify the instance variable the object resides in.
At the moment, the native form helper for textareas doesn't play nicely with fie. Please follow the example above to manually create textareas.
When one of your user modifies an input fields for instance variables holding primitive types (String, Integer, etc...), fie automatically updates the state within your view and commander.
Below is an example of three-way data binding where the change is instantiated by an input in the view.
Viewapp/views/guide/index.html.erb
Boolean: <%= @boolean == '1' %><br>
true: <%= radio_button_tag 'boolean', '1', @boolean == '1' %>
false: <%= radio_button_tag 'boolean', '0', @boolean == '0' %>
<br><br>
Number: <%= @number %><br>
<%= range_field_tag 'number', @number.to_i, in: 1..100 %>
Result:At the moment only strings are supported as primitives in fie. This means that if you need another type, it must be converted from a string (as shown in the example above).
fie doesn't only support three-way data binding for objects and primitives, but also array and hashes.
Below is an example of three-way data binding where the change is instantiated by an input for a nested structure.
Viewapp/views/guide/index.html.erb
<%# @company = { employees: [Employee.new(name: 'Eran Peer', age: 21), Employee.new(name: 'John Smith', age: 43)] } %>
<% @company[:employees].each_with_index do |employee, index| %>
<p>Employee #<%= index + 1 %>:</p>
<ul>
<li>
Name: <%= employee.name %>
</li>
<li>
Age: <%= employee.age %>
</li>
</ul>
<%= form_with model: employee, scope: "company[employees][#{ index }]", url: '#' do |form| %>
Name: <%= form.text_field :name %>
Age: <%= form.text_field :age %>
<% end %>
<br>
<% end %>
Result:Employee #1:
Employee #2:
Pools are a Pub/Sub system for fie. Pools are identified by their subject, similarly to channels in Action Cable. For example, you could have "notification", "chat_random", and "chat_private_1" as pool subjects
Using pools is as simple as creating a hook that contains a callback within your commander. There is no need to define a pool, once a pool hook for a subject is added to any commander, the pool is created automatically.
Subscribing to a pool is as simple as adding a hook within your commander with the pool's subject and a callback (to be called on any new message).
Below is an example where two pools are defined within a commander.
class HomeCommander < Fie::Commander
pool :notification do
execute_js_function("alert", @published_object)
end
pool :chat do
state.chat << @published_object
end
def method1(test: nil)
puts "Called method 1!"
end
end
Method name | Description |
---|---|
state |
Returns a Fie::State object you can use to manipulate the state (read more in
Three-way data binding).
|
execute_js_function(name, *arguments) |
Executes a Javascript function. It makes the following function call to your JS frontend:
name(*arguments) .
e.g. execute_js_function(my_function, argument1, argument2)
|
publish(subject, object) |
Calls Fie::Pools.publish_lazy with the publisher's connection uuid
(view Publishing to pools section).
e.g. publish(:chat, 'What is up people?')
|
Variable name | Description |
---|---|
@published_object |
Holds the object that was published to the pool. |
Publishing to pools is easy, and can be done from anywhere in your Rails application. You only need to know the subject and what kind of data subscribers are expecting.
Below is an example of publishing to a pool.
Fie::Pools.publish(:notifications, 'New message arrived.')
Fie::Pools.publish(:chat, 'What is up?')
Fie::Pools.publish(:owners, Dog.new)
if you wish to publish to a pool in a way that a certain subscriber would be the first to receive the published object,
you can use the publish_lazy
method. This should be your preferred method of publishing in
use cases such as chats where it is preferrential that the sender be the first to receive the message in order to improve their perceived
communication speed (if the pool is big, and the sender is not the first receiver, their client may seem like it is hanging).
Below is an example of publishing to a pool using publish_lazy
Fie::Pools.publish_lazy(:notifications, 'New message arrived.', '1b733ccc-8bfd-49e4-bfc3-6ceed876acf0')
Fie::Pools.publish_lazy(:chat, 'What is up?', '1b733ccc-8bfd-49e4-bfc3-6ceed876acf0')
Fie::Pools.publish_lazy(:owners, Dog.new, '1b733ccc-8bfd-49e4-bfc3-6ceed876acf0')
Manipulating the view and state from outside of the commander can be very useful while testing and debugging.
In order to do so, you need to find the connection UUID identifying the session you would like to interact with. You have two options to find it:
puts @connection_uuid
within a commander method in order to print the connection UUID
in terminal. You could even retrieve the instance variable by prying within a commander method and achieve the same result.
You then need to include the Fie::Manipulator
module wherever you wish to remotely control the state
(whether it is in a class or in rails c).
Finally you need to create a @fie_connection_uuid
instance variable within the location where you wish to
remote control the state.
Below are bash and class remote control examples.
Bash example
[1] pry(main)> include Fie::Manipulator
=> Object
[2] pry(main)> @fie_connection_uuid = 'b952e357-1589-4ce4-beb0-8aadba8f979f'
=> "b952e357-1589-4ce4-beb0-8aadba8f979f"
[3] pry(main)> state.my_variable = 'my_value'
=> "my_value"
[4] pry(main)> execute_js_function('console.log', 'This happened from ruby!')
=> 0
Class example
class MyClass
include Fie::Manipulator
def initialize
@fie_connection_uuid = 'b952e357-1589-4ce4-beb0-8aadba8f979f'
end
def update_state
state.my_variable = 'my_value'
end
def execute_js
execute_js_function('console.log', 'This happened from ruby!')
end
end
Method name | Description |
---|---|
state |
returns a Fie::State object you can use to manipulate the state (read more in
Three-way data binding).
|
execute_js_function(name, *arguments) |
Executes a Javascript function. It makes the following function call to your JS frontend:
name(*arguments) .
e.g. execute_js_function(my_function, argument1, argument2)
|
commander_exists? |
Checks whether the client is still connected to the commander or not. |
Instance variable name | Description |
---|---|
@fie_connection_uuid |
The identifying UUID of the state you would like to manipulate. |
While using fie, you will most likely need to use a bit of JavaScript. Therefore, fie provides functions that can ease that process.
Function name | Description |
---|---|
Fie.addEventListener(event_name, selector, callback) |
fie uses HTML diffing when rerendering your views. To avoid your event listeners being erased on each new render,
use this event listener definition instead of the default when listening to events within your body. e.g. Fie.addEventListener('click', '.button', myFunc);
|
Fie.executeCommanderMethod(function_name, parameters = {}) |
Use this function to execute a commander method from JavaScript. You would call a commander method
my_method(arg1:, arg2:) with the following JavaScript call:
Fie.executeCommanderMethod('my_method', { arg1: 123, arg2: 'my argument' });
|
app/views/guide/index.html.erb
<%= render partial: 'guide/commander', cached: true %>
<%= render partial: 'guide/html_dom_bindings', cached: true %>
<%= render \
partial: 'guide/tw_data_binding',
locals: {
array: @tw_data_binding_backend,
post_object: @tw_data_binding_form,
primitive: @tw_data_binding_primitive,
primitive2: @tw_data_binding_primitive2,
employees: @tw_binding_view_nest[:employees]
},
cached: true
%>
<%= render \
partial: 'guide/pools',
locals: { chat_entries: @chat_entries, username: @username },
cached: true
%>
<%= render partial: 'guide/manipulator', cached: true %>
<%= render partial: 'guide/caching', cached: true %>
<%= render partial: 'guide/limitations', cached: true %>
Just like any other piece of software, fie is not perfect. Below are the current known limitations of the framework.
Instance variables that you wish to use with fie must contain a value (even if it is an empty String/Array/Hash).
Partials are supported by fie, however when rendering a partial, you must always prefix it with its path, even when it is
in the same folder. e.g. do not render 'name'
but rather render 'guide/name'
.
fie allows you to do away with most of your Javascript, however at a certain cost. At the moment, most jQuery libraries do not work well out of the box, and we would therefore recommend sticking with vanilla Javascript when you need it.
When writing Javascript code, you will inevitably write event listeners. If any of these listeners is added within the body tag,
there is a chance it will be overwritten on the next fie action (as fie uses HTML diffing). Therefore we recommend you use our own
listener. Simply use the following function anywhere in your Javascript:
Fie.addEventListener(event_name, selector, callback)
.
Performance will often be slow (over 300 ms request-response cycle) in development. However we have found that the performance increases dramatically in production even when using native action cable (15-20 ms request-response cycle). Do not make an impression about performance when simply using fie in development.
While the performance with action cable is good, especially for fie's use case, it has proven not to be excellent at scale. We therefore recommend using anycable in production (same memory footprint and broadcasting speeds as Go/Erlang).
fie only supports strings for non active record objects. You must therefore find a way to convert to and from strings when you are planning on using fie with these types.
fie works over websockets, so you need to be more mindful of where your audience is to provide them with the best experience possible.