If the attribute editable is true, the events can be created, edited (content), changed (start / end time) and deleted. Events can be created by double click on empty space. Events can only be editable when the attribute selectable is true (default). Editable timeline can fire AJAX events select, change, changed, edit, add, delete.
<style>
div.vis-item-content {
padding: 4px;
border-radius: 2px;
-moz-border-radius: 2px;
}
div.vis-item.vis-item-range {
border-width: 0;
}
#overlappedOrders {
margin-top: 20px;
width: 100%;
}
#overlappedOrders .ui-chkbox {
vertical-align: middle;
margin: 3px 5px;
}
</style>
<div class="card">
<h:form id="form">
<p:growl id="growl" showSummary="true" showDetail="false">
<p:autoUpdate/>
</p:growl>
<p:timeline id="timeline" value="#{editServerTimelineView.model}" 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="changed" update="@none" listener="#{editServerTimelineView.onChange}"/>
<p:ajax event="edit" update="detailsBookingInner"
listener="#{editServerTimelineView.onEdit}"
oncomplete="PF('detailsBookingWdgt').show()"/>
<p:ajax event="add" update="detailsBookingInner"
listener="#{editServerTimelineView.onAdd}"
oncomplete="PF('detailsBookingWdgt').show()"/>
<p:ajax event="delete" update="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 columns="1">
<h:outputText value="Room: #{booking.roomNumber}"/>
<h:outputText value="Category: #{booking.category.label}"/>
<h:outputText value="Phone: #{booking.phone}"/>
</h:panelGrid>
</p:timeline>
<p:commandButton value="Toggle TimeChangeable" process="@this" update="timeline" style="margin-top:15px"
action="#{editServerTimelineView.toggleEditableTime}"/>
<!-- 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>
</div>
@Named("editServerTimelineView")
@ViewScoped
public class EditServerTimelineView implements Serializable {
private TimelineModel<Booking, ?> model;
private TimelineEvent<Booking> event; // current event to be changed, edited, deleted or added
private long zoomMax;
private LocalDateTime start;
private LocalDateTime end;
private boolean editableTime = true;
@PostConstruct
protected void 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 onChange(TimelineModificationEvent<Booking> e) {
// get clone of the TimelineEvent to be changed with new start / end dates
event = e.getTimelineEvent();
// 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();
}
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();
// 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();
}
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() {
return event;
}
public void setEvent(TimelineEvent<Booking> 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 = event.getData().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 + "?";
}
public String getRoom() {
Integer room = event.getData().getRoomNumber();
if (room == null) {
return "(new booking)";
}
else {
return "(room " + room + ")";
}
}
}
public class Booking implements Serializable {
private Integer roomNumber;
private RoomCategory category;
private String phone;
private String comment;
public Booking() {
}
public Booking(Integer roomNumber, RoomCategory category, String phone, String comment) {
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() {
return category;
}
public void setCategory(RoomCategory 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;
}
}
public enum RoomCategory {
STANDARD("Standard"),
SUPERIOR("Superior"),
DELUXE("Deluxe"),
JUNIOR("Junior"),
EXECUTIVE_SUITE("Executive Suite");
private final String label;
private RoomCategory(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}