/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.isis.viewer.wicket.ui.components.scalars; import java.io.Serializable; import com.google.common.base.Strings; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.behavior.Behavior; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.AbstractTextComponent; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.validation.IValidatable; import org.apache.wicket.validation.IValidator; import org.apache.wicket.validation.ValidationError; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; import org.apache.isis.core.metamodel.facets.SingleIntValueFacet; import org.apache.isis.core.metamodel.facets.all.named.NamedFacet; import org.apache.isis.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet; import org.apache.isis.core.metamodel.facets.objectvalue.typicallen.TypicalLengthFacet; import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings; import org.apache.isis.viewer.wicket.model.models.ScalarModel; import org.apache.isis.viewer.wicket.ui.components.widgets.bootstrap.FormGroup; import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract; import org.apache.isis.viewer.wicket.ui.util.CssClassAppender; /** * Adapter for {@link PanelAbstract panel}s that use a {@link ScalarModel} as * their backing model. * * <p> * Supports the concept of being {@link Rendering#COMPACT} (eg within a table) or * {@link Rendering#REGULAR regular} (eg within a form). * </p> * * <p> * This implementation is for panels that use a textfield/text area. * </p> */ public abstract class ScalarPanelTextFieldAbstract<T extends Serializable> extends ScalarPanelAbstract2 implements TextFieldValueModel.ScalarModelProvider { private static final long serialVersionUID = 1L; protected final Class<T> cls; protected static class ReplaceDisabledTagWithReadonlyTagBehaviour extends Behavior { @Override public void onComponentTag(final Component component, final ComponentTag tag) { super.onComponentTag(component, tag); if(component.isEnabled()) { return; } tag.remove("disabled"); tag.put("readonly","readonly"); } } private AbstractTextComponent<T> textField; public ScalarPanelTextFieldAbstract(final String id, final ScalarModel scalarModel, final Class<T> cls) { super(id, scalarModel); this.cls = cls; } // /////////////////////////////////////////////////////////////////// protected AbstractTextComponent<T> getTextField() { return textField; } /** * Optional hook for subclasses to override */ protected AbstractTextComponent<T> createTextFieldForRegular(final String id) { return createTextField(id); } protected TextField<T> createTextField(final String id) { return new TextField<>(id, newTextFieldValueModel(), cls); } TextFieldValueModel<T> newTextFieldValueModel() { return new TextFieldValueModel<>(this); } // /////////////////////////////////////////////////////////////////// @Override protected MarkupContainer createComponentForRegular() { // even though only one of textField and scalarValueEditInlineContainer will ever be visible, // am instantiating both to avoid NPEs // elsewhere can use Component#isVisibilityAllowed or ScalarModel.getEditStyle() to check whichis visible. textField = createTextFieldForRegular(ID_SCALAR_VALUE); textField.setOutputMarkupId(true); addStandardSemantics(); // // read-only/dialog edit // final MarkupContainer scalarIfRegularFormGroup = createScalarIfRegularFormGroup(); final String describedAs = getModel().getDescribedAs(); if(describedAs != null) { scalarIfRegularFormGroup.add(new AttributeModifier("title", Model.of(describedAs))); } return scalarIfRegularFormGroup; } protected Component getScalarValueComponent() { return textField; } private void addReplaceDisabledTagWithReadonlyTagBehaviourIfRequired(final Component component) { if(!getSettings().isReplaceDisabledTagWithReadonlyTag()) { return; } if (component == null) { return; } if (!component.getBehaviors(ReplaceDisabledTagWithReadonlyTagBehaviour.class).isEmpty()) { return; } component.add(new ReplaceDisabledTagWithReadonlyTagBehaviour()); } private MarkupContainer createScalarIfRegularFormGroup() { Fragment textFieldFragment = createTextFieldFragment("scalarValueContainer"); final String name = getModel().getName(); textField.setLabel(Model.of(name)); final FormGroup formGroup = new FormGroup(ID_SCALAR_IF_REGULAR, this.textField); textFieldFragment.add(this.textField); formGroup.add(textFieldFragment); final Label scalarName = new Label(ID_SCALAR_NAME, getRendering().getLabelCaption(textField)); NamedFacet namedFacet = getModel().getFacet(NamedFacet.class); if (namedFacet != null) { scalarName.setEscapeModelStrings(namedFacet.escaped()); } if(getModel().isRequired()) { final String label = scalarName.getDefaultModelObjectAsString(); if(!Strings.isNullOrEmpty(label)) { scalarName.add(new CssClassAppender("mandatory")); } } formGroup.add(scalarName); return formGroup; } private Fragment createTextFieldFragment(String id) { return new Fragment(id, createTextFieldFragmentId(), this); } protected String createTextFieldFragmentId() { return "text"; } /** * Overrides default to use a fragment, allowing the inner rendering to switch between a simple span * or a textarea */ protected Component createInlinePromptComponent( final String id, final IModel<String> inlinePromptModel) { final Fragment fragment = new Fragment(id, "textInlinePrompt", this); final Label label = new Label("scalarValue", inlinePromptModel); fragment.add(label); return fragment; } protected void addStandardSemantics() { textField.setRequired(getModel().isRequired()); setTextFieldSizeAndMaxLengthIfSpecified(); addValidatorForIsisValidation(); } private void addValidatorForIsisValidation() { final ScalarModel scalarModel = getModel(); textField.add(new IValidator<T>() { private static final long serialVersionUID = 1L; @Override public void validate(final IValidatable<T> validatable) { final T proposedValue = validatable.getValue(); final ObjectAdapter proposedAdapter = getPersistenceSession().adapterFor(proposedValue); final String reasonIfAny = scalarModel.validate(proposedAdapter); if (reasonIfAny != null) { final ValidationError error = new ValidationError(); error.setMessage(reasonIfAny); validatable.error(error); } } }); } private void setTextFieldSizeAndMaxLengthIfSpecified() { final Integer maxLength = getValueOf(getModel(), MaxLengthFacet.class); Integer typicalLength = getValueOf(getModel(), TypicalLengthFacet.class); // doesn't make sense for typical length to be > maxLength if(typicalLength != null && maxLength != null && typicalLength > maxLength) { typicalLength = maxLength; } if (typicalLength != null) { textField.add(new AttributeModifier("size", Model.of("" + typicalLength))); } if(maxLength != null) { textField.add(new AttributeModifier("maxlength", Model.of("" + maxLength))); } } // ////////////////////////////////////// /** * Mandatory hook method to build the component to render the model when in * {@link Rendering#COMPACT compact} format. * * <p> * This default implementation uses a {@link Label}, however it may be overridden if required. */ @Override protected Component createComponentForCompact() { Fragment compactFragment = getCompactFragment(CompactType.SPAN); final Label labelIfCompact = new Label(ID_SCALAR_IF_COMPACT, getModel().getObjectAsString()); compactFragment.add(labelIfCompact); return labelIfCompact; } public enum CompactType { INPUT_CHECKBOX, SPAN } Fragment getCompactFragment(CompactType type) { Fragment compactFragment; switch (type) { case INPUT_CHECKBOX: compactFragment = new Fragment(ID_SCALAR_IF_COMPACT, "compactAsInputCheckbox", ScalarPanelTextFieldAbstract.this); break; case SPAN: default: compactFragment = new Fragment(ID_SCALAR_IF_COMPACT, "compactAsSpan", ScalarPanelTextFieldAbstract.this); break; } return compactFragment; } // ////////////////////////////////////// @Override protected InlinePromptConfig getInlinePromptConfig() { return InlinePromptConfig.supportedAndHide(textField); } @Override protected IModel<String> obtainInlinePromptModel() { IModel<T> model = textField.getModel(); // must be "live", for ajax updates. return (IModel<String>) model; } // ////////////////////////////////////// @Override protected void onInitializeWhenViewMode() { super.onInitializeWhenViewMode(); textField.setEnabled(false); addReplaceDisabledTagWithReadonlyTagBehaviourIfRequired(textField); setTitleAttribute(""); } @Override protected void onInitializeWhenDisabled(final String disableReason) { super.onInitializeWhenDisabled(disableReason); textField.setEnabled(false); addReplaceDisabledTagWithReadonlyTagBehaviourIfRequired(textField); inlinePromptLink.setEnabled(false); setTitleAttribute(disableReason); } @Override protected void onInitializeWhenEnabled() { super.onInitializeWhenEnabled(); textField.setEnabled(true); inlinePromptLink.setEnabled(true); setTitleAttribute(""); } private void setTitleAttribute(final String titleAttribute) { AttributeModifier title = new AttributeModifier("title", Model.of(titleAttribute)); textField.add(title); inlinePromptLink.add(title); } // ////////////////////////////////////// private static Integer getValueOf(ScalarModel model, Class<? extends SingleIntValueFacet> facetType) { final SingleIntValueFacet facet = model.getFacet(facetType); return facet != null ? facet.value() : null; } @com.google.inject.Inject WicketViewerSettings settings; protected WicketViewerSettings getSettings() { return settings; } @Override public AdapterManager getAdapterManager() { return getPersistenceSession(); } }