Creating a Donation Widget with Flight Components
Save article ToRead Archive Delete · Log in Log out
14 min read · View original · sitepoint.com
This article was peer reviewed by Tom Greco. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
In this tutorial I will be teaching you the basics of Twitter’s Flight.js by making a donation widget, which also uses Materialize for the front-end, and Stripe to handle the payments. We’ll be covering Flight’s main concepts and methods.
Flight is an event driven framework by Twitter. Based on components, Flight maps behaviors to DOM nodes independently. Unlike other popular frameworks, Flight doesn’t prescribe a particular approach to how you render or fetch your data it is, however, dependent on jQuery. In its essence, Flight is all about events. These can be triggered by the DOM or by artificial triggers within other UI components. Flight is basically a framework for making frameworks. This might sound complicated, but while Flight isn’t as 1-2-3 as jQuery, its learning curve is exaggerated and learning Flight could definitely improve your JavaScript skills.
Why Use Flight?
- Write more readable jQuery.
- Write re-useable components.
- Use as much or as little of other libraries as you want.
- A better structure for your code in general.
- Supports and even prefers modular JS.
Readability: jQuery vs Flight
Let’s say we’re listening for a click and a hover on a button. Using jQuery you’d probably do something like this:
$('#button').on('click', function() {
confirm('You clicked?');
});
$('#button').on('mouseover', function() {
console.log('Oops');
});
But using Flight this all goes into one component.
var Button = flight.component(function () {
this.log = function () {
console.log('Oops!');
}
this.confirm = function () {
confirm('You clicked?');
}
this.after('initialize', function(){
this.on('mouseover', this.log);
this.on('click', this.confirm)
})
});
Button.attachTo('#button');
Of course, jQuery requires less code but, with Flight our code is structured a lot clearer. The events are two different threads in jQuery, but in Flight these are both contained within the same component. We can easily see that this component and the element it is attached to are listening for two events. But let’s say we wanted to add that hover
event listener 200 lines later in our code. Using jQuery, you’d probably add it at that point in your file. However using Flight we are almost forced to add it to the existing component.
I’ve made a template that includes everything you need to get started so you can get straight to coding. You can fork/download it from CodePen.
Want to make your own template? Just copy in these CDN’s:
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="http://flightjs.github.io/release/latest/flight.min.js"></script>
<script src="https://checkout.stripe.com/checkout.js"></script>
Making Your First Component
Flight consists of ‘components’. Components are reusable blocks of code that stand alone within your application. Creating a component is a lot like creating an object constructor except that components cannot be changed or accessed after they have been initialized. Components can only communicate with your app through events. Either by triggering them, or listening to them.
Flight component’s can be as simple or as complex as you like. In the example below we are only listening for a hover (mouseover) event on a button, and showing an alert when that happens.
var Button = flight.component(function () {
this.alert = function () {
alert('Oops!');
}
this.after('initialize', function(){
this.on('mouseover', this.alert);
})
});
Button.attachTo('#button');
This is obviously nothing jQuery couldn’t do, and structuring your code like this may seem like a lot of effort now, but once you start using Flight for things other than hover events you will understand its benefits.
As long as you understand that it’s listening for a mouseover
event and triggering an inner function you’re fine for now. I’ll explain what the rest means later on in this tutorial.
Getting Started
If you are using the template, the form in your index.html
file should look like this:
<form action="#">
<input type="checkbox" id="accept" />
<label for="accept">By proceeding your agree to our terms, which are completely unfair and well, not very real.</label>
<br/>
<button class="waves-effect btn" style="margin-top: 15px;" id="launch" disabled>Let's Go</button>
</form>
We want to enable the button when the check-box has been checked and disable it otherwise. Let’s head over to our JavaScript code and create our first component.
var checkToEnable = flight.component(function () {
// Magic
});
Each Flight component consists of a few different methods. Some of these are required, others are not. The first method we’ll be adding is attributes(). The attributes()
method contains one or more attributes. Attributes are scoped variables and / or arguments. Empty attributes (ones declared with a value of null
), require a value to be passed to them when the component is initialized. Other attributes will use their default values unless instructed otherwise. Attributes are typically used to hold element references. Insert the following code into your component:
this.attributes({
button: null
});
The button attribute
will serve as a reference to the button we want to enable. The next method we want to add is the initialize() method.
this.after('initialize', function () {
this.on('change', this.enableButton);
});
Flight components already define a default implementation of initialize()
which we want to extend rather than overwrite, which is why we’re using the after() method here. Inside of the callback function we’ve added an event listener. This will listen for a change on the element which the component will be attached to and consequently trigger the enableButton()
function, which we’ll create next.
this.enableButton = function (e) {
var buttonEl = document.getElementById(this.attr.button);
switch (e.target.checked) {
case true:
buttonEl.disabled = false;
break;
case false:
buttonEl.disabled = true;
break;
}
};
This doesn’t do anything too fancy. It’s just a simple function which enables the button when the checkbox, which this component will be attached to, is checked and vice versa. You may have noticed that we accessed the attribute
by calling this.attr. This is bad practice and I will show you a better solution later on.
Our component isn’t working just yet. To complete it we need to attach it to the DOM. This happens ‘outside’ of the component. A component can be attached to as many elements as you like, it can also be attached to the document, but the component has to come first. We will pass in the reference to the button element as an argument.
checkToEnable.attachTo('#accept', {
button: 'launch'
});
Great! You’ve created your first Flight component. To recap, it should be looking like this:
var checkToEnable = flight.component(function () {
this.attributes({
button: null
});
this.enableButton = function (e) {
var buttonEl = document.getElementById(this.attr.button);
switch (e.target.checked) {
case true:
document.getElementById(this.attr.button).disabled = false;
break;
case false:
document.getElementById(this.attr.button).disabled = true;
break;
}
};
this.after('initialize', function () {
this.on('change', this.enableButton);
});
});
Diving In
By now you should understand the three most important concepts: attributes, callback functions, and initialize()
. Most Flight components consist of just these parts. Our next one is going to use a core concept of Flight: event bubbling. Event bubbling sounds a little complicated, but it’s actually not that hard to understand. For example, let’s say I have a button
and its parent is a div
. When the button
is clicked its event will bubble up to the div
, assuming our component is attached to the div
.
This is exactly how our next component will work. It will be attached to the donation widget, in the form of a Materialize modal, but we’ll be listening for events from its children. First things first, we need to add the markup for the modal into our index.html
file. Insert it before the closing body tag:
<div id="stripe-widget" class="modal">
<div class="modal-content">
<h4>Give us your money.</h4>
<p>We'll use it well, we promise.</p>
<form action="#">
<p class="range-field">
<input type="range" id="stripe-amount" value="10" min="0" max="100" />
</p>
</form>
</div>
<div class="modal-footer">
<button class="btn blue waves-effect waves-blue" id="checkout" disabled>Donate <span data-amount=""></span> <i class="fa fa-cc-stripe"></i></button>
<a href="#!" class=" modal-action modal-close waves-effect waves-red btn-flat">Close</a>
</div>
</div>
Now let’s create our component.
var getAmount = flight.component(function () {
// Magic
});
To better understand our component we’ll be adding its methods in reverse order. First of all add the initialize
method.
this.after('initialize', function () {
this.on(this.attr.range,'change', this.onChange);
this.on(this.attr.checkout, 'click', this.onClick);
});
Looks different doesn’t it? Because our component is attached to the donation widget we’re passing in two of its children within our event listeners. This might not make sense now but it soon will. Let’s add the attributes.
this.attributes({
checkout: '#checkout',
range: '#stripe-amount',
display_amount: '[data-amount]'
});
These attributes work with the current markup. You might add this component to some different markup in the future, in which case you can pass in different selectors. Next up we’ll be adding the onChange()
function.
this.onChange = function (event) {
var amount = this.select('range').val();
if (amount == 0) {
alert('please enter an amount');
this.select('checkout').prop('disabled', true);
} else {
this.select('checkout').prop('disabled', false);
this.select('display_amount').text('$' + amount);
this.select('checkout').attr('data-stripe-amount', amount);
}
};
The one method that stands out is select(), which is a lot like jQuery’s find method, but its scope only includes children of the attached element (the donation widget). The important thing to understand is that we’re referencing our attribute names as strings. This confused me to at first, so just keep this in mind, because this is actually one of the shortcuts Flight has created.
Since the button has been enabled it can now listen for events. Let’s add the onClick()
function now.
this.onClick = function (event) {
var stripeAmount = this.select('checkout').attr('data-stripe-amount');
stripeAmount = stripeAmount + 0 + 0;
this.trigger('callStripe', {
amount: stripeAmount
});
};
First of all we’re fetching the amount from an attribute of the button, then we’re making it valid for Stripe by adding two zeros. That’s nothing new though. The real magic is happening in the trigger method, which is triggering a custom event and passing along the amount as data (also known as the payload). We’re going to create the component which will listen for that event next. The finished component should look like this:
var getAmount = flight.component(function () {
this.attributes({
checkout: '#checkout',
range: '#stripe-amount',
display_amount: '[data-amount]'
});
this.onChange = function (event) {
var amount = this.select('range').val();
if (amount == 0) {
alert('please enter an amount');
this.select('checkout').prop('disabled', true);
} else {
this.select('checkout').prop('disabled', false);
this.select('display_amount').text('$' + amount);
this.select('checkout').attr('data-stripe-amount', amount);
}
};
this.onClick = function (event) {
var stripeAmount = this.select('checkout').attr('data-stripe-amount');
stripeAmount = stripeAmount + 0 + 0;
this.trigger('callStripe', {
amount: stripeAmount
});
};
this.after('initialize', function () {
this.on(this.attr.range,'change', this.onChange);
this.on(this.attr.checkout, 'click', this.onClick);
});
});
Before we create the final component we have to initialize
the previous one. Because the modal itself is initialized dynamically, we’ll attach it after it has been initialized. The code below is just a simple click event listener for the button we enabled in the first component, after which we attach our new component. The openModal()
method belongs to Materialize.
$('#launch').on('click', function (event) {
event.preventDefault();
$('#stripe-widget').openModal();
getAmount.attachTo('#stripe-widget');
});
Meet Stripe
In a nutshell, Stripe is the PayPal you always imagined, made with developers in mind. Stripe is used in many websites and apps to handle payments (Twitter and Kickstarter, to name a few). They offer a range of services or APIs (whatever you want to call them), but the one we’re going to use is Checkout.
After Stripe has verified someone’s credit card your website will receive a token back, this token then needs to be sent to Stripe along with your secret key. Since your key is secret this can’t happen on the front-end, because JavaScript wasn’t and isn’t meant to be, at least in its original form, secure. To do this you can use one of Stripe’s libraries for PHP, Sinatra, Python (Flask), Rails or Node.js.
I mentioned keys right? Well, to get a key you need to sign up for a free Stripe account. You don’t even need a credit card yourself, a plain old bank account will do! After you get your own key you will have to replace my public key with yours when calling Stripe (as shown below). They offer ‘test’ and ‘real’ keys so you easily test your application with little effort. When using test keys, Stripe will also accept test credit cards.
Everything should be working up to the point where you click on the donate button, and nothing happens. Let’s add the last bit of magic.
var launchStripe = flight.component(function () {
// Magic
});
This component will launch Stripe for us. In order to do this we first have to create a Stripe instance at the top of our document (not within a component).
var handler = StripeCheckout.configure({
key: 'pk_test_hue7wHe5ri0xzDRsBSZ9IBEC', //replace this with your key!
image: 'http://freedesignfile.com/upload/2014/06/Cup-of-coffee-design-vector-material-03.jpg',
locale: 'auto',
token: function(token) {
console.log(token);
var html = 'Thank you! <i class="fa fa-beer"></i>';
Materialize.toast(html, 3000);
// Send to server
}
});
While getting into the back-end side of things is a bit beyond the scope of this article, as mentioned above, Stripe has a Node.js library that might be a bit less intimidating for those comfortable with JavaScript. You can send your key to a Node server via a HTTP request using a module like Express. Within your Node server’s response to that request, you would do something like this:
var stripe = require("stripe")(
"sk_test_yourkeyhere"
);
stripe.customers.create({
description: 'Coffeehouse Customer',
source: "tok_yourtoken" // This comes from our front end JavaScript code
}, function(err, customer) {
// asynchronously called
});
If you’d like a more complete example of using Stripe and Node.js, the SitePoint article on Building a Daily Affirmations SMS Service with Stripe & Syncano covers the Node.js side of the process in more detail and even shows how you can use a microservice platform like Syncano to run the Node.js code for you from the backend.
Now let’s get back to our component. We are going to add the initialize
method first and make it listen for the event we triggered in our second component.
this.after('initialize', function () {
this.on('callStripe', this.launch);
});
We’re basically listening for a custom, internally triggered, event rather than a DOM event. All we have to do now is create the callback, which will launch Stripe.
this.launch = function (event, data) {
$('#stripe-widget').closeModal();
handler.open({
name: 'the Coffeehouse',
description: 'Thank You!',
currency: "usd",
amount: data.amount
});
};
Our function is expecting two arguments, the event and the data. The event is the same as usual but the data includes the payload we included upon initially triggering the event in the previous component. The payload is simply our amount, because that’s all we added to the event. But in more complex cases this could be a full response from an API call.
The other thing we’re doing is launching Stripe’s payment form using the amount we got from the previous component. You can find the full Checkout documentation here.
Finally we need to initialize the last component. Instead of attaching it to a specific element we’re hooking it up to the document.
launchStripe.attachTo(document);
Our finished code should look something like this. We’ve done quite a lot in less than 100 lines of JavaScript!
Conclusion
Hopefully Flight makes a little sense by now, but whatever the case may be you can find the full documentation here. As you can see, Flight’s component system results in extremely readable and modular code. For example you could reuse the launchStripe
component anytime you wanted to process a payment, or reuse the enableButton
component anytime you wanted a checkbox to be checked before enabling a button. Flight is great like this, and because it doesn’t prescribe a particular approach the possibilities are endless.
If you’ve built something interesting with Flight, or you just want to share your thoughts about it, please comment down below!