dotnetco.de

Create a Recommendation box similar to LinkedIn using asp.net

If you are a member of LinkedIn (like me) you probably know the recommendation box at the upper right. It shows you some people you might know (according to LinkedIns algorithms). I like this plugin because it’s easy to use. As I needed to add a similar functionality to a new project I’ve investigated a bit and here is how to add such a plugin to your own aspx.net website.

Requirements

First let’s write down the requirements:

  • Box should display a photo and some text.
  • The profile page should be opened if the user clicks on the photo or text.
  • User should be able to skip proposals if they don’t know them.
  • Skiipped users should be logged so they won’t be proposed for the same user again.
  • License of involved plugins is important. It need not be freeware or open-source, but it should be usable in multiple projects without additional charge

Additionally, as this will be implemented into an existing project, the use of jQuery and Bootstrap is fine because it’s already in use in my project. For current article I removed all other resources I use in my project (e.g. jQuery UI) because they are not needed for current example. Even though e.g. the formatting is quite ok you should of course design the plugin according to your already in-use techniques.

 

The Slider

Instead of just removing a user I of course want to have some slide effects. Therefore first I checked for some existing slider solutions. As said, sliders based on jQuery are fine. Ah yes, there are hundreds of sliders…. I checked CodeCanyon, Github and jQuery. After testing some of them I decided to use bxSlider from Steven Wanderski. It’s released on Github under MIT License so it could be used whereever you want. Steven has some examples and a well documented list of options. Additionally the github repository was last updated 4 months ago so it’s up-to-date.

 

Actions

User has 2 options when a profile is displayed:

  1. Show user profile, or
  2. confirm that this profile should be ignored.

First one is easy: Just open the profile page of the displayed user. Then current user could decide whether he wants to contact this user or not. Of course it would also be possible to add a ‘Connect now’-Button already to the profile displayed by current plugin. I don’t need this in my project, but with the technique described below it would be easy for you to add it on your own.

So on to the second option: Ignore this user. This should not only show the next profile but of course it should store this information so that the currently displayed user profile should never be displayed for the current user again. Nothing could be more boring than ‘ignoring’ the same user again and again… Of course we don’t want a postback to the server as this would be time-consuming. Instead we use a webservice. So the bxSlider needs to provide a unique id to the webservices.

Get unique ID

bxSlider has an option ‘getCurrentSlideElement’ but unfortunately this does not seem to work well when called in action ‘onSlideBefore’. But thanks to the data-attribute of HTML5 it’s very easy to get the unique ID. bxSliders ‘onSlideBefore’ supplies 3 parameters: $slideElement, oldIndex and newIndex. $slideElement contains the next element to be displayed, so this does not help because we need the current / previous element. oldIndex contains the index of this previous element so that’s what we need. We add the data-attribute to the LI-Element and then grab it via oldIndex. This script, embedded in onSlideBefore, contains the value of the data-id attribute of the person to be ignored:

var id = $('ul.bxslider li:nth-child(' + (oldIndex + 1) + ')').data('id');

Take care: oldIndex is 0-based while nth-child starts at 1 so we need to increase oldIndex by 1 to get the correct nth child. So now that we have the id we could just call the webservice and supply the ID.

Update bxSlider CSS

While playing around I noticed that the text is not wrapped correct if the width of the slider is very small. As you see in the screenshot below, large words will not be displayed correct but appear on the next slide. Therefore I changed the css of bxslider according to my needs. I just added min-width: 200px; for class definition of .bx-viewport. This fits my needs but of course you should take care for your solution on your own.

This is the End

Next thing to decide: What to do at the end of the slider? Typically you don’t load thousands of profiles as this will waste bandwidth. But if you only show e.g. 4 profiles it’s very likely that the user reaches the end. So what to do now? First I’ve setup bxSlider to do no endless loop. So you can’t scroll any further if you reached the end. You would have different options now, e.g. show ‘Thank you, that’s it’ or load some more profiles to display. I’ve chosen the easiest option: Display ‘You’re done. Please reload to refresh’. So the user could click there and just reload the whole page. While reloading also the plugin will be refreshed with new data because we stored the users to be ignored and therefore new users will be retrieved. Of course you could make it more elegant and reload the data using a webservice but that’s not important for my current project so it’s up to you.

 

The webpage

The code should be easy to understand. Basically I have a Repeater-Control for the design. This Repeater is filled with some example data in ‘CreateDataTable’. Of course you should retrieve the data from your database later on. The Footer also contains the element which should be shown as last element when all others are processed.

Please also note the span-class ‘slider-next’ which shows the next profile when the user clicks it. I’ve defined it as a class instead of id so you could also add it into your repeater-control if you want to have the ‘Ignore’-Link on each profile instead of just below the box.

The script

On the webpage you also find 2 scripts. The first one initializes the bxSlider with some options, e.g. we don’t want to show the page numbers. The onSlideBefore retrieves the unique userid as explained above and the calls the procedure IgnoreThisUser, supplying the userid. IgnoreThisUser then calls the webservice.

 

The webservice

Webservice ‘Webservice1.asmx’ within subdirectory ‘Webservices’ has just one method ‘ProcessID’ which expects ‘id’ as String-Parameter. This is the unique ID of the user who should not be displayed for current user anymore so you should add your code there, e.g. store this info in your database.

 

That’s all

So now you should have a recommendation box similar to the one from LinkedIn

 

 

jquery.bxslider.css

.bx-wrapper .bx-viewport {
    -moz-box-shadow: 0 0 5px #ccc;
    -webkit-box-shadow: 0 0 5px #ccc;
    box-shadow: 0 0 5px #ccc;
    border:  5px solid #fff;
    left: -5px;
    background: #fff;
    min-width: 200px;
   }

Recommendation.aspx

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Recommendation.aspx.vb"
    Inherits="WebApplication1.Recommendation" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <script src="//code.jquery.com/jquery-1.11.0.min.js" type="text/javascript"></script>
    <!-- bxSlider Javascript file -->
    <script src="/js/jquery.bxslider.js"></script>
    <!-- bxSlider CSS file -->
    <link href="/css/jquery.bxslider.css" rel="stylesheet" />
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <title>Recommendation</title>
</head>
<body>
<h1>Recommendation box</h1>
    <div class="col-lg-3 col-md-3 col-sm-4 col-xs-6">
        <asp:Repeater ID="MyRepeater" runat="server">
            <HeaderTemplate>
                <ul class="bxslider">
            </HeaderTemplate>
            <ItemTemplate>
                <li data-id='<%# Eval("UserID") %>'><a href='<%# Eval("TargetURL") %>' target='_blank'>
                    <span class="col-lg-6 col-md-6 col-xs-6 col-sm-6">
                        <img style='height: 100%; width: 100%;' src='<%# Eval("ImageURL") %>' class='img-rounded'
                            id='ShowTopNavi_CurrentUserImage' />
                    </span><span class='col-lg-6 col-md-6 col-xs-6 col-sm-6'>
                        <p class='text-left'>
                            <strong>
                                <%# Eval("Username")%></strong></p>
                        <p class='text-left small'>
                            <%# Eval("AdditionalInfo")%></p>
                    </span></a></li>
            </ItemTemplate>
            <FooterTemplate>
                <li data-id='Restart'><a href='Recommendation.aspx'><span class="col-lg-6 col-md-6 col-xs-6 col-sm-6">
                </span><span class='col-lg-6 col-md-6 col-xs-6 col-sm-6'>
                    <p class='text-left'>
                        <strong>Restart</strong></p>
                    <p class='text-left small'>
                        Or implement any other action if you like</p>
                </span></a></li>
                </ul>
            </FooterTemplate>
        </asp:Repeater>
        <span class="slider-next"></span>
    </div>
    <script type="text/javascript">
        $(document).ready(function () {
            $('.bxslider').bxSlider({
                infiniteLoop: false,
                hideControlOnEnd: true,
                pager: false,
                nextSelector: '.slider-next',
                nextText: 'Skip this user →',
                onSlideBefore: function ($slideElement, oldIndex, newIndex) {
                    var id = $('ul.bxslider li:nth-child(' + (oldIndex + 1) + ')').data('id');
                    IgnoreThisUser(id);
                }
            });
        });
        function IgnoreThisUser(key) {
            $.ajax({
                type: "POST",
                url: '/Webservices/Webservice1.asmx/ProcessID',
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                data: JSON.stringify({
                    'id': key
                })
            });
        }
    </script>
</body>
</html>

 

Recommendation.aspx.vb

Public Class Recommendation
    Inherits System.Web.UI.Page
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not Page.IsPostBack Then
            MyRepeater.DataSource = CreateDatatable()
            MyRepeater.DataBind()
        End If
    End Sub
    Private Function CreateDatatable() As DataTable
        Dim dt As New DataTable
        dt.Columns.Add("UserID")
        dt.Columns.Add("Username")
        dt.Columns.Add("ImageURL")
        dt.Columns.Add("AdditionalInfo")
        dt.Columns.Add("TargetURL")
        Dim row As DataRow
        row = dt.NewRow
        row("UserID") = "ZuckerbergM"
        row("Username") = "Mark Zuckerberg"
        row("ImageURL") = "https://upload.wikimedia.org/wikipedia/commons/3/31/Mark_Zuckerberg_at_the_37th_G8_Summit_in_Deauville_018_v1.jpg"
        row("AdditionalInfo") = "Co-Founder of Facebook"
        row("TargetURL") = "https://en.wikipedia.org/wiki/Mark_Zuckerberg"
        dt.Rows.Add(row)
        row = dt.NewRow
        row("UserID") = "MuskE"
        row("Username") = "Elon Musk"
        row("ImageURL") = "https://upload.wikimedia.org/wikipedia/commons/0/04/Elon_Musk_-_The_Summit_2013.jpg"
        row("AdditionalInfo") = "CEO of SpaceX and Tesla Motors"
        row("TargetURL") = "https://en.wikipedia.org/wiki/Elon_Musk"
        dt.Rows.Add(row)
        row = dt.NewRow
        row("UserID") = "GatesB"
        row("Username") = "Bill Gates"
        row("ImageURL") = "https://upload.wikimedia.org/wikipedia/commons/0/01/Bill_Gates_July_2014.jpg"
        row("AdditionalInfo") = "Co-Founder of Microsoft"
        row("TargetURL") = "https://en.wikipedia.org/wiki/Bill_Gates"
        dt.Rows.Add(row)
        row = dt.NewRow
        row("UserID") = "DorseyJ"
        row("Username") = "Jack Dorsay"
        row("ImageURL") = "https://upload.wikimedia.org/wikipedia/commons/6/68/Jack_Dorsey_2012_Shankbone.JPG"
        row("AdditionalInfo") = "Co-Founder of Twitter"
        row("TargetURL") = "https://en.wikipedia.org/wiki/Jack_Dorsey"
        dt.Rows.Add(row)
        Return dt
    End Function
End Class

 WebService1.asmx.vb

Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
' To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
<System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
 Public Class WebService1
    Inherits System.Web.Services.WebService
    <WebMethod()> _
    Public Sub ProcessID(id As String)
        'Place your code here!
    End Sub
End Class

Leave a Comment