Ember.js is an open source JavaScript client-side framework for developing the web applications and uses the MVC(Model-View-Controller) architecture pattern. It uses handlebar template as view, route is used as model and controller manipulates the data in the model. In this article we will look into how to do pagination in EmberJS.
Introduction
Ember.js is an open source JavaScript client-side framework for developing the web applications and uses the MVC(Model-View-Controller) architecture pattern. It uses handlebar template as view, route is used as model and controller manipulates the data in the model. In this article we will look into how to do pagination in EmberJS.
Creating the WebAPI project
Fire up Visual Studio and then File -> New -> Project -> Web -> Asp.Net Web Application.
The next screen that pops up display's a list of templates and we need to choose Web API template.
Also set the authentication mode to No Authentication
Clicking on the OK button creates the Web API project
Now let us first create the Employee model as under
namespace WebApplication1.Models
{
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
}
}
Our Employee model is a simple Employee Class which has some scalar properties like EmployeeID,EmployeeName,EmailAddress,PhoneNumber.
Now let us create the DBOperations class that will perform the pagination operation by using ADO.NET and Stored Procedures.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
namespace WebApplication1.Models
{
public class DBOperations
{
/// <summary>
/// Function: GetEmployees
/// Get the Employee Records
/// </summary>
/// <returns></returns>
public static List<Employee> GetEmployees()
{
List<Employee> lstEmployees = new List<Employee>();
try
{
string ConnectionPath = ConfigurationManager.ConnectionStrings["DbConnection"].ToString();
using (var sqlCon = new SqlConnection(ConnectionPath))
{
using (SqlCommand cmd = new SqlCommand("usp_GetEmployees", sqlCon))
{
cmd.CommandType = CommandType.StoredProcedure;
sqlCon.Open();
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (dr.Read())
{
lstEmployees
.Add
(
new Employee
{
EmployeeID = Convert.ToInt32(dr["EmployeeID"].ToString())
,
EmployeeName = dr["EmployeeName"].ToString()
,
EmailAddress = dr["EmailAddress"].ToString()
,
PhoneNumber = dr["PhoneNumber"].ToString()
}
);
}
dr.Close();
}
}
}
catch (Exception ex)
{
throw ex;
}
return lstEmployees;
}
/// <summary>
/// Function: GetEmployeesForPagination
/// Get the Employee Records for the range
/// </summary>
/// <param name="startIdx"></param>
/// <param name="endIdx"></param>
/// <returns></returns>
public static List<Employee> GetEmployeesForPagination(int startIdx, int endIdx)
{
List<Employee> lstEmployees = new List<Employee>();
try
{
string ConnectionPath = ConfigurationManager.ConnectionStrings["DbConnection"].ToString();
using (var sqlCon = new SqlConnection(ConnectionPath))
{
using (SqlCommand cmd = new SqlCommand("usp_GetEmployeesForPagination", sqlCon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@StartIndex", SqlDbType.Int).Value = startIdx;
cmd.Parameters.Add("@EndIndex", SqlDbType.Int).Value = endIdx;
sqlCon.Open();
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (dr.Read())
{
lstEmployees
.Add
(
new Employee
{
EmployeeID = Convert.ToInt32(dr["EmployeeID"].ToString())
,
EmployeeName = dr["EmployeeName"].ToString()
,
EmailAddress = dr["EmailAddress"].ToString()
,
PhoneNumber = dr["PhoneNumber"].ToString()
}
);
}
dr.Close();
}
}
}
catch (Exception ex)
{
throw ex;
}
return lstEmployees;
}
}
}
Now expand the Controllers folder and create EmployeeController.cs and add the following code
using System.Collections.Generic;
using System.Web.Http;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
[RoutePrefix("Employee")]
public class EmployeeController : ApiController
{
[Route("EmployeeRecords")]
[HttpGet]
public List<Employee> GetEmployeeRecords()
{
return DBOperations.GetEmployees();
}
[Route("EmployeeRecordsForPagination")]
[HttpGet]
public EmployeeRelatedInformation GetEmployeeRecordsForPagination(int startIdx,int endIdx)
{
EmployeeRelatedInformation eri = new EmployeeRelatedInformation();
eri.EmployeeRecordCount = DBOperations.GetEmployees().Count;
eri.Employees = DBOperations.GetEmployeesForPagination(startIdx, endIdx);
return eri;
}
}
public class EmployeeRelatedInformation
{
public int EmployeeRecordCount { get; set; }
public List<Employee> Employees { get; set; }
}
}
Basically, the GetEmployeeRecordsForPagination method accepts two parameters viz.startIdx and endIdx. These are the Start and End Index respectively. Suppose we have 20 records and we want to obtain records from 10 to 15. So, the value of startIdx will be 10 and endIdx will be 15. The EmployeeRelatedInformation class exposes two properties viz. a)EmployeeRecordCount - Total Records Count of teh Employees and b)List<Employee> Employees - The collection of Emloyee records within the specified range.
Now from the Nu-get package manager issue
Install-Package Microsoft.AspNet.WebApi.Cors
Once done, then open WebApiConfig.cs file and enable the CORS as under
The complete code is as under
using System.Web.Http;
using System.Web.Http.Cors;
namespace WebApplication1
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableCors();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Run the application and click the API link in the browser to receive the below image
But we want to add some XML documentation comments to our API's. For doing so, let's open the Areas/HelpPage/App_Start/HelpPageConfig.cs
Open the HelpPageConfig.cs file. We will find that there is a HelpPageConfig static class whose purpose is to customize the Help Page. This class has a Register method where we need to uncomment the below.
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
Now, right click on the project and select Properties and then select the Build option. Under Output section, check XML documentation file. In the edit box, type App_Data/XmlDocument.xml.
Now add some comments to the web methods as under
/// <summary>
/// Provides employee related information
/// </summary>
[RoutePrefix("Employee")]
public class EmployeeController : ApiController
{
/// <summary>
/// Get the Employee Records
/// </summary>
[Route("EmployeeRecords")]
[HttpGet]
public List<Employee> GetEmployeeRecords()
{
return DBOperations.GetEmployees();
}
/// <summary>
/// Get the Employee Records for the range
/// </summary>
[Route("EmployeeRecordsForPagination")]
[HttpGet]
public EmployeeRelatedInformation GetEmployeeRecordsForPagination(int startIdx,int endIdx)
{
EmployeeRelatedInformation eri = new EmployeeRelatedInformation();
eri.EmployeeRecordCount = DBOperations.GetEmployees().Count;
eri.Employees = DBOperations.GetEmployeesForPagination(startIdx, endIdx);
return eri;
}
}
Now again run the application and click the API link in the browser to receive the updated information
Now we can test the API Services of the EmployeeController.cs through PostMan.
Design the Database Table and Stored Procedures
Next execute the below database script that will create both the table (say tblEmployees) and the stored procedures.
USE [YOURDATABASE]
GO
/****** Object: Table [dbo].[tblEmployees] Script Date: 8/24/2016 4:08:08 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[tblEmployees](
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[EmployeeName] [varchar](50) NOT NULL,
[EmailAddress] [varchar](50) NOT NULL,
[PhoneNumber] [varchar](50) NOT NULL,
CONSTRAINT [PK_tblEmployee] PRIMARY KEY CLUSTERED
(
[EmployeeID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object: StoredProcedure [dbo].[usp_GetEmployees] Script Date: 8/24/2016 4:08:08 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: RNA Team
-- Description: Get employee records
-- exec [dbo].[usp_GetEmployees]
-- =============================================
CREATE PROCEDURE [dbo].[usp_GetEmployees]
-- Add the parameters for the stored procedure here
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT
EmployeeID
,EmployeeName
,EmailAddress
,PhoneNumber
FROM [dbo].[tblEmployees]
END
GO
/****** Object: StoredProcedure [dbo].[usp_GetEmployeesForPagination] Script Date: 8/24/2016 4:08:08 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: RNA Team
-- Description: Select Employee Records within the Range
-- exec [dbo].[usp_GetEmployeesForPagination] 19,20
-- =============================================
CREATE PROCEDURE [dbo].[usp_GetEmployeesForPagination]
-- Add the parameters for the stored procedure here
(
@StartIndex INT
,@EndIndex INT
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT
EmployeeID
,EmployeeName
,EmailAddress
,PhoneNumber
FROM [dbo].[tblEmployees]
WHERE EmployeeID BETWEEN @StartIndex AND @EndIndex
END
GO
As can be figure out that, in the usp_GetEmployeesForPagination we are selecting the records within the range
......
......
WHERE EmployeeID BETWEEN @StartIndex AND @EndIndex
We can populate the table with some random data by executing the below script
;WITH NumCte AS
(
Select Rn=1
Union All
Select Rn=Rn+1 From NumCte Where Rn < 50
)
INSERT INTO [dbo].[tblEmployees]
([EmployeeName]
,[EmailAddress]
,[PhoneNumber])
Select
EmployeeName = 'Name' + Cast(Rn As Varchar(10))
,EmailAddress = 'Email' + Cast(Rn As Varchar(10)) + '@test.com'
,PhoneNumber=ABS(CHECKSUM(NEWID()))
From NumCte
Option(MaxRecursion 0)
This will insert 50 records to the [dbo].[tblEmployees] table
Implementing Pagination in EmberJS
Let us create a "index.html" and add the below
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.10.0/ember.min.js"></script>
<script src="http://builds.emberjs.com/tags/v1.10.0-beta.3/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/release/ember.debug.js"></script>
<script src="http://builds.emberjs.com/beta/ember-data.js"></script>
</head>
<body>
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Pagination in EmberJS</h2>
<button {{action "Previous"}} id="btnPrev"> Previous </button>
<button {{action "Next"}} id="btnNext"> Next </button>
<table border="1">
<tr>
<th>EmployeeID</th>
<th>EmployeeName</th>
<th>EmailAddress</th>
<th>PhoneNumber</th>
</tr>
{{#each employee in App.employeeRecord}}
<tr>
<td>{{employee.EmployeeID}}</td>
<td>{{employee.EmployeeName}}</td>
<td>{{employee.EmailAddress}}</td>
<td>{{employee.PhoneNumber}}</td>
</tr>
{{/each}}
</table>
</script>
<script type="text/javascript">
var App = Ember.Application.create()
App.pageSize = 10; //number of records to be displayed
App.startIndex=1; //start page index
App.endIndex = App.pageSize; //end page index
App.employeeRecord = null; //this will hold the employee records
App.employeeRecordCount = 0; //stored the total Employee Records count
//the below code will execute on page load with the startIndex = 1 and endIndex = 10
var uRL = 'http://localhost:22900/Employee/EmployeeRecordsForPagination?startIdx='+App.startIndex+'&endIdx='+App.endIndex;
var empData = null;
$.ajax({
type: "get",
url: uRL,
cache: false,
data: {},
async: false,
success: function (data) {
empData = data;
},
error: function (err) {
alert(err);
}
});
App.employeeRecordCount = empData.EmployeeRecordCount;
App.employeeRecord = empData.Employees;
//---------end
App.IndexRoute = Ember.Route.extend({
//defining the action for the User click event
actions: {
Previous: function() {
App.startIndex=App.startIndex - App.pageSize ;
App.endIndex=App.endIndex - App.pageSize ;
if(App.startIndex == 1){
document.getElementById("btnPrev").disabled = true;
getEmployeeRecordsInRange(App.startIndex,App.endIndex);
} else {
document.getElementById("btnNext").disabled = false;
getEmployeeRecordsInRange(App.startIndex,App.endIndex); }
} //end of Previous function
,
Next: function() {
App.startIndex=App.startIndex + App.pageSize ;
App.endIndex=App.endIndex + App.pageSize ;
if(App.endIndex === App.employeeRecordCount){
document.getElementById("btnNext").disabled = true;
getEmployeeRecordsInRange(App.startIndex,App.endIndex);
} else {
document.getElementById("btnPrev").disabled = false;
getEmployeeRecordsInRange(App.startIndex,App.endIndex); }
} //end of Next function
}//end action
}); //end extend function
//function name: getEmployeeRecordsInRange
function getEmployeeRecordsInRange(startIDX,endIDX)
{
var uRL = 'http://localhost:22900/Employee/EmployeeRecordsForPagination?startIdx='+startIDX+'&endIdx='+endIDX;
var empData = null;
$.ajax({
type: "get",
url: uRL,
cache: false,
data: {},
async: false,
success: function (data) {
empData = data; }
, error: function (err) {
alert(err);}
});
//Ember.set method is use to reflect the updated changes it in the handlebar template
Ember.set(App,'employeeRecord',empData.Employees);
}
</script>
</body>
</html>
When the page loads, then the default startIndex and EndIndex values are set and the JQuery's "ajax" method is invoked for the WebAPI call. The values return from the API call is use for storing the total employee record count (in employeeRecordCount variable) and the employee records for the range in employeeRecord variable. This gets displayed through the Handlebars expressions by using the curly braces: {{}}. Here {{outlet}} is the placeholder that the router fills the appropriate template. The result is as under (on page load)
Previous method
This method will be triggered while the previous button is clicked. Here the start and end index is calculated based on the below formula.
App.startIndex=App.startIndex - App.pageSize ;
App.endIndex=App.endIndex - App.pageSize ;
That means, whatever is the current page start and end index, we need to subtract the pagesize from that.
Next method
This method will be triggered while the next button is clicked. Here the start and end index is calculated based on the below formula.
App.startIndex=App.startIndex + App.pageSize ;
App.endIndex=App.endIndex + App.pageSize ;
That means, whatever is the current page start and end index, we need to add the pagesize to that.
Now click on the "Next" button till it reached the last record
Now click on the "Previous" button till it reached the first record
Reference
EmberJS
Conclusion
In this article we have learnt at the bare minimum
- Creating WebAPI
- Interacting with Ado.net
- How to write XML documentation comments to Web API's.
- Pagination concept in stored procedure
- Pagination implementation using EnberJS
- Display the record in EmberJS
- Use and importance of Ember.set method
Hope this will be useful. Thanks for reading. Zipped file attached.