1
\$\begingroup\$

This function combines a Twitter feed and an Instagram feed and sorts the items by date. I just want to write better, more efficient code, so any tips on how to transform this into more of a "pro" JavaScript function would be much appreciated! Would it work to combine all these functions into one namespace?

The result of this code can be seen here (scroll down, it's in the right sidebar)

function extract_relative_time(date) {
    var toInt = function(val) { return parseInt(val, 10); };
    var relative_to = new Date();
    var delta = toInt((relative_to.getTime() - date) / 1000);
    if (delta < 1) delta = 0;
    return {
        days:    toInt(delta / 86400),
        hours:   toInt(delta / 3600),
        minutes: toInt(delta / 60),
        seconds: toInt(delta)
    };
}

function format_relative_time(time_ago) {
    if ( time_ago.days > 2 )     return 'about ' + time_ago.days + ' days ago';
    if ( time_ago.hours > 24 )   return 'about a day ago';
    if ( time_ago.hours > 2 )    return 'about ' + time_ago.hours + ' hours ago';
    if ( time_ago.minutes > 45 ) return 'about an hour ago';
    if ( time_ago.minutes > 2 )  return 'about ' + time_ago.minutes + ' minutes ago';
    if ( time_ago.seconds > 1 )  return 'about ' + time_ago.seconds + ' seconds ago';
    return 'just now';
}

$j(document).ready(function($){
    var fwyFeed = [],
        feedIndex,
        feedResponse,
        feedHeight,
        feedOutput = '',
        user = 'friendswithyou',
        $feedBox = $('.fwy-feed-content'),
        $feedWrap = $('.fwy-feed'),
        $feedLoad = $('.fwy-feed-load'),
        $imgWrap,
        $imgs,
        imgLen;

    $feedWrap.find('h2').html('Loading Feed... <span class="fwy-feed-load ic-icon fr f16">$</span>');

    // Get instagram feed

    $.ajax({
        type: "GET",
        dataType: "jsonp",
        cache: false,
        url: "https://api.instagram.com/v1/users/5774772/media/recent/?access_token=5774772.cf44785.bdf87268cff04f98b33581ad4ef60732",
        success: function(data) {
            for (var i = 0; i < 3; i++) {
                var thedata = data.data[i],
                    created = thedata.created_time*1000,
                    url = thedata.images.low_resolution.url,
                    link = thedata.link;

                fwyFeed[i] = {};
                fwyFeed[i]["type"] = 'photo'; 
                fwyFeed[i]["created"] = created;                        
                fwyFeed[i]["text"] = url;
                fwyFeed[i]["link"] = link;
                fwyFeed[i]["date"] = format_relative_time(extract_relative_time(created));
            }
        }
    });

    // Wait 1.25 seconds for the instagram request to return
    window.setTimeout(function() {
        feedIndex = fwyFeed.length;

        $.getJSON('http://twitter.com/statuses/user_timeline.json?screen_name=' + user + '&count=5&callback=?', function(data)      {
            for (i = 0; i < data.length; i++) { 
                var thedata = data[i],
                    created = data[i].created_at,
                    id =  thedata.id_str,
                    tweetDex = i + 1 + feedIndex;

                // Twitter time object given in human time, convert to timestamp
                created = new Date(created);
                created = created.getTime();
                created = created.toString();

                fwyFeed[tweetDex] = {};
                fwyFeed[tweetDex]["type"] = 'tweet';
                fwyFeed[tweetDex]["created"] = created;                     
                fwyFeed[tweetDex]["text"] = thedata.text;
                fwyFeed[tweetDex]["link"] = id;
                fwyFeed[tweetDex]["date"] = format_relative_time(extract_relative_time(created));
            }   
        });

        // Wait 1.25 seconds for the twitter request to return
        window.setTimeout(function() {

            // Sort our feed array by time
            fwyFeed.sort(function(a,b) {
                return parseInt(b.created,10) - parseInt(a.created,10);
            });

            // Loop through each tweet/photo
            for (var i = 0; i < fwyFeed.length; i++) {
                if(i in fwyFeed) {
                    var type = fwyFeed[i]["type"], 
                        created = fwyFeed[i]["created"],                        
                        text = fwyFeed[i]["text"],
                        link = fwyFeed[i]["link"],
                        date = fwyFeed[i]["date"];

                    if(type === 'photo') {
                        feedOutput += '<div class="bbd fwy-feed-item fwy-feed-photo"><a class="bgpale img-wrap pr ic-img block" target="_blank" href="' + link + '"><img class="pr img-loading center block" src="' + text + '" /></a><a href="' + link + '" class="cpink f11" target="_blank">' + date + '</a></div>';
                    } else {
                        feedOutput += '<div class="group bbd fwy-feed-item fwy-feed-tweet"><p class="f11">' + text + ' <a target="_blank" class="cpink" href="http://twitter.com/Friendswithyou/status/' + link + '">' + date + '</a></p>';
                        feedOutput += '<div class="fr fwy-feed-share tdn">';
                        feedOutput += '<a href="http://twitter.com/intent/tweet?in_reply_to=' + link + '" target="_blank" class="ic-reply"></a>';
                        feedOutput += '<a href="http://twitter.com/intent/retweet?tweet_id=' + link + '" target="_blank" class="ic-retweet pr"></a>';
                        feedOutput += '<a href="http://twitter.com/intent/favorite?tweet_id=' + link + '" target="_blank" class="ic-fav"></a>';
                        feedOutput += '</div>';
                        feedOutput += '</div>';
                    }
                }
            }

            if(feedOutput){
                $feedBox.append(feedOutput);
                $imgs = $feedBox.find('img');
                imgLen = $imgs.length;
                $feedBox.addClass('show');

                // Fade in images
                if(imgLen > 0) {
                    $imgs.imgpreload({
                        each: function(){
                            $(this).removeClass('img-loading');
                        },
                        all: function() {
                            $feedWrap.addClass('loaded').find('h2').html('tweet/instagram');
                            $feedLoad.remove();
                        }
                    });                 
                }
            } else {
                $feedWrap.addClass('loaded error').find('h2').html('Error loading feed');
            }
        }, 1250);
    }, 1250);
});
\$\endgroup\$
2
\$\begingroup\$

Starting with the steps here, I am left with this.

From here the first thing I notice is that you are abusing window.setTimeout in order to do something with the ajax responses. This is leaving you with some pretty nasty arrow code and what happens if it takes more than 1.25 seconds for either of those sites to return?

I'll start by making the inner timeout into a named function, for now calling it render:

function render(fwyFeed, $feedBox, $feedWrap) {
    var i,
        type,
        created,
        text,
        link,
        date,
        feedOutput='',
        $imgs,
        imgLen;

    // Sort our feed array by time
    fwyFeed.sort(function (a, b) {
        return +b.created - +a.created;
    });

    // Loop through each tweet/photo
    for (i = 0; i < fwyFeed.length; i += 1) {
        if (fwyFeed[i]) {
            type = fwyFeed[i].type;
            created = fwyFeed[i].created;
            text = fwyFeed[i].text;
            link = fwyFeed[i].link;
            date = fwyFeed[i].date;

            if (type === 'photo') {
                feedOutput += '<div class="bbd fwy-feed-item fwy-feed-photo"><a class="bgpale img-wrap pr ic-img block" target="_blank" href="' + link + '"><img class="pr img-loading center block" src="' + text + '" /></a><a href="' + link + '" class="cpink f11" target="_blank">' + date + '</a></div>';
            } else {
                feedOutput += '<div class="group bbd fwy-feed-item fwy-feed-tweet"><p class="f11">' + text + ' <a target="_blank" class="cpink" href="http://twitter.com/Friendswithyou/status/' + link + '">' + date + '</a></p>';
                feedOutput += '<div class="fr fwy-feed-share tdn">';
                feedOutput += '<a href="http://twitter.com/intent/tweet?in_reply_to=' + link + '" target="_blank" class="ic-reply"></a>';
                feedOutput += '<a href="http://twitter.com/intent/retweet?tweet_id=' + link + '" target="_blank" class="ic-retweet pr"></a>';
                feedOutput += '<a href="http://twitter.com/intent/favorite?tweet_id=' + link + '" target="_blank" class="ic-fav"></a>';
                feedOutput += '</div>';
                feedOutput += '</div>';
            }
        }
    }

    if (feedOutput) {
        $feedBox.append(feedOutput);
        $imgs = $feedBox.find('img');
        imgLen = $imgs.length;
        $feedBox.addClass('show');

        // Fade in images
        if (imgLen > 0) {
            $imgs.imgpreload({
                each: function () {
                    $(this).removeClass('img-loading');
                },
                all: function () {
                    $feedWrap.addClass('loaded').find('h2').html('tweet/instagram');
                }
            });
        }
    } else {
        $feedWrap.addClass('loaded error').find('h2').html('Error loading feed');
    }
}

and replace the setTimeout with:

window.setTimeout(function () { render(fwyFeed, $feedBox, $feedWrap); }, 1250);

This I would then append it to the getJSON twitter call:

$.getJSON(...
    ...
}).then(function () { render(fwyFeed, $feedBox, $feedWrap); });

Full result so far

The next change I would make would be to change fwyFeed[i] = into fwyFeed.push( so that you don't need to concern yourself with its length so much.

The next change I would make would be to change the logic of:

$.ajax({
    ...
});

window.setTimeout(function () {
    $.getJSON(...
        ...
    }).then(function () { render(fwyFeed, $feedBox, $feedWrap); });           
}, 1250);

Knowing that both $.ajax and $.getJSON return deferred objects. This can be written as:

$.when(
    $.ajax({
        ...
    }),
    $.getJSON(...
        ...
    })
).then(function () { render(fwyFeed, $feedBox, $feedWrap); });  

Which already looks significantly cleaner.

Afterword I would name the functions which take the data from instagram and twitter and move them out of the ajax promises (naming them addInstagram and addTwitter respectively). I'd also change the instagram call to use getJSON to be more consistent and I'd make these string urls into constant variables defined at the top of the script. This would leave me with the ajax logic:

$.when(
    $.getJSON(instagram, addInstagram),
    $.getJSON(twitter, addTwitter)
).then(function () { render(fwyFeed, $feedBox, $feedWrap); });

Leaving us with this.

The next puzzle here is the fact that you shouldn't need to wait for the page to be ready before making these requests. If we store these promises in a variable we can move them out of the ready event function (along with the fwyFeed variable and the two functions). This leaves the full ready event function as:

$(function () {
    var $feedBox = $('.fwy-feed-content'),
        $feedWrap = $('.fwy-feed');

    $feedWrap.find('h2').html('Loading Feed... <span class="fwy-feed-load ic-icon fr f16">$</span>');
    requests.then(function () { render(fwyFeed, $feedBox, $feedWrap); });
});

Much more can be done:

  • make the render function nicer
  • doing something about how that h2 tag is all over the place
  • the $feedBox and $feedWrap variables can probably be written with better selectors
  • could be nice to be able to add other services in an easier manner

Full result:

(function ($, Date, Math) {
    'use strict';

    var instagram = "https://api.instagram.com/v1/users/5774772/media/recent/?access_token=5774772.cf44785.bdf87268cff04f98b33581ad4ef60732&callback=?",
        user = 'friendswithyou',
        twitter = 'http://twitter.com/statuses/user_timeline.json?screen_name=' + user + '&count=5&callback=?',
        fwyFeed = [],
        extract_relative_time = function (date) {
            var relative_to = new Date(),
                delta = (relative_to.getTime() - date) / 1000;

            return {
                days: Math.floor(delta / 86400),
                hours: Math.floor(delta / 3600),
                minutes: Math.floor(delta / 60),
                seconds: Math.floor(delta)
            };
        },
        format_relative_time = function (time_ago) {
            if (time_ago.days > 2) { return 'about ' + time_ago.days + ' days ago'; }
            if (time_ago.hours > 24) { return 'about a day ago'; }
            if (time_ago.hours > 2) { return 'about ' + time_ago.hours + ' hours ago'; }
            if (time_ago.minutes > 45) { return 'about an hour ago'; }
            if (time_ago.minutes > 2) { return 'about ' + time_ago.minutes + ' minutes ago'; }
            if (time_ago.seconds > 1) { return 'about ' + time_ago.seconds + ' seconds ago'; }
            return 'just now';
        },
        addInstagram = function (data) {
            var i, thedata, created, url, link;
            for (i = 0; i < 3; i += 1) {
                thedata = data.data[i];
                created = +(thedata.created_time * 1000);
                url = thedata.images.low_resolution.url;
                link = thedata.link;

                fwyFeed.push({
                    type: 'photo',
                    created: created,
                    text: url,
                    link: link,
                    date: format_relative_time(extract_relative_time(created))
                });
            }
        },
        addTwitter = function (data) {
            var i, thedata, created, id;

            for (i = 0; i < data.length; i += 1) {
                thedata = data[i];
                created = data[i].created_at;
                id = thedata.id_str;

                // Twitter time object given in human time, convert to timestamp
                created = new Date(created);
                created = created.getTime();

                fwyFeed.push({
                    type: 'tweet',
                    created: created,
                    text: thedata.text,
                    link: id,
                    date: format_relative_time(extract_relative_time(created))
                });
            }
        },
        requests = $.when(
            $.getJSON(instagram, addInstagram),
            $.getJSON(twitter, addTwitter)
        );

    function render(fwyFeed, $feedBox, $feedWrap) {
        var i,
            type,
            created,
            text,
            link,
            date,
            feedOutput = '',
            $imgs,
            imgLen;

        // Sort our feed array by time
        fwyFeed.sort(function (a, b) {
            return +b.created - +a.created;
        });

        // Loop through each tweet/photo
        for (i = 0; i < fwyFeed.length; i += 1) {
            if (fwyFeed[i]) {
                type = fwyFeed[i].type;
                created = fwyFeed[i].created;
                text = fwyFeed[i].text;
                link = fwyFeed[i].link;
                date = fwyFeed[i].date;

                if (type === 'photo') {
                    feedOutput += '<div class="bbd fwy-feed-item fwy-feed-photo"><a class="bgpale img-wrap pr ic-img block" target="_blank" href="' + link + '"><img class="pr img-loading center block" src="' + text + '" /></a><a href="' + link + '" class="cpink f11" target="_blank">' + date + '</a></div>';
                } else {
                    feedOutput += '<div class="group bbd fwy-feed-item fwy-feed-tweet"><p class="f11">' + text + ' <a target="_blank" class="cpink" href="http://twitter.com/Friendswithyou/status/' + link + '">' + date + '</a></p>';
                    feedOutput += '<div class="fr fwy-feed-share tdn">';
                    feedOutput += '<a href="http://twitter.com/intent/tweet?in_reply_to=' + link + '" target="_blank" class="ic-reply"></a>';
                    feedOutput += '<a href="http://twitter.com/intent/retweet?tweet_id=' + link + '" target="_blank" class="ic-retweet pr"></a>';
                    feedOutput += '<a href="http://twitter.com/intent/favorite?tweet_id=' + link + '" target="_blank" class="ic-fav"></a>';
                    feedOutput += '</div>';
                    feedOutput += '</div>';
                }
            }
        }

        if (feedOutput) {
            $feedBox.append(feedOutput);
            $imgs = $feedBox.find('img');
            imgLen = $imgs.length;
            $feedBox.addClass('show');

            // Fade in images
            if (imgLen > 0) {
                $imgs.imgpreload({
                    each: function () {
                        $(this).removeClass('img-loading');
                    },
                    all: function () {
                        $feedWrap.addClass('loaded').find('h2').html('tweet/instagram');
                    }
                });
            }
        } else {
            $feedWrap.addClass('loaded error').find('h2').html('Error loading feed');
        }
    }

    $(function () {
        var $feedBox = $('.fwy-feed-content'),
            $feedWrap = $('.fwy-feed');

        $feedWrap.find('h2').html('Loading Feed... <span class="fwy-feed-load ic-icon fr f16">$</span>');
        requests.then(function () { render(fwyFeed, $feedBox, $feedWrap); });
    });
}(jQuery, Date, Math));
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.