Archive for the ‘jQuery’ Category

Loading a blog feed on your website

Friday, July 29th, 2011

Recently I needed to load recent blog posts onto a website. I discovered the Google Feed API which, as most things Google, is awesome. I also discovered this jQuery plugin which uses it along with what appears to be an old version of their Dynamic Feed Control. This plugin, like all of Mike Alsup’s other plugins, is great. However, I couldn’t get it to work exactly the way I wanted. So I built my own.

I also borrowed and modified some code from John Resig to format the dates. Oh yeah, it also depends on the jQuery Templating Plugin.

It takes an options argument with a required url property and a couple of other optional properties, gets the feed, and loads the items into semantic markup inside the selected element. You can then style it any way you want.

/**
*  Plugin which uses the Google AJAX Feed API for creating feed content
*  depends on jquery template plugin
*  very loosely based on http://jquery.malsup.com/gfeed/
*/

(function ($) {
    if (!$.template) {
        alert('You must include the jQuery Templating Plugin script');
        return;
    }

    //feed item template
    var templateHtml = '<div>' +
                            '<div class="feed-item">' +
                                '<a href="${link}" target="_blank">' +
                                    '<h2 class="feed-title">${title}</h2>' +
                                '</a>' +
                                '<div class="feed-snippet">${contentSnippet}</div>' +
                                '<div class="feed-footer">Posted ${publishedDate} by ${author}</div>' +
                            '</div>' +
                        '</div>';

    //from based on code from John Resig
    // - http://ejohn.org/blog/javascript-pretty-date/
    function prettyDate(dateString) {
        var date = new Date(dateString);
        diff = (((new Date()).getTime() - date.getTime()) / 1000),
		            day_diff = Math.floor(diff / 86400);

        if (isNaN(day_diff) || day_diff < 0) {
            return;
        }

        return day_diff == 0 && (
			        diff < 60 && "just now" ||
			        diff < 120 && "1 minute ago" ||
			        diff < 3600 && Math.floor(diff / 60) + " minutes ago" ||
			        diff < 7200 && "1 hour ago" ||
			        diff < 86400 && Math.floor(diff / 3600) + " hours ago") ||
		            day_diff == 1 && "Yesterday" ||
		            day_diff < 7 && day_diff + " days ago" ||
		            day_diff < 42 && Math.ceil(day_diff / 7) + " weeks ago" ||
                    day_diff >= 42 && Math.floor(day_diff / 31) + " months ago";
    }
    new Date().tou
    $.fn.feed = function (options) {
        var opts = $.extend({
            url: '',
            target: this,
            title: 'The latest from our blog',
            max: 5   // max number of items
        }, options || {});

        //show a loading indicator
        opts.target.append('<div id="feed-loading">loading blog feed...</div>');

        //get the feed items
        var feedItemTemplate = $(templateHtml).template();
        var gFeedsUrl = 'http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=?&num=' + opts.max + '&q='
        $.ajax({
            url: gFeedsUrl + encodeURIComponent(opts.url),
            dataType: 'json',
            success: function (response) {
                //remove the loading indicator
                $('#feed-loading').remove();

                if (response && response.responseData
                                  && response.responseData.feed
                                  && response.responseData.feed.entries) {
                    var feedItems = response.responseData.feed.entries;
                    //map them to format the date
                    $.map(feedItems, function (item, idx) {
                        item.publishedDate = prettyDate(item.publishedDate);
                        return item;
                    });
                    //load the feeds into the dom
                    opts.target.append('<h1>' + opts.title + '</h1><div id="rss-feed"></div>')
                        .find('#rss-feed')
                        .append($.tmpl(feedItemTemplate, feedItems));
                } else {
                    opts.target.append('<div id="feed-error">Unable to retrieve blog feed.</div>');
                }
            }
        });

        return this;
    };
})(jQuery);

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 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...

});

jQuery rocks

Friday, April 9th, 2010

I’ve previously posted a bunch on dojo, which is a very fine javascript library. But in the last year or so I’ve been using jQuery a bunch and I’ve got to say it is great. The dom selection and manipulation is more full featured and easier to use than dojo’s. One thing that dojo has going for it is the classes and inheritance (dojo.declare, dojo.provide, dojo.require) but on a lot of projects, you just don’t need that. It’s interesting that both libraries recently released version 1.4 and at that version they both seem to have adopted some features of the other which I think is great.

Expect to see some posts on jQuery from me in the future. If you haven’t tried jQuery yet, you should.