Introduction
ADF Faces provide us by quite handy tag af:autoSuggestBehavior. It could be used together with some input control in order to implement very common use-case when a user typing some text is suggested by a drop-down list with some values to be selected and displayed in the input field. The following screenshot shows an example of such behavior:
The jspx code for this example is very simple:
ADF Faces provide us by quite handy tag af:autoSuggestBehavior. It could be used together with some input control in order to implement very common use-case when a user typing some text is suggested by a drop-down list with some values to be selected and displayed in the input field. The following screenshot shows an example of such behavior:
The jspx code for this example is very simple:
<af:inputText label="Currency" id="i21"> <af:autoSuggestBehavior suggestedItems="#{InputSuggestBean.onCurrencySuggest}"/> </af:inputText>
The af:autoSuggestBehavior tag needs to be bounded to a backing bean method returning a list of items to be suggested. The method has only one String argument containing submitted user's value. Depending on this value you can implement your own logic for the returning list like filtering, sorting, etc. For example, the following backing bean method returns currency codes that match user's input string:
private final static String[] ccys = {"USD", "EUR", "UAH", "CAD"}; public List onCurrencySuggest(String inputValue) { ArrayList<SelectItem> suggestItems = new ArrayList<SelectItem>(); for (String s: ccys) { if (s.startsWith(inputValue)) suggestItems.add(new SelectItem(s)); } return suggestItems; }
Of course, you can implement your backing bean method getting suggested items from ADF BC layer in order to retrieve them from database or from other data sources. You can find detailed example of this feature in the ADF Code Corner Article posted by Frank Nimphius.
Suggestion on demand
But there is another common use-case when users don't need any auto-suggestion for their input, and they want to be suggested on demand only. For example, by pressing "Ctrl-H", a user is provided by previously submitted values for this input field. Something like a history of values.
In this post I'm going to show two different implementations of this use-case. The first one is based on modified af:autoSuggestBehavior tag and the second implementation is built using usual af:popup tag.
Modified af:autoSuggestBehavior
The jspx definition of inputText looks as usual:
<af:inputText label="Modified input" id="it1"> <af:autoSuggestBehavior suggestedItems="#{InputSuggestBean.onSuggest}"/> </af:inputText>
And the backing bean method looks like this:
public List onSuggest(String string) { ArrayList<SelectItem> selectItems = new ArrayList<SelectItem>(); //Let's assume these are history values: selectItems.add(new SelectItem("One")); selectItems.add(new SelectItem("Two")); return selectItems; }
af:autoSuggestBehavior tag renders some JavaScript object AdfAutoSuggestBehavior responsible for auto-suggestion functionality. It adds a number of event listeners to the inputText for different event types like
onKeyUp, onBlur, onFocus, etc.
In order to prevent default behavior of af:autoSuggestBehavior tag I have to override its onKeyUp listener and do some JavaScript coding:
<af:document id="d1"> <af:resource type="javascript"> var initialized = false; //Initialization flag initMySuggestBehavior = function () { if (!initialized) { //Saving default _fireKeyUp function AdfAutoSuggestBehavior.prototype._fireKeyUpEx = AdfAutoSuggestBehavior.prototype._fireKeyUp; //Writing proxy for _fireKeyUp function AdfAutoSuggestBehavior.prototype._fireKeyUp = function (e) { //Getting the event's source and its content var input = e.getSource(); if (input instanceof AdfUIEditableValue) var inputContent = AdfDhtmlEditableValuePeer.GetContentNode(input); //If user has pressed ctrl H then fire suggestion if ("ctrl H" == e.getKeyStroke().toMarshalledString()) { AdfAutoSuggestBehavior.prototype._autoSuggest(input); //Just for better visualization if (inputContent.value.length == 0) inputContent.value = "..."; } //This will prevent default suggestion firing for every input character if (inputContent) this._nodeLength = inputContent.value.length; //Calling default event listener we saved before AdfAutoSuggestBehavior.prototype._fireKeyUpEx(e); }; //Looking for our inputText var inp = AdfPage.PAGE.findComponentByAbsoluteId("it1"); /* At this moment AdfAutoSuggestBehavior has already initialized * and added his default listeners to the inputText. * We need to clear them and reinitialize with our overridden * _fireKeyUp function. */ inp.setProperty("clientListeners", null); AdfAutoSuggestBehavior.prototype.initialize(inp); /* Adding a simple listener for onKeyDown event in order to * prevent default browser behavior for Ctrl-H */ inp.addEventListener(AdfUIInputEvent.KEY_DOWN_EVENT_TYPE, myKeyDown, this); //We've done it. initialized = true; } } //Preventing default browser behavior for Ctrl-H myKeyDown = function (event) { if ("ctrl H" == event.getKeyStroke().toMarshalledString()) event.cancel(); } </af:resource> ...
I'm going to call initMySuggestBehavior JavaScript function in a phase listener written for the f:view tag:
<f:view beforePhase="#{InputSuggestBean.viewPhaseListener}">
public void viewPhaseListener(PhaseEvent phaseEvent) { if (phaseEvent.getPhaseId() == PhaseId.RENDER_RESPONSE) { FacesContext fctx = FacesContext.getCurrentInstance(); ExtendedRenderKitService ks = Service.getRenderKitService(fctx, ExtendedRenderKitService.class); StringBuffer script = new StringBuffer(); script.append("window.initMySuggestBehavior()"); ks.addScript(fctx, script.toString()); } }
Using af:popup
The jspx page for this implementation looks like this:
<af:popup id="ctrlHPopup" contentDelivery="lazyUncached" animate="false"> <af:selectOneListbox id="sol4" simple="true" autoSubmit="false" > <f:selectItems value="#{InputSuggestBean.popupSuggestion}" id="si7"/> <af:clientListener type="keyDown" method="enterSelection"/> <af:clientListener type="click" method="clickSelection"/> </af:selectOneListbox> </af:popup> <af:panelLabelAndMessage label="Input with popup" id="plam1"> <af:inputText id="it2" simple="true"> <af:clientListener method="ctrlHKeyDown" type="keyDown"/> </af:inputText> </af:panelLabelAndMessage>
I have a popup and selectOneListbox component inside it. The listbox get suggested items from the backing bean property popupSuggestion and it has client listeners to submit selected values when Enter is pressed or mouse is clicked. The inputText has a listener to fire popup with suggested items when Ctrl-H is pressed.
The backing bean has the following method:
public List getPopupSuggestion() { ArrayList<SelectItem> selectItems = new ArrayList<SelectItem>(); //Let's assume these are history values selectItems.add(new SelectItem("The first value")); selectItems.add(new SelectItem("The second value")); return selectItems; }
And for sure we have some JavaScript implementation of our listeners:
//Firing popup and preventing default browser behavior for Ctrl-H ctrlHKeyDown = function (event) { if ("ctrl H" == event.getKeyStroke().toMarshalledString()) { showPopup(); event.cancel(); } } //Firing popup showPopup = function(){ //Looking for popup component var popup = AdfPage.PAGE.findComponentByAbsoluteId("ctrlHPopup"); //Define popup alignment var hints = {}; hints[AdfRichPopup.HINT_ALIGN_ID] = AdfPage.PAGE.findComponentByAbsoluteId("it2").getClientId(); hints[AdfRichPopup.HINT_ALIGN] = AdfRichPopup.ALIGN_AFTER_START; //Showing popup.show(hints); } //Hiding popup hidePopup = function(){ var popup = AdfPage.PAGE.findComponentByAbsoluteId("ctrlHPopup"); popup.hide(); } //If Enter is pressed enterSelection = function(event){ if (AdfKeyStroke.ENTER_KEY == event.getKeyStroke().getKeyCode()) { setSelectedValue(event); } } //If mouse is clicked clickSelection = function(event){ setSelectedValue(event); } //Submit selected value and hide the popup setSelectedValue = function(event) { var items = event.getSource(); var selectedItem = items.getSelectItems()[items.getValue()]; var inputText = AdfPage.PAGE.findComponentByAbsoluteId("it2"); var inputContent = AdfDhtmlEditableValuePeer.GetContentNode(inputText); inputContent.value = selectedItem.getLabel(); hidePopup(); }
And the result of our work looks like this:
That's all. You can download sample application for this post.
No comments:
Post a Comment
Post Comment