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

Showcase

Last updated: Jul 25th, 2018

Todo list

Todo list
Controller
      
        class ShowcaseController < ApplicationController
          def index
            @todo_list = []
            @todo_item = ''
          end
        end
      
    
View
      
        <b>Todo list</b>

        <input type="text" fie-enter="add_todo_item" value="<%= todo_item %>" name="todo_item" />
        <button fie-click="add_todo_item">Add</button>

        <table class="table table-striped">
          <tbody>
            <% @todo_list.each_with_index do |item, index| %>
              <tr>
                <td><%= item %></td>
                <td fie-parameters="<%= { item_index: index }.to_json %>" fie-click="remove_todo_item">X</td>
              </tr>
            <% end %>
          </tbody>
        </table>
      
    
Commander
      
        class ShowcaseCommander < Fie::Commander
          def add_todo_item
            todo_item = state.todo_item
            state.todo_item = ''
            state.todo_list << todo_item
          end

          def remove_todo_item(item_index:)
            state.todo_list.delete_at item_index
          end
        end
      
    

Slideshow



This example requires you to have the Animate.css library installed and have three images with the names "dog1.jpg", "dog2.jpg", "dog3.jpg" in your app/assets/images folder.

Controller
      
        class ShowcaseController < ApplicationController
          def index
            @slideshow_images_names = ['dog1', 'dog2', 'dog3']
            @slideshow_selected_image_index = 1
          end
        end
      
    
View
      
        <div class="carousel">
          <% @slideshow_images_names.each_with_index do |image_name, image_index| %>
            <% if image_index == @slideshow_selected_image_index %>
              <%= image_tag(image_name, class: 'animated fadeIn') %>
            <% else %>
              <%= image_tag(image_name, class: 'hidden') %>
            <% end %>
          <% end %>

          <button fie-click="previous_image">&#x3C;-</button>
          <button fie-click="next_image">-&#x3E;</button>
        </div>
      
    
Commander
      
        class ShowcaseCommander < Fie::Commander
          def next_image
            max_index = state.slideshow_images_names.size
            image_index = state.slideshow_selected_image_index
            state.slideshow_selected_image_index = (image_index + 1) % max_index
          end

          def previous_image
            max_index = state.slideshow_images_names.size
            image_index = state.slideshow_selected_image_index
            state.slideshow_selected_image_index = (image_index - 1) % max_index
          end
        end
      
    
SCSS
      
        .carousel {
          position: relative;
          display: inline-block;

          img {
            width: 100%;
            display: block;

            &.hidden-img {
              display: none;
            }
          }

          button {
            height: 40px;
            width: 40px;

            top: 50%;
            margin-top: -20px;
            position: absolute;

            &:first-child {
              left: 0;
            }

            &:last-child {
              right: 0;
            }
          }
        }
      
    

Chat


Controller
			
        class ShowcaseController < ApplicationController
          def index
            @username = ''
            @message = ''
            @entries = []
          end
        end
			
		
View
			
        <div class="chat">
          <% @entries.each do |entry| %>
            <b><%= entry[:username] %></b>: <%= entry[:message] %><br>
          <% end %>
        </div class="chat">

        <input type="text" placeholder="Username" value="<%= @username %>" name="username">
        <input type="text" placeholder="Message" value="<%= @message %>" name="message" fie-enter="publish_chat_entry" />
			
		
Javascript
			
        function scrollElement(selector) {
          const chatElement = document.querySelector('.chat');
          chatElement.scrollTop = chatElement.scrollHeight;
        }
			
		
Commander
			
        class GuideCommander < Fie::Commander
          pool :chat do
            state.entries << @published_object
            execute_js_function('scrollElement', '.chat')
          end

          def publish_chat_entry
            entry = { message: state.message, username: state.username.presence || 'Anonymous' }
            state.message = ''
            publish(:chat, entry)
          end
        end
			
		

Long Running Job


This example uses the Active Jobs framework for background jobs processing that comes by default with Rails. In production it is important to set up a queuing backend (this showcase uses Sidekiq).

Controller
      
        class ShowcaseController < ApplicationController
          def index
            @long_job_progress = 0
            @is_long_job_running = false
          end
        end
      
    
View
      
        <% if @is_long_job_running %>
          <div class="showcase-progress-bar">
            <p>Progress:</p>
            <progress value="<%= @long_job_progress %>" max="100"></progress>
          </div>
        <% else %>
          <button fie-click="start_long_job">Start long running job</button>
        <% end %>
      
    
Commander
      
        class ShowcaseCommander < Fie::Commander
          def start_long_job
            return if state.is_long_job_running
            
            state.long_job_progress = 0
            state.is_long_job_running = true

            LongRunningJob.perform_later(@connection_uuid)
          end
        end
      
    
Job (app/jobs/long_running_job.rb)
      
        class LongRunningJob < ApplicationJob
          include Fie::Manipulator

          queue_as :default

          def perform(connection_uuid)
            @fie_connection_uuid = connection_uuid

            is_job_finished = -> { state.long_job_progress >= 100 }

            if commander_exists? && !is_job_finished.call
              state.long_job_progress += 10
              sleep(1.second)
              LongRunningJob.perform_later(connection_uuid)
            else
              state.is_long_job_running = false
            end
          end
        end