Integrating Polymer/Dart and Symfony – Part 1

Taylor Ren
Tweet
This entry is part 1 of 2 in the series Integrating Polymer/Dart and Symfony

Integrating Polymer/Dart and Symfony

My first four articles for SitePoint are devoted to Symfony (my favorite PHP framework) and Dart (my favorite HTML 5 development tool).

In this 2-part series, we are going to look at how to integrate these two powerful tools together, i.e. to run Dart (after compiling to JavaScript) in a Symfony website to add some dynamics. We will also discuss the work-around to avoid JSONP to access data from a remote server where the user has no direct control and the RESTful API called has no CORS enabled. Finally, we will highlight the limitation of the integration and seek the attention of the Dart team to solve the issue and make Dart a better platform.

The topics discussed in this article are quite advanced. Readers are required to have a basic understanding of both Symfony and Dart/Polymer. By basic understanding, I mean the user can develop a Symfony site and a Dart app on their own and thus is familiar with the terms we are going to use in the rest of this article. The reader should also understand the structures of both Symfony web applications and Dart/Polymer applications. To learn about Dart/Polymer, see here, and to get familiar with Symfony, see my previous articles.

The Dart/Polymer codes used in this program have been uploaded to Github. Feel free to clone a copy and play around. However, the Symfony code is not included as it is too big and involves a lot of files that are not relevant to the topic we are covering in this article.

Let's get started with part 1.

A typical Symfony Twig template

Symfony by default uses Twig (another product from the same author) template engine. Let's take a look at a typical Twig template:

File: Resources/views/Default/index.html.twig
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="description" content="">
        <meta name="author" content="TR@SOE">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

        <link rel="shortcut icon" href="/favicon.ico">

        <link rel="stylesheet" type="text/css" href="/css/semantic.css">
        <link rel="stylesheet" type="text/css" href="/css/rsywx.css">

        <title>{%block title%}Welcome to my website!{%endblock%}</title>
    </head>

    <body id="home">
        {% block content %}
        <div class="ui page grid overview segment">
            <div class="sixteen wide column">
                <div class="ui four column center aligned stackable divided grid">
                    <div class="equal height row">
                        <div class="column">
                            <div class="ui icon header">
                                <a href="{{path('book_list')}}"><i class="circular inverted book link icon"></i></a>
                                Books
                            </div>
                            <p>Up to {{"now"|date('Y-m-d')}}, I have {{bs.summary.bc|number_format(0,'.',',')}}books. <br><br>The newest book collected (on ({{bs.last.purchdate|date('Y-m-d')}}) is by {{bs.last.author}}, title: <a href="{{path('book_detail', {'id':bs.last.id})}}">{{bs.last.title}}</a></p>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        {%endblock%}
    {{ include ('trrsywxBundle:Default:footer.html.twig') }}
    </body>
</html>

The above is the concise version of my revised site's index page. It is fully HTML5 compliant, with all the necessary components: metas, CSS links, Semantic-UI (Yes, I changed my preference from Bootstrap to Semantic-UI) and the contents.

The Twig engine of the HTML is the {%...%} and {{...}} part. Please pay special attention to the {{...}} notation. We will see how it will interfere with our Dart/Polymer HTML template later.

A typical Dart/Polymer HTML template (after pub build)

In my new website's homepage, I want to integrate two dynamic elements: one is to get a random quote from my database (hosted in the same server), and the other to get weather information of my residence from two sources (one in Chinese and another in English). Each "widget" will have a "Refresh" button. For the quote widget, it will fetch a new random quote and for the weather widget, it will switch between displaying Chinese information and English information.

The first Dart app we look at is the QOTD (Quote Of The Day). The code is located here. It is a very simple Dart app so we will skip the explanation of the Dart portion and focus on the compiled version of this app only.

In your Dart IDE (I am using the Eclipse-based Dart Editor, but PHPStorm and WebStorm also have Dart support), select the build.dart file and from menu choose "Tools | Pub Build". The generated files will be placed inside the build directory. In that directory, alongside other supporting files (packages, CSS, fonts, images, etc) used for the Dart app, there will be two files. One is the compiled JavaScript version of our Dart files (in my example it is getqotd.html_bootstrap.dart.js) and the other is the HTML file (getqotd.html).

What we are going to do here is to integrate the contents of this build directory into my Symfony setup. To do so, a few steps will be necessary:

First of all, copy all the supporting files/directories (except the getqotd.html file) into Symfony project's web directory. Please make sure to preserve the directory structure. After all, the directory structure shown in the above screenshot is quite a traditional setup in any web site. Advanced users can choose to copy the files and directories into a different location. It is doable but may require further tuning in later steps.

Now in Symfony project's web directory, besides those Symfony generated files/directories, we have a few more files that establishes the foundation for a Dart app (or, in my context, I'd rather call it a Dart Widget).

Next, go back to the index.html.twig file, make some changes to include necessary Dart-related files. The changes to be made can actually be grabbed from the getqotd.html file and I summarize below:

Include 3 JS files from Dart in the HTML <head>...</head> section. There 3 files are: shadow_dom.debug.js, custom-elements.debug.js, and interop.js. NOTE: In production environment, please don't use the debug version of the first two JS files and replace them with shadow_dom.min.js and custom-elements.min.js, respectively.

Now the <head>...</head> section looks like this:

<head>
        <script src="/packages/shadow_dom/shadow_dom.debug.js"></script>
        <script src="/packages/custom_element/custom-elements.debug.js"></script>
        <script src="/packages/browser/interop.js"></script>
        <meta charset="utf-8">
...
</head>

Make sure CSS links are correct. Usually this is redundant but no harm in double checking.

Bring in the Bootstrap JS file: getgetqotd.html_bootstrap.dart.js.

And the <head>...</head> section looks like this:

<head>
        <script src="/packages/shadow_dom/shadow_dom.debug.js"></script>
        <script src="/packages/custom_element/custom-elements.debug.js"></script>
        <script src="/packages/browser/interop.js"></script>
        <meta charset="utf-8">
...
        <script src="/getwidget.html_bootstrap.dart.js"></script>
</head>
  1. Include the <polymer-element>...</polymer-element> declaration in the <body>...</body> segment at the very beginning:
    <body id="home">
    <polymer-element>
        <polymer-element name="qotd-tag">
            <template>
                <div class="ui message">
                    <p>{%verbatim%}{{quote}}{%endverbatim%}<br>
                        <small><em>{%verbatim%}{{source}}{%endverbatim%}</em></small>&nbsp;&nbsp;<i title="Refresh" class="refresh teal icon small link" on-click="{%verbatim%}{{getQuote}}{%endverbatim%}"></i></p>
                </div>
            </template>
        </polymer-element>
...

The template is actually extracted from qotd.html file in my repository. During the publishing process, Dart simply copies the contents in qotd.html into the final output of getqotd.html.

Pay special attention to the {%verbatim%}...{%endverbatim%} tag pair. This is a Twig template syntax (not Dart) telling Twig engine that everything between these two tags should be outputted verbatim.

If we skip these tags, there will be a run-time error. In Twig, {{...}} is used to output a variable. So Twig will treat, say {{source}} as displaying the value of a variable called source. Obviously, source is a Dart variable used in Polymer template and we never would define it in our Symfony (or if we did, we need to change the variable name either in Symfony or in Dart to avoid unnecessary referencing the wrong variable). Thus, as Twig is very strict in using undeclared variables, the error pops saying "Variable 'source' does not exist".

We can get around of this limitation by configuring our Twig engine to use a different set of delimiters (like Smarty-styled {$ ... } pairs, or Ruby-styled <%= ... %> pairs). But that requires some advanced tweaking inside Twig. So with a more straightforward and simpler way on hand, we will skip this. The only drawback is we have to key in more letters. Interested parties can read how to define your own Twig delimiters for more details.

Finally, insert the custom-element tag (qotd-tag) pair wherever you want that information to be displayed:

    <div class="ten wide column">
        <qotd-tag></qotd-tag>
    </div>

Voila! Load that page in your web server and we can see that the QOTD widget is working properly. It displays a random quote from the database every time the page loads or the "Refresh" button is clicked.

This has marked a milestone in our Dart-Symfony integration.

Conclusion

In this part, we implemented a Dart Widget into our website and explained some possible interferences between the used technologies. In part 2, we'll do some more advanced implementations.

Have feedback? Leave it in the comments!

Integrating Polymer/Dart and Symfony

Integrating Polymer/Dart and Symfony – Part 2 >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Taylor Ren

    Forget to mention: the approach discussed here and in my next article should be able to be applied to other frameworks too.

  • Hendrik Jan van Meerveld

    You should probably state “By basic understanding, I mean the user can develop a Symfony site and a Dart/Polymer app on their own” (including Polymer). Other than that, I’m looking forward to reading this article once I’ve read something about Polymer.

    • Taylor Ren

      Thanks for this point. Yes, Polymer is used in my demo.