OdeToCode IC Logo

Deferred and Promises (BYOSP Part 8)

Wednesday, February 29, 2012 by K. Scott Allen

When I started thinking about adding animations to the slide presenter I pictured a few different types of animations – simple animations to make items appear one by one, but also animations to pan, zoom, and rotate a slide (like Prezi, which is Flash based, but really more like Impress.js, which is all script and CSS).

I wanted animations to be decoupled and extensible, yet still give the slide presenter control over the animations. When an animation like "appear one by one" is in effect, then a "move forward" operation shouldn't go to the next slide, but should step the animation forward to make an item appear. Once all the items are on the screen then "move forward" can advance the slide. A "move back" should cancel any running animations and go to the previous slide.

unit test warning bellIt's funny – as I was thinking of these features I thought about eventing with jQuery trigger and completion signals with jQuery promises. Then I thought I better keep it simple and plowed ahead using function callbacks for everything.

My unit tests told me the design wasn't working. When small changes make tests break and feel brittle, it is a general indication of trouble – like a warning bell.

With YAGNI disproved I was back to promises and triggers. At the heart of the matter is a completionSignal. All the animation modules rely on completionSignal to broadcast when the animation is finished, and tell them if the animation needs to stop early (the presenter triggers the killAnimations event if the slides need to jump backwards, to the home, or to the end).

var completionSignal = function () {

    var deferred = $.Deferred();

    var complete = function () {
        $(window).unbind("killAnimations", complete);
        deferred.resolve();
    };

    $(window).bind("killAnimations", complete);

    return {
        resolve: complete,
        promise: deferred.promise()        
    };
};

 

The beauty of a deferred object, which typically represents an asynchronous operations,  is how other components can register zero or more functions into callback queues via the object's promise. When the deferred object is resolved (successful completion) or rejected (failed), the object invokes the proper callbacks. We'll see an example in just a bit, but for more, see "Creating Responsive Applications Using jQuery Deferred and Promises". For custom events and trigger see  "Event Aggregation with jQuery".

Simple Animation

To see how animation works in the slide presenter, here is a simple animation that will pop up an alert box for the current slide before allowing the presentation to move to the next slide. Animations are added using data- attributes.

<section>
    <h1>This is the title</h1>
    <ul>
        <li data-animation="alert">Show this in a popup</li>
        <li>More text</li>
    </ul>
</section>

The slide presenter finds the data-animation attributes, then looks up a module to call using the value of the attribute. Multiple animations modules can register with the slide show software.

p5.registerAnimations({
    "onebyone": onebyone,
    "timedAppear": timedAppear,
    "zoom": zoom,
    "addclass": addclass,
    "alert": annoying
});

The "alert" animation is aliased from a module named "annoying" (because any kind of modal dialog is annoying). The annoying module uses a signalCompletion to help with lifetime events.

var annoying = function (element) {
    var signal = completionSignal();

    return {
        step: function () {
            alert(element.text());
            signal.resolve();
        },
        done: signal.promise
    };
};

The public API exposed from any animation requires a step method and done property. The slide show software is responsible for invoking the step method when the slide show tries to move forward. The animation is responsible for resolving the "done" promise when the animation is complete. The animation can also use the signal to register cleanup code (none needed in this example). The presenter software uses the promise with jQuery's $.when to remove completed animations from it's call queue.

$.when(animation.done)
 .then(function () {
     currentAnimations.remove(animation);
 });

 

Does This Painfully Boring Series Ever End?

I'll try to wrap it up next week by looking at only two more pieces: formatting code in a slide, and rotating, panning, and zooming with CSS 3 transforms.

For all the code, see github.

Primitive JavaScript (BYOSP Part 7)

Tuesday, February 28, 2012 by K. Scott Allen

When starting into the animations I decided it was time to refactor. One problem was the little "updateState" calls floating around inside conditional logic – always a bad sign. A second problem was the sheer number of strings and CSS selectors in the code.

I've become more sensitive to primitive obsession in JavaScript. Some code I see is nothing but strings and numbers. I think the obsession hurts readability and maintainability.

The selectors scattered in the slide presenter code do one of three things: get all slides, get the next slide, and get the previous slide. It would be nice to hide all of these behind tiny function abstractions.

var slides = function () {
    return $("section");
};

var nextSlide = function () {
    return $("section.current").next("section");
};

var previousSlide = function () {
    return $("section.current").prev("section");
};

Those functions make the logic for moving around much easier to read.

var moveForward = function () {
    setCurrentSlide(nextSlide());
};

var moveBackward = function () {
    setCurrentSlide(previousSlide());
};

var moveFirst = function () {
    setCurrentSlide(slides().first());
};

var moveLast = function () {
    setCurrentSlide(slides().last());
};

As a bonus, all of the book keeping logic for setting the current slide moves into a single location.

var setCurrentSlide = function (slide) {
    if (slide.length > 0) {
        slides().removeClass("current");
        slide.addClass("current");
        updateHistory(slide);
    }
};

Having a central location to set the currently selected slides is important for the upcoming animation logic.

For all the code, see github.

Plain Old JavaScript

Monday, February 27, 2012 by K. Scott Allen

Once upon a time there was a pure JavaScript model. The model sang songs about simplicity in a land full of complexity and confusion. The model's clear and beautiful voice comforted many hearts in the land.

var protagonist = {

    firstName: "Priscilla",
    lastName: "Flannery",

    todos: [
        { description: "Kiss frogs", done: false },
        { description: "Slay dragons", done: false }
    ],

    addTodo: function (description) {
        this.todos.push({
            description: description, 
            done: false
        });
    }
};

One day framework dragons descended on the land, enslaving inhabitants and ravaging models. The model songs, once all keyed in F major, became never-ending vamps over B flat augmented arpeggios.

var protagonist = {

    firstName: ko.observable("Priscilla"),
    lastName: ko.observable("Flannery"),

    todos: ko.observableArray([
        { description: "Kiss frog", done: false },
        { description: "Slay dragons", done: false }
    ]),

    // ...
};

The models exchanged melody for infrastructure ...

var protagonist = Ember.Object.create({
    firstName: "Priscilla",
    lastName: "Flannery",
    
    // ...
});

... until one day the models stopped singing and fell silent.

Getting Back To RealityDragon by Jeda Villa Bali

It's getting harder to write a client-side domain model using plain old JavaScript these days. Frameworks have always been a part of JavaScript programming, mostly thanks to the hostile environment of the DOM, but the frameworks grow more intrusive all the time.

Plain old JavaScript is capable of expressing all the concepts and core ideas of an application. On the server-side we've learned to isolate and protect this sort of code using hexagonal style architectures, and what I fear about many of today's frameworks is just how deeply they need to intertwine themselves with my code. Everyone manipulates the DOM and communicates with the server, but there should also be some logic in the scripts folder that makes an app unique. Can anyone find it? Is it hidden by all the framework code?

Libraries like Knockout and Ember bury themselves into the deepest parts of an application's code. Even using extensibility points that can remove some infrastructure the frameworks change the public API of an object, they change how an object is tested, they change how an object is used, and they change how the object looks in the debugger. Most of all, they change your ability to move to some better, faster, newer, or updated framework in the future without touching and rewriting large sections of code. In exchange you'll get features like data binding, which isn't difficult to do (but is admittedly laborious). I'm not convinced this is a fair trade.

Does anybody else in here feel the way I do?

What Web Developers Should Know About HTTP

Thursday, February 23, 2012 by K. Scott Allen

I tried to think of everything a web developer should know about HTTP, then recorded those things into a Pluralsight course: HTTP Fundamentals.

In the course you'll look at HTTP messages, message headers, cookies, caching, and authentication protocols. You'll see HTTP work from a low level TCP/IP perspective using Wireshark and System.Net.Sockets. But, you'll also see HTTP from a higher level architectural perspective and learn to think of the Web as an extension of your application's architecture.

Persistent connections, proxy servers, and content negotiation – it's all there. Give it a try.

Wireshark and Sockets

History and Hash Changes (BYOSP Part 6)

Wednesday, February 22, 2012 by K. Scott Allen

It's happened to everyone. You thought you typed "Use Ruby To Capture Screen Shot" into a slide, but somehow you hit the i key instead of the o key and didn't notice. Now the slide is presenting itself proudly in front of an  ultra-conservative Ruby programming crowd who is still amped up from drinking green tea all night.

What to do?

You can open a text editor, fix the HTML, and refresh the browser. The slide is fixed, but the slide show always starts from the first slide so you've lost your position in the deck. The crowd grows even more restless.

Let's see if we can prevent this sort of ugliness.

The first step is to assign every slide an ID when the slide show starts. We'll put this logic in a method named assignSlideIdentifiers and call it from the start method.

var assignSlideIdentifiers = function () {
    $("section").each(function (index) {
        $(this).data("id", index);
    });
};

The method numbers every slide from 0 to N and stores the associated number in each section element using jQuery.data.

Now that we have an ID for each slide, we can update the browser history whenever the presenter changes the current slide.

var updateHistory = function () {
    var id = $("section.current").data("id");
    window.history.pushState(
        { "id": id }, id, "#" + id
    );
};

var onKeyDown = function (event) {

    var handler = keys[event.keyCode];
    if (handler) {
        event.preventDefault();
        handler.action();
        updateHistory();
    }
};    

The pushState method is part of the session history navigation API. The API has spotty support, but you can use Modernizr to test for the presence of the feature and there are shims to provide the API if the browser is not up to snuff.

The end result is the slide ID will appear in the URL fragment (a.k.a the hash) of the browser's address bar. For example, the address of the first slide will look like:

   http://localhost/slides/http.htm#0

When you move to the second slide, the address will change to:

   http://localhost/slides/http.htm#1

When you hit the browser refresh button, it is a simple matter for the script to look at the URL fragment and jump to the proper slide.

var setFirstVisibleSlide = function () {
    if (window.location.hash) {
        moveTo(window.location.hash.slice(1));
    }
    else {
        $("section").first().addClass("current");
        updateHistory();
    }
};

var moveTo = function (id) {
    $("section.current").removeClass("current");
    $("section").filter(function () {
        return $(this).data("id") == id;
    }).addClass("current");
};

With the slide ID in the URL,  we can also use the moveTo method to support another new feature – the ability to jump to a new slide by modifying the URL.

var start = function () {
    assignSlideIdentifiers();
    setFirstVisibleSlide();

    $(window).bind("keydown", onKeyDown)
             .bind("hashchange", hashChange);

};

var hashChange = function () {
    moveTo(window.location.hash.slice(1));
};

The hashchange event has been around for a bit, and allows us to detect if the user types a new slide number into the URL, or follows a link to a different slide. We can also now edit slides on the fly, refresh the browser, and not lose position.

At this point we have a fairly useful slide presentation tool. Should we add animation features next? Or should we refactor some problems in the scripts?

Tune in next week to find out.

For all the code, see github.

Media Queries (BYOSP Part 5)

Tuesday, February 21, 2012 by K. Scott Allen

Occasionally it's useful to print a slide deck onto paper, or export a slide deck to PDF using a print driver. Unfortunately, printing the slide show in it's current form will only display the single, current slide. We can fix this by adding a CSS media query at the bottom of the stylesheet.

@media print{
   
   @page {
       size: landscape;
   }
   
   section {
       opacity: 1;
       height: auto;
       overflow: auto;
       page-break-after: always;
   }      
    
}

Media queries are logical expressions that return true or false and are well supported in modern browsers. When the expression returns true, the browser will apply the styles inside the expression to the current document. Inside the media query shown above, we'll turn on the display of all <section> elements while printing. 

CSS media queries are powerful and becoming important these days. "Screen" and "print" are two media types you can use in a query to target styles for a specific output, but today you can also use media queries to target different screen resolutions, aspect ratios, color capabilities, and device orientations. As more and more mobile devices come online with powerful web browsers, media queries are an important tool in making HTML look good on different screen sizes.

When a web site goes to the trouble of recognizing and responding to a user's screen size and orientation it's called responsive design. For a great article on this topic, see Smashing Magazine's Responsive Web Design: What It Is and How To Use It.

And now ...

The slide presenter behaves well until we refresh the browser in the middle of a presentation. A refresh will restart the the slide show from the beginning. We'll fix this problem and add new features in the next post.

For all the code, see github.

Modernization (BYOSP Part 4)

Monday, February 20, 2012 by K. Scott Allen

What happens if someone using an old web browser wants to view our slides? We can add the following div outside of the section elements containing the slide content.

<div id="warning">
    Your browser may not support the 
    features needed to display these slides. 
</div>

 

Following a long standing tradition amongst software developers, we are not going to the trouble of specifying exactly what might be missing. Instead, we are only hinting at a potential catastrophe to create an aura of mystery and suspense.

We don't want the warning to display on the good browsers, though, so we'll turn the warning off by default using CSS.

#warning {
    display: none;
}

Now we can download a custom Modernizr build to detect the features required by our slides. Thanks to the CSS classes added by Modernizr, we can add additional style code to turn on the warning if a feature is missing.

.no-flexbox #warning, 
.no-opacity #warning, 
.no-csstransitions #warning {
    display: block;
    color: red;
    font-size: smaller;
    position: fixed;
    bottom: 0px;
}

In this case we've decided to warn the user if their browser doesn't support flexbox, opacity, or CSS transitions.

Next up – printing.

For all the code, see github.