<p:ajax event="lazyload" disabled="#{lazyTimelineController.disabled}" listener="#{lazyTimelineController.onLazyLoad}" onstart="PF('dialogWidget').show()" oncomplete="PF('dialogWidget').hide()"/>And of course you can control exactly what should be executed and updated (process / update attributes). To understand how this new feature works (before posting a lot of code :-)) I sketched one diagram. Please read from top to down.
On intial page load, only events in the visible time range should be lazy loaded. After moving to the right or left direction, area on the right or left side is coming into the view. Only events in this new displayed time range should be loaded. Time range with already loaded events is cached internally (read line in the diagram). When you zoom out now, you will probably have two time ranges the events should be loaded for. This is demonstrated in the 4. use case. When you zoom in, no AJAX event for lazy loading is sent. It is very smart! As you can see, the "lazyload" listener is not invoked again when the visible time range (incl. some hidden ranges defined by preloadFactor) already has lazy loaded events.
What is preloadFactor? The preloadFactor attribute of the pe:timeline is a positive float value or 0 (default). When the lazy loading feature is active, the calculated time range for preloading will be multiplicated by the preload factor. The result of this multiplication specifies the additional time range which will be considered for the preloading during moving / zooming too. For example, if the calculated time range for preloading is 5 days and the preload factor is 0.2, the result is 5 * 0.2 = 1 day. That means, 1 day backwards and / or 1 day onwards will be added to the original calculated time range. The event's area to be preloaded is wider then. This helps to avoid frequently, time-consuming fetching of events. Note: the preload factor in the diagram above was 0.
Let me show the code now. The code is taken from this live example of the deployed showcase.
<div id="loadingText" style="font-weight:bold; margin:-5px 0 5px 0; visibility:hidden;">Loading ...</div> <pe:timeline id="timeline" value="#{lazyTimelineController.model}" preloadFactor="#{lazyTimelineController.preloadFactor}" zoomMax="#{lazyTimelineController.zoomMax}" minHeight="170" showNavigation="true"> <p:ajax event="lazyload" update="@none" listener="#{lazyTimelineController.onLazyLoad}" onstart="$('#loadingText').css('visibility', 'visible')" oncomplete="$('#loadingText').css('visibility', 'hidden')"/> </pe:timeline>You see a hidden "Loading ..." text and the timeline tag. The text is shown when a "lazyload" AJAX request is sent and gets hidden when the response is back. The bean class LazyTimelineController looks as follows
@ManagedBean @ViewScoped public class LazyTimelineController implements Serializable { private TimelineModel 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 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 (Exception ex) { // ignore } TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":mainForm:timeline"); Date startDate = e.getStartDateFirst(); // alias getStartDate() can be used too Date 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(Date startDate, Date endDate, TimelineUpdater timelineUpdater) { Calendar cal = Calendar.getInstance(); Date curDate = startDate; Random rnd = new Random(); while (curDate.before(endDate)) { // create events in the given time range if (rnd.nextBoolean()) { // event with only one date model.add(new TimelineEvent("Event " + RandomStringUtils.randomNumeric(5), curDate), timelineUpdater); } else { // event with start and end dates cal.setTimeInMillis(curDate.getTime()); cal.add(Calendar.HOUR, 18); model.add(new TimelineEvent("Event " + RandomStringUtils.randomNumeric(5), curDate, cal.getTime()), timelineUpdater); } cal.setTimeInMillis(curDate.getTime()); cal.add(Calendar.HOUR, 24); curDate = cal.getTime(); } } 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; } }The listener onLazyLoad gets an event object TimelineLazyLoadEvent. The TimelineLazyLoadEvent contains one or two time ranges the events should be loaded for. Two times ranges occur when you zoom out the timeline (as in the screenshot at the end of this post). If you know these time ranges (start / end time), you can fetch events from the database or whatever. Server-side added events can be automatically updated in the UI. This happens as usally by TimelineUpdater: model.add(new TimelineEvent(...), timelineUpdater).
I hope the code is self-explained :-).
Great component Oleg! Congratulations!
ReplyDeleteNice effort Mr. Oleg. You put your best in this article by explaining everything using examples and graphs. I'm glad that such hard working blogger exists in the Internet world. Keep sharing informative blog posts like this.
ReplyDeleteRegards,
Prasant
Hey Oleg,
ReplyDeleteWon't be a good idea to implement the same concept for unloading?
Correct me if I'm wrong, but in your outstanding example, the TimelineModel seems to keep growing. If the user start at 2000 and keep moving till 2013 you can have a lot of data loaded on the backing bean. So an unloadFactor would be cool. You then can adjust depending on you model an system load. What do you think?
LA
Awesome timeline component.
ReplyDeleteAre there any plans to support timeline printing? (Maybe with the component)
Thank.
Printing? Do you mean with p:printer? http://www.primefaces.org/showcase/ui/printer.jsf
Delete