Archive for June, 2011

Single finger scrolling in Mobile Safari

Tuesday, June 21st, 2011

We built the BackChannel using the Mobile View Engine I discussed in this post. If you hit the site with a phone we serve a mobile version, but if you hit it with a tablet, we serve the full version, which, of course, we expect to work. For the schedule page, I built a nifty full screen multi-pane interface that works perfectly in all desktop browsers I tried and in the Android 3.1 browser on my Xoom. But yesterday Dave pointed out that the bottom left and right panes wouldn’t scroll on iPad.

After a little digging, I found that Mobile Safari doesn’t support single finger scrolling on divs. You have to use two fingers. This is actually documented in the iPad User Guide. Presumably this is because on touch based devices it’s hard to know if you intend to scroll the whole page or just the div. But, like I said, it works perfectly on my Xoom. Now, if I didn’t know this, I certainly can’t expect my users to.

I found that Nick Larson had created a very simple fix to implement single finger scrolling. I took it and rolled it into a jQuery plugin (my first, actually) using Mike Alsup’s plugin pattern. There are other solutions out there but mine has the virtue of being very easy to understand and very light (644 bytes minified).

/*mjuniper 06/21/2011
    based on code from:

http://www.flexmls.com/developers/2011/04/13/

ipad-and-single-finger-scrolling-in-flexmls/
    and http://www.learningjquery.com/2007/10/a-plugin-development-pattern
*/
//
// create closure
//
(function ($) {
    //
    // plugin definition
    //
    $.fn.mobileScroll = function (options) {
        //only do it for iOS devices
        var userAgent = window.navigator.userAgent.toLowerCase();
        if (userAgent.indexOf('ipad') > -1 ||
                userAgent.indexOf('iphone') > -1) {
            this.bind('touchstart', function (evt) {
                var e = evt.originalEvent;
                startY = e.touches[0].pageY;
                startX = e.touches[0].pageX;
            });

            this.bind('touchmove', function (evt) {
                //need the touches property of the native dom event
                // - jquery exposes originalEvent for that
                var e = evt.originalEvent;
                var touches = e.touches[0];
                //cache $(this)
                var $this = $(this);

                // override the touch event’s normal functionality
                e.preventDefault();

                // y-axis
                var touchMovedY = startY - touches.pageY;
                startY = touches.pageY; // reset startY for the next call
                $this.scrollTop($this.scrollTop() + touchMovedY);

                // x-axis
                var touchMovedX = startX - touches.pageX;
                startX = touches.pageX; // reset startX for the next call
                $this.scrollLeft($this.scrollLeft() + touchMovedX);
            });
        }

        return this;
    };
    //
    // end of closure
    //
})(jQuery);

ESRI UC BackChannel is live!

Wednesday, June 15th, 2011

We’re still tweaking but the BackChannel app for the 2011 ESRI International User Conference is live! We’ve had tons of fun building this app; we hope you enjoy it.

Esri UC BackChannel app – localStorage and jQuery deferred

Friday, June 10th, 2011

So Dave and I recently put together a BackChannel app for the Esri User Conference next month. For the agenda part of it, we wanted to use localStorage to store the data so that after the initial page load, it would be snappy (the session data is several hundred kilobytes). So I built a little repository class that wraps all that up using jQuery deferred. Below is the getUcSessions method. Basically, it checks to see if the sessions are in local storage and, if so, returns them, if not, it requests them with an XHR, returns a deferred, and resolves the deferred when it’s got them. That way my calling code doesn’t need to know anything about localstorage or the XHR or anything.

getUcSessions: function (sessionsJsonPath) {
       //if we've got it in localStorage, return that
       var data = localStorage.getItem(_sessionKey);
       if (data) {
           console.debug('got sessions from localStorage.');
           _ucSessions =JSON.parse(data);
           return _ucSessions;
       }

       //otherwise, load the json and stuff it into localstorage
       //here we return a promise that will be resolved when we've gotten and processed all the data
       var dfd = new $.Deferred();
       $.getJson(sessionsJsonPath, function (sessions) {
            _ucSessions = sessions;
            console.debug('got sessions via XHR.');

            dfd.resolve(_ucSessions);
       });
       return dfd.promise();
}

Then to call it, you just do:

$.when(_repo.getUcSessions(args.sessionDataUrl)).then(function (ucSessions) {
    //Do stuff with the sessions...

});