Search code examples
javascriptjqueryruby-on-railsajaxunobtrusive-javascript

rails wrapping duplicate js.erb code into function


I have a rails app with a custom pusher chat where chat message will be loaded by AJAX for the sender and by pusher socket for the receiver.

In my create.js.erb file I have duplicate code at the moment and I don't know what the best way is to wrapping a piece of code when it comes to js.erb template.

Could sby tell me, how to get rid of the duplication properly? The code before the broadcast function is the same code that is wrapped into the broadcast function.

create.js.erb

//first part is AJAX, only goes to sender
var id = "<%= @conversation.id %>";
var chatbox = $(".chatboxcontent");
var sender_id = "<%= @message.user.id %>";
var receiver_id = $('meta[name=user-id]').attr("content");

if (event.handled !== true) {

   $message = $('<%= j render @message %>');
   chatbox.append($message);
   chatbox.scrollTop(chatbox[0].scrollHeight);

   $(".chatboxtextarea").val("");
   $(".chatboxtextarea").focus();
   $(".chatboxtextarea").css('height', '44px');
   var timeField = ($message).find('.timefield');
   var nameField = ($message).find('#chatname');
   var createdAt = timeField.attr('datetime');
   var momentCreatedAt = moment(createdAt).format('hh:mm A');
   timeField.remove();
   $( "<span class='newtime'>" + " • " + momentCreatedAt + "</span>" ).insertAfter(nameField);

   if(sender_id != receiver_id) {
     chatbox.children().last().removeClass("self").addClass("other");
     chatbox.scrollTop(chatbox[0].scrollHeight);
   }
   event.handled = true;
}

//only broadcasted to receiver, sender is getting the message via the prev AJAX
<% broadcast_to_conversation(@conversation.id, @receiver_id) do %>
  var id = "<%= @conversation.id %>";
  var chatbox = $(".chatboxcontent");
  var sender_id = "<%= @message.user.id %>";
  var receiver_id = $('meta[name=user-id]').attr("content");

  if (event.handled !== true) {

    $message = $('<%= j render @message %>');
    chatbox.append($message);
    chatbox.scrollTop(chatbox[0].scrollHeight);

    $(".chatboxtextarea").val("");
    $(".chatboxtextarea").focus();
    $(".chatboxtextarea").css('height', '44px');
    var timeField = ($message).find('.timefield');
    var nameField = ($message).find('#chatname');
    var createdAt = timeField.attr('datetime');
    var momentCreatedAt = moment(createdAt).format('hh:mm A');
    timeField.remove();
    $( "<span class='newtime'>" + " • " + momentCreatedAt + "</span>" ).insertAfter(nameField);

    if(sender_id != receiver_id) {
        chatbox.children().last().removeClass("self").addClass("other");
        chatbox.scrollTop(chatbox[0].scrollHeight);
    }

    event.handled = true;
  }
<% end %>

Solution

  • 1. Solution

    You could create a partial and render it twice:

    _chatbox.js.erb

    var id = "<%= @conversation.id %>";
    var sender_id = "<%= @message.user.id %>";
    var receiver_id = $('meta[name=user-id]').attr("content");
    var chatbox = $(".chatboxcontent");
    
    if (event.handled !== true) {
    
      $message = $('<%= j render @message %>');
      chatbox.append($message);
      chatbox.scrollTop(chatbox[0].scrollHeight);
    
      $(".chatboxtextarea").val("");
      $(".chatboxtextarea").focus();
      $(".chatboxtextarea").css('height', '44px');
      var timeField = ($message).find('.timefield');
      var nameField = ($message).find('#chatname');
      var createdAt = timeField.attr('datetime');
      var momentCreatedAt = moment(createdAt).format('hh:mm A');
      timeField.remove();
      $( "<span class='newtime'>" + " • " + momentCreatedAt + "</span>" ).insertAfter(nameField);
    
      if(sender_id != receiver_id) {
        chatbox.children().last().removeClass("self").addClass("other");
        chatbox.scrollTop(chatbox[0].scrollHeight);
      }
      event.handled = true;
    }
    

    create.js.erb

    <%= render "chatbox" %>
    
    <% broadcast_to_conversation(@conversation.id, @receiver_id) do %>
      <%= render "chatbox" %>
    <% end %>
    

    2. Solution

    But a better solution would be to extract the common code into one javascript function and parametrize this function with data you are getting from the controller. In this case you can better reuse your code from other parts of your application.

    in your controller:

    def create
      #...
      @chatbox_params = {id: @conversation.id, sender_id: @message.user.id}
      #...
    end
    

    application.js

    function chatbox(params){
      var receiver_id = $('meta[name=user-id]').attr("content");
      var chatbox     = $(".chatboxcontent");
    
      if (event.handled !== true) {
        chatbox.append(params.message);
        chatbox.scrollTop(chatbox[0].scrollHeight);
    
        $(".chatboxtextarea").val("");
        $(".chatboxtextarea").focus();
        $(".chatboxtextarea").css('height', '44px');
        var timeField = (params.message).find('.timefield');
        var nameField = (params.message).find('#chatname');
        var createdAt = timeField.attr('datetime');
        var momentCreatedAt = moment(createdAt).format('hh:mm A');
        timeField.remove();
        $( "<span class='newtime'>" + " • " + momentCreatedAt + "</span>" ).insertAfter(nameField);
    
        if(params.sender_id != receiver_id) {
          chatbox.children().last().removeClass("self").addClass("other");
          chatbox.scrollTop(chatbox[0].scrollHeight);
        }
        event.handled = true;
      }
    }
    

    create.js.erb

    var chatboxParams     = JSON.parse('<%= @chatbox_params.to_json %>');
    chatboxParams.message = $('<%= j render @message %>');
    
    chatbox(chatboxParams);
    
    <% broadcast_to_conversation(@conversation.id, @receiver_id) do %>
      chatbox(chatboxParams);
    <% end %>