Primefaces Tree node is draggable to children - jsf

I have a PrimeFaces (6.2.5) tree, where I have enabled draggable and droppable. I have a few issues with this, and have implemented the drag and drop manually with jquery in stead, while I figure this out;
<div class="plantree" id="realplantree">
<p:tree id="plantree" value="#{curriculumngTreeFacade.root}" var="node" dynamic="false" selectionMode="single" selection="#{curriculumngTreeFacade.selectedNode}" draggable="true" droppable="true">
<p:ajax event="select" listener="#{curriculumngTreeFacade.onNodeSelect}" update="administration-form:curriculummain" oncomplete="initDNDPlan();"/>
<p:ajax event="expand" listener="#{curriculumngTreeFacade.onNodeExpand}" />
<p:ajax event="collapse" listener="#{curriculumngTreeFacade.onNodeCollapse}" />
<p:ajax event="dragdrop" listener="#{curriculumngTreeFacade.onDragDrop}"/>
<p:treeNode expandedIcon="ui-icon ui-icon-folder-open" collapsedIcon="ui-icon ui-icon-folder-collapsed">
<h:outputText value="#{node}"/>
</p:treeNode>
</p:tree>
</div>
My issues are:
I have not added an "update='plantree'" variable to the p:ajax event
for dragdrop. Even still, if I try and drag a parent category to a
sub category, it breaks my tree, because the UI tries to update. I
actually run a check for parent / child connection in the backend
code, and I dont update the tree if that's the case, but in this
case, my method in the backing bean is not even called before the
tree explodes. It does the very same on the PrimeFaces Showcase
Backing code:
public void onDragDrop(TreeDragDropEvent event) throws Exception {
TreeNode dragNode = event.getDragNode();
TreeNode dropNode = event.getDropNode();
int dropIndex = event.getDropIndex();
if (dragNode.getData() instanceof CurriculumCategoryMetaModel && dropNode.getData() instanceof CurriculumCategoryMetaModel) {
CurriculumCategoryMetaModel drop = (CurriculumCategoryMetaModel) dropNode.getData();
CurriculumCategoryMetaModel drag = (CurriculumCategoryMetaModel) dragNode.getData();
if (drop.getType() == drag.getType() && drag.getClassid() > 0) {
drag.setParent(drop);
CurriculumBuilderProvider provider = CurriculumBuilderProvider.getProvider(drop.getType());
provider.saveCategory(drag, UserSessionFacade.getUserLocale());
this.initialize();
expandCategory(root, drop.getClassid(), drop.getType());
} else {
this.initialize();
}
}
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Dragged " + dragNode.getData(), "Dropped on " + dropNode.getData() + " at " + dropIndex);
FacesContext.getCurrentInstance().addMessage(null, message);
List<String> updates = new ArrayList<>();
updates.add("administration-form:curriculummenu");
updates.add("administration-form:growl");
PrimeFaces.current().ajax().update(updates);
}
private boolean dragIsParentOfDrop(CurriculumCategoryMetaModel drop, CurriculumCategoryMetaModel drag) {
CurriculumCategoryMetaModel parent = drop.getParent();
while (parent != null) {
if (parent.getClassid() == drag.getClassid()) {
return true;
} else {
parent = parent.getParent();
}
}
return false;
}
Is there a way to add accepted nodes that you can drop into. I have 2
root nodes, for different categories, and it is not allowed to drag
and drop between them. In fact, I dont even want the root nodes to be
draggable. On the picture, the "Learning paths" and "Ad hoc" are not supposed to be draggable, since they're only containers for the categories underneath.

Related

Primefaces update fails after removing component in a model

My goal is to create a panel where I can drag and drop elements of two different types: button and textfield. All of the elements are stored in a TreeMap and are iterated over with JSTL <c:forEach> tag. I can add extra components and remove the most recently selected ones thorough dedicated p:commandXxx fields outside of the viewPanel. Once draggable components are in the viewPanel, I can drag and drop them. Through javascript, I'm appending top and left coordinates of a particular component before the drop action to update component's location is invoked. The delete commandButton should remove the selected element from the list and trigger the update of the viewPanel.
Problem: Deleting any component except the most recently added fails to update the viewPanel resulting in the following exception:
Severe: Error Rendering View[/developer/testDr.xhtml]
org.primefaces.expression.ComponentNotFoundException: Cannot find >component for expression "button-" referenced from "defStep-10:j_idt13"..
It appears that in render time draggable's for attribute evaluates #{child.idstepNode} to null. Does anyone know why this happens, or how to circumvent this?
Interestingly, when Delete button is clicked again or the whole page is refreshed, viewPanel renders without a hitch.
<h:form id="viewPanel" class="default-step droppable" style="width:500px;height:500px;background:green;">
<p:droppable for="viewPanel" tolerance="touch" activeStyleClass="ui-state-highlight" >
<p:ajax listener="#{stepUtility.onDrop}" update="viewPanel" />
</p:droppable>
<c:forEach items="${stepUtility.stepNodesMap}" var="child">
<c:if test="${child.value.elementType == 'BUTTON'}">
<p:outputLabel id="button-${child.value.idstepNode}" style="top:${child.value.top};left:${child.value.left};
background-color: beige;position:absolute;" value="button ${child.value.elementValue}"/>
<p:draggable for="button-${child.value.idstepNode}"/>
</c:if>
<c:if test="${child.value.elementType == 'TEXT'}">
<p:outputLabel id="text-${child.value.idstepNode}" style="top:${child.value.top};left:${child.value.left};
background-color: beige;position:absolute;" value="text ${child.value.elementValue}"/>
<p:draggable for="text-${child.value.idstepNode}" />
</c:if>
</c:forEach>
</h:form>
//...
<p:commandButton value="Delete" actionListener="#{stepUtility.removeStepNode()}"
update=":viewPanel"/>
stepUtility bean
#Named(value = "stepUtility")
#SessionScoped
public class StepUtility implements Serializable {
private Integer selecteNode;
public void setSelectedElement(Integer idstepNode) //'StepNode')
{
selecteNode=idstepNode;
}
public void addNewComp(String typeNode)
{
if(stepNodesMap == null)
stepNodesMap = new TreeMap<>();
StepNodeSimple snode = new StepNodeSimple();
Integer id = 0;
if(stepNodesMap.size()>0)
{
Integer lastNodeId = ((StepNodeSimple) stepNodesMap.lastEntry().getValue()).idstepNode;
id = lastNodeId+1;
}
snode.idstepNode = id;
snode.elementType = typeNode;
snode.elementValue = typeNode +"value";
snode.left = "2px;";
snode.top = "2px;";
stepNodesMap.put(id, snode);
}
NavigableMap stepNodesMap;
public NavigableMap getStepNodesMap() {
return stepNodesMap;
}
public void setStepNodesMap(NavigableMap stepNodesMap) {
this.stepNodesMap = stepNodesMap;
}
public void removeStepNode()
{
if(selecteNode!=null)
{
stepNodesMap.remove(selecteNode);
}
}
public void onDrop(DragDropEvent dragDropEvent)
{
String dragId = dragDropEvent.getDragId();
Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
String left = params.get(dragId + "_left");
String top = params.get(dragId + "_top");
Integer idstepNode = Integer.parseInt(dragId.substring(dragId.lastIndexOf("-")+1));
setSelectedElement(idstepNode);
StepNodeSimple element = (StepNodeSimple) stepNodesMap.get(selecteNode);
element.left = left+"px;";
element.top = top+"px;";
}
}

How to Add Partial Selected Treenode into SelectedNodes in p:tree

I use tree component in PrimeFaces. In this component, selected nodes is added in selected nodes arrays automatically. However, I need to add partial selected nodes into this selected nodes array. What can I do in this situation, can you help me?
<p:tree id="treCard" value="#{authorizeBean.rootCard}" var="Folder" propagateSelectionUp="false" showUnselectableCheckbox="true" style="border:0px none;background:none; " selectionMode="checkbox" dynamic="true" selection="#{authorizeBean.selectedNodes}">
<p:treeNode class="authorizationPage" expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed" >
<h:outputText value="#{Folder.tag}">
</h:outputText>
</p:treeNode>
<p:treeNode class="authorizationPage" type="page" icon="ui-icon-document">
<h:outputText value="#{Folder.tag}" />
</p:treeNode>
<p:treeNode class="authorizationPage" type="tab" icon="fa fa-bars">
<h:outputText value="#{Folder.tag}" />
</p:treeNode>
<p:treeNode class="authorizationPage" type="button" icon="fa fa-square-o">
<h:outputText value="#{authorizeBean.btnName(Folder.tag)}" />
</p:treeNode>
</p:tree>
you have to create a root node and attach the part that you want to show to your root example create root : root= new DefaultTreeNode( new YourJavaClass(), null);
after that you lined it with your partiel tree : PartielTree = new DefaultTreeNode( new YourJavaClass(), root);
As primeface does not include partial selected node into selected nodes,
so below function is a hack to include partial selected nodes into the selected nodes and It must be called before using selectedNodes array everytime, else selectedNodes will not contain partially selected nodes. (calling everytime because primefaces updates this array on every request- pretty much ;) )
here 'selectedNodes' is a array of TreeNode where primeface framework will be storing selected nodes
public void updatePartialSelectedNodes() {
Set<TreeNode> allSelected = new HashSet<>();
if (selectedNodes != null && selectedNodes.length != 0) {
for (TreeNode sel : selectedNodes) {
allSelected.add(sel);
if (!((yourTreeDataObject)sel.getData()).isSupplier() && sel.getParent().isPartialSelected()) {
allSelected.add(sel.getParent());
}
}
}
selectedNodes = new TreeNode[allSelected.size()];
selectedNodes = allSelected.toArray(selectedNodes);
}
Edit1 : Other option and probably good one if the length of your tree in not greater than 100 nodes and not deeper than 2 level than You can iterate over the tree root node children and check using TreeNode.isSelected() or TreeNodes.isPartialSelected(), as the primefaces framework also update it in the main tree whether the node is selected or partial selected or not selected.
for a two level deep .. an example
for (TreeNode tn : this.treeRoot.getChildren()) {
for (TreeNode child : tn.getChildren()) {
if (child.isSelected()) {
selectionString.append(((TreeNodeObjectDataWrapper)child.getData()).getId());
}
}
}

Selected tree node not set when context menu action is invoked [duplicate]

This question already has an answer here:
What event should be used on a p:tree to select a tree node and have a context menu?
(1 answer)
Closed 6 years ago.
I'm creating a PrimeFaces (5.3) tree with a context menu. Selected nodes should be stored in #{myBean.selectedNode}. When I select a node using the left mouse button the correct node is set. But, when I try to run an action on a node from a context menu, without selecting it first, the correct node isn't set (the setter in my bean is not called).
I'm following the example in the PrimeFaces showcase, and I suppose I've got everything lined up. What am I doing wrong?
As you can see, in the PrimeFaces showcase you are able to immediately right click a node, click "View", and the growl will display the correct node.
Bean
I don't think the bean code is relevant (it is ViewScoped and there is a private TreeNode selectedNode with getter and setter).
Here are the interesting bits though:
public void onNodeSelect(NodeSelectEvent event) {
MyTreeNode myTreeNode = (MyTreeNode) event.getTreeNode();
myController.setSelected(myTreeNode.getEntity());
}
public void addChild(String name) {
MyTreeNode myTreeNode = (MyTreeNode) selectedNode;
MyTreeNode childNode = myTreeNode.addChild(name);
myController.setSelected(childNode.getEntity());
myController.insert();
}
XHTML
<h:form id="mainForm">
<p:tree value="#{myBean.root}" var="node"
id="myTree" dynamic="true"
selectionMode="single" selection="#{myBean.selectedNode}">
<p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed"
type="myType">
<h:outputText value="#{node}"/>
</p:treeNode>
<p:ajax event="select" listener="#{myBean.onNodeSelect}" />
</p:tree>
<p:contextMenu for="myTree">
<p:menuitem action="#{myBean.addChild('new')}"
value="Add"
process="#this"
update=":mainForm:myTree"/>
</p:contextMenu>
</h:form>
hy,
is a bug in primefaces.
You can fixed this issue by youself. For it you neccessary add verification in the primefaces js file which mouse button has been pressed. You can override file primefaces.js in your project, find first call function selectNode(d) and add follow checking:
if (e.which == 1) {
this.selectNode(d)
this.cursorNode = d;
}
This is part of code you can find then function nodeClick: function (e, a) is called:
nodeClick: function (e, a) {
PrimeFaces.clearSelection();
if ($(e.target).is(":not(.ui-tree-toggler)")) {
var d = a.parent();
if (this.cfg.onNodeClick) {
this.cfg.onNodeClick.call(this, d)
}
if (a.hasClass("ui-tree-selectable") && this.cfg.selectionMode) {
var c = this.isNodeSelected(d), f = e.metaKey || e.ctrlKey, b = e.shiftKey;
if (this.isCheckboxSelection()) {
this.toggleCheckboxNode(d)
} else {
if (c && f) {
this.unselectNode(d)
} else {
if (this.isSingleSelection() || (this.isMultipleSelection() && !f)) {
this.unselectAllNodes()
}
if (this.isMultipleSelection && b) {
} else {
if (e.which == 1) {
this.selectNode(d);
this.cursorNode = d;
}
}
}
}
}
}
}
It turns out you need the undocumented contextMenu Ajax event listener.
See:
What event should be used on a p:tree to select a tree node and have a context menu?

Refreshing PrimeFaces form element dataTable (code details provided)

I have Search form. After clicking on Search button data refreshes, but dataTable columns not (columns get refreshed only on second button click). Apparently I am doing something wrong. Dynamic column code is inspired by PrimeFaces Showcase
myForm.xhtml:
<h:form id="MY_FORM">
#{myBean.initBean()}
...
<h:panelGrid id="main">
<h:panelGrid id="buttons">
<p:commandButton id="submitSearch"
value="#{msg['button.execute']}"
actionListener="#{myBean.submitSearch}"
update="resultPanel"/>
</h:panelGrid>
</h:panelGrid>
<h:panelGrid id="resultPanel" border="0">
<p:dataTable id="resultTable" var="result" value="#{myBean.searchResults}">
<p:columns value="#{myBean.columns}" var="column" columnIndexVar="colIndex">
<f:facet name="header">
<h:outputText value="#{column.header}" />
</f:facet>
<h:outputText value="#{result[column.property]}" />
</p:columns>
</p:dataTable>
</h:panelGrid>
</h:form>
I've called column calculation method createColumns() from myBean.initBean() and myBean.submitSearch() with the same result (I see it works correctly from debugger).
#ManagedBean("myBean")
#Scope(value = "session")
public class MyBean {
private ArrayList<HashMap<String,Object>> searchResults;
private List<ColumnModel> columns;
...
public void initBean() {
...
createColumns(selectedDates);
}
public void submitSearch() {
...
ArrayList<HashMap<String, Object>> results = prpRepository.getSearchResultByParams(searchParams);
createColumns(selectedDates);
}
private void createColumns(ArrayList<String> selectedDates){
columns = new ArrayList<ColumnModel>();
columns.add(new ColumnModel("Name", "NAME"));
columns.add(new ColumnModel("Amount", "AMOUNT"));
for (int i = 0; i < selectedDates.size(); i++) {
columns.add(new ColumnModel(selectedDates.get(i), "DATE" + i));
}
setColumns(columns);
}
public List<ColumnModel> getColumns() {
return columns;
}
public void setColumns(List<ColumnModel> columns) {
this.columns = columns;
}
}
Additional information:
I am using:
Oracle's Implementation of the JSF 2.1 Specification
JavaServer Pages API 2.2
Springframework 3.1.2 (incl. spring-context, spring-web)
Glassfish Javax.annotation 3.1.1
The problem is that columns is calculted only once time in the org.primefaces.component.datatable.DataTabe
public List<UIColumn> getColumns() {
// if(columns == null) {
columns = new ArrayList<UIColumn>();
FacesContext context = getFacesContext();
char separator = UINamingContainer.getSeparatorChar(context);
for(UIComponent child : this.getChildren()) {
if(child instanceof Column) {
columns.add((UIColumn) child);
}
else if(child instanceof Columns) {
Columns uiColumns = (Columns) child;
String uiColumnsClientId = uiColumns.getClientId(context);
uiColumns.updateModel(); /* missed code */
for(int i=0; i < uiColumns.getRowCount(); i++) {
DynamicColumn dynaColumn = new DynamicColumn(i, uiColumns);
dynaColumn.setColumnKey(uiColumnsClientId + separator + i);
columns.add(dynaColumn);
}
}
}
// }
return columns;
}
You could comment it and overwrite the class file,
or find the DataTable object and setColumns to NULL inside the createColumns method
Finally I've worked better with debugger and found an answer. Solution was to put createColumns() method call into columns getter getColumns() as it is called automatically before submitSearch(). My problem was in application lifecycle understanding.

Unable to maintain JSF ViewState within a dataTable component

I'm working an inherited JSF app. Still quite new to it.
Here's a screenshot of the page I'm working with: http://goo.gl/GidpCD. This is some exam software and what you're looking at is question administration. On this screen the user can write the question, cite the question source, write answers, associate to a category and associate images.
Where I'm getting stuck is when I add/delete answers down toward the bottom.
I'm suspicious that my issue is with ViewState.
When I click the add or delete answers. The page reloads and I lose my file associations within a JSF dataTable component. ViewState is maintained in all of the other form elements on the page, it's just those files within that dataTable that I can't seem to get my head wrapped around. Here's that JSF dataTable component (I'll list the code in it's entirety below):
<h:dataTable id="table0" value="#{adminQuestions.filesList}" var="file" columnClasses="id,title,selected last" rendered="true" >
<h:column>
<f:facet name="header">File</f:facet>
<h:graphicImage value="#{file.path2}" alt="" />
</h:column>
<h:column>
<f:facet name="header">Title</f:facet>
<h:outputText value="#{file.title}" />
</h:column>
<h:column>
<f:facet name="header">Select files</f:facet>
<h:selectBooleanCheckbox value="#{file.selected}" />
</h:column>
</h:dataTable>
Here's the JSF button compnent to fire the add add answer method:
<h:commandButton value="#{msgs.addButton}" action="#{adminQuestions.addQuestionToAnswerRow}" />
And here's that add answer method:
public String addQuestionToAnswerRow() {
if(this.questionToAnswerList.size() < 8) {
// set the scroll
if(FacesContext.getCurrentInstance().getMessageList().size() > 0) {
this.scroll = false;
}
else {
this.scroll = true;
}
//Create a new answer, set the current question, and add it to the collection
Answers answer_local = new Answers();
QuestionToAnswer qta_local = new QuestionToAnswer();
answer_local.setTitle(this.inputTextAnswer);
answer_local.setDescription(this.inputTextAnswer);
answer_local.setCorrect(this.correctAnswer);
qta_local.setAnswers(answer_local);
qta_local.setAnswer(this.questionToAnswerList.size() + 1);
qta_local.setQuestions(this.question);
this.questionToAnswerList.add(qta_local);
// reset the input fields
this.inputTextAnswer = "";
this.correctAnswer = false;
List<File1> thisFilesList = getFilesList();
QuestionsToFiles qtf;
List<QuestionsToFiles> qtfc = new ArrayList<QuestionsToFiles>();
if(this.filesList != null) {
for(Iterator<File1> entries = this.filesList.iterator(); entries.hasNext();) {
File1 file_temp = entries.next();
if(file_temp.isSelected()) {
qtf = new QuestionsToFiles();
qtf.setQuestions(this.question);
qtf.setFile1(file_temp);
qtfc.add(qtf);
}
}
}
return null;
}
else {
JSFUtils.addErrorMessage("No more than eight answers are allowed: ", "The question cannot have more than eight answers");
return null;
}
}
Here's the JSF button component to fire the delete method
<h:commandButton value="#{msgs.deleteButton}" action="#{adminQuestions.deleteQuestionToAnswerRow(a)}" />
And the delete method:
public String deleteQuestionToAnswerRow(QuestionToAnswer qta_local) {
// set the scroll
if(FacesContext.getCurrentInstance().getMessageList().size() > 0) {
this.scroll = false;
}
else {
this.scroll = true;
}
//Iterate through the category exam collection and delete the associated category so it's reflected on the page
for (Iterator<QuestionToAnswer> itr = this.questionToAnswerList.iterator(); itr.hasNext();) {
QuestionToAnswer qta_temp = itr.next();
if(qta_temp == qta_local) {
qta_temp.setQuestions(null);
qta_temp.setAnswers(null);
itr.remove();
return null;
}
}
return null;
}
Here's a Gist the XHTML page in it's entirety
Here's a Gist the Java class in it's entirety
Thanks for taking a look.

Resources