Saturday, June 14, 2014

Applying All the Page Styles Inside Polymer Elements


Up tonight a follow-up to yesterday's dynamic, external stylesheet styling of Polymer elements. Instead of applying arbitrary styles from external stylesheets, I would like to apply all styles from the “owner document” — the page that contains the custom Polymer element.

I am styling the page containing the pizza building <x-pizza> Polymer element with Bootstrap:
<!DOCTYPE html>
<html lang="en">
  <head>
    <link type="text/css" rel="stylesheet" href="assets/bootstrap.min.css">
    <script src="bower_components/platform/platform.js"></script>
    <link rel="import" href="elements/x-pizza.html">
  </head>
  <body>
    <div class="container">
      <h1>JavaScript Pizza Builder</h1>
      <x-pizza></x-pizza>
    </div>
  </body>
</html>
The goal today will be to apply that style—along with any other in the <head> section of the owner document—in my custom element. Toward that end, I add styles before and after the Bootstrap <link> tag:
    <!-- ... -->
    <style>
      .btn-default { color: yellow; }
    </style>
    <link type="text/css" rel="stylesheet" href="assets/bootstrap.min.css">
    <style>
      .btn-default { border: 5px solid orange; }
    </style>
    <!-- ... -->
Bootstrap overrides the yellow in the first <style> while the orange border in the second overrides Boostrap resulting in:



I hard-coded Bootstrap into my custom element yesterday, but without the <style> tags, button elements lack the amazing orange border:



To pull in both the Bootstrap <link> and styles, I replace yesterday's hard-coded Boostrap:
Polymer('x-pizza', {
 // ...
  addExternalCss: function() {
    var link = document.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.href = '/assets/bootstrap.min.css';
    this.shadowRoot.appendChild(link);

    this.element.convertSheetsToStyles(this.shadowRoot);
  },
  // ...
});
Instead, I loop through the owner document's <head> in search of <link> and <style> tags to clone and add to my Polymer element's shadow root:
  addExternalCss: function() {
    for (var i=0; i<this.ownerDocument.head.children.length; i++) {
      var el = this.ownerDocument.head.children[i];
      if (el.tagName != 'LINK' && el.tagName != 'STYLE') continue;

      var clone = el.cloneNode(true);
      this.shadowRoot.appendChild(clone);
    }

    // TODO: is this really the best way to handle this?
    this.element.convertSheetsToStyles(this.shadowRoot);
  },
The convertSheetsToStyles() method call comes from yesterday's efforts. I defer the commented question above it for another day.

As for the rest of the code, it almost works. I specified the href in the stylesheet <link> as a relative URL. Inside the element, this URL no longer resolves. So I need an extra step to specify the absolute URL. The URL() constructor does that for me:
  addExternalCss: function() {
    for (var i=0; i<this.ownerDocument.head.children.length; i++) {
      var el = this.ownerDocument.head.children[i];
      if (el.tagName != 'LINK' && el.tagName != 'STYLE') continue;

      var clone = el.cloneNode(true);
      if (clone.href != 'undefined') {
        clone.href = new URL(clone.href, document.baseURI).href;
      }
      this.shadowRoot.appendChild(clone);
    }

    // TODO: is this really the best way to handle this?
    this.element.convertSheetsToStyles(this.shadowRoot);
  },
Which does the trick. Now the native elements in my Polymer look exactly as they do on the page:



In addition to the question about convertSheetsToStyles() being the best approach, I now have an additional problem: this approach pulls in styles that are dynamically added to the page—and there are several from Polymer alone. Although they do not have an effect, it would be nice to be able to apply only hard-coded styles. Still, this works and is relatively low on the amount of code. So I take my small victory and call it a day here.



Day #93

No comments:

Post a Comment