Creating Dashboards with NetAdvantage for jQuery and ASP.Net MVC 4

[Infragistics] Mihail Mateev / Monday, June 11, 2012

Developers always want to follow the latest technologies. Microsoft announced in June ASP.Net MVC 4 RC.  Perhaps many of you are asking: how new versions of ASP.Net MVC work with existing components. This blog is a quick review how you can use Infragistics jQuery controls with MVC 4.

The latest NetAdvantage for jQuery Vol. 12.1 includes many new exciting components and many feature enhancements to existing controls. You could find a detailed information about the existing controls in the Infragistics jQuery blogs.

One of the advantages to using  nice client components as those in NetAdvantage for jQuery is the ability to create  quickly beautiful dashboards in your WEB applications using pre-defined styles and themes. ASP.Net MVC 4 offers better UI and possibility to easily create applications in Metro Style.

 

Before to start:

You need to install ASP.Net MVC 4 RC and NetAdvantage for jQuery Vol. 12.1. This demo is created with Visual Studio 2010Sp1. It is possible to create the project to create a similar manner and with Visual Studio 2012 RC

 

Scripts and styles references

In ASP.Net MVC4 you could place Infragistics css and js files in the same place like in ASP.Net MVC 3 projects.
Infragistics  recommends :

  • styles to be placed under /Content/Infragistics folder
  • JavaScript files location is under /Scripts/Infragistics folder

You can use another location and change the path to your files

 

ASP.Net MVC4 – Bundling

One of the handy new features in ASP.NET MVC 4 is the ability to easily bundle your JavaScript and CSS.

What means Bundling: this is the process of combining all of your disparate js and css files into one file (one for js and one for css).
When you create a new ASP.NET MVC 4 application, you will find this code in the Application_Start() event in the Global.asax:

   1: protected void Application_Start()

   2: {

   3:     ....

   4:     BundleConfig.RegisterBundles(BundleTable.Bundles);

   5: }

 

You could configure your bundle using BundleConfig,cs

 

The code below demonstrates you you could add to your bundle Infragistics script loader.

   1: public class BundleConfig

   2: {

   3:     public static void RegisterBundles(BundleCollection bundles)

   4:     {

   5:         bundles.Add(new ScriptBundle("~/bundles/jquery").Include(

   6:                     "~/Scripts/jquery-1.*"));

   7:  

   8:         bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(

   9:                     "~/Scripts/jquery-ui*"));

  10:  

  11:         bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(

  12:                     "~/Scripts/jquery.unobtrusive*",

  13:                     "~/Scripts/jquery.validate*"));

  14:  

  15:         bundles.Add(new ScriptBundle("~/bundles/infragistics.loader").Include(

  16:                  "~/Scripts/Infragistics/js/infragistics.loader*"));

  17:  

  18:         bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(

  19:                     "~/Scripts/modernizr-*"));

  20:  

  21:         bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));

  22:  

  23:         bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(

  24:                     "~/Content/themes/base/jquery.ui.core.css",

  25:                     "~/Content/themes/base/jquery.ui.resizable.css",

  26:                     "~/Content/themes/base/jquery.ui.selectable.css",

  27:                     "~/Content/themes/base/jquery.ui.accordion.css",

  28:                     "~/Content/themes/base/jquery.ui.autocomplete.css",

  29:                     "~/Content/themes/base/jquery.ui.button.css",

  30:                     "~/Content/themes/base/jquery.ui.dialog.css",

  31:                     "~/Content/themes/base/jquery.ui.slider.css",

  32:                     "~/Content/themes/base/jquery.ui.tabs.css",

  33:                     "~/Content/themes/base/jquery.ui.datepicker.css",

  34:                     "~/Content/themes/base/jquery.ui.progressbar.css",

  35:                     "~/Content/themes/base/jquery.ui.theme.css"));

  36:     }

  37: }

If you prefer to add script and styles references without bundling it is possible to use the script and styles references in the same way like in ASP.Net MVC 3.  If you refer styles and scripts you need to know that the reference path starts from the site root – not from the current document location:

  • /Scripts/Infragistics/js/[my resource] – MVC 4

instead

  • ../../Scripts/Infragistics/js/[my resource] – MVC 3

If you want to use MVC wrappers there is another step:

MVC wrappers reference

In ASP.Net MVC 4 it is possible to use libraries, build against MVC 3. You can add reference to:

[NetAdvantage for jQuery install path]\MVC\MVC3\Bin\Infragistics.Web.Mvc.dll

 

Application design

The sample application is an ASP.Net MVC 4 Application, that uses Northwind sample database via Entity Framework. In the sample are used Infragistics jQuery Grid, Chart and Map (igGrid, igDataChart and igMap).

 

Application has views with dashboards about Northwind customers:

  • JavaScript dashboard (uses Infragistics jQuery widgets)
  • MVC dashboard (uses Infragistics MVC library)

 

Creating a dashboard using JavaScript

The easiest way to add Infragistics jQuery controls is via Infragistics Script Loader. This is the reason to include this library in the bundle. Infragistics Loader is used via JavaScript in the same way like in other types of projects (see the code below) . The only one difference is in the path used when load other scripts - path starts from the site root – not from the current document location!

   1: $.ig.loader({

   2:     scriptPath: "/Scripts/Infragistics/js/", //local files version

   3:     cssPath: "/Content/Infragistics/css/",

   4:     resources: "igGrid.Selection.Paging.Sorting,igMap,igDataChart.*",

   5:     theme: "metro"

   6:     });

 

Using Infragistics jQuery controls.

NetAdvantage for jQuery controls could be included via JavaScript in the same way like in other project types.

   1: $("#map").igMap({

   2:     width: "500px",

   3:     height: "500px",

   4:     panModifier: "control",

   5:     horizontalZoomable: true,

   6:     verticalZoomable: true,

   7:     windowResponse: "immediate",

   8:     overviewPlusDetailPaneVisibility: "visible",

   9:     seriesMouseLeftButtonUp: function (ui, args) {

  10:         var tets = args;

  11:     }

  12: });

 

The code below shows the whole script, used for JavaScript Dashboard

   1: <script type="text/javascript">

   2: jQuery.support.cors = true;

   3:     $.ig.loader({

   4:         scriptPath: "/Scripts/Infragistics/js/", //local files version

   5:         cssPath: "/Content/Infragistics/css/",

   6:         resources: "igGrid.Selection.Paging.Sorting,igMap,igDataChart.*",

   7:         theme: "metro"

   8:         });

   9:         $.ig.loader(function () {

  10:             var data = [];

  11:             var selected;

  12:  

  13:             $("#map").igMap({

  14:                 width: "500px",

  15:                 height: "500px",

  16:                 panModifier: "control",

  17:                 horizontalZoomable: true,

  18:                 verticalZoomable: true,

  19:                 windowResponse: "immediate",

  20:                 overviewPlusDetailPaneVisibility: "visible",

  21:                 seriesMouseLeftButtonUp: function (ui, args) {

  22:                     var tets = args;

  23:                 }

  24:             });

  25:  

  26:  

  27:             $("#chart").igDataChart({

  28:                 width: "650px",

  29:                 height: "220px",

  30:                 dataSource: "/Home/Orders",

  31:                 axes: [{ name: "xAxis", type: "categoryX", label: "OrderID", labelVisibility: "visible" },

  32:                         { name: "yAxis", type: "numericY", labelVisibility: "visible"}],

  33:                 series: [

  34:                     { name: "series",

  35:                         title: "Order Freight Series",

  36:                         type: "line",

  37:                         xAxis: "xAxis",

  38:                         yAxis: "yAxis",

  39:                         valueMemberPath: "Freight", trendLineThickness: 6, thickness: 4,

  40:                         trendLineBrush: "cyan",

  41:                         transitionDuration: 1500,

  42:                         trendLineType: "exponentialAverage"

  43:                     }],

  44:                 horizontalZoomable: true,

  45:                 verticalZoomable: true,

  46:                 windowResponse: "immediate",

  47:                 overviewPlusDetailPaneVisibility: "visible"

  48:             });

  49:  

  50:             $('#grid').igGrid({

  51:                 virtualization: false, height: 280, width: 650,

  52:                 dataSource: "/Home/Customers",

  53:                 autoGenerateColumns: false,

  54:                 columns: [

  55:                     { headerText: "Customer ID", key: "CustomerID", width: "120px", dataType: "string" },

  56:                     { headerText: "Country", key: "Country", width: "150px", dataType: "string" },

  57:                     { headerText: "City", key: "City", dataType: "string" },

  58:                     { headerText: "Contact Name", key: "ContactName", dataType: "string" },

  59:                     {headerText: "Phone", key: "Phone", dataType: "string" }

  60:                 ],

  61:                 features: [

  62:  

  63:                  {

  64:                      name: 'Selection',

  65:                      mode: 'row',

  66:                      multipleSelection: false,

  67:                      rowSelectionChanged: function (ui, args) {

  68:                          $("#chart").igDataChart({

  69:                              dataSource: "/Home/Orders?userID=" + args.row.element[0].cells[0].textContent

  70:                          });

  71:  

  72:                          selected = args.row.element[0].cells[0].textContent; //keep track of selected user

  73:                          var url = "http://nominatim.openstreetmap.org/search/" + args.row.element[0].cells[1].textContent + "?format=json";

  74:                         $.getJSON(url,

  75:                             function (json, text) {

  76:                              var name, lat, lon;

  77:                              $.each(json, function (index, value) {

  78:                                  if (value.class === "place" && value.type === "country") {

  79:                                      name = value.display_name;

  80:                                      lat = parseFloat(value.lat);

  81:                                      lon = parseFloat(value.lon);

  82:                                      //alert(lat);

  83:                                  }

  84:                              });

  85:                              data = [{ Name: name, Latitude: lat, Longitude: lon}];

  86:                              //add or override existing series named 'Countries'

  87:                              //adding this series *after* the shapefile ones will cause the markers to appear above the shape lines, which is what we want

  88:                              $("#map").igMap({

  89:                                  series: [{

  90:                                      name: "Countries",

  91:                                      type: "geographicSymbol",

  92:                                      longitudeMemberPath: "Longitude",

  93:                                      latitudeMemberPath: "Latitude",

  94:                                      /*

  95:                                      The provided object should have properties called render and optionally measure.

  96:                                      These are functions which will be called that will be called to handle the user specified custom rendering.

  97:                                      */

  98:                                      markerTemplate: {

  99:                                          render: function (renderInfo) {

 100:                                              var ctx = renderInfo.context; //2d canvas context

 101:                                              var x = renderInfo.xPosition;

 102:                                              var y = renderInfo.yPosition;

 103:  

 104:                                              if (renderInfo.isHitTestRender) {

 105:                                                  //  This is called for tooltip hit test only

 106:                                                  //  Rough marker rectangle size calculation

 107:                                                  ctx.fillStyle = "yellow";

 108:                                                  ctx.fillRect(x, y, renderInfo.availableWidth, renderInfo.availableHeight);

 109:                                              } else {

 110:                                                  //actual marker drawing is here:

 111:                                                  var markerData = renderInfo.data;

 112:                                                  var name = markerData.item()["Name"];

 113:                                                  //set font or measure will be for the default one

 114:                                                  ctx.font = '10pt Segoe UI';

 115:                                                  var textWidth = ctx.measureText(name).width;

 116:  

 117:                                                  //Move the path point to the desired coordinates:

 118:                                                  ctx.moveTo(x, y);

 119:                                                  //Draw lines:

 120:                                                  ctx.beginPath();

 121:                                                  ctx.lineTo(x - (textWidth / 2) - 5, y + 5);

 122:                                                  ctx.lineTo(x - (textWidth / 2) - 5, y + 40); // 35width rect.

 123:                                                  ctx.lineTo(x + (textWidth / 2) + 5, y + 40); // full textWidth line plus 5 margin

 124:                                                  ctx.lineTo(x + (textWidth / 2) + 5, y + 5); // 35 up

 125:                                                  ctx.lineTo(x, y);

 126:                                                  //finish the shape

 127:                                                  ctx.closePath();

 128:                                                  ctx.fillStyle = "rgba(78,183,226,0.7)";

 129:                                                  ctx.fill();

 130:                                                  ctx.lineWidth = 0.5;

 131:                                                  ctx.strokeStyle = "#185170";

 132:                                                  ctx.stroke();

 133:                                                  //add a point at the start

 134:                                                  ctx.beginPath();

 135:                                                  ctx.fillStyle = "black";

 136:                                                  ctx.arc(x, y, 1.5, 0, 2 * Math.PI, true);

 137:                                                  ctx.fill();

 138:  

 139:                                                  //  Draw text

 140:                                                  ctx.textBaseline = "top";

 141:                                                  ctx.fillStyle = "black";

 142:                                                  ctx.textAlign = 'center';

 143:                                                  ctx.fillText(selected, x, y + 8);

 144:                                                  ctx.fillText(name, x, y + 20);

 145:                                              }

 146:                                          }

 147:                                      },

 148:                                      dataSource: data

 149:                                  }]

 150:                              });

 151:                          //} 

 152:                          });

 153:  

 154:                      }

 155:                  }

 156:                 ,

 157:  

 158:                 {

 159:                     name: 'Sorting',

 160:                     type: "remote"

 161:                 },

 162:                 {

 163:                     name: 'Paging',

 164:                     type: "local",

 165:                     pageSize: 10

 166:                 }]

 167:                 ,

 168:                 rendered: function (ui, args) {

 169:                     //set up on-load selection 

 170:                     $('#grid').igGridSelection("selectRow", 0);

 171:                     //another way to get cell value independant of event parameters

 172:                     var id = $('#grid').igGrid("getCellValue", 0, "CustomerID");

 173:                     $("#chart").igDataChart({

 174:                         dataSource: "/Home/Orders?userID=" + id

 175:                     });

 176:  

 177:  

 178:                 }

 179:             });

 180:         });

 181:    </script>

 

You could see below the HTML, which defines the view layout

   1: <div style="display: block; height: 500px; width: 1155px;">

   2:     <div style="float: left; width:650px;  height: 280px; margin-right: 5px;">

   3:         <table id="grid">

   4:         </table>

   5:     </div>

   6:     <div style="float: right; top: -280px; height: 500px; width: 500px;">

   7:         <div id="map" />

   8:     </div>

   9:    <div style="position: relative; width: 650px; height: 260px; top: -220px; left: -650px; margin-right: 5px;">

  10:         <div id="chart" />

  11:     </div>

  12:  

  13: </div>

 

Application start page

JavaScript dashboard in action!

No worries! Everything works. Dashboard presents Northwind customers. When you select a specific customer from the grid you could see customer orders in the chart and his country (you will see a pushpin, that shows the county on the map).

Creating a dashboard using MVC Wrappers

The next step is to see how MVC libraries, built against MVC 3 work in ASP.Net MVC.4 projects.  Infragistics.Web.Mvc library contains MVC wrappers of the Infragistics jQuery components. The whole logic is related with the application UI and you could use it without any issues in MVC 4 applications.

MVC Dashboard view demonstrates how you could use MVC wrappers.

The first step is to add Infragistics Script Loader with Razor. You could use the wrapper in the same way like in MVC 3 projects. The resources path should be started from the application root (like for the JavaScript dashboard).

   1: @using Infragistics.Web.Mvc;

   2:  

   3: @(Html.Infragistics().Loader()

   4:                 .ScriptPath(Url.Content("/Scripts/Infragistics/js/"))

   5:                 .CssPath(Url.Content("/Content/Infragistics/css/"))

   6:             .Theme("metro")

   7:             .Render()

   8:     )

 

The layout should be created like MVC 3 view layout with Razor – no changes!

   1: <div style="display: block; height: 500px; width: 1155px;">

   2:     <div style="float: left; width:650px;  height: 280px; margin-right: 5px;">

   3:         @(Html.Infragistics().Grid<jQueryMapMVC4Demo.Customer>()

   4:                                     .DataSourceUrl("/Home/Customers").ResponseDataKey("")

   5:             .ID("grid").Width("650px").Height("280px")

   6:             .LoadOnDemand(false)

   7:             .AutoGenerateColumns(false)

   8:                             .Columns(column =>

   9:                             {

  10:                                 column.For(x => x.CustomerID).HeaderText("Customer ID").Width("120px").DataType("string");

  11:                                 column.For(x => x.Country).HeaderText("Country").Width("150px").DataType("string");

  12:                                 column.For(x => x.City).HeaderText("City").Width("120px").DataType("string");

  13:                                 column.For(x => x.ContactName).HeaderText("Contact Name").DataType("string").Width("140px");

  14:                                 column.For(x => x.Phone).HeaderText("Phone").DataType("string").Width("120px");

  15:                             })

  16:                             .Features(features =>

  17:                             {

  18:                                 features.Paging().Type(OpType.Local).VisiblePageCount(5).ShowPageSizeDropDown(true).PageSize(10).PrevPageLabelText("Previous").NextPageLabelText("Next");

  19:                                 features.Sorting().Mode(SortingMode.Single).ColumnSettings(settings =>

  20:                                 {

  21:                                     settings.ColumnSetting().ColumnKey("CustomerID").AllowSorting(true);

  22:  

  23:                                 });

  24:                                 features.Selection().MouseDragSelect(true).MultipleSelection(false).Mode(SelectionMode.Row);

  25:                             })

  26:             .Width("650")

  27:             .DataBind()

  28:             .Render()

  29:             )

  30:     </div>

  31:     <div style="float: right; top: -280px; height: 500px; width: 500px;">

  32:     @(Html.Infragistics().Map()

  33:                 .ID("map")

  34:                 .Width("500px").Height("500px")

  35:                 .VerticalZoomable(true)

  36:                 .HorizontalZoomable(true)

  37:                 .OverviewPlusDetailPaneVisibility(Visibility.Visible)

  38:                 .BackgroundContent(bgr => bgr.OpenStreetMaps())

  39:                 .PanModifier(ModifierKeys.Control)

  40:                 .WindowResponse(WindowResponse.Immediate)

  41:                 //.WindowRect(0.27, 0.20, 0.5, 0.5)

  42:                 .DataBind()

  43:                 .Render()

  44:         )

  45:     </div>

  46:    <div style="position: relative; width: 650px; height: 220px; top: 280px; margin-right: 5px;">

  47:         @(  Html.Infragistics().DataChart<jQueryMapMVC4Demo.Order>().DataSourceUrl("/Home/Orders").ResponseDataKey("")

  48:            .ID("chart")

  49:            .Width("650px")

  50:            .Height("220px")

  51:            .VerticalZoomable(true)

  52:            .HorizontalZoomable(true)

  53:            .Axes(axes =>

  54:             {

  55:                 axes.CategoryX("xAxis").Label(item => item.OrderID).LabelVisibility(Visibility.Visible);

  56:                 axes.NumericY("yAxis");

  57:             })

  58:            .Series(series =>

  59:             {

  60:                 series

  61:                     .Line("series").Title("Order Freight Series")

  62:                     .XAxis("xAxis").YAxis("yAxis")

  63:                     .ValueMemberPath(item => item.Freight).ValueMemberPath("Freight").TrendLineThickness(6).TrendLineBrush("blue")

  64:                     .TrendLineType(TrendLineType.ExponentialAverage).TransitionDuration(1500)

  65:                     .Thickness(4);

  66:             })

  67:            .DataBind()

  68:            .Render()

  69:     )

  70:     </div>

  71:  

  72: </div>

 

When you are using live events there also no changes.

   1: <script type="text/javascript">

   2:     var data = [];

   3:     var selected;

   4:     $('#grid').live('iggridselectionactiverowchanged', function (event, args) {

   5:         //set data chart source

   6:         $("#chart").igDataChart({

   7:             dataSource: "/Home/Orders?userID=" + args.row.element[0].cells[0].textContent

   8:         });

   9:         //set map series

  10:         selected = args.row.element[0].cells[0].textContent; //keep track of selected user

  11:         var url = "http://nominatim.openstreetmap.org/search/" + args.row.element[0].cells[1].textContent + "?format=json";

  12:  

  13:         $.getJSON(url,

  14:                         function (json, text) {

  15:                             var name, lat, lon;

  16:                             $.each(json, function (index, value) {

  17:                                 if (value.class === "place" && value.type === "country") {

  18:                                     name = value.display_name;

  19:                                     lat = parseFloat(value.lat);

  20:                                     lon = parseFloat(value.lon);

  21:                                     //alert(lat);

  22:                                 }

  23:                             });

  24:                             data = [{ Name: name, Latitude: lat, Longitude: lon}];

  25:                             //add or override existing series named 'Countries'

  26:                             //adding this series *after* the shapefile ones will cause the markers to appear above the shape lines, which is what we want

  27:                             $("#map").igMap({

  28:                                 series: [{

  29:                                     name: "Countries",

  30:                                     type: "geographicSymbol",

  31:                                     longitudeMemberPath: "Longitude",

  32:                                     latitudeMemberPath: "Latitude",

  33:                                     /*

  34:                                     The provided object should have properties called render and optionally measure.

  35:                                     These are functions which will be called that will be called to handle the user specified custom rendering.

  36:                                     */

  37:                                     markerTemplate: {

  38:                                         render: function (renderInfo) {

  39:                                             var ctx = renderInfo.context; //2d canvas context

  40:                                             var x = renderInfo.xPosition;

  41:                                             var y = renderInfo.yPosition;

  42:  

  43:                                             if (renderInfo.isHitTestRender) {

  44:                                                 //  This is called for tooltip hit test only

  45:                                                 //  Rough marker rectangle size calculation

  46:                                                 ctx.fillStyle = "yellow";

  47:                                                 ctx.fillRect(x, y, renderInfo.availableWidth, renderInfo.availableHeight);

  48:                                             } else {

  49:                                                 //actual marker drawing is here:

  50:                                                 var markerData = renderInfo.data;

  51:                                                 var name = markerData.item()["Name"];

  52:                                                 //set font or measure will be for the default one

  53:                                                 ctx.font = '10pt Segoe UI';

  54:                                                 var textWidth = ctx.measureText(name).width;

  55:  

  56:                                                 //Move the path point to the desired coordinates:

  57:                                                 ctx.moveTo(x, y);

  58:                                                 //Draw lines:

  59:                                                 ctx.beginPath();

  60:                                                 ctx.lineTo(x - (textWidth / 2) - 5, y + 5);

  61:                                                 ctx.lineTo(x - (textWidth / 2) - 5, y + 40); // 35width rect.

  62:                                                 ctx.lineTo(x + (textWidth / 2) + 5, y + 40); // full textWidth line plus 5 margin

  63:                                                 ctx.lineTo(x + (textWidth / 2) + 5, y + 5); // 35 up

  64:                                                 ctx.lineTo(x, y);

  65:                                                 //finish the shape

  66:                                                 ctx.closePath();

  67:                                                 ctx.fillStyle = "rgba(78,183,226,0.7)";

  68:                                                 ctx.fill();

  69:                                                 ctx.lineWidth = 0.5;

  70:                                                 ctx.strokeStyle = "#185170";

  71:                                                 ctx.stroke();

  72:                                                 //add a point at the start

  73:                                                 ctx.beginPath();

  74:                                                 ctx.fillStyle = "black";

  75:                                                 ctx.arc(x, y, 1.5, 0, 2 * Math.PI, true);

  76:                                                 ctx.fill();

  77:  

  78:                                                 //  Draw text

  79:                                                 ctx.textBaseline = "top";

  80:                                                 ctx.fillStyle = "black";

  81:                                                 ctx.textAlign = 'center';

  82:                                                 ctx.fillText(selected, x, y + 8);

  83:                                                 ctx.fillText(name, x, y + 20);

  84:                                             }

  85:                                         }

  86:                                     },

  87:                                     dataSource: data

  88:                                 }]

  89:                             });

  90:  

  91:                         });

  92:  

  93:     });

  94:  

  95:     $('#grid').live('iggridrendered', function (event, args) {

  96:         $('#grid').igGridSelection("selectRow", 0);

  97:         //another way to get cell value independant of event parameters

  98:         var id = $('#grid').igGrid("getCellValue", 0, "CustomerID");

  99:         $("#chart").igDataChart({

 100:             dataSource: "/Home/Orders?userID=" + id

 101:         });

 102:     });

 103: ript>

 

Congratulations! You have a real MVC 4 dashboard with the Infragistics jQuery controls.

If you want to move forward and start using ASP.Net MVC 4 you could use all your jQuery stuff and it’s MVC 3 wrappers.

You can find ASP.Net MVC 4 Demo project here.  Northwind sample database is available here.

As always, you can follow us on  Twitter: @mihailmateev  and  @Infragistics , all tweets with hashtag #infragistcs  and stay in touch on Facebook, Google+ , LinkedIn and Infragistics Friends User Group !