Meteor Composability

Written by Pete Corey on Mar 9, 2015.

Last week I had the chance to give a talk at the Meteor San Diego Meetup. My goal was to give some techniques and ideas for building component based Meteor applications. You can check out the slides here and read my write-up below!

What do AngularJS, Polymer and React all have in common? They emphasize building complex web applications by combining and composing together simple, independent components. By “component”, I mean a stand-alone piece of functionality (usually) tied to a visual element. Because they’re responsible only for their own view and functionality and communicate with the outside world through an established interface, they’re very easy to understand and digest. Once you start building applications as compositions of components, you’ll start to see them everywhere!

So how about Meteor? Can we build component based web applications with our favorite framework? Templates and inclusion tags are presented as the go-to system for building components in Meteor in just about every beginner resource and in the official docs. In fact, as a beginner I believed that this was the only way to pull templates into my application.

As I began to build more complex applications, I quickly began to realize that this simple template inclusion just wasn’t cutting it. A modal dialog is a fantastic example to illustrate the issues I was having with this system. Suppose I had a basic modal component. It’s purpose was to render content in a container centered on the screen, and to de-emphasise the rest of the content on the page. When the user clicks outside of the content container, the modal would be dismissed.

If I only had one modal in my application, a naive approach would be to create a single template and include it with inclusion tag syntax:

{{> modal}}

What if I wanted other content in the modal? Maybe I would pass it in through the data context:

{{> modal content="This is my content"}}

What if I wanted more complex content? Like a sign-out button? I may either add a flag to my data context, or create an entirely new modal template:

{{> modal content="Content..." signout=true}}
{{> signoutModal content="Content..."}}

If you take the first route, the complexity of your modal template will soon spiral out of control. The modal now has to concern itself with the complexities of the data it contains, along with the complexities of just being a modal. If you go the second route, you’ll quickly have an explosion of modal-esque templates. A change to your modal’s behavior would require updating each of these templates, instead of making your change in a single place. Both of these scenarios are far from ideal.

Custom Block Helpers

Thankfully, there is a solution! Deep within the Spacebars readme, you’ll find a section on custom block helpers. Custom block helpers give us a new syntax for pulling templates into our application, and even allow us to pass content into our templates. Within our template we can use standard inclusion tag syntax to pull in a special block, Template.contentBlock, which injects the content we’ve passed into our template into the DOM at this point. If you’re familiar with AngularJS, you’ll notice that this is very similar to the ng-transclude directive.

Using custom block helpers, we can build a more powerful modal template:

<body>
    {{#modal}}
        <h1>Hello!</h1>
        <p>I'm modal content!</p>
    {{/modal}}
</body>

<template name="modal">
    <div class="modal fade">
        <div class="modal-dialog">
            <div class="modal-content">
                {{> Template.contentBlock}}
            </div>
        </div>
    </div>
</template>
if (Meteor.isClient) {
    Template.modal.rendered = function() {
        $('.modal').modal();
    };
}

Now, our modal template is only concerned with being a modal. The template sets up the DOM structure required to create a Bootstrap modal and instantiates the modal when it’s rendered. It doesn’t have to concern itself with the complexities of the content you want to place inside of it.

Dynamic Template Includes

We can elevate our modal template to the next level by leveraging another powerful Meteor feature, dynamic template includes. Dynamic templates includes let us provide the name of the template we want to include along with a data context at runtime, rather than at compile time.

What if we wanted to define custom content for our modal’s header, content container and footer? With dynamic template includes, it’s easy:

<template name="modal">
    <div class="modal fade" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    {{> Template.dynamic
                        template=header
                        data=headerData}}
                </div>
                {{> Template.dynamic
                    template=content
                    data=contentData}}
                <div class="modal-footer">
                    {{> Template.dynamic
                        template=footer
                        data=footerData}}
                </div>
            </div>
        </div>
    </div>
</template>
<body>
    {{> modal
        header='myHeader'
        content='myContent'
        footer='myFooter'}}
</body>

<template name="myHeader">
    <em>This is the header</em>
</template>

<template name="myContent">
    <p>This is where the content goes.</p>
    <p>There can be multiple elements.</p>
</template>

<template name="myFooter">
    <button>Footer!</button>
</template>

While it is a little more work defining our modal content in templates instead of in-line, it makes our modal template much more powerful and versatile.

Build Your Own Data Context

Our new modal template even allows us to explicitly pass data contexts into our content templates. Check out how we would pass a data context into our footer template:

<body>
    {{> modal
        header='myHeader'
        content='myContent'
        footer='myFooter' footerData=getFooterData}}
</body>
Template.body.helpers({
    getFooterData: function() {
        return {
            runInFooterContext: function() {
                console.log('in footer');
            },
            runInBodyContext: function() {
                console.log('in body');
            }.bind(this)
        };
    }
});

Template.myFooter.events({
    'click button': function() {
        this.runInFooterContext();
        this.runInBodyContext();
    }
});

In this example, we’re building our footer’s data context in a helper method on the body template. This data context has two methods: runInFooterContext and runInBodyContext. When the button in the footer template is clicked, we call both of these functions.

An interesting trick that can be used when juggling data contexts is that methods bound to the current data context can be passed into a child template’s data context. In this example, the runInBodyContext function is bound to the body’s data context before it’s passed into the footer template. When it’s accessed and called within the footer data context, it is executed under the body’s data context.

Final Thoughts

I hope this gave you a few ideas for building more component based systems with Meteor. Check out the slide deck for this talk, and let me know if you have any feedback or questions!

Customizable Meteor Navbar with Orion CMS

Written by Pete Corey on Mar 2, 2015.

Last week I talked about building a dictionary driven category system for articles using the fantastic Orion CMS. This week I’m going to continue in that same vein by building a fully customizable navbar!

So what’s the plan of attack? In Orion, we’ll define an entity called pages. This entity will hold the title and content for a variety of pages that will exist on our site. We want to be able to choose a selection of these pages to appear on our navbar in a specified order. We’ll keep that list of pages in an Orion dictionary.

Defining the Pages Entity

The first step is to define the pages entity that will be used by our application. This will be a straight-forward Orion entity:

orion.addEntity('pages', {
    title: {
        type: String,
        label: 'Title'
    },
    content: {
        type: String,
        label: 'Content',
        autoform: {
            afFieldInput: {
                type: 'froala',
                inlineMode: false,
            }
        }
    }
}, {
    icon: 'bookmark',
    sidebarName: 'Pages',
    pluralName: 'Pages',
    singularName: 'Page',
    tableColumns: [
        {
            data: 'title',
            title: 'Title'
        },
        orion.attributeColumn('froala', 'content', 'Preview')
    ]
});

Defining the Page Order

The next step is to define an Orion dictionary entry to hold the list of pages as they will appear in the navbar:

orion.admin.addAdminSubscription(orion.subs.subscribe('entity', 'pages'));

orion.dictionary.addDefinition('pages.$', 'config', {
    type: String,
    label: 'Page',
    optional: false,
    autoform: {
        type: 'select',
        options: function() {
            return orion.entities.pages.collection.find().fetch().map(function(page) {
                return {
                    value: page._id,
                    label: page.title
                };
            });
        }
    }
});

orion.dictionary.addDefinition('pages', 'config', {
    type: Array,
    minCount: 1,
    optional: false,
    label: 'Page Order'
});

There are a few moving parts here. Let’s break them down to get a better understanding of what’s going on.

Building Our Orion Interface

The first thing to notice is that our page order dictionary entry actually consists of two entries: pages and pages.$. Our goal is to have the page order list be a list of selections populated with pages entities. This means that pages must be an Array type. The children of pages (pages.$) are given the type String. When using Orion, it’s required that you add the definition for child dictionary entries (pages.$) before adding the parent (pages).

Populating Our Selects

In order to build the navbar, we need to be able to select which pages will appear from the set of all pages in the system. This means that we need to have the options of our pages.$ attribute driven by the pages entity collection. How do we do this?

Just like last time, the answer is to provide a custom options function. Our options function will fetch all of the documents in the pages entity collection and transform them into select options:

options: function() {
    return orion.entities.pages.collection.find().fetch().map(function(page) {
        return {
            value: page._id,
            label: page.title
        };
    });
}

If you were to visit your Orion dashboard at this point, you may notice that your page order selects aren’t populating with data (or maybe they are, if you visited the entities page first). This is because we haven’t subscribed to the pages entity collection yet. The final piece to this puzzle is to add a new subscription to the admin dashboard:

orion.admin.addAdminSubscription(orion.subs.subscribe('entity', 'pages'));

And that’s it! We now have a system where we can create pages in our CMS, and choose which of those pages will appear in our navbar. Check out the nav tag on my hello-orion Github project to see a working example.

Custom Categories with Meteor's Orion CMS

Written by Pete Corey on Feb 23, 2015.

Lately I’ve been playing with Orion, the fantastic Meteor based CMS. The way Orion builds on top of aldeed:collection2 and aldeed:autoform to create an incredibly flexible and powerful CMS is inspiring.

One feature I wanted out of Orion was the ability to have data from the dictionary be accessible from within an entity. For example, I wanted to keep a list of Categories in Orion’s dictionary and associate each Article entity with one of these categories. After doing a little digging, I found a way to accomplish this.

I’ve created a gist to show off this functionality. The key lines of code are shown below:

allowedValues: function() {
    return orion.dictionary.collection.findOne()['categories'];
},
autoform: {
    options: function() {
        return orion.dictionary.collection.findOne()['categories'].map(function(value) {
            return {
                value: value,
                label: value
            };
        });
    }
}

When setting up your entity’s attributes, you’ll need to add custom allowedValues and autoform functions. The allowedValues function returns the possible string values that can be saved into this field. These values are pulled from the categories field of the dictionary document. autoform is used to build the select options presented to the user. In this case, we’re using the category string as both the value and the label.

Interestingly, if allowedValues is not a function, it will build the options automatically. However, if allowedValues is a function, the dropdown will be empty. We need to explicitly specify a autoform.options function to build our options for us. I haven’t looked into what’s causing this.

These are issues with this approach. If a user creates an article with a certain category, but then deletes that category from the dictionary, the article will still hold the deleted value in its category field. When that article is edited in Orion, the category field will be blank. I’m hoping to spend a little more time in the future to address these issues and dig deeper into this kind of Orion functionality.