A Rails-centric frontend framework running over a permanent WebSocket connection.

Guide

Last updated: Jul 25th, 2018

Commanders

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
		

How to define methods.

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
		

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.

List of available methods and variables within your commander methods


Methods
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 variables
Instance variable name Description
@caller Parameters of the DOM element that called this method. The parameters are structured in the following way:

								{
									id: 'DOM element id',
									class: 'DOM element class',
									value: 'DOM element value (e.g. text field value)'
								}
							
@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.)

HTML DOM bindings.

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.

How to use HTML DOM bindings.

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>
			
		

List of HTML DOM bindings.

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

Three-way data binding.

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.

Backend.

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.

View
app/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="">
			
		
Commander
app/commanders/guide_commander.rb
			
				class GuideCommander < Fie::Commander
					def append_to_array
						state.array << @caller[:value]
					end
				end
			
		
Result:
Array:
  • value1
  • value2
Press enter to add to array:

View forms.

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.

View
app/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:
Title
Lorem ipsum dolores sit amet.

Title:


Body:

The possibilities are endless.

Text fields and text areas are the only HTML form elements used in this example, however they are all supported by fie.

Mind the scope

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.

Textareas are weird...

At the moment, the native form helper for textareas doesn't play nicely with fie. Please follow the example above to manually create textareas.

View primitives.

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.

View
app/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:
Boolean: false
true:    false:

Number: 50

Not truly primitive

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).

View others.

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.

View
app/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:

  • Name: Eran Peer
  • Age: 21
Name:   
Age:

Employee #2:

  • Name: John Smith
  • Age: 43
Name:   
Age:

Pools

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.

How to subscribe to pools.

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      
      
    

List of available methods and variables.


Methods
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?')
Instance Variables
Variable name Description
@published_object Holds the object that was published to the pool.

Publishing to pools.

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')
      
    

How to manipulate the view and state from outside of the commander.

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:

  1. You may 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.
  2. You can find the connection UUID in the terminal in development mode when a user connects. It should look like this:

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
      
    

List of available methods and variables.


Methods
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 variables
Instance variable name Description
@fie_connection_uuid The identifying UUID of the state you would like to manipulate.

JavaScript Functions

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' });

Caching

fie may become sluggish when handling large pages such as this one. In these situations, it is recommended to divide the page into partials and caching them. Doing so will prevent the rerendering of the entire page on each fie event when only one part of it has actually changed. Below is an example of how this page uses caching.
View
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 %>
    
  

Limitations

Just like any other piece of software, fie is not perfect. Below are the current known limitations of the framework.

Instance variable cannot be nil

Instance variables that you wish to use with fie must contain a value (even if it is an empty String/Array/Hash).

Partials

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'.

Avoid jQuery

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.

Avoid native event listener and use ours

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 in development is deceitful

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.

Do not settle for action cable, go for any cable

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).

Only strings for non active records

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.

Regions matter

fie works over websockets, so you need to be more mindful of where your audience is to provide them with the best experience possible.