Trigger stimulus event when target added/removed from DOM - ruby-on-rails

I have what is essentially a form with multiple pages, where there is a partial which renders the form content for each page inside of a partial that contains the header, footer, "next" button.
I need my "Next" button to respond dynamically to each page. So if the user is viewing Page 1 of the form, then I'm checking that the fields (targets) on Page 1 are not empty before enabling the "Next" button, at which point they move on to Page 2, and the next button should be disabled again until all of the fields/targets on Page 2 have been completed.
So the controller should be able to fire a function each time the page change happens, which I figured I could do by detecting when certain targets are present on the DOM. But I haven't been able to find a straightforward way to do this.
This is more or less a stripped down version of what I have
The outer wrapper:
<body data-controller="next-button">
<main>
<%= turbo_frame_tag :main, do %>
<%= yield %>
<% end %>
</main>
<%= render partial: 'application/progress_bar' %>
<% end %>
</body>
Then I also have a rails form with a text field:
<%= form.text_field :name, data: { target: "next-button.contactName"} %>
This is my controller:
export default class extends Controller {
static targets = ['contactName']
connect() {
console.log(this.contactNameTarget);
}
contactNameTargetConnected() {
console.log('foo');
}
}
When the page loads, console.log(this.contactNameTarget) outputs the HTML for contactName as expected, but contactNameTargetConnected() does nothing.
Based on the stimulus documentation regarding lifecycle callbacks, I would expect this function to fire on load, especially since it clearly can access this.contactNameTarget
I also looked at a posted solution from the stimulus team on github where they have an example that looks like it could solve my issue, but the code they've posted is deprecated, and I was not able to get it working:
const event = document.createEvent("CustomEvent")
event.initCustomEvent("inner-connected", true, true, null)
this.element.dispatchEvent(event)

Related

Dealing with 2 models in 1 view

I have two models I'm trying to interact with in 1 view. The first model is Room and the second is Availability. 1 Room has many Availabilities.
On 1 Rooms page, I render out availabilities through a partial like this:
<% #room.availabilities.where("booking_id is NULL").order("start_time").each_with_index do |a|%>
Beside each availability I have a button to delete and update. The delete worked out fine since it was in my loop so I could do something like this.
<%= link_to("Delete", availability_path(a.id), method: :delete, remote: true) %>
But I'm having trouble with edit. I'm trying to do it through a modal which doesn't have access to the 'a' variable from the loop. So I'm not sure how to pass in the unique availability to the form
Button:
<!-- Edit button -->
<button type="button" class="btn btn-xs btn-primary" data-toggle="modal" data-target="#editAvailability"><i class='fa fa-pencil'></i></button>
<!-- Edit Availability Form -->
<%= simple_form_for #facility.availabilities.find(???), method: :put, remote: true do |f| %>
You should be able to do this with AJAX. I have an app that has a modal dropdown that lets me toggle pieces of equipment in or out of service with a button in the dropdown. You can use a route that points towards the Availabilities controller that renders a form in the modal. Mine is simple in that it just toggles, but I don't see why you can't use a form. I would move your query out of your view and make a helper that gives you the results of your query in a variable.
I can provide more detail but need to see a lot more of your current code. If you can post your controller and all of your view code for the modal. I don't know understand why you don't have access to the variable you need in the modal? If the modal has an AJAX call you should be able to populate it with any data available to your controllers.
edit
Take a look at this: https://coderwall.com/p/ej0mhg/open-a-rails-form-with-twitter-bootstrap-modals . Be sure and read the links at the end of this article, it has some StackOverflow examples that are spot on. I'm thinking link_to is the way to go:
<%= link_to 'Update, availabilities_edit_path(a.id), {:remote => true, 'data-controls-modal' => "modal-window", 'data-backdrop' => true, 'data-keyboard' => true} %>
This should open a modal and ask the availabilities#edit controller for a JS response. Make sure you have an edit action, I don't see one in your controller:
availabilities.rb
def edit
#availability = Availability.find_by(id: params[:id])
respond_to do |format|
format.html
format.js
end
So the JS call will cause it to look in the /views/availabilities/ folder for a file called edit.js.erb with content like:
$('#editAvailability').html('<%= escape_javascript(render :partial => 'editForm') %>');
In your edit form you you now have the #availability instance variable to use in your form:
<%= simple_form_for #availability, method: :put, remote: true do |f| %>
...
So your modal gets built using an AJAX lookup that returns a form built using the needed instance variable. I'm putting this together from stuff I've written and other stuff I've read, so there will probably be some errors and tweaking to get your code working. Let me know how far this gets you.
You can store all the information necessary in your update button within your loop like this:
<%= link_to("Update", method: :put, remote: true, data: { attr1: a.attr1, attr2: a.attr2 }) %>
Then every button knows what entity they are operating for.

Render some content on Rails view

I am new to Rails, so I'm not sure if this is the correct way to go about doing this. I have a view that contains an AJAX link for a number of checkboxes in the form of
<% #row_headers.each do |row_header| %>
<% row_header_ = row_header.gsub(" ", "-") %>
<%= check_box_tag row_header_, row_header, params.key?(row_header_),
:data => { :remote => true,
:url => url_for(
controller: :web_pages,
action: :administratorswgraph} %> <%= row_header %>
<% end %>
<% end %>
This is being called from the :administratorswgraph view. Later on in the file I have
<% if not #swChart.nil? %>
<div id="swChart"></div>
<%= render_chart(#swChart, 'swChart') %>
<% end %>
Where render_chart is from the GoogleVisualr library. On the controller I have
def administratorswgraph
headers = []
#row_headers.each_with_index do |row_header, i|
if params.key? row_header.gsub(" ", "-")
headers.push i
end
end
if headers.empty?
#swChart = nil
else
#swChart = MakeSiteChart(headers, 580, 480)
end
end
Where the MakeSiteChart function returns a GoogleVisualr object based on the checkboxes. What I want is that for every time the checkbox's state is changed a new chart is made and shown. I can tell from my debugger, that indeed <%= render_chart(#swChart, 'swChart') %> is getting called in the view whenever a checkbox's state is changed, however the display in the browser is never updated. How do I get the display in the browser to show the chart?
Edit
I was able to get control of the ajax event by using the following method
$('#<%= #row_headers[0].gsub(" ", "-") %>').on('ajax:success', function(event, xhr, settings) {
alert("HERE")
});
For testing purposes I'm only hooking up the first checkbox. However, I'm not sure how to parse the arguments, how to get the chart, and how to insert it back into the DOM.
If your controller is RESTful then web_pages#administratorswgraph can answer to html and ajax with separate view (say administratorswgraph.html.erb and administratorswgraph.js.erb).
Place in administratorswgraph.js.erb all your logic responsible for replacing the entire chart... I think its enough to place <%= render_chart(#swChart, 'swChart') %> there, but Im not sure.
assuming that you are using jQuery and so jQuery-UJS, you can hook into the AJAX-Callbacks and replace the chart in your dom with the response that came from the server: https://github.com/rails/jquery-ujs/wiki/ajax
unfortunately, there is no good example for this on guides.rubyonrails.org...
After messing around with this a lot I was able to come up with a satisfactory solution. I used the following JavaScript within the view
<script type="text/javascript" charset="utf-8" id ="myScript">
<% #row_headers.each do |row_header| %>
$('#<%= row_header.gsub(" ", "-") %>').bind('ajax:success', function(event, data, status, xhr) {
var scripts = $(data).filter('script')
var chartScript;
scripts.each(function(index, Element){
if (Element.text.indexOf("swChart") != -1 && Element.id != "myScript") {
chartScript = Element.text;
}
});
jQuery.globalEval(chartScript)
});
<% end %>
</script>
This allowed me to extract the JavaScript that needed to be run after the ajax request came back and I was able to run it.

Attaching functionality to a button in rails 3

I have a set of buttons displaying on my webpage. The effect of clicking one of the buttons needs to be that a call is made to an external API (and maybe the response being received, and updating something on the page).
Some additional information: these buttons are placed on the page by a partial, and make up part of a list of users. The buttons are intended to activate and deactivate the users being listed. I'm not sure if this setup will affect the best approach for doing what I want to do, so I thought it would be worth mentioning.
How should this be done? Should the buttons be links to some controller within my rails app? Wouldn't that require the page to be reloaded when the button is hit? Can I offload that request to ajax?, etc.
I don't know the best way to approach this, and any guidance would prove invaluable.
Ok. I believe I have found a good implementation of this.
The trick is to create a form encapsulating the button in order to hit the proper controller when the button is clicked. In my case, I used the rails form_tag function to generate my button within my _list_item.html.erb partial view for my Developer controller as follows:
<div id=<%= list_item.id %>>
<%= form_tag "/Developer/toggle", :method => "get", :remote => true do %>
<p>
<% if list_item.inactive? %>
<%= submit_tag "Activate", :name => nil %>
<input type="hidden" name="act" value="activate" />
<% else %>
<%= submit_tag "Deactivate", :name => nil %>
<input type="hidden" name="act" value="deactivate" />
<% end %>
</p>
<input type="hidden" name="dev_id" value=<%=list_item.id%> />
<% end %>
</div>
There are 2 things that should be called to attention within this partial.
Since this is a partial rendered as part of a list, you want to give each list item a unique id so that your javascript will act on only that element. This is done in the first line, <div id=<%= list_item.id %>>, which I know will be unique because each Developer in the list necessarily has a unique id.
:remote => true is a necessary argument to the form_for function this is what causes the request to be made in the background as opposed to loading a new page.
This form, when submitted hits my the Developer#toggle action with two parameters: act, which is either activate or deactivate and id which is the id of the Developer we are acting on. Both of these parameters are hidden fields within the form.
After the form is submitted, inside of the controller, I just obtain an instance of the correct Developer (in my case, doing so is rather complicated, but in most cases it's probably something like #dev = Developer.find(id)), and performs the steps necessary to activate/deactivate the developer.
Lastly, I created a toggle.js.erb file within the view directory for my Developer controller which gets rendered once the controller has completed its task. This file simply obtains the element (through the unique id we gave it in the partial) and replaces the inner html by re-rendering the partial as follows:
document.getElementById("<%=escape_javascript(#dev.id)%>").innerHTML="<%=escape_javascript(render :partial => 'developer/list_item', :object => #dev) %>";
The result is the partial being re-rendered after the developers active status has changed, resulting in the appropriate Activate or Deactivate button.
I realize that this answer is highly focused on my particular application, especially needing to deal with the toggling of active vs. inactive, however I believe it is easily simplified and adapted to other cases that may require less complexity.
You could do this task with Rails but then page refresh must be done. If you use Javascript/AJAX then page shouldn't be refreshed. Example (jQuery):
$(":button").click(function(){
//Your Code here..
});
EDIT:
Above is jQuery code. What you want to do is some action to happen when you click on button. If you want to call external API then you could use ajax. Look for documentation for POST and GET in jQuery: http://api.jquery.com/jQuery.post/ and http://api.jquery.com/jQuery.get/.
Google for rails jquery tutorial.

How to pass a lambda to link_to

I would like to use lambda as a parameter for link_to for the code below: edit.html.erb
<h2>Edit customer info</h2>
<%= render 'form' %>
<%= link_to(#return_to) do %>
Back
step_back()
<% end %>
Here is the def for step_back:
#return link for previous page in page step
def step_back
session[:page_step] -= 1
end
The problem with the code above is that the step_back() is executed as soon as the edit.html.erb is loaded. Actually the step_back should only be executed when the user clicks the Back link. I figure that only lambda can accomplish this.
Any thoughts?
Your options are limited since you're interacting with the session.
You're getting that #return_to from somewhere; it'd probably be easiest to call an action that gets the same data and redirects to it, and does the same session manipulation.
See about writing your step_back() function in javascript and attaching it to an onClick html attribute on an tag instead of using the link_to rails helper
Then also .preventDefault() the event with javascript if you don't want the link to go anywhere, or to '#'
This will allow the code to execute on the click event in the browser and not during asset compilation before the page is served.

Dynamic Fancybox forms in Rails

Can anyone point me towards some simple step by step guides that would help me to understand how to couple Fancybox and Ajax together?
I have three models:
Class Event
belongs_to :site
belongs_to :operator
End
Class Site
has_many :events
End
Class Operator
has_any :events
End
Users can add a new event, and select Site or Operator from select boxes. I've also created a quick-add partials for Site and Operator. These are rendered in a div on the Event form, and displayed in Fancybox when a link is clicked.
<%= link_to_box "Add", "#site" %>
<%= link_to_box "Add", "#operator" %>
<div id="site">
<%= render 'sites/quickadd' %>
</div>
<div id="operator">
<%= render 'operators/quickadd' %>
</div>
So far so good.
Now I have two questions.
1- How to I hide the quick-add divs on the Event form, but display them in Fancybox. CSS classes such as display:none or visibility:hidden result in the partials not displaying in either location. Currently the partials are rendered at the end of the Event form as well as in the Fancybox popup, but this is not ideal.
2- How do I setup these quickadd partials so that they dynamically update the Event form. For example I'm adding a new Event for site Foobar. Foobar is not available in the select box so I click "add", enter Foobar in the popup form, click save, and Foobar is automatically set in the select box on the Event form.
I assume that question 2 will involve Ajax and calls for remote => true. However I'm very new to this and really need a basic step by step guide that would help me understand how to implement this. For example, do the "add" links need to be remote, or the partials? If the partials, how do I code that? After save, how do I update and set the parent form select box?
Like I said, basic stuff, but having read several guides for Fancybox and other lightbox-like popups, and several guides for Ajax, I'm still having trouble tying the two together.
Thank you for any pointers.
1) If it isn't showing and hiding the divs correctly for you, then I would put them as display:none; in the css and use something like this for your fancybox call
$("#site").fancybox({
onStart : function() {
$('#DIV').show();
},
onClosed : function() {
$('#DIV').hide();
}
});
2) When I have a form like your describing, after you are done submitting the form (which I would submit using :remote => true on the form tag, you can have it check for ajaxComplete. and you can set some variables on your create.js.erb or update.js.erb files that will show and hide forms, set the current index, or you might have to reload your list after the ajaxCompleted is "completed"
Not sure if this helps or gives direction, but hope it will give some guidance..might be able to post more later today to help

Resources