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 !

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.

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 !