Integrating Polymer/Dart and Symfony – Part 2

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

Integrating Polymer/Dart and Symfony

In the previous article, we implemented a Dart widget in our Symfony app. Let's handle the rest now. This might get advanced, so grab a cup of coffee and pay close attention – I recommend you follow along only after completing Part 1!

Get around JSONP and display an Object's member in the template

If the server (and thus the configuration, the programming) is managed by ourselves, the process to get data from a RESTful API from that same server will be simple. We can enable CORS in the returned response header. Done! But if the remote server's RESTful API does not set that header, we will face a CORS error when we try to invoke that API call from within the Dart app.

One commonly discussed technique is to use JSONP. Dart also has a few packages serving jsonp functionality. You can find these packages here by searching for "jsonp".

However, in our implementation, we will do it another way due to the "hacky" nature of JSONP.

It is a fair assumption to say that anyone who is doing web site development should have at least one site under his/her own jurisdiction.

In my 2nd Dart widget to be integrated into my homepage, I try to grab some weather information from two different remote servers (and none of them enabled CORS). Obviously I have no control over these two servers, so I do the following:

First, create a controller in Symfony to grab the data from these two remote servers:

    public function WeatherAction($type, $param)
    {
        if ($type == 1) // Local weather info
        {
            $weather = file_get_contents("http://m.weather.com.cn/data/$param.html"); //101190401 for Suzhou, my hometown
        }
        else
        {
            $weather = file_get_contents("http://api.openweathermap.org/data/2.5/weather?q=$param&units=metric"); //suzhou,china
        }
        $response = $this->render('trrsywxBundle:Default:json.html.twig', array('res' => $weather));
        $response = $this->setExtraHeader($response);

        return $response;
    }

These two sites all return JSON strings upon calling but neither has set the CORS header. In Dart, such an API call will fail but in Symfony's environment, it is successful – only JavaScript is restricted by CORS, server side languages can still consume resources on other domains. Also note that by wrapping the returns from the remote servers by providing extra headers, we can enable CORS.

Next, in Dart, we create another Polymer app to get the information grabbed by Symfony and do further processing:

  void getWeather()
  {
    var path1='http://rsywx/app_dev.php/json/weather/1/$code';
    var path2='http://rsywx/app_dev.php/json/weather/2/$city';


    HttpRequest.getString(path1).then((String res)=>processWeather1(res));
    HttpRequest.getString(path2).then((String res)=>processWeather2(res));

    if(one)
      one=false;
    else
      one=true;
  }

  void processWeather1(String res)
  {
    w1=new Weather1.created(res);    
  }

  void processWeather2(String res)
  {
    w2=new Weather2.created(res);
  }
}

After retrieving the data from our own Symfony site, the process is straightforward.

In this implementation, every "Refresh" will grab weather info from both servers. It can be fine tuned to eliminate redundant API calls by determining what we are to show for the "Refresh" click (variable one toggles its Boolean status every time we calls the getWeather function, which is invoked by every "Refresh" request).

one is also used in our Dart Polymer template to decide which weather information is to be displayed as these two pieces of information will share a same slot in our HTML flow. The HTML template to show weather information is something like this:

<polymer-element name="weather-tag" attributes="city code">
  <template>
    <meta charset="utf-8">
    <template if='{{!one}}'>
        <div class="ui message">
          //Display local weather information
        </div>
    </template>
    <template if='{{one}}'>
        <div class="ui message">
        //Display local weather information from another source
        </div>

    </template>

  </template>
  <script type="application/dart" src="weather.dart"></script>
</polymer-element>

We declared two classes to simplify the instantiation of our weather information and for easier access. The Weather2 class is shown below:

class Weather2
{
  @observable var desc, city, temp, humidity;
  Weather2.created(String s)
  {
    Map w=JSON.decode(s);
    this.temp=w['main']['temp'].toStringAsFixed(1);
    this.humidity=w['main']['humidity'];
    this.city=w['name'];
    this.desc=w['weather'][0]['description']; 
  }
}

Basically, we just extract certain data from the returned JSON string and put them into the members of the class.

Special attention should be paid to the declarations of the class members. We have decorated them with @observable. This is to prevent Dart compile-time tree shaking from dropping the reference of these class members as (and most likely the reason is) they are not explicitly referenced anywhere in a Polymer expression in our Dart program when it is compiled to JavaScript. Thanks for Günter Zöchbauer for the tip in my question raised in StackOverflow.

This is not required to run the app in Dartium. But it is a MUST if we compile it to JS.

With the help of the defined Weather class, we can display the object in Polymer template in a more OO way:

    <template if='{{one}}'>
        <div class="ui message">
        <p>Today in {{w2.city}}: {{w2.desc}}, temperature {{w2.temp}} celcius, humidity {{w2.humidity}}%
        <br><i title="Refresh" class="refresh icon small link" on-click="{{getWeather}}"></i></p>
        </div>

    </template>

Without using the class, we can name our variables as w2city, w2temp, etc. But that is neither very user-friendly, nor developer-friendly, right?

Two Dart, One HTML? Limitation of Dart

So far the process is smooth. Now I need to integrate the above two created Dart widget into the same homepage. Without hesitation, I modified my HTML template to something like this:

<!DOCTYPE html>
<html lang="zh-CN">
    <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">

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

        <script src="/getqotd.html_bootstrap.dart.js"></script>
        <script src="/getweather.html_bootstrap.dart.js"></script>

        <title>...</title>
    </head>

    <body id="home">
        <polymer-element name="qotd-tag">
            <template>
                ...
            </template>
        </polymer-element>
        <polymer-element name="weather-tag" attributes="city code">
            <template>
                ...
            </template>
        </polymer-element>
        {% block content %}
        <div class="ui inverted page grid masthead segment">
            <div class="ui grid">
                <div class="row">
                    <div class="ten wide column">
                        <qotd-tag></qotd-tag>
                        <weather-tag code="101190401" city="Suzhou,china"></weather-tag>
                    </div>
                </div>
            </div>
        </div>

We inserted the necessary files, in particular, the two compiled JS files and declared the tags, then insert the tags where they are needed, following exactly the steps described earlier.

Looks promising, right?

Unfortunately, NO.

In the above implementation, only the widget whose JS is imported first (qotd) can be displayed properly. If we switch the sequence of the two JS import to make weather JS gets imported first, only the weather widget can be displayed.

I believe the issue is due to the compiled two JS files having many conflicts in function names, prototype declarations etc. For example, we can find the below declarations in both files:

ght:function(a){return new P.C7(this,P.WV.prototype.h,a,"h")},
zw:function(a,b){if(this.Gv>=4)throw H.b(this.q7())
this.pb(a,b)},

Thus, only one JS can survive and function. Only, it's strange to me why the 2nd JS fails (instead of overriding everything in the 1st JS).

To get around this, we have to create one more Dart/Polymer app, importing both Dart files created in previous steps for QOTD and weather. Then we need to wrap these two Dart widgets into a new widget.

The Github repository contains the final Dart application which is working fine. I have put a Youtube video to demonstrate the result (sorry to say that the homepage is mostly in Chinese but you will see how Dart goes into a Symfony page and responds to user interaction. You may need to switch to 720p to get a clearer view.)

This is obviously a big drawback in Dart-compiled JS.

This is not flexible and makes expanding difficult. We are now integrating two widgets. What if we want to see some stock quotes later on? My favorite team's score? A news feed? Is the only option developing a new or modifying an existing Dart widget every time we need to add or delete a widget component?

This approach will work, of course, but with very low efficiency. Besides, not all web developers are Dart developers at the same time. To ask a web developer to open up a Dart IDE and start Dart coding to insert a simple new component is not practical.

Why can't we have a Dart development such that Dart (and Dart programmers) can build various self-contained widgets (components) and the web developer can just grab the compiled JS/html/CSS/image files and just modify the web pages and start formulating a "new" widget? If this could be done, Dart would reach widespread acceptance much faster.

This is a confirmed issue in Dart. A feature request has been posted to the Dart Google Group to seek their attention and further improvement. Help out by voting on it.

Conclusion

In this series, we demonstrated how to integrate Dart/Polymer into Symfony to bring our page dynamic contents and user interaction. A detailed step-by-step instruction was given to help users to manage the integration, in particular, to insert the Polymer template into the Twig engine.

Also, we covered several topics in Dart programming: get around the JSONP and display objects in the Polymer template.

Finally, we highlighted the limitation in current Dart JS compilation: two or more separate Dart apps can't live in one HTML unless a new wrapping Dart app is created. I do hope this issue is resolved as soon as possible.

This brings us the end of this series. Feel free to comment and raise questions!

Integrating Polymer/Dart and Symfony

<< Integrating Polymer/Dart and Symfony – Part 1

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

    @mikemitterer:disqus I have cloned your repo in Github to my local repo. Opened it in Dart IDE (1.1.1). It has some errors and not compilable.

    Meanwhile, can you advise if your program still runs well if it is compiled to Javascript? Thanks.

  • Taylor Ren

    @mikemitterer:disqus

    Sorry, my network issue. Brought up my VPN and managed to update the dependencies/packages.

    The sampel you provided is running well.

    However, your approach is different from what I have described. If I read your codes correctly, you create a Dart app, then 2nd Dart, then 3rd Dart…

    You put them under one bigger Dart wrapper, run in Dartium, run the compiled JS in a modern browser… That is exactly what I have achieved in my final version.

    What I tried to do is this:

    1. Build 1st Dart, 2nd Dart
    2. Compile each of them into JS
    3. Without going into Dart IDE again, directly embed the JS into HTML (in my case, a Symfony/Twig template).

    That is not working.

  • Taylor Ren

    Good. Now we are synchronized.