SilverStripe - 根据下拉选择创建分页

2022-01-04 00:00:00 jquery php pagination silverstripe

我正在为 SilverStripe 网站上的页面构建一些分页,默认情况下该页面首先显示所有文章,但用户可以通过从下拉控件中选择年份来选择要查看的文章.

这是文章的模板.现在我有代码可以在页面第一次加载或重新加载时添加分页,并且所有文章都从服务器获取:

 输入字段并将其替换为:$YearFilterForm.

运行 dev/build 后,您应该有一个页面,该页面的表单允许按年份过滤(使用工作分页)

启用 AJAX

使用 AJAX,我们希望能够仅加载页面的更改部分.因此首先要做的是:

1.为应该异步加载的内容创建一个单独的模板

创建模板Includes/ArticleList.ss

<% 循环 $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%><% 循环 $PaginatedReleases.Pages %><% if $CurrentBool %>$PageNum<%其他%><% if $Link %><a href="$Link" class="pagination">$PageNum</a><%其他%>...<%end_if%><%end_if%><% end_loop %><% if $PaginatedReleases.NotLastPage %><a class="next pagination" href="$PaginatedReleases.NextLink">Next</a><%end_if%><%end_if%>

然后可以将您的页面模板精简为:

$YearFilterForm<%包括文章列表%>

dev/build之后,一切都应该像以前一样工作.

2.通过 AJAX 请求页面时,提供部分内容

由于这会影响对 yearindex(未过滤条目)的调用,请在您的控制器中创建一个辅助方法,如下所示:

 受保护的函数 handleYearRequest(SS_HTTPRequest $request){$year = $request->param('ID');$数据=数组('年' =>$年,'分页发布' =>$this->PaginatedReleases($year));if($request->isAjax()) {//在ajax请求的情况下,只渲染部分模板return $this->renderWith('ArticleList', $data);} 别的 {//返回一个数组会导致页面正常渲染返回 $data;}}

然后您可以添加/修改 indexyear 方法以使其看起来相同:

公共函数 year(){返回 $this->handleYearRequest($this->request);}公共函数索引(){返回 $this->handleYearRequest($this->request);}

3.用一些 JavaScript 连接一切

(function($) {$(函数(){//隐藏表单动作,因为我们要触发表单提交//当下拉列表改变时自动$("#Form_YearFilterForm .Actions").hide();//在下拉菜单上绑定更改事件以自动提交$("#Form_YearFilterForm").on("change", "select", function (e) {$("#Form_YearFilterForm").submit();});//处理表单提交事件$("#Form_YearFilterForm").on("提交",函数(e){e.preventDefault();var form = $(this);$("#ArticleList").addClass("loading");//通过ajax提交表单$.post(form.attr("动作"),form.serialize(),功能(数据,状态,xhr){$("#ArticleList").replaceWith($(data));});返回假;});//处理分页点击$("body").on("click", "a.pagination", function (e) {e.preventDefault();$("#ArticleList").addClass("loading");$.get($(this).attr("href"),功能(数据,状态,xhr){$("#ArticleList").replaceWith($(data));});返回假;});});})(jQuery);

结论

您现在有了一个可以在非 JS 设备上正常降级的解决方案.通过下拉菜单和分页过滤启用 AJAX.标记没有在 JS 和 中定义在模板中,它只是负责标记的 SilverStripe 模板.

剩下要做的就是在内容刷新时添加一个漂亮的加载动画;)

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 to view by selecting a year from a dropdown control.

Here is the template for the articles. Right now I have code in place that adds pagination when the page first loads or is reloaded and all articles are grabbed from the server:

<select id="SelectNewsYear">
    <option value="">Select a year</option>
    <% loop $GroupedNewsByDate.GroupedBy(PublishYear) %>
        <option value="$PublishYear">$PublishYear</option>
    <% end_loop %>
    <option value="all">Show all</option>
</select>
<br /><br />

<div 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" href="$PaginatedReleases.PrevLink">Prev</a>
        <% end_if %>
        <% loop $PaginatedReleases.Pages %>
            <% if $CurrentBool %>
                $PageNum
            <% else %>
                <% if $Link %>
                    <a href="$Link">$PageNum</a>
                <% else %>
                    ...
                <% end_if %>
            <% end_if %>
        <% end_loop %>
        <% if $PaginatedReleases.NotLastPage %>
            <a class="next" href="$PaginatedReleases.NextLink">Next</a>
        <% end_if %>
    <% end_if %>
</div>

The PaginatedReleases function in Page.php:

//Returns a paginted list of news releases
public function PaginatedReleases(){
    $newslist = NewsReleaseArticlePage::get()->sort('ArticleDate', "DESC");
    return new PaginatedList($newslist, $this->getRequest());
}

The problem now is figuring out how to maintain the pagination feature whenever a year is selected from the dropdown. Initially, I did not concern myself with pagination as I was more concerned with the functionality of the dropdown list. This is the jQuery and AJAX code I have set up currently that grabs the year value from the dropdown list and passes it to the server to the appropriate function:

(function($) {
    $(document).ready(function() {

        var SelectNewsYear = $('#SelectNewsYear');

        var month = new Array();
        month[0] = "January";
        month[1] = "February";
        month[2] = "March";
        month[3] = "April";
        month[4] = "May";
        month[5] = "June";
        month[6] = "July";
        month[7] = "August";
        month[8] = "September";
        month[9] = "October";
        month[10] = "November";
        month[11] = "December";


        SelectNewsYear.change(function() {

            if (SelectNewsYear.val() != "" && SelectNewsYear.val() != null &&  SelectNewsYear.find('option:selected').attr('value') !="all") {
                sendYear();
            }
            else{
                showAll();
            }
        });

        //get all articles by the year selected
        function sendYear(){
            var year = SelectNewsYear.find('option:selected').attr('value');
            $.ajax({
                type: "POST",
                url: "/home/getNewsByYear/"+year,
                dataType: "json"
            }).done(function (response) {
                var list = '';
                var newsSection = $('.RecentNewsByYear');

                for (var i=0;i<response.length;i++){
                    var newsDate = new Date(response[i].date);
                    var monthFullName = month[newsDate.getUTCMonth()];

                    list += monthFullName + " " + newsDate.getUTCDate() +", " +newsDate.getFullYear()  + ', ' + '<a href="' + response[i].article + '"target="_blank">' + response[i].title +"</a> <br />";
                }
                newsSection.empty();
                newsSection.append(list);
            });
        }

    });
}(jQuery));

$ = jQuery.noConflict();

And the getNewsByYear function from Page.php:

//Get all recent news by year based on selected year from dropdown
    public function getNewsByYear(){
        //Get the year selected by the dropdown
        $newsReleaseYear = $this->getRequest()->param('ID');

        //Group together all news that are associated with that selected year
        $newsReleases = NewsReleaseArticlePage::get();

        $return = array();

        //put the news releases into the array that match the selected year
        foreach($newsReleases as $newsRelease){
            $newsDate = date("Y", strtotime($newsRelease->ArticleDate));

            if($newsDate == $newsReleaseYear){
                $return[] = array(
                    'title' => $newsRelease->H1,
                    'date' => $newsRelease->ArticleDate,
                    'article' => $newsRelease->URLSegment
                );
            }
        }

        return json_encode($return);
    }

The getNewsByYear function works fine as is, but I am not sure how to incorporate the SilverStripe PaginationList feature here. I am wondering if there is a way to return the selected articles without relying on json encoded data?

解决方案

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

相关文章