Saturday, June 7, 2014

Snap.svg and Polymer


Ew. SVG coding is awful in JavaScript. The videos that I am preparing for “Extras” readers of Patterns in Polymer will include some SVG coding. I will focus on the Polymer coding, of course, but SVG coding will be there and I would like to make it as palatable as possible.

Not that it's great in Dart, but built-in Dart language features like method cascades plus actual constructors make it decent:
  _svgImage(basename) {
    var svg = new GElement()
      ..append(
          new CircleElement()
            ..setAttribute('r', '15')
            ..setAttribute('cx', '15')
            ..setAttribute('cy', '15')
            ..setAttribute('style', 'opacity:0')
        )
      ..append(new SvgElement.svg(svgContent['$basename.svg']));
    // ...
  }
That adds a circle element (for mouse events) and an SVG asset to an SVG <g> tag to be added to the pizza building <x-pizza> custom element:



JavaScript lacks constructors (as far as I can tell) and certainly doesn't have method cascades. So the equivalent JavaScript code wound up looking like:
  _svgSausage: function() {
    var ns = "http://www.w3.org/2000/svg";
    var group = document.createElementNS(ns, "g");

    group.innerHTML = this.svgContent['sausage.svg'];
    var svg = group.querySelector('svg');

    var mouseCircle = document.createElementNS(ns, "circle");
    mouseCircle.setAttribute('r', '15');
    mouseCircle.setAttribute('cx', '15');
    mouseCircle.setAttribute('cy', '15');
    mouseCircle.setAttribute('style', 'opacity:0');
    group.appendChild(mouseCircle);
    // ...
  }
Instead of constructing objects, I create elements. Instead of method cascades, I repeat the same object / method on line after line. This is not pleasing.

So tonight, I do what all JavaScript programmers do when faced with ugliness—try out a library that someone has created to eliminate some of the sting. In this case, I try Snap. I start with a Bower install:
➜  js git:(master) bower install -S snap.svg
...
bower snap.svg#*                                 download https://github.com/adobe-webplatform/Snap.svg/archive/v0.3.0.tar.gz
bower snap.svg#*                                 resolved git://github.com/adobe-webplatform/Snap.svg.git#0.3.0
...
bower snap.svg#~0.3.0                             install snap.svg#0.3.0
...
snap.svg#0.3.0 bower_components/snap.svg
The chapter on external libraries in Patterns in Polymer still applies so I add this to the HTML definition of <x-pizza>:
<link rel="import"
      href="../bower_components/polymer/polymer.html">
<link rel="import"
      href="../bower_components/core-ajax/core-ajax.html">
<script src="../bower_components/snap.svg/dist/snap.svg.js"></script>
<polymer-element name="x-pizza">
  <template>
    <core-ajax
       auto
       id="pizza.svg"
       url="../../assets/images/pizza.svg"
       on-core-response="{{responseReceived}}"></core-ajax>
    <!-- ... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
I am already loading SVG via <core-ajax> tags, so I probably will not make use of Snap's SVG loading ability (at least for now). Instead, I want to take an already loaded SVG string and have Snap generate the SVG DOM, which is what Snap.fragement() does. I replace some innerHTML ugliness above:
    // ...
    group.innerHTML = this.svgContent['sausage.svg'];
    var svg = group.querySelector('svg');
    // ...
The equivalent in Snap is:
    // ...
    var f = Snap.fragment(this.svgContent['sausage.svg']);
    var svg = f.node.firstElementChild;
    group.appendChild(svg);
    // ...
I have exchange 2 lines of code for 3, which is going in the wrong direction, but I am also working against the Snap.svg rails here. I should not be reaching down for the node property. Hopefully I can switch off of that in a bit, but this lets me verify that things are still working—and they are:



In the end, I am able to work with Snap to reduce the earlier mess to:
  _svgSausage: function() {
    var s = Snap(0,0);
    var group = s.group();

    var f = Snap.fragment(this.svgContent['sausage.svg']);
    var svg = f.select('svg');
    group.add(f);

    var mouseCircle = s.circle(15, 15, 15);
    mouseCircle.attr({style: 'opacity:0'});
    group.add(mouseCircle);
    // ...
  }
That is a big improvement. The attr() setter / getter method is almost worth the trouble alone. And really, this wasn't too much trouble considering that I am still a bit off the rails with the whole Snap.fragment() thing.

I think that I might be able to get this in even better shape if I switch the entire library over to Snap. So far, I am only messing around with the pizza toppings in <x-pizza>. If I move everything—crust, sauce, cheese, etc—into Snap, I think I might realize a big improvement. Worth a follow-up tomorrow.


Day #87

No comments:

Post a Comment