Ask A Question

Notifications

You’re not receiving notifications from this thread.

Dynamic Nested Forms with Stimulus JS Discussion

Yes this is what ive been waiting for!

Reply

Great, I like your screencasts about Stimulus a lot!

Reply

That's a great one!

I'd suggest using <button type="button"> elements instead of links. This way you don't have to use event.preventDefault() because by default a button doesn't do anything.

Also, for both links and buttons, the default Stimulus action is click so the click-> part in the data-action is optional.

Reply

I included these revisions and the code still works fine, and is now more readable.

Thanks for sharing.

Reply

This limits the flexibility of the nested_form controller. Now you can ONLY use it with buttons

Reply

Great episode !
I use cocoon often in my invoicing apps projects to allow people to add items into a bill.
Next, I'll give stimulus way definetly a chance !

Reply

Well done! Keep the Stimulus JS tutorials coming!

I wonder if there is a tutorial you could assemble where we'd see Turbolinks and Stimulus working together, to provide some context around why Basecamp promotes them.

Even cooler would be a use case where someone today is typically reaching for Rails with React, but instead having the tutorial depicting how the Rails/Turbolinks/Stimulus combo would solve the same use case.

Thanks!

Reply

+1

Reply

Great episode Chris! Totally bypassed an entire gem.

Reply

Keep the stimulus stuff coming!

Reply

This is really great stuff. Stimulus with rails just completes the toolkit so nicely.

Reply

How do you display the Tasks in the Project Index and Show Views?

Reply

Nevermind, got it working.

Reply

Great episode, I've been using Cocoon in past projects to add a "content section block" to a page, which then has different types of content associated to it (ie textarea, text with image, image gallery, etc.), so that it can then be reordered if needed.

I'd like to use more Stimulus moving forward, so interested in trying this technique out.

One part that I could never quite suss out, was when adding a content section block with a file auto uploader, such as jQuery File Upload (for multiple images/or documents) I couldn't upload anything until the content section block was saved to the database via a page save.

I guess with Stimulus, a new nested field could be saved to the database within the Stimulus controller, when it's added to the form? Then there would be an ID to use for any files to upload against.

Saying that, with a move away from jQuery, would Stimulus be able to deal with file uploads in a simular way to jQuery File Upload or is it best to leave that sort of thing to ActiveStorage?

Reply

I've been playing around with Stimulus over the last couple of evenings, trying to implement drag and drop sorting. Everything is working as expected when swapping elements on the page around. I seem to be fighting a battle, however, getting an AJAX post to send data from my stimulus controller to my rails controller. No parameters are being passed in the AJAX post.

In my stimulus contrller I have the following code

var data =  {
      data: {
        name: "test"
      }
    };

    Rails.ajax({
      dataType: 'json',
      type: 'POST',
      url: '/projects/test',
      data: { data },
      success: function(data) {
        console.log(data);
      },
      error: function (response) {
        console.log(response);
      }
    })

When the code is triggered I'm not seeing anything parameters passed over in the log. All I see is

Started POST "/projects/test" for ::1 at 2019-03-06 22:30:42 +0000
Processing by ProjectsController#test as JSON
<ActionController::Parameters {"controller"=>"projects", "action"=>"test"} permitted: false>

Looking in the Chrome Inspector, it looks like the data is being sent as [object Object]. If I add a query string to the URL, the parameters show as expected. I've tried all sorts of combinations of using JSON.stringify and JSON.parse, but no joy.

If I change the type to "GET" I get the following in my log

Started GET "/projects/test?[object%20Object]" for ::1 at 2019-03-06 22:47:40 +0000
Processing by ProjectsController#show as JSON
  Parameters: {"object Object"=>nil, "id"=>"test"}

It feels like i've exhausted all options, with no luck. I'm probably missing someting obvious. If anyone has an pointers or suggestions, they'd be most welcome. Think in the meantime I'll go back to Cocoon, just to get it working and then refine it later

Reply

Have you tried converting the payload to a string before sending it?

data: JSON.stringify({ data: { name: "test" } }),
Reply

Hey there! first comment here, trying to follow up the tutorial but missing the template.rb file you're using to start the project... i'm quite sure missing something basic :)

can someone help?

thanks!

Reply

you can find it here: https://github.com/excid3/jumpstart. See readme for instructions.

But frankly, I don't like using this template. In more than a few tutorials that this template is used, I encounted bugs that only slowed down the progress of following through the tutorial, instead of helping it.

Because the jumpstart script is constantly updated, older tutorials using it see a breakage.

Reply

then what way you'd suggest to follow up tutorials? cloning the project?
thx!

Reply

interesting replacement for cocoon. Wondering, does anyone see something here that cocoon does cover and this does not? Weighing options here, and want to be wary of moving away from the mature cocoon implementation and perhaps missing some implicit functionality it offers, gotchas, etc that this may not cover.

side note:
I have implemented a text_field_tag version of this using stimulus where it wasn't attached to a nested association directly and used a similar setup to this...template is a cool alternative to building the inputs in JS - thanks!

Reply

Great episode.. (I also would love to see the same stuff with JSON(B) fields (I mean child elements as part of a hash, I have always great troubles with validation and types regarding Hash-type columns)
I also have an idea for a follow up video:
How to use stimulus to manage multi-step forms (with or without AJAX -but I don't know if doing this without AJAX makes sense since it will need JS anyway)

Without AJAX, I wonder how to embed the elements of the next steps in the first page?
Using the template element for each subsequent steps?

And with AJAX, I wonder how to insert the server response as a replacement of the current form?

BTW keep the good work!

Reply

Hey Chris! I'm really enjoying episodes like this one that seek to replace common jQuery solutions. There's a number of people on my team who come from the WP world, who used jQuery a lot, but are now trying to stay away from it. The simplicity and quickness here is hard to beat.

One thing I ran into, and I don't know if it's something with Firefox, is that the items within the template were submitting to the server, causing it to not properly convert the strong params. Any one else seeing this? Rails didn't like "NEW_VALUE" as a hash key. (I'm using it for a nested has_many)

I was able to solve it by adding a disabled: local_assigns.fetch(:disabled, false) attribute to all inputs in my fields partial, passing that value as true for my template partial, and then removing that disabling in Stimulus via our "addAssociation" method.

Reply

Would there be a way to order the objects returned on edit? For example, each item I have has a date, quantity, and notes field. How would I sort by date?

Reply

here is how I did it with asset pipeline and coffee script (adapted from your code)

class window.NestedFormController extends Stimulus.Controller
  @targets = ["links", "template"]

  add_association: (event) ->
    event.preventDefault()

    content = @templateTarget.innerHTML.replace(/NEW_RECORD/g, u.rand())
    @linksTarget.insertAdjacentHTML("beforebegin", content)

  remove_association: (event) ->
    event.preventDefault()

    wrapper = event.target.closest(".nested-fields")

    if wrapper.dataset.newRecord == "true"
      wrapper.remove()
    else
      wrapper.querySelector("input[name*='_destroy']").value = 1
      wrapper.style.display = "none"

window.application.register 'nested-form', window.NestedFormController
Reply

Neat CLI trick: mv app/javascript/controllers/{hello,nested_form}_controller.js to rename the file.

Reply

Ok Chris, you saved my life again.

Reply

Thanks, Chris. This help me a lot.
Only one problem.

In the stimulus controller, when task is not a new record, remove it will not hide the content.

change

wrapper.style.display = "none"

to

$(wrapper).children().hide()

Hope this could help others.

Reply

Because the task_fields partial is a continuation of the form builder, it doesn't lend itself to the optimized partial render for collections. In your server logs, you'll see a lot of:

rendered partial 'projects/task_fields' (Duration 0.1 to 0.5 ms)
for each task in your project.

I'm not sure how to optimize this b/c of the form builder pattern is making this slightly different than the normal use case for collection partial renders.

Reply

When writing rails system tests, it might not be intuitive how to fill in multiple inputs that have the same label, but here is how I did it:

def test_create_multiple_tasks_from_project
  visit new_project_path
  click_on 'Add Task' # now there are 2 Task inputs with the same Task Label
  all("input[id^='project_tasks_attributes'][id$='_description']").each.with_index do |task, index|
    task.fill_in with: "Task number #{index}!"
  end
  click 'Save Project'
  assert_text "Task number 0!"
  assert_text "Task number 1!"
Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 87,563+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.