What are some ways I can create a reusable search/pagination feature in my application?

Our application has implemented server-side pagination, which was achieved through a simple implementation. Here is an outline of how it works:

  • Let's assume we have a search action on the server side:

    [HttpGet]
    public ActionResult GetPeopleByName(String term, Int32 itemsPerPage = 10, Int32 page = 0) {
        var matches = this.people.Where(i => i.Name.Contains(term));
        return Json(
            data: new {
                people = matches.Skip(itemsPerPage * page).Take(itemsPerPage).OrderBy(i => i.Name),
                total = matches.Count()
            },
            behavior: JsonRequestBehavior.AllowGet
        );
    }
    
  • On the client side, we have a subscriptionHolderController:

    app.controller('subscriptionHolderController', ['$scope', '$http', function($scope, $http) {
        $scope.matches = [];
        $scope.itemsPerPage = 5;
        $scope.currentPage = 0;
        $scope.searchTerm = '';
    
        // $scope.prevPage = function() { ... };
        // $scope.prevPageDisabled = function() { ... };
        // $scope.nextPage = function() { ... };
        // $scope.nextPageDisabled = function() { ... };
    
        $scope.pageCount = function() { 
            return Math.ceil($scope.totalPages / $scope.itemsPerPage); 
        };
    
        $scope.$watch('currentPage', function() { $scope.search(); });
    
        $scope.search = function() {
            if($scope.searchTerm === '') return;
            // initiate a GET-request with params: { page: $scope.currentPage, term: $scope.searchTerm, itemsPerPage: $scope.itemsPerPage }
        }
        $scope.matchesFound = function () { return $scope.matches.length > 0; }
    }]);
    

issue at hand

While we have successfully implemented pagination for one type of search in our application, we now require another type of search which does not rely on search terms but still needs to be paginated in a similar manner.

The question arises: How can we reuse the pagination logic for different types of searches?

Answer №1

When working on the server side, one approach is to create a generic class that will store your data and the total number of rows.

public class PagedResult<T>
    {
        public PagedResult(IEnumerable<T> data, long total)
        {
            Data = data;
            Total = total;
        }
        public PagedResult()
        {
        }
        public IEnumerable<T> Data { get; set; }
        public long Total { get; set; }

    }

Additionally, you can abstract the input parameters for functions using something like:

public class PageInfo
    {

        public int Page { get; set; }           
        public int PageSize { get; set; }
        public int Skip
        {
            get
            {
                return PageSize*(Page - 1);
            }
        }

        public PageInfo(int page, int pageSize)
        {
            Page = page;
            PageSize = pageSize;
        }

    }

An example implementation could look like this:

[HttpGet]
public ActionResult GetPeopleByName(String term, PageInfo pageInfo) {
    var matches = this.people.Where(i => i.Name.Contains(term));

    var pagedResult =  new PagedResult<AnySearchType>{
            Data = matches.Skip(pageInfo.skip).Take(pageInfo.size).OrderBy(i => i.Name),
            Total = matches.Count()
        };

    return Json(
        data: pagedResult,
        behavior: JsonRequestBehavior.AllowGet
    );
}

For the client side, consider using a directive to handle paging logic by passing parameters like:

View:

<div class="box-content" scroll-pager="pagerConfig">
your data
<div>

Controller:

You can configure the directive with settings like:

$scope.pagerConfig = {
        PageSize: 10,
        Data: 'model.Data', // reference to your model
        Total: 'model.Total', // reference to your model
        CurrentPage: 1,
        SearchParamName: 'Text',// reference to your model
        Resource: projectResource,// pass a resource object
        Success: function (data, page, total) {

            }
    };

Answer №2

Creating a distinct class specifically for handling pagination functionality is recommended. By defining methods within this class, you can easily implement paging for various data types and customize the parameters according to your needs. This approach also allows you to tailor the fields on which paging should be applied and adjust the page size to suit your requirements.

Answer №3

After some work, this is the final implementation:

app.directive('pager', function() {
    return {
        restrict: 'EA',
        scope: {
            onChange: '&',
            items: '=',
            itemsPerPage: '@'
        },
        replace: true,
        templateUrl: '/scripts/js/app/pager.html',
        link: function(scope, el, attrs) {
            scope.currentPage = 1;

            scope.isFirstPage = function() {
                return scope.currentPage === 1;
            }

            scope.decPage = function() {
                if(scope.currentPage > 1) --scope.currentPage;
            }

            scope.isLastPage = function() {
                return scope.currentPage >= scope.totalPages();
            }

            scope.totalPages = function() {
                return Math.ceil(scope.items.total / scope.itemsPerPage);
            }

            scope.incPage = function() {
                if(!scope.isLastPage()) ++scope.currentPage;
            }

            scope.$watch("currentPage", function(value) {
                scope.onChange({page: value, itemsPerPage: scope.itemsPerPage});
            });
        }
    };
});

markup

<div id="content" ng-app="subscriptionsManager">
    <div ng-controller="subscriptionHolderController">
        <div class="row">
            <div class="columns medium-6 large-6">
                <div class="searchbar">
                    <div class="searchbar-inner">
                        <input ng-model="searchTerm" type="text" />
                        <button ng-click="search(1, 35)" class="tiny">search</button>
                    </div>
                </div>
            <div pager items-per-page="35" items="data" on-change="respondToChanges(page, itemsPerPage)"></div>
           </div>
           <div class="columns medium-6 large-6">
                <div class="button-group filter-sample">
                     <button ng-click="toggleState1()" ng-class="{true: 'selected', false: 'secondary'}[state1]" class="tiny">filter1</button>
                     <button ng-click="toggleState2()" ng-class="{true: 'selected', false: 'secondary'}[state2]" class="tiny">filter2</button>
                     <button ng-click="toggleState3()" ng-class="{true: 'selected', false: 'secondary'}[state3]" class="tiny">filter3</button>
                     <button ng-click="search2(1, 35)" class="tiny">search</button>
                </div>
                <div pager items-per-page="35" items="data2" on-change="respondToChanges2(page, itemsPerPage)"></div>
             </div>
        </div>
    </div>
</div>    

controller

// search by input term

$scope.data = { items: [], total: 0 };
$scope.searchTerm = '';

$scope.state1 = false;
$scope.state2 = false;
$scope.state3 = false;

$scope.toggleState1 = function() {
    $scope.state1 = !$scope.state1;
}

$scope.toggleState2 = function() {
    $scope.state2 = !$scope.state2;
}

$scope.toggleState3 = function() {
    $scope.state3 = !$scope.state3;
}

$scope.search = function(page, itemsPerPage) {
    if($scope.searchTerm === '') return;
    if(!angular.isDefined(page) || page == null) page = 1;
    if(!angular.isDefined(itemsPerPage) || itemsPerPage == null) itemsPerPage = 35;

    $http({
        url: '/subscriptions/GetSubscribersByNamePaged',
        method: 'GET',
        params: { term: $scope.searchTerm, page: page, itemsPerPage: itemsPerPage }
    })
    .success(function(data, status, headers, config) {
        $scope.data = data;
    }).error(function(data, status, headers, config) {
        console.log('error: ' + data);
    });
}

// search using other criteria

$scope.search2 = function(page, itemsPerPage) {
    if(!angular.isDefined(page) || page == null) page = 1;
    if(!angular.isDefined(itemsPerPage) || itemsPerPage == null) itemsPerPage = 35;

    $http({
        url: '/subscriptions/GetSubscribersByFilters',
        method: 'GET',
        params: { state1: $scope.state1, state2: $scope.state2, state3: $scope.state3, page: page, itemsPerPage: itemsPerPage }
    })
    .success(function(data, status, headers, config) {
        $scope.data2 = data;
    }).error(function(data, status, headers, config) {
        console.log('error: ' + data);
    });
}

// handle search events

$scope.respondToChanges = function(page, itemsPerPage) {
    $scope.search(page, itemsPerPage);
}

$scope.respondToChanges2 = function(page, itemsPerPage) {
    $scope.search2(page, itemsPerPage);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for ensuring all data is downloaded in Firebase (Firestore) Storage before proceeding?

I am currently developing a light dashboard in Vue that connects to Firestore and Storage. As someone who is not an expert, I have encountered a roadblock in what should be a simple task. The issue lies with a function that is meant to retrieve all URLs ba ...

Does a new custom usercontrol get generated whenever the datacontext is modified?

Here's a question I have about a user control I've created (not a custom control). I'm using this control in a DataGridColumn to provide lookup functionality, as shown in the example code below: <DataGridTemplate ColumnHeader="Company"&g ...

Where can I find information about Javascript in the MSDN library?

In my Visual Studio 2010 (Visual Web Developer) environment, I have used the "Help Library Manager" to install all available documentation related to web development and "JScript". However, I am unable to find any documentation specifically on javascript i ...

Invoke a method in an Angular 2 component using an HTML event

Can an angular component method be invoked using an HTML event? <shape onclick="myMethodInParentComponent()" > I am unable to use (click) as shape is not recognized by Angular. Shape also contains several unknown sub elements making it impractical ...

What are the steps to extract information from a website that is located remotely?

Imagine there is a URL out there, for example: www.website.com/data.jsp This link contains the following JSON data: {"successful":"true","rows":[{"zip":"65472","user_id":"10843","name":"Rufio"}]} I am struggling to extract this data at runtime using get ...

What is the best way to implement a Highcharts Solid Gauge in Angular 6 using TypeScript?

In my ongoing project, I am utilizing Angular 6 along with TypeScript. The objective is to incorporate a Solidgauge in one of my Components. However, as I proceeded with the development of this component, I encountered some challenges: When attempting to ...

What is the method for incorporating functionality into the "X" icon within the Material UI Searchbar?

Check out this code snippet: import SearchBar from "material-ui-search-bar"; const info = [ { name: "Jane" }, { name: "Mark" }, { name: "Jason" } ]; export default function App() { const [o ...

What is the best way to delete an element from a list in a Meteor data source?

Currently, I am working through a tutorial on Meteor and attempting to delete an item. The code snippet I have been using is: lists.remove({Category:"Fraggles"}) Unfortunately, this method no longer works in the latest version of Meteor and I am encounte ...

Expanding the MatBottomSheet's Width: A Guide

The CSS provided above is specifically for increasing the height of an element, but not its width: .mat-bottom-sheet-container { min-height: 100vh; min-width: 100vw; margin-top: 80px; } Does anyone have a solution for expanding the width of MatBott ...

What is the most effective approach for revealing AngularJS controller methods in TypeScript?

I have been exploring a way to attach the controller object to the scope in a different manner. Here is an example of how it can be achieved. interface IAppCtrlScope extends ng.IScope { info: any; } class InfoCtrl { header: string; name: strin ...

Chrome is known for its seamless integration with PDFs,

<div align="justify" style="height: 500px; border: 1px solid #ccc; overflow: auto;"> <object data="/policy/download/#zoom=65,65,720" type="application/pdf" width="100%" height="500"></object> </d ...

Why do the async functions appear to be uncovered branches in the Jest/Istanbul coverage report?

I am encountering an issue throughout my application: When running Jest test coverage script with Istanbul, I am getting a "branch not covered" message specifically on the async function part of my well covered function. What does this message mean and how ...

Whenever I try to POST to the database, an error pops up saying "Headers cannot be set after they have been sent."

For this question, I am outlining the entire data flow for processing a sale from beginning to end because I am unsure where the error is occurring. In my application, there is a function called handlePay() within the Checkout component which then triggers ...

What is the best way to utilize a 'Control.Tag' in another function?

I am struggling with utilizing the Control.Tag property. I know that it can be assigned to any object, but what eludes me is how to invoke it within another method. As a newcomer in this field, I apologize if my question seems naive. The goal is to design ...

Craft a brief description for every individual component discovered

Is there a way to identify and shorten all elements containing a <tspan> tag to just ten characters each? For example: <tspan>Bla bla bla bla</tspan> <tspan>Bla bla bla bla</tspan> <tspan>Bla bla bla bla</tspan> ...

Images in ListView are refusing to appear. As much as it pains me, I have to inquire: why is this happening?

I am attempting to showcase images in a WinForms ListView that is set in details view mode, but for some reason, the images are not appearing. Despite adding a SmallImageList (and a large one as well), inserting images into them, and populating the ListV ...

Unlimited digest loop in Angular.js caused by nested ng-repeat and filter

In my AngularJS project, I have developed a custom filter that categorizes elements by type and performs a search for multiple search terms across all attributes of devices. angular.module('abc').filter('searchFor', function(){ return ...

The clientWidth and scrollWidth properties will constantly be the same

I am currently utilizing Vuetify text-fields and aiming to exhibit a tooltip showcasing the content only if it exceeds the field width (requiring user scroll). The tooltip should solely be visible on hover, as per default behavior. My initial approach ensu ...

Acquiring a website's dynamic value using jquery

This question is pretty self-explanatory... I am trying to extract a value from a website's source code, but the value I need is dynamically generated using jQuery. Let's use example.com as an example: <div id="currentTime"></div> ...

What sets apart the meteor angular:angular and urigo:angular-meteor packages from each other?

I am currently working on developing an app that incorporates both meteor and angular. There are two main angular packages that I have come across: one is called angular:angular, and the other is named urigo:angular-meteor According to the angular:angula ...