package er.extensions.batching;
import java.io.Serializable;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WODisplayGroup;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSMutableArray;
import er.extensions.appserver.ERXDisplayGroup;
import er.extensions.components.ERXComponent;
import er.extensions.localization.ERXLocalizer;
/**
* <p>
* ERXFlickrBatchNavigation is a batch navigation component that provides
* pagination that behaves like the paginator on Flickr.com.
* </p>
*
* <p>
* Include ERXFlickrBatchNavigation.css in ERExtensions for a default stylesheet
* that looks (very) similar to Flickr.
* </p>
* <p>Can also be used for pagination on the parent component, where the objects being paginated may be POJOs in an array,
* or where paging all the objects in the allObjects array is not feasible due to memory requirements.</p>
*
* @author mschrag
* @author rob, cug (non displayGroup batching)
*
* @binding displayGroup the display group to paginate
* @binding displayName the name of the items that are being display ("photo", "bug", etc)
* @binding showPageRange if <code>true</code>, the range of items on the page is shown, for example "(1-7 of 200 items)"
* @binding showBatchSizes if <code>true</code>, a menu to change the items per page is shown "Show: (10) 20 (100) (All) items per page"
* @binding batchSizes can be either a string or an NSArray of numbers that define the batch sizes to chose from. The number "0" provides an "All" items batch size. For example "10,20,30" or "10,50,100,0"
* @binding small if <code>true</code>, a compressed page count style is used
*
* @binding parentActionName (if you don't provide a displayGroup) the action to be executed on the parent component to get the next batch of items.
* @binding currentBatchIndex (if you don't provide a displayGroup) used to get and set on the parent component the selected page index
* @binding maxNumberOfObjects (if you don't provide a displayGroup) used to get the total number of objects that are being paginated.
* @binding numberOfObjectsPerBatch (if you don't provide a displayGroup) the number of objects per batch (page)
*/
public class ERXFlickrBatchNavigation extends ERXComponent {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
private int _lastPageCount;
private int _lastPageSize;
private int _lastCurrentPageNumber;
private NSMutableArray<PageNumber> _pageNumbers;
private PageNumber _repetitionPageNumber;
public Integer currentBatchSize;
//Note: Lazily Cached
private String _parentActionName;
public ERXFlickrBatchNavigation(WOContext context) {
super(context);
_lastPageCount = -1;
_lastCurrentPageNumber = -1;
_lastPageSize = -1;
}
@Override
public boolean synchronizesVariablesWithBindings() {
return false;
}
public NSArray objects() {
NSArray objects = null;
if(displayGroup() != null){
if (displayGroup() instanceof ERXDisplayGroup) {
ERXDisplayGroup dg = (ERXDisplayGroup) displayGroup();
objects = dg.filteredObjects();
} else {
objects = displayGroup().allObjects();
}
}
return objects;
}
public WODisplayGroup displayGroup() {
return (WODisplayGroup) valueForBinding("displayGroup");
}
public void setRepetitionPageNumber(PageNumber repetitionPageNumber) {
_repetitionPageNumber = repetitionPageNumber;
}
public PageNumber repetitionPageNumber() {
return _repetitionPageNumber;
}
public boolean hasMultiplePages() {
if(batchCount() > 1) {
return true;
}
if(showBatchSizes() && possibleBatchSizes().objectAtIndex(0).intValue() < displayNameCount()) {
return true;
}
return false;
}
public boolean showLabels() {
if(batchCount() > 1) {
return true;
}
if(showBatchSizes()) {
return batchSize() != 0;
}
return false;
}
public boolean hasPreviousPage() {
return currentBatchIndex() > 1;
}
public WOActionResults previousPage() {
WOActionResults previousPage = null;
if(displayGroup() != null){
WODisplayGroup displayGroup = displayGroup();
displayGroup.displayPreviousBatch();
} else if(parentActionName() != null){
Integer previousBatchIndex = Integer.valueOf((currentBatchIndex() - 1));
if(previousBatchIndex.intValue() < 1){
previousBatchIndex = Integer.valueOf(1);
}
setValueForBinding(previousBatchIndex, "currentBatchIndex");
previousPage = performParentAction(parentActionName());
}
return previousPage;
}
public boolean hasNextPage() {
return currentBatchIndex() < batchCount();
}
public WOActionResults nextPage() {
WOActionResults nextPage = null;
if(displayGroup() != null){
WODisplayGroup displayGroup = displayGroup();
displayGroup.displayNextBatch();
} else if(parentActionName() != null){
Integer nextBatchIndex = Integer.valueOf(currentBatchIndex() + 1);
int pageCount = batchCount();
if(nextBatchIndex.intValue() > pageCount){
nextBatchIndex = Integer.valueOf(pageCount);
}
setValueForBinding(nextBatchIndex, "currentBatchIndex");
nextPage = performParentAction(parentActionName());
}
return nextPage;
}
public WOActionResults selectPage() {
WOActionResults selectPage = null;
Integer pageNumber = _repetitionPageNumber.pageNumber();
if (pageNumber != null) {
if (displayGroup() != null) {
displayGroup().setCurrentBatchIndex(pageNumber.intValue());
} else {
setValueForBinding(pageNumber, "currentBatchIndex");
selectPage = performParentAction(parentActionName());
}
}
return selectPage;
}
public String displayName() {
String displayName = stringValueForBinding("displayName");
if (displayName == null) {
displayName = stringValueForBinding("objectName"); // CHECKME should this be deprecated?
}
if (displayName == null) {
displayName = ERXLocalizer.currentLocalizer().localizedStringForKey("ERXFlickrBatchNavigation.item");
}
return displayName;
}
public Integer displayNameCount(){
Integer displayNameCount = Integer.valueOf(0);
if(displayGroup() != null){
if (displayGroup() instanceof ERXBatchingDisplayGroup) {
displayNameCount = Integer.valueOf(((ERXBatchingDisplayGroup)displayGroup()).rowCount());
} else {
NSArray objects = objects();
if(objects != null && objects.count() > 0){
displayNameCount = Integer.valueOf(objects.count());
}
}
} else {
displayNameCount = Integer.valueOf(maxNumberOfObjects());
}
return displayNameCount;
}
public boolean isCurrentPageNumber() {
Integer pageNumber = _repetitionPageNumber.pageNumber();
return pageNumber != null && pageNumber.intValue() == _lastCurrentPageNumber;
}
public NSArray<PageNumber> pageNumbers() {
int pageCount = batchCount();
int currentPageNumber = currentBatchIndex();
int pageSize = numberOfObjectsPerBatch();
if (_lastPageCount != pageCount || _lastCurrentPageNumber != currentPageNumber || _lastPageSize != pageSize) {
_pageNumbers = new NSMutableArray<>();
int nearEdgeCount;
int endCount;
int nearCount;
int minimumCount;
if (booleanValueForBinding("small")) {
nearEdgeCount = 1;
endCount = 1;
nearCount = 0;
minimumCount = 5;
}
else {
nearEdgeCount = 8;
endCount = 2;
nearCount = 3;
minimumCount = 15;
}
if (pageCount <= minimumCount) {
addPageNumbers(1, pageCount);
}
else if (currentPageNumber <= nearEdgeCount) {
addPageNumbers(1, Math.max(nearEdgeCount - 1, currentPageNumber + nearCount));
addEllipsis();
addPageNumbers(pageCount - endCount + 1, pageCount);
}
else if (currentPageNumber > pageCount - nearEdgeCount) {
addPageNumbers(1, endCount);
addEllipsis();
addPageNumbers(Math.min(pageCount - nearEdgeCount + 2, currentPageNumber - nearCount), pageCount);
}
else {
addPageNumbers(1, endCount);
if (currentPageNumber - nearCount > (endCount + 1)) {
addEllipsis();
}
addPageNumbers(Math.max(endCount + 1, currentPageNumber - nearCount), Math.min(currentPageNumber + nearCount, pageCount - endCount));
if (currentPageNumber + nearCount < pageCount - endCount) {
addEllipsis();
}
addPageNumbers(pageCount - endCount + 1, pageCount);
}
_lastPageCount = pageCount;
_lastCurrentPageNumber = currentPageNumber;
_lastPageSize = pageSize;
}
return _pageNumbers;
}
protected void addEllipsis() {
_pageNumbers.addObject(new PageNumber(null, true));
}
protected void addPageNumbers(int startIndex, int endIndex) {
for (int pageNumber = startIndex; pageNumber <= endIndex; pageNumber++) {
_pageNumbers.addObject(new PageNumber(Integer.valueOf(pageNumber), false));
}
}
public static class PageNumber implements Serializable {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
private Integer _pageNumber;
private boolean _ellipsis;
public PageNumber(Integer pageNumber, boolean ellipsis) {
_pageNumber = pageNumber;
_ellipsis = ellipsis;
}
public Integer pageNumber() {
return _pageNumber;
}
public boolean isEllipsis() {
return _ellipsis;
}
}
public int batchCount(){
int batchCount = 0;
if(displayGroup() != null){
batchCount = displayGroup().batchCount();
} else {
int numberOfObjectsPerBatch = numberOfObjectsPerBatch();
int maxNumberOfObjects = maxNumberOfObjects();
if (numberOfObjectsPerBatch != 0){
if (maxNumberOfObjects == 0)
batchCount = 1;
else
batchCount = (maxNumberOfObjects - 1) / numberOfObjectsPerBatch + 1;
}
}
return batchCount;
}
public int numberOfObjectsPerBatch(){
int numberOfObjects = 0;
if(displayGroup() != null){
numberOfObjects = displayGroup().numberOfObjectsPerBatch();
} else {
numberOfObjects = intValueForBinding("numberOfObjectsPerBatch", 0);
if (numberOfObjects < 0) {
numberOfObjects = 0;
}
}
return numberOfObjects;
}
public int maxNumberOfObjects() {
int maxNumber = intValueForBinding("maxNumberOfObjects", 0);
if (maxNumber < 0) {
maxNumber = 0;
}
return maxNumber;
}
public int currentBatchIndex(){
int index = 1;
if(displayGroup() != null){
index = displayGroup().currentBatchIndex();
} else {
index = intValueForBinding("currentBatchIndex", 1);
if (index < 1) {
index = 1;
}
}
return index;
}
public String parentActionName(){
if(_parentActionName == null){
_parentActionName = stringValueForBinding("parentActionName");
}
return _parentActionName;
}
public int firstIndex(){
int firstIndex = 0;
if(displayGroup() != null){
firstIndex = displayGroup().indexOfFirstDisplayedObject();
} else {
int currentBatchIndex = currentBatchIndex();
int numberOfObjectsPerBatch = numberOfObjectsPerBatch();
firstIndex = (currentBatchIndex * numberOfObjectsPerBatch) - (numberOfObjectsPerBatch - 1);
}
return firstIndex;
}
public int lastIndex(){
int lastIndex = 0;
if(displayGroup() != null){
lastIndex = displayGroup().indexOfLastDisplayedObject();
} else {
int currentBatchIndex = currentBatchIndex();
int numberOfObjectsPerBatch = numberOfObjectsPerBatch();
lastIndex = currentBatchIndex * numberOfObjectsPerBatch;
if(lastIndex > maxNumberOfObjects()) {
lastIndex = maxNumberOfObjects();
}
}
return lastIndex;
}
public boolean showBatchSizes() {
if(booleanValueForBinding("showBatchSizes") || valueForBinding("batchSizes") != null) {
return true;
}
return false;
}
public NSArray<? extends Number> possibleBatchSizes() {
Object value = valueForBinding("batchSizes");
if(value == null) {
return new NSArray<>(new Integer[] {Integer.valueOf(10), Integer.valueOf(50), Integer.valueOf(100), Integer.valueOf(0)});
}
NSMutableArray<Integer> result = new NSMutableArray<>();
if (value instanceof String) {
String[] parts = value.toString().split("\\s*,");
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
result.addObject(Integer.valueOf(part));
}
} else if (value instanceof NSArray) {
result.addObjectsFromArray((NSArray)value);
}
return result;
}
public int batchSize() {
if(displayGroup() == null) {
return 0;
}
return displayGroup().numberOfObjectsPerBatch();
}
public String currentBatchSizeString() {
return currentBatchSize == 0 ? ERXLocalizer.currentLocalizer().localizedStringForKeyWithDefault("ERXFlickrBatchNavigation.all") : (currentBatchSize + "");
}
public boolean isCurrentBatchSizeSelected() {
if(currentBatchSize == null) {
return batchSize() == 0;
}
return currentBatchSize.equals(batchSize());
}
public WOActionResults selectBatchSize() {
displayGroup().setNumberOfObjectsPerBatch(currentBatchSize);
return context().page();
}
}