Archives for category: Web Development

I started playing with AppCache today after working at better offline support for LunchTable. Though information is well-dispersed on the topic (especially when it comes to using Google Fonts), I eventually sorted it.

Step 1. Add a cache manifest file

For more details, MDN is awesome.

My landing page contains <html manifest="manifest.appcache">, and manifest.appcache contains (abridged):

CACHE MANIFEST
# v1
NETWORK:
*

CACHE:
css/main.css
fonts/fontawesome-webfont.eot?v=4.0.3
fonts/fontawesome-webfont.eot?#iefix&v=4.0.3
fonts/fontawesome-webfont.woff?v=4.0.3
fonts/fontawesome-webfont.ttf?v=4.0.3
fonts/fontawesome-webfont.svg?v=4.0.3
js/myjs.js

The * under NETWORK: ensures that everything we don’t explicitly set here isn’t cached, because the next time we refresh it actually won’t load (see?).

Also note that we include query strings in the manifest; the CSS file references them this way, so we ensure they match here, because the browser cares.

Step 1.1. Serve the manifest file correctly

Your .appcache file should be served with the text/cache-manifest MIME type. On Apache, I added this to my universal server configuration:

AddType text/cache-manifest .appcache

Step 2. Host all previously remote files

Browsers also seem to care about caching files only from the application domain. I stopped using the FontAwesome CDN without a problem, but Google Fonts provided another hurdle.

Step 2.1. Host your Google Fonts

Based on some comments on this 2010 post, I found a simple script to download the font you want, and added a user agent for iPhone, though it doesn’t seem to do anything different right now (I was hoping to get SVGs), and needs testing.

family='Noto+Sans'; for url in $( { for agent in 'Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0' 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1' 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)' 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_4 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25' ; do curl -A "$agent" -s "http://fonts.googleapis.com/css?family=$family" | grep -oiE 'http://[a-z0-9/._-]+'; done } | sort -u ) ; do extn=${url##*.} ; file=$(echo "$family"| tr +[:upper:] _[:lower:]); echo $url $file.$extn; curl -s "$url" -o "$file.$extn"; done

Simply replace Noto+Sans with the font you want, and add your newly downloaded fonts with something like this CSS in place of your old one-liner:

@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: normal;
    src: url('/fonts/noto_sans.eot');
    src: url('/fonts/noto_sans.eot?#iefix') format('embedded-opentype'),
         url('/fonts/noto_sans.woff') format('woff'),
         url('/fonts/noto_sans.ttf') format('truetype');
}

Step 3. Test

To see what resources you’re caching on your site (as well as others), visit:

Chrome: chrome://appcache-internals
Firefox: about:cache?device=offline

Step 4. Read more

This is just the start, and there are a lot of additional concerns this kind of caching can add to your web application. But hopefully this will get you started. Besides LunchTable, forecast.io is another great app that uses this (look how fast it loads the second time!). $ logout

Advertisements

I very recently had a conversation with a client about implementing email notifications for his online community built with my software. There was, of course, an urge in him to notify users every time the equivalent of a thread is created or responded to. There was also the need for an email to go out as soon as a personal message was received.

For the latter, I was on board; but for the former, I had to make a point– no matter how subjective. My point was that I will unsubscribe from any site’s mailing list the minute I get an email with things I don’t care about. Emails are on the level with text messages for me, arriving on my phone with an envelope notification, pining to pull me away from reality the minute it arrives. And it’s not that my time is money, but because I loathe spending time reading random minutia that I hate your everyday “engaging” emails (especially when they arrive every day).

Needless to say, I made the point that when designing a product and how it will send out notification emails, it’s important to remember:

  • You’re not the only site that any given user is a member of.
  • People have vastly varying degrees of tolerance for emails meant only for getting them to your site again.
  • Don’t automatically opt users in. Unless your software is going to take me out to dinner and learn my quirks first, don’t assume you know how I like my email notifications. Instead, give me the choice and demonstrate why your notifications will be useful to me.

On a related note, LunchTable just got email notifications today, unsent until you subscribe.

$ logout

Recently LunchTable received an influx of spam registrations: marked by similar-looking nonsensical usernames, “real names” that didn’t match their gender, and email addresses on adult sites. I didn’t want to use CAPTCHA to mitigate this, because I personally die a little inside at each CAPTCHA encounter, so I took another route.

With a little help from StackOverflow (can’t find the link now, will update), I found a clever solution that banks on spam scripts not executing Javascript. Less than 12 hours of being live captured and prevented two spam registrations. I start by adding a hidden input field to my registration form and filling it with default text:

<input type="hidden" id="robot" name="beep-beep-boop" value="iamspam" />

Next, I add a little Javascript to change this value to a numeric value, and subsequently increment it every second (this will come in handy later):

var botZapper = function() {
    if (document.getElementById("robot")) {
        a = document.getElementById("robot");
        if (isNaN(a.value) == true) {
            a.value = 0;
        } else {
            a.value = parseInt(a.value) + 1;
        }
    }
    setTimeout(botZapper, 1000);
}
botZapper();

Finally, on the server side, I do a simply check (in PHP):

$botTest = $_POST['beep-beep-boop'];
if ( $botTest == "iamspam" || !is_numeric($botTest) || $botTest < 10) {
    // This appears to be spam!
    header("Location: /");
    exit;
}
// ...database INSERT code untouched by bots...

This checks if any of the conditions are true, indicating a bot:

  • First, if the value hasn’t changed; meaning the user didn’t have Javascript enabled.
  • If the submitted value is other textual information we didn’t expect.
  • If JS was enabled, but the form was submitted within 10 seconds.

If these tests fail, I mercilessly redirect the bot to the home page with no “fail” message! My scorn is certainly felt.

Your time threshold will definitely vary depending on the length of your form, and you will need to accommodate users without Javascript enabled at all. However, at the time of this sentence’s writing, I’ve captured four attempted spammings from China, all who never updated the hidden input field (failing at the first test).

$ logout

My last few days have been spent on a lovely security feature of Internet Explorer, which manifests as an ambiguous “SCRIPT5: Access is denied” message. After some digging, and with some lucky previous experience, I figured out how to get around this.

Cause?

IE sees you take some jQuery (or plain javascript) and do something like $('input:file').click(); and starts to get suspicious. But on the FreshAiR Editor we did this with no nefarious intent; we needed to check some things before file selection and get ready to crop the user’s image once they selected one. When we later called $('form').submit(); everything would load into a same-domain iframe to be ready to send, and then… Access is denied.

Workaround time.

How to solve this? There are Adobe Flash and myriad other uploaders, but I remember implementing an open-source AJAX photo uploader-with-progress-bar in LunchTable a while ago, called qqFileUploader (now, it appears, called Fine Uploader). It was pure HTML/JavaScript/CSS, so I took a peek at how it was done, and came up with this result:

  • Using input buttons with this structure (optionally substituting “Upload Here” for an image):
<div class="button">
    Upload Here
    <input type="file" name="media" />
</div>
  • And styling the transparent file input on top of the text / image to make it clickable:
.button {
    position: relative;
    overflow: hidden;
}
.button input[type=file] {
    position: absolute;
    right: 0px;
    top: 0px;
    font-size: 118px;
    margin: 0px;
    padding: 0px;
    opacity: 0;
    z-index: 1; /* In case you use an img for input button */
}
/* For effect emphasis... */
.button:hover {
    background-color: blue;
    color: white;
}

You have a customizable, clickable file input that won’t upset Internet Explorer. See the full result on jsFiddle.

$ logout

If you’re going the route of a vertical background gradient on your site (using a tool like the Ultimate CSS Gradient Generator (seriously, it’s great)), you might notice it does in fact fill your entire body, but not the entire window like you’re used to with solid colors. Simple fix:

html {
    min-height: 100%;
}

Just that like that: beautiful browser-based gradients.

I’ve made a lot of from-scratch websites in the years Chrome has been around (and before that, Firefox). In the old days—around IE 7 or so—I remember hoping to support old versions of Internet Explorer. I would test thoroughly and spend hours on workarounds for the archaic browsers. But not anymore.

With Chrome and Firefox on a quick update schedule, it’s obvious that they are doing web browsers right: quick iterations of browsers that keep up with the web that they provide the lens to. Not faux-modern browsers.

So I no longer address older browsers in the websites I create with anything more than a message telling users to upgrade. One site I’ve made, LunchTable, will simply let you know your browser is out of date before you can log in if you’re using IE (made easy through conditional statements), since only IE 9 has some CSS3 features I use, and IE 10 finally supports WebSockets for chat. Other browsers get a lazier approach that works well: feature detection. Before a user can chat, I check if WebSockets are supported— no need for me to research and test exhaustively, or update the tests when new browser versions come out.

The company I work for has a web editor that utilizes a ton of browser APIs (like FileReader) to create a seamless “editor” that works entirely on the client-side. While Opera happens to break for mysterious reasons and IE apparently doesn’t know every CSS2 or 3 selector, every other browser can check for the needed functionality and alert the user about certain functions that just won’t work with their current browser. This allows, for example, Safari 5.1 users to still log in and use most features of the application, but when they can’t upload images, we expect they won’t be surprised.

This approach puts more of a burden on the end user, which my old CS professor used to argue vehemently against (that the developer should be trimming input data and formatting it correctly, for example). But it is only the burden of keeping your software up to date— a responsibility of any computer user. And in the case of IE, this approach encourages users to exercise their freedom of choice in the web browser market. Truly, a win for everyone.

On the last from-scratch CMS I built (for Radford University’s EMS), I utilized jWYSIWYG for the content editor. It was easy to implement, but had a few bugs (including with the content manager). Before that, I used TinyMCE and its MCFileManager to power Dean J. Baer‘s site, which both worked great, but the file manager required licensing fees. Currently I’m looking at these open source options:

The deciding factor here will mostly be how easy it is to add more robust media features.

I like the idea of on-the-page, limited section editing that Mercury provides, though I’m not a fan of the OS X aesthetic. And I also like what looks to be a simple, clean interface and backend for CLEditor. But this is all conjecture right now, so it’s time to dig in.

$ logout