Make a form submission targeting several turbo frames?

Hello,

I have a page with 2 turbo frames, “add_content” and “content”. The “add_content” contains a form submission which allows to create new items in the “content” turbo frame (Note that the “add_content” turbo frame has also elements to be uptated when new items are added for example the count of items in “content”).

I would like the form submission to target both “add_content” and “content” turbo frames to be uploaded, Is there a way to do that ?


For now I only managed to upload 1 turbo frame at a time or to refresh the whole page:

  • With the data-turbo-frame="content" in the form submission I can update only the “content” turbo frame
  • If I remove the data-turbo-frame="content" in the form submission, only the “add_content” turbo frame is uploaded
  • If I use data-turbo-frame="_top" the whole page is refreshed

Thank you for your help !

1 Like

Out of the box you cannot target multiple turbo frames. Options to consider are:

  • Submit to add_content and return a turbo-frame with includes a turbo-stream element targeting html inside content
  • Use data-turbo-frame="_top" (or don’t use turbo-frame at all) and respond with 2 turbo-streams (one for add_content and one for content)
  • Submit to add_content and use a stimulus controller which reloads or alters the src tag of content turbo-frame.

My personal preference would be the second option.

2 Likes

Thank you for your answer. Second option looks the easiest for me but I’m not sure how to set it.

Here is my html structure:

<turbo-frame id="data_frame-add">
    <form name="AddContentForm" action="{{ url_for('add_content') }}" method="POST" data-turbo-frame="data_frame-content">
        <input type="text" list="sectionList" id="section" name="section">
            <datalist id="sectionList">
                {% for content in contents %}
                    <option value="{{ content }}">
                {% endfor %}
            </datalist>
        <button type="submit" onclick="submitForm()">Add</button>
    </form>
</turbo-frame>

<turbo-frame id="data_frame-content">
        {% for content in contents %}
                <div class="flex-column">
                    <p>>{{ content }}</p>
                </div>
        {% endfor %}
</turbo-frame>

Looking at the doc, from the 7 actions that can be performed by turbo streams, update seems to be the closest to what I want but I am unclear with the usage.

Can I just replace both turbo-frames data_frame-add and data_frame-content by turbo-stream with update action like:

<turbo-stream action="update" target="sectionList">
    <template>
        <form name="AddContentForm" action="..." method="POST" data-turbo-frame="data_frame-content">
            <input type="text" list="sectionList" id="section" name="section">
            <datalist id="sectionList">
                {% for content in contents %}
                    <option value="{{ content }}">
                {% endfor %}
            </datalist>        
            <button type="submit" onclick="submitForm()">Add</button>
        </form>
    </template>
</turbo-stream>

<turbo-stream action="update" target="content">
    <template>
        <div id="content">
            {% for content in contents %}
                <div class="flex-column">
                    <p>>{{ content }}</p>
                </div>
            {% endfor %}
        </div>
    </template>
</turbo-stream>

How would you suggest to proceed ?

If you are using turbo-stream, you don’t need turbo-frame. You could do something like the following:

Original HTML:

<form name="AddContentForm" action="{{ url_for('add_content') }}" method="POST">
  <input type="text" list="sectionList" id="section" name="section">
  <datalist id="sectionList">
    {% for content in contents %}
      <option value="{{ content }}">
    {% endfor %}
  </datalist>
  <button type="submit" onclick="submitForm()">Add</button>
</form>

<div id="contents">
  {% for content in contents %}
    <div class="flex-column">
      <p>>{{ content }}</p>
    </div>
  {% endfor %}
</div>

Turbo-Stream Response:

<turbo-stream action="update" target="sectionList">
  <template>
    {% for content in contents %}
      <option value="{{ content }}">
    {% endfor %}
  </template>
</turbo-stream>

<turbo-stream action="update" target="contents">
  <template>
    {% for content in contents %}
      <div class="flex-column">
         <p>>{{ content }}</p>
      </div>
    {% endfor %}
  </template>
</turbo-stream>

Where should I put the Turbo-Stream Responses ?

I put it at the end of the original html but adding new content reloads everything and not only the 2 concerned div sections. Is there something to add in order to trigger the 2 Turbo-Streams when the submit button is clicked ?

What backend are you using?

Read the section Streaming From HTTP Responses from the turbo documentation.

Turbo looks at the mimetype of the response. If it’s a mimetype text/vnd.turbo-stream.html it will process it as a turbo-stream, otherwise it will process a test/html response as a standard request.

I’m using Flask from Python

OK thank you for your help, I will check how to implement turbo elements in my add_content function

Here is the answer to my question which makes a form submission targeting several DOM elements using Turbo-Streams (no need to use Turbo-Frames).


Considering I have a main view function called Main in my Flask app:

@app.route('/')
def Main():
    ...
    return render_template('base.html', contents=contents)

With base.html:

<form name="AddContentForm" action="{{ url_for('add_content') }}" method="POST">
  <input type="text" list="sectionList" id="section" name="section">
  <datalist id="sectionList">
    {% for content in contents %}
      <option value="{{ content }}">
    {% endfor %}
  </datalist>
  <button type="submit" onclick="submitForm()">Add</button>
</form>

<div id="contents">
  {% for content in contents %}
    <div class="flex-column">
      <p>>{{ content }}</p>
    </div>
  {% endfor %}
</div>

In order to reload only specific targeted parts of the html page when adding new content I had to modify the add_content function in the Flask app.
Instead of redirecting toward the main view function (which reloads the whole page):

@app.route('/add', methods=['POST'])
def add_content():

...

return redirect('/')

I returned a Turbo-Streams element:

@app.route('/add', methods=['POST'])
def add_content():

...

sectionListHtml = render_template("sectionList.html", contents=contents)
contentsHtml = render_template("contents.html", contents=contents)

return turbo.stream([
        turbo.update(sectionListHtml, target="sectionList"),
        turbo.update(contentsHtml, target="contents")
    ])

with sectionList.html:

{% for content in contents %}
      <option value="{{ content }}">
{% endfor %}

and contents.html:

{% for content in contents %}
      <div class="flex-column">
         <p>>{{ content }}</p>
      </div>
{% endfor %}

Complementary informations

initialization of the python Flask app with Turbo:

from flask import Flask, render_template, request, url_for, redirect
from turbo_flask import Turbo

app = Flask(__name__)

turbo = Turbo(app)

Thank you again for your help @tleish !