I'm in the process of converting a JavaFX application from declaring/configuring its controls in Java code to splitting out the layout to an FXML config. The problem I'm having is that I can't locate the equivalent attribute (?) to the code's ChangeListener.
In the original Java code, I have
class TextFieldChangeListener implements ChangeListener<String> {
private boolean isRequiredDataPresent() {
return outputNameTextField.getText().length() > 0 && numOfOutputFilesTextField.getText().length() > 0;
}
#Override
public void changed( ObservableValue<? extends String> observableValue, String s, String s2 ) {
mergeButton.setDisable( ! isRequiredDataPresent() );
}
}
About the closest I can get using FXML is:
<TextField id="outputNameTextField" onKeyPressed="#textBoxOnChange" promptText="Path of merge file" GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.columnSpan="2" GridPane.rowSpan="1" />
The problem with using onKeyPressed is that it doesn't pickup pasted in values like ChangeListener does. How do I add a change listener in FXML?
You can not do that because the value property is a sub-part of TextField. So you have to write it in your code. FXML comes only for the graphical aspects.
For more information about FXML :
http://docs.oracle.com/javafx/2/get_started/fxml_tutorial.htm
http://docs.oracle.com/javafx/2/fxml_get_started/jfxpub-fxml_get_started.htm
Related
I'm trying to do a conditional styling on a JSF application and trying to implement it as follows
<h:inputText id="contact_phone2" value="#{jobs_Builder.selected.contactPhone2}" title="#{lbl.ContactPhone2}" binding="#{phoneNumberValidator.myComponent}" style="#{phoneNumberValidator.valid ? 'border-color:black;' : 'border-color:red;'}"/>
In the PhoneNumberValidator.java class I have the bellow code
private UIComponent myComponent;
public UIComponent getMyComponent() {
return myComponent;
}
public void setMyComponent(UIComponent myComponent) {
this.myComponent = myComponent;
}
public boolean isValid(){
UIInput input = (UIInput) myComponent;
String value = input.toString();
return value != null && value.length() > 2;
}
Note that the validation logic is dummy one and not the correct one.
When I debug this, the validation code is hit only when the page is initially loading, so this is not working in runtime. The expected behavior is to have this in the runtime so whenever user types something, the validation logic should trigger, and based on that the styling should be changed.
I've replaced the f:ajax tag with an homemade solution that doesn't put inline script. It works wonder for actionButton. However I cannot make it work for a listener on a panelGroup. The reason is that it is specified nowhere what the bean target method resulting from the ajax request should be. In other words with a commandButton I can specify the target bean method in action, but there is no such attribute for panelGroup; as I don't want to use f:ajax listener, I want to replace it.
<h:commandButton data-widget="jsfajax" value="ajax" action="#{someAction}"/>
$(document).ready(function(){
(function(widgets){
document.body.addEventListener("click", function(e) {
var w = e.target.getAttribute("data-widget");
if(w){
e.preventDefault();
widgets[w](e.target);
}
});
})(new Widgets);
});
function Widgets(){
this.jsfajax = function jsfajax(elem){
if(elem.id == ""){
elem.id = elem.name;
}
mojarra.ab(elem,"click",'action','#form',0);
}
}
This works.
But this obviously doesn't (it does but it doesn't invoke anything) :
<h:panelGroup>
<f:passThroughAttribute name="data-widget" value="jsfajax"/>
Click here
</h:panelGroup>
But this does :
<h:panelGroup>
<f:ajax event="click" listener="#{someAction}"/>
Click here
</h:panelGroup>
Both those panelGroup result in the same HTML output, so I assume it's the jsf container which "remembers" the click on that panelGroup is linked to #{someAction}.
What I'd like to do is recreate that link without using f:ajax listener. At the moment I've to use an hidden commandButton which is less elegant.
So maybe a composite component panelGroup which would save the "action link", I've no idea.
What you want to achieve is only possible on UICommand components, not on ClientBehaviorHolder components. One solution would be to create a custom component extending HtmlCommandLink which renders a <div> instead of <a> and use it like so <your:div action="#{bean.action}">.
The most ideal solution would be to replace the standard renderers. E.g. for <h:panelGorup>:
<render-kit>
<renderer>
<component-family>javax.faces.Panel</component-family>
<renderer-type>javax.faces.Group</renderer-type>
<renderer-class>com.example.YourPanelGroupRenderer</renderer-class>
</renderer>
</render-kit>
Basically, those renderers should skip rendering <f:ajax>-related on* attributes and instead render your data-widget attribute (and preferably also other attributes representing existing <f:ajax> attributes such as execute, render, delay, etc). You should also program against the standard API, not the Mojarra-specific API. I.e. use jsf.ajax.request() directly instead of mojarra.ab() shortcut.
This way you can keep your view identical conform the JSF standards. You and future developers would this way not even need to learn/think about a "proprietary" API while writing JSF code. You just continue using <h:panelGroup><f:ajax>. You simply plug in the custom renders and script via a JAR in webapp and you're done. That JAR would even be reusable on all other existing JSF applications. It could even become popular, because inline scripts are indeed considered poor practice.
It's only quite some code and not necessarily trivial for a starter.
A different approach is to replace the standard response writer with a custom one wherein you override writeAttribute() and check if the attribute name starts with on and then handle them accordingly the way you had in mind. E.g. parsing it and writing a different attribute. Here's a kickoff example which also recognizes <h:panelGroup><f:ajax>.
public class NoInlineScriptRenderKitFactory extends RenderKitFactory {
private RenderKitFactory wrapped;
public NoInlineScriptRenderKitFactory(RenderKitFactory wrapped) {
this.wrapped = wrapped;
}
#Override
public void addRenderKit(String renderKitId, RenderKit renderKit) {
wrapped.addRenderKit(renderKitId, renderKit);
}
#Override
public RenderKit getRenderKit(FacesContext context, String renderKitId) {
RenderKit renderKit = wrapped.getRenderKit(context, renderKitId);
return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new NoInlineScriptRenderKit(renderKit) : renderKit;
}
#Override
public Iterator<String> getRenderKitIds() {
return wrapped.getRenderKitIds();
}
}
public class NoInlineScriptRenderKit extends RenderKitWrapper {
private RenderKit wrapped;
public NoInlineScriptRenderKit(RenderKit wrapped) {
this.wrapped = wrapped;
}
#Override
public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) {
return new NoInlineScriptResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding));
}
#Override
public RenderKit getWrapped() {
return wrapped;
}
}
public class NoInlineScriptResponseWriter extends ResponseWriterWrapper {
private ResponseWriter wrapped;
public NoInlineScriptResponseWriter(ResponseWriter wrapped) {
this.wrapped = wrapped;
}
#Override
public ResponseWriter cloneWithWriter(Writer writer) {
return new NoInlineScriptResponseWriter(super.cloneWithWriter(writer));
}
#Override
public void writeAttribute(String name, Object value, String property) throws IOException {
if (name.startsWith("on")) {
if (value != null && value.toString().startsWith("mojarra.ab(")) {
super.writeAttribute("data-widget", "jsfajax", property);
}
}
else {
super.writeAttribute(name, value, property);
}
}
#Override
public ResponseWriter getWrapped() {
return wrapped;
}
}
The most important part where you have your freedom is the writeAttribute() method in the last snippet. The above kickoff example just blindly checks if the on* attribute value starts with Mojarra-specific "mojarra.ab(" and then instead writes your data-widget="jsfajax". In other words, every single (naturally used!) <f:ajax> will be rewritten this way. You can continue using <h:commandLink><f:ajax> and <h:panelGroup><f:ajax> the natural way. Don't forget to deal with other <f:ajax> attributes while you're at it.
In order to get it to run, register as below in faces-config.xml:
<factory>
<render-kit-factory>com.example.NoInlineScriptRenderKitFactory</render-kit-factory>
</factory>
You only still need to take into account existing implementation-specific details (fortunately there are only two: Mojarra and MyFaces).
See also:
How do I determine the renderer of a built-in component
I have a problem when dynamically instantiating a PF 3.4.2 AutoComplete component.
The component intially renders ok, its value is refreshed on partial processing
but the suggestions are never displayed.
I am instantiating this control the following way :
AutoComplete ac = (AutoComplete) context.getApplication().createComponent(AutoComplete.COMPONENT_TYPE);
final String varName = "p";
ValueExpression ve = JSFUtils.createValueExpression("#{minContext.selected.sen}"), Sen.Type);
ac.setValueExpression("value", ve);
ac.setForceSelection(true);
ac.setVar(varName);
ValueExpression itemLabel = JSFUtils.createValueExpression("#{sc:senLibelle(p)}"), String.class);
ac.setValueExpression("itemLabel", itemLabel);
ValueExpression itemValue = JSFUtils.createValueExpression("#{" + varName + "}");
ac.setValueExpression("itemValue", itemValue);
MethodExpression completeMethod = JSFUtils.createMethodExpression("#{senUtils.completeAllSens}", List.class,new Class[]{String.class});
ac.setCompleteMethod(completeMethod);
then adding it to parent control using
getChildrens().add(ac);
The parent component is a derivation of PF PanelGrid. I use this approach successfully to generate various edition panels and it works like a charm. But I can not figure why it does not with autoComplete.
The parent control looks like :
#FacesComponent(SenatDataTableEntryDetail.SENAT_COMPONENT_TYPE)
public class SenatDataTableEntryDetail extends PanelGrid {
/** Leaving renderer unchanged, so that PF renderer for PanelGrid is used.
*/
public static final String SENAT_COMPONENT_FAMILY = "fr.senat.faces.components";
public static final String SENAT_COMPONENT_TYPE = SENAT_COMPONENT_FAMILY + ".SenatDataTableEntryDetail";
private enum PropertyKeys { mapper, bean; }
#Override
public void encodeBegin(FacesContext context) throws IOException {
super.encodeBegin(context);
addDynamicChildren(context);
}
#Override
public boolean getRendersChildren()
{
return true;
}
...
private Boolean isInitialized() {
return (Boolean)getStateHelper().eval(SENAT_INITIALIZED,false);
}
private void setInitialized(Boolean param) {
getStateHelper().put(SENAT_INITIALIZED, param);
}
private void addDynamicChildren(FacesContext context) throws IOException {
if(isInitialized()) {
return;
}
setInitialized(true);
/* components are instiated and added as children only once */
}
}
It just adds children to the panel grid.
The other aspects of custom component declaration (in taglib and so on) are ok.
The problem doest not seem to be in EL expressions, completeMethod definition, etc. If I include in my test xhtml page an instanciation of the p:autoComplete with the very same parameters, it just works as expected :
<p:autoComplete value="#{minContext.selected.sen}" forceSelection="true"
var="p" itemLabel="#{sc:senLibelle(p)}" itemValue="#{p}"
completeMethod="#{senUtils.completeAllSens}"/>
I noticed that the PF AutoComplete component is a bit special as it renders differently
when a query is detected. See AutoCompleteRenderer source code in http://primefaces.googlecode.com/files/primefaces-3.4.2.zip .
In the "dynamically instantiated" case, the decode method of this component is not called. I failed to find why those last days, but did not succeed.
I look forward for your suggestions on what to check to correct this annoying "bug".
So, the problem was in id generation (see the two comments).
The beginning of component instantiation becomes :
AutoComplete ac = (AutoComplete) context.getApplication().createComponent(AutoComplete.COMPONENT_TYPE);
ac.setParent(this);
ac.setId(...some application specific unique id generation...);
final String varName = "p";
This way, the naming container is properly taken in account on client id generation.
I asked about pass through attributes in a different question and found I could create a custom renderer for the <p:autocomplete> component but the problem is my custom renderer would be used for every p:autocomplete in my project (site-wide). Therefore I have elected to create a custom component which extends org.primefaces.component.autocomplete.AutoComplete and adds the necessary attributes to the text box.
My initial thought was to add a constructor but it doesn't seem to work because the attribute map is null at this point:
#FacesComponent("com.mycomponents.SiteSearch")
public class SiteSearch extends AutoComplete {
public SiteSearch() {
Map<String,Object> attrs = getAttributes();
attrs.put("x-webkit-speech", null);
attrs.put("x-webkit-grammer", "builtin:search");
attrs.put("onwebkitspeechchange", "this.form.submit();");
attrs.put("placeholder", "Enter a Search Term");
}
}
My other thought was leave this custom component empty (empty class) and then specify a custom renderer that extends org.primefaces.component.autocomplete.AutoCompleteRenderer and modify the attributes there.
After all is said and done, I just need a way to keep these attributes separate to this one text box so just putting a custom renderer on the p:autoComplete is not going to work (unless maybe I can use renderType= attribute for this one p:autoComplete?).
If you need a specific component which uses a different renderer than <p:autoComplete> then you really can't go around creating a custom component with its own family and component type. You can still just extend the PrimeFaces AutoComplete (and its renderer) to save some boilerplate code.
In the custom component, you need to provide getters for those attributes. You could as good specify setters as well, this way you can always override the default values from in the view side. Those getters/setters should in turn delegate to StateHelper.
There's only a little problem with x-webkit-* attributes. The - is an illegal character in Java identifiers. So you have to rename the getters/setters and change the renderer somewhat as the standard renderer relies on the component property name being exactly the same as the tag attribute name. Update: I understand that x-webkit-speech should just be rendered as is (so, no getter/setter necessary) and that x-webkit-grammer is actually a typo, it should be x-webkit-grammar.
Here's how the SiteSearch component can look like:
#FacesComponent(SiteSearch.COMPONENT_TYPE)
public class SiteSearch extends AutoComplete {
public static final String COMPONENT_FAMILY = "com.example";
public static final String COMPONENT_TYPE = "com.example.SiteSearch";
private enum PropertyKeys {
grammar, onspeechchange, placeholder
}
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public String getRendererType() {
return SiteSearchRenderer.RENDERER_TYPE;
}
public String getGrammar() {
return (String) getStateHelper().eval(PropertyKeys.grammar, "builtin:search");
}
public void setGrammar(String grammar) {
getStateHelper().put(PropertyKeys.grammar, grammar);
}
public String getOnspeechchange() {
return (String) getStateHelper().eval(PropertyKeys.onspeechchange, "submit()");
}
public void setOnspeechchange(String onspeechchange) {
getStateHelper().put(PropertyKeys.onspeechchange, onspeechchange);
}
public String getPlaceholder() {
return (String) getStateHelper().eval(PropertyKeys.placeholder, "Enter a Search Term");
}
public void setPlaceholder(String placeholder) {
getStateHelper().put(PropertyKeys.placeholder, placeholder);
}
}
Please note that the getters have all default values specified. If the eval() returns null, then the default value will be returned instead. I have also neutralized the attribute names somewhat so that it can be reused for any future non-webkit browsers by just modifying the renderer accordingly.
And here's how the SiteSearchRenderer renderer should look like for the above component:
#FacesRenderer(
componentFamily=SiteSearch.COMPONENT_FAMILY,
rendererType=SiteSearchRenderer.RENDERER_TYPE
)
public class SiteSearchRenderer extends AutoCompleteRenderer {
public static final String RENDERER_TYPE = "com.example.SiteSearchRenderer";
#Override
protected void renderPassThruAttributes(FacesContext facesContext, UIComponent component, String[] attrs) throws IOException {
ResponseWriter writer = facesContext.getResponseWriter();
writer.writeAttribute("x-webkit-speech", "x-webkit-speech", null);
writer.writeAttribute("x-webkit-grammar", component.getAttributes().get("grammar"), "grammar");
writer.writeAttribute("onwebkitspeechchange", component.getAttributes().get("onspeechchange"), "onspeechchange");
writer.writeAttribute("placeholder", component.getAttributes().get("placeholder"), "placeholder");
super.renderPassThruAttributes(facesContext, component, attrs);
}
}
To use it in the view, we of course need to register it as a tag. Create a /WEB-INF/my.taglib.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
version="2.0"
>
<namespace>http://example.com/ui</namespace>
<tag>
<tag-name>siteSearch</tag-name>
<component>
<component-type>com.example.SiteSearch</component-type>
<renderer-type>com.example.SiteSearchRenderer</renderer-type>
</component>
</tag>
</facelet-taglib>
Note that you don't need a <renderer> in faces-config.xml for this anymore. The #FacesRenderer annotation can just do its job on real custom components. So remove the <renderer> entry in faces-config.xml which you created based on your previous question.
Now tell JSF that you've got a custom taglib by the following context param in web.xml:
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/my.taglib.xml</param-value>
</context-param>
Finally you can use it as follows:
<html ... xmlns:my="http://example.com/ui">
...
<my:siteSearch />
You can even specify additional attributes which will override the defaults set in the component:
<my:siteSearch grammar="builtin:language" onspeechchange="alert('peek-a-boo')" placeholder="Search" />
For IDE autocomplete on attributes, you'd need to specify every one as a separate <attribute> in the <tag> declaration in the my.taglib.xml.
I'm having a hard time with a selectManyCheckbox.
Basically what I am doing is loading a List of Categories in a selectManyCheckbox type controller (have done this either with a List or with a List with convertEntity). My problem is with the selected elements (value="#{cardListProvider.categoriesHolder.selectedCategories}"). After some reading I understand it also has to be a List, but what kind? And how can I set the selected categories? I'm not saving them in DB but I need to run some action in the bean with them!
Here's what I have:
<h:selectManyCheckbox id="supportCategoryCardFilter"
value="#{cardListProvider.categoriesHolder.selectedCategories}" styleClass="greyText" required="false" >
<s:selectItems var="filterList" value="#{cardListProvider.categoriesList}" label="#{filterList.label}" />
<a:support id="supportCategoryCardFilter2" event="onchange"
reRender="someHolder, categoriesPanel" eventsQueue="onchange" action="#{cardListProvider.findCards(cardListProvider.categoriesHolder.selectedCategories)}" />
</h:selectManyCheckbox>
I have wasted several hours with this... Can anyone help me?
Thank you
You can bind to a String[] array like so:
public class CheckSelector {
private String[] chosen;
public String[] getChosen() { return chosen; }
public void setChosen(String[] chosen) { this.chosen = chosen; }
public SelectItem[] getChoices() {
return new SelectItem[] { new SelectItem("1"), new SelectItem("2"),
new SelectItem("3") };
}
}
The value of the selectManyCheckbox should be bound to chosen. Alternatively, you can use a List:
public class CheckSelector {
private List<String> chosen;
public List<String> getChosen() { return chosen; }
public void setChosen(List<String> chosen) { this.chosen = chosen; }
public List<SelectItem> getChoices() {
return Arrays.asList(new SelectItem("1"), new SelectItem("2"),
new SelectItem("3"));
}
}
The exact rules for value support are listed in the javadoc:
If the component has an attached Converter, use it.
If not, look for a ValueExpression for value (if any). The ValueExpression must point to something that is:
An array of primitives (such as int[]). Look up the registered by-class Converter for this primitive type.
An array of objects (such as Integer[] or String[]). Look up the registered by-class Converter for the underlying element type.
A java.util.List. Assume that the element type is java.lang.String, so no conversion is required.
If for any reason a Converter cannot be found, assume the type to be a String array.
I see you are using Seam so no need to use Strings or any primitive type, you can bind directly to List. You just have to add another tag inside your selectManyCheckbox component which is and it will automatically do everything.
Better than doing all by yourself, check Seam documentation
http://docs.jboss.org/seam/2.2.0.GA/reference/en-US/html/controls.html#d0e28378