I want to 'update' a component with a new component in the bean.
XHTML
<composite:interface>
<composite:attribute name="rootKey" required="true" />
<composite:attribute name="id" required="true" />
</composite:interface>
<composite:implementation>
<rich:panel id="#{cc.attrs.id}" binding="#{myBean.customPanel}"/>
<a4j:jsFunction name="createPanels"
action="#{myBean.createPanels}"
render="#{cc.attrs.id}">
<a4j:param name="rootId" assignTo="#{myBean.rootId}"/>
<a4j:param name="rootKey" assignTo="#{myBean.rootKey}"/>
</a4j:jsFunction>
<script type="text/javascript">
/* <![CDATA[ */
jQuery(document).ready(function() {
createPanels('#{cc.attrs.id}','#{cc.attrs.rootKey}');
});
/*]]> */
</script>
</composite:implementation>
</ui:composition>
Bean
private UIPanel rootPanel;
public void setCustomPanel(UIPanel panel) {
rootPanel = panel;
}
public UIPanel getCustomPanel() {
return rootPanel;
}
public void createPanels() {
//try #1 : Adding new panels as children
rootPanel.getChildren().add((UIPanel)createPanels(rootId,rootKey));
//try #2 : A new Panel component
rootPanel = (UIPanel)createPanels(rootId,rootKey);
...
rootPanel.setId(rootId); // this ID is the same as the 'placeholder' panelId
FacesContext.getCurrentInstance().getPartialViewContext().getRenderIds().add(rootPanel.getId()); // the rerender should also already be done in the XHTML js function.
}
With the debugger i see the rootPanel changes into the new panel component, but not in the view.
What do i miss?
What i try in short: Generating dynamically components as children for the panel component in the xhtml view. The generation needs the 'rootKey' param for generating the right set.
Using:
JSF Mojarra 2.1.19
Richfaces
The solution for me was to rerender te parent, not the actual component.
Also adding the new element children as children to the bound component.
XHTML : Added a h:panelgroup around the place holder.
<h:panelGroup id="#{cc.attrs.id}_parent">
<rich:panel id="#{cc.attrs.id}" binding="#{myBean.panel}"/>
</h:panelGroup>
<a4j:jsFunction name="createPanels"
action="#{myBean.createPanels}"
render="#{cc.attrs.id}_parent">
<a4j:param name="rootId" assignTo="#{myBean.rootId}"/>
<a4j:param name="rootKey" assignTo="#{myBean.rootKey}"/>
</a4j:jsFunction>
BEAN : try #1 was the right one.
public void createPanels() {
UIPanel newPanel = (UIPanel)createPanels(rootId,RootKey);
if(newPanel.getChildCount() >= 1){
rootPanel.getChildren().addAll(newPanel.getChildren());
rootPanel.setStyleClass(newPanel.getStyleClass());
rootPanel.setHeader(newPanel.getHeader());
}
}
Related
Is it possible to add an Context Menu to an Element in an Diagram?
I was able to add the Context Menu to the whole diagram in the xhtml file, but the context menu should only be shown if the user do a right click on the elment.
I try to solve it programmatically but the context menu is not show.
Here is my code:
DefaultDiagramModel model = new DefaultDiagramModel();
model.setMaxConnections(-1);
Element element = new Element();
element.setData("someData");
element.setId("testId");
ContextMenu contextMenu = new ContextMenu();
DynamicMenuModel menuModel = new DynamicMenuModel();
DefaultMenuItem menuItem = new DefaultMenuItem("show Info");
menuModel.addElement(menuItem);
contextMenu.setModel(menuModel);
contextMenu.setFor(element.getId());
model.addElement(element);
I use Primefaces 7
I ran into the same needs. Instead of adding the ContextMenu in the Bean. I was able to get it working using <f:facet> in the xhtml file like the following:
Create an new Class to use as the Data
public class NodeElement implements Serializable {
private Long id;
private String title;
private String type;
//... other variables
// getter and setters and other methods
}
Example creating an Element
Element element = new Element(new NodeElement(1L, "Element Title", "type1"), "30em", "16em");
in diagram.xhtml
...
<h:form id="patent-form" enctype="multipart/form-data">
<p:growl id="msgs" showDetail="true" skipDetailIfEqualsSummary="true" />
<p:diagram id="sample-graph" value="#{diagramBasicView.model}" styleClass="ui-widget-content" var="el">
<f:facet name="element">
<h:panelGroup layout="block" rendered="#{el.type == 'type1'}" id="type1-node" styleClass="type1-node" ps:data-id="#{el.id}">
<h:panelGroup laybout="block" styleClass="graph-node-content" ps:data-id="#{el.id}">
<h:outputText ps:title="Node Title: #{el.title}" ps:data-id="#{el.id}" value="#{el.title.length() gt 45 ? el.title.substring(0, 45) : el.title}" styleClass="graph-node-title"/>
<!-- ... OTHER data (make sure to use h:outputText or other JSF tags) -->
</h:panelGroup>
</h:panelGroup>
...
</h:panelGroup>
<f:facet>
</p:diagram>
<!-- from https://stackoverflow.com/questions/35812682/jsf-primefaces-eventing-in-diagram-elements -->
<p:remoteCommand name="elementClicked" actionListener="#{diagramBasicView.onSelection()}"/>
<p:contextMenu for="type1-node" style="width: 220px; height: 150px;">
<p:menuitem value="Show Info"
update="sample-graph"
id="show-info-menu-item"/>
<p:menuitem value="Second Menu"
update="sample-graph"
id="second-menu-item"/>
</p:contextMenu>
</h:form>
<!-- use to get the id from the element as data attribute -->
<script>
$('.ui-diagram > .ui-diagram-element').contextmenu(function(info){
// console.log($(info.target).data('id'));
elementClicked([ {
name : 'elementId',
value : $(info.target).data('id')
} ]);
});
</script>
...
I am building a simple component in JSF (Mojarra 2.1.x) where I have to access parent ui components to update them, currently I'm using binding to achieve this, but it only works as long as I don't use the component more than once on the same page.
So I need a solution that would allow me to use the component multiple times on same page.
In the following code I'm updating commandButton with binding="#{msButton}" and panel with binding="#{msPanel}":
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:layout="http://sterz.stlrg.gv.at/jsf/layout"
xmlns:p="http://primefaces.org/ui">
<cc:interface>
<cc:attribute name="controller" required="true" />
<cc:attribute name="converter" required="true" />
</cc:interface>
<cc:implementation>
<p:commandButton id="msButton#{cc.attrs.controller.class.getSimpleName()}" binding="#{msButton}" value="#{msg.mehr} (#{cc.attrs.controller.itemList.size()})" type="button" />
<p:overlayPanel id="msOverlayPanel" for=":#{msButton.clientId}" hideEffect="fade" my="right top" at="right bottom">
<p:panel id="msPanel#{cc.attrs.controller.class.getSimpleName()}" binding="#{msPanel}" styleClass="ui-panel-fit">
<ui:repeat id="repeat" value="#{cc.attrs.controller.itemList}"
var="item">
<p:commandButton id="removeButton"
actionListener="#{cc.attrs.controller.removeItem(item)}"
icon="ui-icon-trash" update=":#{msPanel.clientId} :#{msButton.clientId}" ajax="true"
process="#this" disabled="#{cc.attrs.controller.itemList.size() == 1}"/>
<p:selectBooleanButton id="value1" value="#{item.exclude}"
offLabel="und" onLabel="und nicht" style="width:80px;">
<p:ajax event="change" process="#this" />
</p:selectBooleanButton>
<p:autoComplete converter="#{cc.attrs.converter}"
readonly="#{cc.attrs.readonly}" value="#{item.from}"
dropdown="true"
completeMethod="#{cc.attrs.controller.autocomplete}" var="gp"
itemLabel="#{gp.displayName}" itemValue="#{gp}">
<p:ajax event="itemSelect" process="#this" />
</p:autoComplete>
<h:outputText value=" #{msg.bis} " />
<p:autoComplete converter="#{cc.attrs.converter}"
readonly="#{cc.attrs.readonly}" value="#{item.to}" dropdown="true"
completeMethod="#{cc.attrs.controller.autocomplete}" var="gp"
itemLabel="#{gp.displayName}" itemValue="#{gp}">
<p:ajax event="itemSelect" process="#this" />
</p:autoComplete>
<br />
</ui:repeat>
<hr />
<p:commandButton id="addButton" actionListener="#{cc.attrs.controller.addItem}"
icon="ui-icon-plus" value="#{msg.zufuegen}" update="#parent :#{msButton.clientId}"
ajax="true" process="#this"/>
</p:panel>
</p:overlayPanel>
</cc:implementation>
</ui:component>
Any help is much apprecieted.
The solution is to use the faces component bean
#FacesComponent("com.xxx.MultiselectorIdComponent")
public class MultiselectorIdComponent extends UINamingContainer
{
UIComponent msPanel;
// getters/settter and other ui compunents
}
tell the component interface what faces component bean to use
<cc:interface componentType="com.xxx.MultiselectorIdComponent">
bind the JSF component to the one in the faces component bean
<p:panel binding="#{cc.msPanel}"/>
and to access the components, for example to update the component, we use the binding
<p:commandButton value="My Button" update=":#{cc.msPanel.clientId}"/>
ALSO:
A good practice is to use a parent container (like <div>) with the following ID
<div id="#{cc.clientId}">
Hope this helps,
Regards
You could target the components for update by their style class
<p:commandButton styleClass="msButton" ... />
<p:panel styleClass="msPanel" ... />
And I guess you update them from addButton, which would look like this
<p:commandButton id="addButton" update="#(.msButton, .msPanel)" ... />
It should have no problems working with many cc instances on the same page.
UPDATE
You can try with update="#composite", which should refresh the whole custom component. Or, the cumbersome update="#parent #parent:#parent:#parent:#child(1)" which should target panel and button respectively. Or update="#parent #parent:#parent:#previous", which should do the same. Take a look at chapter 4.3 in Primefaces User Guide for more examples and supported keywords.
I solve similar problem by common bean "bindingBean" stored in ViewContext. BindingBean holds all binded component in internal MAP of component records. The key of hash map is an EL expression - it is factory of the component. EL is used for components creating. Components are hold in records stored in MAP in bind bean.
Example:
<h:panel binding="#{bindBean.get('msPanelFactory.getPanel()').component}"/>
Record:
public class BindingRecord {
private Object component;
private String compExpr;
public BindingRecord(String compExpr) {
this.compExpr = compExpr;
}
public Object getComponent() {
if (component == null) {
component = getValueExprValue("#{" + compExpr + "}");
}
return component;
}
public void setComponent(Object component) {
this.component = component;
}
public Object getStoredComponent() {
return component;
}
}
Binding bean:
public class BindingBean {
private final Map<String, BindingRecord> bindingMap = new HashMap<>();
public BindingRecord get(String key) {
BindingRecord result = bindingMap.get(key);
if (result == null) {
result = new BindingRecord(key);
bindingMap.put(key, result);
}
return result;
}
}
In you case you can use:
<h:panel binding="#{bindingBean.get('panelFactory.create()').component}"/>
Inside composite component you can use trick - Pass component name by composite parameter:
<h:panel binding="#{bindingBean.get('panelFactory.create('.concate(cc.attrs.componentName).concate(')')).component}"/>
Using Primefaces I pass to a bean the id of the component that the user just clicked. I want the bean to change the style of this component. The relevant part of the bean:
public void passIdClicked() {
FacesContext context = FacesContext.getCurrentInstance();
Map map = context.getExternalContext().getRequestParameterMap();
String idClicked = (String) map.get("idClicked");
UIComponent tile = FindComponent.doFind(context, idClicked);
Iterator<UIComponent> it = tile.getChildren().iterator();
UIComponent comp = it.next();
if (comp.getId().equals(idClicked)) {
HtmlOutputText labelDone = (HtmlOutputText) comp;
labelDone.setValue("heyyyyyyyyy");
labelDone.setStyleClass("myStyleClass");
}
}
The modifications are not appearing on screen. What am I missing?
[EDIT] UI Code:
(additional components get created in the "tiles" panelGroup dynamically in an initialization phase. The passIdClicked method modifies one of these components - the one clicked by the user.)
<h:head>
</h:head>
<h:body>
<h:form id="form">
<h:commandButton actionListener="#{bean.createNewTile()}" title="new" value="new"/>
<p:remoteCommand name="sendNameClicked" actionListener="#{bean.passIdClicked}"/>
</h:form>
<h:panelGroup layout="block" id="tiles">
</h:panelGroup>
<script type="text/javascript">
$(document).ready(function() {
$('.tile').click(function() {
sendNameClicked([{
name: 'idClicked',
value: $(this).attr('id')
}]);
});
});
</script>
</h:body>
#kocko answer is absolutely correct, but since you are using Primefaces you can also do it programmatically:
RequestContext.getCurrentInstance().update(comp.getClientId());
You will have to refresh the component(s), so that the styleClass change takes place.
You can, either:
Refresh the whole page.
For example:
<f:ajax render="#all" />
Or
Refresh a particular component:
For example:
<p:someComponent id="componentId">
....
</p:someComponent>
...
<f:ajax render="componentId" />
composite component xhtml:
<composite:interface componentType="metroComponent">
<composite:attribute name="uniqueId" />
</composite:interface>
<composite:implementation>
<rich:panel width="100%" header="#{msgs['pages.metroEth.header2']}">
<table class="resData">
<tr>
<td class="labelLeft"><h:outputLabel id="optionLabelId"
value="#{msgs['pages.ccparams.serviceOption']}" /></td>
<td><h:inputText id="optionTextId"
binding="#{cc.serviceOption}" size="15" /> <h:message
for="ccvodTextId" style="color:red" /></td>
</table>
</rich:panel>
</composite:implementation>
</ui:composition>
component type implementation is as follow:
#FacesComponent (value="metroComponent")
public class HtmlMetroCC extends UIComponentBase implements NamingContainer {
UIInput serviceOption;
public UIInput getServiceOption() {
return serviceOption;
}
public void setServiceOption(UIInput serviceOption) {
this.serviceOption = serviceOption;
}
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
}
there is also a backing bean which prepare panelGroup element with list of presented above composite components. i removed getters/setters for better reading
#ManagedBean (name="metroethernetBean")
#RequestScoped
public class MetroEthernetMBean implements IBean{
private MetroEthCCData metroCCData;
private HtmlPanelGroup metroCCPanel;
private List<HtmlMetroCC> metroCClist;
#PostConstruct
public void initBean(){
metroCClist = new ArrayList<HtmlMetroCC>();
metroCCPanel = new HtmlPanelGroup();
HtmlMetroCC initialMetroCC = new HtmlMetroCC();
metroCClist.add(initialMetroCC);
processMetroCCPanel();
}
private void processMetroCCPanel(){
metroCCPanel.getChildren().clear();
for (HtmlMetroCC comp: metroCClist){
metroCCPanel.getChildren().add(comp);
}
}
}
page fragment responsible for displaying panelGroup looks like this:
<h:panelGroup id="metroCCPanelGrouId" binding="#{metroethernetBean.metroCCPanel}" />
the question is, why my composite components are not presented on panel? it looks panel has no children added at all..
when I put it as follows on page:
<gui:metroCC />
then composite comp. is displayed properly on page. What i want to achive is a panel on page with composite components which can be added or removed dynamically by clickin add new or delete selected
There is a wrokaround, instead of binding dynamically created panel in menaged bean
<h:panelGroup id="metroCCPanelGrouId" binding="#{metroethernetBean.metroCCPanel}" />
use mBean to initialize list/add new/ remove and loop through it on page:
<h:panelGroup id="metroCCPanelGrouId">
<ui:repeat var="test" value="#{metroethernetBean.metroCClist}">
<gui:metroCC binding="#{test}" />
</ui:repeat>
</h:panelGroup>
BUT: still don't understand why doing it with my first aproach fails...... BaluC, where are you ;)
I would like to ask some help in understanding a particular behaviour that JSF shows when Validation Phase
fails.
I'm using:
Tomcat 7.0.2
JSF 1.2_12
RichFaces 3.3.3
Problem description.
I wrote a form with 4 input fields: an inputText and 3 selectOneMenu. The inputText is required
while the selectOneMenus don't require any validation. Attached to the first selectOneMenu (row 32),
is an a4j:support tag so that, whenever the change event is fired, the list of items of the second
and the third selectOneMenu (row 44 and 58) are correctly filled. In particular, after loading
the two lists of items, the method forces the value of the second and the third selectOneMenu
to null. This mechanism seems to work fine until I submit the form without filling the input
text: as expected, JSF validation fails but when I change the value of the first selectOneMenu,
the page keeps displaying the values specified before JSF validation failed for the second and the
third selectOneMenu (note that the actionListener is still called and the values of the second and
the third selectOneMenu are still forced to null).
Since I'm using a simple PhaseListener, I noticed the following:
before JSF validation fails, every time I change the value of the first selectOneMenu, JSF life
cycle always calls the get method for the second and the third selectOneMenu during the Render
Response Phase. In this way, JSF is able to "see" that those values have been set to null during
the Invoke Application Phase.
After validation fails, JSF stops calling those getters when I change the value of the first
selectOneMenu.
Here is my view
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich">
<head>
<title>Prove Rich</title>
</head>
<body>
<h2>Prove Rich</h2>
<f:view>
<a4j:outputPanel ajaxRendered="true">
<h:messages style="color:red" />
</a4j:outputPanel>
<h:form>
<p>
Input required: <h:inputText value="#{provaProbReplyBean.inputRequired}" required="true" />
</p>
<p>
<h:outputText value="Scegli il canale:" />
<h:selectOneMenu value="#{provaProbReplyBean.canale}">
<f:selectItem itemLabel="--" itemValue="" />
<f:selectItem itemLabel="Profamily" itemValue="Profamily" />
<f:selectItem itemLabel="Captive" itemValue="Captive" />
<a4j:support event="onchange" action="#{provaProbReplyBean.caricaProcBanche}" ajaxSingle="true" reRender="procedure, banche" />
</h:selectOneMenu>
</p>
<p>
<h:outputText value="Scegli la procedura:" />
<h:selectOneMenu id="procedure" value="#{provaProbReplyBean.procedura}">
<f:selectItem itemLabel="--" itemValue="" />
<f:selectItems value="#{provaProbReplyBean.procedureList}" />
<!-- immediately save the current value -->
<a4j:support event="onchange" ajaxSingle="true" />
</h:selectOneMenu>
</p>
<p>
<h:outputText value="Scegli la banca:" />
<h:selectOneMenu id="banche" value="#{provaProbReplyBean.banca}">
<f:selectItem itemLabel="--" itemValue="" />
<f:selectItems value="#{provaProbReplyBean.bancheList}" />
<!-- immediately save the current value -->
<a4j:support event="onchange" ajaxSingle="true" />
</h:selectOneMenu>
</p>
<p><h:commandButton value="Submit" /></p>
</h:form>
</f:view>
</body>
</html>
And here is my model:
public class ProvaProbReply {
private String inputRequired;
private String canale;
private String procedura;
private String banca;
private Map<String, List<SelectItem>> canaliProc = new HashMap<String, List<SelectItem>>();
private Map<String, List<SelectItem>> canaliBanche = new HashMap<String, List<SelectItem>>();
private List<SelectItem> procedureList = new ArrayList<SelectItem>();
private List<SelectItem> bancheList = new ArrayList<SelectItem>();
public ProvaProbReply() {
List<SelectItem> l = new ArrayList<SelectItem>();
l.add(new SelectItem("Cessione del quinto"));
l.add(new SelectItem("Credito al consumo"));
l.add(new SelectItem("Mutui"));
canaliProc.put("Profamily", l);
l = new ArrayList<SelectItem>();
l.add(new SelectItem("Credito al consumo"));
canaliProc.put("Captive", l);
l = new ArrayList<SelectItem>();
canaliBanche.put("Profamily", l);
l = new ArrayList<SelectItem>();
l.add(new SelectItem("BDL"));
l.add(new SelectItem("BM"));
l.add(new SelectItem("BPM"));
l.add(new SelectItem("CRA"));
canaliBanche.put("Captive", l);
}
public String getInputRequired() {
return inputRequired;
}
public void setInputRequired(String ir) {
inputRequired = ir;
}
public String getCanale() {
return canale;
}
public void setCanale(String c) {
canale = c;
}
public String getProcedura() {
System.out.println("\ngetProcedura called\n");
return procedura;
}
public void setProcedura(String p) {
procedura = p;
}
public String getBanca() {
System.out.println("\ngetBanca called\n");
return banca;
}
public void setBanca(String b) {
banca = b;
}
public List<SelectItem> getProcedureList() {
return procedureList;
}
public List<SelectItem> getBancheList() {
return bancheList;
}
public String caricaProcBanche() {
System.out.println("\nListener called\n");
procedureList.clear();
bancheList.clear();
if(canale != null && !canale.equals("")) {
procedureList.addAll(canaliProc.get(canale));
bancheList.addAll(canaliBanche.get(canale));
}
System.out.println("BEFORE setting:\n");
System.out.println("\nProcedura: "+procedura+"\n");
System.out.println("Banca: "+banca+"\n");
procedura = null;
banca = null;
System.out.println("\n\n\nAFTER setting:\n");
System.out.println("\nProcedura: "+procedura+"\n");
System.out.println("Banca: "+banca+"\n");
return "";
}
}
It sounds like you are observing the expected behaviour of EditableValueHolder types.
If validation or conversion of types fails, the form will be rerendered in the state it was submitted. This is hinted at in the documentation for the Apply Request Values phase (JSF 2 spec):
At the end of this phase, all EditableValueHolder components in the component tree will have been updated with new submitted values included in this request (or enough data to reproduce incorrect input will have been stored, if there were conversion errors).
If conversion or validation fails, the Update Model Values phase will not be run (none of the setters will be called on the beans.) If the user just submitted a complex form, they wouldn't expect all the fields to be wiped because one was wrong. The renderers emit the value submitted by the user and do not call the getters (see isValid() and getSubmittedValue()).