gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Remove the link. In base.html.twig
, we already have a few JavaScript files that are included on every page. But now, I want to include some JavaScript on just this page - I don't need this stuff everywhere.
Remember from earlier that those script tags live in a javascripts
block. Hey, that's perfect! In the child template, we can override that block: {% block javascripts %}
then {% endblock %}
:
... lines 1 - 23 | |
{% block javascripts %} | |
... lines 25 - 36 | |
{% endblock %} |
Now, whatever JS we put here will end up at the bottom of the layout. Perfect, right?
No, not perfect! When you override blocks, you override them completely. With this code, it will completely replace the other scripts in the base template. I don't want that! I really want to append content to this block.
The secret awesome solution to this is the parent()
function:
... lines 1 - 23 | |
{% block javascripts %} | |
{{ parent() }} | |
... lines 26 - 36 | |
{% endblock %} |
This prints all of the content from the parent block, and then we can put our cool stuff below that.
Here's the goal: add some JavaScript that will make an AJAX request to the notes API endpoint and use that to render them with the same markup we had before. We'll use ReactJS to do this. It's powerful... and super fun, but if it's new to you, don't worry. We're not going to learn it now, just preview it to see how to get our API working with a JavaScript frontend.
First, include three external script tags for React itself. Next, I'm going to include one more script tag that points to a file in our project: notes.react.js
:
... lines 1 - 23 | |
{% block javascripts %} | |
{{ parent() }} | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> | |
<script type="text/babel" src="{{ asset('js/notes.react.js') }}"></script> | |
... lines 31 - 36 | |
{% endblock %} |
Let's check that file out! Remember, it's in web/js/notes.react.js
:
var NoteSection = React.createClass({ | |
getInitialState: function() { | |
return { | |
notes: [] | |
} | |
}, | |
componentDidMount: function() { | |
this.loadNotesFromServer(); | |
setInterval(this.loadNotesFromServer, 2000); | |
}, | |
loadNotesFromServer: function() { | |
$.ajax({ | |
url: '/genus/octopus/notes', | |
success: function (data) { | |
this.setState({notes: data.notes}); | |
}.bind(this) | |
}); | |
}, | |
render: function() { | |
return ( | |
<div> | |
<div className="notes-container"> | |
<h2 className="notes-header">Notes</h2> | |
<div><i className="fa fa-plus plus-btn"></i></div> | |
</div> | |
<NoteList notes={this.state.notes} /> | |
</div> | |
); | |
} | |
}); | |
var NoteList = React.createClass({ | |
render: function() { | |
var noteNodes = this.props.notes.map(function(note) { | |
return ( | |
<NoteBox username={note.username} avatarUri={note.avatarUri} date={note.date} key={note.id}>{note.note}</NoteBox> | |
); | |
}); | |
return ( | |
<section id="cd-timeline"> | |
{noteNodes} | |
</section> | |
); | |
} | |
}); | |
var NoteBox = React.createClass({ | |
render: function() { | |
return ( | |
<div className="cd-timeline-block"> | |
<div className="cd-timeline-img"> | |
<img src={this.props.avatarUri} className="img-circle" alt="Leanna!" /> | |
</div> | |
<div className="cd-timeline-content"> | |
<h2><a href="#">{this.props.username}</a></h2> | |
<p>{this.props.children}</p> | |
<span className="cd-date">{this.props.date}</span> | |
</div> | |
</div> | |
); | |
} | |
}); | |
window.NoteSection = NoteSection; |
This is a small ReactJS app that uses our API to build all of the same markup that we had on the page before, but dynamically. It uses jQuery to make the AJAX call:
var NoteSection = React.createClass({ | |
... lines 2 - 12 | |
loadNotesFromServer: function() { | |
$.ajax({ | |
url: '/genus/octopus/notes', | |
success: function (data) { | |
this.setState({notes: data.notes}); | |
}.bind(this) | |
}); | |
}, | |
... lines 21 - 32 | |
}); | |
... lines 34 - 69 |
But I have a hardcoded URL right now - /genus/octopus/notes
. Obviously, that's a problem, and lame. But ignore it for a second.
Back in the template, we need to start up the ReactJS app. Add a script
tag with type="text/babel"
- that's a React thing. To boot the app, add ReactDOM.render
:
... lines 1 - 23 | |
{% block javascripts %} | |
... lines 25 - 29 | |
<script type="text/babel" src="{{ asset('js/notes.react.js') }}"></script> | |
<script type="text/babel"> | |
ReactDOM.render( | |
... lines 33 - 34 | |
); | |
</script> | |
{% endblock %} |
PhpStorm is not going to like how this looks, but ignore it. Render the NoteSection
into document.getElementById('js-notes-wrapper')
:
... lines 1 - 31 | |
ReactDOM.render( | |
<NoteSection />, | |
document.getElementById('js-notes-wrapper') | |
); | |
... lines 36 - 38 |
Back in the HTML area, clear things out and add an empty div with this id
:
... lines 1 - 4 | |
{% block body %} | |
... lines 6 - 20 | |
<div id="js-notes-wrapper"></div> | |
{% endblock %} | |
... lines 23 - 38 |
Everything will be rendered here.
Ya know what? I think we should try it. Refresh. It's alive! It happened quickly, but this is loading dynamically. In fact, I added some simple magic so that it checks for new comments every two seconds. Let's see if it'll update without refreshing.
In the controller, remove one of the notes - take out AquaWeaver
in the middle. Back to the browser! Boom! It's gone. Now put it back. There it is! So, really cool stuff.
But... we still have that hardcoded URL. That's still lame, and a problem. How you fix this will depend on if you're using AngularJS, ReactJS or something else. But the idea is the same: we need to pass the dynamic value into JavaScript. Change the URL to this.props.url
:
var NoteSection = React.createClass({ | |
... lines 2 - 12 | |
loadNotesFromServer: function() { | |
$.ajax({ | |
url: this.props.url, | |
... lines 16 - 18 | |
}); | |
}, | |
... lines 21 - 32 | |
}); | |
... lines 34 - 69 |
This means that we will pass a url
property to NoteSection
. Since we create that in the Twig template, we'll pass it in there.
First, we need to get the URL to the API endpoint. Add var notesUrl = ''
. Inside, generate the URL with twig using path()
. Pass it genus_show_notes
and the genusName
set to name
:
... lines 1 - 23 | |
{% block javascripts %} | |
... lines 25 - 30 | |
<script type="text/babel"> | |
var notesUrl = '{{ path('genus_show_notes', {'genusName': name}) }} | |
... lines 33 - 37 | |
</script> | |
{% endblock %} |
Yes, this is Twig inside of JavaScript. And yes, I know it can feel a little crazy.
Finally, pass this into React as a prop using url={notesUrl}
:
... lines 1 - 33 | |
... lines 38 - 40 |
Try that out. It still works very nicely.
Go Deeper!
There is also an open-source bundle called FOSJsRoutingBundle that allows you to generate URLs purely from JavaScript. It's pretty awesome.
Congrats on making it this far: it means you're serious! We've just started, but we've already created a rich HTML page and an API endpoint to fuel some sweet JavaScript. And we're just starting to scratch the surface of Symfony.
What about talking to a database, using forms, setting up security or handling API input and validation? How and why should you register your own services? And what are event listeners? The answers to these will make you truly dangerous not just in Symfony, but as a programmer in general.
See you on the next challenge.
Check your js code in twig file. It's must be;
<script type="text/babel">
ReactDOM.render(
<notesection/>,
document.getElementById('js-notes-wrapper')
);
</script>
Instead of
ReactDOM.render(
<notesection url="{notesUrl}"/>,
document.getElementById('js-notes-wrapper')
);
Good tutorial but it would be easier to follow with just an JQuery AJAX call instead of the react stuff! I will have to follow the React course too to understand it I guess :P
Yea... I regret throwing React in here - the JavaScript isn't really the point of this tutorial and it trips people up :).
Hey, I don't agree. Your tutorials are so awesome! It helped me so much when I was joining my company. None else can describe so many things in such a cool way, as you do - AND - You motivated me to take a look at reactJS back in the day. Now I have been working in symfony for more than half year, every day and all because of this series of tutorial you have created. I like to rewatch them solely because of nostalgia purposes and quality it holds! :)
Hi,
A year has passed of this tutorial but I would like to mention that the 'notes.react.js' file doesn't load.
What's wrong?
NICE AND WONDERFUL TUTORIAL!
Kindest regards,
Hey Abelardo L. ,
What do you mean on "file doesn't load"? The file could not be found by your browser? Do you see 404 error for this file in "Network" tab og Google Chrome Dev Tools? We would love to help you fix this problem.
Cheers!
Hi victor ,
Thanks you for replying quickly.
It seems like that file isn't loaded by the browser.
How could I upload a screenshot of this issue?
Best regards.
The following files are only loaded: 'main.js' and 'jquery'.
I am using Opera 48.0.2685.39 on MAC OS El Capitan. It also happens in Google Chrome 61.0.3163.100.
Could be happen due to the newest version of React, jQuery or anything else?
Best regards, @Victor Bocharsky
I think it could be a problem related to version, but I'm not sure yet. Btw, did you download the course code and ran it from finish/ directory? Or, did you follow screencasts step by step and write all the code we show by yourself?
Hi Victor,
Yes, I followed screencasts step by step but I didn't run it from finish/ directory. I will do it.
Hey Abelardo L. ,
Great, I'd recommend you to do it separately of your code. So if it works from the downloaded archive, then we'll know exactly that the problem in *your* code only, so it'll be easy to debug things further.
Cheers!
Hi again,
I have just uploaded a screenshot when I run the 'finish' version of your app.
Please, check it out and let me know why these errors appear when I executed.
Best regards.
https://imgur.com/a/eZG6f
Hey Abelardo L.
Symfony couldnt find your autoloader file, look like you forgot to run `composer install`. Oh, also, there is a README file at the root of the project with setup instructions, you may find them useful
Cheers!
It worked! I forgot to run "composer install" (Y)
The notes were loaded too.
I am still investigating why the notes are not loaded into my app.
Cheers!
With my last comment I referred to your 'finish' version, not mine.
I am investigating why my app doesn't work.
Whenever I fix it, I will post here. :)
Regards.
Hey Abelardo L. ,
Hm, I'm going to investigate the problem. What about screenshots - we do not allow to upload screenshots in Disqus comments, but you can upload it to any cloud, e.g. https://imgur.com/ and send a link to it in comments.
Cheers!
Hey Abelardo L. ,
Hm, that's interesting! So I see this file exists and you can get it by requesting http://localhost:8000/js/notes.react.js . But... I don't see it's loaded in Network tab, which means you don't have it in HTML code - probably it's due to cache! I bet if you press "Cmd + Option + U" (or right click -> View Page Source) you won't see the line:
<script type="text/babel" src="{{ asset('js/notes.react.js') }}"></script>```
Could you double check it doesn't exist in page source code to be sure? So it means, you just need to clear the cache, which make sense if you load Symfony in production environment - you should always clear the cache manually in prod after any change in templates or in configuration. Symfony regenerate the cache by itself in dev environment only.
In shorts, try to execute in your console:
bin/console cache:clear --env=prod
I'm 99% sure it should solve your problem.
Cheers!
Obviously, this file doesn't exist in view source code page. A new picture was uploaded to be verified by you.
I executed that line but the problem persists.
I deleted Opera cache and by using (cmd + R) and (cmd + alt + R) but none happens.
How can I change between dev-prod environments with Symfony?
Hey Abelardo L.
You only have to hit (inside web folder) `app.php` instead of `app_dev.php` file. Those files are you front controller, so it depends on which web server are you running, but basically you only have to switch your project public root path
Cheers!
Hey Abelardo L. ,
Hm, I just double check it and it works for me well, i.e. browser is able to download "js/notes.react.js" file. I downloaded the course code, install composer deps and run "bin/console server:run" - that's it. So let's debug your code a bit. We include this file in one spot only: in "genus/show.html.twig" template.
{% block javascripts %}
{{ parent() }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script type="text/babel" src="{{ asset('js/notes.react.js') }}"></script>
<script type="text/babel">
var notesUrl = '{{ path('genus_show_notes', {'genusName': name}) }}';
ReactDOM.render(
<NoteSection url={notesUrl} />,
document.getElementById('js-notes-wrapper')
);
</script>
{% endblock %}
Is it the same you have?
Next, could you make sure you have this file in "web/js/notes.react.js" path?
Also, try to debug 404 error in Google Chrome dev tools, i.e. right click on "notes.react.js" -> "Open in new tab". What URL do you have? it should be exactly "http://localhost:8000/js/notes.react.js". Is it true for you?
Do you have Symfony's 404 error, i.e in the dev mode you should see something like:
No route found for "GET /js/notes.react.js"
Try to follow those debug instructions step by step, I bet we'll fix it together. If you still have the same problem in the end, then provide some answers on my questions so I can move forward.
Cheers!
In the 2:30 of the screencast, you didn't write this line:
var notesUrl = '{{ path('genus_show_notes', {'genusName': name}) }}';
Yes, we add it a bit later, about 3:35. Haven't you seen the video to the end? If not, then what minute have you stuck at?
Yes, i saw the video to the end. But in that minute, 2:30, you can show that notes but I can't do it.
I am still investigating why I don't see the notes. Curious, I have the same code than you. ¿?
2) Yes, that file is in that path.
3) The 404 error isn't showed; simply, that file doesn't appear in the list of downloaded files.
2) Great!
3) I mean open Chrome dev tools -> Network tab and reload the page. If this page not found, you should see 404 status opposite this "notes.react.js". So you can perform right click on this line and choose "Open in new tab". After it, what URL do you have in the browser's address bar? is it "http://localhost:8000/js/notes.react.js"? Do you see an error or content of "notes.react.js" file in opened browser window?
That's great! It means you have this file and you can access it. So the problem is that you do not require it in HTML code, most probably it's the cache problem, you just need to clear the cache... or you wrote this line in a wrong file :)
No, I haven't written this line in a wrong file.
I think it's the cache problem.
I will check it out with FF.
If you don't see this error, so then you should see content of "notes.react.js" file, right? Or, what do you see then in the browser's window when opens http://localhost:8000/js/notes.react.js URL?
I am not able to load the comments dynamically, even after replacing my base.html.twig, show.html.twig, GenusController.php,notes.react.js and main.js with the files in your finished folder? Where should I look for the mistake?
There is one error I do have in the console:
Uncaught SyntaxError: embedded: Unexpected token (6:59)
4 | ReactDOM.render(
5 | <notesection url="{notesUrl}/">,
> 6 | document.getElementById('js-notes-wrapper');
| ^
7 | );
Also if I look at genus/octopus/notes page, every forward slash is escaped by a blackslash. I don't think that should cause any issues though.
Hi Michael!
As you listed here (good details!), the problem is happening earlier - when the React app is being initialized. I think it's a small syntax issue on line 5. Try this:
<NoteSection url={notesUrl} />,
The NoteSection upper-casing is important, but the real problem is that you have an extra set of quotes around {notesUrl} and the ending / is inside of these quotes (that last detail I believe is causing the error).
Let me know if this helps!
Cheers!
Hi Ryan,
Thanks for the great tutorial. Can you tell me how I can set the Symfony that I am working on Development Environment?
Unless I add /app_dev.php/ in the notes.react.js as below, it doesn't find http://domain/genus/octopus/notes.
Thanks.
Hey Enkhbilguun E.!
Ah yes, great question! When we run the built-in web server, when you go to http://localhost:8000/genus/octopus/notes, it automatically knows to use app_dev.php. So, that's why it works in the tutorial :).
But, the *real* solution is to basically do what you did - send the request to /app_dev.php/genus/octopus/notes. Of course, you don't want to hardcode this (then it would break on production!). What I would actually do is either:
A) Use FOSJsRoutingBundle - which allows you to generate URLs from routes right inside your JS code. It's awesome!
B) Generate the URL in Twig, set it on some global JS variable - e.g. window.notesUrl = '{{ path('...') }}'; - and access that inside of the React.js app.
I didn't want to dive into these details this early in the Symfony series - but it's a really valid question.
Cheers!
Hi, Ryan,
Well done with your colleagues, as a foreigner I am quite impressed to these tutorials, so I am planning to have the subscription for further courses, that must be great fun!
In the end of this episode, I run the course code perfectly ok, but when I downloaded the *react.js*, *react-dom.js* and *browser.min.js* to local machine, the dynamic effect and content doesn't exist anymore. I mean, the <div id="js-notes-wrapper"></div> part.
After my testing, I noticed that, if I change:
<script src="https://cdnjs.cloudflare.co..."></script>
into
<script src="{{ asset('js/browser.min.js') }}"></script>
then it stops just working, but *react.js* and *react-dom.js* can be used locally.
I am a newbie to this, so any advice or possible solutions will be appreciated!
Best regards,
Sheeran
Hey Sheeran!
Ah, awesome! I'm *thrilled* that these are useful (and fun!) for you - that's exactly what we're hoping for :).
So hmm, let's figure out your issue :). First, when it doesn't work, do you see any JavaScript errors in your browser's console? And also, when you switch from the cdnjs URL to the js/browser.min.js version, you said that "it stops working ,but react... can be used locally". What do you mean by "can be used locally"?
Let me know! And we'll find the problem.
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0" // v3.1.3
}
}
TypeError: this.props.notes is undefined