question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Timeline: ContextMenu not displaying on item

See original GitHub issue

Also 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:closed
  • Created 2 years ago
  • Comments:14 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
HKM-CIAcommented, Mar 16, 2022

Applying the patch and using

            <p:contextMenu id="contextMenu"
                           for="timeline"
                           targetFilter="div.vis-drag-center,div.vis-drag-left,div.vis-drag-right"
                           model="#{dndTimelineView.contextMenuModel}" >
            </p:contextMenu>

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!

0reactions
mellowarecommented, Mar 16, 2022

Fantastic. Glad I could help.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why is the Context Menu not Showing up in ListView?
I want to display a context menu on long click, on the ListView item in Search Result Activity, i've used "registerForContextMenu(View v)" ...
Read more >
How can I open a <p:contextMenu> on a <p:timeline>'s event?
The first thing to do is to study the TimeLine JavaScript and Widget and see how you add a Jquery handler to make...
Read more >
Context menu for BODY tag not appearing when ... - Telerik
Hello, I am adding a context menu to body tag of rad editor control while "EditorContentAreaMode" is set to Div. Problem is that,...
Read more >
Context menu of Product Backlog Item is not working after ...
9. Try to click 3 dots to open context menu (see blue square box on attached picture) of this or any other item....
Read more >
Timeline segment context menu only displays options based on the ...
Get answers fast from Autodesk support staff and product experts in the forums. Visit Flame Products forum. Find Service Providers. Connect, consult with,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found