Polymer: Dynamically loading Polymer Elements “on the fly”

With Polymer, it’s very easy to build a SPA (Single Page-App), and they are nice.

However, depending on how you code it, you can end up loading all your components into memory at the same time. It hurts performance (due to excessive amount of in-memory information) and can create other issues (suppose your “hidden page” has a timer doing some job, for example).

The solution is easy: you can load and create your elements on the fly. This post brings you a sample that can load elements on the fly and alternate between them.

How does it work

To create an element on the fly, you can use DOM Manipulation methods like:

var myElement = document.createElement("my-element");
this.$.container.appendChild(myElement);
// The line above supposes you have a <div id="container>
// which will hold your new my-element instance

To remove it, you can just find it in DOM and remove:

var element = this.$.container.querySelector("my-element");
element.parentNode.removeChild(element);

And if you need dynamic load a HTML Import, you must use Polymer’s utility function importHref (docs here) :

this.importHref('path/to/myElement.html', function(e) {
    // Create your element here
});

IMPORTANT NOTE:

You need to be aware that importHref sees current path as being the same of your HTML address bar (*). This can be confusing when you are trying to use it inside a custom Polymer element whose HTML definition resides in a different folder than your HTML.

This means that if you have loaded a page from http://localhost/test/main.html which imports a custom element my-custom-element from http://localhost/test/elements/myCustomElement.html, and you try to call importHref from a method defined in this my-custom-element, you must understand that importHref will see paths relative to http://localhost/test (and not to http://localhost/test/elements as you could expect).

(*) In fact, there’s a way to make it relative to your element’s path using resolveUrl(url) utility function. See my note at the end of this post.

Putting things together

Note 1: You must change methods _loadElement1 and _loadElement2 to load your own custom elements.

<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/paper-button/paper-button.html">

<dom-module id="app-test">
    <style>
        #controls {
            height: 50px;
            width: 100%;
            display: block;
        }

        /* Lets occupy all screen */
        #container {
            width: 100%;
            top: 50px; bottom: 0px;
            position: absolute;
            background-color: #e0f7fa;
        }

        /* This selector is applied to direct children of our container */
        #container > * {
            width: 100%;
            height: 100%;
            display: block;
        }
    </style>
    <template>
        <div id="controls">
            <paper-button on-click="_loadElement1">Load Element 1</paper-button>
            <paper-button on-click="_loadElement2">Load Element 2</paper-button>
            <paper-button on-click="_removeElement">Remove Element</paper-button>
        </div>
        <div id="container"></div>
    </template>
</dom-module>

<script language="javascript">
    Polymer({
        is: 'app-test',
        _myLoadElement: function(elementName, htmlImport) {
            // Let's remove last loaded element if exists
            this._removeElement();
            // Now we load it on-the-fly
            this.importHref(htmlImport, function (e) {
                // Create a new instance
                var myElement = document.createElement(elementName);
                // And add it to the container
                this.$.container.appendChild(myElement);
                // Lets set a dummy property just to show how to do it
                myElement.myProperty = "anything";
            });
        },
        _loadElement1: function () {
            // Pay attention to this path.
            // This IS NOT relative to this component.
            // Its relative to current loaded HTML page which contains app-test element
            // (read the "IMPORTANT NOTE:" section of this post before proceeding)
            // Load your element in the line below
            this._myLoadElement("my-element", "elements/myElement.html");
        },
        _loadElement2: function () {
            // Load your element in the next line. Pay attention to the path.
            this._myLoadElement("my-other-element", "elements/myOtherElement.html");
        },
        _removeElement: function () {
            var element = this.$.container.querySelector("*");
            if (element) {
                element.parentNode.removeChild(element);
            }
        }
    });
</script>

PS: You can use resolveUrl(url) utility function to translate paths in order to make them relative to element’s path (and not to loaded page). I haven’t use it in my example trying to keep things less confusing, but if you want, just change line 44 to:

    this.importHref(this.resolveUrl(htmlImport), function (e) {

Polymer Elements: Disable “bold” effect on focus of paper-button elements.

It took me some time to figure out how to remove the bold text style when a paper-button gains focus.

This bold effect comes specially annoying if you are trying to style buttons to use in mobile, because the last button clicked stay bold, sometimes messing your interface layout.

The solution is simple, and can be made for every button on an element or for specific buttons. Basically, you need to override two custom CSS mixins:

--paper-button-raised-keyboard-focus: {
    font-weight: normal;
};
--paper-button-flat-keyboard-focus: {
    font-weight: normal;
};

You can apply it on the :host element if you want all buttons in your component to be disabled or you can apply this style to an specific set of paper-buttons:

<dom-module id="my-element">
    <template>

<style is="custom-style">
            /* This applies to every button in this component */
            :host {
                --paper-button-raised-keyboard-focus: {
                    font-weight: normal;
                };
                --paper-button-flat-keyboard-focus: {
                    font-weight: normal;
                };
            }

            /* You can optionally style just some buttons (this case they have a class="noBoldFocus")*/
            paper-button.noBoldFocus {
                --paper-button-raised-keyboard-focus: {
                    font-weight: normal;
                    background-color: #ccc; /* You can even change other styles */
                };
                --paper-button-flat-keyboard-focus: {
                    font-weight: normal;
                };
            }
            /* ... Everything else in your component... */

MongoDB: Using bluebird promises with mongo db native driver 2.0 (mongodb-node-driver)

By default, mongodb driver always return a promise if you don’t specify a callback. But you can instruct it to return promises using your preferred promises library.

There is a simple method to use bluebird promises when using node-mongodb-native 2.0:

var Promise = require("bluebird");
var MongoClient = require("mongodb").MongoClient;

var MONGO_CONNECTION_STRING = 'mongodb://localhost:27017/myDb';

/*...*/

function saveData(data) {
  MongoClient
    .connect(MONGO_CONNECTION_STRING, {
      promiseLibrary: Promise
    })
    .then(function(db) {
      return db
        .collection('myCollection')
        .insert(data)
        .finally(db.close.bind(db))
    })
    .catch(function(err) {
      console.error("ERROR", err);
    });
}

Polymer: How to make an iron-list display items like a grid with horizontal columns

I’ve seem some people in Polymer’s Slack channel (https://polymer.slack.com/messages/general/) asking for this behavior.

While iron-list doesn’t implement it natively (I believe Feature Backlog #10 will cover it), I needed to implement it by myself (in a quick and dirty way, I admit).

This is not the prettiest solution, but it is simple and it works:

Assumptions for this recipe:

  • Your element has a property called items which holds the items to be drawn.
  • You are using an iron-list inside a custom Polymer element
  • You want a tabular fixed column grid (an horizontal flex wrap grid would be much more hard to implement)
  • You don’t need to use iron-list selection feature, you just want to display things

Caveats:

  • iron-list renders at least 20 rows at once. So if you have 10 columns, you will render at least 200 items at once.
  • You can’t remove / insert items without re-rendering the whole grid.
  • If you replace itemsPerRow value on the fly, it will re-render the whole grid, pushing you to the first item of the list
  • You cannot use iron-list item selection feature, because it will be attached to the row, not to the item

Recipe:

Declare a computedSlicedItems method:

Polymer({
/*...*/
_computeSlicedItems: function (items, itemsPerRow) {
    if (itemsPerRow < 1) {
       // Forces at least one item per row. It makes no sense otherwise.
       itemsPerRow = 1;
    } 
    if (items && items.length > 0) {
       var result = [];
       for (var i = 0; i < items.length; i += itemsPerRow)       {
           result.push(items.slice(i, i + itemsPerRow));
       }
       return result;
    } else {
       return [];
    }
}

Declare a computed property like that:

_slicedItems: {
    type: Array,
    readOnly: true,
    notify: true,
    computed: '_computeSlicedItems(items, itemsPerRow)'
 }

a property to hold desired itemsPerRow

itemsPerRow: {
   type: number,
   value: 4
}

Use them in iron-list, this way:

<iron-list id="list" items="[[_slicedItems]]" as="itemRow">
 <template>
  <div class="rowContainer">
   <template is="dom-repeat" items="[[itemRow]]" as="itemCol">
    <app-piecebrowser-card item="[[itemCol]]"></app-piecebrowser-card>
   </template>
  </div>
 </template>
</iron-list>

and finally, style .rowContainer this way:

.rowContainer > paper-card {
    display: inline-block;
    width: 240px; // You need a fixed width for your columns
    margin: 8px;
}