Search code examples
javajavascriptcustom-componenttapestry

How do I create a custom text field in Tapestry5 that renders some Javascript onto the page?


I have been trying to create a custom textfield in tapestry which will render some javascript when it gains focus. But I have been having trouble trying to find an example of this.

Here is some of the code i have started off with:

package asc.components;

import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.ComponentDefaultProvider;


public class DahserTextField implements Field {

@Parameter (defaultPrefix = "literal")
private String label;
@Inject
private ComponentResources resources;
@Inject
private ComponentDefaultProvider defaultProvider;
@Parameter
private boolean disabled;
@Parameter
private boolean required;

String defaultLabel(){
    return defaultProvider.defaultLabel(resources);
}

public String getControlName() {
    return null;
}

public String getLabel() {
    return label;
}

public boolean isDisabled() {
    return disabled;
}

public boolean isRequired() {
    return required;
}

public String getClientId() {
    return resources.getId();
}


}

I have been unsure on what to do next. I do not know what to put into the .tml file. I would be grateful if anyone could help or point me in the right direction.


Solution

  • There is no need to replicate any of TextField's functionality in your own component, instead you should create a component mixin. Mixins are designed to add behaviour to existing components.

    From the Tapestry 5 docs:

    Tapestry 5 includes a radical feature, component mixins. Component mixins are a tricky concept; it basically allows a true component to be mixed together with special limited components called mixins. The component plus its mixins are represented as just a single tag in the component template, but all the behavior of all the elements.

    You would use the mixin like this:

    <input type="text" t:type="TextField" t:mixins="MyMixin" t:someParam="foo" />
    

    A mixin stub:

    @IncludeJavaScriptLibrary("MyMixin.js")
    public class MyMixin {
    
        /**
         * Some string param.
         */
        @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
        private String someParam;
    
        @Environmental
        private RenderSupport renderSupport;
    
        @InjectContainer
        private AbstractTextField field;
    
        @AfterRender
        void addScript() {
            this.renderSupport.addScript("new MyJavascriptClass('%s', '%s');", 
                    this.field.getClientId(), this.someParam);
        }
    
    }
    

    Note the @InjectContainer annotation, which injects the containing TextField into your Mixin. In this case, we want the TextField's clientId.

    Also note the @IncludeJavaScriptLibrary("MyMixin.js") annotation, which includes the required Javascript file.

    The Javascript could look like this:

    MyJavascriptClass = Class.create({
    
        initialize: function(textField, someParam) 
        {
            this.textField = $(textField);
            this.someParam = someParam;
    
            this.textField.observe('focus', this.onFocus.bindAsEventListener(this));
        },
    
        onFocus: function(event)
        {
            //do something
        }
    }
    

    The key difference to your approach is that this involves defining your own JS class and using Tapestry's built-in facilities to load and initialize the JS. The use of mixins is also relatively light-weight and elegant in comparison to creating your own components.