I developed a custom component (inspired by this article by BalusC) that contains a ui:repeat tag displaying the list of clickable table pages.
Inside the loop block there is a h:commandLink that calls asynchronously an action, posting a parameter. The parameter value changes at every loop. Here's the page code:
[...]
<ui:repeat value="#{cc.attrs.pager.pages}" var="loopPage">
<li>
<h:commandLink
action="#{cc.attrs.pager.changePage}"
value="#{loopPage}"
rendered="#{loopPage != cc.attrs.pager.currentPage}"
immediate="true"
>
<f:param name="selectedPage" value="#{loopPage}" />
<f:ajax execute="#this" render="#{cc.attrs.render}" />
</h:commandLink>
<h:outputText
value="#{loopPage}"
rendered="#{loopPage == cc.attrs.pager.currentPage}"
/>
</li>
</ui:repeat>
[...]
Now, the cc.attrs.pager component attribute is noting else that the view-scoped backing bean, whose pages and currentPage attributes are visible through getters, and changePagemethod is the action to call. The cc.attrs.render component attribute is the target to be refreshed after every action execution. Here is the relevant bean code:
[...]
private Integer[] pages;
private int currentPage;
[...]
public Integer[] getPages() {
return pages;
}
public int getCurrentPage() {
return currentPage;
}
[...]
public void changePage() {
HttpServletRequest request = (HttpServletRequest) FacesContext
.getCurrentInstance()
.getExternalContext()
.getRequest()
;
String page = request.getParameter("selectedPage");
changePage(Integer.valueOf(page) * rowsPerPage);
}
public void changePage(int firstRow){
this.firstRow = firstRow; //Set the first result to be returned
loadDataList(); // Load requested page.
}
[...]
The initial render is perfect: all the page links are correctly shown and the current page number is not linked, as desired. The problem reveals going to another page: nothing happens.
Although the h:commandLink action is posted with the exact loop parameter value, the response returns always the same page (the first).
Debugging it, I observed that the execution doesn't enter in any of the two changePage methods.
I'm not sure if this issue is related to ui:repeat, but omitting it, I can tell that the same mechanism does work for other purposes.
Any suggestion?
I think you should try replacing <ui:repeat> with <c:forEach>
Related
There is a bean method which provides data for a number of xthml tags on a page. For debugging purposes I'd like to know which node in a ViewRoot is triggering the method. Something like that:
<ui:repeat id="alpha" value="#{myBean.objectList}" var="obj">
<!-- some stuff here -->
</ui:repeat>
and the method itself:
public List getObjectList() {
String id = ????;
logger.info("I'm being called by:" + id); // returning "alpha", "beta"
// or whatever component
// calling this method
}
Is it possible?
You can use UIComponent#getCurrentComponent() to obtain the UI component instance currently being processed in the JSF lifecycle.
UIComponent currentComponent = UIComponent.getCurrentComponent(FacesContext.getCurrentInstance());
String currentComponentId = currentComponent.getId();
// ...
i have two oneSelectMenu loaded with default values based on login Details,then second selectonemenu should load value based on first selectonemenu's onchangeEvent menu.i tried to clear the default value before onchange event but the value remains and doesn't work with onchange event.
<h:selectOneMenu id="blS" value="#{BoardAction.serviceAreaId}" >
<f:ajax event="valueChange" render="blSearchFacilityInput" listener="#{BoardAction.svaValueChangeEvent}"/>
<f:selectItems value="#{BoardAction.serviceAreaList}" var="c" itemLabel="#{c.svaCode}" itemValue="#{c.id}"/> </h:selectOneMenu>
<h:selectOneMenu id="blSearchFacilityInput" value="#{BoardAction.facilityId}"> <f:ajax event="valueChange" render="blSearchSectorInput" listener="#{BoardAction.facValueChangeEvent}"/>
<f:selectItems value="#{BoardAction.svaFaciltyList}" var="c" itemLabel="#{c.facCode}" itemValue="#{c.id}"/></h:selectOneMenu>
ActionBean :
private List<FacilityEBean> svaFaciltyList=null;
public List<FacilityEBean> getSvaFaciltyList() {
svaFaciltyList = facilityBusServ.getFacilityListBySVAId(session.getLoginUser());
return svaFaciltyList;
}
public List<FacilityEBean> svaValueChangeEvent(){
if(svaFaciltyList!=null){
svaFaciltyList.clear();
svaFaciltyList=null;
}
svaFaciltyList = facilityBusServ.getFacilityList(Integer.parseInt(serviceAreaId));
return svaFaciltyList;
}
Your code logic flow is wrong. You seem to expect that input components are in some way directly bound to properties and that the ajax action listener methods can return (changed) property values. This is thus actually not true.
For example, the EL expression #{BoardAction.serviceAreaList} actually invokes the getter method for the property. In your particular case, the getter method fills the list with the results from the DB everytime. So whatever you're setting in the ajax listener method is overridden everytime by the business logic in the getter method.
Those getter methods should not contain business logic at all. You need to rewrite your code as follows:
private List<FacilityEBean> svaFaciltyList;
#PostConstruct
public void init() {
svaFaciltyList = facilityBusServ.getFacilityListBySVAId(session.getLoginUser());
}
public void svaValueChangeEvent() {
svaFaciltyList = facilityBusServ.getFacilityList(Integer.parseInt(serviceAreaId));
}
public List<FacilityEBean> getSvaFaciltyList() {
return svaFaciltyList;
}
I have the following construct at several places in my webapp in order to conditionally render page fragments depending on some actions:
<h:panelGroup rendered="#{managedBean.serviceSelected == 'insurance'}">
<ui:include src="/pages/edocket/include/service1.xhtml" />
</h:panelGroup>
I have observed, that the <ui:include> is still executed even when the rendered attribute evaluates false. This unnecessarily creates all backing beans associated with the service1.xhtml file which is been included.
How can I skip executing the <ui:include> when the parent UI component is not rendered, so that all those backing beans are not unnecessarily created?
Unfortunately, this is by design. The <ui:include> runs as being a taghandler during the view build time, while the rendered attribute is evaluated during the view render time. This can be better understood by carefully reading this answer and substituting "JSTL" with "<ui:include>": JSTL in JSF2 Facelets... makes sense?
There are several ways to solve this, depending on the concrete functional requirement:
Use a view build time tag like <c:if> instead of <h:panelGroup>. This however puts implications into the #{managedBean}. It can't be view scoped and should do its job based on HTTP request parameters. Exactly those HTTP request parameters should also be retained in subsequent request (by e.g. <f:param>, includeViewParams, etc) so that it doesn't break when the view is restored.
Replace <ui:include> by a custom UIComponent which invokes FaceletContext#includeFacelet() during the UIComponent#encodechildren() method. So far no such component exist in any of the existing libraries. But I can tell that I've already such one in mind as a future addition for OmniFaces and it works as intuitively expected here at my test environment (with Mojarra). Here's a kickoff example:
#FacesComponent(Include.COMPONENT_TYPE)
public class Include extends UIComponentBase {
public static final String COMPONENT_TYPE = "com.example.Include";
public static final String COMPONENT_FAMILY = "com.example.Output";
private enum PropertyKeys {
src;
}
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public boolean getRendersChildren() {
return true;
}
#Override
public void encodeChildren(FacesContext context) throws IOException {
getChildren().clear();
FaceletContext faceletContext = ((FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY));
faceletContext.includeFacelet(this, getSrc());
super.encodeChildren(context);
}
public String getSrc() {
return (String) getStateHelper().eval(PropertyKeys.src);
}
public void setSrc(String src) {
getStateHelper().put(PropertyKeys.src, src);
}
}
Use conditional expression as ui:include src:
<h:panelGroup>
<ui:include
src="#{managedBean.serviceSelected == 'insurance' ?
'/pages/edocket/include/service1.xhtml'
:
'/pages/empty.xhtml'}"
/>
</h:panelGroup>
I want to genrate a unique number in <p:inputText> on page load.
I can generate an unique value using java.util.UUID, but how can I set it on the value of the <p:inputText> on page load?
<p:inputText id="ptId" label="PatientId" value="#{addBB.pt.patientId}" />
Just set the value during bean's (post)construction.
#ManagedBean
#ViewScoped
public class AddBB {
private Patient pt;
#PostConstruct
public void init() {
pt = new Patient();
pt.setPatientId(UUID.randomUUID().toString());
}
// ...
}
you can do this just by making userId, the default value for inputText like this -
value="#{beanName.userId}"
so that first use it like u want to use and then u can set the value of userId at any point of time as the unique id, just remember to reRender it.
Hope you got what u wanted....
I have some HTML, eg:
<%# Page Title="About Us" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeBehind="ContentManagedTargetPage.aspx.cs" Inherits="xxx.ContentManagedTargetPage" %>
<%# Register TagPrefix="CxCMS" Namespace="xxx.ContentManagement.ASPNET.UI" Assembly="xxx.ContentManagement.ASPNET" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<h2>
Content Managed
</h2>
<p>
Put content here.
[<CxCMS:ContentManagedPlaceHolder Key="keyThingy" runat="server" />]
</p>
</asp:Content>
And I want to find all the instances of the CxCMS:ContentManagedPlaceHolder element.
I'm using HTML Agility Pack, which seems the best fit.
However, despite looking at the [meagre] documentation, I can't get my code to work.
I would expect the following to work:
string searchForElement = "CxCMS:ContentManagedPlaceHolder";
IEnumerable<HtmlNode> contentPlaceHolderHtmlNodes = HtmlDocument.DocumentNode.Descendants(searchForElement);
int count = contentPlaceHolderHtmlNodes.Count();
But I get nothing back.
If I change to DescendantsOrSelf, I get the document node back, "#document" - which is incorrect:
string searchForElement = "CxCMS:ContentManagedPlaceHolder";
IEnumerable<HtmlNode> contentPlaceHolderHtmlNodes = HtmlDocument.DocumentNode.DescendantsOrSelf(searchForElement);
int count = contentPlaceHolderHtmlNodes.Count();
I also tried using LINQ:
string searchForElement = "CxCMS:ContentManagedPlaceHolder";
IEnumerable<HtmlNode> contentPlaceHolderHtmlNodes = HtmlDocument.DocumentNode.DescendantsOrSelf().Where(q=>q.Name==searchForElement);
int count = contentPlaceHolderHtmlNodes.Count();
As neither of these methods work, I moved onto using SelectNodes, instead:
string searchForElement = "CxCMS:ContentManagedPlaceHolder";
string xPath="//"+searchForElement // "//CxCMS:ContentManagedPlaceHolder"
var nodes= HtmlDocument.DocumentNode.SelectNodes(xPath);
This just throws the exception: "Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function.". I can't find any way of adding namespace management to the HtmlDocument object.
What am I missing, here? The DescendantsOrSelf() method works if using a "standard" HTML tag, such as "p", but not the one I have. Surely it should work? (It needs to!)
As usual I spend an hour or so playing, I ask the question, and I figure it out seconds after.
When searching using DescendantsOrSelf(), the node name must be in lower case.
Your example is actually ASPX. If you're parsing the ouput of that page, it's doubtful that <CxCMS:ContentManagedPlaceHolder Key="keyThingy" runat="server" /> actually renders as that on the client side. Look at the html source on the client, find the output tags that correspond to the <CxCMS:ContentManagedPlaceHolder Key="keyThingy" runat="server" />, and then use those in HtmlDocument.DocumentNode.Descendants.
On the other hand, if you're parsing the ASPX source, you may need to tweak your input to HtmlDocument.DocumentNode.Descendants so that the HtmlAgilityPack recognizes it, however keep in mind that ASPX != html and I don't think the HtmlAgilityPack is built to parse it.
Edit: Looking through HtmlNode.cs in the HtmlAgilityPack source code, it looks like you're right about it needing to be lowercase due to the following two sections:
/// <summary>
/// Gets or sets this node's name.
/// </summary>
public string Name
{
get
{
if (_name == null)
{
Name = _ownerdocument._text
.Substring(_namestartindex, _namelength);
}
return _name != null ? _name.ToLower() : string.Empty;
}
set { _name = value; }
}
and
/// <summary>
/// Get all descendant nodes with matching name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public IEnumerable<HtmlNode> Descendants(string name)
{
foreach (HtmlNode node in Descendants())
if (node.Name == name)
yield return node;
}
Note the _name.ToLower() in the getter for Name, and the case-sensitive if (node.Name == name) in the Decendants method. This is the same check used the the DescendantsAndSelf, Element and Elements methods.