How to access ruby instance variable in Javascript through HAML template

959 views Asked by At

I have a partial that is being rendered with the CommentsController. Lets call this partial _edit_comment.html.haml where I'm passing in the instance variable @comment.

$("#edit-comment-#{object.id}").html('#{ escape_javascript(render :partial => 'edit_comment', locals: { story: @comment.story, comment: @comment }) }')

In my partial, I have JS which I want to call when a button is clicked.

= form_with model: [story.industry, story, comment], class: "w-100" do |f|
  %fieldset.w-100                                    
    = f.text_area :content, rows: 2, hide_label: true, class: 'px-2 py-1', placeholder: "Leave a comment" 
  %fieldset  
    = f.submit "Update", class: 'btn submit text-white mt-2'   
    %span.btn.cancel.text-white.mt-2 Cancel-
:javascript
  $(document).ready( function() {
    $('.cancel').click(e => {
      console.log("#{@comment}")       
    })
  } );

I understand that "#{}" works for strings but I want to be able to access the entire object instead. Currently, the result of the console will be

#<Comment:0x000055971a9c5278>

I need to get the entire ruby object because I want to pass it as a variable to another partial which will be rendered. Something like this:

:javascript
  $(document).ready( function() {
    $('.cancel').click(e => {
      $("#edit-comment-#{object.id}").html('#{ escape_javascript(render :partial => 'edit_comment', locals: { story: "#{@comment}".story, comment: "#{@comment}" }) }')
    })
  } );

Is this possible?

2

There are 2 answers

2
BenKoshy On BEST ANSWER

Consider using a nested form...or a similar variation

Here is a link to the rails guide: https://guides.rubyonrails.org/form_helpers.html#nested-forms

It looks liked you want a nested form, and you want to edit comments, using the same form that is used when you want to edit a story (?) or story industry (?). If that's the case then perhaps investigate using nested forms.

Secondly, another gotcha to be aware of: you cannot place one HTML form inside another one: that can't be done. So the javascript workflow you are proposing must render the edit comment partial OUTSIDE the form you have above.........or you can employ a mix of javascript and use the nested forms method above using jquery/javascript. Ryan Bates has devoted two excellent rails casts to this topic - here they are (1) and (2):

But to answer your question:

...it can't be done like that

I don't know if you can actually do it the way you were originally trying to do it - to pass complex ruby objects like that

...but there are solutions:

(A) Render the partials and hide as needed

Why not try a different approach: render the partial directly in your html (but hide it from being seen by the user).

When the button is clicked, you can simply toggle the visibility of the already existing partial.

Since you're using jQuery:

(B) Fetch the partial ayschronously

You can fetch the partial with request in your event handler - stimulus js does this type of workflow very well. You can use the fetch API to get the html partial, or you can use AJAX if you want to support internet explorer etc.

...........you would have to decide whether you want the form nested, or you are happy with completely separate forms.

1
rmlockerd On

To understand why you can't directly do what you're trying to do, it helps to think about the page-rendering process. When your CommentsController controller renders a view, Rails processes your HAML template, which includes processing all your embedded Ruby (like string interpolation). After that, though, your HTML is just HTML and your Javascript is just Javascript.

So, in your case, Rails interpolates the "#{@comment}" at render time, but it's just a string when the Javascript executes in your browser, but you can't pass a Ruby object, etc. Likewise, escape_javascript and render are Rails view helpers, so they won't work when directly stuck into Javascript like that. One good option, as @BKSpurgeon gives, is to render the edit partial in the original page and then just use a bit of plain JS to show/hide it.

Another option is change your button to be a remote (i.e., AJAX) link_to and then put your Javascript into a JS view. I don't really use HAML, but I think the link would be something like:

= link_to 'Cancel', edit_comment_path(@comment), remote: true 

And in edit_comment.js.haml:

:plain
$("#edit-comment-#{object.id}").html('#{ escape_javascript(render :partial => 'edit_comment', locals: { story: "#{@comment}".story, comment: "#{@comment}" }) }')

(A quick google indicated that later versions of HAML might not require the :plain wrapper; perhaps someone with HAML experience can correct that if it's not quite right.)