primefaces
primefaces copied to clipboard
Timeline: null value when used inside a composite
Describe the defect Timeline shows no events
Environment:
- PF Version: 8.0, 10RC1
- JSF + version: Mojarra 2.3
- Affected browsers: ALL_
To Reproduce Steps to reproduce the behavior:
- create a composite component and put a timeline in it
- enable lazy loading
- use composite component attribute for timeline "value" (eg. value="#{cc.attrs.model}")
- See error SEVERE: Exception invoking UIViewRoot PhaseListener org.primefaces.component.timeline.DefaultTimelineUpdater. and empty timeline
Expected behavior The timeline should load and display events
compositeTimeline.xhtml
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:p="http://primefaces.org/ui">
<cc:interface>
<cc:attribute name="value" type="org.primefaces.model.timeline.TimelineModel" required="true" />
</cc:interface>
<cc:implementation>
<p:timeline id="timeline" value="#{cc.attrs.value}"
preloadFactor="#{lazyTimelineView.preloadFactor}"
zoomMax="#{lazyTimelineView.zoomMax}" minHeight="170">
<p:ajax event="lazyload" update="@none" listener="#{lazyTimelineView.onLazyLoad}"
onstart="$('#loadingText').css('visibility', 'visible')"
oncomplete="$('#loadingText').css('visibility', 'hidden')"/>
</p:timeline>
</cc:implementation>
</ui:component>
test.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:cc="http://java.sun.com/jsf/composite/cc">
<h:head>
<title>PrimeFaces Test</title>
<h:outputScript name="test.js" />
</h:head>
<h:body>
<div class="card">
<h:form id="form">
<p:growl id="growl" showSummary="true" showDetail="false">
<p:autoUpdate/>
</p:growl>
<div id="loadingText" style="font-weight:bold; margin:-5px 0 5px 0; visibility:hidden;">Loading ...
</div>
<cc:compositeTimeline id="compositeTimeline" value="#{lazyTimelineView.model}" />
<h:panelGrid columns="2" style="margin-top:15px">
<p:spinner id="spinner" value="#{lazyTimelineView.preloadFactor}"
min="0" max="1" stepFactor="0.05"/>
<p:commandButton value="Update Preload Factor" process="@this spinner" update="compositeTimeline"
action="#{lazyTimelineView.clearTimeline}"
style="margin-left:5px"/>
</h:panelGrid>
</h:form>
</div>
</h:body>
</html>
LazyTimelineView
@Named
@ViewScoped
public class LazyTimelineView implements Serializable {
private TimelineModel<String, ?> model;
private float preloadFactor = 0;
private long zoomMax;
@PostConstruct
protected void initialize() {
// create empty model
model = new TimelineModel<>();
// about five months in milliseconds for zoomMax
// this can help to avoid a long loading of events when zooming out to wide time ranges
zoomMax = 1000L * 60 * 60 * 24 * 31 * 5;
}
public TimelineModel<String, ?> getModel() {
return model;
}
public void onLazyLoad(TimelineLazyLoadEvent e) {
try {
// simulate time-consuming loading before adding new events
Thread.sleep((long) (1000 * Math.random() + 100));
} catch (InterruptedException ex) {
// ignore
}
TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(((Timeline)e.getSource()).getClientId());
LocalDateTime startDate = e.getStartDateFirst(); // alias getStartDate() can be used too
LocalDateTime endDate = e.getEndDateFirst(); // alias getEndDate() can be used too
// fetch events for the first time range
generateRandomEvents(startDate, endDate, timelineUpdater);
if (e.hasTwoRanges()) {
// zooming out ==> fetch events for the second time range
generateRandomEvents(e.getStartDateSecond(), e.getEndDateSecond(), timelineUpdater);
}
}
private void generateRandomEvents(LocalDateTime startDate, LocalDateTime endDate, TimelineUpdater timelineUpdater) {
LocalDateTime curDate = startDate;
Random rnd = new Random();
PrimitiveIterator.OfInt randomInts = rnd.ints(1, 99999).iterator();
while (curDate.isBefore(endDate)) {
// create events in the given time range
if (rnd.nextBoolean()) {
// event with only one date
model.add(TimelineEvent.<String>builder()
.data("Event " + randomInts.nextInt())
.startDate(curDate)
.build(), timelineUpdater);
} else {
// event with start and end dates
model.add(TimelineEvent.<String>builder()
.data("Event " + randomInts.nextInt())
.startDate(curDate)
.endDate(curDate.plusHours(18))
.build(),
timelineUpdater);
}
curDate = curDate.plusHours(24);
}
}
public void clearTimeline() {
// clear Timeline, so that it can be loaded again with a new preload factor
model.clear();
}
public void setPreloadFactor(float preloadFactor) {
this.preloadFactor = preloadFactor;
}
public float getPreloadFactor() {
return preloadFactor;
}
public long getZoomMax() {
return zoomMax;
}
}
Can you zip your reproducer and attach it here please?
Thanks makes our lives much easier.
When running with -Pmyfaces23
I get this stack trace..
Feb 04, 2021 1:05:11 PM javax.faces.component.UIViewRoot notifyListeners
SEVERE: An Exception occured while processing the beforePhase method of PhaseListener org.primefaces.component.timeline.DefaultTimelineUpdater@26490518 in Phase RENDER_RESPONSE(6)
java.lang.NullPointerException
at org.primefaces.component.timeline.TimelineRenderer.calculateGroupsFromModel(TimelineRenderer.java:543)
at org.primefaces.component.timeline.DefaultTimelineUpdater.processCrudOperations(DefaultTimelineUpdater.java:144)
at org.primefaces.component.timeline.DefaultTimelineUpdater.beforePhase(DefaultTimelineUpdater.java:116)
at javax.faces.component.UIViewRoot.notifyListeners(UIViewRoot.java:1099)
at javax.faces.component.UIViewRoot.encodeBegin(UIViewRoot.java:531)
at javax.faces.component.UIComponentBase.encodeAll(UIComponentBase.java:527)
at org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage.renderView(FaceletViewDeclarationLanguage.java:1897)
at org.apache.myfaces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:315)
I think both your issues are related right? Do either of your tickets work WITHOUT lazy loading?
Arf doesn't look good... specially if same problem happen with both impl... I tried without a composite and works fine.
If you debug Timeline#getValue
, you'll see that it's trying to put the composite in ELContext so the value gets properly parsed (see ContextualCompositeValueExpression
) but it doesn't seem to "find" the compositeTimeLine.xhtml (see CompositeComponentStackManager#findCompositeComponentUsingLocation
). No composite found, no value, then NPE
I wans't sure if this had to do with the DefaultTimeLineUpdater PhaseListener either as the other issue gets an NPE there trying to get the model in Lazy Load.
in general i dont like how the DefaultTimeLineUpdater is implemented, maybe we can think about better ideas in 11.0
I started few weeks ago on refactoring timeline, nothing big. I'll submit a PR for review and hopefully ready for 11
This has just punched me in the face... Is there any workaround? Do you have any idea?
Arf doesn't look good... specially if same problem happen with both impl... I tried without a composite and works fine.
Yes, don't use composite 😄 But on a more serious note, I didn't dig that much since last time, so that's my best answer so far
I don't give up that easy. I have a workaround if the composite component is used in a view only once:
Composite component class:
@FacesComponent("mycc")
public class MyCC {
private static final String REQUEST_MAP_KEY = "myccRequestVariable";
/**
* Workaround for https://github.com/primefaces/primefaces/issues/6944
* Idea comes from https://stackoverflow.com/a/11961132
*/
public MyCC() {
Map<String, Object> requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
if ( requestMap.containsKey(REQUEST_MAP_KEY) ) {
//Throwing exception if the component is more than once in view
throw new RuntimeException("Only one " + MyCC.class.getName() + " work in a page. " +
"Cause is a workaround for a bug https://github.com/primefaces/primefaces/issues/6944!");
} else {
requestMap.put(REQUEST_MAP_KEY, this);
}
}
public TimelineModel<X, Y> getTimelineModel() {
....;
}
public void onLazyLoad(TimelineLazyLoadEvent e) {
....;
}
}
CC xhtml:
<p:timeline value="#{myccRequestVariable.timelineModel}"
start="#{myccRequestVariable.attrs.startDate}"
end="#{myccRequestVariable.attrs.endDate}"
widgetVar="timelineWdgt">
<p:ajax event="lazyload" update="@none" listener="#{myccRequestVariable.onLazyLoad}"/>
</p:timeline>
Better than nothing.
Yeah... First, like Thomas mentionned the component needs some love (I have to check if I can find the refacto I started few years back...) and second, you better off using facelet tags (in combination with omnifaces) than composite (works as an include, do better and simpler than cc) and you won't have to patch anything
Yeah... First, like Thomas mentionned the component needs some love (I have to check if I can find the refacto I started few years back...) and second, you better off using facelet tags (in combination with omnifaces) than composite (works as an include, do better and simpler than cc) and you won't have to patch anything
The problem is, that if you want to have state, it is better to use composite, as far as I understand CC. But of course if you can have only one instance in the view, it can be a view scope bean, who keeps the state, I know.
Not sure I follow, every UIComponent
has a state...If by state, you mean your bean then I'm not aware of the relation between this and the type of reusable component you wanna use...
At this point, either you wanna to keep using a cc then you have to stick to your patch (till we refactor this component, and yet there is no guarantee we get rid of PhaseListener, I must say I have no idea why this is used...), or go for an alternative such as facelet tag (which is better IMO) and most probably everything will work fine (i'm a cc hater, so I'm biased 😅)
Not sure I follow, every
UIComponent
has a state...If by state, you mean your bean then I'm not aware of the relation between this and the type of reusable component you wanna use...At this point, either you wanna to keep using a cc then you have to stick to your patch (till we refactor this component, and yet there is no guarantee we get rid of PhaseListener, I must say I have no idea why this is used...), or go for an alternative such as facelet tag (which is better IMO) and most probably everything will work fine (i'm a cc hater, so I'm biased 😅)
I also don't like CCs, since somehow it never works as expected. Still I have not seen possibilities to have a state associated with a facelet tag "instance". Thats the problem.
Again, I have no idea what you mean by state, so I think a bit of reading is required: https://stackoverflow.com/a/6822269/4605161
The link you found is really useful to understand the issue, plus it's BalusC certified therefore I'm posting just to highlight it 😄 : https://stackoverflow.com/a/11961132/4605161