Friday, April 4, 2014

How to find, detect and show Wikipedia links for the concepts and named entity entities like person, place, organization or any such meaningful noun and keyword present in or related to any article or text?

It's a common practice that we have noticed among people who read news to expand their knowledge on daily basis to search on Wikipedia (or any such online or offline encyclopedia) for some new name of some person, place, organization, event or any meaningful thing - which we usually call as name, concept, topic, noun or named entity - that they just came across through some article, blog or any visual piece of artifact.

Here by an article I mean any kind of audio-visual media element consisting of text, image, video, sound clip, song etc.

It will be very convenient for these people if the links to some online encyclopedia like Wikipedia for the concepts or important keywords related to the subject matter being discussed by that artifact are shown near the article, so that viewer can just click on that concept's link to learn more about it.

To explain this use case and ideas on how to implement this with state of the art technologies, I will take an example of a story about Amazon launching FireTV as a next generation TV and Gaming console.

For example here is some text taken from an article on CNET and I have shown how some concepts mentioned in the article or related to the matter being discussed in the article can be shown near and/or in the article and linked to the appropriate pages on Wikipedia:

...

Games like Badland are eye-popping and sharp, and even games like The Walking Dead, while clearly a big step down in graphics detail from the PC version, animates well and looks very watchable. Content looks upscaled, even when not in 1080p, to look like, to a casual observer, it's coming from an Xbox or PlayStation.

...

Learn more about related concepts on Wikipedia:


In addition to the actual content publisher, the news aggregators like CheckDeckGoogle News etc. can also provide this feature along with whatever small title, description, feed content that they show for each article.

Here I am showing steps that a programmer can implement to provide this Wikipedia concepts linking functionality to enhance the knowledge gaining experience of the user.

Assuming that you have the text of the article that you want to enhance by annotating some words present in the article and or some what related to the subject being discussed in the article.

The text for e.g. is:
  
Games like Badland are eye-popping and sharp, and even games like The Walking Dead, while clearly a big step down in graphics detail from the PC version, animates well and looks very watchable. Content looks upscaled, even when not in 1080p, to look like, to a casual observer, it's coming from an Xbox or PlayStation.

Step 1. Finding concepts which are present in some component of the article


Annotate the text with some open source software library DBPedia Spotlight. To explain the concept with simplicity, I am giving an example using its free web service endpoint (as explained here) with Linux command line tool curl. In your program, this can be implemented using HTTP client libraries available for various programming languages. Like in Java, you can use Apache HTTPComponents.

curl http://spotlight.dbpedia.org/rest/annotate \ --data-urlencode "text=Games like Badland are eye-popping and sharp, and even games like The Walking Dead, while clearly a big step down in graphics detail from the PC version, animates well and looks very watchable. Content looks upscaled, even when not in 1080p, to look like, to a casual observer, it's coming from an Xbox or PlayStation." \ --data "confidence=0.2" \ --data "support=20" -H "Accept: application/json"

The response of the command from web service looks like:

{
  "@text": "Games like Badland are eye-popping and sharp, and even games like The Walking Dead, while clearly a big step down in graphics detail from the PC version, animates well and looks very watchable. Content looks upscaled, even when not in 1080p, to look like, to a casual observer, it's coming from an Xbox or PlayStation.",
  "@confidence": "0.2",
  "@support": "20",
  "@types": "",
  "@sparql": "",
  "@policy": "whitelist",
  "Resources":   [
        {
      "@URI": "http://dbpedia.org/resource/The_Walking_Dead",
      "@support": "108",
      "@types": "Freebase:/comic_books/comic_book_story,Freebase:/comic_books,Freebase:/comic_books/comic_book_series,Freebase:/fictional_universe/work_of_fiction,Freebase:/fictional_universe",
      "@surfaceForm": "Walking Dead",
      "@offset": "70",
      "@similarityScore": "0.09552688896656036",
      "@percentageOfSecondRank": "0.6057624587389117"
    },
    {
      "@URI": "http://dbpedia.org/resource/PlayStation",
      "@support": "2228",
      "@types": "Freebase:/business/product_line,Freebase:/business,Freebase:/exhibitions/exhibition_sponsor,Freebase:/exhibitions",
      "@surfaceForm": "PlayStation",
      "@offset": "306",
      "@similarityScore": "0.1963232457637787",
      "@percentageOfSecondRank": "0.8996430368659503"
    } ,
...
]
}

The response is in JSON format and you can parse it in your favorite programming language with some parser library, for e.g. Gson for Java.

The most important field present in this JSON object is Resources. The Resources is an array of object where each of the object represents a named entity (or concept) detected to be present in the supplied text as input to the DBPedia Spotlight service through HTTP request just explained above (with curl tool) and it contains two important fields: @URI and @offset. The @URI refers to the concept present in DBPedia dataset which is essentially a structured version of human readable Wikipedia pages. The @offset represents the position of the word (like PlayStation) in the given text passed as input for which the matching semantic concept(like 
http://dbpedia.org/resource/PlayStation) was just derived from DBPedia and so @offset can be used for underlining or highlighting the words present in article that we aim to link with appropriate Wikipedia page. DBPedia provides programmatic interface to query the database through SPARQL query language which is very similar to SQL. Our goal is to get the Wikipedia page link( like http://en.wikipedia.org/wiki/Playstation) from which this concept has been derived in DBPedia( like http://dbpedia.org/resource/PlayStation) and so this can be done by firing following  SPARQL query for given @URI against publicly accessible DBPedia's SPARQL endpoint an interactive as well as programmatic interface.

select distinct ?WikipediaLink 
from <http://dbpedia.org>
where
<http://dbpedia.org/resource/PlayStation> <http://www.w3.org/ns/prov#wasDerivedFrom> ?WikipediaLink
}

The result is a table containing a single field 
WikipediaLink with value that looks like: http://en.wikipedia.org/wiki/PlayStation?oldid=548612253

Yay! We got a Wikipedia page's link to a concept called PlayStation present in our article!

Replace the 
http://dbpedia.org/resource/PlayStation part of the SPARQL query mentioned above with some other DBPedia concept like http://dbpedia.org/resource/Xbox and you will get Wikipedia page link corresponding to that other concept like http://wikipedia.org/wiki/Xbox in the response.

To programmatically query the SPARQL endpoint like http://dbpedia.org/sparql or 
semantic web database like Virtuoso (which allows you to load and query DBPedia dataset); you can use a library or framework which can act as query engine interface between  and your application program. In Java you have Apache Jena Framework with its ARQ module for processing SPARQL query, just like we have JDBC driver for querying RDBMS through SQL. You can easily find tutorials of getting started with SPARQL and Apache Jena online.

We have to repeat this process of retrieving Wikipedia page link from DBPedia @URI for each of the objects present in @Resources array present in the JSON response received from DBPedia Spotlight service. Remember that we also have got a useful field @offset in each of the object in 
@Resources array, which will be used to highlight/underline and link the appropriate word present in the article text with its corresponding link to the page on Wikipedia.

It's just a matter of choice whether to show the concepts linked to Wikipedia Pages by underlying them as words present in the text itself or displaying them separately as related tags (/words/concepts/topics/nouns whatever you call!) near the article's content like title, description, image or feed content etc.

It’s also matter of choice whether to actually link these extracted concepts with Wikipedia or just to show them as mechanism for facilitating user to easily navigate through various content on your application or site by linking it with your internal pages of application or any application or site other than Wikipedia that you want.


Step 2. Finding and showing concepts (with links to corresponding pages on Wikipedia) that are not present in some component of given article but which might be related to the subject matter being discussed by the article

If you are a news aggregator, you probably have access to the 'similar' articles that are talking about the same subject matter being discussed by the given article; in our case for e.g. Amazon launching FireTV. So if you want to show links to those Wikipedia Pages whose corresponding concepts or words are not explicitly mentioned in the article( and therefore almost no chance of getting detected in the Step 1) but they might be related to the subject matter being discussed by the given article, then you can consider what are the concepts with Wikipedia links derived from the articles similar to the given article and include some of those concepts with Wikipedia links in this article.

For example here is an article from BBC (let's call it Article no. 2) which talks around the same story but discusses some different aspect of it. So let's say as a news aggregator your have processed this Article no. 2 also and retrieved Wikipedia Page links from it; one of which is Netflix. So if your system somehow believes that this article is similar to the earlier one from CNET (let's call it Article no. 1), then you can show the concepts with Wikipedia links derived from Article no. 2 with Article no. 1 and vice versa. If you have a pool of articles similar to an article, then you can maintain a pool of concepts with Wikipedia Links derived from those articles. Then you can choose some of those concepts with the highest frequency of appearance/detection across all these similar articles from that pool and show them with the given article as related concepts with Wikipedia links.



I hope I have made it understandable for any programmer with some introductory knowledge of semantic web, databases, HTTP RESTful services to easily annotate the articles with explicitly present and related concepts' Wikipedia links for making the knowledge gaining experience more convenient for the user.



Saturday, January 25, 2014

How to build an interface with flexible horizontal slider for browsing through multiple stories at a time, like the one used by CheckDeck CheckDeck.com?

In the name of CheckDeck, we are building a beautiful place to discover new exciting things everyday. It's first product Storiesallows you to explore exciting stories from all around the world just at one place.

To create a pleasant content discovery experience, we have taken extreme care and put effort to build a simple and wonderful user interface. Some of the noteworthy features that you will find on the home page of CheckDeck Stories are:
  1. A layout which tries to fully utilize your display screen width and height by arranging the content dynamically based on the dimensions of your screen. Specifically,
    1. It takes into consideration the width of your display by calculating the number of story tiles it can fit to be displayed at a time. When your browser's window is resized, it will recalculate the number of stories which can be shown in each horizontal row per topic and rearrange the layout accordingly.
    2. The layout tries to fill up the whole window in vertical dimension by showing stories from more number of topics until the screen is filled up vertically with stories. As you scroll to the bottom, stories on more topics will be loaded and displayed.
  2. It allows you to browse through large number of stories on each topic without changing the page by presenting them in what we can consider as being inspired from a slider or gallery like layout which is quite popular in photo sharing sites to create a visually delightful experience in addition to facilitating a quick browsing experience. Specifically, 
    1. We show two button like icons on both sides of the array of story tiles in each row. By clicking on the right arrow icon(when visible), it will show you stories next in the order. By clicking on the left arrow icon(when visible), will show you previous stories in the order. The icon with cloud like symbol with a downward pointing arrow just after clicking on any of the left or right arrow icons suggests that there is some activity being carried out (like making the transition of stories by replacing the current set of stories by next or previous ones, preloading images for the stories next in the order, loading next stories further down the line in order on this topic or any other activity) which needs to be completed before re enabling the left and/or right icons to allow the user to browse the stories again in either direction.
    2. To make the layout touch screen friendly, we have enabled stories browsing by swiping over the stories row to browse to left or right for viewing next or previous stories in order.
    3. When clicked on any story tile, it opens a view where we display the essential details of the article like title, published time, source, RSS/Atom feed content and description etc. We also link the title displayed in this view to open the article on its publishers site in new tab.
I am sharing with you the file from CheckDeck which currently arranges the layout of which I am talking about. Forgive me about formatting, I will make a viewer friendly formatting of this code as soon as get chance.
  • It uses require.js as the inter module dependency manager.
  • The other modules being included as a dependency contribute for downloading content etc. But the ideas which I am talking about is mainly included in this code snippet.
  • It uses jQuery's drop effect for transition of stories, other animations can also be used like swipe effect.
  • We have implemented this in JavaScript. But the logic of this technique can also be adapted in different programming languages to create similar visual effect on various platforms like Android, iOS, Windows etc.
  • If time permits, I will share this code something like a jQuery plugin which can be used and edited as per your application specific requirements. Till then, I hope this will work as a reference implementation for you.
define(["./entitiesData", "./windowResized", "./friendlyPublishedTime", "./loadingImages", "./storiesOnEntityData", "./browserCompatibility", "./header"], function(entitiesData, windowResized, friendlyPublishedTime, loadingImages, storiesOnEntityData, browserCompatibility, header) {
    var headerHeight = header.getHeight();
    var numberOfStoriesToBeDisplayed;
    var numberOfDisplayableStoryTiles;
    var storyTileWidth = 250;
    var spacingBetweenStoryTiles = 10;
    var buttonWidth = 64;
    var totalSpacingAroundEachButton = 14;
    var indicatorStripWidth = 3;
    var marginForScrollbar = 20;
    var storyRowBigRepresentiveImageWidth = 100;
    var storyRowTinyRepresentiveImageWidth = 64;
    var viewDOM;
    var layoutToBeShown;
    var isTinyScreen;
    var numberOfStoryRowsToBeDisplayedPerEntity = 3;

    function show(viewDOMReceived, viewInformation) {
        viewDOM = viewDOMReceived;
        header.showMainMenu();
        viewDOM.attr("id", "homeView").append(

        $("<table id='storiesOnEntitiesTable'/>")

        );
        arrangeLayout();
        entitiesData.getFollowedEntities(function(error, entities) {
            if (error) {
                console.error("When requested to get followed entities received: " + error.stack);
            }
            else {
                storiesOnEntityData.startPreparingStories(entities, function(error) {
                    if (error) {
                        console.error("When requested to start preparing stories on entities:" + JSON.stringify(entities, undefined, 2) + " received: " + error.stack);
                    }
                });
                entities.forEach(function(entity) {
                    entity.hasBeenDisplayed = false;
                });
                if (viewInformation && viewInformation.entityBeingExplored) {
                    (function showOneMoreEntity() {
                        showStoriesOnMoreEntities(1, function() {
                            console.log("Shown stories on one more entity.");
                            if (viewInformation.entityBeingExplored.hasBeenDisplayed) {
                                fillUpWindow(function() {
                                    $(window).scrollTop(viewInformation.scrollTop);
                                    arrangeLayout();
                                });
                            }
                            else {
                                showOneMoreEntity();
                            }
                        });
                    })();
                }
                else {
                    fillUpWindow(function() {
                        arrangeLayout();
                    });
                }
            }
        });
    }

    function handleViewResized() {
        // if ($("#storiesOnEntitiesTable").height() + headerHeight < window.innerHeight) {
        //     fillUpWindow();
        // }
        arrangeLayout();

        $("#storiesOnEntitiesTable .entityRow").remove();
        entitiesData.getFollowedEntities(function(error, entities) {
            if (error) {
                console.error("When requested to get followed entities received: " + error.stack);
            }
            else {
                entities.forEach(function(entity) {
                    if (entity.hasBeenDisplayed) {
                        displayEntityRow(entity);
                    }
                });
            }
        });
    }

    function arrangeLayout() {
        numberOfDisplayableStoryTiles = getNumberOfDisplayableStoryTiles();

        if ($(window).innerWidth() > 700) {
            layoutToBeShown = 'tile';
            isTinyScreen = false;
            $("head").append(

            $("<style type='text/css'/>").text(

            "#storiesOnEntitiesTable, #storiesOnEntitiesTable .storiesOnEntityTable {width: auto}" +

            "#storiesOnEntitiesTable .entityRow {height: 433px}" +

            ".cellOfButtonForBrowsingStories {display: table-cell}" +

            "#storiesOnEntitiesTable .entityRow .entityBox .header .emptyCell {display: table-cell}" +

            "#storiesOnEntitiesTable .entityRow .entityBox .header .emptyCell {display: table-cell}"

            )

            );
        }
        else {
            layoutToBeShown = 'row';
            if ($(window).innerWidth() < 500) {
                isTinyScreen = true;
                $("head").append(

                $("<style type='text/css'/>").text(

                "#storiesOnEntitiesTable, #storiesOnEntitiesTable .storiesOnEntityTable {width: 100%}" +

                ".storyRowsTable{width: " + (viewDOM.innerWidth() - 2 * (indicatorStripWidth) - marginForScrollbar) + "px}" +

                ".storyRowsTable .storyRow .textualDetailsOfStoryCell{max-width: " + (viewDOM.innerWidth() - 2 * (indicatorStripWidth) - storyRowTinyRepresentiveImageWidth - marginForScrollbar) + "px}" +

                "#storiesOnEntitiesTable .entityRow {height: auto}"

                )

                );
            }
            else {
                isTinyScreen = false;
                $("head").append(

                $("<style type='text/css'/>").text(

                "#storiesOnEntitiesTable, #storiesOnEntitiesTable .storiesOnEntityTable {width: 100%}" +

                ".storyRowsTable{width: " + (viewDOM.innerWidth() - 2 * (buttonWidth + totalSpacingAroundEachButton) - marginForScrollbar) + "px}" +

                ".storyRowsTable .storyRow .textualDetailsOfStoryCell{max-width: " + (viewDOM.innerWidth() - 2 * (buttonWidth + totalSpacingAroundEachButton) - storyRowBigRepresentiveImageWidth - marginForScrollbar) + "px}" +

                " #storiesOnEntitiesTable .entityRow {height: auto}"

                )

                );
            }
        }
    }

    function fillUpWindow(doneFillingUpWindow) {
        showStoriesOnMoreEntities(3, function() {
            if ($("#storiesOnEntitiesTable").height() + headerHeight < $(window).innerHeight() + marginForScrollbar) {
                fillUpWindow(doneFillingUpWindow);
            }
            else if (typeof doneFillingUpWindow === 'function') {
                doneFillingUpWindow();
            }
        });
    }

    function handleWindowScrolled() {
        var y = $(window).scrollTop(); // (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
        if (($(window).innerHeight() + marginForScrollbar + y) >= $("#storiesOnEntitiesTable").height() + headerHeight) {
            console.log("Reached to bottom of the display");
            showStoriesOnMoreEntities(3);
        }
        else {
            console.log("We haven't yet reached to the bottom.");
        }
    }

    var ongoingShowingStoriesOnMoreEntities = false;

    function showStoriesOnMoreEntities(numberOfMoreEntitiesToBeShown, doneShowingStoriesOnMoreEntities) {
        if (!ongoingShowingStoriesOnMoreEntities) {
            ongoingShowingStoriesOnMoreEntities = true;
            entitiesData.getFollowedEntities(function(error, entities) {
                if (error) {
                    console.error("When requested to get followed entities received: " + error.stack);
                }
                else {
                    for (var i = 0; i < entities.length && numberOfMoreEntitiesToBeShown > 0; i++) {
                        if (!entities[i].hasBeenDisplayed) {
                            displayEntityRow(entities[i], i);
                            entities[i].hasBeenDisplayed = true;
                            numberOfMoreEntitiesToBeShown--;
                        }
                    }
                }
                ongoingShowingStoriesOnMoreEntities = false;
                if (typeof doneShowingStoriesOnMoreEntities === 'function') {
                    doneShowingStoriesOnMoreEntities();
                }
            });
        }
    }
    var imagesStorageLocation = $("meta[name=imagesStorageLocation]").attr("content");

    function displayEntityRow(entity, position) {
        var entityRowDOM = getEntityRowHTML();
        if ($("#storiesOnEntitiesTable > .entityRow").length) {
            $("#storiesOnEntitiesTable > .entityRow").eq(position - 1).after(entityRowDOM);
        }
        else {
            $("#storiesOnEntitiesTable").append(entityRowDOM);
        }

        function getEntityRowHTML() {

            return $("<tr class='entityRow' data-entityID='" + entity.entityID + "'/>").append(

            $("<td/>").append(

            $("<table class='entityBox'/>").append(

            $("<tr class='header'/>").append(

            $("<td class='emptyCell'/>")

            ).append(

            $("<td/>").append(

            $("<table class='entityHeaderTable'/>").append(

            $("<tr/>").append(

            $("<td/>").append(

            $("<span class='entityName'/>").text(entity.name)

            )

            )

            )


            )

            ).append(

            $("<td class='emptyCell'/>")

            )

            )

            )

            );

        }
        entityRowDOM.find(".entityName").click(function() {
            callbacksToHandleRequestForOpeningExploreStoriesOnEntityView.forEach(function(callback) {
                callback({
                    entityBeingExplored: entity,
                    scrollTop: $(window).scrollTop()
                }, entity);
            });
        });
        (function showStoriesOnEntity() {
            var browsableStoriesOnEntityRow = $("<tr class='storiesOnEntityBrowser'/>").append(

            $("<td class='cellOfButtonForBrowsingStories'/>").addClass(isTinyScreen ? 'indicatorStrip' : 'roundButton').append(

            $("<span class='leftButtonForBrowsingStories hidden'/>")

            )

            );

            $("#storiesOnEntitiesTable .entityRow[data-entityID='" + entity.entityID + "'] .entityBox").append(

            browsableStoriesOnEntityRow

            );

            var noStoriesMessage = "Sorry, we don't have any stories at present on this.";
            browsableStoriesOnEntityRow.append(

            $("<td class='loadingStoriesMessageCell'/>").append(

            $("<span class='loadingStoriesOnEntityMessage'/>").text("Loading stories")

            )

            );
            storiesOnEntityData.loadStories(entity, function(error) {
                browsableStoriesOnEntityRow.find(".loadingStoriesMessageCell").remove();
                if (error || entity.stories.length === 0) {
                    browsableStoriesOnEntityRow.append(

                    $("<td/>").append(

                    $("<span class='noStoriesOnEntityMessage'/>").text(noStoriesMessage)

                    )

                    );

                    browsableStoriesOnEntityRow.append(

                    $("<td class='cellOfButtonForBrowsingStories'/>").addClass(isTinyScreen ? 'indicatorStrip' : 'roundButton').append(

                    entity.currentStoryBrowsingPosition > 0 ? $("<span class='leftButtonForBrowsingStories disabled'/>") : $("<span class='leftButtonForBrowsingStories hidden'/>")

                    )

                    );
                    $("#storiesOnEntitiesTable .entityRow[data-entityID='" + entity.entityID + "'] .entityBox").append(

                    browsableStoriesOnEntityRow

                    );
                }
                else {
                    var storiesOnEntityDOMElement = $("<table class='storiesOnEntityTable'/>").append(

                    $("<tr class='storiesOnEntityRow'/>")

                    );
                    browsableStoriesOnEntityRow.append(

                    $("<td/>").append(

                    storiesOnEntityDOMElement

                    )

                    );
                    if (typeof entity.currentStoryBrowsingPosition !== 'number') {
                        entity.currentStoryBrowsingPosition = 0;
                    }
                    var sourceEntitiesToStartPreparingStoriesFor = [];
                    var i = entity.currentStoryBrowsingPosition;
                    var remainingNumberOfStoriesToBeDisplayed;
                    var story;
                    if (layoutToBeShown === 'tile') {
                        remainingNumberOfStoriesToBeDisplayed = numberOfStoriesToBeDisplayed = numberOfDisplayableStoryTiles;
                        while (i < entity.stories.length && remainingNumberOfStoriesToBeDisplayed > 0) {
                            story = entity.stories[i];
                            storiesOnEntityDOMElement.find(".storiesOnEntityRow").append(

                            $("<td class='storyCell'/>").append(

                            $("<span class='storyTile'/>").html(

                            getHTMLForStoryTile(entity, story)

                            )

                            )

                            );
                            sourceEntitiesToStartPreparingStoriesFor.push({
                                entityID: story.sourceEntityID,
                                type: "source",
                                name: story.sourceName
                            });
                            i++;
                            remainingNumberOfStoriesToBeDisplayed--;
                        }
                    }
                    else {
                        //layoutToBeShown === 'row'
                        remainingNumberOfStoriesToBeDisplayed = numberOfStoriesToBeDisplayed = numberOfStoryRowsToBeDisplayedPerEntity;
                        var storyRowsTableDOM = $("<table class='storyRowsTable'/>");
                        storiesOnEntityDOMElement.find(".storiesOnEntityRow").append(storyRowsTableDOM);
                        while (i < entity.stories.length && remainingNumberOfStoriesToBeDisplayed > 0) {
                            story = entity.stories[i];

                            storyRowsTableDOM.append(

                            $("<tr class='storyRow'/>").html(

                            getHTMLForStoryRow(entity, story)

                            )

                            );
                            sourceEntitiesToStartPreparingStoriesFor.push({
                                entityID: story.sourceEntityID,
                                type: "source",
                                name: story.sourceName
                            });
                            i++;
                            remainingNumberOfStoriesToBeDisplayed--;
                        }
                    }
                    storiesOnEntityData.startPreparingStories(sourceEntitiesToStartPreparingStoriesFor, function(error) {
                        if (error) {
                            console.error("When requested to start preparing stories on entities:" + JSON.stringify(sourceEntitiesToStartPreparingStoriesFor, undefined, 2) + " received: " + error.stack);
                        }
                    });
                    browsableStoriesOnEntityRow.append(

                    $("<td class='cellOfButtonForBrowsingStories'/>").addClass(isTinyScreen ? 'indicatorStrip' : 'roundButton').append(

                    entity.stories.length > numberOfStoriesToBeDisplayed + entity.currentStoryBrowsingPosition ? $("<span class='rightButtonForBrowsingStories disabled'/>") : $("<span class='rightButtonForBrowsingStories hidden'/>")

                    )

                    );
                    var enableButtonForBrowsing = function(buttonDOM) {
                        buttonDOM.addClass("enabled").removeClass("disabled").removeClass("hidden");
                    };

                    var disableButtonForBrowsing = function(buttonDOM) {
                        buttonDOM.removeClass("enabled").addClass("disabled").removeClass("hidden");
                    };

                    var hideButtonForBrowsing = function(buttonDOM) {
                        buttonDOM.removeClass("enabled").removeClass("disabled").addClass("hidden");
                    };
                    entity.browsingToRightEnabled = false;
                    if (entity.stories.length > numberOfStoriesToBeDisplayed + entity.currentStoryBrowsingPosition) {
                        var storiesWithImages = [];
                        for (var m = entity.currentStoryBrowsingPosition + numberOfStoriesToBeDisplayed; m < entity.stories.length && m < (entity.currentStoryBrowsingPosition + 2 * numberOfStoriesToBeDisplayed); m++) {
                            if (typeof entity.stories[m].imageR1 === 'string' && entity.stories[m].imageR1.trim().length) {
                                storiesWithImages.push(entity.stories[m]);
                            }
                        }
                        if (isTinyScreen) {
                            //Don't wait for images to be loaded before enabling
                            //browse to right button
                            loadingImages.loadRepresentativeImages(storiesWithImages, "imageR1", function() {});
                            enableButtonForBrowsing(browsableStoriesOnEntityRow.find(".rightButtonForBrowsingStories"));
                            entity.browsingToRightEnabled = true;
                        }
                        else {
                            loadingImages.loadRepresentativeImages(storiesWithImages, "imageR1", function() {
                                enableButtonForBrowsing(browsableStoriesOnEntityRow.find(".rightButtonForBrowsingStories"));
                                entity.browsingToRightEnabled = true;
                            });
                        }
                    }
                    if (entity.currentStoryBrowsingPosition > 0) {
                        enableButtonForBrowsing(browsableStoriesOnEntityRow.find(".leftButtonForBrowsingStories"));
                        entity.browsingToLeftEnabled = true;
                    }
                    else {
                        entity.browsingToLeftEnabled = false;
                    }
                    var browseStories = function(direction, doneBrowsingStories) {
                        var sourceEntitiesToStartPreparingStoriesFor = [];
                        var remainingNumberOfStoriesToBeDisplayed = numberOfStoriesToBeDisplayed;
                        if (layoutToBeShown === 'row') {
                            var storyRowsTableDOM = $("<table class='storyRowsTable'/>");

                            for (var l = entity.currentStoryBrowsingPosition; remainingNumberOfStoriesToBeDisplayed > 0 && l < entity.stories.length;) {
                                storyRowsTableDOM.append(

                                $("<tr class='storyRow'/>").html(getHTMLForStoryRow(entity, entity.stories[l]))

                                );
                                sourceEntitiesToStartPreparingStoriesFor.push({
                                    entityID: entity.stories[l].sourceEntityID,
                                    type: "source",
                                    name: entity.stories[l].sourceName
                                });
                                l++;
                                remainingNumberOfStoriesToBeDisplayed--;
                            }

                            $(browsableStoriesOnEntityRow).find(".storyRowsTable").effect(

                            "drop",

                            {
                                direction: direction === 'right' ? 'left' : 'right'
                            },

                            300,

                            function() {
                                $(this).remove();
                                $(browsableStoriesOnEntityRow).find(".storiesOnEntityRow").append(storyRowsTableDOM.hide().fadeIn(300));
                                doneBrowsingStories();
                            });
                        }
                        else {
                            $(browsableStoriesOnEntityRow).find(".storyCell").addClass("storyToBeHidden");

                            var remainingNumberOfStoriesToBeDropped = $(browsableStoriesOnEntityRow).find(".storyToBeHidden").length;

                            var checkForRemovalOfAllTheStoriesToBeDropped = function() {
                                remainingNumberOfStoriesToBeDropped--;
                                if (remainingNumberOfStoriesToBeDropped === 0) {
                                    doneBrowsingStories();
                                }
                            }

                            for (var k = entity.currentStoryBrowsingPosition; remainingNumberOfStoriesToBeDisplayed > 0 && k < entity.stories.length;) {
                                if ($(browsableStoriesOnEntityRow).find(".storyToBeHidden").length) {

                                    $(browsableStoriesOnEntityRow).find(".storyToBeHidden").eq(0).find(".storyTileTable").css("z-index", "1").effect(

                                    "drop",

                                    {
                                        direction: direction === 'right' ? 'left' : 'right'
                                    },

                                    300,

                                    function() {
                                        $(this).remove();
                                        checkForRemovalOfAllTheStoriesToBeDropped();
                                    }

                                    );
                                    $(browsableStoriesOnEntityRow).find(".storyToBeHidden").eq(0).removeClass("storyToBeHidden").find(".storyTile").append(getHTMLForStoryTile(entity, entity.stories[k]));
                                }
                                else {
                                    //Add storyCells if required
                                    $(browsableStoriesOnEntityRow).find(".storiesOnEntityRow").append($("<td class='storyCell'/>").append($("<span class='storyTile'/>").html(getHTMLForStoryTile(entity, entity.stories[k]))));
                                }
                                sourceEntitiesToStartPreparingStoriesFor.push({
                                    entityID: entity.stories[k].sourceEntityID,
                                    type: "source",
                                    name: entity.stories[k].sourceName
                                });
                                k++;
                                remainingNumberOfStoriesToBeDisplayed--;
                            }
                            //remove remaining storyCells to be hidden
                            if ($(browsableStoriesOnEntityRow).find(".storyToBeHidden").length) {
                                $(browsableStoriesOnEntityRow).find(".storyToBeHidden").find(".storyTileTable").effect("drop", {
                                    direction: direction === 'right' ? 'left' : 'right'
                                },
                                300,

                                function() {
                                    $(browsableStoriesOnEntityRow).find(".storyToBeHidden").remove();
                                    checkForRemovalOfAllTheStoriesToBeDropped();
                                });
                            }
                            storiesOnEntityData.startPreparingStories(sourceEntitiesToStartPreparingStoriesFor, function(error) {
                                if (error) {
                                    console.error("When requested to start preparing stories on entities:" + JSON.stringify(sourceEntitiesToStartPreparingStoriesFor, undefined, 2) + " received: " + error.stack);
                                }
                            });
                        }

                    };

                    var browsingToLeftButtonDOM = $(browsableStoriesOnEntityRow).find(".leftButtonForBrowsingStories");
                    var browsingToRightButtonDOM = $(browsableStoriesOnEntityRow).find(".rightButtonForBrowsingStories");
                    var handleRequestForBrowsingToRight = function(event) {
                        if (entity.browsingToRightEnabled) {
                            entity.browsingToRightEnabled = false;
                            disableButtonForBrowsing(browsingToLeftButtonDOM);
                            entity.browsingToLeftEnabled = false;
                            disableButtonForBrowsing(browsingToRightButtonDOM);
                            entity.currentStoryBrowsingPosition += numberOfStoriesToBeDisplayed;
                            entity.doneWithTransitionOfStories = false;
                            var enableOrDisableButtonsForBrowsing = function() {
                                if (entity.doneWithTransitionOfStories) {
                                    entity.browsingToLeftEnabled = true;
                                    enableButtonForBrowsing(browsingToLeftButtonDOM);
                                    if (entity.currentStoryBrowsingPosition + numberOfStoriesToBeDisplayed < entity.stories.length) {
                                        if (isTinyScreen) {
                                            entity.browsingToRightEnabled = true;
                                            enableButtonForBrowsing(browsingToRightButtonDOM);
                                        }
                                        else if (entity.doneWithLoadingImagesOfNextStories) {
                                            entity.browsingToRightEnabled = true;
                                            enableButtonForBrowsing(browsingToRightButtonDOM);
                                        }
                                    }
                                }
                            };
                            browseStories("right", function() {
                                entity.doneWithTransitionOfStories = true;
                                enableOrDisableButtonsForBrowsing();
                            });
                            if (entity.currentStoryBrowsingPosition + numberOfStoriesToBeDisplayed < entity.stories.length) {
                                entity.doneWithLoadingImagesOfNextStories = false;
                                var storiesWithImages = [];
                                for (var i = entity.currentStoryBrowsingPosition + numberOfStoriesToBeDisplayed; i < entity.stories.length && i < (entity.currentStoryBrowsingPosition + 2 * numberOfStoriesToBeDisplayed); i++) {
                                    if (typeof entity.stories[i].imageR1 === 'string' && entity.stories[i].imageR1.trim().length) {
                                        storiesWithImages.push(entity.stories[i]);
                                    }
                                }
                                loadingImages.loadRepresentativeImages(storiesWithImages, "imageR1", function() {
                                    entity.doneWithLoadingImagesOfNextStories = true;
                                    if (!isTinyScreen) {
                                        enableOrDisableButtonsForBrowsing();
                                    }
                                });
                            }
                            else {
                                hideButtonForBrowsing(browsingToRightButtonDOM);
                            }
                        }
                    };
                    Hammer($(browsableStoriesOnEntityRow).find(".storiesOnEntityRow")[0], {
                        swipe_velocity: 0.2
                    }).on("swipeleft", handleRequestForBrowsingToRight);
                    browsingToRightButtonDOM.on("click", handleRequestForBrowsingToRight);
                    var handleRequestForBrowsingToLeft = function() {
                        if (entity.browsingToLeftEnabled) {
                            entity.browsingToRightEnabled = false;
                            disableButtonForBrowsing(browsingToLeftButtonDOM);
                            entity.browsingToLeftEnabled = false;
                            disableButtonForBrowsing(browsingToRightButtonDOM);
                            entity.currentStoryBrowsingPosition = Math.max(entity.currentStoryBrowsingPosition - numberOfStoriesToBeDisplayed, 0);
                            entity.doneWithTransitionOfStories = false;
                            var enableOrDisableButtonsForBrowsing = function() {
                                if (entity.doneWithTransitionOfStories) {
                                    entity.browsingToRightEnabled = true;
                                    enableButtonForBrowsing(browsingToRightButtonDOM);
                                    if (Math.max(entity.currentStoryBrowsingPosition - numberOfStoriesToBeDisplayed, 0) !== entity.currentStoryBrowsingPosition) {
                                        entity.browsingToLeftEnabled = true;
                                        enableButtonForBrowsing(browsingToLeftButtonDOM);
                                    }
                                }
                            };
                            browseStories("left", function() {
                                entity.doneWithTransitionOfStories = true;
                                enableOrDisableButtonsForBrowsing();
                            });
                            if (Math.max(entity.currentStoryBrowsingPosition - numberOfStoriesToBeDisplayed, 0) !== entity.currentStoryBrowsingPosition) {}
                            else {
                                hideButtonForBrowsing(browsingToLeftButtonDOM);
                            }
                        }
                    };
                    Hammer($(browsableStoriesOnEntityRow).find(".storiesOnEntityRow")[0], {
                        swipe_velocity: 0.2
                    }).on("swiperight", handleRequestForBrowsingToLeft);
                    browsingToLeftButtonDOM.on("click", handleRequestForBrowsingToLeft);
                }
            });

        })();
    }

    function getHTMLForStoryTile(entity, story) {
        var storyTileTable = $("<table class='storyTileTable'/>");
        if (typeof story.imageR1 === 'string' && story.imageR1.trim().length) {
            storyTileTable.addClass("storyTileWithRepresentativeImage").append(

            $("<tr/>").append(

            $("<td/>").append(

            $("<span class='representativeImageWrapper'/>").append(

            $("<img class='representativeImage' src='http://" + imagesStorageLocation + "/" + story.imageR1 + "'/>")

            ).append(

            $("<a class='openArticleInNewTabIcon'/>").attr("href", story.articleURL).attr("target", "_blank").attr("title", "View full article").hide()

            )

            )

            )

            ).append(

            $("<tr/>").append(

            $("<td/>").append(

            $("<span class='title'/>").text(story.title)

            )

            )

            );

        }
        else {
            storyTileTable.addClass("storyTileWithoutRepresentativeImage").append(

            $("<tr class='emptyRowForMargin'/>")

            ).append(

            $("<tr/>").append(

            $("<td class='title'/>").text(story.title).append(

            $("<a class='openArticleInNewTabIcon'/>").attr("href", story.articleURL).attr("target", "_blank").hide()

            )

            )

            ).append(

            $("<tr class='emptyRowForMargin'/>")

            );
        }
        storyTileTable.append(

        $("<tr/>").append(

        $("<td class='sourceNameCell'/>").append(

        $("<span class='sourceName'/>").attr("data-entityID", story.sourceEntityID).attr("data-name", story.sourceName).text(story.sourceName)

        )

        )

        ).append(

        $("<tr/>").append(

        $("<td/>").append(

        $("<span class='publishedTime'/>").text(friendlyPublishedTime.getInFriendlyFormat(story.publishedDate))

        )

        )

        );

        storyTileTable.mouseenter(function() {
            $(this).find(".openArticleInNewTabIcon").fadeIn(300);
        });
        storyTileTable.mouseleave(function() {
            $(this).find(".openArticleInNewTabIcon").fadeOut(300);
        });
        storyTileTable.find(".representativeImage, .title").on("click", function(event) {
            callbacksToHandleRequestForOpeningFullArticleView.forEach(function(callback) {
                callback({
                    entityBeingExplored: entity,
                    scrollTop: $(window).scrollTop()
                }, entity, story);
            });
        });
        storyTileTable.find(".sourceName").click(function(event) {
            callbacksToHandleRequestForOpeningExploreStoriesOnEntityView.forEach(function(callback) {
                callback({
                    entityBeingExplored: entity,
                    scrollTop: $(window).scrollTop()
                }, {
                    entityID: $(event.target).attr("data-entityID"),
                    type: "source",
                    name: $(event.target).attr("data-name")
                });
            });
        });
        return storyTileTable;
    }

    function getHTMLForStoryRow(entity, story) {
        var storyRowCellDOMs = [];
        var imageToBeShown = null;
        if (isTinyScreen && typeof story.imageR3 === 'string' && story.imageR3.trim().length) {
            imageToBeShown = "imageR3";
        }
        else if (typeof story.imageR2 === 'string' && story.imageR2.trim().length) {
            imageToBeShown = "imageR2";
        }
        else if (typeof story.imageR1 === 'string' && story.imageR1.trim().length) {
            imageToBeShown = "imageR1";
        }
        if (imageToBeShown) {
            storyRowCellDOMs.push($("<td class='representativeImageCell'/>").append(

            $("<img class='representativeImage'/>").attr("src", "http://" + imagesStorageLocation + "/" + story[imageToBeShown]).addClass(isTinyScreen ? "forTinyScreen" : "forNotTinyScreen")

            ));
        }
        storyRowCellDOMs.push($("<td class='textualDetailsOfStoryCell' colspan='2'/>").append(

        $("<span class='title'/>").text(story.title)

        ).append(

        $("<span class='sourceName'/>").text(story.sourceName).attr("data-entityID", story.sourceEntityID).attr("data-name", story.sourceName)

        ).append(

        $("<span class='publishedTime'/>").text(friendlyPublishedTime.getInFriendlyFormat(story.publishedDate))

        ).append(

        $("<a class='openArticleInNewTabLink'/>").attr("href", story.articleURL).attr("target", "_blank").text("View full story")

        ));
        storyRowCellDOMs.forEach(function(storyRowCellDOM) {
            storyRowCellDOM.find(".representativeImage, .title").on("click", function(event) {
                callbacksToHandleRequestForOpeningFullArticleView.forEach(function(callback) {
                    callback({
                        entityBeingExplored: entity,
                        scrollTop: $(window).scrollTop()
                    }, entity, story);
                });
            });
            storyRowCellDOM.find(".sourceName").click(function(event) {
                callbacksToHandleRequestForOpeningExploreStoriesOnEntityView.forEach(function(callback) {
                    callback({
                        entityBeingExplored: entity,
                        scrollTop: $(window).scrollTop()
                    }, {
                        entityID: $(event.target).attr("data-entityID"),
                        type: "source",
                        name: $(event.target).attr("data-name")
                    });
                });
            });
        });

        return storyRowCellDOMs;
    }

    function getNumberOfDisplayableStoryTiles() {
        //Important: Keep this values in sync with those with style.css
        //to carry out accurate calculations for preparing layout

        var availableWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
        return Math.floor((availableWidth - 2 * (buttonWidth + totalSpacingAroundEachButton) - spacingBetweenStoryTiles) / (storyTileWidth + spacingBetweenStoryTiles));
    }


    var callbacksToHandleRequestForOpeningExploreStoriesOnEntityView = [];

    function registerCallbackToHandleRequestForOpeningExploreStoriesOnEntityView(callback) {
        callbacksToHandleRequestForOpeningExploreStoriesOnEntityView.push(callback);
    }

    var callbacksToHandleRequestForOpeningFullArticleView = [];

    function registerCallbackToHandleRequestForOpeningFullArticleView(callback) {
        callbacksToHandleRequestForOpeningFullArticleView.push(callback);
    }
    return {
        show: show,
        registerCallbackToHandleRequestForOpeningFullArticleView: registerCallbackToHandleRequestForOpeningFullArticleView,
        registerCallbackToHandleRequestForOpeningExploreStoriesOnEntityView: registerCallbackToHandleRequestForOpeningExploreStoriesOnEntityView,
        handleViewResized: handleViewResized,
        handleWindowScrolled: handleWindowScrolled
    };
});

Here are some of the snapshots from different versions of CheckDeck using this technique described above.
The newer version: (released on 26th January 2014)


The older version: (Released in March 2013)


Creative Commons License
Horizontal slider for browsing through multiple stories at a time by Atharva Patel for CheckDeck is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at http://CheckDeck.com.