Passing data to JavaScript within a Server-Side Rendered template
Web developers often need to add JavaScript for interactivity to their pages, and some times that interaction needs to be kicked off by some (initial) data from the backend. In this article I will show you an approach for passing data between a server-side template and JavaScript which is cleaner that what I've seen many people do in their projects.
MVC is dead, long live MVC!
In projects where the frontend is completely handled by a Single Page Application initial data may be fetched using an API call on the components "onMount" event. However, there are still a lot of (MVC) projects which just sprinkle in JavaScript in a Server Side Rendered page for extra functionality like client-side validation.
Please note by server-side rendered here I mean that the page is being rendered by say, PHP (regular PHP, Twig, Blade template etc..), Rails templates, Java with JSP, Thymeleaf, Velocity etc.. , .NET with ASP.NET pages, Razor templates, Django templates or whatever language/framework you fancy that allows you to render to HTML via some template engine.
In these kinds of projects it is not uncommon to find code that mixes in the template rendering logic with the JavaScript code on the page in a way that results in hard to read or maintain code.
NOTE: I am using PHP in these examples but I have seen this pattern in Spring Boot projects, and the solution can be applied to any framework that offers server side rendering of templates
Below is an example of code you may find in some projects when passing data from server-side template to JavaScript. (Raise your hand if you have done something like this before, I see you).
<script type="text/javascript">
const individuals = [
<?php foreach($people as $p): ?>
{ "firstName": <?php echo '"' . $p->firstName . '"' ?>, "lastName": <?= '"' . $p->lastName . '"' ?> },
<?php endforeach ?>
];
const selectOptions = {
<?php echo '"' . ORGANIZER_CONSTANT . '"'?> : "Organizer",
<?php echo '"' . SPEAKER_CONSTANT . '"'?> : "Speaker",
<?php echo '"' . ATTENDEE_CONSTANT . '"'?> : "Attendee",
<?php echo '"' . SPONSOR_CONSTANT . '"'?> : "Sponsor"
}
</script>
Which would render to something like:
<script>
const individuals = [
{ "firstName": "John", "lastName": "Banda" },
{ "firstName": "Mary", "lastName": "Banda" },
{ "firstName": "Joseph", "lastName": "Gondwe" },
{ "firstName": "Jane", "lastName": "Phiri" },
];
const selectOptions = {
"organizer": "Organizer",
"speaker": "Speaker",
"attendee": "Attendee",
"sponsor": "Sponsor"
}
</script>
This example is trivial, but I have seen situations where lots of data was serialized this way. As you can see this approach of serializing data is messy and can indeed make code hard to follow and also lead to subtle bugs:
A better way to pass data to JavaScript
We can do better by using an approach that I think is better, cleaner, separates the concerns between server-rendered template and JavaScript as well as opening up possibilities for improving testing the JavaScript code.
The approach basically works as follows:
- Put all the data you need on the JavaScript side into a "root" object(s) within the server-side template (note: you can use an
array
in PHP, aMap
in Java/C# or similar languages, hashmap/dictionary in Python etc..) - Encode the data as JSON using the server-side template capabilities (using
json_encode
in PHP,objectMapper#writeAsString
orgson#toJson
in a Java / Spring Boot codebase,json.dumps
orto_json
in Python etc..) - Use
JSON.parse
to decode the JSON into a global object under the toplevelwindow
, typically named the same as the root object from step 1
See the example below...
<!-- Step 1-->
<?php
$pageData = [
"individuals" => $people,
"selectOptions" => [
ORGANIZER_CONSTANT => "Organizer",
SPEAKER_CONSTANT => "Speaker",
ATTENDEE_CONSTANT => "Attendee",
SPONSOR_CONSTANT => "Sponsor"
],
];
?>
<!-- Step 2 and Step 3 -->
<script type="text/javascript" id="pageDataScript">
window.__pageData = JSON.parse(<?php echo json_encode($pageData) ?>);
const individuals = window.__pageData.individuals;
const selectOptions = window.__pageData.selectOptions;
</script>
It seems like an obvious approach in hindsight.
If you are using a template engine like Twig you can use the raw
function to avoid the JSON data from being rendered using HTML escapes.
<script type="text/javascript" id="pageDataScript">
window.__pageData = JSON.parse( {{ json_encode(pageData) | raw }} );
</script>
Benefits of this approach.
- Cleaner - better separation of concerns as the server side code doesn't 'bleed' into the JavaScript code too much.
- Easier to reason about the structure of the data passed to the JavaScript handling code
- Makes it easy to introduce a library like VueJS or React as you could just fetch the data from the __pageData object "onMount"
Extension of the approach
An extension to the above approach enables you to render/write the data separately in a script element and then use another script element to read the data. You will note in the example below that the first <script>
element has type application/json
- this allows us to place data in there that the Browser safely ignores, and will not execute as JavaScript.
This may seem like overkill but could provide you with flexibility for testing if you have issues with getting the data from the backend since you can easily place random JSON that follows the proper structure in the initialPageData
element.
<script type="application/json" id="initialPageData"><?php echo json_encode($pageData) </script>
<script type="text/javascript" id="pageDataScript">
window.__pageData = JSON.parse(document.getElementById("initialPageData").innerHTML);
</script>
Conclusion
In this article we have seen that with a few changes it's possible for us to make it cleaner to pass data from server-side rendered template to JavaScript to enhance the experience on pages of your web applications.
Thanks for reading. If you have questions, comments or suggestions, reach out on Twitter @zikani03.