I've been working on a jQuery plugin for pulling tweets since the v1 endpoints were all closed off and you need oAuth now. This is just the jQuery part but if you want to see the php behind it I'll be happy to post. But basically, I'd love suggestions, as this is one of the first real plugins i've made, i'm really looking for critique on patterns and efficiency. Is there anything I'm doing that can be shorten'd significantly? Is there a feature you think would be cool for me to include?

And just FYI, there is an oAuth/twitter library that authenticates everything first and pulls, and the php file my ajax call references holds the logic for all that and creating / serving the json, but it's not that exciting.

I'm going to post the plugin with the documentation comments I have in there, to see if it all makes sense to people who have never seen it. All opinions and comments welcome, I want to write better code!

Invisible Tweets
Author :    Shan Robertson
Website:    http://invisibled.com
Version:    1.0
Updated:    August 7th, 2013
This plugin allows you to simply display Twitter timelines & lists on your website.
It caches the twitter content in a json file & changing the cache name will
allow you to have as many instances of Invisible Tweets as you want.

  - jQuery
  - invisibletweets.php (included)
  - OAuth.php (included)
  - twitteroauth.php (included)
  - invisibletweets/cache folder (must be writable) 

  user         : [string]   - The desired Twitter username.

  slug         : [string]   - The slug to your Twitter list.

  cache        : [string]   - The cache name, if dealing with multiple feeds.

  path_to_core : [string]   - Path to the invisibletweets folder.

  expire       : [string]   - Cache expire time, uses default PHP time syntax eg. 15 minutes, 10 seconds.
Warning: If expire time is shorter than 15 minutes, you may hit the rate limit.  

  type         : [string]   - Choose what feed to display. Displaying a 'list' requires the 'slug' option to be set to your list's slug.
    Options: 'timeline' or 'list'

  template     : [string]   - Comma delimited string of what elements to show and in what order.
    Options: 'name, avatar, date, time, text, btn_tweet, btn_retweet, btn_favorite'

  limit        : [integer]  - Number of tweets to return.

  retweets     : [boolean]  - Show retweets.
Options: true/false

  clear        : [boolean]  - Clears the cache.
    Options: true/false

  - wordpress plugin version

;(function ( $, window, document, undefined ) {
Plugin Functions    

Plugin.prototype.init = function () {

    var params = [  '?user='     + this.options.user,
                    '&limit='    + this.options.limit,
                    '&type='     + this.options.type,
                    '&slug='     + this.options.slug,
                    '&cache='    + this.options.cache,
                    '&expire='   + this.options.expire,
                    '&clear='    + this.options.clear,
                    '&retweets=' + this.options.retweets

        url: this.options.path_to_core + 'invisibletweets.php' + params,
        context: this,
        dataType: 'json',
        success: function(data){

            // If the errors object is returned, show the error and exit the script
                $(this.element).html("Fatal Error: " + data.errors[0].message);
                return false;

            // Variables
            var scope    = this;
            var parent   = this.element;
            var tpl      = "";
            var tplArray = scope.options.template.replace(/[\s,]+/g, ',').split(',');

            // Creates an empty template with all present data, in order.
            $.each(tplArray, function(i, elm){
                if(elm == 'name'){
                    tpl = tpl + '<div class="it_name"><a href="" target="_blank"></a></div>';
                } else if(elm == 'avatar'){
                    tpl = tpl + '<div class="it_avatar"><a href="" target="_blank"><img src="" /></a></div>';
                } else if(elm == 'date'){
                    tpl = tpl + '<div class="it_date"></div>';
                } else if(elm == 'time'){
                    tpl = tpl + '<div class="it_time"></div>';
                } else if(elm == 'text'){
                    tpl = tpl + '<div class="it_text"><p></p></div>';
                } else if(elm == 'btn_tweet'){
                    tpl = tpl + '<a href="http://twitter.com/intent/tweet?in_reply_to=" target="_blank" class="it_btn_tweet">Tweet</a>';
                } else if(elm == 'btn_retweet'){
                    tpl = tpl + '<a href="http://twitter.com/intent/retweet?tweet_id=" target="_blank" class="it_btn_retweet">Retweet</a>';
                } else if(elm == 'btn_favorite'){
                    tpl = tpl + '<a href="http://twitter.com/intent/favorite?tweet_id=" target="_blank" class="it_btn_favorite">Favorite</a>';

            //Populates the template with data
            $.each(data, function(i, item){

                // Variables
                var text        = scope.findLinks(item.text);
                var date        = scope.dateTime(item.created_at, 'date');
                var time        = scope.dateTime(item.created_at, 'time');
                var profile_url = 'http://twitter.com/' + item.user.screen_name;
                var thisTweet   = ".it_tweet:eq("+i+") ";

                // Creates a searchable object of the template
                $(parent).append('<div class="it_tweet">' + tpl + '</div>');

                //If in array, show, else don't show, for each item.
                if(tplArray.indexOf('name') != -1){
                    $(thisTweet + ".it_name a").attr("href", profile_url).html(item.user.name);
                if(tplArray.indexOf('avatar') != -1){
                    $(thisTweet + ".it_avatar").find('a').attr("href", profile_url).find('img').attr("src", item.user.profile_image_url);
                if(tplArray.indexOf('date') != -1){
                    $(thisTweet + ".it_date").html(date);
                if(tplArray.indexOf('time') != -1){
                    $(thisTweet + ".it_time").html(time);
                if(tplArray.indexOf('text') != -1){
                    $(thisTweet + ".it_text p").html(text);
                if(tplArray.indexOf('btn_tweet') != -1){
                    $(thisTweet + ".it_btn_tweet").attr("href", 'http://twitter.com/intent/tweet?in_reply_to=' + item.id);
                if(tplArray.indexOf('btn_retweet') != -1){
                    $(thisTweet + ".it_btn_retweet").attr("href", 'http://twitter.com/intent/retweet?tweet_id=' + item.id);
                if(tplArray.indexOf('btn_favorite') != -1){
                    $(thisTweet + ".it_btn_favorite").attr("href", 'http://twitter.com/intent/favorite?tweet_id=' + item.id);

            }); //each

        } //success
    }); //ajax

}; //init

Plugin.prototype.findLinks = function(tweet){
    var text = tweet;

    text = text.replace(/http:\/\/\S+/g,  '<a href="$&" target="_blank">$&</a>');
    text = text.replace(/\s(@)(\w+)/g,    ' @<a href="http://twitter.com/$2" target="_blank">$2</a>');
    text = text.replace(/\s(#)(\w+)/g,    ' #<a href="http://twitter.com/search?q=%23$2" target="_blank">$2</a>');

    return text;
}; //findLinks

Plugin.prototype.dateTime = function(created_at, select){
    var dateTime = created_at;
    var strtime = dateTime.replace(/(\+\S+) (.*)/, '$2 $1')
    var date = new Date(Date.parse(strtime)).toLocaleDateString();
    var time = new Date(Date.parse(strtime)).toLocaleTimeString();

    if(select == 'date'){
        return date;
    } else if(select == 'time'){
        return time;
}; //dateTime

Plugin Setup    

var pluginName = 'invisibleTweets',

// Options for the plugin
defaults = {
    user        : 'RobGronkowski',                                                          // Twitter username
    limit       : 10,                                                                       // Number of tweets
    type        : 'timeline',                                                               // Feed select, can be timeline/list
    slug        : '',                                                                       // For lists
    cache       : 'default',                                                                // The cache name if dealing with multiple feeds
    expire      : '15 minutes',                                                             // The time it takes the cache to expire
    clear       : false,                                                                    // Clears the cache.
    retweets    : true,                                                                     // Show retweets
    path_to_core: 'js/vendor/invisibletweets/',                                             // Path to the invisibletweets folder.
    template    : 'name, avatar, date, time, text, btn_tweet, btn_retweet, btn_favorite'    // Elements to show and in what order

// The actual plugin constructor
function Plugin( element, options ) {
    this.element = element;
    this.options = $.extend( {}, defaults, options);
    this._defaults = defaults;
    this._name = pluginName;


// A really lightweight plugin wrapper around the constructor, preventing against multiple instantiations
$.fn[pluginName] = function ( options ) {
    return this.each(function () {
        if (!$.data(this, 'plugin_' + pluginName)) {
            $.data(this, 'plugin_' + pluginName, 
            new Plugin( this, options ));

})( jQuery, window, document );
  • \$\begingroup\$ The big if else could be an switch case: stackoverflow.com/questions/2922948/… \$\endgroup\$ – jerone Feb 16 '14 at 14:56
  • \$\begingroup\$ Agreed, do you know if there are any benefits to using a switch case? I normally do it in situations like this because it looks cleaner. \$\endgroup\$ – Shan Robertson Feb 17 '14 at 17:59
  • \$\begingroup\$ See the accepted answer I posted above. \$\endgroup\$ – jerone Feb 17 '14 at 18:19

A few minor notes:

  1. Comments like this:

    // Creates an empty template with all present data, in order.
    $.each(tplArray, function(i, elm){

    and this:

    //Populates the template with data
    $.each(data, function(i, item){

    could be great function names. It would also increase the abstraction level of the code which helps maintainers with a quick overview about what the code does (without the details). They still can check the details inside the functions if they're interested in. It usually requires less code to understand (so it's faster and easier), especially if they want to modify only a small part of it.

    (Clean Code by Robert C. Martin, Don’t Use a Comment When You Can Use a Function or a Variable, p67.)

  2. var scope     = this;
    var parent    = this.element;
    var tpl       = "";
    var tplArray  = scope.options.template.replace(/[\s,]+/g, ',').split(',');

    I've found this kind of indentation hard to maintain. If you have a new variable with a longer name you have to modify several other lines too to keep it nice. It could also cause unnecessary patch/merge conflicts.

  3. Comments on the closing curly braces are unnecessary and disturbing. Modern IDEs and editors could show blocks.

    }; //findLinks

    “// …” comments at end of code block after } - good or bad?


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.