Showing posts with label jquery. Show all posts
Showing posts with label jquery. Show all posts

Friday, January 29, 2016

A Slider Control for the Web

Today's comic has a slider control embedded in the third frame, allowing you to adjust the brightness of the sun and extend daylight. It's suppose to look a bit like the brightness slider control on your phone. I got the idea from my daughter who keeps her brightness on the lowest setting in order to extend the battery life. She also panics when it gets down to 70%, as if a dead phone battery will somehow mean her own death as well. Teenagers...

You might assume that a big fan of HTML5 such as myself would use the range input field to implement the slider. You would be wrong. Unfortunately, I do try to keep the comic mostly compatible with IE9 (that might change be the end of the year) and it does not support the range type of input. Also, the default styling of the range slider looks pretty terrible. It would have taken me a while to get something that looked the way I wanted.

A standard HTML5 slider control. Input type "range" does this.

Well, how about the jQuery Mobile slider? It looks nice, and I already use jQuery. But, alas, its mobile styling really messed up the default styling of all kinds of things on my pages. It would have taken me quite some time to straighten it all out.

The jQuery Mobile Slider control. Looks nice, destroys Amphibian.com.

So twice I was thwarted by wanting a slider that looked nice and didn't cause me to do a lot of extra work. Before I gave up on this idea, I found the Slider for Bootstrap by Kyle J. Kemp. It came with CSS that didn't interfere with the rest of my CSS, and was easy to use.

Start by including the bootstrap-slider.js (or bootstrap-slider.min.js) file on your page. Also include the bootstrap-slider.css (or bootstrap-slider.min.css). Then write some HTML like this:

<input id="brightnessSlider" type="text"
       data-slider-tooltip="hide"
       data-slider-id="bSlider"
       data-slider-min="0"
       data-slider-max="19"
       data-slider-step="1"
       data-slider-value="19"/>

Note that the type of the input is "text" and not "range." Don't worry about that for now, look at the data-slider- attributes. These are used to pass configuration settings to the control. In my case, I wanted to hide the tooltip, set the minimum value to 0, the maximum value to 19 (for 20 total values), the stepping to 1, and start with a value of 19 (the max). The data-slider-id attribute is of interest, though. What it means is that when the JavaScript turns this input into a bunch of other elements that look like a slider, the parent div for all those will be named "bSlider". Once that's created, this text input will be hidden from view. But when the user interacts with the slider, the value will be written back to the text input's value - making it easy to figure out what to use when the form is submitted.

Take a look at the JavaScript now.

var bChange = function() {

    var n = Number($('#brightnessSlider').val());
    console.log("current value is " + n);

};
    
var bslide = $('#brightnessSlider').slider()
                    .on('change', bChange);

In this example, the bChange function doesn't really do much, but it shows how you can get the current value of the slider. The bChange function is passed in as the event handler for the change even when the slider is created.

If you want to change the width of the slider, the only way I found was to alter the CSS width attribute of the control's created div. I did this programmatically when the comic is displayed in one of the small-screen formats. Remember, the control's div will have the id matching the data-slider-id attribute in the input tag.

$("#bSlider").css("width", "150px");

The default width seemed to be 210px, but I reduced that to 150px for the phone screen sizes.

The styled slider in the comic.

After that, I added a few CSS rules to change the colors of parts of the slider, and I ended up with pretty much exactly what I envisioned it should look like. For now, this is the slider control that gets my recommendation - as long as you also use jQuery and Bootstrap. But who doesn't these days?

See the slider in action in today's comic!

Amphibian.com comic for 29 January 2016

Monday, October 26, 2015

Animate Colors with jQuery

While working on an upcoming comic, I wanted to add a color-change effect to an element. I soon learned that jQuery's animate() function doesn't support animation of non-numeric CSS properties. So while I can animate a width between 100px and 200px, for example, I can't animate a color between #000000 and #FF00FF.

But all is not lost! There is a plugin, appropriately named jQuery Color, which adds the color animation feature. It is trivial to use and extremely small to download.

To start using it, just download the .js file from their GitHub page. I am using the minified version. Include it in your page after jQuery. Then, just animate colors the same way you would animate other CSS properties and it will magically work!

In the following example, the page starts out containing a DIV with a black background. As soon as jQuery is ready, an animation changes the background color to purple over a span of 10 seconds.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Color Animation</title>
</head>

<body>

  <div id="test-div" style="width: 500px; height: 300px; background-color: #000000;"></div>

</body>

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

$(function() {

    $('#test-div').animate({backgroundColor: '#FF00FF'}, 10000);

});

</script>

</html>

It doesn't get much easier than that! But what comic prompted me to use this? It's not for today's. You'll just have to wait to find out (or look in the repository on GitHub).

Amphibian.com comic for 26 October 2015

Wednesday, July 1, 2015

The Basics of jQuery SVG

I know I've been writing more about SVG lately. This is because I am working on a new design for caseyleonard.com that is basically all SVG embedded in an HTML page. But static content is boring! I want a website that moves or something!

One way I can accomplish this is with jQuery SVG, a jQuery plugin that lets you interact with SVGs via JavaScript. I tried to do some basic things with it, just to see if I would like it.

First of all, I will report that the online documentation for jQuery SVG is not the greatest. I found the site navigation to be confusing and explanations of the code samples were hard to understand. There are some good demos, but it's not exactly clear why they work in all cases. I had to do a bit of experimentation in order to figure out the basics.

My goal was to have a very wide scene rendered via SVG. You can only see part of the scene at any given time because of the viewBox settings. Clicking on some elements in the SVG will cause the viewBox to scroll a new part of the scene into view.

Here is a stripped-down version of the HTML page. Most of the SVG has been removed so save space.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery SVG Test</title>
<link rel="stylesheet" type="text/css" href="css/jquery.svg.css">
<link rel="stylesheet" type="text/css" href="css/default.css">
</head>

<body>

    <div id="svgContainer">

        <svg id="svgScene" xmlns="http://www.w3.org/2000/svg"
                viewbox="0 0 500 282">
            ....
            <g id="ceo-frog">
                ....
            </g>
            <g id="science-frog">
                ....
            </g>
            ....
        </svg>

    </div>

</body>

<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="js/jquery.svg.js"></script>
<script type="text/javascript" src="js/jquery.svgdom.js"></script>
<script type="text/javascript" src="js/jquery.svganim.js"></script>

</html>

At the bottom, you can see the jQuery and jQuery SVG imports. jQuery SVG is very modularized - you need to include the base plus any of the different sub-modules you wish to use. I am using the DOM module to allow me to access the DOM of the SVG much like the DOM of the HTML. I am also using the Animations module to allow me to use jQuery animation with the SVG attributes. Also note that there is a small CSS file included as part of the jQuery SVG package that you'll want to include.

Now it gets weird. Look at the following JavaScript needed to make the viewBox move.

$(function() {

    $("#svgScene").svg();

    var svg = $("#svgScene").svg("get");

    $("#ceo-frog", svg.root()).click(function(e) {
        $("#svgScene").animate({svgViewBox: "500 0 500 282"}, 1500);
    });

    $("#science-frog", svg.root()).click(function(e) {
        $("#svgScene").animate({svgViewBox: "1000 0 500 282"}, 1500);
    });

});

Everything is wrapped in a function that gets called when the page is fully loaded. The first thing that must happen is that the SVG code must be initialized on the SVG element in the document. There on line 5, I used the standard jQuery selector to get the SVG element by id and then call .svg() on it. After that, I can get an SVG object by calling .svg("get") on that same element again.

You need the svg object in order to make calls to find elements inside the SVG. See lines 7 and 11 where I set up click listeners on two of the objects in the SVG scene (a picture of CEO Frog and a picture of Science Frog). The object returned from calling svg.root() must be passed as the second parameter to the normal jQuery selector function in order for it to work properly.

Finally, likes 8 and 12 reveal how to animate the viewBox of the SVG. Using the selector for the SVG root element and a call to the standard jQuery .animate() function, it is possible to create a smooth scroll transition between two viewBox configurations. The most important thing to realize is that you can't use the parameter "viewBox" as input to the animate function. You have to animate the "svgViewBox" attribute instead. I know, there is no actual attribute in the document named svgViewBox. It's just viewBox. But the SVG attributes have special names (mostly the same names with "svg" at the beginning but not always - see the documentation) that you must use for animation purposes. The viewBox attribute is made up of four numbers. The first two are the x and y coordinates for the top left corner of the viewBox. The next number is the width of the view and the last number is the height. In my animation, I change the first number to add 500 each time, which essentially scrolls the view sideways by its width.

I don't know about you, but I find the API for this module to be a bit odd. Yes, I got it to work, but I didn't really feel good about it.

Bottom line: I think I'm going to try Snap.svg next before I make a decision on what to use.

Amphibian.com comic for 1 July 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

Friday, April 17, 2015

Publish/Subscribe with jQuery

The frog dodgeball thing from Wednesday's comic was probably more fun for me to create than it was to play. Embedding a simple game in a comic, even a SVG/CSS/JavaScript comic, was not the easiest thing to do. But I used some very effective design patterns to make it easier.

Not a Sub, a Pub/Sub
The comic has supported simple animations since the beginning, but for the game to work I needed to add a custom module to just that one comic which could take additional actions when the animation state changed. I certainly didn't want to tightly couple the general-purpose animation code to this one comic, and also I didn't want to duplicate the most of the animation code in this module just to add one feature. Since I may want to do similar things in the future as well, I wanted a solution that would be flexible.

I decided to use the Publish/Subscribe Pattern. A derivative of the Observer Pattern, a Publish/Subscribe model allows me to completely decouple the event generator (my animation code in this case) from any other components that may wish to take action on certain events (the game code).

The Publish/Subscribe pattern has three parts:

  1. the publisher (creates the events)
  2. the subscriber(s) (want to be notified of events)
  3. the controller (takes events from publishers to subscribers).


My animated comic objects will publish events at certain points in their animation cycles. It doesn't matter if no one is subscribed to those events or if 100 other objects are subscribed. The general contract is that the subscriber says to the controller, "call my notify method when event X happens" and the publisher says to the controller, "event X just happened" the controller will pass the message on to every subscriber by calling the notify method. The publisher doesn't have any idea who is subscribed.

Fortunately, jQuery provides the controller. In jQuery-speak, this system is called custom events. Basically, the .trigger(event) method on jQuery objects invokes a controller that notifies any other objects that may be subscribed to that particular event on that object.

Consider the following example.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Pub/Sub</title>
</head>

<body>

  <p id="publisher">I have something to say.</p>

  <p id="target">Nothing here.</p>

</body>

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

<script>

$(function() {

    // subscribe to events named "myEvent" from #publisher
    $("#publisher").on("myEvent", function(event, data) {

        $("#target").html(data.text);

    });

    // using setTimeout to simulate an asynchronous event
    setTimeout(function() {

        var myData = {
            text: "This is my event text"
        }
        // publish a "myEvent" event, with some custom data
        $("#publisher").trigger("myEvent", [myData]);

    }, 5000);

});

</script>

</html>

As soon as the page is loaded, it sets up a subscriber function to "myEvent" events that may come out of the P element named "publisher." I used setTimeout to simulate an asynchronous event that may happen sometime on your page. When it does, the "publisher" publishes its "myEvent" along with some data. The subscriber function is called, and the P element named "target" changes its contents to be whatever was provided as data.

If you execute this code yourself, you'll see that the text "Nothing here" is replaced by "This is my event text" after 5 seconds.

I used this pattern on IMG tags to have them publish events at certain points in their animation cycles, so I am now able to make comic-specific code that can take special actions on general-purpose animations. All I had to do was add a single line with a call to .trigger() to my animation function! That means comic-cell games will now be even easier for me to create! There will be more!

But not today. It's just a normal comic with a little animation.

Amphibian.com comic for 17 April 2015

Friday, March 6, 2015

What's on the Menu Today?

With such widespread use of touch-screen devices today, I have to wonder if the mouse will be a forgotten input device in 20 years. Some people probably wish it was already forgotten. It tends to be less efficient than using keyboard shortcuts (if you can remember them all). But I don't find it all that terrible and still make use of right-click context menus in my user interfaces.

I've used both Dojo and ExtJS context menus in the past, but for my comic editor I use the jQuery contextMenu plugin. I've found it to be very responsive and relatively lightweight at 65k, but it feels a little awkward to use at times. One of its best features is that the menu object is created only once and can be bound to any number of objects. This makes very efficient use of the DOM, but the menus are also immutable. I thought that a bit odd at first but was easy enough to work around.

The basic use is simple. Here is an example web page.

<!doctype html>

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

<body>

  <p>This text should not have a menu.</p>

  <p class="demo">This text should have a menu.</p>

  <p>This text should not have a menu.</p>

  <p>This text says nothing.</p>

</body>

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

</html>

Note that in addition to the JavaScript for contextMenu, there is a CSS file for it you should include as well. Also, jQuery UI Position is recommended.

To create a right-click menu that will bind to every element with the "demo" class, you can do this...

$.contextMenu({

    zIndex: 100,
    selector: ".demo",
    items: {
        item1: {
            name: "Item 1",
            type: "text",
            value: "nothing"
        },
        item2: {
            name: "Second Item",
            type: "text",
            value: ""
        },
        sep1: "----------",
        item3: {
            name: "Another Item",
            type: "text",
            value: "also nothing"
        },
        sep2: "----------",
        someAction: {
            name: "Take Some Action",
            callback: function(key, options) {
                console.log("take action");
            }
        },
    },
    events: {
        show: function(opt) {
            var mData = $.contextMenu.getInputValues(opt, this.data());
            mData.item2 = "222222";
            $.contextMenu.setInputValues(opt, mData);
            console.log("menu shown");
        },
        hide: function(opt) {
            var mData = $.contextMenu.getInputValues(opt, this.data());
            console.log("item 1 when hidden: " + mData.item1);
        }
    }

});

Calling $.contextMenu creates a menu that will be activated whenever you right-click on an element that matches the selector value. In this case, it will be all elements with the demo class. The menu items are called out as fields in the items object. Note that you can use multiple separators, but they each have to have unique names. Each item can specify a name and optionally a type. The value of type will determine how that item is presented in the menu. In my example, "text" as a type results in a text box. If you just want action items to click on in your menu (things like "cut" and "paste" for example), leave off the type attribute and use callback instead. Specifying a function here enables your code to be called whenever that menu item is clicked on. Clicking on an item as causes the menu to be immediately hidden as well.

The events object gives you the ability to set listeners on the menu's show and hide events. In these functions, you can read the write the values for menu items that happen to be form controls such as input boxes. In my example's show function, I call $.contextMenu.getInputValues(opt, this.data()) to get an object that represents the data values of all the input fields in the menu. I know, that's not exactly the most obvious way to get the data, but it is what it is. Once you have the object, you can inspect and/or change the values. In the show function, it might make sense for you to set a value before the menu is displayed to the user. I do that by setting item2's value to "222222". In the data object, you reference the values by the names you gave the menu items (item1, item2, etc.). I leave the other values alone. When the menu is displayed, the value for "Second Item" should be all those 2's even though the value is blank in the menu definition. In the hide listener, it often makes sense to do something with the values that were entered by the user. Getting the data object is the same here. In my example, I just log the value of item1. If you change the text on the menu before hiding it, you should see your text print out to the console.

The results look like this.


I mentioned near the top about how the menus can't be changed after they are created. So what do you do if you need to make serious changes to the menu structure at runtime? The easiest thing to do is to destroy the menu and create a new one. This is what I do when images are added to my image selector menu and I need to recreate it. There doesn't appear to be any drawback to this method, as long as it is easy for you to run your menu-creation function again.

To blow away a specific menu, call "destroy" and specify the selector:

$.contextMenu( "destroy", "demo" );

To destroy all context menus, you can make the same call without specifying a selector.

$.contextMenu( "destroy" );

There's lots more you can do with these context menus, so I encourage you to experiment with it. While you can't see them while viewing the comics on Amphibian.com, you can trust me when I tell you they are there for me when I create them.

None of this really answers the question of what to do with right-click menus in situations where there is no mouse, but honestly I'm still thinking about the best way to handle that. Right now, I just don't edit the comics on a phone or tablet. Primitive, I know.

Amphibian.com comic for 6 March 2015

Friday, February 20, 2015

It Keeps Blinking, But I'm Not Turning

You know what I miss about the Internet from the 90's?

Blinking text!

You know you miss it too! You don't have to live in denial any longer. Just accept it. It's okay.

Blink.

Blink.

Blink.

Even though Netscape Navigator is a distant memory, it's easy to relive your glory days of website design by making text blink using jQuery. It's really easy.

Just take whatever elements you want to blink and add the blink class to them, like I did with this paragraph tag:

<body>

  <p class="blink">This text should blink.</p>

</body>

Then in a script tag, just do this (updated):

$(function() {

    var vis = "hidden";
    function bringBackBlink() {
        $(".blink").css("visibility", vis);
        vis = ( vis === "hidden" ? "visible" : "hidden" );
    }

    setInterval(bringBackBlink, 500);

});

Updated: After the bringBackBlink (say that 5 times fast) function is defined, setInterval makes sure it gets called every 500 milliseconds. All the function does is find all elements on the page that have the blink class and toggles their display visibility value. If they are visible, they become hidden. If hidden, they become visible. It will happen twice every second. If you want to make them blink faster or slower just change the millisecond value.

As pointed out in the comments, I originally used jQuery's .toggle() function to alternatively hide and show the element. This will only work correctly in situations where the element to be blinked is using fixed or absolute positioning (that's why it works for me in frog comics). In "normal" situations, like my paragraph tag example above, all the other elements on the page will shift around. This is because jQuery's toggle function sets the value of the display property to none, which causes the hidden element to no longer take up space on the page. Using the visibility property instead keeps things where they belong.

Now look at this example of HTML. It has lots of other non-blinking elements on the page that won't shift around when things blink.

<body>

  <p>This text should not blink.</p>

  <p class="blink">This text should blink.</p>

  <p>This text should not move.</p>

  <p class="pink">
    <span class="blink">
      This text should blink and be pink.
    </span>
  </p>

  <p>This text should not move.</p>

</body>

<style>

.pink {
    background-color: #000000;
    color: #FF3399;
    padding: 5px;
}

</style>

Here is an animated GIF showing the results:


Just like neon windbreakers and cargo pants, blinking text on the web is coming back in style! (Disclaimer: I don't actually know if neon windbreakers and cargo pants are coming back in style)

And speaking of blinking...if you still have your Christmas lights up like the frogs do, it might be time to take them down.

Amphibian.com comic for 20 February 2015

Monday, October 27, 2014

Beware the Draggin'

One interesting thing I learned while developing 2-Player Solitaire is that there is no way to programatically cancel a drag in jQuery UI. Once a user starts dragging something, the drag has to finish. That means you have to process any drop events that might occur.

Making something draggable is really easy. Making something a drop target is really easy.

<!doctype html>

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

<body>
    <div id="draggable">What a drag.</div>
    <div id="dropzone">Drop here.</div>
</body>

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

$("#draggable").draggable();
$("#dropzone").droppable();

</script>

</html>

I was in the unfortunate position of having a virtual card table that allowed 2 players to go after the same cards to drag them around. As soon as one person started dragging, the server would place a lock on the card so that no one else could start dragging it. However, since both clients could start the drag and send the "drag start" event to the server simultaneously, one of the draggers would have to lose. The server would lock on one of the events and then asynchronously send a message to the other telling it to stop dragging. That last part turned out to be difficult.

The folks that make jQuery won't provide a "cancel" method. They have some good reasons for doing so, which you can read about on the feature request ticket. In general I agree with their reasoning, but I was trying to create a very non-standard UI. It had to give the impression that the other player just grabbed the card before you did, instead of letting you both drag it around independently and then tell one of you at the end that you were wasting your time.

What did I do? I learned that if you return false from a draggable's start or drag event handlers, it will stop the drag. In the case of the start handler, the drag never really gets to begin. Still, that didn't help me because I couldn't perform a synchronous check with the sever in that handler to see if it should allow the drag. Returning false from the drag event's handler stops the drag, but still fires the drop event if the item being dragged ends up over a droppable area. Since my intent was to make it as if the drag never really happened, that side effect was problematic.

$("#draggable").draggable({
    start: function(event, ui) {
        var ok = true; // determine if dragging is ok to start
        return ok;
    },
    drag: function(event, ui) {
        var ok = true; // determine if dragging should continue
        return ok;
    }
});

It wasn't perfect, but it was my only option. If a player started dragging a card at the same time as the other player, one of them would get shut down by the server. The server would emit an "abort drag" event to the client (via Socket.io) and the card would get a flag set on it, telling the drag event handler function that it needed to return false. The drop event handler also checked this flag, and after clearing the flag, returned immediately instead of doing the normal drop stuff. This was enough to provide the illusion that the drag never really started. And it works in my case because I don't specify a handler for the droppable's drop event - if I did, and the drag was canceled while over that area, the drop event would still be fired there. That might produce more unwanted side effects.

In the following example, I set a timer to cancel the drag 2 seconds after you start it. This provides a good example of an asynchronous even causing the cancel. Drag and drop in under 2 seconds, and you see the "normal" path. Hold on to the draggable for more than 2 seconds, and it will get cancelled on you.

$("#draggable").draggable({
    start: function(event, ui) {
        var d = this;
        d.cancelDrag = false;
        window.setTimeout(function() {
            d.cancelDrag = true;
        }, 2000);
    },
    drag: function(event, ui) {
        if (this.cancelDrag) {
            console.log("stopping the drag!");
            return false;
        }
    },
    stop: function(event, ui) {
        if (this.cancelDrag) {
            console.log("drag was cancelled, don't do anything");
            this.cancelDrag = false;
            return;
        }
        console.log("drag was completed normally");
        // do other stuff here...
    }
});

If this method doesn't suit you, there is another possibility. In my research, I came across this very interesting way to allow a drag to be canceled without firing any events on drop targets. It works by programmatically generating a mouse event which is then dispatched to the window. It tricks jQuery into thinking that the user released the mouse button (thereby stopping the drag) while the cursor was in an impossible location (thereby not over any droppable targets). In the following example, the drag is aborted by pressing the ESC key (note the keydown event checking for keyCode == 27) but you could really do the same thing from anywhere.

$( window ).keydown( function( e ){
    if( e.keyCode == 27 ) {
        var mouseMoveEvent = document.createEvent("MouseEvents");
        var offScreen = -50000;

        mouseMoveEvent.initMouseEvent(
            "mousemove", //event type : click, mousedown, mouseup, mouseover, etc.
            true, //canBubble
            false, //cancelable
            window, //event's AbstractView : should be window
            1, // detail : Event's mouse click count
            offScreen, // screenX
            offScreen, // screenY
            offScreen, // clientX
            offScreen, // clientY
            false, // ctrlKey
            false, // altKey
            false, // shiftKey
            false, // metaKey
            0, // button : 0 = click, 1 = middle button, 2 = right button
            null // relatedTarget : Only used with some event types (e.g. mouseover and mouseout).
                 // In other cases, pass null.
        );

        // Move the mouse pointer off screen so it won't be hovering a droppable
        document.dispatchEvent(mouseMoveEvent);

        // This is jQuery speak for:
        // for all ui-draggable elements who have an active draggable plugin, that
        var dragged = $(".ui-draggable:data(draggable)")
            // either are ui-draggable-dragging, or, that have a helper that is ui-draggable-dragging
            .filter(function(){return $(".ui-draggable-dragging").is($(this).add(($(this).data("draggable") || {}).helper))});

        // We need to restore this later
        var origRevertDuration = dragged.draggable("option", "revertDuration");
        var origRevertValue = dragged.draggable("option", "revert");

        dragged
            // their drag is being reverted
            .draggable("option", "revert", true)
            // no revert animation
            .draggable("option", "revertDuration", 0)
            // and the drag is forcefully ended
            .trigger("mouseup")
            // restore revert animation settings
            .draggable("option", "revertDuration", origRevertDuration)
            // restore revert value
            .draggable("option", "revert", origRevertValue);
    }
}

Cool idea. I can't take credit for that one, it was written by "eleotlecram" who I know only by his Stack Overflow profile page.

Happy dragging!

Amphibian.com comic for 27 October 2014

Wednesday, September 24, 2014

It's the Age of Asynchronous

I'm not sure how many people have noticed this yet, but Firefox 30 deprecated synchronous XHR on the main thread. They said that it leads to a degraded user experience, and they're correct - in most cases. But I've used it on occasion without any serious side effects, I just had to be careful about what I was doing.

Still, I should probably stop doing it. How can I call synchronous XHR Ajax if the A in Ajax stands for Asynchronous? Take away the A and it's just Jax. Not cool. Of course, the X stands for XML and most people use JSON instead these days. But what is Ajaj? That doesn't even make any sense! Let's move on...

A common use of the synchronous XHR is to ensure order of operations. If you have to make calls to get data from 2 URLs before doing some kind of processing, you can't guarantee which one will return first. If you can't use synchronous XHR anymore, you could stack up the calls inside callbacks so you only ever do one at a time...but then you're still only doing one at a time. If one call takes 3 seconds and the other takes 2, you have a total wait time of 5 seconds before you can continue. If you could somehow call both at once but still make sure the results are processed in a certain order, your total wait time is only 3 seconds. Saving 2 seconds might not sound like much, but it can really add up. And when you have a human being waiting for some on-screen response, 2 seconds is an eternity. Plus, performing the calls sequentially in callbacks might be fine for 2 or 3 calls, but it gets crazy when you need to stack 5 or 6 callbacks deep.

There must be a better way.

There is! If you use jQuery, the when().done() functions can handle the difficult task of making multiple asynchronous calls and keeping the responses in order.

The when() function takes as arguments as many Deferred or Promise objects as you want to pass it. When all of them have completed, the function passed to done() is called with the results of the Deferreds as arguments in the order they were given to when().

Since Ajax calls return Promises in jQuery, they can be used as arguments to when(). For Ajax Promises, the results passed to the function given to done() will be 3-element arrays. The first element in the array will be the returned data, the second will be the status text, and the third will be the jqXHR object. I've thrown together a working example of this...

Here is my test HTML page. The JavaScript starting on line 20 is the important part.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Async Order Test</title>
</head>

<body>

  <p>when().done() test page</p>

</body>

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

$(function() {

    $.when(

        $.get('something/a'),
        $.get('something/b')

    ).done(function(a, b) {

        console.log(a);
        console.log(b);

    });

});

</script>

</html>

And here is my Node/Express server app.

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

app.get("/something/a", function(req, res, next) {
    // simulate a long response time for this path.
    // we won't send the response for 3 seconds.
    setTimeout(function() {
        res.status(200).send("this is a");
    }, 3000);
});

app.get("/something/b", function(req, res, next) {
    res.status(200).send("this is b");
});

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

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

I've set up the /something/a resource to take a long time to respond. The /something/b resource should respond as quickly as possible. Therefore "b" should pretty much always return first even though it is second in the list passed in to when(). Looking at the console output, it is clear that the "a" response is printed out first despite returning last.


The when() function can be used for other types of Deferreds as well - it is not limited to Ajax calls although these are perhaps the most common uses.

If you're not using jQuery (perhaps you have a similar issue on the server side in Node), there are other libraries available that provide similar functionality. Check out Async.js for a good one. If you are a hardcore computer science fanatic, you could implement something yourself. Here is an example I quickly threw together for doing it in Node...

var http = require('http');

function waitFor(urls, cb) {

    var countDown = urls.length;
    var retVals = new Array(countDown);

    function processResponse(idx) {
        return function(response) {
            var str = '';
            response.on('data', function(chunk) {
                str += chunk;
            });
            response.on('end', function() {
                fillInData(idx, str);
            });
        };
    }

    function fillInData(slot, data) {
        retVals[slot] = data;
        countDown--;
        if (countDown === 0) {
            cb(retVals);
        }
    }

    for (var i = 0; i < countDown; i++) {
        http.get(urls[i], processResponse(i));
    }

}

var urls = [ 'http://localhost:3000/something/a',
             'http://localhost:3000/something/b' ];

waitFor(urls, function(results) {
    console.log(results);
});

My example is not quite as flexible as what jQuery or Async.js offer, but you can see the basics of how it works. Given an array of URLs and a callback, my waitFor function will request data from all the URLs asynchronously but only call the callback when all the requests have completed. The array of data passed to the callback will be in the same order as the URLs were given. It works using a countdown latch mechanism. For each response received, the countdown in decremented. If the countdown reaches 0, all responses have come in and the callback can be called. The callback is passed an array that has been populated with each response's data. The data went in the proper array index because the index of the URL was passed along to to the function that handled the response. The only unusual thing is how the processResponse callback is given to the http.get function. Calling processResponse and passing an index actually returns an other function which is the real callback for http.get. The reason for this is to create a new closure for the index so that the response callbacks are not affected by the for loop continuing to run and incrementing the index. Without that extra function, the data from all responses almost always goes into the same position, 2, in the retVals array.

Working with lots of asynchronous functions can be confusing and frustrating sometimes. A good library like jQuery can help make it less painful.

Amphibian.com comic for September 24, 2014

Monday, September 22, 2014

When Computed Styles aren't Computed

I noticed something unexpected the other day when working with jQuery to read CSS dimension properties. Maybe everyone else in the world is already aware of this but I'll talk about it anyway.

Typically, whenever you use jQuery to read the value of a height or width property of an element, the value is always returned in pixels. You can set the value in %, px, em, cm, whatever. But it always comes back to you in pixels. It will actually be a string with "px" at the end.

Except when it's not.

I was relying on this behavior. I would grab the value, remove the "px" from the end, and turn it into a Number. But sometimes I was getting "NaN" as my value. Upon closer inspection, it turned out that I was getting a value like "82%" instead of "435px" which was throwing everything off.

Technically, jQuery is giving me what is known as the computed value of the property. Sure, sometimes getting the value in pixels is not what you want, but as long as it's consistent you can deal with it. I was encountering the problem that sometimes it was in pixels and sometimes it was the value that I set it from, which happened to be percent.

It took me a while, but I finally figured out what was going on.

When the element's parent (not the element itself) has a display value of "none," the browser can't give me the computed dimensions because it has no way to compute them. Instead, you get whatever value the CSS says.

Check out this test page I put together to demonstrate the issue.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>CSS Test</title>
</head>

<body>

  <div id="container" style="width: 400px;">
    <div id="test" style="width: 50%">test</div>
  </div>

</body>

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

$(function() {

    console.log($('#test').css('width'));

    $('#container').toggle();

    console.log($('#test').css('width'));

    $('#container').toggle();

    console.log($('#test').css('width'));

});

</script>

</html>

Here is the console output.


As you can see, the test div has a width specified as 50% but when the value of the "width" CSS property is printed out, it is "200px". This is exactly correct, since 200px is 50% of the 400px width specified on the parent container.

After I toggle the visibility of the parent container (the jQuery .toggle() function) and print the width of the test div again, I get a value of "50%". That's what the style attribute says.

Toggle the parent container visibility again (make it visible) and you see "200px" on the console again.

Once I learned this and restored my sanity, I was able to fix my problem easily by making my elements' parent visible before I tried to read the dimensions. Originally I was trying to read them, perform some calculations, and then make the parent visible. There were no real issues with switching the order around a little in my case, but your mileage may vary.

Amphibian.com comic for September 22, 2014

Monday, September 8, 2014

How Big is that Library?

Last weekend I took an old web application that had a Java back-end, a lot of hand-made JavaScript in the client, and a Node server just for websockets and converted everything to an all-JavaScript application to be run via Node.

Along the way I was also switching the JavaScript client library from Dojo to jQuery.

While the process was generally a positive one, I did learn a few things. The one I'll talk about today is the size of the client JavaScript library.

I was surprised to learn that in my case, jQuery was not a smaller client download than Dojo. If you look at their most recent versions, 1.10 for Dojo and 1.11 for jQuery, jQuery is smaller. But I was using a rather old version of Dojo, 1.4.3, which was only 87KB. The current version of jQuery is 94KB.

This got me wondering how large jQuery was back when I first made the app. I decided to perform some analysis just to satisfy my curiosity. Here is what I learned.

jQuery Version Size (KB) Release Date
1.11.0 94.1 1-23-2014
1.10.2 90.9 7-3-2013
1.9.1 90.5 2-4-2013
1.8.3 91.4 11-13-2012
1.7.2 92.6 3-21-2012
1.6.4 89.5 9-12-2011
1.5.2 83.9 3-31-2011
1.4.4 76.8 11-11-2010
1.3.2 55.9 2-19-2009
1.2.6 54.5 5-24-2008

Dojo Version Size (KB) Release Date
1.10.0 117 6-13-2014
1.9.3 115 2-20-2014
1.8.5 114 6-14-2013
1.7.4 101 10-5-2012
1.6.1 89.6 5-20-2011
1.5.1 88.2 4-12-2011
1.4.5 87.4 8-24-2010
1.3.2 79.6 7-15-2009
1.2.3 76.8 12-8-2008

The sizes I list above are for the minimized code of course. I highlighted the versions of each that were the latest when I originally made the application. At the time, jQuery was smaller but not by an enormous margin. I was more familiar with Dojo at the time, which really drove my decision to use it more than the size of the download.

The other thing I thought was interesting here was how stable jQuery has been in terms of size since 2012. The Dojo Toolkit has jumped all over the place but jQuery has been near 90KB for quite some time. In fact, you have to go back to 2009 to see a really significant change in code size.

As for the changes I had to make in my application when I switched, well, they weren't all that significant. I changed things like

dojo.byId('something')

to

$('#something')

and modified the AJAX calls a little, but there were no really major problems with the conversion. These days I just prefer working with jQuery mainly because I think the syntax is easier to remember and I have a much better experience finding useful information in their online documentation when I'm looking for something. I don't know why more products can't have online documentation as easy to use as jQuery's.

Amphibian.com comic for September 8, 2014