Display list of Players supporting pagination, with each page displaying 6 Players
PlayerCollectionProvider
supporting paginationblade init -v portal-7.4-ga3 player
Add to gradle.properties the following properties matching your local environment:
# Set the folder that contains the Liferay bundle downloaded from the
# "liferay.workspace.bundle.url" property. The default value is "bundles".
liferay.workspace.home.dir=/your/path/to/bundles
# Set the folder that contains the Liferay portal source code
liferay.source.home.dir=/your/path/to/liferay-portal
blade create \
-t service \
-p com.liferay.player.web.internal.info.collection.provider \
-c PlayerCollectionProvider \
-s com.liferay.info.collection.provider.InfoCollectionProvider
-v 7.4 \
player-web
blade deploy
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider implements InfoCollectionProvider {
@Override
public InfoPage getCollectionInfoPage(CollectionQuery collectionQuery) {
return null;
}
@Override
public String getLabel(Locale locale) {
return null;
}
}
public class Player {
public Player(long id, String name, String image) {
_id = id;
_name = name;
_image = image;
}
public long getId() {
return _id;
}
public String getImage() {
return _image;
}
public String getName() {
return _name;
}
private long _id;
private String _image;
private String _name;
}
public class Player implements ClassedModel {
@Override
public Class<?> getModelClass() {
return Player.class;
}
@Override
public String getModelClassName() {
return Player.class.getName();
}
@Override
public Serializable getPrimaryKeyObj() {
return _id;
}
// ...
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements InfoCollectionProvider<Player> {
@Override
public InfoPage<Player> getCollectionInfoPage(
CollectionQuery collectionQuery) {
return null;
}
@Override
public String getLabel(Locale locale) {
return null;
}
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements InfoCollectionProvider<Player> {
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "players");
}
}
bnd.bnd
Provide-Capability:\
liferay.language.resources;\
resource.bundle.base.name="content.Language"
Language.properties
model.resource.com.liferay.player.web.internal.model.Player=Player
players=Players
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider implements InfoCollectionProvider<Player> {
@Override
public InfoPage<Player> getCollectionInfoPage(CollectionQuery collectionQuery) {
List<Player> players = new ArrayList();
players.add(new Player(1, "Michael Jordan", _PLAYER1_IMAGE_URL));
players.add(new Player(2, "Shaquille O'Neal", _PLAYER2_IMAGE_URL));
return InfoPage.of(players);
}
// ..
}
@Component(immediate = true, service = InfoItemFieldValuesProvider.class)
public class PlayerInfoItemFieldValuesProvider
implements InfoItemFieldValuesProvider<Player> {
@Override
public InfoItemFieldValues getInfoItemFieldValues(Player player) {
return InfoItemFieldValues.builder(
).infoFieldValues(
_getInfoFieldValues(player)
).infoItemReference(
new InfoItemReference(Player.class.getName(), player.getId())
).build();
}
private List<InfoFieldValue<Object>> _getInfoFieldValues(Player player) {
List<InfoFieldValue<Object>> infoFieldValues = new ArrayList<>();
infoFieldValues.add(
new InfoFieldValue<>(
PlayerInfoFields.nameInfoField, player.getName()));
infoFieldValues.add(
new InfoFieldValue<>(
PlayerInfoFields.imageInfoField, new WebImage(player.getImage())));
return infoFieldValues;
}
}
public interface PlayerInfoFields {
public final InfoField<ImageInfoFieldType> imageInfoField =
InfoField.builder(
).infoFieldType(
ImageInfoFieldType.INSTANCE
).name(
"image"
).labelInfoLocalizedValue(
InfoLocalizedValue.localize(Player.class, "image")
).build();
public final InfoField<TextInfoFieldType> nameInfoField = InfoField.builder(
).infoFieldType(
TextInfoFieldType.INSTANCE
).name(
"name"
).labelInfoLocalizedValue(
InfoLocalizedValue.localize(Player.class, "name")
).build();
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements InfoCollectionProvider<Player> {
@Override
public InfoPage<Player> getCollectionInfoPage(
CollectionQuery collectionQuery) {
List<Player> players = new ArrayList();
players.add(new Player(1, "Player 1", _PLAYER1_IMAGE_URL));
players.add(new Player(2, "Player 2", _PLAYER2_IMAGE_URL));
// ...
players.add(new Player(21, "Player 21", _PLAYER21_IMAGE_URL));
return InfoPage.of(players);
}
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements InfoCollectionProvider<Player> {
@Override
public InfoPage<Player> getCollectionInfoPage(
CollectionQuery collectionQuery) {
List<Player> players = new ArrayList();
players.add(new Player(1, "Player 1", _PLAYER1_IMAGE_URL));
players.add(new Player(2, "Player 2", _PLAYER2_IMAGE_URL));
// ...
players.add(new Player(21, "Player 21", _PLAYER21_IMAGE_URL));
Pagination pagination = collectionQuery.getPagination();
return InfoPage.of(
ListUtil.subList(
players, pagination.getStart(), pagination.getEnd()));
}
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements InfoCollectionProvider<Player> {
@Override
public InfoPage<Player> getCollectionInfoPage(
CollectionQuery collectionQuery) {
List<Player> players = new ArrayList();
players.add(new Player(1, "Player 1", _PLAYER1_IMAGE_URL));
players.add(new Player(2, "Player 2", _PLAYER2_IMAGE_URL));
// ...
players.add(new Player(21, "Player 21", _PLAYER21_IMAGE_URL));
Pagination pagination = collectionQuery.getPagination();
return InfoPage.of(
ListUtil.subList(
players, pagination.getStart(), pagination.getEnd()),
pagination, players.size());
}
}
@Component(immediate = true, service = InfoItemFormProvider.class)
public class PlayerInfoItemFormProvider
implements InfoItemFormProvider<Player> {
@Override
public InfoForm getInfoForm() {
return InfoForm.builder(
).infoFieldSetEntry(
InfoFieldSet.builder(
).infoFieldSetEntry(
PlayerInfoFields.nameInfoField
).infoFieldSetEntry(
PlayerInfoFields.imageInfoField
).labelInfoLocalizedValue(
InfoLocalizedValue.localize(PlayerInfoFields.class, "player")
).build()
).name(
"Player"
).build();
}
}
Requirement
Allow Page Creator to filter players based on their country.
Solution
Player
, PlayerInfoFields
, PlayerInfoItemFormProvider
, PlayerInfoItemFieldValuesProvider
and PlayerCollectionProvider
PlayerCollectionProvider
implement ConfigurableInfoCollectionProvider
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player> {
// ..
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player> {
@Override
public InfoForm getConfigurationInfoForm() {
return InfoForm.builder(
).infoFieldSetEntry(
PlayerInfoFields.countryInfoField
).build();
}
// ...
}
public class PlayerCollectionProvider implements ConfigurableInfoCollectionProvider<Player> {
public InfoPage<Player> getCollectionInfoPage(CollectionQuery collectionQuery) {
// ...
List<Player> filteredPlayers = ListUtil.copy(players);
Optional<Map<String, String[]>> configurationOptional =
collectionQuery.getConfigurationOptional();
if (configurationOptional.isPresent()) {
Map<String, String[]> configurationMap = configurationOptional.get();
String[] countries = configurationMap.get(
PlayerInfoFields.countryInfoField.getName());
filteredPlayers = filteredPlayers.stream().filter(
player -> ArrayUtil.contains(countries, player.getCountry())
).collect(Collectors.toList());
}
List<Player> pageFilteredPlayers = ListUtil.subList(
filteredPlayers, pagination.getStart(), pagination.getEnd());
return InfoPage.of(pageFilteredPlayers, pagination, filteredPlayers.size());
}
}
Requirement
Allow Page Creator to select country for filtering from a drop down menu.
Solution
countryInfoField
to SelectInfoFieldType
OPTIONS
attribute with available Country valuespublic class PlayerInfoFields {
public static final InfoField<SelectInfoFieldType> countryInfoField =
InfoField.builder(
).infoFieldType(
SelectInfoFieldType.INSTANCE
).name(
"country"
).labelInfoLocalizedValue(
InfoLocalizedValue.localize(Player.class, "country")
).attribute(
SelectInfoFieldType.OPTIONS, _getCountryOptions()
).build();
private static List<SelectInfoFieldType.Option> _getCountryOptions() {
List<SelectInfoFieldType.Option> options = new ArrayList();
options.add(new SelectInfoFieldType.Option("Argentina", "Argentina"));
options.add(new SelectInfoFieldType.Option("China", "China"));
options.add(new SelectInfoFieldType.Option("Spain", "Spain"));
options.add(new SelectInfoFieldType.Option("USA", "USA"));
return options;
}
}
Requirement
Allow Page Creator to select multiple countries for filtering from a drop down menu.
Solution
MULTIPLE
attribute to true
public class PlayerInfoFields {
public static final InfoField<SelectInfoFieldType> countryInfoField =
InfoField.builder(
).infoFieldType(
SelectInfoFieldType.INSTANCE
).name(
"country"
).labelInfoLocalizedValue(
InfoLocalizedValue.localize(Player.class, "country")
).attribute(
SelectInfoFieldType.OPTIONS, _getCountryOptions()
).attribute(
SelectInfoFieldType.MULTIPLE, true
).build();
// ..
}
Requirement
Display national teams and let end user filter them based on entered continent and languages selected on a drop down.
Solution
Allow filtering countries in which at least one language of two different groups is spoken.
Add a second Categories Collection Filter fragment
Let end user filter Players based on the Olympics where they participated
Support Keywords filter on PlayerCollectionProvider and allow end user to enter Olympic cities as comma separated string to filter Players.
public class Olympics {
public Olympics(int year, String city) {
_year = year;
_city = city;
}
public String getCity() {
return _city;
}
public int getYear() {
return _year;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(6);
sb.append(_city);
sb.append(" ");
sb.append(_year);
return sb.toString();
}
private String _city;
private int _year;
}
public class Player implements ClassedModel {
public Player(
long id, String name, String image, String country,
Olympics[] olympics) {
_id = id;
_name = name;
_image = image;
_country = country;
_olympics = olympics;
}
public Olympics[] getOlympics() {
return _olympics;
}
private Olympics[] _olympics;
// ..
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
@Override
public List<InfoFilter> getSupportedInfoFilters() {
return Collections.singletonList(new KeywordsInfoFilter());
}
// ..
}
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
public InfoPage<Player> getCollectionInfoPage(CollectionQuery collectionQuery) {
// ..
Optional<KeywordsInfoFilter> keywordsInfoFilterOptional =
collectionQuery.getInfoFilterOptional(KeywordsInfoFilter.class);
if (keywordsInfoFilterOptional.isPresent()) {
KeywordsInfoFilter keywordsInfoFilter = keywordsInfoFilterOptional.get();
String keywords = keywordsInfoFilter.getKeywords();
if (Validator.isNotNull(keywords)) {
String[] keywordsArray = keywords.split(", ");
if (keywordsArray.length > 0) {
Stream<Player> stream = filteredPlayers.stream();
filteredPlayers = stream.filter(
player -> {
List<String> olympicCities = Stream.of(player.getOlympics())
.map(Olympics::getCity).collect(Collectors.toList());
return olympicCities.containsAll(Arrays.asList(keywordsArray));
}
).collect(Collectors.toList());
}
}
}
// ..
}
}
Let end user filter Players based on the Olympics where they participated and whether they won any gold, silver or bronze medals.
Support Categories filter on PlayerCollectionProvider and allow end user to select Gold / Silver / Bronze in a drop down menu with multiple selection.
public class Player implements ClassedModel {
public Player(
long id, String name, String image, String country, Olympics[] olympics,
int[] goldMedals, int[] silverMedals, int[] bronzeMedals) {
// ..
_goldMedals = goldMedals;
_silverMedals = silverMedals;
_bronzeMedals = bronzeMedals;
}
public int[] getBronzeMedals() {
return _bronzeMedals;
}
// ..
public int[] getSilverMedals() {
return _silverMedals;
}
private int[] _bronzeMedals;
private int[] _goldMedals;
private int[] _silverMedals;
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
// ..
@Override
public List<InfoFilter> getSupportedInfoFilters() {
List<InfoFilter> infoFilters = new ArrayList<>();
infoFilters.add(new CategoriesInfoFilter());
infoFilters.add(new KeywordsInfoFilter());
return infoFilters;
}
}
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
public InfoPage<Player> getCollectionInfoPage(CollectionQuery collectionQuery) {
// ..
Optional<CategoriesInfoFilter> categoriesInfoFilterOptional =
collectionQuery.getInfoFilterOptional(CategoriesInfoFilter.class);
if (categoriesInfoFilterOptional.isPresent()) {
CategoriesInfoFilter categoriesInfoFilter = categoriesInfoFilterOptional.get();
long[] categoryIds = ArrayUtil.append(categoriesInfoFilter.getCategoryIds());
categoryIds = ArrayUtil.unique(categoryIds);
LongStream longStream = Arrays.stream(categoryIds);
List<String> categoryNames = longStream.mapToObj(
categoryId -> _assetCategoryService.fetchCategory(categoryId)
).filter(Objects::nonNull).map(AssetCategory::getName).collect(Collectors.toList());
// [continues]
}
// ..
}
}
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
public InfoPage<Player> getCollectionInfoPage(CollectionQuery collectionQuery) { //..
if (categoriesInfoFilterOptional.isPresent()) { // [continued]
if (!categoryNames.isEmpty()) {
Stream<Player> stream = filteredPlayers.stream();
filteredPlayers = stream.filter(player -> {
boolean matches = true;
for (String categoryName : categoryNames) {
if (categoryName.equals("Gold")) {
int[] goldMedals = player.getGoldMedals();
matches = matches && (goldMedals.length > 0);
}
// ..
if (categoryName.equals("Bronze")) {
int[] goldMedals = player.getBronzeMedals();
matches = matches && (goldMedals.length > 0);
}
}
return matches;
}
).collect(Collectors.toList());
}
} // ..
}
}
Filter Players based on a range of total medals won
Create new Filter that allows setting a minimum and maximum value for number of medals and filter players returned by PlayerCollectionProvider based on values selected
getCollectionFilterValue
and setCollectionFilterValue
public class NumericRangeInfoFilter implements InfoFilter {
public static final String FILTER_TYPE_NAME = "numericRange";
@Override
public String getFilterTypeName() {
return FILTER_TYPE_NAME;
}
public int getMax() {
return _max;
}
public int getMin() {
return _min;
}
public void setMax(int max) {
_max = max;
}
public void setMin(int min) {
_min = min;
}
private int _max;
private int _min;
}
@Component(immediate = true, service = InfoFilterProvider.class)
public class NumericRangeInfoFilterProvider
implements InfoFilterProvider<NumericRangeInfoFilter> {
@Override
public NumericRangeInfoFilter create(Map<String, String[]> values) {
NumericRangeInfoFilter numericRangeInfoFilter = new NumericRangeInfoFilter();
for (Map.Entry<String, String[]> entry : values.entrySet()) {
if (StringUtil.startsWith(
entry.getKey(), NumericRangeInfoFilter.FILTER_TYPE_NAME + "_")) {
String[] value = entry.getValue();
if ((value != null) && (value.length > 0)) {
String[] valueParts = value[0].split("-");
if (valueParts.length > 0) {
numericRangeInfoFilter.setMin(
GetterUtil.getInteger(valueParts[0], -1));
} else {
numericRangeInfoFilter.setMin(-1);
}
// ..
}
return numericRangeInfoFilter;
}
}
return null;
}
}
<%
FragmentRendererContext fragmentRendererContext =
(FragmentRendererContext)request.getAttribute(FragmentRendererContext.class.getName());
FragmentEntryLink fragmentEntryLink = fragmentRendererContext.getFragmentEntryLink();
String fragmentEntryLinkNamespace =
fragmentEntryLink.getNamespace() + fragmentEntryLink.getFragmentEntryLinkId();
%>
<form id="<%= fragmentEntryLinkNamespace %>form">
<label>
<span><%= LanguageUtil.get(request, "number-of-medals") %></span>
</label>
<br />
<label>
<span><%= LanguageUtil.get(request, "minimum") %></span>
<input id="min" max="28" min="0" name="min" type="number" />
</label>
<label>
<span><%= LanguageUtil.get(request, "maximum") %></span>
<input id="max" max="28" min="0" name="max" type="number" />
</label>
</form>
<liferay-frontend:component
context='<%=
HashMapBuilder.<String, Object>put(
"fragmentEntryLinkId", fragmentEntryLink.getFragmentEntryLinkId()
).put(
"fragmentEntryLinkNamespace", fragmentEntryLinkNamespace
).build()
%>'
module="NumericRangeFilter"
/>
getCollectionFilterValue
import {getCollectionFilterValue, setCollectionFilterValue,}
from '@liferay/fragment-renderer-collection-filter-impl';
export default function NumericRangeFilter(
{fragmentEntryLinkId, fragmentEntryLinkNamespace,}) {
const form = document.getElementById(`${fragmentEntryLinkNamespace}form`);
const minInput = form && form.elements['min'];
const maxInput = form && form.elements['max'];
if (!form || (!minInput && !maxInput)) {return;}
const urlValue = getCollectionFilterValue('numericRange', fragmentEntryLinkId);
if (urlValue !== null) {
const parts = urlValue.split('-');
minInput.value = isNaN(parts[0]) ? '' : parts[0];
maxInput.value = isNaN(parts[1]) ? '' : parts[1];
}
// ..
}
setCollectionFilterValue
import {getCollectionFilterValue, setCollectionFilterValue,}
from '@liferay/fragment-renderer-collection-filter-impl';
export default function NumericRangeFilter(
{fragmentEntryLinkId, fragmentEntryLinkNamespace,}) {
const form = document.getElementById(`${fragmentEntryLinkNamespace}form`);
const minInput = form && form.elements['min'];
const maxInput = form && form.elements['max'];
// ..
const handleChange = () => {
setCollectionFilterValue(
'numericRange', fragmentEntryLinkId, `${minInput.value}-${maxInput.value}`);};
form.addEventListener('change', handleChange);
return {
dispose() {form.removeEventListener('change', handleChange);},
};
}
@Component
public class NumericRangeFragmentCollectionFilter implements FragmentCollectionFilter {
@Override
public String getFilterKey() {
return "numericRange";
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "numeric-range");
}
@Override
public void render(FragmentRendererContext fragmentRendererContext,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
try {
RequestDispatcher requestDispatcher =
_servletContext.getRequestDispatcher("/page.jsp");
requestDispatcher.include(httpServletRequest, httpServletResponse);
}
catch (Exception exception) {}
}
@Reference(target = "(osgi.web.symbolicname=com.liferay.player.web)")
private ServletContext _servletContext;
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
// ..
@Override
public List<InfoFilter> getSupportedInfoFilters() {
List<InfoFilter> infoFilters = new ArrayList<>();
infoFilters.add(new CategoriesInfoFilter());
infoFilters.add(new KeywordsInfoFilter());
infoFilters.add(new NumericRangeInfoFilter());
return infoFilters;
}
}
@Component(immediate = true, service = InfoCollectionProvider.class)
public class PlayerCollectionProvider
implements ConfigurableInfoCollectionProvider<Player>,
FilteredInfoCollectionProvider<Player> {
@Override
public InfoPage<Player> getCollectionInfoPage(CollectionQuery collectionQuery) {// ..
Optional<NumericRangeInfoFilter> optional =
collectionQuery.getInfoFilterOptional(NumericRangeInfoFilter.class);
if (optional.isPresent()) {
NumericRangeInfoFilter numericRangeInfoFilter = optional.get();
int min = numericRangeInfoFilter.getMin();
int max = numericRangeInfoFilter.getMax();
if ((min != -1) || (max != -1)) {
Stream<Player> stream = filteredPlayers.stream();
filteredPlayers = stream.filter(player -> {
boolean matches = true;
int totalMedals = player.getGoldMedals().length +
player.getSilverMedals().length + player.getBronzeMedals().length;
if (min != -1) {matches = matches && (totalMedals >= min);}
if (max != -1) {matches = matches && (totalMedals <= max);}
return matches;
}).collect(Collectors.toList());
}
}// ..
}
}
Display the logos of the Olympics where each player participated
Create a Related Items Collection Provider that given a Player returns list of Olympics he participated
@Component(immediate = true, service = RelatedInfoItemCollectionProvider.class)
public class OlympicsWherePlayerParticipatedCollectionProvider
implements RelatedInfoItemCollectionProvider<Player, Olympics> {
@Override
public InfoPage<Olympics> getCollectionInfoPage(CollectionQuery collectionQuery) {
Optional<Object> relatedItemOptional =
collectionQuery.getRelatedItemObjectOptional();
Object relatedItem = relatedItemOptional.orElse(null);
if (!(relatedItem instanceof Player)) {
return InfoPage.of(Collections.emptyList());
}
Player player = (Player)relatedItem;
return InfoPage.of(Arrays.asList(player.getOlympics()));
}
@Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "olympics-where-a-player-participated");
}
}