SilverStripe - 根据下拉选择创建分页
我正在为 SilverStripe 网站上的页面构建一些分页,默认情况下该页面首先显示所有文章,但用户可以通过从下拉控件中选择年份来选择要查看的文章.
这是文章的模板.现在我有代码可以在页面第一次加载或重新加载时添加分页,并且所有文章都从服务器获取:
Page.php 中的 PaginatedReleases 函数:
//返回分页的新闻发布列表公共函数分页发布(){$newslist = NewsReleaseArticlePage::get()->sort('ArticleDate', "DESC");返回新的分页列表($newslist, $this->getRequest());}
现在的问题是弄清楚如何在从下拉列表中选择一年时维护分页功能.最初,我并不关心分页,因为我更关心下拉列表的功能.这是我当前设置的 jQuery 和 AJAX 代码,它从下拉列表中获取年份值并将其传递给服务器以执行相应的函数:
(function($) {$(document).ready(function() {var SelectNewsYear = $('#SelectNewsYear');var 月 = 新数组();月[0] =一月";月 [1] = "二月";月 [2] = "三月";月 [3] = "四月";月[4] = "五月";月 [5] = "六月";月[6] = "七月";月[7] = "八月";月[8] = "九月";月[9] = "十月";月[10] = "十一月";月[11] = "十二月";选择NewsYear.change(函数(){if (SelectNewsYear.val() != "" && SelectNewsYear.val() != null && SelectNewsYear.find('option:selected').attr('value') !="all"){发送年();}别的{显示所有();}});//获取所选年份的所有文章函数发送年(){var year = SelectNewsYear.find('option:selected').attr('value');$.ajax({类型:POST",url: "/home/getNewsByYear/"+year,数据类型:json"}).完成(功能(响应){变量列表 = '';var newsSection = $('.RecentNewsByYear');for (var i=0;i<response.length;i++){var newsDate = new Date(response[i].date);var monthFullName = 月[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();
以及来自 Page.php 的 getNewsByYear 函数:
//根据下拉列表中选择的年份按年份获取所有最近的新闻公共函数 getNewsByYear(){//获取下拉选择的年份$newsReleaseYear = $this->getRequest()->param('ID');//将与所选年份相关的所有新闻组合在一起$newsReleases = NewsReleaseArticlePage::get();$return = 数组();//将新闻稿放入与所选年份匹配的数组中foreach($newsReleases as $newsRelease){$newsDate = date("Y", strtotime($newsRelease->ArticleDate));if($newsDate == $newsReleaseYear){$return[] = 数组('标题' =>$newsRelease->H1,'日期' =>$newsRelease->ArticleDate,'文章' =>$newsRelease->URLSegment);}}返回 json_encode($return);}
getNewsByYear 函数可以正常工作,但我不确定如何在此处合并 SilverStripe PaginationList 功能.我想知道是否有一种方法可以在不依赖 json 编码数据的情况下返回所选文章?
解决方案当您返回 JSON 以从 JSON 构建 HTML 标记时,肯定有改进的余地...
我还认为以一种无需 JS 的方式编写您的应用程序逻辑,然后添加 JS 以逐步增强您的应用程序是一种很好的做法.这样你就不会锁定所有非 JS 设备/阅读器/用户.
所以这就是我要做的(准备广泛的答案):
启用按年份过滤
首先,您希望能够按年份过滤您的记录.我认为您通过 URL 启用过滤的方法很好,所以这就是我们要做的:
1.获取分页列表,可选择按年份过滤
在您的控制器中,添加/修改以下方法:
公共函数PaginatedReleases($year = null){$list = NewsReleaseArticlePage::get()->sort('ArticleDate', 'DESC');如果($年){$list = $list->where(array('YEAR("ArticleDate") = ?' => $year));}return PaginatedList::create($list, $this->getRequest());}
这将允许您通过传入 $year
参数来获取所有条目,或仅获取特定年份的条目.
2.向控制器添加年份操作
public static $allowed_actions = array('年' =>真的);公共函数年(){$year = $this->request->param('ID');$数据=数组('年' =>$年,'分页发布' =>$this->PaginatedReleases($year));返回 $data;}
运行 dev/build
后,您应该已经能够通过更改 URL(例如 mypage/year/2016
或 mypage/year/2015
等)
使用带有下拉菜单的表单进行过滤
将以下内容添加到您的控制器以创建一个表单来过滤您的条目:
公共函数 YearFilterForm(){//获取所有不同年份的数组$list = SQLSelect::create()->addFrom('NewsReleaseArticlePage')->selectField('YEAR("ArticleDate")', 'Year')->setOrderBy('Year', 'DESC')->addGroupBy('"Year"')->execute()->column('Year');//创建一个以年份为键的关联数组 &价值观$values = array_combine($list, $list);//我们的字段只包含使用年份值的下拉列表$fields = FieldList::create(array(下拉字段::创建('年','年',$值,$this->getRequest()->param('ID'))->setHasEmptyDefault(true)->setEmptyString('(all)')));$actions = FieldList::create(array(FormAction::create('doFilter', '提交')));return Form::create($this, 'YearFilterForm', $fields, $actions);}
实现doFilter
函数.它只是重定向到正确的 URL,具体取决于选择的年份:
公共函数 doFilter($data, $form){if(empty($data['Year'])){返回 $this->redirect($this->Link());} 别的 {return $this->redirect($this->Link('year/' . $data['Year']));}}
不要忘记将表单名称添加到allowed_actions
:
public static $allowed_actions = array('YearFilterForm' =>true,//<- 这应该添加!'年' =>真的);
现在从模板中删除您的 输入字段并将其替换为:
$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 请求页面时,提供部分内容
由于这会影响对 year
和 index
(未过滤条目)的调用,请在您的控制器中创建一个辅助方法,如下所示:
受保护的函数 handleYearRequest(SS_HTTPRequest $request){$year = $request->param('ID');$数据=数组('年' =>$年,'分页发布' =>$this->PaginatedReleases($year));if($request->isAjax()) {//在ajax请求的情况下,只渲染部分模板return $this->renderWith('ArticleList', $data);} 别的 {//返回一个数组会导致页面正常渲染返回 $data;}}
然后您可以添加/修改 index
和 year
方法以使其看起来相同:
公共函数 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 ;)
相关文章