Naughty-Nice ThumbScanner (FIXED) (phone app)
Summary
For this assignment (Adobe Gen Pro App Design course, Jun 2015, Class 3), we were to create a fake “thumb scanner” app in Brackets and PhoneGap following the tutorial by Mark Shufflebottom (https://vimeo.com/126302628). This exercise would teach us how to perform basic manipulations on the DOM via JavaScript (and some CSS) for an app running on a mobile device.
 
I originally wanted to do only the bare minimum for this assignment, since the end-of-class deadline for all homework is rapidly approaching. However, the tutorial app has some issues that I wanted to fix. After some research, I came up with solutions and added minor improvements.
 
(Notes: This post provides only partial code for this project. I assume the reader will view the tutorial and then understand the changes I have made. Also, Mark S. provided the thumbprint image.)
Buggy App
The (Buggy) Tutorial App
The tutorial app “scans” a thumbprint and checks (i.e., in Santa’s database) whether that person has been naughty or nice. The app does not actually scan anything, nor does it check any database, but it gives the appearance of doing so.
 
More specifically, the app displays an image of a thumbprint and invites the user to press it. When the user touches the image, the app informs the user that it is scanning. After a short time, the app replaces that message with one that appears to verify whether the user has been naughty or nice. That second message fades out and the “scanner” resets for another detection.
 
The “scan” happens via a JavaScript touchstart event, which binds onto the thumbprint image. The user touches the image and triggers the event, which fades in a message that the app is “scanning.” The app then randomly selects “0” (naughty) or “1” (nice) and replaces the “scanning” message with one that describes the results of the “scan,” e.g., “Congratulations, you have been nice!”
 
Each message resides in its own hidden <div> and displays only if the logic brings it out. A series of timers keeps each message on the screen for only a couple of seconds, after which it fades away. Each press of the thumbprint triggers a new process.
 
Therein lies the primary “bug”: The code does not prevent the user from pressing the thumbprint in the middle of a “scan” and thereby launching overlapping “scans.” If the user taps the image quickly several times in a row, the app may well display both “naughty” and “nice” messages at the same time.
 
Other issues include:
- The thumbprint image is static and gives no appearance of scanning anything.
- The messages are all in black and white and can be hard to distinguish.
Improved App
Fix 1: Lock the Scan
I wanted to allow the user to start a “scan,” but only after all messages from any previous scan have come and gone. The simplest solution seemed to be to deactivate the thumbprint image immediately after a scan begins and then reactivate it only after all related processing completes.
 
I found numerous posts on similar issues. Some speak of binding touchstart and then unbinding it. Some offer complex code that enables and disables an event. Others suggest using .on and .off to control whether an event handler is active.
 
The tutorial uses .bind, and so at first I built my code to .bind and .unbind. However, one site notes that jQuery 1.7+ prefers .on/.off and deprecates .bind/.unbind. In this project, both solutions seem to work equally well. My final project uses .on/.off.
 
The tutorial code first binds touchstart to the thumbprint image and then attaches an unnamed event handler. Any tap on the image triggers the event handler code, which displays the “scanning” message, waits 2s, and then jumps out of the event handler to continue processing. The “bug” lies here, because the scan-and-wait code resides inside the unnamed event handler, creating a delay of at least 2s wherein the app cannot unbind the event.
 
My solution moves the scan-and-wait code out of the event handler to its own function, scanning. My touchstart event handler therefore has very little code: Bind the event, and when the event triggers, get out. This opens up possibilities, such as being able to unbind the event immediately after a tap.
 
I set up the overall code to act like an indefinite loop controlled by a sentinel value. Procedures that use such loops follow a few “standard” steps:
 
1) Get a user response before entering the loop.
 
2) If the user response warrants it, enter the loop and run some business logic.
 
3) End the loop with the same code that prefaced it, i.e., get a user response. This means that you write the same code twice: once before the loop starts, and once at the end of the loop.
 
The touchstart event handler prefaces the loop. If the user presses the thumbprint image, the app immediately jumps to the scanning function, which begins the loop. This function immediately unbinds the event. This and other functions then process the business logic. The last stage of the loop repeats and rebinds the touchstart event handler. Therefore, as soon as the user taps the image, the image becomes inactive and remains so until all messages have come and gone. No more overlapping taps.
 
(See the index.js code example at the end of this post for specific details.)
Photoshop animated GIF layers and partial results
Animated thumbprint
(repeat-forever version; project version runs only once per refresh)
Fix 2: Animate the Thumbprint
I wanted a “scanner” that at least appears to work. The best solution seemed to be to animate the thumbprint when the user taps it. I investigated several ways to do this.
 
jQuery solutions include slideDown (animates the height of an element) and animate (can animate nearly any element property). However, slideDown squishes the height of the element and then stretches it to its full value, making the element look like an unwinding spring; the thumbprint image looked silly in this context. You could animate top, rather than height, but you would have to apply this to an opaque <div> that overlays the thumbprint image and slowly “uncovers” it by moving down the page, and this could inadvertently also move elements below it.
 
Straight CSS solutions would be similar to animate and therefore difficult to implement well.
 
A better solution: An animated GIF that fires only once per refresh. I created such an animation in Photoshop using a Video Timeline (rather than a Frame Timeline). On GIF refresh:
 
1) A thick green border surrounds the thumbprint image, as if the user “activates” the scan area.
 
2) A light green box fills the scan area and slowly descends, revealing the thumbprint, as if the app is actually scanning the print.
 
3) Several green dots across the thumbprint fade in and out, vaguely similar to Abby’s facial recognition scanner on NCIS.
 
As the animation closes out, the box, background, and dots all fade away, leaving only the thumbprint. This is, of course, the same thumbprint as before, and the user would certainly realize that the scanner did not actually capture his or her thumbprint. One could make this more realistic by having the jQuery code randomly select one of several animated GIF thumbprints.
 
Creating the animated “scan” was the easy part. Triggering it on each tap was much more challenging.
 
The problem: To restart a one-pass animated GIF, you must refresh it. Mobile devices in general don’t like to refresh the same image; they just pull it from the cache.
 
After a bit of research, I found a solution: Set the image src to nothing and then immediately back to the animated GIF, to trick the browser into thinking that it is loading a new image. This gave an additional benefit: I could set the initial image (the image src in index.html) to the plain thumbprint image and then let jQuery take over and change the src as needed.
 
The result: The app starts with the plain image (it would look silly to start with a “scan,” since the user has not yet pressed anything); the animated GIF “scan” image displays thereafter and restarts on each tap. Problems solved.
Fix 3: Color the Messages
I wanted the messages to stand out by color as well as by content. I also wanted to clean up the message grammar and readability and add a third random message.
 
To add color, I created color classes in index.html and assigned these to the respective message <div>s.
 
To help make the messages more readable, I added <br> and <strong> tags.
 
To add a third random message, I expanded the tutorial’s if...else structure to if...else if...else.
Index.html (partial, with comments)
<head>
  <style>
    ...
        p, h2 {
            text-align: center; /* Center all paragraphs and headers */
        }
           
        .hidden {
            display: none;
        }
 
        .green {
            color: green;
        }
 
        .red {
            color: red;
        }
 
        .blue {
            color: blue;
        }
    </style>
</head>
 
<body>
  <div data-role="page">
      <div data-role="header" data-theme="b">
          <h1>Santa's Database</h1>
      </div><!-- /header -->
 
      <div role="main" class="ui-content">
          <div id="cent">
              <h2>Place your thumb to scan</h2>
              <p><img id="thumb" src="img/thumbprint.png" width="108"></p>
          </div>
               
          <div id="scanning" class="ui-body ui-body-a ui-corner-all hidden">
              <p>---Scanning your biological data---<br>
              <strong>Please wait...</strong></p>
          </div>
 
          <div id="nice" class="ui-body ui-body-a ui-corner-all hidden green">
              <p><strong>Congratulations!</strong><br>You have been good all year!</p>
          </div>
 
          <div id="naughty" class="ui-body ui-body-a ui-corner-all hidden red">
              <p>Our records show a deficit of good deeds so far this year.
                  Fortunately, you still have time to correct this.<br>
                  <strong>Please brighten someone's day!</strong></p>
          </div>
 
          <div id="overloaded" class="ui-body ui-body-a ui-corner-all hidden blue">
              <p>Our systems are overloaded at this time.<br>
                  <strong>Please try again later!</strong></p>
          </div>
      </div><!-- /content -->
  </div><!-- /page -->
  ...
Index.js (partial, with comments)
...
// [Update DOM on a Received Event]
receivedEvent: function(id) {
 
    // [Add our App behavior here]
    // Logic:
    //   I) Bind touchstart function
    //      - THIS ACTS AS THE PREFACE TO A USER-CONTROLLED LOOP
    //      1) When phone indicates ready, bind "touchstart" event to #thumb
    //      2) When user first touches the image, run initial setup steps:
    //         a) Prevent scrolling and mouse events via e.preventDefault()
    //         b) Immediately run "scanning" function
       
    //   II) "scanning" function
    //      - THIS STARTS THE LOOP BODY
    //      1) Unbind touchstart so that the user cannot re-press the image
    //         - Necessary to prevent overlapping naughty or nice messages
    //         - This app does not scroll, so unbinding touchstart is OK
    //      2) Change the thumbprint img to nothing, then to an animated GIF
    //         - Allows reload of animated GIF every time user restarts "scan"
    //         - Original startup image is static thumbprint
    //         - After user first presses img, changes to animated GIF thereafter
    //      3) Slowly fade in hidden "scanning" div
    //      4) After 2s, run "decide" function
    //         - Per http://api.jquery.com/slideDown/, 'slow' = 600ms
    //         - My animated GIF lasts 1.2s
    //         - Total "scanning" time = 2.6s, so combo w/A-GIF looks realistic
       
    //   III) "decide" function
    //      - THIS CONTINUES THE LOOP BODY
    //      1) Quickly fade out "scanning" div
    //      2) Generate random 0, 1, or 2 to determine naughty, nice, or waiting
    //      3) Slowly fade in respective message div
    //         - Unbinding from previous step prevents multiple msgs at one time
    //      4) After 3-5s, run "remove" function
 
    //   IV) "remove" function
    //      - THIS CONTINUES THE LOOP BODY
    //      1) Remove all result msg divs from screen
    //      2) After 1s, run "restart" function
    //         - Makes user waits 1s before being able to try again
    //         - Helps prevent multiple msgs
       
    //   V) "restart" function
    //      - THIS ENDS THE LOOP BODY AND RETURNS TO THE LOOP START
    //      - Repeats the same code as in preface to the loop
    //        * Typical behavior for this type of loop
    //        * Preface, Body, Repeat Preface code AT END, Return to Body
 
    // To bind or not to bind:
    //   Default tutorial uses .bind
    //   See: http://stackoverflow.com/questions/15884772/remove-touch-event-handler-on-element-using-jquery-off
    //     Use .on and .off and a "disabled" class to control whether an EH is active'
    //     .on and .off are preferred over .bind and .unbind, as of jQuery 1.7
    //     .unbind() removes ALL event handlers attached to the respective element(s)
    //   See: http://api.jquery.com/off/
    //     Use .on and .off to control whether an EH is active
    //   NOTE: both .bind/.unbind and .on/.off work equally well in this project
       
    $('#thumb').on('touchstart', function(e){
        // Set preventDefault()
        //   See: https://developer.mozilla.org/en-US/docs/Web/API/event/preventDefault
        //     Explanation of how Event.preventDefault() works, in general
           
        //   See: http://www.quora.com/Whats-the-default-behaviour-of-the-touch-e-g-touchstart-event-on-touch-screen-devices
        //   See: https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
        //   See: http://blog.mobiscroll.com/working-with-touch-events/
        //     touchstart preventDefault() prevents:
        //       1. Firing of emulated mouse events
        //       2. Native page scroll (scrolling of screen when user touches it))
        e.preventDefault();
 
        //console.log('tap');           // Log every tap in PG Desktop server log
           
        // Move the next two lines into own function
        //   Makes it possible to create a user-triggered loop (see overview above)
        //$('#scanning').fadeIn('slow'); // Slowly fade in hidden "scanning" message
        //setTimeout(decide, 2000);      // After 2s, run "decide" function
 
        // Info on running functions:
        //   See: http://www.w3schools.com/js/js_function_invocation.asp
        scanning();                      // Immediately run "scanning" function
    });
 
    function scanning() {
        $('#thumb').off('touchstart'); // User cannot re-press the img
           
        // "slideDown" animates img H, IF you specify the img W in index.html
        //   See: http://api.jquery.com/slideDown/
        //     Default 'fast' = 200ms, 'slow' = 600ms
        //   If don't specify W, "slideDown" animates BOTH H AND W
        //     Looks like it is blowing up a balloon
        //   With thumbprint img, "correct" slideDown looks unrealistic
        //     Squishes the img vertically at first by making H = 0 but W "normal"
        //$('#thumb').slideDown(1000);
 
        // Could also do custom animations
        //   See: http://code.tutsplus.com/tutorials/jquery-animations-a-7-step-program--net-8426
        //   However, this still won't look like a "real" scan
           
        // Refreshing an animated GIF works better than "slideDown" in this case
        //   See: http://stackoverflow.com/questions/3191922/restart-an-animated-gif-from-javascript-without-reloading-the-image?rq=1
        //   Set src to nothing, then to animated GIF, to re-trigger its behavior
        $('#thumb').attr('src', '');
        $('#thumb').attr('src', 'img/thumbprint.gif');
 
        $('#scanning').fadeIn('slow'); // Slowly fade in hidden "scanning" message
        setTimeout(decide, 2000);      // After 2s, run "decide" function
    }
       
    function decide() {
        // Quickly fade out "scanning" msg, then run random generator
        $('#scanning').fadeOut('fast', function(e) {
            var decide = Math.floor(Math.random() * 3); // Gen random # {0, 1, 2}
            if (decide==0) {                   // If 0, slowly fade in "naughty" div
                $('#naughty').fadeIn('slow');
                setTimeout(remove, 5000);      // After 5s, run "remove" function
            }
            else if (decide==1) {              // If 1, fade in "nice" div
                $('#nice').fadeIn('slow');
                setTimeout(remove, 3000);      // After 3s, run "remove" function
            }
            else {                             // Otherwise, fade in "overloaded" div
                $('#overloaded').fadeIn('slow');
                setTimeout(remove, 4000);      // After 4s, run "remove" function
            }
        });
    }
       
    function remove() {
        $('#naughty').fadeOut('fast');     // Remove any instance of "naughty" div
        $('#nice').fadeOut('fast');        // Remove any instance of "nice" div
        $('#overloaded').fadeOut('fast');  // Remove any instance of "overloaded" div
        setTimeout(restart, 1000);         // Make user wait 1s before try again
    }
       
    function restart() {
        $('#thumb').on('touchstart', function(e){
            e.preventDefault();            // Rurun same code as at first
            scanning();
        });
    }
       
    console.log('Received Event: ' + id);  // Log event in PG Desktop server log
}
Naughty-Nice ThumbScanner (FIXED) (phone app)
5
300
0
Published:

Naughty-Nice ThumbScanner (FIXED) (phone app)

Our instructors provided a tutorial on creating a fake thumb scanner app that “detects” and informs whether you have been naughty or nice this ye Read More
5
300
0
Published:

Tools

Creative Fields