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.