If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWatch... if I refresh... there's a slight delay before the table loads. That's because... we designed it that way! Open assets/js/Components/RepLogApp.js
. In the constructor, we call this.loadRepLogs()
, which... surprise! Loads the initial rep logs... by making an AJAX call:
... lines 1 - 9 | |
class RepLogApp { | |
constructor($wrapper) { | |
... lines 12 - 16 | |
this.loadRepLogs(); | |
... lines 18 - 33 | |
} | |
... lines 35 - 44 | |
loadRepLogs() { | |
$.ajax({ | |
url: Routing.generate('rep_log_list'), | |
}).then(data => { | |
for (let repLog of data.items) { | |
this._addRow(repLog); | |
} | |
}) | |
} | |
... lines 54 - 197 | |
} | |
... lines 199 - 217 |
This builds the table.
That's cool! But... I'm going to be unreasonable and make our life complicated. I demand that this table load instantly! To make crazy me happy, let's update our app so that when the page loads, our JavaScript already knows what the initial rep logs are... without needing that AJAX call.
Well... if we still wrote JavaScript in our template... this would be pretty easy. We could create a JavaScript variable and use Twig to print out all the rep logs into that variable. Yep, mixing Twig and JavaScript is kinda handy.
But obviously... we can't start writing Twig code right in the middle of RepLogApp.js
. Sigh, nope, we need a new way to communicate from our server to JavaScript. And there is a great... and simple solution.
First, remove loadRepLogs()
:
... lines 1 - 9 | |
class RepLogApp { | |
... lines 11 - 44 | |
loadRepLogs() { | |
$.ajax({ | |
url: Routing.generate('rep_log_list'), | |
}).then(data => { | |
for (let repLog of data.items) { | |
this._addRow(repLog); | |
} | |
}) | |
} | |
... lines 54 - 197 | |
} | |
... lines 199 - 217 |
Instead, add initialRepLogs
as a second argument to the constructor
:
... lines 1 - 9 | |
class RepLogApp { | |
constructor($wrapper, initialRepLogs) { | |
... lines 12 - 35 | |
} | |
... lines 37 - 189 | |
} | |
... lines 191 - 209 |
Whoever calls me will need to pass this in.
Down below, loop over these: for (let repLog of initialRepLogs)
, then, this._addRow(repLog)
:
... lines 1 - 9 | |
class RepLogApp { | |
constructor($wrapper, initialRepLogs) { | |
... lines 12 - 16 | |
for (let repLog of initialRepLogs) { | |
this._addRow(repLog); | |
} | |
... lines 20 - 35 | |
} | |
... lines 37 - 189 | |
} | |
... lines 191 - 209 |
Ok! Who creates this object? Ah yes, it's our entry file: assets/js/rep_log.js
:
... lines 1 - 8 | |
$(document).ready(function() { | |
... line 10 | |
var repLogApp = new RepLogApp($wrapper); | |
}); |
Dang it! We can't put Twig code here either. For now, just to see if this is working, I'll paste two hard-coded logs above this:
... lines 1 - 8 | |
const logs = [ | |
{ | |
"links": {"_self": "\/reps\/78"}, | |
"id": 78, | |
"reps": 1, | |
"itemLabel": "Big Fat Cat", | |
"totalWeightLifted": 18 | |
}, | |
{ | |
"links": {"_self": "\/reps\/79"}, | |
"id": 79, | |
"reps": 2, | |
"itemLabel": "Big Fat Cat", | |
"totalWeightLifted": 36 | |
} | |
]; | |
$(document).ready(function() { | |
... lines 27 - 28 | |
}); |
This is the exact format returned by the AJAX endpoint. Pass logs
as the second argument:
... lines 1 - 8 | |
const logs = [ | |
{ | |
"links": {"_self": "\/reps\/78"}, | |
"id": 78, | |
"reps": 1, | |
"itemLabel": "Big Fat Cat", | |
"totalWeightLifted": 18 | |
}, | |
{ | |
"links": {"_self": "\/reps\/79"}, | |
"id": 79, | |
"reps": 2, | |
"itemLabel": "Big Fat Cat", | |
"totalWeightLifted": 36 | |
} | |
]; | |
$(document).ready(function() { | |
... line 27 | |
var repLogApp = new RepLogApp($wrapper, logs); | |
}); |
In theory, this should work. Side note: you should always worry when a programmer says:
In theory, this should work!
But actually, it does this time! Sometimes we get lucky. The table starts with the two hard-coded logs. Ok, we are close!
data-
AttributeOpen up the server code: src/AppBundle/Controller/RepLogController.php
. This is the code for the AJAX endpoint that we were using before:
... lines 1 - 13 | |
class RepLogController extends BaseController | |
{ | |
/** | |
* @Route("/reps", name="rep_log_list", options={"expose" = true}) | |
* @Method("GET") | |
*/ | |
public function getRepLogsAction() | |
{ | |
$models = $this->findAllUsersRepLogModels(); | |
return $this->createApiResponse([ | |
'items' => $models | |
]); | |
} | |
... lines 28 - 96 | |
} |
Open LiftController
, indexAction()
: this is the method that renders the current page:
... lines 1 - 10 | |
class LiftController extends BaseController | |
{ | |
/** | |
* @Route("/lift", name="lift") | |
*/ | |
public function indexAction(Request $request) | |
{ | |
... lines 18 - 35 | |
return $this->render('lift/index.html.twig', array( | |
... lines 37 - 38 | |
)); | |
} | |
... lines 41 - 69 | |
} |
Here's the plan: I want to get all the of the initial rep logs as JSON, pass that into the template, then render it in a way that our JavaScript can read.
Let's steal the first line of code from the AJAX controller. Paste it in indexAction()
, but rename the variable to $repLogModels
:
... lines 1 - 10 | |
class LiftController extends BaseController | |
{ | |
... lines 13 - 15 | |
public function indexAction(Request $request) | |
{ | |
... lines 18 - 35 | |
$repLogModels = $this->findAllUsersRepLogModels(); | |
... lines 37 - 39 | |
return $this->render('lift/index.html.twig', array( | |
... lines 41 - 43 | |
)); | |
} | |
... lines 46 - 74 | |
} |
Then, create another variable called $repLogsJson
set to $this->get('serializer')->serialize($repLogModels, 'json')
:
... lines 1 - 10 | |
class LiftController extends BaseController | |
{ | |
... lines 13 - 15 | |
public function indexAction(Request $request) | |
{ | |
... lines 18 - 35 | |
$repLogModels = $this->findAllUsersRepLogModels(); | |
$repLogsJson = $this->get('serializer') | |
->serialize($repLogModels, 'json'); | |
return $this->render('lift/index.html.twig', array( | |
... lines 41 - 43 | |
)); | |
} | |
... lines 46 - 74 | |
} |
Cool! Pass that into the template: repLogsJson
set to $repLogsJson
:
... lines 1 - 10 | |
class LiftController extends BaseController | |
{ | |
... lines 13 - 15 | |
public function indexAction(Request $request) | |
{ | |
... lines 18 - 35 | |
$repLogModels = $this->findAllUsersRepLogModels(); | |
$repLogsJson = $this->get('serializer') | |
->serialize($repLogModels, 'json'); | |
return $this->render('lift/index.html.twig', array( | |
... lines 41 - 42 | |
'repLogsJson' => $repLogsJson, | |
)); | |
} | |
... lines 46 - 74 | |
} |
Get that template open: app/Resources/views/lift/index.html.twig
. Ok, so how can we pass the repLogsJson
variable to JavaScript?
My favorite way is by leveraging data attributes. If you look inside rep_log.js
, we look for a .js-rep-log-table
element:
... lines 1 - 25 | |
$(document).ready(function() { | |
var $wrapper = $('.js-rep-log-table'); | |
... line 28 | |
}); |
This lives near the top of the template. On it, add a new attribute: data-rep-logs="{{ repLogsJson|e('html_attr') }}"
:
... lines 1 - 2 | |
{% block body %} | |
<div class="row"> | |
<div class="col-md-7 js-rep-log-table" data-rep-logs="{{ repLogsJson|e('html_attr') }}"> | |
... lines 6 - 43 | |
</div> | |
</div> | |
{% endblock %} | |
... lines 47 - 62 |
Brilliant! That prints the rep logs and escapes them so that they can live safely on an attribute.
Dude! Now our job is easy. In rep_log.js
, delete the old logs
variable. And instead, say $wrapper.data('rep-logs')
:
... lines 1 - 8 | |
$(document).ready(function() { | |
... line 10 | |
var repLogApp = new RepLogApp($wrapper, $wrapper.data('rep-logs')); | |
}); |
That's it! Refresh and... yes! We instantly have a real table. My unreasonable demands have been met!
So, whenever you want to pass data from your server to JavaScript
, a great option is to leverage data attributes.
// composer.json
{
"require": {
"php": "^7.2.0",
"symfony/symfony": "3.3.*", // v3.3.16
"twig/twig": "2.10.*", // v2.10.0
"doctrine/orm": "^2.5", // v2.7.0
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
"symfony/swiftmailer-bundle": "^2.3", // v2.6.3
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.4.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "^2.0", // v2.1.2
"doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0" // v3.3.5
}
}
// package.json
{
"dependencies": [],
"devDependencies": {
"babel-core": "^6.25.0", // 6.25.0
"babel-loader": "^7.1.1", // 7.1.1
"babel-plugin-syntax-dynamic-import": "^6.18.0", // 6.18.0
"babel-preset-env": "^1.6.0", // 1.6.0
"bootstrap-sass": "^3.3.7", // 3.3.7
"clean-webpack-plugin": "^0.1.16", // 0.1.16
"copy-webpack-plugin": "^4.0.1", // 4.0.1
"core-js": "^2.4.1", // 2.4.1
"css-loader": "^0.28.4", // 0.28.4
"extract-text-webpack-plugin": "^3.0.0", // 3.0.0
"file-loader": "^0.11.2", // 0.11.2
"font-awesome": "^4.7.0", // 4.7.0
"jquery": "^3.2.1", // 3.2.1
"lodash": "^4.17.4", // 4.17.4
"node-sass": "^4.5.3", // 4.5.3
"resolve-url-loader": "^2.1.0", // 2.1.0
"sass-loader": "^6.0.6", // 6.0.6
"style-loader": "^0.18.2", // 0.18.2
"sweetalert2": "^6.6.6", // 6.6.6
"webpack": "^3.4.1", // 3.4.1
"webpack-chunk-hash": "^0.4.0", // 0.4.0
"webpack-dev-server": "^2.6.1", // 2.6.1
"webpack-manifest-plugin": "^1.2.1" // 1.2.1
}
}
Is there a reason to get a symbol .iterator error of undefined at RepLogApp.js when updating the importing of json file ?