SilverStripe - Create pagination based on dropdown selection

后端 未结 1 1472
野的像风
野的像风 2021-01-20 02:14

I am working on building out some pagination for a page on a SilverStripe site that is meant to show all articles at first by default, but the user can select which articles

相关标签:
1条回答
  • 2021-01-20 02:55

    There's definitely room for improvement when you return JSON to build HTML markup from JSON…

    I also think it's good practice to write your application logic in a way that works without JS, then add the JS to progressively enhance your application. That way you don't lock out every non-JS device/reader/user.

    So here's what I'd do (prepare for extensive answer):

    Enable filtering by year

    First of all, you want to be able to filter your records by year. I think your approach of enabling filtering via URL is fine, so that's what we're going to do:

    1. Get paginated list, optionally filtered by year

    In your controller, add/modify the following method:

    public function PaginatedReleases($year = null)
    {
        $list = NewsReleaseArticlePage::get()->sort('ArticleDate', 'DESC');
        if ($year) {
            $list = $list->where(array('YEAR("ArticleDate") = ?' => $year));
        }
        return PaginatedList::create($list, $this->getRequest());
    }
    

    This will allow you to get all entries, or only the ones from a certain year by passing in the $year parameter.

    2. Add a year action to your controller

    public static $allowed_actions = array(
        'year' => true
    );
    
    public function year()
    {
        $year = $this->request->param('ID');
        $data = array(
            'Year' => $year,
            'PaginatedReleases' => $this->PaginatedReleases($year)
        );
        return $data;
    }
    

    After running dev/build, you should already be able to filter your entries by year by changing the URL (eg. mypage/year/2016 or mypage/year/2015 etc.)

    Use a form with a dropdown to filter

    Add the following to your controller to create a form to filter your entries:

    public function YearFilterForm()
    {
        // get an array of all distinct years
        $list = SQLSelect::create()
            ->addFrom('NewsReleaseArticlePage')
            ->selectField('YEAR("ArticleDate")', 'Year')
            ->setOrderBy('Year', 'DESC')
            ->addGroupBy('"Year"')->execute()->column('Year');
    
        // create an associative array with years as keys & values
        $values = array_combine($list, $list);
    
        // our fields just contain the dropdown, which uses the year values
        $fields = FieldList::create(array(
            DropdownField::create(
                'Year',
                'Year',
                $values,
                $this->getRequest()->param('ID')
            )->setHasEmptyDefault(true)->setEmptyString('(all)')
        ));
    
        $actions = FieldList::create(array(
            FormAction::create('doFilter', 'Submit')
        ));
    
        return Form::create($this, 'YearFilterForm', $fields, $actions);
    }
    

    Implement the doFilter function. It simply redirects to the proper URL, depending what year was selected:

    public function doFilter($data, $form)
    {
        if(empty($data['Year'])){
            return $this->redirect($this->Link());
        } else {
            return $this->redirect($this->Link('year/' . $data['Year']));
        }
    }
    

    Don't forget to add the Form name to the allowed_actions:

    public static $allowed_actions = array(
        'YearFilterForm' => true, // <- this should be added!
        'year' => true
    );
    

    Now delete your <select> input field from your template and replace it with: $YearFilterForm.

    After running dev/build, you should have a page with a form that allows filtering by year (with working pagination)

    Enable AJAX

    With AJAX, we want to be able to load only the changed portion of the page. Therefore the first thing to do is:

    1. Create a separate template for the content that should be loaded asynchronously

    Create a template Includes/ArticleList.ss

    <div id="ArticleList" class="RecentNews">
        <% loop $PaginatedReleases %>
           $ArticleDate.format("F j, Y"), <a href="$URLSegment">$H1</a><br />
        <% end_loop %>
    
        <% if $PaginatedReleases.MoreThanOnePage %>
            <% if $PaginatedReleases.NotFirstPage %>
                <a class="prev pagination" href="$PaginatedReleases.PrevLink">Prev</a>
            <% end_if %>
            <% loop $PaginatedReleases.Pages %>
                <% if $CurrentBool %>
                    $PageNum
                <% else %>
                    <% if $Link %>
                        <a href="$Link" class="pagination">$PageNum</a>
                    <% else %>
                        ...
                    <% end_if %>
                <% end_if %>
            <% end_loop %>
            <% if $PaginatedReleases.NotLastPage %>
                <a class="next pagination" href="$PaginatedReleases.NextLink">Next</a>
            <% end_if %>
        <% end_if %>
    </div>
    

    Your page template can then be stripped down to:

    $YearFilterForm
    <% include ArticleList %>
    

    After dev/build, everything should work as it did before.

    2. Serve partial content, when page is requested via AJAX

    Since this affects calls to year and index (unfiltered entries), create a helper method in your controller like this:

    protected function handleYearRequest(SS_HTTPRequest $request)
    {
        $year = $request->param('ID');
        $data = array(
            'Year' => $year,
            'PaginatedReleases' => $this->PaginatedReleases($year)
        );
    
        if($request->isAjax()) {
            // in case of an ajax request, render only the partial template
            return $this->renderWith('ArticleList', $data);
        } else {
            // returning an array will cause the page to render normally
            return $data;
        }
    }
    

    You can then add/modify the index and year methods to look identical:

    public function year()
    {
        return $this->handleYearRequest($this->request);
    }
    
    public function index()
    {
        return $this->handleYearRequest($this->request);
    }
    

    3. Wire everything with some JavaScript

    (function($) {
        $(function(){
            // hide form actions, as we want to trigger form submittal
            // automatically when dropdown changes
            $("#Form_YearFilterForm .Actions").hide();
    
            // bind a change event on the dropdown to automatically submit
            $("#Form_YearFilterForm").on("change", "select", function (e) {
                $("#Form_YearFilterForm").submit();
            });
    
            // handle form submit events
            $("#Form_YearFilterForm").on("submit", function(e){
                e.preventDefault();
                var form = $(this);
                $("#ArticleList").addClass("loading");
                // submit form via ajax
                $.post(
                    form.attr("action"),
                    form.serialize(),
                    function(data, status, xhr){
                        $("#ArticleList").replaceWith($(data));
                    }
                );
                return false;
            });
    
            // handle pagination clicks
            $("body").on("click", "a.pagination", function (e) {
                e.preventDefault();
                $("#ArticleList").addClass("loading");
                $.get(
                    $(this).attr("href"),
                    function(data, status, xhr){
                        $("#ArticleList").replaceWith($(data));
                    }
                );
    
                return false;
            });
    
        });
    })(jQuery);
    

    Conclusion

    You now have a solution that gracefully degrades on non JS devices. Filtering via dropdown and pagination is AJAX enabled. The markup isn't defined in JS and in templates, it's just the SilverStripe templates that are responsible for the markup.

    All that is left to do is add a nice loading animation when content refreshes ;)

    0 讨论(0)
提交回复
热议问题