Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Passing Server Data to JS

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Watch... 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.

Refactoring initialRepLogs to an Argument

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!

Rendering initialLogs as a data- Attribute

Open 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.

Leave a comment!

3
Login or Register to join the conversation
Gary P. Avatar
Gary P. Avatar Gary P. | posted 5 years ago

Is there a reason to get a symbol .iterator error of undefined at RepLogApp.js when updating the importing of json file ?

Reply
Gary P. Avatar

Apologies my mistake editing the template file "index.html.twig"

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial explains the concepts of an old version of Webpack using an old version of Symfony. The most important concepts are still the same, but you should expect significant differences in new versions.

What PHP libraries does this tutorial use?

// 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
    }
}

What JavaScript libraries does this tutorial use?

// 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
    }
}
userVoice