/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.control.view; import com.esri.gpt.framework.jsf.FacesContextBroker; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.request.PageCursor; import com.esri.gpt.framework.util.Val; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import javax.faces.component.UIComponent; import javax.faces.component.UIForm; import javax.faces.component.UISelectItem; import javax.faces.component.html.HtmlCommandLink; import javax.faces.component.html.HtmlOutputLabel; import javax.faces.component.html.HtmlOutputText; import javax.faces.component.html.HtmlPanelGrid; import javax.faces.component.html.HtmlPanelGroup; import javax.faces.component.html.HtmlSelectOneListbox; import javax.faces.context.FacesContext; import javax.faces.el.MethodBinding; import javax.faces.event.ActionEvent; import javax.faces.event.ValueChangeEvent; /** * Provides support for binding a page cursor associated with a query result to * an HtmlPanelGroup. * <p> * The cursor requires message resource keys. All resource keys used are * prefixed with the supplied resourceKeyPrefix property (the default * value is "general.pageCursor."). * <p> * The following resource keys are used by the cursor: * <br>nomatch results first previous next last * <p> * Examples: * <br/>catalog.general.pageCursor.nomatch = No matching records were located. * <br/>catalog.general.pageCursor.results = Results {0}-{1} of {2} record(s) * <br/>catalog.general.pageCursor.first = First * <br/>catalog.general.pageCursor.previous = < * <br/>catalog.general.pageCursor.next = > * <br/>catalog.general.pageCursor.last = Last * <p> * The "results" resource key is record (not page based) and * takes the following 3 arguments: * <br/>The starting record for the current page, the ending record for the * current page and the total number of records. * <p> */ public class PageCursorPanel { // class variables ============================================================= /** Default list of results per page. */ private static final Integer[] _defaultResultsPerPage = new Integer[]{5, 10, 20, 50}; // instance variables ========================================================== private String _actionListenerExpression = ""; private String _changeListenerExpression = ""; private HtmlPanelGroup _bottomHtmlPanelGroup; private String _commandAttributeName; private String _commandAttributeValue; private String _currentStyleClass; private String _cursorPageAttributeName; private PageCursor _pageCursor; private String _resourceKeyPrefix; private String _resultStyleClass; private HtmlPanelGroup _topHtmlPanelGroup; // constructors ================================================================ /** Default constructor. */ public PageCursorPanel() { this.setCommandAttributeName("command"); this.setCommandAttributeValue("setCursorPage"); this.setCurrenStyleClass("current"); this.setCursorPageAttributeName("cursorPage"); this.setResourceKeyPrefix("general.pageCursor."); this.setResultStyleClass("result"); } // properties ================================================================== /** * Gets the expression used to bind generated command links to the controller. * <br/>Typically the expression will have the following form: * <br/>#{SomeController.processAction} * @return the action listener binding expression */ public String getActionListenerExpression() { return _actionListenerExpression; } /** * Sets the expression used to bind generated command links to the controller. * <br/>Typically the expression will have the following form: * <br/>#{SomeController.processAction} * @param expression the action listener binding expression */ public void setActionListenerExpression(String expression) { _actionListenerExpression = Val.chkStr(expression); } public String getChangeListenerExpression() { return _changeListenerExpression; } public void setChangeListenerExpression(String expression) { _changeListenerExpression = Val.chkStr(expression); } /** * Gets the bound HtmlPanelGroup for the bottom portion of the page. * <br/>This object is used during the Faces component binding process. * @return the bound HtmlPanelGroup */ public HtmlPanelGroup getBottomHtmlPanelGroup() { return _bottomHtmlPanelGroup; } /** * Sets the bound HtmlPanelGroup for the bottom portion of the page. * <br/>This object is used during the Faces component binding process. * @param htmlPanelGroup the bound HtmlPanelGroup */ public void setBottomHtmlPanelGroup(HtmlPanelGroup htmlPanelGroup) { _bottomHtmlPanelGroup = htmlPanelGroup; } /** * Gets the name of the attribute used to indicate the command that * should be executed. * <br/>The default value is "command". * @return the command attribute name */ public String getCommandAttributeName() { return _commandAttributeName; } /** * Sets the name of the attribute used to indicate the command that * should be executed. * <br/>The default value is "command". * @param name the command attribute name */ public void setCommandAttributeName(String name) { _commandAttributeName = Val.chkStr(name); } /** * Gets value of the command attribute indicating that a page cursor * navigation event has occurred. * <br/>The default value is "setCursorPage". * @return the command attribute value for a page cursor navigation event */ public String getCommandAttributeValue() { return _commandAttributeValue; } /** * Sets value of the command attribute indicating that a page cursor * navigation event has occurred. * <br/>The default value is "setCursorPage". * @param value the command attribute value for a page cursor navigation event */ public void setCommandAttributeValue(String value) { _commandAttributeValue = Val.chkStr(value); } /** * Gets the style class associated with the current page. * <br/>The default value is "current". * @return the current page style class */ public String getCurrentStyleClass() { return _currentStyleClass; } /** * Sets the style class associated with the current page. * <br/>The default value is "current". * @param styleClass the current page style class */ public void setCurrenStyleClass(String styleClass) { _currentStyleClass = Val.chkStr(styleClass); } /** * Gets the name of the attribute used to indicate the cursor page. * <br/>The default value is "cursorPage". * @return the cursor page attribute name */ public String getCursorPageAttributeName() { return _cursorPageAttributeName; } /** * Sets the name of the attribute used to indicate the cursor page. * <br/>The default value is "cursorPage". * @param name the cursor page attribute name */ public void setCursorPageAttributeName(String name) { _cursorPageAttributeName = Val.chkStr(name); } /** * Gets the underlying page cursor. * <br/>The page cursor UI will be generated from the content of this cursor. * @return the page cursor */ public PageCursor getPageCursor() { return _pageCursor; } /** * Sets the underlying page cursor. * <br/>The page cursor UI will be generated from the content of this cursor. * <br/>Setting the page cursor triggers the building of the HtmlPanelGroup * UI components. * <br/>This method should be invoked immediately following the generation * of query results. * @param cursor the page cursor */ public void setPageCursor(PageCursor cursor) { _pageCursor = cursor; build(); } /** * Gets the resource key prefix to be used when generating UI components. * <br/>The default value is "general.pageCursor.". * @return the resource key prefix */ public String getResourceKeyPrefix() { return _resourceKeyPrefix; } /** * Sets the resource key prefix to be used when generating UI components. * <br/>The default value is "general.pageCursor.". * @param resourceKeyPrefix the resource key prefix */ public void setResourceKeyPrefix(String resourceKeyPrefix) { _resourceKeyPrefix = Val.chkStr(resourceKeyPrefix); } /** * Gets the style class associated with the result text. * <br/>The result text takes the form: * <br/>Results 1-10 of 61 record(s) * <br/>The default value is "result". * @return the result text style class */ public String getResultStyleClass() { return _resultStyleClass; } /** * Sets the style class associated with the result text. * <br/>The result text takes the form: * <br/>Results 1-10 of 61 record(s) * <br/>The default value is "result". * @param styleClass the result text style class */ public void setResultStyleClass(String styleClass) { _resultStyleClass = Val.chkStr(styleClass); } /** * Gets the bound HtmlPanelGroup for the top portion of the page. * <br/>This object is used during the Faces component binding process. * @return the bound HtmlPanelGroup */ public HtmlPanelGroup getTopHtmlPanelGroup() { return _topHtmlPanelGroup; } /** * Sets the bound HtmlPanelGroup for the top portion of the page. * <br/>This object is used during the Faces component binding process. * @param htmlPanelGroup the bound HtmlPanelGroup */ public void setTopHtmlPanelGroup(HtmlPanelGroup htmlPanelGroup) { _topHtmlPanelGroup = htmlPanelGroup; } // methods ===================================================================== /** * Builds the components of the top and bottom HtmlPanelGroup based upon the content * of the PageCursor. */ public void build() { build(getTopHtmlPanelGroup(), getPageCursor(), false); build(getBottomHtmlPanelGroup(), getPageCursor(), true); } /** * Builds the components of an HtmlPanelGroup based upon the content * of the PageCursor. * @param navigationPanelGroup the panel group to build * @param cursor the underlying page cursor * @param isBottom true if we are building the bottom panel */ private void build(HtmlPanelGroup masterPanelGroup, PageCursor cursor, boolean isBottom) { // clear existing children, return if there is nothing to build if (masterPanelGroup != null) { masterPanelGroup.getChildren().clear(); } if ((masterPanelGroup == null) || (cursor == null)) { return; } // don't build the bottom panel if it's not really necessary if (isBottom && ((cursor.getEndRecord() - cursor.getStartRecord()) < 4)) { return; } // initialize parameters int nPage; String sMsg; String sRecordsPerPageMsg; HtmlCommandLink cmdLink; HtmlOutputText outText; FacesContextBroker ctxBroker = new FacesContextBroker(); FacesContext facesContext = ctxBroker.getFacesContext(); MessageBroker msgBroker = ctxBroker.extractMessageBroker(); String sKeyPfx = getResourceKeyPrefix(); // check the cursor cursor.checkCurrentPage(); int nStartPage = cursor.getStartPage(); int nEndPage = cursor.getEndPage(); int nTotalPageCount = cursor.getTotalPageCount(); // add the result text Integer[] args = new Integer[3]; if (nTotalPageCount == 0) { sMsg = msgBroker.retrieveMessage(sKeyPfx + "nomatch", args); } else { args[0] = cursor.getStartRecord(); args[1] = cursor.getEndRecord(); args[2] = cursor.getTotalRecordCount(); sMsg = msgBroker.retrieveMessage(sKeyPfx + "results", args); } sRecordsPerPageMsg = msgBroker.retrieveMessage(sKeyPfx + "resultsPerPage"); // create grid to separate navigoation panel from records per page panel HtmlPanelGrid panelGrid = new HtmlPanelGrid(); masterPanelGroup.getChildren().add(panelGrid); panelGrid.setBorder(0); panelGrid.setColumns(2); panelGrid.setWidth("100%"); panelGrid.setColumnClasses("nav,count"); // create navigation panel HtmlPanelGroup navigationPanelGroup = new HtmlPanelGroup(); panelGrid.getChildren().add(navigationPanelGroup); outText = makeResultText(facesContext, sMsg); navigationPanelGroup.getChildren().add(outText); int linkCount = 0; // add page navigation links if (nTotalPageCount > 1) { // first and previous pages if (cursor.getHasPreviousPage()) { if (nStartPage != 1) { sMsg = msgBroker.retrieveMessage(sKeyPfx + "first"); cmdLink = makePageLink(facesContext, 1, sMsg, isBottom, ++linkCount); navigationPanelGroup.getChildren().add(cmdLink); } nPage = cursor.getPreviousPage(); sMsg = msgBroker.retrieveMessage(sKeyPfx + "previous"); cmdLink = makePageLink(facesContext, nPage, sMsg, isBottom, ++linkCount); navigationPanelGroup.getChildren().add(cmdLink); } // pages for (int i = nStartPage; i <= nEndPage; i++) { cmdLink = makePageLink(facesContext, i, "" + i, isBottom, ++linkCount); navigationPanelGroup.getChildren().add(cmdLink); } // next and last pages if (cursor.getHasNextPage()) { nPage = cursor.getNextPage(); sMsg = msgBroker.retrieveMessage(sKeyPfx + "next"); cmdLink = makePageLink(facesContext, nPage, sMsg, isBottom, ++linkCount); navigationPanelGroup.getChildren().add(cmdLink); if (nEndPage != nTotalPageCount) { sMsg = msgBroker.retrieveMessage(sKeyPfx + "last"); cmdLink = makePageLink(facesContext, nTotalPageCount, sMsg, isBottom, ++linkCount); navigationPanelGroup.getChildren().add(cmdLink); } } } // create records per page panel if (!isBottom && getChangeListenerExpression().length()>0) { HtmlPanelGroup resultsPerPagePanelGroup = new HtmlPanelGroup(); panelGrid.getChildren().add(resultsPerPagePanelGroup); // listbox id String listBoxId = "recsPerPage"; // Display label HtmlOutputLabel resultsPerPageLabel = makeResultsLabel(facesContext, sRecordsPerPageMsg); resultsPerPagePanelGroup.getChildren().add(resultsPerPageLabel); resultsPerPageLabel.setFor(listBoxId); // Create lisbox HtmlSelectOneListbox listBox = new HtmlSelectOneListbox(); resultsPerPagePanelGroup.getChildren().add(listBox); listBox.setId(listBoxId); listBox.setSize(1); listBox.setValue(Integer.toString(cursor.getRecordsPerPage())); UIComponent form = findForm(masterPanelGroup); if (form!=null) { String inChangeExpression = "document.forms['"+form.getId()+"'].submit(); return false;"; listBox.setOnchange(inChangeExpression); } // create listener Class[] parms = new Class[]{ValueChangeEvent.class}; MethodBinding mb = FacesContext.getCurrentInstance().getApplication().createMethodBinding( getChangeListenerExpression(), parms); listBox.setValueChangeListener(mb); // create list content Set<Integer> rpp = new TreeSet<Integer>(Arrays.asList(_defaultResultsPerPage)); rpp.add(new Integer(cursor.getRecordsPerPage())); for (Integer p : rpp) { int resultPerPage = p.intValue(); UISelectItem selectItem = new UISelectItem(); listBox.getChildren().add(selectItem); selectItem.setItemLabel(Integer.toString(resultPerPage)); selectItem.setItemValue(Integer.toString(resultPerPage)); } } } /** * Calles when record per page has changed. * @param event event */ public void onChange(ValueChangeEvent event) { _pageCursor.setRecordsPerPage( Val.chkInt(event.getNewValue().toString(), _pageCursor.getRecordsPerPage())); } /** * Checks an action event to determine if this is a page cursor navigation event. * <br/>If so, the current page of the associated page cursor is set. * <br/>This method will not re-execute a query, it will simply reset the * current page. * <br/>To rebuild the underlying UI components, user the setPageCursor() or * build() methods. * @param event the associated JSF action event * @param resetPageIfNot if true, reset the current page to 1 if this was not * a page cursor navigation event * @return <code>true</code> page cursor event detected */ public boolean checkActionEvent(ActionEvent event, boolean resetPageIfNot) { // determine if the command is a page cursor navigation event boolean bWasPageCursorEvent = false; if (event != null) { UIComponent component = event.getComponent(); String sName = getCommandAttributeName(); String sCmd = (String) component.getAttributes().get(sName); if (Val.chkStr(sCmd).equalsIgnoreCase(getCommandAttributeValue())) { bWasPageCursorEvent = true; // determine the page to navigate to sName = getCursorPageAttributeName(); String sPage = (String) component.getAttributes().get(sName); int nPage = Val.chkInt(sPage, -1); if (nPage < 1) { nPage = 1; } if (getPageCursor() != null) { getPageCursor().setCurrentPage(nPage); } } } // reset the current page if this was not a page cursor navigation event if (!bWasPageCursorEvent && resetPageIfNot) { if (getPageCursor() != null) { getPageCursor().setCurrentPage(1); } } return bWasPageCursorEvent; } /** * Makes an HtmlCommandLink component for UI page cursor navigation. * @param facesContext the active Faces context * @param page the subject page * @param pageText the text for the subject page * @return the new HtmlCommandLink component */ private HtmlCommandLink makePageLink(FacesContext facesContext, int page, String pageText, boolean isBottom, int linkCount) { HtmlCommandLink cmd = new HtmlCommandLink(); String sExpr = getActionListenerExpression(); Class a[] = {ActionEvent.class}; MethodBinding mb = facesContext.getApplication().createMethodBinding(sExpr, a); cmd.setValue(pageText); cmd.setId((isBottom? "bottomLink_":"topLink_")+linkCount); cmd.setActionListener(mb); cmd.getAttributes().put(getCommandAttributeName(), getCommandAttributeValue()); cmd.getAttributes().put(getCursorPageAttributeName(), "" + page); if (page == getPageCursor().getCurrentPage()) { if (getCurrentStyleClass().length() > 0) { cmd.setStyleClass(getCurrentStyleClass()); } } return cmd; } /** * Makes an HtmlOutputText component for result text display. * @param facesContext the active Faces context * @param text the text to display * @return the new HtmlOutputText component */ private HtmlOutputText makeResultText(FacesContext facesContext, String text) { HtmlOutputText outText = new HtmlOutputText(); outText.setEscape(false); outText.setValue(text); if (getResultStyleClass().length() > 0) { outText.setStyleClass(getResultStyleClass()); } return outText; } /** * Makes an HtmlOutputLabel component for result text display. * @param facesContext the active Faces context * @param text the text to display * @return the new HtmlOutputLabel component */ private HtmlOutputLabel makeResultsLabel(FacesContext facesContext, String text) { HtmlOutputLabel outLabel = new HtmlOutputLabel(); outLabel.setEscape(false); outLabel.setValue(text); if (getResultStyleClass().length() > 0) { outLabel.setStyleClass(getResultStyleClass()); } return outLabel; } /** * Looks for the parent form. * @param component child component * @return form or <code>null</code> if form doesn't exist */ private UIComponent findForm(UIComponent component) { if (component==null) { return null; } if (component.getFamily().equals(UIForm.COMPONENT_FAMILY)) { return component; } return findForm(component.getParent()); } }