Recursive Components with Meteor and Polymer

Written by Pete Corey on Mar 30, 2015.

I spent some time last week playing with Meteor’s Blaze templates and Polymer elements to build recursive components. The final result is a neat DOM based Cantor set. Before we dig into the source, take a look at a polished version of the Polymer implementation on Codepen:

See the Pen Polymer Cantor Set by Pete Corey (@pcorey) on CodePen.

Building Our Cantor Set

The general idea is to build a component that renders one iteration of the Cantor set construction. By one iteration, I mean a single line (or block), and below it another line divided equally with another instance of the component, and blank space and another instance of the component.

I’m using flexbox to simplify the spacing between my components.

To get a better idea of the construction I’m describing, try inspecting the DOM generated by either, or both, of the implementations.

Meteor Implementation

I started by experimenting with recursive custom block helpers in Meteor. After some trial and error, I came up with a cantor template that renders the Template.contentBlock for the set pieces, and Template.elseBlock for the empty pieces. Take a look at the template and the template’s helpers:

<template name="cantor">
    <div class="row">
        {{> Template.contentBlock}}
        {{#if more}}
            <div class="flex">
                {{#cantor max=max i=next}}
                    {{> Template.contentBlock}}
                {{else}}
                    {{> Template.elseBlock}}
                {{/cantor}}

                {{> Template.elseBlock}}

                {{#cantor max=max i=next}}
                    {{> Template.contentBlock}}
                {{else}}
                    {{> Template.elseBlock}}
                {{/cantor}}
            </div>
        {{/if}}
    </div>
</template>
Template.cantor.helpers({
    more: function () {
        return (this.i || 0) < this.max-1;
    },
    next: function() {
        return (this.i || 0) + 1;
    }
});

There are a few key take-aways from this layout. The row element defaults to display:block, which forces its content onto a new line. The flex element is set to display:flex. All of its children have their widths set to 100%, which effectively evenly distributes them within the flex element.

The recursion is where the magic happens. You can see what we’re including the cantor custom block helper inside of itself! We’re passing in an incremented value of i to the next iteration of the component. The component guards itself against infinite repetition with the check before recursing.

The Meteor implementation can be used like this:

{{#cantor max=6}}
    <section> </section>
{{else}}
    <div class="E"> </div>
{{/cantor}}

Polymer Implementation

The Polymer implementation is very similar, if a little cleaner. Check out the source below:

<polymer-element name="flex-cantor" attributes="max i">
    <template>
        <style>
            .e { border: 1px solid rgba(0,0,0,1); border-bottom: none; }
            .c { height: {{100/max}}vh; background-color: tomato; }
            .f { display: flex; }
            *  { width: 100%; }
        </style>
        <div class="row">
            <section class="c"> </section>
            <template if="{{i < max - 1}}">
                <div class="f">
                    <flex-cantor max="{{max}}" i="{{i + 1}}"></flex-cantor>
                    <section class="e"> </section>
                    <flex-cantor max="{{max}}" i="{{i + 1}}"></flex-cantor>
                </div>
            </template>
        </div>
    </template>
    <script>
        Polymer({
            i: 0,
            max: 0
        });
    </script>
</polymer-element>

Polymer allows us to declare the style for the component inline with the component declaration. Additionally, due to the power of Polymer interpolation, we can include all of our increment and guard logic inline in the markup, rather than offloading these to helper methods like we were forced to do in the Meteor implementation.

The Polymer implementation can be used like this:

<flex-cantor max="6"></flex-cantor>
 

Final Thoughts

I set up a Github repository with branches for both the Meteor implementation and the Polymer implementation. Check it out!

This was very much an experiment, but I’m very interested in further developing this idea. I can already think of other fun implementations of this idea, like a recursive version of my golden spiral layout, but I’m more interested in practical applications for this type of component composition.

Let me know if you have any ideas for recursive components!

Materialize Highs and Lows

Written by Pete Corey on Mar 25, 2015.

After reading Nick Wientge’s great post on using the Materialize framework to add Material Design ideals to your Meteor project, I was eager to jump on board.

The CSS and static component side of the Materialize framework is fantastic! After a few hours, I had converted a Bootstrap project over to a more Material Design inspired aesthetic, complete with schnazzy animations and transitions.

Unfortunately, I began to hit a few roadblocks when I started combining Materialize’s javascript components with reactive data from Meteor.

Form Select

The application I was converting to Materialize made heavy use of reactively populated select elements. I figured the transition to Materialize would be as simple as calling material_select when the select was rendered:

Template.select.rendered = function() {
    this.$('select').material_select();
};

But, since Materialize mirrors the options data in a hidden list in the DOM, we’ll need to re-initialize the select plugin every time the data changes. No problem:

Template.select.rendered = function() {
    this.autorun(function() {
        this.data.options; //autorun trigger goes here
        this.$('select').material_select();
    });
};

And, it doesn’t work! At least with the most current Materialize release at the time of this post (v0.95.3). In v0.95.3, re-running the material_select plugin will not re-generate the options list, even if new options have been added to the select. Thankfully, this has been reported as a bug and subsequently fixed, but you’ll need to grab to latest code for yourself to make use of the fix.

These issues can also be entirely avoided by adding the browser-default class to your select. This will cause Materialize to not mirror your select’s options in the DOM and use a styled version of the native select instead. Reactivity will work out of the box, as it would for any other select element.

Collapsible Elements

Collapsible elements also have issues with dynamic content. Collapsible elements are initialized by calling the collapsible plugin on the collapsible list. This will turn all of the child list items into collapsible containers:

<template name="collapsible">
    <ul class="collapsible">
        {{#each items}}
            <li>
                <div class="collapsible-header">{{header}}</div>
                <div class="collapsible-body">{{body}}</div>
            </li>
        {{/each}}
    </ul>
</template>
Template.collapsible.rendered = function() {
    this.$('.collapsible').collapsible();
};

But what happens when another item is added to items? We’ll need to re-initialize the collapsible plugin:

Template.select.rendered = function() {
    this.autorun(function() {
        this.data.items; //autorun trigger goes here
        this.$('.collapsible').collapsible();
    });
};

Unfortunately, this doesn’t work exactly as we expected. While the new item is collapsible, re-initializing the plugin also closes all currently open items. If we dig into the source, we can see why this happens.

Let’s take a look at the “expandable” data path. When the plugin is initialized, it loops over each collapsible-header and checks its active status. If it is active, it calls expandableOpen. Take a look.

expandableOpen toggles the active class on the collapsible-header’s parent (li), and then checks its value. If it is active, it expands the container, otherwise it collapses it. Check it out. The re-initialize issue happens because the parent li already has the active class for previously initialized items. When we toggle the class, we inadvertently close the container.

The accordion data path is a little more complicated, but the same general issue exists.

I’ve created an issue and a pull request to fix this issue with the collapsible plugin. Go open source!

Final Thoughts

Materialize is a great front-end framework. It allowed me to quickly and easily build a Material Design style application.

That being said, I don’t think it’s the best fit for a Meteor application. I’m not interested in dealing with the unnecessary complexity of managing the initialization and re-initialization of each of my components as they’re reactively added to the DOM.

At the end of the day, I believe I’m better off using Polymer as a static component library to build my Material Design applications.

User Fields and Universal Publications

Written by Pete Corey on Mar 16, 2015.

How do you specify which fields are published to the client’s Meteor.users collection? Using universal publications, obviously(1)! Let’s take a look at Meteor’s universal, or unnamed, publications and see how they’re used to accomplish this.

Put simply, a universal, or unnamed, publication is a publication without a name. That may not seem very special at first, but it raises some interesting questions in terms of functionality within the framework.

If a publication has no name, how do I subscribe to it?

You don’t! Universal publications are immediately and unconditionally started by the server and every connected client receives their data. Don’t take my word for it, it’s in the docs:

Name of the record set. If null, the set has no name, and the record set is automatically sent to all connected clients.

If I don’t subscribe to it, how do I know when the subscription is ready?

You don’t! The server never sends ready messages for universal publications. Universal publications just march to the beat of a different drummer.

What if I want to universally publish multiple collections?

Go for it! You can define as many universal publications as you’d like. Meteor does not check for duplicates like it does with named publications.

This does lead to an interesting point about multiple universal publications for a single collection. Imagine that a package, like accounts-base, sets up a universal publication for the Meteor.users collection. Let’s pretend that this publication only returns the profile, username and emails fields for each user.

What if we create another universal publication for the Meteor.users collection that returns some other set of user fields like username and roles?

Meteor.publish(null, function() {
    if (this.userId) {
        return Meteor.users.find(
            {_id: this.userId},
            {fields: {username: 1, roles: 1}});
    } else {
        return null;
    }
});

Interestingly enough, Meteor will run both of these publish handlers and publish the union of the fields returned by each. In our case, profile, username, emails and roles would all be published to the client!

So there’s the answer to our question. How do we publish more fields to the Meteor.users collection? By creating a universal publication that publishes only the fields that we depend on.

Why don’t we just use named publications?

A named publication that publishes additional fields from the Meteor.users collection will work, but you run the risk of accidentally dropping your subscription to that publication. I recently spend some time tracking down a fairly complicated bug in the Orion framework that dealt with this exact issue.

The Orion admin panel defined an adminUsers publication that published additional fields on the Meteor.users collection. It used these additional fields to determine if the current user had permission to view or modify content. The subscription to adminUsers was maintained by SubsManager.

SubsManager only keeps around a certain number of subscriptions before dropping older subscriptions to make room for new ones. After navigating through the Orion admin panel for a few minutes, the old adminUsers subscription was dropped to make room for a new subscription, which caused the Meteor.users collection to fall back to universal publication defined by accounts-base.

This universal publication wasn’t publishing fields required by Orion (isAdmin, permission), so the Orion client was forced to assume that the client wasn’t authorized to view the current page.

The issue was fixed by creating a new universal publication in the Orion core package that returned the isAdmin and permission fields that the client depended on.

(1) This is not an obvious answer.