J2EE developer with over 7 years of experience in designing and implementing enterprise j2ee solutions based on open source technologies like Tapestry, Hibernate, Spring. Current interests include Tapestry, Plastic, Spock, Scala. Taha is a DZone MVB and is not an employee of DZone and has posted 40 posts at DZone. You can read more from them at their website. View Full User Profile

Tapestry and 'Editable for Bootstrap'

12.04.2012
| 2071 views |
  • submit to reddit

 Twitter Bootstrap is a relief to programmers like me who were never good at web-designing. It has a nice javascript library and there are already modules to integrate twitter-bootstrap with tapestry. There are a lot of good add ons coming up for bootstrap and one of my favourites is the Editable for Bootstrap. Every time I see such a library I wait for a oppurtunity to integrate it with Tapestry.

To use this library you don’t have to do much. Even the ajax part is handled for you. So you just have to handle the server-side stuff and return success status or a failure message. As there are a few of these components, we can start with a base component and then extend it for each component.

@SupportsInformalParameters
@Import(
    library = {
        "classpath:bootstrap-editable/js/bootstrap-editable.js",
        "classpath:pines/jquery.pnotify.js"
    },
    stylesheet = {
        "classpath:bootstrap-editable/css/bootstrap-editable.css",
        "classpath:pines/jquery.pnotify.default.css"
    }
)
public abstract class AbstractBootstrapEditable implements ClientElement {

    @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
    private String client;

    @Parameter(autoconnect = true, required = true)
    private Object value;

    @Parameter
    private Object[] context;

    @Parameter
    private boolean disabled;

    private String assignedClientId;

    @Inject
    private JavaScriptSupport javaScriptSupport;

    @Inject
    private ComponentResources resources;

    @Inject
    private Request request;

    @Inject
    private TypeCoercer typeCoercer;

    @BeginRender
    void begin(MarkupWriter writer) {
        assignedClientId = javaScriptSupport.allocateClientId(resources);

        if (disabled) {
            writer.element("span");
        } else {
            Element element = writer.element("span",
                "id", getClientId(),
                "data-name", getClientId(),
                "data-url", getPostbackLink());

            contributeDataParams(element);
        }

        resources.renderInformalParameters(writer);
    }

    protected void contributeDataParams(Element element) {

    }

    @AfterRender
    void after(MarkupWriter writer) {
        writer.end();

        if (!disabled) {
            javaScriptSupport.addScript(
                "jQuery('#%s').editable({" +
                    "success:function(data){ " +
                    "if(!data.success){return data.msg; }else return null;}" +
                    "});",
                getClientId());
        }
    }

    private String getPostbackLink() {
        return resources.createEventLink("postback", context).toAbsoluteURI();
    }

    @SuppressWarnings("unchecked")
    @OnEvent("postback")
    Object submit(@RequestParameter("value") String value, EventContext context) {
        this.value = toValue(value);
        CaptureResultCallback<String> callback = new CaptureResultCallback<String>();
        boolean handled = resources.triggerContextEvent(EventConstants.SUBMIT, context, callback);

        JSONObject result = new JSONObject();

        if (handled) {
            String response = callback.getResult();
            if (response != null) {
                result.put("success", false);
                result.put("msg", response);
                return result;
            }
        }

        return result.put("success", true);
    }

    protected abstract Object toValue(String value);

    @Override
    public String getClientId() {
        return assignedClientId;
    }
}


The data-url is set to the server-side callback and will be called for updation. The callback delegates the updation to the SUBMIT event along with the context. The value is passed to the parameter of the same name.

A text field will be as simple as

public class Text extends AbstractBootstrapEditable {

    @Override
    protected void contributeDataParams(Element element){
        element.attribute("data-type", "text");
    }

    @Override
    protected Object toValue(String value) {
        return value;
    }

}

and a textarea field will be

public class TextArea extends AbstractBootstrapEditable {

    @Override
    protected void contributeDataParams(Element element) {
        element.attribute("data-type", "textarea");
    }

    @Override
    protected Object toValue(String value) {
        return value;
    }

}

Select will be a bit more code as you will need a SelectModel and a ValueEncoder

public class Select extends AbstractBootstrapEditable {

    @Parameter(required = true, allowNull = false)
    private SelectModel model;

    @Parameter(required = true, allowNull = false)
    private ValueEncoder encoder;

    @Inject
    private ComponentResources resources;

    @Inject
    private ComponentDefaultProvider defaultProvider;

    @SuppressWarnings("unchecked")
    ValueEncoder defaultEncoder() {
        return defaultProvider.defaultValueEncoder("value", resources);
    }

    @SuppressWarnings("unchecked")
    SelectModel defaultModel() {
        Class valueType = resources.getBoundType("value");

        if (valueType == null)
            return null;

        if (Enum.class.isAssignableFrom(valueType))
            return new EnumSelectModel(valueType, resources.getContainerMessages());

        return null;
    }

    @Override
    protected void contributeDataParams(Element element) {
        element.attribute("data-type", "select");
        element.attribute("data-source", getSource());
    }

    @SuppressWarnings("unchecked")
    public String getSource() {
        JSONObject source = new JSONObject();

        for (OptionModel option : model.getOptions()) {
            source.put(encoder.toClient(option.getValue()), option.getLabel());
        }

        return source.toCompactString();
    }

    @Override
    protected Object toValue(String value) {
        return encoder.toValue(value);
    }

}


Usage

Just implement the onSubmit() event and return null for success and a failure message in case of a failure.. That is all!





Published at DZone with permission of Taha Siddiqi, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)