1.3K Followers
·
Follow

Image for post
Image for post

Build Your Own /r/thebutton with Meteor

Update: Since writing this over a year ago, a couple of things have changed. First, /r/thebutton timed out a little after this article was published, so you won’t be able to see the real thing in action. Second, Meteor has obviously been under development since, so some of the recommendations written below may no longer accurate. I’m leaving the article up since it’s still generally interesting.

Reddit has been taken over by a clever little toy launched as an April Fools’ joke: The Button. Over three weeks on, it’s still getting plenty of clicks.

In short, The Button is just that: a button. It’s attached to a timer that counts down from 60 seconds. You’re invited to click it, but there’s a catch: The Button can only be clicked by Redditors who had an account before April 1st. Once you click it, you can never click it again. There’s a reward, though: you get to display flair next to your username indicating how much time was left on The Button when you clicked it. What will happen when time runs out? No one yet knows.

Besides being a fascinating social experiment, The Button is also a terrific example of a modern real-time web app. The counter counts down and resets in real time as it’s clicked. The number of participants (people who’ve clicked The Button) increments in real-time. State is shared across potentially thousands of clients. Despite being a small app, there’s probably a lot of hidden complexity in the codebase.

To explore how The Button works and to offer a tutorial for those looking to write similar apps, I decided to create a rudimentary clone, which you can see here:

https://github.com/Primigenus/thebutton

I chose the Meteor platform to do this, as it was designed with exactly this kind of application in mind. It took me nearly no time at all. Below, I’ll walk through the most important parts of the app.

Wait, what’s Meteor?

Meteor is a platform for writing software for the web and mobile. At its heart lies a philosophy that programming is currently too hard and that there could be a simpler way. This is reflected in almost everything Meteor does, including installing it.

Why use Meteor instead of something like Angular, Ember or React? Because with Meteor we’ll be able to build the whole app including the server and database, not just the part running in the browser. What about full stack frameworks like Rails, Django or MEAN? Meteor is designed for real-time software, and as a result we can write much less code and spend much less time configuring everything to work.

This article assumes basic familiarity with programming and web development. If you want to get a handle on Meteor’s basics, follow its excellent tutorial for beginners first.

Building a basic /r/thebutton

Looking at /r/thebutton, we can roughly observe how it works and infer what the server is probably doing. At the very least, there needs to be a timer that syncs with the server. We need a button that sends a signal to the server when pressed, and becomes disabled afterwards. Then we need to record some information about the person pressing it: how much time remained when they clicked, who they are, etc. And when they do press the button, we need to reset the timer for everyone else.

First we need to start the timer on the server and then make sure we can access it in the browser. One strategy for accomplishing this would be to have a timer on the server which we can poll from the client:

if (Meteor.isServer) {
var timer = 1000 * 60;
Meteor.setInterval(function() {
timer = timer - 1000;
}, 1000);
Meteor.methods({
getTimer: function() {
return timer;
}
});
}
if (Meteor.isClient) {
Meteor.setInterval(function() {
Meteor.call("getTimer", function(err, res) {
Session.set("timer", res);
});
}, 1000);
}

However, this is quite low level — we’re managing the exchange of data between server and client ourselves, and asking the server for new information every second, which is inefficient. Meteor makes this much easier by offering collections that are synced automatically between the server and client:

Timer = new Mongo.Collection("timer");
if (Meteor.isServer) {
Meteor.setInterval(function() {
Timer.update(Timer.findOne()._id, {
$inc: {value: -1000}
});
}, 1000);
}

…which allows us to get rid of the method and take advantage of Meteor’s reactivity and publish/subscribe model.

Further, by being able to reason about the timer as a value that changes throughout the app without needing to consider the environment (eg. server or client), Meteor helps simplify how we model our app and reduce the number of elements we need to keep track of.

On the client, we can then just refer to the value in the collection. Since Meteor is reactive, all we have to do to display the timer is print the current value:

<template name="countdown">
{{timeRemaining}}
</template>

…then hook it up, and it will be updated when it changes:

Template.countdown.helpers({
timeRemaining: function() {
return Timer.findOne().value;
}
});

Great! Now our server is counting down to zero and our page is showing us the current time remaining. Time to make a button.

This can be described in HTML, like so:

<template name="thebutton">
<button>Click me!</button>
</template>

When we click it, we store the click in our database:

Clicks = new Mongo.Collection("clicks");
Template.thebutton.events({
'click button': function() {
Clicks.insert({
userId: Meteor.userId(),
clickDate: new Date(),
timeRemaining: Timer.findOne().value
});
}
});

But wait, what’s Meteor.userId()? Meteor provides that as part of its accounts package, but in order to get access to it, we’ll need to install it. Meteor comes with an extensive package system that includes such accounts. Back in the terminal, we can just run this:

meteor add accounts-ui accounts-password

And then to log in, we can just display login buttons somewhere:

<div class="login">{{> loginButtons}}</div>

So now we have a timer, a button, and clicks by signed in users recorded. But what makes The Button work is the fact that you can only click it once.

First, let’s prevent the user from clicking more than once. To do this we can just check whether we already recorded a click for this particular user when he or she clicks:

var click = Clicks.findOne({userId: Meteor.userId()});
if (click) return;

We also want to show the user how much time was remaining when they clicked, and only allow them to click if they’re logged in. To do this we can update our Button template and include the various states we expect:

<template name="thebutton">
{{#if currentUser}}
{{#unless clicked}}
<button>Click me!</button>
{{else}}
You clicked with {{timeRemaining}}s left.
{{/unless}}
{{else}}
<button disabled>Log in first</button>
{{/if}}
</template>

Then hook them up to the right behaviour (currentUser is provided courtesy of the accounts package):

Template.thebutton.helpers({
clicked: function() {
return Clicks.findOne({userId: Meteor.userId()}) != null;
},
timeRemaining: function() {
var click = Clicks.findOne({userId: Meteor.userId()});
if (click)
return click.timeRemaining;
}
});

That’s it! We now have a persistent timer, a countdown, and a button that you can only click once. But it’s no fun just playing with this by ourselves, since the whole point of the button is to use it with other people. So let’s list the number of people who’ve clicked, and then put it online so others can join in.

Like earlier, listing the number of participants is straightforward. First we need a template:

<template name="participants">
{{numParticipants}} participants
</template>

Then we need to tell the template where to get the number:

Template.participants.helpers({
numParticipants: function() {
return Clicks.find().count();
}
});

And we’re done! Time to put this baby online and share it with friends. I recommend Meteor’s own hosting platform, Galaxy, to do this. Once you’ve set up an account, you can just run:

DEPLOY_HOSTNAME=galaxy.meteor.com meteor deploy mybutton.meteorapp.com

Have fun!

More to do

The above is a quick run-through of the core components of building your own Button with Meteor. Pretty straightforward, right? There’s much more, including basic security, the pie chart that /r/thebutton has, and a more compelling countdown timer in the full repository at https://github.com/Primigenus/thebutton, which you can re-use or fork to play around with.

The real /r/thebutton probably also contains a lot more code on the server to ensure things continue to run smoothly, especially at Reddit’s scale. And what happens when time runs out? By the looks of how Reddit is keeping this thing alive, it may take a long time to find out.

I’m looking forward to seeing whether we, as a community, can remix this and come up with our own alternatives and derivatives. What would you change about The Button?

Written by

Escape the cyberpunk dystopia - UXE + DEI @google

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store