Timeline: ContextMenu not displaying on item
See original GitHub issueAlso see https://forum.primefaces.org/posting.php?mode=edit&f=3&p=198313
I am migrating an Application from PF 6.2 to PF 10.0.8. In this Application we have a ContextMenu for the TimelineEvent. In the old Version with PF 6.2 it worked well. If i right click on an not selected event, the ContextMenu is programmatically blocked. If i right click on an selected event, the ContextMenu for the selected event is shown.
But with PF 10, if i right click an selected event, no ContextMenu is shown. If i right click an not selected event, the ContextMenu would be shown. But without any Information about the right clicked event.
Based on the showcase example “Editable Server-side” i build a testcase.
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head id="head">
<f:facet name="first">
<meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
</f:facet>
<title>Primefaces-Showcase-Timeline: Basic</title>
</h:head>
<h:body >
<h:form id="form">
<p:remoteCommand name="beforeShowContextMenu" actionListener="#{editServerTimelineView.beforeShowContextMenu}" />
<p:growl id="growl" showSummary="true" showDetail="true">
<p:autoUpdate/>
</p:growl>
<p:timeline id="timeline"
value="#{editServerTimelineView.model}"
style="margin-top: 124px;"
var="booking"
zoomMax="#{editServerTimelineView.zoomMax}"
start="#{editServerTimelineView.start}"
end="#{editServerTimelineView.end}"
editable="true" editableTime="#{editServerTimelineView.editableTime}"
editableOverrideItems="true" minHeight="200" widgetVar="timelineWdgt">
<p:ajax event="select" update="contextMenu" listener="#{editServerTimelineView.onSelect}"/>
<p:ajax event="changed" update="contextMenu" listener="#{editServerTimelineView.onChange}"/>
<p:ajax event="edit" update="contextMenu detailsBookingInner"
listener="#{editServerTimelineView.onEdit}"
oncomplete="PF('detailsBookingWdgt').show()"/>
<p:ajax event="add" update="contextMenu detailsBookingInner"
listener="#{editServerTimelineView.onAdd}"
oncomplete="PF('detailsBookingWdgt').show()"/>
<p:ajax event="delete" update="contextMenu deleteBookingInner"
listener="#{editServerTimelineView.onDelete}"
onstart="PF('timelineWdgt').cancelDelete()" oncomplete="PF('deleteBookingWdgt').show()"/>
<f:facet name="loading">
<h1>Loading please wait...</h1>
</f:facet>
<h:panelGrid id="eventData" columns="1" style="width: 100%" >
<h:outputText value="Room: #{booking.roomNumber}" style="width: 100%"/>
<h:outputText value="Category: #{booking.category.label}" style="width: 100%"/>
<h:outputText value="Phone: #{booking.phone}" style="width: 100%"/>
</h:panelGrid>
</p:timeline>
<p:contextMenu id="contextMenu" for="eventData" beforeShow="beforeShowContextMenu()">
<p:menuitem value="#{editServerTimelineView.selectedEventInfo}"
icon="ui-icon-scissors" update="deleteBookingInner" />
</p:contextMenu>
<!-- Booking details dialog -->
<p:dialog id="detailsBookingDlg" header="Booking Details" widgetVar="detailsBookingWdgt"
showEffect="clip" hideEffect="clip">
<h:panelGroup id="detailsBookingInner" layout="block">
<h:panelGrid columns="2" columnClasses="bookingDetails1,bookingDetails2">
<h:outputText value="Room"/>
<p:inputText value="#{editServerTimelineView.event.data.roomNumber}"
rendered="#{not empty editServerTimelineView.event}"
required="true" label="Room"/>
<h:outputText value="Category"/>
<p:selectOneMenu value="#{editServerTimelineView.event.data.category}"
rendered="#{not empty editServerTimelineView.event}">
<f:selectItem itemLabel="Standard" itemValue="STANDARD"/>
<f:selectItem itemLabel="Superior" itemValue="SUPERIOR"/>
<f:selectItem itemLabel="Deluxe" itemValue="DELUXE"/>
<f:selectItem itemLabel="Junior" itemValue="JUNIOR"/>
<f:selectItem itemLabel="Executive Suite" itemValue="EXECUTIVE_SUITE"/>
</p:selectOneMenu>
<h:outputText value="From"/>
<p:calendar value="#{editServerTimelineView.event.startDate}"
rendered="#{not empty editServerTimelineView.event}"
pattern="dd/MM/yyyy HH:mm" required="true" label="From"/>
<h:outputText value="Until"/>
<p:calendar value="#{editServerTimelineView.event.endDate}"
rendered="#{not empty editServerTimelineView.event}"
pattern="dd/MM/yyyy HH:mm" label="Until"/>
<h:outputText value="Phone"/>
<p:inputMask value="#{editServerTimelineView.event.data.phone}" mask="(9999) 999-999"
rendered="#{not empty editServerTimelineView.event}"/>
<h:outputText value="Comment"/>
<p:inputTextarea value="#{editServerTimelineView.event.data.comment}" autoResize="false"
rendered="#{not empty editServerTimelineView.event}"/>
</h:panelGrid>
</h:panelGroup>
<f:facet name="footer">
<h:panelGroup layout="block" style="text-align:right; padding:2px; white-space:nowrap;">
<p:commandButton value="Save" process="detailsBookingDlg" update="@none"
action="#{editServerTimelineView.saveDetails}"
oncomplete="if(!args.validationFailed){PF('detailsBookingWdgt').hide();}"/>
<p:commandButton type="button" value="Close" onclick="PF('detailsBookingWdgt').hide()"/>
</h:panelGroup>
</f:facet>
</p:dialog>
<!-- Booking delete dialog -->
<p:dialog id="deleteBookingDlg" header="Booking Details" widgetVar="deleteBookingWdgt"
showEffect="clip" hideEffect="clip" dynamic="true">
<h:panelGroup id="deleteBookingInner" layout="block" style="margin:10px;">
<h:outputText value="#{editServerTimelineView.deleteMessage}"/>
</h:panelGroup>
<f:facet name="footer">
<h:panelGroup layout="block" style="text-align:right; padding:2px; white-space:nowrap;">
<p:commandButton value="Delete" process="deleteBookingDlg" update="@none"
action="#{editServerTimelineView.delete}"
oncomplete="PF('deleteBookingWdgt').hide()"/>
<p:commandButton type="button" value="Close" onclick="PF('deleteBookingWdgt').hide()"/>
</h:panelGroup>
</f:facet>
</p:dialog>
</h:form>
</h:body>
</html>
EditServerTimelineView.java
@Named("editServerTimelineView")
@SessionScoped
public class EditServerTimelineView implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(EditServerTimelineView.class);
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
private TimelineModel<Booking, ?> model;
private TimelineEvent<Booking> event; // current event to be changed, edited, deleted or added
private TimelineEvent<Booking> selectedEvent;
private long zoomMax;
private LocalDateTime start;
private LocalDateTime end;
private boolean editableTime = true;
@PostConstruct
protected void initialize() {
logger.info("initialize");
// initial zooming is ca. one month to avoid hiding of event details (due to wide time range of events)
zoomMax = 1000L * 60 * 60 * 24 * 30;
// set initial start / end dates for the axis of the timeline (just for testing)
start = LocalDate.of(2019, Month.FEBRUARY, 9).atStartOfDay();
end = LocalDate.of(2019, Month.MARCH, 10).atStartOfDay();
// create timeline model
model = new TimelineModel<>();
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(211, RoomCategory.DELUXE, "(0034) 987-111", "One day booking"))
.startDate(LocalDateTime.of(2019, Month.JANUARY, 2, 0, 0))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(202, RoomCategory.EXECUTIVE_SUITE, "(0034) 987-333", "Three day booking"))
.startDate(LocalDateTime.of(2019, Month.JANUARY, 26, 0, 0))
.endDate(LocalDateTime.of(2019, Month.JANUARY, 28, 23, 59, 59))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(150, RoomCategory.STANDARD, "(0034) 987-222", "Six day booking"))
.startDate(LocalDateTime.of(2019, Month.FEBRUARY, 10, 0, 0))
.endDate(LocalDateTime.of(2019, Month.FEBRUARY, 15, 23, 59, 59))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(178, RoomCategory.STANDARD, "(0034) 987-555", "Five day booking"))
.startDate(LocalDateTime.of(2019, Month.FEBRUARY, 23, 0, 0))
.endDate(LocalDateTime.of(2019, Month.FEBRUARY, 27, 23, 59, 59))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(101, RoomCategory.SUPERIOR, "(0034) 987-999", "One day booking"))
.startDate(LocalDateTime.of(2019, Month.MARCH, 6, 0, 0))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(80, RoomCategory.JUNIOR, "(0034) 987-444", "Four day booking"))
.startDate(LocalDateTime.of(2019, Month.MARCH, 19, 0, 0))
.endDate(LocalDateTime.of(2019, Month.MARCH, 22, 23, 59, 59))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(96, RoomCategory.DELUXE, "(0034) 987-777", "Two day booking"))
.startDate(LocalDateTime.of(2019, Month.APRIL, 3, 0, 0))
.endDate(LocalDateTime.of(2019, Month.APRIL, 4, 23, 59, 59))
.build());
model.add(TimelineEvent.<Booking>builder()
.data(new Booking(80, RoomCategory.JUNIOR, "(0034) 987-444", "Ten day booking"))
.startDate(LocalDateTime.of(2019, Month.APRIL, 22, 0, 0))
.endDate(LocalDateTime.of(2019, Month.MAY, 1, 23, 59, 59))
.build());
}
public void onSelect(TimelineSelectEvent<Booking> e) {
selectedEvent = e.getTimelineEvent();
String eventAsString = timelineEventAsString(selectedEvent);
logger.info("onSelect: event={}", eventAsString);
logger.info("onSelect: {}", getEventInfo(selectedEvent));
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "select", eventAsString));
}
public void onChange(TimelineModificationEvent<Booking> e) {
// get clone of the TimelineEvent to be changed with new start / end dates
event = e.getTimelineEvent();
String eventAsString = timelineEventAsString(event);
logger.info("onChange: event={}", eventAsString);
// update booking in DB...
// if everything was ok, no UI update is required. Only the model should be updated
model.update(event);
FacesMessage msg
= new FacesMessage(FacesMessage.SEVERITY_INFO, "The booking dates " + getRoom() + " have been updated", null);
FacesContext.getCurrentInstance().addMessage(null, msg);
// otherwise (if DB operation failed) a rollback can be done with the same response as follows:
// TimelineEvent oldEvent = model.getEvent(model.getIndex(event));
// TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":form:timeline");
// model.update(oldEvent, timelineUpdater);
}
public void onEdit(TimelineModificationEvent<Booking> e) {
// get clone of the TimelineEvent to be edited
event = e.getTimelineEvent();
String eventAsString = timelineEventAsString(event);
logger.info("onEdit: event={}", eventAsString);
}
public void onAdd(TimelineAddEvent e) {
// get TimelineEvent to be added
event = TimelineEvent.<Booking>builder()
// the id generated from the UI must be set
.id(e.getId())
.data(new Booking())
.startDate(e.getStartDate())
.endDate(e.getEndDate())
.editable(true)
.build();
String eventAsString = timelineEventAsString(event);
logger.info("onAdd: event={}", eventAsString);
// add the new event to the model in case if user will close or cancel the "Add dialog"
// without to update details of the new event. Note: the event is already added in UI.
model.add(event);
}
public void onDelete(TimelineModificationEvent<Booking> e) {
// get clone of the TimelineEvent to be deleted
event = e.getTimelineEvent();
String eventAsString = timelineEventAsString(event);
logger.info("onDelete: event={}", eventAsString);
}
public void delete() {
// delete booking in DB...
// if everything was ok, delete the TimelineEvent in the model and update UI with the same response.
// otherwise no server-side delete is necessary (see timelineWdgt.cancelDelete() in the p:ajax onstart).
// we assume, delete in DB was successful
TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":form:timeline");
model.delete(event, timelineUpdater);
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "The booking " + getRoom() + " has been deleted", null);
FacesContext.getCurrentInstance().addMessage(null, msg);
}
public void saveDetails() {
// save the updated booking in DB...
// if everything was ok, update the TimelineEvent in the model and update UI with the same response.
// otherwise no server-side update is necessary because UI is already up-to-date.
// we assume, save in DB was successful
TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":form:timeline");
model.update(event, timelineUpdater);
FacesMessage msg
= new FacesMessage(FacesMessage.SEVERITY_INFO, "The booking details " + getRoom() + " have been saved", null);
FacesContext.getCurrentInstance().addMessage(null, msg);
}
public TimelineModel<Booking, ?> getModel() {
return model;
}
public TimelineEvent<Booking> getEvent() {
logger.info("getEvent({})", event);
return event;
}
public void setEvent(TimelineEvent<Booking> event) {
logger.info("setEvent({})", event);
this.event = event;
}
public long getZoomMax() {
return zoomMax;
}
public LocalDateTime getStart() {
return start;
}
public LocalDateTime getEnd() {
return end;
}
public boolean isEditableTime() {
return editableTime;
}
public void toggleEditableTime() {
editableTime = !editableTime;
}
public String getDeleteMessage() {
Integer room = getRoomNumber();
if (room == null) {
return "Do you really want to delete the new booking?";
}
return "Do you really want to delete the booking for the room " + room + "?";
}
private Integer getRoomNumber() {
if (null == event || null == event.getData())
return null;
return event.getData().getRoomNumber();
}
public String getRoom() {
Integer room = getRoomNumber();
if (room == null) {
return "(new booking)";
}
else {
return "(room " + room + ")";
}
}
public String getSelectedEventInfo() {
return getEventInfo(selectedEvent);
}
private String getEventInfo(TimelineEvent<Booking> eve) {
String result = "Nothing selected";
if (null != eve && null != eve.getData()) {
Booking eventData = eve.getData();
logger.info("getEventInfo: {}", eventData);
result = "" + eventData.getRoomNumber() + "/" + eventData.getCategory();
}
logger.info("getEventInfo: {}", result);
return result;
}
public String beforeShowContextMenu() {
String eventAsString = timelineEventAsString(selectedEvent);
logger.info("beforeShowContextMenu: '{}'", eventAsString);
FacesMessage msg
= new FacesMessage(FacesMessage.SEVERITY_INFO, "ShowContextMenu: " + getEventInfo(selectedEvent), null);
FacesContext.getCurrentInstance().addMessage(null, msg);
return "false";
}
private String timelineEventAsString(TimelineEvent<Booking> event) {
StringBuilder builder = new StringBuilder();
if (null != event) {
Booking data = event.getData();
LocalDateTime startDate = event.getStartDate();
LocalDateTime endDate = event.getEndDate();
if (null == data) {
builder.append(" : ");
} else {
builder.append(data);
builder.append(" : ");
}
if (null == endDate) {
if (null == startDate) {
builder.append("??? ");
} else {
builder.append(startDate.format(FORMATTER));
builder.append(" ");
}
} else {
if (null == startDate) {
builder.append("??? - ");
} else {
builder.append(startDate.format(FORMATTER));
builder.append(" - ");
}
builder.append(endDate.format(FORMATTER));
}
}
return builder.toString();
}
}
And slightly modified Booking.java
public class Booking implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(Booking.class);
private Integer roomNumber;
private RoomCategory category;
private String phone;
private String comment;
public Booking() {
logger.info("Booking()");
}
public Booking(Integer roomNumber, RoomCategory category, String phone, String comment) {
logger.info("Booking()");
this.roomNumber = roomNumber;
this.category = category;
this.phone = phone;
this.comment = comment;
}
public Integer getRoomNumber() {
return roomNumber;
}
public void setRoomNumber(Integer roomNumber) {
this.roomNumber = roomNumber;
}
public RoomCategory getCategory() {
logger.info("getCategory({})", category);
return category;
}
public void setCategory(RoomCategory category) {
logger.info("setCategory({})", category);
this.category = category;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Booking booking = (Booking) o;
if (roomNumber != null ? !roomNumber.equals(booking.roomNumber) : booking.roomNumber != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return roomNumber != null ? roomNumber.hashCode() : 0;
}
@Override
public String toString() {
return "Booking{" +
"roomNumber=" + roomNumber +
", category=" + category +
", phone='" + phone + '\'' +
", comment='" + comment + '\'' +
'}';
}
}
I also tested this with PF 11. The same behavior.
Runtime: JDK 1.8 WildFly 18 Primefaces 10.0.8
Is this possible an issue like https://github.com/primefaces/primefaces/issues/8376 on DataTable?
Thanks in advance!
Ludger
Issue Analytics
- State:
- Created 2 years ago
- Comments:14 (9 by maintainers)

Top Related StackOverflow Question
Applying the patch and using
for dragable items, it works like a charm!
With the targetFilter on div.vis-drag-center,… i achieved, that the contextMenu only appears if i right click on an selected item, which was my goal.
Thanks for the help!
Fantastic. Glad I could help.