When an item is dragged and dropped, an event with an end date will be created. The end date is the start date + 10% of the timeline width. This default algorithm fits the most use cases. Tip: if events are editable, you can adjust the start / end date later by double clicking on an event.
To activate the built-in drag-and-drop feature, simple add p:ajax with event="drop" to the timeline tag.
<div style="float:left;">
<strong>Drag and drop events</strong>
<p/>
<p:dataList id="eventsList" value="#{dndTimelineController.events}"
var="event" itemType="circle">
<h:panelGroup id="eventBox" layout="box" style="z-index:9999; cursor:move;">
#{event.name}
</h:panelGroup>
<p:draggable for="eventBox" revert="true" helper="clone" cursor="move"/>
</p:dataList>
</div>
<pe:timeline id="timeline" value="#{dndTimelineController.model}" var="event"
editable="true" eventMargin="10" eventMarginAxis="0" minHeight="250"
start="#{dndTimelineController.start}" end="#{dndTimelineController.end}"
timeZone="#{dndTimelineController.localTimeZone}"
style="margin-left:135px;" snapEvents="false" showNavigation="true"
dropActiveStyleClass="ui-state-highlight" dropHoverStyleClass="ui-state-hover">
<p:ajax event="drop" listener="#{dndTimelineController.onDrop}" global="false" update="eventsList"/>
<h:outputText value="#{event.name}"/>
... other properties can be displayed too
</pe:timeline>
In this example, the objects in the list (on the left side) have properties name, start date, end date.
public class Event implements Serializable {
private String name;
private Date start;
private Date end;
// constructors, getter / setter
...
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Event event = (Event) o;
if (name != null ? !name.equals(event.name) : event.name != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}
The bean class defines the AJAX listener onDrop. The listener gets an instance of the class TimelineDragDropEvent. Besides start / end date and group, this event object also contains a client ID of the dragged component and dragged model object if draggable item is within a data iteration component.
public class DndTimelineController implements Serializable {
private TimelineModel model;
private TimeZone localTimeZone = TimeZone.getTimeZone("Europe/Berlin");
private List<Event> events = new ArrayList<Event>();
@PostConstruct
protected void initialize() {
// create timeline model
model = new TimelineModel();
// create available events for drag-&-drop
for (int i = 1; i <= 13; i++) {
events.add(new Event("Event " + i));
}
}
public void onDrop(TimelineDragDropEvent e) {
// get dragged model object (event class) if draggable item is within a data iteration component,
// update event's start / end dates.
Event dndEvent = (Event) e.getData();
dndEvent.setStart(e.getStartDate());
dndEvent.setEnd(e.getEndDate());
// create a timeline event (not editable)
TimelineEvent event = new TimelineEvent(dndEvent, e.getStartDate(), e.getEndDate(), false, e.getGroup());
// add a new event
TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":mainForm:timeline");
model.add(event, timelineUpdater);
// remove from the list of all events
events.remove(dndEvent);
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO,
"The " + dndEvent.getName() + " was added", null);
FacesContext.getCurrentInstance().addMessage(null, msg);
}
public TimelineModel getModel() {
return model;
}
public TimeZone getLocalTimeZone() {
return localTimeZone;
}
public List<Event> getEvents() {
return events;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
Please also consider the drag-and-drop related attributes dropHoverStyleClass, dropActiveStyleClass, dropAccept, dropScope. They are similar to corresponding attributes in p:droppable. There is only one difference - the datasource attribute is not needed. p:droppable in PrimeFaces has this attribute which represents an ID of an UIData component to connect with. I have never understood why we need this. It is possible to get the UIData component with a simple trick. The reading below is for advanced developers.The drop callback from the jQuery Droppable gets an ui object with a reference to the draggable element. So we can look for a closest DOM element which is a container for an UIData component.
var params = [];
// Check if draggable is within a data iteration component.
// Note for PrimeFaces team: an additional unified style class ".ui-data" for all UIData components would be welcome here!
var uiData = ui.draggable.closest(".ui-datatable, .ui-datagrid, .ui-datalist, .ui-carousel");
if (uiData.length > 0) {
params.push({
name: this.id + '_uiDataId',
value: uiData.attr('id')
});
}
params.push({
name: this.id + '_dragId',
value: ui.draggable.attr('id')
});
// call the drop listener
this.getBehavior("drop").call(this, evt, {params: params, ...});
In the component itself, the current dragged object is extracted by this snippet
FacesContext context = FacesContext.getCurrentInstance();
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String clientId = this.getClientId(context);
Object data = null;
String dragId = params.get(clientId + "_dragId");
String uiDataId = params.get(clientId + "_uiDataId");
if (dragId != null && uiDataId != null) {
// draggable is within a data iteration component
UIDataContextCallback contextCallback = new UIDataContextCallback(dragId);
context.getViewRoot().invokeOnComponent(context, uiDataId, contextCallback);
data = contextCallback.getData();
}
TimelineDragDropEvent te = new TimelineDragDropEvent(this, behaviorEvent.getBehavior(), ... dragId, data);
The JSF standard method invokeOnComponent does the job. Now the data object is available in TimelineDragDropEvent. The class UIDataContextCallback is a simple implementation of the JSF ContextCallback interface.
public class UIDataContextCallback implements ContextCallback {
private String dragId;
private Object data;
public UIDataContextCallback(String dragId) {
this.dragId = dragId;
}
public void invokeContextCallback(FacesContext fc, UIComponent component) {
UIData uiData = (UIData) component;
String[] idTokens = dragId.split(String.valueOf(UINamingContainer.getSeparatorChar(fc)));
int rowIndex = Integer.parseInt(idTokens[idTokens.length - 2]);
uiData.setRowIndex(rowIndex);
data = uiData.getRowData();
uiData.setRowIndex(-1);
}
public Object getData() {
return data;
}
}

No comments:
Post a Comment
Note: Only a member of this blog may post a comment.