4

JSON Data (test.json)

[{
    "name": "A Goofy Movie (1995) 720p HDTVRip x264 Eng Subs [Dual Audio] [Hindi DD 2.0 - English DD 2.0] Exclusive By -=!Dr.STAR!=-",
    "progress": 0,
    "size": "1.06 GB",
    "downloaded": "87.98 KB",
    "hash": "8fe65e43464debe1bc0961e4656ea780368d4fba",
    "peer": 0,
    "delete": "delete.php?del=8fe65e43464debe1bc0961e4656ea780368d4fba",
    "speed": "0 Byte",
    "eta": "23:59:59",
    "path": "C:\\xampp\\htdocs\\final\\download\/8fe65e43464debe1bc0961e4656ea780368d4fba"
}, {
    "name": "Logan+2017+HDCAM+XviD+UnKnOwN",
    "progress": 0,
    "size": "0 Byte",
    "downloaded": "0 Byte",
    "hash": "624911f8e4fc172e5ed7970d3bc097198bfd4e76",
    "peer": 0,
    "delete": "delete.php?del=624911f8e4fc172e5ed7970d3bc097198bfd4e76",
    "speed": "0 Byte",
    "eta": "23:59:59",
    "path": "C:\\xampp\\htdocs\\final\\download\/624911f8e4fc172e5ed7970d3bc097198bfd4e76"
}, {
    "name": "Internet Download Manager 6.27 Build 5 Multilingual + Patch",
    "progress": 100,
    "size": "6.97 MB",
    "downloaded": "7.49 MB",
    "hash": "bffe600ae08ba8e55db30dae6acd86979e30ce15",
    "peer": 0,
    "delete": "delete.php?del=bffe600ae08ba8e55db30dae6acd86979e30ce15",
    "speed": "0 Byte",
    "eta": "23:59:59",
    "path": "C:\\xampp\\htdocs\\final\\download\/bffe600ae08ba8e55db30dae6acd86979e30ce15"
}]

KnockoutJS Data

function ExampleViewModel() {
    var self = this;
    self.ExampleData = ko.observableArray([]);
    self.update = function() {
        $.ajax("test.json", {
            success: function(allData) {
                var mappeddata = $.map(allData, function(item) {
                    return new DataItem(item)
                });
                self.ExampleData(mappeddata);
            }
        });
    }
}

function DataItem(data) {
    ko.observable(data.name);
    ko.observable(data.progress);
}
var exampleViewModel = new ExampleViewModel();
ko.applyBindings(exampleViewModel);

How to provide pagination and show only first two value? ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

Working example

ɢʀᴜɴᴛ
  • 32,025
  • 15
  • 116
  • 110
Amitya
  • 103
  • 10
  • Possible duplicate of [How to handle pagination with Knockout](https://stackoverflow.com/questions/17668491/how-to-handle-pagination-with-knockout) – Hakan Fıstık Jun 10 '17 at 07:52

2 Answers2

1

You can loop through from the self.Data below

function ExampleViewModel() {
  var self = this;
  self.ExampleData = ko.observableArray([]);
  self.CurrentPage = ko.observable(1);
  self.DataPerPage = ko.observable(2); // You can change from here if you want to show the data other than 2 per page
  self.Data = ko.pureComputed(function(){
   var startIndex = self.CurrentPage() === 1? 0 : (self.CurrentPage() - 1) * self.DataPerPage();
   return self.ExampleData().slice(startIndex, startIndex + self.DataPerPage())
  });
  self.update = function() {
    $.ajax("test.json", {
      success: function(allData) {
        var mappeddata = $.map(allData, function(item) {
          return new DataItem(item)
        });
        self.ExampleData(mappeddata);
      }
    });
  }
}

Simple example:

function ViewModel() {
  var self = this;
  self.ExampleData = ko.observableArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
  self.CurrentPage = ko.observable(1); // Store the current page of the user
  self.DataPerPage = ko.observable(2); // To identify how many data we want to see per page

  self.Data = ko.pureComputed(function(){
   var startIndex = self.CurrentPage() === 1? 0 : (self.CurrentPage() - 1) * self.DataPerPage();
   return self.ExampleData().slice(startIndex, startIndex + self.DataPerPage ())
  });

  self.Next = function() {
    var totalData = self.ExampleData().length;
    var currentPage = self.CurrentPage();

    // if the length is 0, don't allow next
    // if we're on the last page, don't allow next
    if(totalData >= 1 && currentPage < (Math.ceil(totalData/2))) self.CurrentPage(currentPage + 1);
  };

  self.Prev = function() {
    var currentPage = self.CurrentPage();
    
    // if we're on the first page, don't allow prev
    if(currentPage > 1) self.CurrentPage(currentPage - 1);
  };
}   

$(document).ready(function () {
  var myViewModel = new ViewModel();
  ko.applyBindings(myViewModel);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div>
  <ul data-bind="foreach: Data">
    <li data-bind="text: $data"></li>
  </ul>
  <button data-bind="click: Next">Next</button>
  <button data-bind="click: Prev ">Prev</button>
</div>
muhihsan
  • 2,250
  • 6
  • 27
  • 42
  • its say Uncaught TypeError: ko.pureComputed is not a function – Amitya Mar 26 '17 at 12:16
  • @Amitya try to use ko.computed then – muhihsan Mar 26 '17 at 12:19
  • i`m sorry sir its show all data instead of first two data i also start to learn something from your example but its not helping me – Amitya Mar 26 '17 at 12:50
  • Did you copy and paste the code from what I created? Can you put it in jsfiddle and share it? – muhihsan Mar 26 '17 at 13:08
  • You should do foreach on "DataPerPage" instead of "ExampleData" – muhihsan Mar 26 '17 at 13:36
  • And also at the return of "DataPerPage" it should be "return self.ExampleData().slice(startIndex, startIndex + 2)". Or follow my updated answer so that you can change it dynamically just by updating it from 1 location. This will also allows you to let the user decide how many data they want to see per page ;) – muhihsan Mar 26 '17 at 13:43
  • Its that Possible to echo Page Number between next and prev ? – Amitya Mar 26 '17 at 16:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/139122/discussion-between-m-ihsan-and-amitya). – muhihsan Mar 27 '17 at 07:23
0

Actually I am working on website, which has a lot of tables (most of them need paging), so actually I needed some reusable-component for paging to use it in all the cases which I need paging.
Also I needed more advanced features than which provided in the accepted answer of this question.

So I developed my own component of the this issue, here it is.

Now on Github

JsFiddle

And for more details, continue reading

JavaScript

function PagingVM(options) {
    var self = this;

    self.PageSize = ko.observable(options.pageSize);
    self.CurrentPage = ko.observable(1);
    self.TotalCount = ko.observable(options.totalCount);

    self.PageCount = ko.pureComputed(function () {
        return Math.ceil(self.TotalCount() / self.PageSize());
    });

    self.SetCurrentPage = function (page) {
        if (page < self.FirstPage)
            page = self.FirstPage;

        if (page > self.LastPage())
            page = self.LastPage();

        self.CurrentPage(page);
    };

    self.FirstPage = 1;
    self.LastPage = ko.pureComputed(function () {
        return self.PageCount();
    });

    self.NextPage = ko.pureComputed(function () {
        var next = self.CurrentPage() + 1;
        if (next > self.LastPage())
            return null;
        return next;
    });

    self.PreviousPage = ko.pureComputed(function () {
        var previous = self.CurrentPage() - 1;
        if (previous < self.FirstPage)
            return null;
        return previous;
    });

    self.NeedPaging = ko.pureComputed(function () {
        return self.PageCount() > 1;
    });

    self.NextPageActive = ko.pureComputed(function () {
        return self.NextPage() != null;
    });

    self.PreviousPageActive = ko.pureComputed(function () {
        return self.PreviousPage() != null;
    });

    self.LastPageActive = ko.pureComputed(function () {
        return (self.LastPage() != self.CurrentPage());
    });

    self.FirstPageActive = ko.pureComputed(function () {
        return (self.FirstPage != self.CurrentPage());
    });

    // this should be odd number always
    var maxPageCount = 7;

    self.generateAllPages = function () {
        var pages = [];
        for (var i = self.FirstPage; i <= self.LastPage() ; i++)
            pages.push(i);

        return pages;
    };

    self.generateMaxPage = function () {
        var current = self.CurrentPage();
        var pageCount = self.PageCount();
        var first = self.FirstPage;

        var upperLimit = current + parseInt((maxPageCount - 1) / 2);
        var downLimit = current - parseInt((maxPageCount - 1) / 2);

        while (upperLimit > pageCount) {
            upperLimit--;
            if (downLimit > first)
                downLimit--;
        }

        while (downLimit < first) {
            downLimit++;
            if (upperLimit < pageCount)
                upperLimit++;
        }

        var pages = [];
        for (var i = downLimit; i <= upperLimit; i++) {
            pages.push(i);
        }
        return pages;
    };

    self.GetPages = ko.pureComputed(function () {
        self.CurrentPage();
        self.TotalCount();

        if (self.PageCount() <= maxPageCount) {
            return ko.observableArray(self.generateAllPages());
        } else {
            return ko.observableArray(self.generateMaxPage());
        }
    });

    self.Update = function (e) {
        self.TotalCount(e.TotalCount);
        self.PageSize(e.PageSize);
        self.SetCurrentPage(e.CurrentPage);
    };

    self.GoToPage = function (page) {
        if (page >= self.FirstPage && page <= self.LastPage())
            self.SetCurrentPage(page);
    }

    self.GoToFirst = function () {
        self.SetCurrentPage(self.FirstPage);
    };

    self.GoToPrevious = function () {
        var previous = self.PreviousPage();
        if (previous != null)
            self.SetCurrentPage(previous);
    };

    self.GoToNext = function () {
        var next = self.NextPage();
        if (next != null)
            self.SetCurrentPage(next);
    };

    self.GoToLast = function () {
        self.SetCurrentPage(self.LastPage());
    };
}

HTML

<ul data-bind="visible: NeedPaging" class="pagination pagination-sm">
    <li data-bind="css: { disabled: !FirstPageActive() }">
        <a data-bind="click: GoToFirst">First</a>
    </li>
    <li data-bind="css: { disabled: !PreviousPageActive() }">
        <a data-bind="click: GoToPrevious">Previous</a>
    </li>

    <!-- ko foreach: GetPages() -->
    <li data-bind="css: { active: $parent.CurrentPage() === $data }">
        <a data-bind="click: $parent.GoToPage, text: $data"></a>
    </li>
    <!-- /ko -->

    <li data-bind="css: { disabled: !NextPageActive() }">
        <a data-bind="click: GoToNext">Next</a>
    </li>
    <li data-bind="css: { disabled: !LastPageActive() }">
        <a data-bind="click: GoToLast">Last</a>
    </li>
</ul>

Features

  1. Show on need
    When there is no need for paging at all (for example the items which need to display less than the page size) then the HTML component will disappear.
    This will be established by statement data-bind="visible: NeedPaging".

  2. Disable on need
    for example, if you are already selected the last page, why the last page or the Next button should be available to press?
    I am handling this and in that case I am disabling those buttons by applying the following binding data-bind="css: { disabled: !PreviousPageActive() }"

  3. Distinguish the Selected page
    a special class (in this case called active class) is applied on the selected page, to make the user know in which page he/she is right now.
    This is established by the binding data-bind="css: { active: $parent.CurrentPage() === $data }"

  4. Last & First
    going to the first and last page is also available by simple buttons dedicated to this.

  5. Limits for displayed buttons
    suppose you have a lot of pages, for example 1000 pages, then what will happened? would you display them all for the user ? absolutely not you have to display just a few of them according to the current page. for example showing 3 pages before the page page and other 3 pages after the selected page.
    This case has been handled here <!-- ko foreach: GetPages() -->
    the GetPages function applying a simple algorithm to determine if we need to show all the pages (the page count is under the threshold, which could be determined easily), or to show just some of the buttons.
    you can determine the threshold by changing the value of the maxPageCount variable
    Right now I assigned it as the following var maxPageCount = 7; which mean that no more than 7 buttons could be displayed for the user (3 before the SelectedPage, and 3 after the Selected Page) and the Selected Page itself.

    You may wonder, what if there was not enough pages after OR before the current page to display? do not worry I am handling this in the algorithm
    for example, if you have 11 pages and you have maxPageCount = 7 and the current selected page is 10, Then the following pages will be shown

    5,6,7,8,9,10(selected page),11

    so we always stratifying the maxPageCount, in the previous example showing 5 pages before the selected page and just 1 page after the selected page.

  6. Selected Page Validation
    All set operation for the CurrentPage observable which determine the selected page by the user, is go through the function SetCurrentPage. In only this function we set this observable, and as you can see from the code, before setting the value we make validation operations to make sure that we will not go beyond the available page of the pages.

  7. Already clean
    I use only pureComputed not computed properties, which means you do not need to bother yourself with cleaning and disposing those properties. Although ,as you will see in example below, you need to dispose some other subscriptions which are outside of the component itself

NOTE 1
You may noticed that I am using some bootstrap classes in this component, This is suitable for me, but of course you can use your own classes instead of the bootstrap classes.
The bootstrap classes which I used here are pagination, pagination-sm, active and disabled
Feel free to change them as you need.

NOTE 2
So I introduced the component for you, It is time to see how it could work.
You would integrate this component in your main ViewModel as like this.

function MainVM() {
    var self = this;

    self.PagingComponent = ko.observable(new Paging({
        pageSize: 10,      // how many items you would show in one page
        totalCount: 100,   // how many ALL the items do you have.
    }));

    self.currentPageSubscription = self.PagingComponent().CurrentPage.subscribe(function (newPage) {
        // here is the code which will be executed when the user change the page.
        // you can handle this in the way you need.
        // for example, in my case, I am requesting the data from the server again by making an ajax request
        // and then updating the component

        var data = /*bring data from server , for example*/
        self.PagingComponent().Update({

            // we need to set this again, why? because we could apply some other search criteria in the bringing data from the server, 
            // so the total count of all the items could change, and this will affect the paging
            TotalCount: data.TotalCount,

            // in most cases we will not change the PageSize after we bring data from the server
            // but the component allow us to do that.
            PageSize: self.PagingComponent().PageSize(),

            // use this statement for now as it is, or you have to made some modifications on the 'Update' function.
            CurrentPage: self.PagingComponent().CurrentPage(),
        });
    });

    self.dispose = function () {
        // you need to dispose the manual created subscription, you have created before.
        self.currentPageSubscription.dispose();
    }
}

Last but not least, Sure do not forget to change the binding in the html component according to you special viewModel, or wrap all the component with the with binding like this

<div data-bind="with: PagingComponent()">
    <!-- put the component here -->
</div>

Cheers

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131