Chapters
57 Chapters
|
6:19:46
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
45.
Multiple Updates in one Stream
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
"doctrine/annotations": "^1.0", // 1.13.1
"doctrine/doctrine-bundle": "^2.2", // 2.3.2
"doctrine/orm": "^2.8", // 2.9.1
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^6.1", // v6.1.4
"symfony/asset": "5.3.*", // v5.3.0-RC1
"symfony/console": "5.3.*", // v5.3.0-RC1
"symfony/dotenv": "5.3.*", // v5.3.0-RC1
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/form": "5.3.*", // v5.3.0-RC1
"symfony/framework-bundle": "5.3.*", // v5.3.0-RC1
"symfony/property-access": "5.3.*", // v5.3.0-RC1
"symfony/property-info": "5.3.*", // v5.3.0-RC1
"symfony/proxy-manager-bridge": "5.3.*", // v5.3.0-RC1
"symfony/runtime": "5.3.*", // v5.3.0-RC1
"symfony/security-bundle": "5.3.*", // v5.3.0-RC1
"symfony/serializer": "5.3.*", // v5.3.0-RC1
"symfony/twig-bundle": "5.3.*", // v5.3.0-RC1
"symfony/ux-chartjs": "^1.1", // v1.3.0
"symfony/ux-turbo": "^1.3", // v1.3.0
"symfony/ux-turbo-mercure": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.0-RC1
"symfony/webpack-encore-bundle": "^1.9", // v1.11.2
"symfony/yaml": "5.3.*", // v5.3.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.3.1
"twig/intl-extra": "^3.2", // v3.3.0
"twig/string-extra": "^3.3", // v3.3.1
"twig/twig": "^2.12|^3.0" // v3.3.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
"symfony/debug-bundle": "^5.2", // v5.3.0-RC1
"symfony/maker-bundle": "^1.27", // v1.31.1
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/stopwatch": "^5.2", // v5.3.0-RC1
"symfony/var-dumper": "^5.2", // v5.3.0-RC1
"symfony/web-profiler-bundle": "^5.2", // v5.3.0-RC1
"zenstruck/foundry": "^1.10" // v1.10.0
}
}
What JavaScript libraries does this tutorial use?
// package.json
{
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.13.13
"@fortawesome/fontawesome-free": "^5.15.3", // 5.15.3
"@hotwired/turbo": "^7.0.0-beta.5", // 1.2.6
"@popperjs/core": "^2.9.1", // 2.9.2
"@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
"@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/Resources/assets", // 0.1.0
"@symfony/ux-turbo-mercure": "file:vendor/symfony/ux-turbo-mercure/Resources/assets", // 0.1.0
"@symfony/webpack-encore": "^1.0.0", // 1.3.0
"bootstrap": "^5.0.0-beta2", // 5.0.1
"chart.js": "^2.9.4",
"core-js": "^3.0.0", // 3.13.0
"jquery": "^3.6.0", // 3.6.0
"react": "^17.0.1", // 17.0.2
"react-dom": "^17.0.1", // 17.0.2
"regenerator-runtime": "^0.13.2", // 0.13.7
"stimulus": "^2.0.0", // 2.0.0
"stimulus-autocomplete": "https://github.com/weaverryan/stimulus-autocomplete#toggle-event-always-dist", // 2.0.0
"stimulus-use": "^0.24.0-1", // 0.24.0-2
"sweetalert2": "^11.0.8", // 11.0.12
"webpack-bundle-analyzer": "^4.4.0", // 4.4.2
"webpack-notifier": "^1.6.0" // 1.13.0
}
}
14 Comments
Instead of adding another else statement to the template you can also add an "append" turbo stream to the reviews frame to add a button within the turbo frame tags:
That's very nice! 😀
Hey Ryan, I have a Turbo Stream update working well. On my site I have a small Stimulus controller that listens for changes in a form, and then on change submits the form automatically, successfully doing inline editing of objects from my database. But after this is executed:
I no longer have a listener for that form that is on _subs.html.twig.
So I can successfully edit once, but say I hit '7' when I wanted to hit '8', it won't recognize the change because it isn't listening anymore and I have to manually reload the page. If I switch my action on the stream to the new 'refresh' the page reloads and it works, but I get that screen blink from a refresh and it kind of defeats the purpose I think.
Any ideas on how I can correct this?
Hey @Brandon!
Sorry for taking so long to respond! This is the type of thing that simply "should work" with Stimulus: it's built to keep working even after content is changed in the site. So my guess is that there's just some small issue with how that Stimulus controller is set up :). To "listen to changes on the form", are you adding the event listener in JavaScript via the
connectmethod of your controller? Or are you addingdata-actionattributes to each element that should trigger the auto-submit. If you're doing the first, that would cause this problem because the listeners are only registered once. So when some new HTML is added, there are no listeners added to the new elements.Anyway, I'm doing some guessing. But if you're still having issues and this doesn't help, show me the template code & Stimulus controller and I bet we can figure it out :).
Cheers!
Ryan,
I ended up doing this:
And just adding the controller to the Stream. It works well enough, but obviously the controller connects twice in my console.
The listener is in the connect method, this is it:
A small side note, this didn't work well at all for me until I changed 'form.Submit()' to 'form.requestSubmit' which I found in the forum's, someone else had the same issue I was having. I don't think that is affecting my original issue, but I thought I'd mention it.
Lastly, in my template, on my 'index.html.twig' in my top column div is where I initially connect this controller. My page then contains two Turbo Frames, which I also want to mention, the second column on my page contains the frame where a form is added to the page, but it doesn't listen to it until a page refresh. When you change the form in the second column, it updates the first column with the another Turbo Frame.
Hey @Brandon!
Ah, perfect! Seeing your Stimulus controller is what I needed :). So yes, the problem is what I described above. Let's assume that you haven't added the
span data-controller="submit-on-change">to the<turbo-stream>. In that case, here is the flow:A) You load the page. The
connect()method ofsubmit-on-changeis called. It finds all of the correctformelements inside... then finds each field. For each field, it registers achangeevent.B) Then, via the
<turbo-stream>you replace the<form>elements on the page with NEW<form>elements. However, importantly, the parent element that has thedata-controller="submit-on-change"was NOT replaced. And so itsconnect()method was not called. But now, those new<form>elements? They don't have anychangelisteners on their fields. That's because all of the old form fields were just deleted from the page and brand new fresh elements were added. And those fresh elements do NOT have any listeners attached to them.The "proper" solution might be annoying in your situation, but here it is: do not "find" elements from inside
connect()and manually register event listeners. Instead:1) Add the
data-controller="submit-on-change"to each<form>element where this is needed.2) add
data-action="change->submit-on-change#submit()to every form field where you want this behavior.3) create a
submitmethod that is simply:(Side note, yes
requestSubmit()is correct - it's a weird thing where callingsubmit()doesn't dispatch asubmitevent and so Turbo or other JavaScript can't hook into it).So, this is what I'd recommend and it will "just work". Otherwise, you're trying to fight against Turbo. If adding the
data-actionis to every field is annoying, you could implement afinishView()method in your form that loops over every field and adds this to the field'sattr. This old tutorial - https://symfonycasts.com/screencast/symfony-form-theming/controlling-vars-with-finish-view - is still pretty relevant. You can alsoforeach ($view as $childView)to loop over every field to get the childFormView. Just be wary that more work would need to be done if you have a more complex form with fields that go beyond 1 level deep.Cheers!
It seems that now it's no longer
$request->setFormat(TurboBundle::STREAM_FORMAT);but
$request->setRequestFormat(TurboBundle::STREAM_FORMAT);related to <a href="https://github.com/symfony/ux-turbo/commit/3f59eadd9e5b6d282031c4ab0fa699b201027511">this commit </a>
Thanks jgrasp - it's on my list to add a note about this! I asked for the latest tag to be created for that library... but haven't had a chance to add this note yet!
EDIT: Nevermind, I DID have a note, but you're correct that I still had this mistake in my updated code. This was also just fixed on the main repo. Thanks!
Another way to skin the cat.
In the real world you're only allowing one review per product per user. So, move the addFlash to before returning the TurboStreamResponse (I have a print 'review_success' flash in my _product_reviews twig, so it will show up in the stream response). The _reviews template more-or-less stays the same except I add a messages saying "you have already reviewed this product" or you could have a link to EDIT your review if the "form" variable isn't passed. There are some other "handle yourself" coding here, like determining in twig one review per product per user (no good performance / reusable way to do this, unless perhaps you create a whole new entity to keep track of product/review/user but that seems like a mess, you have to query for if this product has been reviewed by this user, put it in a service if you use it more than twice). I'm sandboxing mix/match code for how I would use this code for my projects as I move along here. So far I haven't thought of a situation where I wouldn't just ternary this in the render options array, or if/else for readability depending on how long the line is. Your results may vary.
Hey @Jay Gee!
Thanks for the post! I think Turbo Streams (and frames) are still so new that there are undoubtedly better ways to do certain things - I was pushing things pretty far, but mostly thinking of the "best ways that *I* could think of doing something"... and I almost definitely didn't always end up with the best way. I'm glad you're thinking critically to challenge what I came up with :).
Cheers!
"Turbo can also be used to build Native iOS or Android apps". Now that has me convinced. Does this mean one could take a mostly PHP powered website and with some JS/Turbo sprincles make a mobile App? I's love to see an example of that even if it's just a Hello World or To Do List App.
Hey Matthias K.!
That's.... an interesting idea! It's a bit outside of my wheelhouse... but it could be kind of fun to try. We'll add it to our idea list - but it wouldn't be something that would happen too soon :).
Cheers!
I wonder if this would also be a good use to move the logic for a faceted search completely to the backend or might there be a limit where it might be too many templates in a stream response?
Hi, Michael!
To my knowledge, there is no limit as to how many templates you can return from a request. This might be a matter of practice: When would a response become too large, or the number of templates so big that it would take noticeable time for the JavaScript to parse it. In a real world scenario, I can't imagine this becoming an issue!
"Houston: no signs of life"
Start the conversation!