Rails: Nested remote form doesn't work on page load - javascript

I have a Rails app in which I have a form that looks something like this:
[ Parent list item 1 ]
[ Parent list item 2 ]
[ Parent list item 3 - expanded ]
[ Child list item 1 ]
Child inline input Child submit button
------------------
[Parent input]
Parent submit button
The parent entity input always works. It is a remote form, using remote: true. When I add a parent object it gets automatically added to the list with the other parent objects. Each parent can have many children, they are displayed and listed when the user expands the corresponding parent list item (like the example above). Users can add more children by entering a value in the Child inline input, this form is also using remote: true.
The problem I'm having is that add children element doesn't always work on the first page load. It works however if I refresh the page. I'm having a hard time to understand why this is.
When I create a parent object the following js.erb is rendered:
# screen_table_id is the list with parent objects.
# localized_strings/localized_string is the tr with the object
$("#screen_table_<%= #localized_string.screen.id %>").append("<%= j render partial: 'localized_strings/localized_string', locals: { screen: #localized_string.screen, localized_string: #localized_string } %>");
# I use the best in place gem to manage inline editing
jQuery('.best_in_place').best_in_place()
The relevant parts of localized_strings/localized_string looks like this:
%tbody{ id: "localized_string_parent_#{localized_string.id}"}
%tr
%td.expandable-column
Clicking this reveals the child objects
/ The list of children is initially hidden
%tbody.hidden[localized_string]
- if localized_string.translations.any?
/ Renders the children
%tr
/ This form doesn't work on page load, after I have added the parent
= render "translations/inline_form", app: localized_string.screen.app, screen: localized_string.screen, localized_string: localized_string, translation: localized_string.translations.build
And translations/inline_form looks like this:
= form_for [app, screen, localized_string, translation], remote: true do |f|
%td{ colspan: 2 }
.inline-form-group
= f.text_field :value, class: "form-control inline-form-control", placeholder: "Translation value", id: "localized_string_input_# {localized_string.id}"
%td
/ Sometimes nothing happens when I click Submit.
= f.submit 'Add translation', class: "btn ban-success-outline"
The faulty flow looks like this:
Page load and I create a parent object (LocalizedString)
It gets added to the list correctly.
Expanding the new parent list element works as expected.
When clicking on the submit button for the child (Translation) does nothing.
Hope my question is understandable. Please comment if you have any comments or need clarification. I'm tankful for all ideas.

Ryan Bates did a great Railscast on this topic Nested Model Form Part 2. There are many interacting dependencies depending on your routes and model associations, but this RailsCast looks to be directly applicable.

I'm pretty sure that my issue was caused by invalid HTML. I previously rendered the form inside the tr tag, like this:
%tr
= render "translations/inline_form", app: localized_string.screen.app, screen: localized_string.screen, localized_string: localized_string, translation: localized_string.translations.build
And the inline_form started with the form itself.
Instead of doing like that I instead tried to wrap it inside a td tag, like this:
# inline_form.html.haml
%td{ colspan: 4 }
# the form wasn't previously inside a td tag.
= form_for [app, screen, localized_string, translation], remote: true, style: "margin-bottom: 0" do |f|
I haven't seen this problem again after this. But I'm not 100% sure that it is the solution, since the problem showed up somewhat randomly.

Related

How to update single column using modal on one page based on ajax request

I have page who contains 3 boxes and all of these boxes's data need to update in one db table so I used to update all of them one by one using partial and custom ajax.
View Code:
.col-lg-3.col-md-3.col-sm-6.col-xs-6
.box-bar
h5.prodman Short Description
br
btn.btn-primary.btn-lg
= link_to 'Edit', edit_product_path(#product, field: 'sd'), remote: true
.col-lg-3.col-md-3.col-sm-6.col-xs-6
.box-bar
h5.prodman Long Description
br
btn.btn-primary.btn-lg
= link_to 'Edit', edit_product_path(#product, field: 'ld'), remote: true
.col-lg-3.col-md-3.col-sm-6.col-xs-6
.box-bar
h5.prodman T&Cs (General, Privacy, Fair Usage etc)
br
btn.btn-primary.btn-lg
= link_to 'Edit', edit_product_path(#product, field: 'tc'), remote: true
Upon clicking link_to the modal loads all those content:
edit.js.erb code:
<% field_name, field_title = get_field_name_title(params[:field]) %>
$('#dialog h4').html("<i class=' glyphicon glyphicon-pencil'></i> Update <%= field_title %>");
$('.modal-body').html('<%= j render('terms_field_form',{field_name: field_name}) %>');
$('#dialog').modal("show");
$('#dialog').on('shown.bs.modal', function () {
CKEDITOR.replace('<%= "#{field_name}" %>');
});
$(document).on('click', '.update_terms', function () {
$.ajax({
type: 'PATCH',
dataType: 'script',
url: '<%= product_path(#product) %>',
data: {
"authenticity_token": $('input[name=authenticity_token]').val(),
"product": {"<%= field_name %>": $('.terms_content').val()}
}
});
});
Partial Code:
= text_area_tag "#{field_name}".to_sym, #product.send(field_name), class: 'form-control terms_content', id: field_name
= button_tag 'Submit', class: 'btn btn-primary mg-t-20 mg-b-20 mg-r-10 update_terms'
Dynamic fields (column and titles ) code:
def get_field_name_title(field)
return 'short_description', 'Short Description' if field == 'sd'
return 'long_description', 'Lease Terms' if field == 'ld'
return 'terms_and_conditions', 'T&Cs (General, Privacy, Fair Usage etc)' if field == 'tc'
end
Problem
The boxes contents always remain same. Means, I am updating 'Long Description' and I will update it in db but if I try to update any other box it show the name of that box again ( the previous one I updated ).
I got an impression that on each click and updation the modal stay same and on next click its adding with existing once. And it iterates it to next click. So, click1, next time I clicked2, so click1,click2. then next time i clicked3, so click1,click2,click3. this is the issue.
So, no new fresh event to new click.
Is there any proper way to do it if my process lags any feature?
You are having issues because you haven't fully committed to the single page paradigm. I'm not sure of the interaction with CKEDITOR, but the 3 warning signs I see ...
1) Your update doesn't work, but create does
2) Your code is randomly populating all of the fields with the same data
3) Your code has no unique identifier for the fields in the css/html
You need to uniquely identify the fields which will be altered.
Note: all of this assumes that you are intending to treat each field separately as it's own form - I'm not sure you make a conscious decision about which strategy to use - so I'm just following the lead of the questions you seem to be asking - as I said in the rails/ruby chat - the simplest way is to make it all one form, have a single update button for it and be done
Your div or id for the html code needs to reflect this fact, by being wrapped around those modal fields.
Which will allow you to use to jquery .bind & attach a trigger on just that field to run the update against server.
... example of us setting up a html class/div to anchor the jquery too ...
Old:
.col-lg-3.col-md-3.col-sm-6.col-xs-6
.box-bar
h5.prodman Short Description
br
btn.btn-primary.btn-lg
= link_to 'Edit', edit_product_path(#product, field: 'sd'), remote: true
New with class - (not tested but general idea):
.sd-class # -- note this can be any name, as long as sd/ld/tc aren't the same
.col-lg-3.col-md-3.col-sm-6.col-xs-6
.box-bar
h5.prodman Short Description
br
btn.btn-primary.btn-lg
= link_to 'Edit', edit_product_path(#product, field: 'sd'), remote: true
Next your simply setup an jquery binding based on that anchor - answered thousands of times - Modal updating with jquery.
I'm guessing here - but CKEDITOR should be able to take that modal field that is properly pointed too inside the anchoring div I pointed out to you ... see this answer for how to test and locate an element inside another to feed it the right field.
Note: the other part is that CKEDITOR MIGHT need to be set to nil/initialized each time you call it, to ensure blank or set to the field using the unique class div we setup as an anchor
See this answer for help selecting the right field with jquery
I feel like most of your issue is the code you wrote doesn't explicitly tell CKEDITOR where to find the information it's targeting & it's not reseting after each run. So you might have to initialize CKEDITOR each time - see their forums for help on that one.

Stimulus: how to handle repeating items with the same target name

I have a list of items, and each one has a link to click to edit it. I am using stimulus to make the edit "modal" form visible when they click that edit link. The id of what is going to be edited is present as an id= on the corresponding link tag of the list
So, the edit link looks like this:
<td>
<a data-action="click->content#edit"
data-target="content.editBtn"
id="<%= url_for(content) %>")>
Edit
</a>
</td>
And the idea is that the content#edit action in the stimulus controller examines the and locates the id of it and uses that to edit the right row.
However the problem I am having is, I think, that as a result all the rows of this list have a data-target with the same name and the wrong one (the first one?) gets bound to the target..
However if I wanted to make each data-target different by e.g. appending the id to it, now I have a long list of targets in the controller.js so that doesn't make sense.
What's the right way to handle?
If you're using Rails as the backend like your other questions seem to indicate, there may be a simpler, non-Stimulus solution. To use Stimulus, you'd need to fetch the data for the item from the server or from the DOM, display it in a form, then submit the correct form with the correct ID to the server through JavaScript. Why not just have a remote link_to button to the edit action for each item? Rails gets a JS request to the edit controller action, and you can load the modal form with the data that you have from your Ruby object.
If you use Stimulus for anything on the form, I'd use this opportunity to craft a Stimulus controller that listens to the ajax->send/error/complete events and automatically disables/enables buttons, sets loading spinners on buttons, and closes the modal. Those would be good areas to sprinkle in some functionality that Stimulus makes very simple.
This is actually a good use of Stimulus since it is modular. You add a controller for each row instead of having the controller around the page or table.
<tr data-controller="content">
<td>
<a data-action="click->content#edit" data-target="content.editBtn" id="<%= url_for(content) %>")>
Edit
</a>
</td>
</tr>
I was just having a similar problem.
This helped:
https://codepen.io/smashingmag/pen/ExPprPG
Basically you can loop over the targets like:
for(let tgt of this.mySameNameTargets) {
tgt.innerHTML = "Some Value"
}
This assuming you have this in the controller:
state targets = ["mySameName"]
You can also use "Action Parameters" to put the id in each row:
https://stimulus.hotwired.dev/reference/actions#action-parameters
From those docs it looks like this:
<div data-controller="item spinner">
<button data-action="item#upvote spinner#start"
data-item-id-param="12345"
data-item-url-param="/votes"
data-item-payload-param='{"value":"1234567"}'
data-item-active-param="true">…</button>
</div>
// ItemController
upvote(event) {
// { id: 12345, url: "/votes", active: true, payload: { value: 1234567 } }
console.log(event.params)
}
You can set the id of the item in that row in a param, and then when they click on that row you can dig it out of the params.

Rails: ActionView::Template::Error while removing row from table using designated .js.erb

I need a help with removing rows from a table using jQuery in Rails 4.2. Once I click on the green button, I want to have the entire row deleted and the table refreshed.
As for the logic, I am working with documents and users. A user is able to hide a document and attach a note to specific hiding. Therefore, I've decided to create additional model Hiding, which contains references to User and Document along with other relevant information:
hiding.rb
class Hiding < ActiveRecord::Base
belongs_to :document
belongs_to :user
end
From the other perspective, Document and User models have the following association:
has_one :hiding
After clicking the green button, the controller's unhide action gets called:
class Admin::HidingsController < AdminController
authorize_resource
def index
#hidings = Hiding.all.page params[:page]
end
def unhide
#hidings = Hiding.all.page params[:page]
#hiding = Hiding.where(document_id: params[:id])
# update document and delete hiding from DB
search_un_hide_document(false, nil)
end
end
However, when I want to perform deletion of table row via unhide.js.erb
$('#<%=#hiding.document.id%>').remove()
the following exception is triggered:
ActionView::Template::Error (undefined method `document' for #Hiding::ActiveRecord_Relation:0x0000000c4508d8>)
I've been stuck on this for some time as I am a newbie in Rails. Could not figure out, how to properly remove those rows without re-rendering entire page again, though.
Your suggestions would be appreciated with big gratitude. Thanks in advance!
def unhide
#hidings = Hiding.all.page params[:page]
#hiding = Hiding.where(document_id: params[:id]).first
# update document and delete hiding from DB
search_un_hide_document(false, nil)
end
Probably you forgot to add .first for #hiding
Edited:
Instead of where().first, you can also use find_by(document_id:
params[:id])
find_by is defined like this
def find_by(*args)
where(*args).take
end
Now, 'take' differs from 'first' in regards to order. first returns the first record according to the order of the primary key while take returns whatever the database spits out first.

Rails5: how to pass element to bs modal with form_tag

I have an index view listing my elements. I want a button link to open a modal view containing a form_tag to pass some text which then get's sent to a third party API, but 'linked' to the element (it's ID) passed.
So far I get the modal showing and it's submitting correctly. But: the modal get's always the first/last element of the index list.
/config/routes.rb
namespace: manage do
resources :elements do
member do
post :my_action
end
end
end
/manage/elements/index.html.haml
#elements.each do |element|
%tr
%td
= link_to 'btn-text', '#myModal', data: { toggle: 'modal', target: '#myModal'}
= render 'manage/elements/element_modal', element: element
/manage/elements/_element_modal.html.haml
#myModal.modal.fade{...}
.modal-dialog
.modal-content
= form_tag path_to_my_action(element), method: :post do
= text_field_tag :text, params[:text]
= submit_tag
I tried different approaches (a modal for each element, data-attributes, AJAX) like found eg here: Bootstrap Modal in Rails Keeps displaying the first record
But actually I can't get any of these working and I don't get why. Can you give me a hint based on above example so I finally get this working? Thanks!

Rails nested form dynamic div id

In my Rails App I am using a nested form and in my controller I have a loop which looks like this:
2.times do
practice = #team.practices.build
end
In my nested form I create a select field with the following code:
<%= f.select :day, Practice.days.keys.to_a, {}, {class: 'form-control'} %>
which gets the id: "team_practices_attributes_0_day". This is for the first practice. The second practice shower receives this id: "team_practices_attributes_1_day"
Now I want to use the variable (0, 1) in my other ids for some jQuery. Is there a way to access this number in a form?
I'm a little confused by the context of your 2.times loop (is practice referenced on the form?) Is that where the two selects are built?). So, I'm not sure if you want this on the select helper or in the answers. But a data attribute is an easy way to access data from any element.
You can add data attributes to the a select helper as part of the HTML options.
<%= f.select :day, Practice.days.keys.to_a, {}, {class: 'form-control', data-id: practice.id} %>
And then access them by calling the attribute name after data in jQuery. Note, you might want to add a specific class to query against.
var id = $('.form-control').data("id");

Categories

Resources