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.
Related
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.
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;";
}
}
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.
I have one dataTable in my screen. In the column header there is a selectBooleanCheckBox. If I
select it then all the boolean check boxes are get selected and deselected if I deselect the header boolean check box. My datatable is binded is with htmlDatatable. In bean all values are being set but it is not reflected in UI. Kindly help....
<t:dataTable id="physicalAccessDetailsDataTable1" binding="#{bean.htmlDataTable}">
<f:facet name="header">
<h:selectBooleanCheckbox id="selectAll" value="#{bean.editablePhysical}"
valueChangeListener="#{bean.selectAllFromPhysicalDatatable}"
onclick="submit()" />
</f:facet>
here is my bean code...
public String selectAllFromPhysicalDatatable(ValueChangeEvent vcep) {
if (editablePhysical == false) {
System.out.println(editablePhysical);
editablePhysical = true;
for (PhysicalAccessDetail physicalAccessDetail : physicalAccessDetailsList) {
System.out.println("INside loop of true");
physicalAccessDetail.setEditable(true);
}
} else {
editablePhysical = false;
for (PhysicalAccessDetail physicalAccessDetail : physicalAccessDetailsList) {
physicalAccessDetail.setEditable(false);
}
}
return null;
}
I'm using PrimeFaces for a new project and it's quite an impressive set of components.
Anyway, I have problem with "real world" use of filedownload component.
In my page I have a datalist that shows the attachments related to a particular document, and I want provide a link to directly download that file inside the datalist item.
Here's my xhtml code:
<p:dataList id="ListaAllegati" value="#{documentBean.documento.allegati}" type="definition" var="attach" style="border: none" ">
<f:facet name="description">
<h:outputText value="#{attach.name}" />
<p:commandLink ajax="false" title="Download" action="#{documentBean.selectAttach}>
<h:graphicImage style="margin-left: 10px; border: none" value="./images/article.png" height="24" width="24" ></h:graphicImage>
<p:fileDownload value="#{documentBean.downloadFile}"/>
<f:setPropertyActionListener target="#{documentBean.selectedAttach}" value="#{attach}" />
</p:commandLink>
</f:facet>
</p:dataList>
and the relative java bean (request scoped):
private StreamedContent downloadFile;
public StreamedContent getDownloadFile() {
log.info("getter dell'allegato invocato");
InputStream stream = null;
byte[] rawFile = null;
if (selectedAttach == null) {
log.warn("Nessun allegato passato");
return null;
} else {
try {
log.info("Recupero del file " + selectedAttach.getGuid());
rawFile = attachManager.retrieveFile(selectedAttach.getGuid());
} catch (Exception e) {
String msg = "Errore durante il recupero del file";
log.error(msg, e);
FacesMessage fmsg = new FacesMessage(msg, "");
FacesContext.getCurrentInstance().addMessage(null, fmsg);
}
stream = new ByteArrayInputStream(rawFile);
DefaultStreamedContent file = new DefaultStreamedContent(stream,
selectedAttach.getMimeType(), selectedAttach.getName());
return file;
}
}
public void selectAttach() {
log.info("commandLink action invocata");
}
private Allegato selectedAttach;
public Allegato getSelectedAttach() {
return selectedAttach;
}
public void setSelectedAttach(Allegato selectedAttach) {
log.info("Allegato selezionato");
if (selectedAttach==null) log.warn("L'allegato passato รจ nullo");
this.selectedAttach = selectedAttach;
}
So, couple of question:
Am I doing the right thing trying to pass the selected attachment that way? Otherwise, how can I pass a parameter to tell the bean wich attachment has been clicked?
Why the first time I click the command link, nothing happen? It make a roundtrip with server, but nothing happens. Second time, it gives me an exception.
Why documentBean.selectAttach is never called and the documentBean.selectedAttach property is never set (neither the second time)?
Thanks to anyone for any hint
How to get the row object from the datatable is answered in this question:
How can I pass selected row to commandLink inside dataTable?
This answers basically all the three questions.
As to the exception in the second click, that's likely because you didn't return from the catch block when an exception is been thrown in your getDownloadFile() method. You're continuing the remnant of the code flow while the rawFile is still null. Fix it accordingly as well. Add a return null to the end of catch or something. Better yet, you should be posting the entire stacktrace in the question as you don't seem to be able to understand it. It basically already contains the answer :)
Primefaces has its own dedicated servlet for file download and upload components that handle all of this asynchronously.
Try doing something like what I have in my code
<p:commandLink ajax="false" actionListener="#{managedBean.downloadAction(object)}">
<span class="ui-icon icoFolderGo" style="padding-right: 1.5em;" />
<p:fileDownload value="#{managedBean.downloadContentProperty}" />
</p:commandLink>
And in the managed bean,
public void downloadAction(Object object) {
try {
InputStream stream = // get input stream from argument
this.setDownloadContentProperty(new DefaultStreamedContent(stream, "application/pdf", "filename.pdf");
} catch (Exception e) {
log.error(e);
}
}
public void setDownloadContentProperty(StreamedContent downloadContentProperty) {
this.downloadContentProperty = downloadContentProperty;
}
public StreamedContent getDownloadContentProperty() {
return downloadContentProperty;
}