Friday, May 29, 2015

Variations in JavaScript from Anchor Tags

Don't click on this anchor.
This came up again for me the other day, and I couldn't remember all the rules. Let's say you want to call a JavaScript function when someone clicks on a link on your web page. Do you make the href attribute in the anchor tag a javascript: URI? Do you set the href to "#" and set the onclick attribute to your function call? Is it better to add an event handler instead of using the onclick attribute?

Maybe there's no right answer (I know, some people will tell you that using the javascript: URI in the href is the wrong answer because it mixes the layout and logic...but in small projects I don't get too hung up on those things). But you should be aware of the different behaviors in the function depending on which method you choose.

I set up a demo page so I can remind myself which is which.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Link Functions</title>
</head>

<body>

  <p><a id="a" href="javascript:doSomething(event, this);">JavaScript in the href</a></p>

  <p><a id="b" href="#" onclick="doSomething(event, this)">JavaScript onclick</a></p>

  <p><a id="c" href="#">Handler added by jQuery</a></p>

</body>

<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<script>

function doSomething(evt, src) {

    // what is "this" now?
    console.log(this);

    // what event got us here?
    console.log(evt);

    // DOM source of the click
    console.log(src);

}

$(function() {

    $("#c").click(doSomething);

});

</script>

</html>

If you click on the first link, the javascript: URI version, inside the function this will reference the Window object. There is no event object, and the source of the click also appears to be the Window object. This form is certainly the least useful. It executes your code, but you really get no insight into where the click came from.

The second link, which uses the onclick attribute, also results in the Window object being this inside the function. But the event is populated with a MouseEvent and the source node is the anchor tag. That's all good information.

The third form, which set the click event handler using jQuery, also gives good information but in a slightly different way. Inside the function, this refers to the anchor tag instead of the Window. The event object is a jQuery event object, which wraps the original event. This is nice because jQuery events tend to be more standard across different browsers.

The bottom line is that the second and third forms are better because they give you more complete information about the click. But they do so in different ways - ways of which you must be aware! The first form should probably be avoided in most cases.

I'll probably have to refer back to this post myself in a few months to remember the rules. One of the best things about writing a blog is that it documents information for your future self. Often I search my own posts for some bit of information that I know I wrote about once but can't remember.

My comics don't really contain any useful information but sometimes I look back at them too. You should look at the one for today:

Amphibian.com comic for 29 May 2015

Wednesday, May 27, 2015

More Stats

After last week's Bitcoin Paywall comic was posted on Reddit, hits to my comic reached new levels. At first I didn't know where all the additional traffic was coming from, so I improved my stats page to let me see the top referring URLs and top comics accessed in the past day.

The most important thing I learned from the stats is that I shouldn't look at the stats.

First of all, I worry that the comic is not getting enough traffic some days. Is it not well-publicized? Do people just not like it? Am I a failure? And on days when it does get lots of traffic, I tell myself that it's not enough.

Why should I care? I do the comics because I need a creative outlet. I went 20 years without writing a frog comic and it bothered me. I bought amphibian.com in 1997 with the intention of putting comics on it and never did until 2014. Now I don't know why I waited so long. Some of them are bound to be more popular than others, but I write them for myself - not for anyone else. If people like them, that's great. If they don't, that's fine too. My kids like them. Well, most of them.

Secondly, I noticed that I get a lot of "referer" (yes, it's a misspelling of "referrer" and it's really that way in HTTP headers) sites that are very suspicious and almost always Russian. When used properly, the HTTP "referer" header is supposed to tell me the URL that had a link to me and was followed by a user. But these sites have no such links to me. When I first saw this, I went to some of the sites - and regretted it. They are clearly not operated by anyone who has a legitimate interest in linking to frog-related entertainment.

So what's up with that? I can only conclude that they are some kind of bots who feed me bogus referer data. Really, with the content of web pages all advertisements and the web readers all bots, is there anything useful on either end of the Internet these days?

And so I have a new slogan, one that will really differentiate my site from everything else out there:

Amphibian.com: Almost Completely Legitimate Content!

And in today's comic, the frogs pick a logo to go with that slogan...

Amphibian.com comic for 27 May 2015

Monday, May 25, 2015

Using the Coinbase Merchant API with Node

I've been writing about different pieces of my Bitcoin integration since last week when Wednesday's comic broke the Fourth Wall while building a paywall. First I explained the client-side code that handled the 402 response. Then on Friday I gave an outline of my Express middleware that controls the access to non-free resources. I used several stubs and mock objects in that post because the actual implementations of the pieces will vary based on where they are integrated - it's just not very interesting what I did with my database access because it's probably different from yours.

But one last part in which people may be interested is the integration with Coinbase's Merchant API via their Node module. I used Coinbase in my implementation because their API was very easy to work with and the integration points are extremely modular. It is set up in such a way that I could easily replace Coinbase with another service such as BlockCypher or calls to the Blockchain API. But I had a wallet with Coinbase already so I went with that...

There are really only two interactions with Coinbase that I perform. One is creating a new Bitcoin address that is tied to my Coinbase wallet. The other is processing the callback from Coinbase informing my server that payment has been made.

Creating a new address is very simple if you've set up your Coinbase API access correctly. Log in to your Coinbase account and go to https://www.coinbase.com/settings/api to create a new API key. Make sure it has the "address" permission and it is enabled. You have to verify every step with 2-factor authentication so have your phone nearby. If possible, set the IP restriction on your key as an extra precaution against unauthorized access.

Your key has two parts, the key and the secret. After creation, you can copy them and use them with the Coinbase Node module. Keep them secure! Don't put them directly in your code and accidentally push it to a public repo on GitHub! Besides the key and secret, you'll also need the Coinbase account number for which you want to generate addresses. Your account number is basically just a Coinbase identification for each wallet you have with them. It's displayed as part of the URL when you are looking at your wallet on their web site.

Once you have those three things, to generate a new address you do something like this:

var Client = require("coinbase").Client;

var client = new Client({
    apiKey: "your-coinbase-api-key",
    apiSecret: "your-coinbase-api-secret"
});

var accountId = "your-coinbase-account-id";

var Account   = require("coinbase").model.Account;
var myBtcAcct = new Account(client, {
    "id" : accountId
});

var secretCode = "secret-code-unique-to-each-address";

var args = {
    callback_url: "http://example.com/callback?secret=" + secretCode,
    label: "your address label"
};

myBtcAcct.createAddress(args, function(err, data) {

    if (err) {
        console.log("unable to create address: " + err);
    } else {
        console.log(data.address);
    }

});

The basic steps are to create a client, create an account object with that client, and then call createAddress on that account. The createAddress function takes an arguments object as the first parameter, in which you specify an optional callback URL and label. The label is just some text you can add for your own reading later. It has no functional aspect. But the callback URL is very important. As you can see on line 18, I use a secret code as part of the callback query string. In my example it is hard-coded, but in real life you'll want to generate a different one for every address. If you keep the secret code only on your server - never send it to the client in any way - it ensures that someone won't be able to send their own POST to your callback URL and trick your server into thinking that they have paid. Store the address and secret code in your database and make sure they match in your callback processor. If they don't, something is wrong and you shouldn't honor the validity of the callback!

If you wanted, you could take the code above and plug it into the example app from Friday's post to generate real addresses. If you wanted to handle the real callbacks in that app as well, you'd want to be able to process the actual data format that comes from Coinbase with those POSTs. The POST to your callback URL will contain this as the body:

{
  "address": "somebitcoinaddress",
  "amount": 1.234,
  "transaction": {
    "hash": "somebiglonghashvaluething"
  }
}

For me, the most important parts were the secret code in the callback query string, the Bitcoin address from the body, and the amount from the body. I don't need the transaction hash, but other applications might. The two pieces of information from the POST body are easy to extract if you use the body-parser middleware for JSON. Here is an example of an app that just takes the callbacks - you could substitute the callback route from Friday's post with this one if you want.

var express = require("express");
var bodyParser = require('body-parser');

var app = express();
app.use(bodyParser.json());

app.get("/callback", function(req, res, next) {

    var secretCode = req.query.secret;
    var addr = req.body.address;
    if (addr) {

        var amountRecieved = req.body.amount;

        // make sure the secret and address
        // match your records, and if so
        // mark the transaction as paid if
        // the amount >= the price.

    } else {

        console.log("something went wrong!");

    }

    // always give coinbase a 200
    res.sendStatus(200);

});

// ------------ start listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

One thing to note (that I missed at first) is that a customer doesn't have to pay the full amount in a single transaction. If you were charging 0.05 BTC, it would be perfectly fine to get two callbacks, each indicating a payment of 0.025 BTC. When handling the data, make sure you add up amounts sent to the same address instead of just taking the latest.

I think I've covered all the major parts of my paywall function now. Just to be clear, I'm not actually advocating everyone putting up paywalls on all their content. Last week's comic was just a joke that happened to include a novel way to handle online micropayments. I do believe that there are lots of legitimate use cases for Bitcoin microtransactions on the web, particularly for content creators such as myself. Charging a few cents worth of cryptocurrency here and there for a pieces of premium content or special features could be a viable alternative to displaying ads on the web (tipping is another alternative, which I've discussed previously).

So enjoy today's comic - completely free!

Amphibian.com comic for 25 May 2015

Friday, May 22, 2015

Making Bitcoin Paywall Middleware

Today's comic doesn't include a lot of built-in technology like Wednesday's did. Just a moth joke. Or was that just a mistake? I'm not sure. Moving on...

Wednesday's comic had a Bitcoin paywall, and in Wednesday's blog I explained how I processed a 402 response in the client to make it work. But the server played more than a bit part in the whole operation. Get it? Bit part. Bitcoin. Moving on...

It's like regular money, but with more math.
To handle the Bitcoin payments and unlock the content, there are several pieces at work. I am using the Express framework for my web application, so I have a middleware applied to each non-free resource. There is also a Bitcoin address generator to create new payment addresses. Another resource serves as the payment notification - when payment is made to an address, this resource receives data about the transaction. Finally, there is a datastore that manages the records to keep all the other pieces in sync.

This is how it works:

First, a resource is requested which requires payment. The middleware checks with the datastore to see if the requesting client has an existing record for this resource.
If no existing record is found, it means the client has never attempted to access this resource before. The middleware uses the address generator to create a new payment address record and gives it to the datastore. The client is returned the 402 response along with the special payment instruction headers.
Alternatively, the datastore might find a record for this resource and client. That means the client has been here before. The record returned by the datastore will indicate whether or not the payment is complete. If the payment has been made, the middleware allows normal access. If payment has not been made, the middleware returns the 402 response to the client with the special payment instruction headers. The data for those headers comes out of the payment record.
At any time, an external Bitcoin processing system can determine that payment has been made to one of the addresses generated by the address generator. It sends data on the payment transaction to the payment callback resource which looks up the record in the datastore and marks it as paid.

I used the Coinbase Merchant API as the external Bitcoin processing system. When I create addresses with Coinbase, I can specify a callback URL for each one and Coinbase will POST some data to it when payment is made. Each address gets a unique URL so my system can look up the payment record that matches it. I am using the official Coinbase Node module in my app.

Here is a basic outline of the app:

var express = require("express");
var app = express();

// mock purchase record
var demoRecord = {
    recordId: "12345",
    code: "product-code",
    paid: false,
    cost: 123.45,
    address: "bitcoinaddress",
    secret: "itsasecret"
};

// mock datastore object
var datastore = {
    findRecord: function(req, productCode, callback) {
        callback("12345");
    },
    checkPaidStatus: function(recordId, callback) {
        callback(demoRecord);
    },
    newRecord: function(data) {
        // store the record
    },
    findRecordBySecret: function(secret, callback) {
        if (secret === demoRecord.secret) {
            callback(demoRecord);
        } else {
            callback(null);
        }
    }
};

function newBitcoinAddress(productCode, callback) {
    callback(null, demoRecord);
}

function send402(res, data) {

    res.setHeader("X-Payment-Types-Accepted", "Bitcoin");
    res.setHeader("X-Payment-Address-Bitcoin", data.address);
    res.setHeader("X-Payment-Amount-Bitcoin", data.cost);
    res.sendStatus(402);

}

function paywallMiddleware(req, res, next) {

    // let's use the URL as the product code,
    // since that's really what is being sold
    var productCode = req.path;

    datastore.findRecord(req, productCode, function(recordId) {

        if (recordId) {

            // found a record, which means that this client
            // has a payment address and purchase record already.
            // now check to see if it has been paid.
            datastore.checkPaidStatus(recordId, function(data) {

                if (data.paid) {

                    next(); // all paid, move along...

                } else {

                    // respond with the payment address that already exists
                    send402(res, data);

                }

            });

        } else {

            // no record found, which means a new Bitcoin
            // address and purchase record must be created.

            newBitcoinAddress(productCode, function(err, data) {

                if (err) {
                    next(err);
                } else {

                    datastore.newRecord(data);
                    send402(res, data);

                }

            });

        }

    });

}

app.get("/free", function(req, res, next) {
    res.send("free content");
});

app.get("/paid", paywallMiddleware, function(req, res, next) {
    res.send("paid content");
});

app.get("/callback", function(req, res, next) {

    var secretCode = req.query.secret;
    datastore.findRecordBySecret(secretCode, function(record) {
        if (record) {
            record.paid = true;
        }
    });
    res.sendStatus(200);

});

// ------------ start listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

That looks like a lot of code, but it's not really that complicated. Near the top, you see I have some mock objects for the demo. In a real scenario, your datastore object would access an RDBMS or the file system to store and retrieve actual purchase records. Here, though, a single fake record and some stub functions will suffice. The newBitcoinAddress function is also a stub - in the real app, this is where I use the Coinbase API to create addresses and associated purchase records each with their own unique secret code as part of the callback URL. That secret code should never be returned to the user, but needs to be stored so the record can be retrieved later in the callback handler. The send402 function is just a utility function that sets the special response headers and sends the 402 code.

The paywallMiddleware function is the implementation of the algorithm I described above. It uses the mock datastore and address generation function.

The routes at the bottom allow testing the system. Launch the app and point your browser to http://localhost:3000/free. You should get the free content. Now try http://localhost:3000/paid. You should get a 402 response. This is because the /paid route includes the paywallMiddleware. To get access, you'll need to pay.

This demo doesn't require any real Bitcoin transactions to take place. To simulate the callback coming from Coinbase, I included a /callback route that is a GET, which makes it easy to test from your browser. In real life, this will be a POST. To simulate sending payment to the address, you just have to go to http://localhost:3000/callback?secret=itsasecret. When you do, you should get a simple OK response...but on the server side, the demo record was updated to paid because the secret code matched. Now you can go back to http://localhost:3000/paid and get the paid content. If you had used a different (incorrect) value for secret, the /paid resource would not have been unlocked.

Next week I'll go into a little more detail about how I use the Coinbase API. My hope is that someday more content creators such as myself can use functions like this for micropayments in place of running advertisements on their sites.

But that's just a dream right now. How long will it take to become reality? A few moths? I mean, months?

Amphibian.com comic for 22 May 2015

Wednesday, May 20, 2015

Using HTTP 402 for a Bitcoin Paywall

Bitcoin. Perfect for Microtransactions?
The punchline for today's comic is hidden behind a paywall. It is a literal wall, built by the frogs to hide the last frame of the comic. However, if you have some Bitcoin to spare (it costs 0.001 coins, about $0.25 US at the present time) you can actually pay for the wall to be removed.

A fair amount of interesting work went in to this comic, but it all revolves around the concept of using the HTTP 402 response code.

Response code 402 is officially "reserved for future use" but I felt that it was about time the future showed up. It's 2015. Where is my moon colony?? Anyway, 402 means "Payment Required" and was apparently intended to be a way for web servers to indicate to clients that the requested resource had to be purchased. Unfortunately, a way to pay for those resources was never worked out and the response code has languished in the realm of TBD for many years.

But these days we have Ajax, REST web services, and Bitcoin. It's all coming together. People have kicked around the notion of integrating 402's with Bitcoin transactions for a few years now but no significant implementation has emerged. With today's comic, sadly, nothing has changed.

I did, however, create a functional web content paywall for Bitcoin microtransactions. Here's how it works...

When a client - be it a human-operated web browser or another computer program - accesses a URL which does not dispense free content, the server will return a 402 response instead of the content. That response also includes three special headers, examples of which are shown here:

X-Payment-Types-Accepted: Bitcoin
X-Payment-Address-Bitcoin: putarealbitcoinaddresshere
X-Payment-Amount-Bitcoin: 1.234

The first, X-Payment-Types-Accepted, should be a comma-separated list of acceptable payment types. In my example and in my comic I am only accepting Bitcoin for now, although a similar form of payment such as Litecoin would easily be possible in place of or in addition to Bitcoin. Each item listed in this header should be used to check the other 2 headers. The X-Payment-Address-XXX header specifies an address to which the payment should be sent. The X-Payment-Amount-XXX header specifies how much this particular resource costs.

The key is that the last part of the address and amount headers should match an item in the list of types accepted. If I wanted to accept either Bitcoin or Litecoin, my headers might look like this:

X-Payment-Types-Accepted: Bitcoin, Litecoin
X-Payment-Address-Bitcoin: putarealbitcoinaddresshere
X-Payment-Amount-Bitcoin: 1.234
X-Payment-Address-Litecoin: putareallitecoinaddresshere
X-Payment-Amount-Litecoin: 2.345

It's then up to the client to deal with this response. Today, browsers will silently ignore this extra information and just tell you that the response code means "Payment Required" without letting you know how to pay. In the future, browsers might prompt you to submit the payment (see this: Zero Click Bitcoin Micropayments). Today, though, you have to do things manually.

In certain cases, the server could include the Bitcoin address and price in the HTML that accompanies the 402 response. Since I am just requesting the paid resources via Ajax, I didn't bother with that and don't include anything human-readable in the response. Here is how my client-side code works when you view the comic:

function checkForPayment() {

    $.ajax({
        url: "/paidContent/paywall-comic",
        dataType: "html",
        success: function(data) {

            $('#comicArea').html(data);

        },
        error: function(xhr, respText, et) {

            if (xhr.status === 402) {

                var addr = xhr.getResponseHeader("X-Payment-Address-Bitcoin");
                var cost = xhr.getResponseHeader("X-Payment-Amount-Bitcoin");

                var bcurl = "bitcoin:" + addr + "?amount=" + cost;
                var bcqr = encodeURIComponent(bcurl);

                if ($("#paydiv").html() === "") {
                    $("#paydiv").html("<p>Pay with Bitcoin!<br>" +
                                      "<a href='" + bcurl + "'><img src='/qrc?text=" + 
                                      bcqr + "'></a><br>" +
                                      "Send " + cost + " BTC to<br>" +
                                      addr + "</p>");
                }

                setTimeout(checkForPayment, 2000);

            } else {
                console.log("failed");
            }

        }

    });

}

When the free version (missing the last frame) of the comic loads, checkForPayment() is called and attempts to get the data for the complete comic from the URL /paidContent/paywall-comic. Anything on my site with a path starting with /paidContent/ will require purchasing. On a successful response, which you'll only get if you've paid, the unpaid version of the comic is replaced with the data from the server.

The interesting part is the error response handling on line 11. A 402 response is an error because it is in the 400 range - indicating a client-side error. It's not a very serious error - just asking for something for which you have not paid! I am using jQuery so the first parameter passed to the error handler function is the jqXHR object. There are many things you can do with that object, but I only need to check the response status to see if it is a 402, and if so I read the values of the X-Payment-Address-Bitcoin and X-Payment-Amount-Bitcoin headers. Yes, I am ignoring my own X-Payment-Types-Accepted header because I happen to know that it only contains Bitcoin (that's the only kind of coin I have right now). If I expected other types, the right thing to do would be to read the X-Payment-Types-Accepted header and loop over the list of values to get the names of the other headers.

I take the address and price and submit them to my QR Code image generation URL to make an easy, scannable way to pay - but also just display the address and price to the user. Again, I'm doing this on the client side because I'm requesting the data with Ajax. If the user navigated directly to a URL which required payment, the server could have responded with that HTML as part of the 402 page, much in the same way servers respond with custom (and sometimes helpful) 404 pages today.

The last part is to check the /paidContent/paywall-comic URL again to see if a payment has gone through. Since it can be a few seconds before Coinbase tells my server that payment has been sent to the address, I took the quick and dirty route of setting a timeout and running the whole checkForPayment() function again. I could have connected a Websocket to wait for payment confirmation from the server if I wanted to get fancy - a website that was expected to get more traffic than my comic would probably want to go that route instead. A possible future enhancement would be to include an additional header with a recommendation for the client - specifying if it should refresh, wait, redirect so some other URL, etc.

In the future, I might put more special features on Amphibian.com on the /paidContent/ URL, instead of it just being part of the joke. Maybe special comics that you can only access by paying a few cents? I'm not sure yet, but I tried to develop a framework that robust enough to support future expansion. At this point, I can easily make the server request payment for anything thanks to the Express middleware that I wrote combined with the Coinbase Node module. But that's too much for a single blog post so I'll talk about that on Friday!

I also hope that more web sites adopt and expand on this model now that I have it working in a semi-legitimate application. In my opinion, it beats displaying ads for enabling content creators to monetize. You can help me monetize by paying to get the whole comic today!

Amphibian.com comic for 20 May 2015

Monday, May 18, 2015

Enabling Sound Effects on Web Pages

Got something to say?
I threw in a simple effect in today's comic where the computer is playing an acoustic guitar. Push the play, pause, and stop buttons above it, and you can control some real acoustic guitar music. What used to be a monumental task - putting sound effects in a web page - is now extremely simple.

It occurred to me that while I've mentioned using SoundManager2 several times in the blog, I haven't actually shown an example of its use since 2010! Things have changed since then, so it's worth bringing up again.

I like SoundManager2 because it's easy. I've been using it since before there was an HTML5 Audio specification, and have been very thankful for it. Abstraction solves all problems in computer science, and SoundManager2 provides a great abstraction on top of all the different ways to play sounds across different browsers on different types of devices. Sometimes it will use HTML5, sometimes it will revert to Flash (hopefully not often these days). But it just works.

When I made my game entry for the GitHub Game Off 2015, I used the Crafty JavaScript game framework. To play sounds across different platforms, I had to provide the sound effects in MP3, OGG, and WAV formats. It chose the correct one to play for whatever the client's capability happened to be. But back in 2013, my Game Off entry was coded from scratch and I used SoundManager2. I only had to provide sounds in MP3 format, which made my life easier.

Not that it's too difficult to convert sound formats with Audacity, but it was an extra step that I would have preferred to avoid. But I digress...

I've put sound effects in more than one of my comics recently and the pattern is always the same. First, simply include SoundManager's JavaScript file in your page. It comes with both debug and non-debug versions of the full-size and minimized versions. For production, I use soundmanager2-nodebug-jsmin.js.

<script src="soundmanager2-nodebug-jsmin.js"></script>

After that, playing sounds is extremely simple. Here is the complete guitar.js file from today's comic:

var guitarSound = null;

$(function() {

    soundManager.setup({
        url : '/swf/',
        onready : function() {
            guitarSound = soundManager.createSound({
                id : 'guitarSound',
                url : '/audio/guitar.mp3'
            });
        },
        ontimeout : function() {
            console.log("could not start soundmanager!");
        }
    });

    $('#play-button').click(function() {
        if (guitarSound) {
            if (guitarSound.playState === 0 || guitarSound.paused) {
                guitarSound.play();
            }
        }
    });

    $('#pause-button').click(function() {
        if (guitarSound) {
            guitarSound.togglePause();
        }
    });

    $('#stop-button').click(function() {
        if (guitarSound) {
            guitarSound.stop();
        }
    });

});

Since I also use jQuery, I do all the setup inside an anonymous function passed in to $(). This simply ensures that the code inside won't be called until the page is completely loaded and ready.

The call to soundmanager.setup on line 5 passes in an object describing the configuration of SoundManager. The url field is very important as it specifies where SoundManager can find the Flash files it needs if fallback to Flash becomes necessary. Those files are packaged in the SoundManager download file inside the /swf directory so I mimicked that path structure on my web site. The onready field is a function that will be called when SoundManager has completely initialized itself. This is where it is safe to create sounds which can be played later. In my case, I create one sound, guitarSound, from a file in my web application's /audio directlory named guitar.mp3. The ontimeout function, on line 13, is what gets called in the error condition that SoundManager could not start. It might happen if you try to use it on Netscape Navigator 4, or you just messed up your configuration.

The rest of the code just adds click listeners to the three types of buttons. I always check to see if my sound exists before doing anything with it, just in case SoundManager failed to start. The pause and stop functions are simple - just calling togglePause() and stop(). Toggle pause is different from the alternate pause() function in that it will also un-pause the sound if called when paused. The stop function should be self-explanatory. The handling of play is a little more complicated because SoundManager (on most modern platforms) allows you to play a sound multiple times and it will mix with itself. If you don't want that, check the playState before calling play() to ensure that it's not already playing. I also allow play() to be called if the sound is paused, which has the same effect as un-pausing.

It really is that easy. Sound can add an important dimension to human/computer interaction, and thanks to SoundManager2 it need not be excluded from the web-based user experience. Just don't make every button click play an annoying sound. Please.

Amphibian.com comic for 18 May 2015

Friday, May 15, 2015

Is Remote Work Remotely Possible?

I wrote today's comic because I was thinking about working remotely. It seems that many types of modern software developer jobs can be done by people sitting in their homes instead of in a cubicle. There are probably many other types of jobs that could be done remotely as well. Why does it still seem like the exception instead of the rule?

Is it distrust of workers that you can't watch?

Is it a desire to have a cohesive company culture?

Is it an honest belief that workers need an office environment to be productive?

Is it just tradition?

As usual, I don't have any answers. Just questions. I hope that most people don't work for a boss that distrusts them. And I really appreciate a desire to have a company culture (but I would argue that not many people know how to achieve it). I can also understand that some people focus better on their job when they are removed from the distractions in their home.

But I suspect that tradition plays a major part in it. Let's face it, the major enabler of working remotely, the Internet, is still very new. And high-speed Internet in the home is still just a dream in many parts of the United States. It's going to take a while before working from non-office locations becomes "normal" in the minds of business decision-makers.

And let's not forget that many jobs don't make sense for remote work (yet).
  • Plumbers
  • Automotive technicians
  • Barbers
  • Pastry chefs
  • Landscapers
  • Lumberjacks
I am working from home when I make these comics. But I'm not a good example of anything.

Amphibian.com comic for 15 May 2015

Wednesday, May 13, 2015

MIME Type is Important for REST

Today's comic makes fun of MIME (or Internet Media) types, but they can a very useful part of web applications that make use of REST.

MIME started out as a way to tell email clients what type of file you'd attached to a message, but have become much more useful. Every time your web browser makes a request for content, it sends along a header called "Accept" which is a list of data types it can process. Typically, this includes HTML, JavaScript, and images. The server, as part of its response, includes a header called "Content-Type" which specifies what is actually being returned. The two sides usually find some common ground.

When making REST web services, being a little more particular about your MIME types can open up new possibilities. REST stands for Representational State Transfer. When a human being is driving a web browser and asks for a resource (a.k.a. a web page or URL), they expect the server to transfer a representation of the current state of that resource. That representation is often HTML or an image because that's what humans can understand (well, assuming the browser turns the HTML into something pretty first). But the real power of REST web services is how it opens things up to the possibilities of machine-to-machine communication. Let's say a computer program requests the same resource as the human. The machine would have an easier time dealing with it if it could be in a different representation other than HTML, such as JSON. Two representations, one resource.

And that's where the MIME type comes in. When making a web service call, specifying exactly what you can deal with (your desired representation of that resource) lets the server know if it should give you HTML, an image, JSON, or something else. When serving requests, explicitly setting the content type of the response lets the client know if it should even try to process it.

To help visualize it, I've created a demo. The following Node/Express server has a single route, /pizza. Depending on what the client wants, it will return either a picture of a pizza, a JSON string describing a pizza, or an HTML snippet describing a pizza.

var express = require("express");
var fs = require("fs");
var app = express();

app.get("/pizza", function(req, res, next) {

    if (req.accepts("image/jpg") === "image/jpg") {

        fs.readFile("pizza.jpg", function(err, data) {

            if (err) {
                res.sendStatus(500).send(err);
            } else {
                res.setHeader("Content-Type", "image/jpg");
                res.send(data);
            }

        });

    } else if (req.accepts("application/json") === "application/json") {

        res.setHeader("Content-Type", "application/json");
        res.send({
            size: "large",
            toppings: ["pepperoni", "mushrooms"]
        });

    } else if (req.accepts("text/html") === "text/html") {

        res.setHeader("Content-Type", "text/html");
        res.send("<p>pizza with<ul><li>pepperoni</li><li>mushrooms</li></ul></p>");

    } else {
        res.sendStatus(406);
    }

});

//------------ static content
app.use(express.static("public"));

// ------------ start listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

To make this happen, the route has to check what the client can accept, which is sent as part of the "accept" header. Express gives us the method req.accepts() to check that header. Unfortunately, I found it a little awkward to use. You pass a string or an array of strings to req.accepts() and it returns either a string of the best match or undefined if there is no match. This is because browsers typically send a whole list of things as part of the accept header - after all, a browser can deal with HTML, plain text, JavaScript, and a variety of images to say the least. Express tries to help you by telling you what the browser prefers for each request if there is more than one option. That's why I did things like put the image check first (browsers accept image/* first when requesting the src of an img tag but also accept anything else!) and always check to see if the best choice exactly equals my test string.

Look at this jQuery client code:

$.ajax("/pizza", {
    headers: { Accept: "text/html" },
    success: function(data) {
        $("#d1").html(data);
    }
});

$.ajax("/pizza", {
    headers: { Accept: "application/json" },
    success: function(data) {

        var h = "<h3>got a " + data.size + " pizza with ";
        for (var i = 0; i < data.toppings.length; i++) {
            if (i > 0) h += " and ";
            h += data.toppings[i];
        }
        h += "</h3>";
        $("#d2").html(h);

    }
});

$.ajax("/pizza", {
    headers: { Accept: "application/xml" },
    success: function(data) {
        console.log("success!");
    },
    error: function() {
        console.log("error!");
    }
});

$("#d3").html("<img src=\"/pizza\" />");

Using jQuery to make Ajax calls to a single URL three different times, I specifically set the "accept" header to just one thing in each of the requests. The first request says it can accept only text/html, so the server gives me exactly that. The second request wants JSON, so the server responds accordingly and the JSON is turned into HTML on the client side. The third request wants XML, but the server doesn't support that and responds with a 406 - Not Acceptable. That response sounds backwards, since the server is telling the client that it doesn't have a way of giving it something that it can accept not that the server got something unacceptable from the client. Finally, an image tag is generated which specifies that same URL as the source and it does in fact get the JPG from the server.

Running that JavaScript code on this web page

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MIME Types</title>
</head>

<body>

  <div id="d1"></div>
  
  <div id="d2"></div>
  
  <div id="d3"></div>

</body>

<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

</html>

produces results that look like this (supply your own pizza image):



And that's how you can help usher in the future of machine-to-machine communications via the Internet. Make web services that serve machines as well as people. Build the Internet of Things. Now go get some REST.

Amphibian.com comic for 13 May 2015

Monday, May 11, 2015

Make Your Own QR Codes with Node

Friday's comic showed the frogs' first Bitcoin address as a QR Code framed and hanging on a tree, much like many stores have their first dollar framed. It was a real Bitcoin address, but sadly no one has sent any money to it yet.

I had a bit of a problem getting that QR Code into the comic. If you haven't been reading this blog for long, you may not know that the comics are made out of SVG images. That means I needed a SVG QR Code. No problem, right? There are a bunch of web sites that will generate QR Codes for you in SVG format (this one, this one, this one). Unfortunately, they create them a way which makes it difficult to embed inside another SVG - like my picture frame. I either ended up with nothing, or an incredibly huge file.

Why not try a different approach?

Next I used one of those free online tools to make a .PNG version of the QR Code. It was a very small file, and I could embed the image inside the SVG of the picture frame. Great! Unlike a "pure" SVG, it will pixilize a little when scaled but it's a bunch of blocks to begin with! No problem!

Unless I want it to show up on iOS devices. Bugger!

For whatever reason, the iOS browser doesn't display the image embedded in the SVG. I was, therefore, forced to return to the SVG approach. I had to do it with two separate files - one for the empty frame and one for the QR Code in it. Not exactly what I wanted, but it works.

There is some good that came out of this, however. In my research, I came upon a really nice Node module for generating your own QR Codes in PNG or SVG format. Why rely on some other website to do it for me? It's called simply qr-image, and it has no other package dependencies.

In just a few lines of code, I was able to add a feature to my website which enables me to make all the QR Codes I want. If you use the Express framework, as I do, you can make a simple app like this, just for QR Code generation:

var express = require("express");
var qr = require("qr-image");
var app = express();

app.get("/qrc", function(req, res, next) {

    var text = req.query.text;
    res.setHeader("Content-Type", "image/png");
    res.send(qr.imageSync(text, {type: "png"}));

});

// ------------ start listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

Launch that app, point your browser to

http://localhost:3000/qrc?text=some+text+here

and you should see a nice QR Code. Put anything you want as the value of the text parameter, such as URLs, Bitcoin addresses, secret codes, etc. If you don't want to type the URL out manually, set up a simple web page form that submits the data. Or create your own custom routes that encode data from another source. The possibilities are endless. Well, maybe not endless. But certainly very numerous.

In today's comic, the frogs are trying to find something else that is endless...

Amphibian.com comic for 11 May 2015

Friday, May 8, 2015

Editable Text on Your Web Pages with X-editable

Was He Whig? No, WYSIWYG!
Right behind pictures of frogs, speech balloons are the next most important part of my web comic. I've discussed previously how the balloons in my comic are really just cleverly styled <P> tags. For my WYSIWYG comic editor, I needed to display them just like they appear in the comics, but easily allow me to type new text into them.

Inline editing of text on a page can be a helpful addition to many types of web applications that rely on human-entered data. It's not just about speech balloons. If you need to use a feature like this as well, consider X-editable, the JavaScript/CSS library I selected for my comic editor.

X-editable is a great package to use when you need inline editing and your site uses jQuery. It gets even better if you also use jQuery UI and/or Bootstrap, but it can be a little confusing to set up properly. First of all, it offers multiple download packages depending on if you want to use it with just jQuery, jQueryUI, or two different version of Bootstrap. My comic uses jQuery and Bootstrap 3, so my examples are based off of that package.

Here is the HTML for a simple demo.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Editable Text</title>
  <link rel="stylesheet" href="css/bootstrap.min.css">
  <link rel="stylesheet" href="css/bootstrap-editable.css">
  <style>

    p {
        border: 1px solid #CCCCCC;
        margin: 10px;
        padding: 5px;
        width: 250px;
    }

    .editable-clear-x {
        background: url('clear.png');
    }

    .editableform-loading {
        background: url('loading.gif');  
    }

  </style>
</head>

<body>

  <p id="e12" class="canedit">This is some text you can edit.</p>

  <p id="e13">This is some text you can't edit.</p>

</body>

<script src="jquery-1.11.2.min.js"></script>
<script src="bootstrap.min.js"></script>
<script src="bootstrap-editable.min.js"></script>

</html>

For stylesheets you'll need Bootstrap (bootstrap.min.css in my example) and X-editable (bootstrap-editable.css). You'll also need the jQuery JavaScript, Bootstrap's JavaScript, and then finally X-editable's JavaScript.

By default, X-editable tries to find some images in the "../img/" path. That will only work if you keep your static images in a directory called "img" which is sibling to the "css" directory where you keep the X-editable CSS file. That wasn't an option for me, so I get around it by just copying the "clear.png" and "loading.gif" images to my preferred location and then overriding the CSS for those elements with my own background URL path.

In my example I have two sections of text, one that should be editable and other other not. To make the text editable inline, just execute the following JavaScript:

$(".canedit").editable({
    type: "text",
    mode: "inline",
    escape: false
});

It finds all the elements with the canedit class and makes them editable. The options object passed indicates that the type of editor will be a simple text box, using inline mode (a popup box is the alternative), and the contents will not be escaped. "Escaping" in this context means getting the value via jQuery's .text() vs. .html(). I choose not to escape so that I can put links and other HTML markup in the speech balloons. A full list of options can be found in the documentation. After executing that code, the editable text will be indicated by a blue dotted underline. Clicking on the text will replace it with a text box and some buttons. Type something new and hit enter or click the check and it turns back into plain text with the new value.

X-editable has a whole set of features that enable it to automatically POST updates to the text to a server as soon as the changes are made, but my comic editor uses a different model. I arrange all my images and text and then click on a "Save" button that reads through the DOM and builds a complete JSON representation of the comic. For speech balloons, I just do something like this:

var balloons = [];
$('p.editable').each(function(idx, elem) {

    var b = {
        // ...
        // other balloon data
        // ...
        text: $(elem).html()
    };
    balloons.push(b);

});

That finds all the editable text (in my comic editor, all <p> tags inside cells are editable), grabs the contents, creates a "balloon" object out of it, and adds it to the list of balloons.

If you're wondering what "other balloon data" I need to collect, consider that editable <p> tags can also be made draggable and resizable with jQuery UI (see my previous post where I did it with images) which enables me to easily position, size, and edit the frog speech balloons. So in addition to the actual contents of the balloons, I also read their top, left, and width CSS attributes just like I do for images.

There are lots more ways you can use X-editable in your applications. It supports built-in validation functions so you can check the input before accepting it. For example, execute this JavaScript on the demo page:

$(".canedit").editable({
    type: "text",
    mode: "inline",
    escape: false,
    validate: function(value) {
        if (value.match(/cat/gi)) {
            return "no talking about cats!";
        }
    }
});

Now you won't be able to enter "cat" in the text area. If you try it, you'll get a warning message and the edit won't complete. Your only options are to remove the cat or cancel the edit.

I mentioned above how X-editable has the capability to POST updates back to a server as soon as they are made. I don't use that capability at all, but it also lets you replace that behavior with your own custom handler. Instead of sending the data to a server via Ajax, you could send it via Websockets or update a client-side data model. To take advantage of this ability, specify a function instead of a string as the url parameter. The only catch is that the function has to return a Deferred object so the rest of the X-editable code can process it the same as a jQuery Ajax call. But you should like using Deferreds anyway, right? Check out this code:

$(".canedit").editable({
    type: "text",
    mode: "inline",
    escape: false,
    validate: function(value) {
        if (value.match(/cat/i)) {
            return "no talking about cats!";
        }
    },
    url: function(params) {
        var d = new $.Deferred;
        console.log("id of element changed: " + params.name);
        console.log("new value: " + params.value);
        setTimeout(function() {
            // to simulate some asynchronous processing
            d.resolve();
        }, 500);
        return d.promise();
    }
});

In your custom function, you can access the new value of the element in params.value. If the element that was edited had an id attribute, as my example does, it will be accessible in params.name. If you want to be able to tell your fields apart, you should probably make sure they all have ids! Finally, my example just sets a 500 millisecond timeout before resolving the Deferred, but if the action you need to take has no asynchronous component you could call d.resolve() immediately.

With all of these great tools, creating WYSIWYG editors for web content, even complex content like a comic, is easier than ever before. Now if only writing jokes for the comics was somehow made easier...

Amphibian.com comic for 8 May 2015

Wednesday, May 6, 2015

Both Draggable and Resizable Images with jQuery UI

Not a WYSI wig.
Probably the most important pieces of my WYSIWYG comic editor are the ability to drag the frogs around and pull on the corners to resize them. These were the first things I added to the editor, and of course where I encountered the first weird problems.

My idea was to just display the comic exactly like it is displayed to people viewing it, but make the images draggable and resizable using the features in jQuery UI. However, there's always a catch. It turns out that when you apply resizable to an image element directly, jQuery wraps it in a div. Then if you apply draggable to it, the container it drags inside is that wrapper div and you get strange behavior.

Try it yourself. Here's some code similar to my starting point. You will be able to resize the image but not drag it.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Draggable and Resizable</title>
  <link rel="stylesheet" href="jquery-ui-1.10.4.min.css">
</head>

<body>

  <img id="frogImg" style="width: 276; height: 281;" src="frog.svg">

</body>

<script src="jquery-1.11.2.min.js"></script>
<script src="jquery-ui-1.10.4.min.js"></script>
<script>

$(function() {

    $("#frogImg").draggable();
    $("#frogImg").resizable({aspectRatio: true});

});

</script>

</html>

The trick was to wrap the images in a <div> and set that wrapper div to be draggable instead of the image directly. The image still has resizable applied to it, but only after draggable is applied to the wrapper. Then it will work perfectly. That little snag cost me many hours, but it doesn't have to cost you! Look at the better example code below:

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Draggable and Resizable</title>
  <link rel="stylesheet" href="jquery-ui-1.10.4.min.css">
</head>

<body>

  <div id="wrapper">
    <img id="frogImg" style="width: 276; height: 281;" src="frog.svg">
  </div>

</body>

<script src="jquery-1.11.2.min.js"></script>
<script src="jquery-ui-1.10.4.min.js"></script>
<script>

$(function() {

    $("#wrapper").draggable();
    $("#frogImg").resizable({aspectRatio: true});

});

</script>

</html>

The flip-side of that was that I couldn't just grab the size and position of the images when it was time to save the data. I had to examine my wrapper divs and understand the structure that jQuery adds to them in order to figure out the coordinates used to re-create the comic later. I need to get the top and left CSS attributes as well as the width of the resized image. After closely examining the DOM after jQuery did its stuff, I came up with the following solution.

// Sometimes, the style for 'top' and 'left' comes back as 'auto'...
// In these cases, we should treat these values as 0.
function getCssPosition(elem) {

    var pos = {};

    pos.top = isNaN(elem.css('top').replace('px', '')) ? 0 : Number(elem.css('top').replace('px', ''));
    pos.left = isNaN(elem.css('left').replace('px', '')) ? 0 : Number(elem.css('left').replace('px', ''));

    return pos;

}

function getData() {

    $("div.divimg").each(function(idx, elem) {

        var iEl = $(elem);

        // get the position of my draggable wrapper div
        var draggablePos = getCssPosition($(elem));
        var draggableTop  = draggablePos.top;
        var draggableLeft = draggablePos.left;

        // get the position of jQuery's resizable wrapper div
        var wrapper = iEl.find("div.ui-wrapper");
        var wrapperPos = getCssPosition(wrapper);
        var wrapperTop = wrapperPos.top;
        var wrapperLeft = wrapperPos.left;

        var t = draggableTop + wrapperTop;
        var l = draggableLeft + wrapperLeft;

        console.log("top = " + t);
        console.log("left = " + l);

        var img = wrapper.find('img');
        var w = Number(img.css('width').replace('px', ''));

        console.log("width = " + w);

    });

}

The first function normalizes the top and left values. Depending on if the image was specifically positioned before being drug around (in my case, I almost always do this), the values for CSS top and left might be of the format "NNNpx" or just the word "auto". If it comes back as auto, that essentially means 0 because these are offset values from the parent element.

The getData function can use that to find the top and left values for both my draggable wrapper and the ui-wrapper that jQuery puts around the resizable image. By adding them together, I arrive at the top and left values to use directly on the image when the comic is displayed in static form on the main page. It is necessary to add the values together like this to compensate for how jQuery sets absolute and relative positioning on the two divs in the process of making them draggable and resizable.

Additionally, by grabbing the "width" attribute of the image itself from inside the ui-wrapper div, I'll know how wide the image should be as well.

This example has been simplified somewhat, but that's basically how I get the position and size data for every image that makes up a comic. The editor assembles them all into a JSON string and POSTs this data to the Node backend which stores it in a database. When the comic is viewed, that JSON is applied to a Jade template and it comes back out just like it looked in the editor!

Amphibian.com comic for 6 May 2015

Monday, May 4, 2015

The Secret of My Success - a WYSIWYG Comic Editor

One thing I haven't written much about is the editor that I use to make the comics. Sure, the framework that turns the JSON data into a comic is somewhat interesting, but it's mostly just some Jade templates and basic Express routes. The really complex part of the comic is the editor.

When I started out to make a comic, I knew that being able to publish consistently would be a big issue. How many web comics succeed when their publishing schedule is erratic? The key to success, in my opinion, was the ability to make comics quickly. That way I could keep up with a schedule of multiple new comics per week. Ideally, I should be able to throw together a comic in well under an hour. After many months of fine-tuning the editor, that's exactly what I can do.

The Comic Editor in action
This isn't the first time I've done something like this. Way back in the late 90's, before it was popular, I had the idea that web pages with dynamic content should be editable from a WYSIWYG editor that was either built right into the pages or was accessible from an administration page. This was not some high-minded exercise in human-computer-interaction, it was just my way of streamlining the production of web sites for clients. If I could give them a way to change their own page content by clicking on the page and typing new data, they wouldn't have to call me up every time they wanted to have a sale on potato chips (I did a chip factory site once. Really). I could focus on programming instead of fixing basic text, which freed up more time to make more complex web sites. It was a win-win.

Of course, back in those days the interface was a lot less refined. We didn't have all the nice HTML5 and JavaScript capabilities we have today. But it worked, and over the years I've created a number of different editors for different web applications. Most have been WYSIWYG or, as I like to call it, NearlyWYG (pretty close to WYSIWYG, but not exact). My web comic editor looks almost 100% identical to the way the comics look when published, and it's all HTML/JavaScript.

The comic editor gets refined over time as I use it and fight with it. When I find myself going into the JSON directly to tweak some content, I ask myself if I should add a feature (or fix a feature) in the editor instead. There are a lot of front-end JavaScript tricks I learned along the way, so I thought I'd take the next few blog posts to share some of them.

I made today's comic in about 45 minutes, including the time it took me in Inkscape to draw the cat. You're either saying to yourself, "Wow! Only 45 minutes!" or "Yeah? I would have guessed 10" depending on your opinion of my work...

Amphibian.com comic for 4 May 2015

Friday, May 1, 2015

More Automated Deployment

I'm still working on my automatic deployment system for the webcomic code. One thing that was left out of my first version was special handling for certain types of file updates.

For example, if I add a new package to package.json I'll need to run npm install after pulling the changes if I want the application to work. Fortunately, GitHub includes lists of files added, removed, and modified as part of the data POSTed to my listener.

If you want to see exactly what GitHub sends you from these Webhooks, just go to the management page for one of them. At the bottom, there's a list of all the POSTs they've sent (or attempted to send, in case some of them have failed). Click on the ids to expand the details and you'll see the JSON. I just copied and pasted it into JSON Editor Online so that I could easily examine it.

GitHub's Webhook transmission history - check it out!

If you look at them, you'll notice that the "head_commit" field has three fields named "added", "removed", and "modified." As you might expect, these fields are arrays of file names that were added, removed, or modified. I made a simple function to search through these arrays and look for package.json. If I find it, I know that I'll have to run the npm install command after the pull is complete.

function checkMods(listOfLists) {

    var ret = {
        runNpm: false,
        restartApp: false
    };

    for (var j = 0; j < listOfLists.length; j++) {
        var fileList = listOfLists[j];
        for (var i = 0; i < fileList.length; i++) {
            if (fileList[i] === "package.json") {
                ret.runNpm = true;
            }
        }
    }

    return ret;

}

My function returns an object that informs the caller if npm should be run and also includes a flag for telling the caller if the app should be restarted as well. I'm thinking ahead but haven't implemented that check quite yet.

I modified the "push" listener from earlier in the week to look like this now:

github.on("push", function(repo, ref, data) {

    var branchName = ref.substring(ref.lastIndexOf("/")+1);
    if (branchName === "master") {

        var repoName = data.repository.name;
        if (repoName === "comic") {

            var checks = checkMods([data.head_commit.added,
                                    data.head_commit.removed,
                                    data.head_commit.modified]);

            var toRun = "/home/comic/update_comic.sh";
            if (checks.runNpm) {
                toRun += " npm";
            }

            child = exec(toRun, function(error, stdout, stderr) {

                if (error !== null) {

                    var e = new Error("exec error: " + error);
                    console.log(e);

                } else {

                    console.log("pulled update for " + repoName);
                    if (checks.runNpm) {
                        console.log("...and installed new packages");
                    }

                }

            });

        }

    }

});

The only real change is the call to checkMods on line 9 that passes in an array of arrays, and the subsequent conditional addition of the "npm" flag on the Bash script that gets called. Obviously, that flag indicates to the script that npm install should be executed after git pull origin master.

Now I just need to get to work on that conditional restart part. In theory, I just have to look for changes to server-side .js files (the .js files not under public) and then flag the app for a restart. The biggest issue for me is my lack of Bash scripting skills. But I'll figure it out.

Soon, I'll have even more autonomous electronic robot mackerels in my sea.

If you're confused by that last statement, see today's comic:

Amphibian.com comic for 1 May 2015