Подтвердить что ты не робот

Как обрабатывать разбиение на страницы с помощью нокаута

У меня есть div, который настроен для привязки к observeableArray, но я хочу показать не более 50 элементов из этого observeableArray в любой момент времени. Я хочу обработать это с разбивкой по страницам с помощью предыдущей и следующей кнопки вместе с индексами на странице, чтобы пользователи могли циклически перебирать страницы элементов из коллекции.
Я знаю, что я мог бы сделать это с помощью computedObservable и пользовательские привязки данных, но я не уверен, как это сделать (я все еще нокаут-неофит).
Может ли кто-нибудь указать мне в правильном направлении?

Вот мой код (JS находится в TypeScript):

<div class="container-fluid">
    <div class="row-fluid">
        <div class="span12">
            <%=
            if params[:q]
              render 'active_search.html.erb'
            else
              render 'passive_search.html.erb'
            end
            %>
            <%= form_tag("/search", method: "get", :class => "form-search form-inline") do %>
            <%= label_tag(:q, "Search for:") %>
            <%= text_field_tag(:q, nil, class:"input-medium search-query") %>
            <%= submit_tag("Search", :class=>"btn") %>
            <% end %>

            <div class="media" data-bind="foreach: tweetsArray">
                <%= image_tag('twitter-icon.svg', :class=>"tweet_img", :style=>"display:inline;") %>
                <div class="media-body" style="display:inline;">
                    <h4 class="media-heading" data-bind="text: user.screen_name" style="display:inline;"></h4>
                    <span data-bind="text:text" style="display:inline;"></span> <br />
                    <span data-bind="text:'Created at '+created_at"></span> <br />
                </div>
            </div>

            <div class="pagination pagination-centered">
                <ul>
                    <li>
                        <a href="#">Prev</a>
                    </li>
                    <li>
                        <a href="#">1</a>
                    </li>
                    <li>
                        <a href="#">Next</a>
                    </li>
                </ul>
            </div>

        </div>
    </div>
</div>

<script>
    var viewModel = new twitterResearch.TweetViewModel();
    ko.applyBindings(viewModel);

    //TODO: notes to self, use custom binding for pagination along with a computed observable to determine where at in the list you are

    //document.onReady callback function
    $(function() {
        $.getJSON('twitter', {}, function(data) {
            viewModel.pushTweet(data);
            console.log(data.user);
        });
    });
</script>

declare var $: any;
declare var ko: any;

module twitterResearch {
    class Tweet {
        text: string;
        created_at: string;
        coordinates: string;
        user: string;
        entities: string;
        id: number;
        id_str: string;

        constructor(_text: string, _created_at: string, _coordinates: any, _user: any,
                    _entities: any, _id_str: string, _id: number){

            this.text = _text;
            this.created_at = _created_at;
            this.coordinates = _coordinates;
            this.user = _user;
            this.entities = _entities;
            this.id_str = _id_str;
            this.id = _id;
        }
    }

    export class TweetViewModel{

        tweetsArray: any;
        constructor()
        {
            this.tweetsArray = ko.observableArray([]);
        }

        //tweet is going to be the JSON tweet we return
        //from the server
        pushTweet(tweet)
        {
            var _tweet = new Tweet(tweet.text, tweet.created_at, tweet.coordinates,
                                    tweet.user, tweet.entities, tweet.id_str, tweet.id);
            this.tweetsArray.push(_tweet);
            this.tweetsArray.valueHasMutated();
        }
    }
}
4b9b3361

Ответ 1

Разбиение страницы довольно просто с нокаутом. Я лично достиг этого так:

  • Имеет наблюдаемый массив, содержащий все ваши элементы.
  • Имеет наблюдаемое, содержащее текущую страницу (с инициализацией до 0)
  • Имейте переменную, объявляющую количество элементов на странице
  • Вычислили, что возвращает количество страниц, рассчитанное с учетом количества элементов на странице и общего количества элементов.
  • Наконец, добавьте вычисленный, который срезает массив, содержащий все элементы.

Учитывая это, теперь вы можете добавить функцию, которая увеличивает (далее) или уменьшает (предыдущую) текущую страницу.

Вот краткий пример:

var Model = function() {
    var self = this;
    this.all = ko.observableArray([]);
    this.pageNumber = ko.observable(0);
    this.nbPerPage = 25;
    this.totalPages = ko.computed(function() {
        var div = Math.floor(self.all().length / self.nbPerPage);
        div += self.all().length % self.nbPerPage > 0 ? 1 : 0;
        return div - 1;
    });

    this.paginated = ko.computed(function() {
        var first = self.pageNumber() * self.nbPerPage;
        return self.all.slice(first, first + self.nbPerPage);
    });

    this.hasPrevious = ko.computed(function() {
        return self.pageNumber() !== 0;
    });

    this.hasNext = ko.computed(function() {
        return self.pageNumber() !== self.totalPages();
    });

    this.next = function() {
        if(self.pageNumber() < self.totalPages()) {
            self.pageNumber(self.pageNumber() + 1);
        }
    }

    this.previous = function() {
        if(self.pageNumber() != 0) {
            self.pageNumber(self.pageNumber() - 1);
        }
    }
}

Здесь вы найдете простой и полный пример: http://jsfiddle.net/LAbCv/ (может быть, немного глючит, но идея есть).

Ответ 2

На самом деле я работаю на веб-сайте с большим количеством таблиц (большинство из них нуждаются в подкачке), поэтому на самом деле мне понадобился reusable-component для подкачки, чтобы использовать его во всех случаях, в которых мне нужен пейджинг.
Также мне нужны более сложные функции, чем в принятом ответе на этот вопрос.

Итак, я разработал свой собственный компонент для решения этой проблемы, вот он.

Теперь о Github

JsFiddle

И для более подробной информации, продолжайте читать (пожалуйста, подумайте о том, чтобы взять код из github, а не отсюда, так как код github был обновлен и улучшен, так как я положил его здесь)

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>

Функции

  • Показывать по необходимости
    Если нет необходимости в пейджинге вообще (например, элементы, которые должны отображаться меньше размера страницы), тогда компонент HTML исчезнет.
    Это будет установлено выражением data-bind="visible: NeedPaging".

  • Отключить по необходимости
    например, если вы уже выбрали последнюю страницу, почему кнопка last page или Next должна быть доступна для нажатия?
    Я обрабатываю это, и в этом случае я отключу эти кнопки, применив следующую привязку data-bind="css: { disabled: !PreviousPageActive() }"

  • Разделить выбранную страницу
    на выбранную страницу применяется специальный класс (в данном случае класс active), чтобы пользователь знал, на какой странице он сейчас находится.
    Это устанавливается привязкой data-bind="css: { active: $parent.CurrentPage() === $data }"

  • Последняя и первая
    переход на первую и последнюю страницу также осуществляется с помощью простых кнопок, посвященных этому.

  • Ограничения для отображаемых кнопок
    предположим, что у вас много страниц, например 1000 страниц, и что произойдет? вы бы отобразили их все для пользователя? абсолютно не нужно отображать только некоторые из них в соответствии с текущей страницей. например, показ 3 страниц до и других 3 страниц после выбранной страницы.
    Этот случай был рассмотрен здесь <!-- ko foreach: GetPages() -->
    функция GetPages, применяющая простой алгоритм, чтобы определить, нужно ли показывать все страницы (количество страниц находится под порогом, которое может быть легко определено) или показать только некоторые из кнопок.
    вы можете определить порог, изменив значение переменной maxPageCount
    Прямо сейчас я назначил его следующим var maxPageCount = 7;, который означает, что для пользователя может отображаться не более 7 кнопок (3 до SelectedPage и 3 после выбранной страницы) и самой выбранной страницы.

    Вы можете задаться вопросом, что, если страниц ИЛИ не хватило, чтобы отобразить текущую страницу? не беспокойтесь, я обрабатываю это в алгоритме
    например, если у вас есть 11 pages, и у вас есть maxPageCount = 7 и текущий selected page is 10, тогда будут показаны следующие страницы

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

    поэтому мы всегда стратифицируем maxPageCount в предыдущем примере, показывая страницы 5 перед выбранной страницей и только 1 после выбранной страницы.

  • Выбранная проверка страницы
    Вся операция установки для наблюдаемого CurrentPage, определяющего выбранную страницу пользователем, проходит через функцию SetCurrentPage. Только в этой функции мы устанавливаем это наблюдаемое, и, как вы можете видеть из кода, перед установкой значения мы делаем операции проверки, чтобы убедиться, что мы не выйдем за пределы доступной страницы страниц.

  • Уже чистый
    Я использую только свойства pureComputed not computed, что означает, что вам не нужно беспокоиться о чистке и удалении этих свойств. Хотя, как вы увидите в примере ниже, вам нужно распорядиться некоторыми другими подписками, которые находятся вне самого компонента

ПРИМЕЧАНИЕ 1
Вы можете заметить, что я использую несколько классов bootstrap в этом компоненте, Это подходит для меня, но, конечно, вы можете использовать свои собственные классы вместо классов начальной загрузки.
Используемые здесь классы начальной загрузки pagination, pagination-sm, active и disabled
Не стесняйтесь изменять их по мере необходимости.

ПРИМЕЧАНИЕ 2
Поэтому я представил компонент для вас, пришло время посмотреть, как он может работать.
Вы бы интегрировали этот компонент в свой основной ViewModel, как это.

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();
    }
}

И последнее, но не менее важное: Конечно, не забудьте изменить привязку в html-компоненте в соответствии с вашим специальным представлениемModel или обернуть весь компонент с помощью with binding, как этот

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

Приветствия

Ответ 3

Я создал blogpost с подробным объяснением того, как создать разбиение на страницы с помощью небольшого плагина JQuery (здесь).

В принципе, я использовал обычную привязку данных к нокауту с AJAX и после того, как данные были получены с сервера, я вызываю плагин. Здесь вы можете найти плагин здесь. Это называется "Простая разбивка".

Ответ 4

Этот вопрос по-прежнему остается одним из лучших поисков "разбивки на нокаут", поэтому расширение нокаута knockout-paging (git). Он предоставляет разбиение на страницы, расширяя ko.observableArray. Он хорошо документирован и прост в использовании.
Пример использования здесь.