Kanban items react to clicks, right-clicks and the client-side widget API. Kanban is a board component for visualizing workflow and managing tasks with drag-and-drop, including touch drag-and-drop on mobile devices. Based on jkanban by Riktar.
Click an item on the board to view and edit its details in a dialog (the itemClick event).
The bindContextMenu attribute and the itemRightClick event open a PrimeFaces context menu on a task with options to edit, view history or delete. The addItemButton of each column adds new tasks.
The widget exposes a client-side API via PF(widgetVar).methodName(): addElement, findElement, findBoard, getParentBoardID, getBoardElements, replaceElement and removeBoard. Try them with the toolbar; click a task to fill the item id.
<style>
.kanban-item-title {
font-weight: 600;
}
.kanban-item-description {
font-size: 0.8rem;
opacity: 0.7;
margin-top: 2px;
}
#formItemInteraction .kanban-board header.kanban-todo,
#formRightClick .kanban-board header.kanban-todo { background-color: #4f46e5; color: #fff; }
#formItemInteraction .kanban-board header.kanban-inprogress,
#formRightClick .kanban-board header.kanban-inprogress { background-color: #ea580c; color: #fff; }
#formItemInteraction .kanban-board header.kanban-done,
#formRightClick .kanban-board header.kanban-done { background-color: #16a34a; color: #fff; }
#formRightClick .kanban-board header { font-size: 1rem; }
#formClientApi .kanban-board header.kanban-backlog { background-color: #6366f1; color: #fff; }
#formClientApi .kanban-board header.kanban-inprogress { background-color: #f59e0b; color: #fff; }
#formClientApi .kanban-board header.kanban-done { background-color: #10b981; color: #fff; }
.api-toolbar { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px; margin-bottom: 12px; }
.api-toolbar .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.api-toolbar .row:last-child { margin-bottom: 0; }
.api-toolbar .sep { border-left: 1px solid #d1d5db; height: 24px; margin: 0 4px; }
.board-highlight { outline: 3px solid #6366f1; outline-offset: 2px; border-radius: 4px; }
.item-highlight { outline: 3px solid #f59e0b; outline-offset: 2px; border-radius: 4px; }
.selected-badge { background: #eef2ff; color: #4f46e5; padding: 2px 10px; border-radius: 4px; font-size: .8rem; font-weight: 600; }
.result-pill { background: #ecfdf5; color: #059669; padding: 2px 10px; border-radius: 4px; font-size: .8rem; font-weight: 600; display: none; }
.result-pill.visible { display: inline-block; }
</style>
<div class="card">
<h5>Item Interaction</h5>
<p>Click an item on the board to view and edit its details in a dialog (the <i>itemClick</i> event).</p>
<h:form id="formItemInteraction">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<pe:kanban id="kanban"
widgetVar="kanbanItemWidget"
value="#{kanbanController.columns}"
draggable="true"
addItemButton="true"
style="height:400px">
<p:ajax event="drop" listener="#{kanbanController.onDrop}" update="messages" />
<p:ajax event="itemAdd" listener="#{kanbanController.onAddItem}" update="kanban messages" />
<p:ajax event="itemClick" listener="#{kanbanController.onItemClick}" update="dialogContent" oncomplete="PF('itemDialog').show()" />
</pe:kanban>
<p:dialog id="dialog" header="Task Details" widgetVar="itemDialog" modal="true" responsive="true" width="400">
<h:panelGroup id="dialogContent" layout="block" styleClass="p-fluid">
<h:panelGroup rendered="#{not empty kanbanController.selectedItem}">
<div class="field mb-3">
<p:outputLabel for="title" value="Title" styleClass="font-bold block mb-1" />
<p:inputText id="title" value="#{kanbanController.selectedItem.title}" required="true" />
</div>
<div class="field mb-3">
<p:outputLabel for="desc" value="Description" styleClass="font-bold block mb-1" />
<p:inputTextarea id="desc" value="#{kanbanController.selectedItem.description}" rows="3" />
</div>
<div class="flex justify-content-end gap-2 mt-4">
<p:commandButton value="Save" action="#{kanbanController.saveItem}" update="kanban messages" oncomplete="PF('itemDialog').hide()" styleClass="p-button-primary mr-2" />
<p:commandButton value="Cancel" onclick="PF('itemDialog').hide()" type="button" styleClass="p-button-secondary" />
</div>
</h:panelGroup>
</h:panelGroup>
</p:dialog>
</h:form>
</div>
<div class="card">
<h5>Right-Click Context Menu</h5>
<p>The <code>bindContextMenu</code> attribute and the <i>itemRightClick</i> event open a PrimeFaces context menu on a task with options to edit, view history or delete. The <code>addItemButton</code> of each column adds new tasks.</p>
<h:form id="formRightClick">
<p:messages id="messages" showSummary="true" showDetail="true"/>
<pe:kanban id="kanban"
widgetVar="kanbanRightClickWidget"
value="#{kanbanRightClickController.columns}"
draggable="true"
addItemButton="true"
style="height:420px"
bindContextMenu="kanbanContextMenu">
<p:ajax event="itemRightClick" listener="#{kanbanRightClickController.onItemRightClick}" />
<p:ajax event="itemAdd" listener="#{kanbanRightClickController.onItemAdd}" update="kanban messages" />
</pe:kanban>
<p:contextMenu id="contextMenu" for="kanban" widgetVar="kanbanContextMenu" targetFilter=".kanban-item">
<p:menuitem value="Edit Task"
icon="pi pi-pencil"
update="dialogContent"
oncomplete="PF('taskDialog').show()" />
<p:divider />
<p:menuitem value="View History"
icon="pi pi-clock"
update="historyContent"
oncomplete="PF('historyDialog').show()" />
<p:menuitem value="Delete Task"
icon="pi pi-trash"
styleClass="text-red-600"
onclick="PF('confirmDelete').show()" />
</p:contextMenu>
<p:confirmDialog id="confirmDeleteDialog" widgetVar="confirmDelete"
showEffect="fade" hideEffect="fade"
message="Are you sure you want to delete this task?"
header="Delete Task" icon="pi pi-exclamation-triangle"
closeOnEscape="true" width="400">
<p:commandButton value="Delete"
styleClass="p-button-danger mr-2"
actionListener="#{kanbanRightClickController.deleteSelectedItem}"
process="@this"
update="kanban messages"
oncomplete="PF('confirmDelete').hide()"
icon="pi pi-check" />
<p:commandButton value="Cancel"
onclick="PF('confirmDelete').hide()"
type="button"
styleClass="p-button-secondary"
icon="pi pi-times" />
</p:confirmDialog>
<p:dialog header="Task Details" widgetVar="taskDialog"
modal="true" responsive="true" width="400"
showEffect="fade" hideEffect="fade"
closeOnEscape="true">
<h:panelGroup id="dialogContent" layout="block" styleClass="p-fluid">
<h:panelGroup rendered="#{not empty kanbanRightClickController.selectedItem}">
<div class="field mb-3">
<p:outputLabel for="title" value="Title" styleClass="font-bold block mb-1" />
<p:inputText id="title" value="#{kanbanRightClickController.selectedItem.title}" readonly="true" />
</div>
<div class="field mb-3">
<p:outputLabel for="desc" value="Description" styleClass="font-bold block mb-1" />
<p:inputTextarea id="desc" value="#{kanbanRightClickController.selectedItem.description}" rows="3" readonly="true" />
</div>
</h:panelGroup>
<h:panelGroup rendered="#{empty kanbanRightClickController.selectedItem}">
<p>Right-click on a task and select "Edit Task" to see its details here.</p>
</h:panelGroup>
</h:panelGroup>
</p:dialog>
<p:dialog header="Task History" widgetVar="historyDialog"
modal="true" responsive="true" width="400"
showEffect="fade" hideEffect="fade"
closeOnEscape="true">
<h:panelGroup id="historyContent" layout="block" styleClass="p-fluid">
<h:panelGroup rendered="#{not empty kanbanRightClickController.selectedItem}">
<p>Activity history for <strong>#{kanbanRightClickController.selectedItem.title}</strong>:</p>
<ul>
<li>Created - Jan 15, 2026</li>
<li>Moved to #{kanbanRightClickController.selectedColumnId} - Jan 20, 2026</li>
<li>Milestone updated - Feb 01, 2026</li>
</ul>
</h:panelGroup>
<h:panelGroup rendered="#{empty kanbanRightClickController.selectedItem}">
<p>Right-click on a task and select "View History" to see its activity timeline.</p>
</h:panelGroup>
</h:panelGroup>
</p:dialog>
</h:form>
</div>
<div class="card">
<h5>Client API</h5>
<p>The widget exposes a client-side API via <code>PF(widgetVar).methodName()</code>: <code>addElement</code>, <code>findElement</code>, <code>findBoard</code>, <code>getParentBoardID</code>, <code>getBoardElements</code>, <code>replaceElement</code> and <code>removeBoard</code>. Try them with the toolbar; click a task to fill the item id.</p>
<h:form id="formClientApi">
<p:growl id="growl" widgetVar="growlClientApi" showDetail="true" showSummary="true"/>
<p:remoteCommand name="rcAddItem" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onAddItem()" />
<p:remoteCommand name="rcRemoveBoard" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onRemoveBoard()" />
<p:remoteCommand name="rcCountItems" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onCountItems()" />
<p:remoteCommand name="rcFindItem" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onFindItem()" />
<p:remoteCommand name="rcParentBoard" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onParentBoard()" />
<p:remoteCommand name="rcReplaceItem" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onReplaceItem()" />
<p:remoteCommand name="rcHighlightBoard" actionListener="#{kanbanClientApiController.onClientApiCommand}" update="@none" process="@this" oncomplete="KanbanDemo.onHighlightBoard()" />
<div class="api-toolbar">
<div class="row">
<span style="font-weight:bold;min-width:50px">Board</span>
<p:selectOneMenu id="boardSelect" style="width:150px">
<f:selectItem itemLabel="Backlog" itemValue="backlog" />
<f:selectItem itemLabel="In Progress" itemValue="inprogress" />
<f:selectItem itemLabel="Done" itemValue="done" />
</p:selectOneMenu>
<p:commandButton value="Add Item" icon="pi pi-plus" type="button"
styleClass="p-button-sm"
onclick="rcAddItem()" />
<p:commandButton value="Count Items" icon="pi pi-list" type="button"
styleClass="p-button-sm"
onclick="rcCountItems()" />
<span class="result-pill" id="countResult"></span>
<p:commandButton value="Remove Board" icon="pi pi-trash" type="button"
styleClass="p-button-sm p-button-danger"
onclick="rcRemoveBoard()" />
<p:commandButton value="Highlight Board" icon="pi pi-eye" type="button"
styleClass="p-button-sm"
onclick="rcHighlightBoard()" />
</div>
<div class="row">
<span style="font-weight:bold;min-width:50px">Item</span>
<p:inputText id="itemIdInput" placeholder="e.g. t-1" style="width:130px" />
<h:panelGroup id="selectedItemBadge" layout="block" style="display:inline">
<span class="selected-badge">
Selected: #{kanbanClientApiController.lastClickedItemId != null ? kanbanClientApiController.lastClickedItemId : 'click a task'}
</span>
</h:panelGroup>
<span class="sep"></span>
<p:commandButton value="Find" icon="pi pi-search" type="button"
styleClass="p-button-sm"
onclick="rcFindItem()" />
<p:commandButton value="Parent Board" icon="pi pi-arrow-right" type="button"
styleClass="p-button-sm"
onclick="rcParentBoard()" />
<span class="result-pill" id="parentResult"></span>
<p:commandButton value="Replace Title" icon="pi pi-pencil" type="button"
styleClass="p-button-sm"
onclick="rcReplaceItem()" />
</div>
</div>
<pe:kanban id="kanban"
widgetVar="kanbanClientApiWidget"
value="#{kanbanClientApiController.columns}"
draggable="true"
addItemButton="true"
style="height:400px">
<p:ajax event="itemClick" listener="#{kanbanClientApiController.onItemClick}" update="growl selectedItemBadge"
oncomplete="KanbanDemo.onItemClick(args)" />
<p:ajax event="itemAdd" update="kanban growl" />
</pe:kanban>
</h:form>
<h:outputScript>
var KanbanDemo = KanbanDemo || {};
KanbanDemo.widget = function() {
return PF('kanbanClientApiWidget');
};
KanbanDemo.boardId = function() {
return $(PrimeFaces.escapeClientId('formClientApi:boardSelect_input')).val();
};
KanbanDemo.itemId = function() {
return $(PrimeFaces.escapeClientId('formClientApi:itemIdInput')).val();
};
KanbanDemo.notify = function(summary, detail, severity) {
severity = severity || 'info';
PF('growlClientApi').show([{
summary: summary,
detail: detail,
severity: severity
}]);
};
KanbanDemo.notifyError = function(summary, detail) {
KanbanDemo.notify(summary, detail, 'error');
};
KanbanDemo.notifyWarn = function(summary, detail) {
KanbanDemo.notify(summary, detail, 'warn');
};
KanbanDemo.flashBoard = function(boardEl) {
boardEl.classList.add('board-highlight');
setTimeout(function() {
boardEl.classList.remove('board-highlight');
}, 2000);
};
KanbanDemo.flashItem = function(itemEl) {
itemEl.classList.add('item-highlight');
setTimeout(function() {
itemEl.classList.remove('item-highlight');
}, 2000);
};
KanbanDemo.showPill = function(pillId, text) {
var $pill = $('#' + pillId);
$pill.text(text).addClass('visible');
setTimeout(function() {
$pill.removeClass('visible');
}, 3000);
};
KanbanDemo.hidePill = function(pillId) {
$('#' + pillId).removeClass('visible').text('');
};
KanbanDemo.assertItemId = function() {
var id = KanbanDemo.itemId();
if (!id) {
KanbanDemo.notifyError('No ID', 'Click a task or type an ID');
return null;
}
return id;
};
KanbanDemo.onAddItem = function() {
var widget = KanbanDemo.widget();
var board = KanbanDemo.boardId();
if (!widget.findBoard(board)) {
KanbanDemo.notifyError('Board not found', 'Board "' + board + '" no longer exists');
return;
}
widget.addElement(board, {
id: 't-' + Date.now(),
title: 'New Task',
description: 'Added via client API'
});
KanbanDemo.notify('Item Added', 'New task added to ' + board);
};
KanbanDemo.onRemoveBoard = function() {
var widget = KanbanDemo.widget();
var board = KanbanDemo.boardId();
if (!widget.findBoard(board)) {
KanbanDemo.notifyWarn('Already removed', 'Board "' + board + '" does not exist');
return;
}
widget.removeBoard(board);
KanbanDemo.notifyWarn('Board Removed', 'Board removed: ' + board);
};
KanbanDemo.onCountItems = function() {
var widget = KanbanDemo.widget();
var board = KanbanDemo.boardId();
var boardEl = widget.findBoard(board);
var pillId = 'countResult';
KanbanDemo.hidePill(pillId);
if (!boardEl) {
KanbanDemo.notifyError('Board not found', 'Board "' + board + '" no longer exists');
return;
}
var count = boardEl.querySelectorAll('.kanban-item').length;
KanbanDemo.flashBoard(boardEl);
KanbanDemo.showPill(pillId, count + ' items');
KanbanDemo.notify(count + ' items', 'Board "' + board + '" has ' + count + ' items');
};
KanbanDemo.onFindItem = function() {
var widget = KanbanDemo.widget();
var id = KanbanDemo.assertItemId();
if (!id) return;
var itemEl = widget.findElement(id);
if (itemEl) {
KanbanDemo.flashItem(itemEl);
KanbanDemo.notify('Found', 'Item "' + id + '" highlighted');
} else {
KanbanDemo.notifyError('Not found', 'No item with ID "' + id + '"');
}
};
KanbanDemo.onParentBoard = function() {
var widget = KanbanDemo.widget();
var id = KanbanDemo.assertItemId();
var pillId = 'parentResult';
KanbanDemo.hidePill(pillId);
if (!id) return;
var board = widget.getParentBoardID(id);
if (board) {
var boardEl = widget.findBoard(board);
if (boardEl) {
KanbanDemo.flashBoard(boardEl);
KanbanDemo.showPill(pillId, 'In: ' + board);
}
}
if (board) {
KanbanDemo.notify('Parent Board', 'Item "' + id + '" is in board "' + board + '"');
} else {
KanbanDemo.notifyError('Not found', 'Item "' + id + '" not found');
}
};
KanbanDemo.onReplaceItem = function() {
var widget = KanbanDemo.widget();
var id = KanbanDemo.assertItemId();
if (!id) return;
var itemEl = widget.findElement(id);
if (!itemEl) {
KanbanDemo.notifyError('Not found', 'No item with ID "' + id + '"');
return;
}
widget.replaceElement(id, {
title: 'Updated at ' + new Date().toLocaleTimeString()
});
KanbanDemo.notify('Replaced', 'Content replaced for "' + id + '"');
};
KanbanDemo.onHighlightBoard = function() {
var widget = KanbanDemo.widget();
var board = KanbanDemo.boardId();
var boardEl = widget.findBoard(board);
if (!boardEl) {
KanbanDemo.notifyError('Board not found', 'Board "' + board + '" no longer exists');
return;
}
KanbanDemo.flashBoard(boardEl);
KanbanDemo.notify('Board Found', 'Board "' + board + '" highlighted');
};
KanbanDemo.onItemClick = function(args) {
if (args.itemId) {
$(PrimeFaces.escapeClientId('formClientApi:itemIdInput')).val(args.itemId);
}
};
</h:outputScript>
</div>
package org.primefaces.extensions.showcase.controller;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.event.KanbanAddEvent;
import org.primefaces.extensions.event.KanbanDragEvent;
import org.primefaces.extensions.event.KanbanItemClickEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* KanbanController
*
* @author @jxmai
* @since 16.0.0
*/
@Named
@ViewScoped
public class KanbanController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private KanbanItem selectedItem;
private String selectedColumnId;
@PostConstruct
public void init() {
columns = new ArrayList<>();
// To Do column
KanbanColumn todoColumn = new KanbanColumn("todo", "To Do");
todoColumn.setCssClass("kanban-todo");
todoColumn.addItem(new KanbanItem("item1", "Add support for Kanban",
"Create a new Kanban component for PrimeFaces Extensions"));
todoColumn.addItem(new KanbanItem("item2", "Write documentation",
"Document the new component"));
// In Progress column
KanbanColumn inProgressColumn = new KanbanColumn("inprogress", "In Progress");
inProgressColumn.setCssClass("kanban-inprogress");
inProgressColumn.addItem(new KanbanItem("item3", "Design component",
"Design the Kanban board component"));
// Done column
KanbanColumn doneColumn = new KanbanColumn("done", "Done");
doneColumn.setCssClass("kanban-done");
doneColumn.addItem(new KanbanItem("item4", "Research libraries",
"Research available Kanban libraries"));
columns.add(todoColumn);
columns.add(inProgressColumn);
columns.add(doneColumn);
}
public void onDrop(KanbanDragEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Moved",
"Item " + event.getItemId() + " moved from " + event.getSourceColumnId()
+ " to " + event.getTargetColumnId() + " at position " + event.getNewPosition());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void onAddItem(KanbanAddEvent event) {
for (KanbanColumn column : columns) {
if (column.getId().equals(event.getColumnId())) {
int count = column.getItems().size() + 1;
column.addItem(new KanbanItem("new-" + count, "New Task " + count,
"Click to edit this task"));
break;
}
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Added",
"New item added to column " + event.getColumnId());
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void onItemClick(KanbanItemClickEvent event) {
String itemId = event.getItemId();
String columnId = event.getColumnId();
for (KanbanColumn col : columns) {
if (col.getId().equals(columnId)) {
for (KanbanItem item : col.getItems()) {
if (item.getId().equals(itemId)) {
this.selectedItem = item;
this.selectedColumnId = columnId;
break;
}
}
}
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Clicked",
"Loaded task: " + (selectedItem != null ? selectedItem.getTitle() : itemId));
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void saveItem() {
if (selectedItem != null) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Updated",
"Updated task: " + selectedItem.getTitle());
FacesContext.getCurrentInstance().addMessage(null, message);
}
}
public List<KanbanColumn> getColumns() {
return columns;
}
public void setColumns(List<KanbanColumn> columns) {
this.columns = columns;
}
public KanbanItem getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(KanbanItem selectedItem) {
this.selectedItem = selectedItem;
}
public String getSelectedColumnId() {
return selectedColumnId;
}
public void setSelectedColumnId(String selectedColumnId) {
this.selectedColumnId = selectedColumnId;
}
}
package org.primefaces.extensions.showcase.controller.kanban;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Named;
import org.primefaces.extensions.event.KanbanAddEvent;
import org.primefaces.extensions.event.KanbanItemRightClickEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* Controller for the Kanban right-click context menu showcase.
*/
@Named
@jakarta.faces.view.ViewScoped
public class KanbanRightClickController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private KanbanItem selectedItem;
private String selectedColumnId;
@PostConstruct
public void init() {
columns = new ArrayList<>();
KanbanColumn todo = new KanbanColumn();
todo.setId("todo");
todo.setTitle("To Do");
todo.setCssClass("kanban-todo");
todo.getItems().addAll(List.of(
new KanbanItem("task-1", "Design new landing page", "Create wireframes and mockups for the new product landing page"),
new KanbanItem("task-2", "Research competitor features", "Analyze top 3 competitor products and summarize feature differences"),
new KanbanItem("task-3", "Write unit tests", "Increase code coverage to 80% for the payment module")));
columns.add(todo);
KanbanColumn inprogress = new KanbanColumn();
inprogress.setId("inprogress");
inprogress.setTitle("In Progress");
inprogress.setCssClass("kanban-inprogress");
inprogress.getItems().addAll(List.of(
new KanbanItem("task-4", "Implement OAuth integration", "Add Google and GitHub OAuth providers"),
new KanbanItem("task-5", "API rate limiting", "Implement rate limiter for public API endpoints")));
columns.add(inprogress);
KanbanColumn done = new KanbanColumn();
done.setId("done");
done.setTitle("Done");
done.setCssClass("kanban-done");
done.getItems().addAll(List.of(
new KanbanItem("task-6", "Setup CI/CD pipeline", "Configure GitHub Actions for automated builds and deployment"),
new KanbanItem("task-7", "Database migration script", "Write migration scripts for schema updates")));
columns.add(done);
}
public List<KanbanColumn> getColumns() {
return columns;
}
public KanbanItem getSelectedItem() {
return selectedItem;
}
public String getSelectedColumnId() {
return selectedColumnId;
}
public void onItemRightClick(final KanbanItemRightClickEvent event) {
String itemId = event.getItemId();
String columnId = event.getColumnId();
for (KanbanColumn col : columns) {
if (col.getId().equals(columnId)) {
for (KanbanItem item : col.getItems()) {
if (item.getId().equals(itemId)) {
this.selectedItem = item;
this.selectedColumnId = columnId;
break;
}
}
}
}
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Right-clicked",
"Item: " + (selectedItem != null ? selectedItem.getTitle() : itemId) + ", Column: " + columnId));
}
public void onItemAdd(final KanbanAddEvent event) {
String columnId = event.getColumnId();
for (KanbanColumn column : columns) {
if (column.getId().equals(columnId)) {
int count = column.getItems().size() + 1;
column.addItem(new KanbanItem(columnId + "-new-" + count, "New Task " + count,
"Click to edit this task"));
break;
}
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Added",
"New item added to column " + columnId);
FacesContext.getCurrentInstance().addMessage(null, message);
}
public void deleteSelectedItem() {
if (selectedItem != null) {
for (KanbanColumn col : columns) {
col.getItems().removeIf(item -> item.getId().equals(selectedItem.getId()));
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Item Deleted",
"Deleted task: " + selectedItem.getTitle());
FacesContext.getCurrentInstance().addMessage(null, message);
selectedItem = null;
selectedColumnId = null;
}
}
}
package org.primefaces.extensions.showcase.controller.kanban;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import org.primefaces.extensions.event.KanbanItemClickEvent;
import org.primefaces.extensions.model.kanban.KanbanColumn;
import org.primefaces.extensions.model.kanban.KanbanItem;
/**
* Controller for the Kanban client API showcase.
*/
@Named
@ViewScoped
public class KanbanClientApiController implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private List<KanbanColumn> columns;
private String lastClickedItemId;
private String lastClickedItem;
@PostConstruct
public void init() {
columns = new ArrayList<>();
KanbanColumn backlog = new KanbanColumn();
backlog.setId("backlog");
backlog.setTitle("Backlog");
backlog.setCssClass("kanban-backlog");
backlog.getItems().addAll(List.of(
new KanbanItem("t-1", "Research authentication", "Evaluate OAuth"),
new KanbanItem("t-2", "Design database schema", "Create ERD")));
columns.add(backlog);
KanbanColumn inprogress = new KanbanColumn();
inprogress.setId("inprogress");
inprogress.setTitle("In Progress");
inprogress.setCssClass("kanban-inprogress");
inprogress.getItems().addAll(List.of(
new KanbanItem("t-3", "Implement REST API", "Build CRUD endpoints"),
new KanbanItem("t-4", "Add validation", "Server-side validation")));
columns.add(inprogress);
KanbanColumn done = new KanbanColumn();
done.setId("done");
done.setTitle("Done");
done.setCssClass("kanban-done");
done.getItems().addAll(List.of(
new KanbanItem("t-5", "Setup CI/CD", "GitHub Actions"),
new KanbanItem("t-6", "Write tests", "80% coverage")));
columns.add(done);
}
public List<KanbanColumn> getColumns() {
return columns;
}
public String getLastClickedItemId() {
return lastClickedItemId;
}
public String getLastClickedItem() {
return lastClickedItem;
}
public void onItemClick(final KanbanItemClickEvent event) {
this.lastClickedItemId = event.getItemId();
String title = resolveItemTitle(lastClickedItemId, event.getColumnId());
this.lastClickedItem = title != null ? title : lastClickedItemId;
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Item Clicked", "Task: " + lastClickedItem));
org.primefaces.PrimeFaces.current().ajax().addCallbackParam("itemId", lastClickedItemId);
}
public void onClientApiCommand() {
// no-op: used by p:remoteCommand to complete the AJAX cycle
}
private String resolveItemTitle(final String itemId, final String columnId) {
for (KanbanColumn col : columns) {
if (col.getId().equals(columnId)) {
for (KanbanItem item : col.getItems()) {
if (item.getId().equals(itemId)) {
return item.getTitle();
}
}
}
}
return null;
}
}