Package org.apache.isis.viewer.wicket.ui.components.widgets.entitylink

Source Code of org.apache.isis.viewer.wicket.ui.components.widgets.entitylink.EntityLinkSelect2Panel

/*
*  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.widgets.entitylink;

import java.util.List;

import com.google.common.collect.Lists;
import com.vaynberg.wicket.select2.ChoiceProvider;
import com.vaynberg.wicket.select2.Select2Choice;
import com.vaynberg.wicket.select2.Settings;

import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.form.FormComponentPanel;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.IModel;

import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
import org.apache.isis.core.metamodel.facets.object.autocomplete.AutoCompleteFacet;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
import org.apache.isis.viewer.wicket.model.models.ActionModel;
import org.apache.isis.viewer.wicket.model.models.EntityModel;
import org.apache.isis.viewer.wicket.model.models.EntityModel.RenderingHint;
import org.apache.isis.viewer.wicket.model.models.ScalarModel;
import org.apache.isis.viewer.wicket.model.models.ScalarModelWithPending;
import org.apache.isis.viewer.wicket.model.models.ScalarModelWithPending.Util;
import org.apache.isis.viewer.wicket.model.util.MementoFunctions;
import org.apache.isis.viewer.wicket.ui.ComponentFactory;
import org.apache.isis.viewer.wicket.ui.ComponentType;
import org.apache.isis.viewer.wicket.ui.components.actions.ActionInvokeHandler;
import org.apache.isis.viewer.wicket.ui.components.actions.ActionParametersFormPanel;
import org.apache.isis.viewer.wicket.ui.components.widgets.ObjectAdapterMementoProviderAbstract;
import org.apache.isis.viewer.wicket.ui.components.widgets.Select2ChoiceUtil;
import org.apache.isis.viewer.wicket.ui.components.widgets.formcomponent.CancelHintRequired;
import org.apache.isis.viewer.wicket.ui.components.widgets.formcomponent.FormComponentPanelAbstract;
import org.apache.isis.viewer.wicket.ui.pages.entity.EntityPage;

/**
* {@link FormComponentPanel} representing a reference to an entity: a link and
* (optionally) an autocomplete field.
*/
public class EntityLinkSelect2Panel extends FormComponentPanelAbstract<ObjectAdapter> implements CancelHintRequired, ActionInvokeHandler  {

    private static final long serialVersionUID = 1L;
    private static final String ID_AUTO_COMPLETE = "autoComplete";

    private static final String ID_ENTITY_ICON_AND_TITLE = "entityIconAndTitle";

    /**
     * This component may be null if there are no choices or autoComplete, or if in read-only mode.
     */
    private Select2Choice<ObjectAdapterMemento> select2Field;
    private Link<String> entityDetailsLink;
    private Link<String> entityClearLink;

    public EntityLinkSelect2Panel(final String id, final ScalarModel scalarModel) {
        super(id, scalarModel);
        setType(ObjectAdapter.class);
        buildGui();
    }

    public ScalarModel getScalarModel() {
        return (ScalarModel) getModel();
    }

    /**
     * Builds the parts of the GUI that are not dynamic.
     */
    private void buildGui() {
        syncWithInput();
    }

    /**
     * Necessary because {@link FormComponentPanel} overrides this as <tt>true</tt>, whereas we want to
     * report on the state of the underlying {@link Select2Choice}.
     */
    @Override
    public boolean checkRequired() {
        if(select2Field != null) {
            return select2Field.checkRequired();
        }
        return super.checkRequired();
    }

    @Override
    public boolean isRequired() {
        return super.isRequired();
    }

   
    /**
     * Must be called after {@link #setEnabled(boolean)} to ensure that the
     * <tt>findUsing</tt> button, and the <tt>entityClearLink</tt> are
     * shown/not shown as required.
     *
     * <p>
     * REVIEW: there ought to be a better way to do this. I'd hoped to override
     * {@link #setEnabled(boolean)}, but it is <tt>final</tt>, and there doesn't
     * seem to be anyway to install a listener. One option might be to move it
     * to {@link #onBeforeRender()} ?
     */
    public void syncVisibilityAndUsability() {
       
        final boolean mutability = isEnableAllowed() && !getScalarModel().isViewMode();

        if(entityClearLink != null) {
            entityClearLink.setVisible(mutability);
        }

        if(entityDetailsLink != null) {
            entityDetailsLink.setVisible(getScalarModel().getRenderingHint() == RenderingHint.REGULAR);
        }
       
        if(select2Field != null) {
            select2Field.setEnabled(mutability);
        }
       
        if(isEditableWithEitherAutoCompleteOrChoices()) {
            permanentlyHide(ID_ENTITY_ICON_AND_TITLE);
        }
    }

    protected void doSyncVisibilityAndUsability(boolean mutability) {
        if(select2Field != null) {
            select2Field.setEnabled(mutability);
        }

        if(isEditableWithEitherAutoCompleteOrChoices()) {
            permanentlyHide(ID_ENTITY_ICON_AND_TITLE);
        }
    }

    /**
     * Since we override {@link #convertInput()}, it is (apparently) enough to
     * just return a value that is suitable for error reporting.
     *
     * @see DateField#getInput() for reference
     */
    @Override
    public String getInput() {
        final ObjectAdapter pendingElseCurrentAdapter = getScalarModel().getPendingElseCurrentAdapter();
        return pendingElseCurrentAdapter != null? pendingElseCurrentAdapter.titleString(null): "(no object)";
    }

    @Override
    protected void convertInput() {

        if(getScalarModel().isEditMode() && isEditableWithEitherAutoCompleteOrChoices()) {
            // flush changes to pending
            onSelected(select2Field.getConvertedInput());
        }

        final ObjectAdapter pendingAdapter = getScalarModel().getPendingAdapter();
        setConvertedInput(pendingAdapter);
    }


    @Override
    protected void onBeforeRender() {
        syncWithInput();
        super.onBeforeRender();
    }

    private void syncWithInput() {
        final ObjectAdapter adapter = getPendingElseCurrentAdapter();

        syncLinkWithInput(adapter);
        doSyncWithInputIfAutoCompleteOrChoices();
        syncVisibilityAndUsability();
    }

    private void doSyncWithInputIfAutoCompleteOrChoices() {
       
        if(!isEditableWithEitherAutoCompleteOrChoices()) {
            // this is horrid; adds a label to the id
            // should instead be a 'temporary hide'
            permanentlyHide(ID_AUTO_COMPLETE);
            select2Field = null; // this forces recreation next time around
            return;
        }

        final IModel<ObjectAdapterMemento> model = ScalarModelWithPending.Util.createModel(getScalarModel().asScalarModelWithPending());      

        if(select2Field == null) {
            setRequired(getScalarModel().isRequired());
            select2Field = Select2ChoiceUtil.newSelect2Choice(ID_AUTO_COMPLETE, model, getScalarModel());
            setProviderAndCurrAndPending(select2Field, getScalarModel().getActionArgsHint());
            if(!getScalarModel().hasChoices()) {
                final Settings settings = select2Field.getSettings();
                ScalarModel scalarModel = getScalarModel();
                final int minLength = scalarModel.getAutoCompleteMinLength();
                settings.setMinimumInputLength(minLength);
                settings.setPlaceholder(scalarModel.getName());
            }
            addOrReplace(select2Field);
        } else {
            //
            // the select2Field already exists, so the widget has been rendered before.  If it is
            // being re-rendered now, it may be because some other property/parameter was invalid.
            // when the form was submitted, the selected object (its oid as a string) would have
            // been saved as rawInput.  If the property/parameter had been valid, then this rawInput
            // would be correctly converted and processed by the select2Field's choiceProvider.  However,
            // an invalid property/parameter means that the webpage is re-rendered in another request,
            // and the rawInput can no longer be interpreted.  The net result is that the field appears
            // with no input.
            //
            // The fix is therefore (I think) simply to clear any rawInput, so that the select2Field
            // renders its state from its model.
            //
            // see: FormComponent#getInputAsArray()
            // see: Select2Choice#renderInitializationScript()
            //
            select2Field.clearInput();
        }
       
       
        // no need for link, since can see in drop-down
        permanentlyHide(ID_ENTITY_ICON_AND_TITLE);

        // no need for the 'null' title, since if there is no object yet
        // can represent this fact in the drop-down
        // permanentlyHide(ID_ENTITY_TITLE_NULL);
    }


    private ChoiceProvider<ObjectAdapterMemento> providerForObjectAutoComplete() {
        final EntityModel entityModel = getScalarModel();
        return new ObjectAdapterMementoProviderAbstract(getScalarModel()) {

            private static final long serialVersionUID = 1L;

            @Override
            protected List<ObjectAdapterMemento> obtainMementos(String term) {
                final ObjectSpecification typeOfSpecification = entityModel.getTypeOfSpecification();
                final AutoCompleteFacet autoCompleteFacet = typeOfSpecification.getFacet(AutoCompleteFacet.class);
                final List<ObjectAdapter> results = autoCompleteFacet.execute(term);
                return Lists.transform(results, ObjectAdapterMemento.Functions.fromAdapter());
            }

        };
    }

    private ChoiceProvider<ObjectAdapterMemento> providerForParamOrPropertyAutoComplete() {
        final EntityModel entityModel = getScalarModel();
        return new ObjectAdapterMementoProviderAbstract(getScalarModel()) {
           
            private static final long serialVersionUID = 1L;
           
            @Override
            protected List<ObjectAdapterMemento> obtainMementos(String term) {
                final List<ObjectAdapter> autoCompleteChoices = Lists.newArrayList();
                final ScalarModel scalarModel = (ScalarModel) entityModel;
                if(scalarModel.hasAutoComplete()) {
                    autoCompleteChoices.addAll(scalarModel.getAutoComplete(term));
                }
                // take a copy otherwise is only lazily evaluated
                return Lists.newArrayList(Lists.transform(autoCompleteChoices, ObjectAdapterMemento.Functions.fromAdapter()));
            }
           
        };
    }
   
    private List<ObjectAdapterMemento> getChoiceMementos(final ObjectAdapter[] argsIfAvailable) {
       
        final List<ObjectAdapter> choices = Lists.newArrayList();
        final ScalarModel scalarModel = (ScalarModel) getScalarModel();;
        if(scalarModel.hasChoices()) {
            choices.addAll(scalarModel.getChoices(argsIfAvailable));
        }
        // take a copy otherwise is only lazily evaluated
        return Lists.newArrayList(Lists.transform(choices, MementoFunctions.fromAdapter()));
    }

    private void syncLinkWithInput(final ObjectAdapter adapter) {
        if (adapter != null) {
            addOrReplaceIconAndTitle(adapter);
        } else {
            permanentlyHide(ID_ENTITY_ICON_AND_TITLE);
        }
    }


    private void addOrReplaceIconAndTitle(ObjectAdapter pendingOrCurrentAdapter) {
        final EntityModel entityModelForLink = new EntityModel(pendingOrCurrentAdapter);
        entityModelForLink.setContextAdapterIfAny(getScalarModel().getContextAdapterIfAny());
        entityModelForLink.setRenderingHint(getScalarModel().getRenderingHint());
        final ComponentFactory componentFactory = getComponentFactoryRegistry().findComponentFactory(ComponentType.ENTITY_ICON_AND_TITLE, entityModelForLink);
        final Component component = componentFactory.createComponent(entityModelForLink);
        addOrReplace(component);
    }


    @Override
    public void onClick(final ActionModel actionModel) {
    }

    public void onSelected(final ObjectAdapterMemento selectedAdapterMemento) {
        getScalarModel().setPending(selectedAdapterMemento);
        getScalarModel().setObject(selectedAdapterMemento!=null?selectedAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK):null);
        if(this.select2Field != null) {
            select2Field.getModel().setObject(selectedAdapterMemento);
        }
        // not sure why this was here, at any rate it trips up when performing validation with modal dialogs.
        // renderSamePage();
    }

    public void onNoResults() {
        renderSamePage();
    }

    @Override
    public void onCancel() {
        getScalarModel().clearPending();
    }

    private ObjectAdapter getPendingElseCurrentAdapter() {
        return getScalarModel().getPendingElseCurrentAdapter();
    }

    private void renderSamePage() {
        setResponsePage(getPage());
    }
   
    private boolean isEditableWithEitherAutoCompleteOrChoices() {
        // never doesn't apply in compact rendering contexts (ie tables)
        if(getScalarModel().getRenderingHint().isInTable()) {
            return false;
        }
        // doesn't apply if not editable, either
        if(getScalarModel().isViewMode()) {
            return false;
        }
        return getScalarModel().hasChoices() || hasParamOrPropertyAutoComplete() || hasObjectAutoComplete();
    }

    private boolean hasParamOrPropertyAutoComplete() {
        return getScalarModel().hasAutoComplete();
    }

    private boolean hasObjectAutoComplete() {

        // on property/param
        boolean hasAutoComplete = getScalarModel().hasAutoComplete();
        if(hasAutoComplete) {
            return true;
        }
       
        // else on underlying type
        final ObjectSpecification typeOfSpecification = getScalarModel().getTypeOfSpecification();
        final AutoCompleteFacet autoCompleteFacet =
                (typeOfSpecification != null)? typeOfSpecification.getFacet(AutoCompleteFacet.class):null;
        return autoCompleteFacet != null;
    }

    // //////////////////////////////////////

    public void addFormComponentBehavior(Behavior behavior) {
        if(select2Field != null) {
            select2Field.add(behavior);
        }
    }

    public boolean updateChoices(ObjectAdapter[] argsIfAvailable) {
        if(select2Field != null) {
            setProviderAndCurrAndPending(select2Field, argsIfAvailable);
            return true;
        } else {
            return false;
        }
    }
   
    private void setProviderAndCurrAndPending(
            final Select2Choice<ObjectAdapterMemento> select2Field,
            final ObjectAdapter[] argsIfAvailable) {
       
        if (getScalarModel().hasChoices()) {
            final List<ObjectAdapterMemento> choiceMementos = getChoiceMementos(argsIfAvailable);
            select2Field.setProvider(providerForChoices(choiceMementos));
            getScalarModel().clearPending();
            resetIfCurrentNotInChoices(select2Field, choiceMementos);
        } else if(hasParamOrPropertyAutoComplete()) {
            select2Field.setProvider(providerForParamOrPropertyAutoComplete());
            getScalarModel().clearPending();
        } else {
            select2Field.setProvider(providerForObjectAutoComplete());
            getScalarModel().clearPending();
        }
    }

    private ObjectAdapterMementoProviderAbstract providerForChoices(final List<ObjectAdapterMemento> choiceMementos) {
        return new ObjectAdapterMementoProviderAbstract(getScalarModel()) {
            private static final long serialVersionUID = 1L;
            @Override
            protected List<ObjectAdapterMemento> obtainMementos(String unused) {
                return choiceMementos;
            }
        };
    }

    private void resetIfCurrentNotInChoices(final Select2Choice<ObjectAdapterMemento> select2Field, final List<ObjectAdapterMemento> choiceMementos) {
        final ObjectAdapterMemento curr = select2Field.getModelObject();
        if(curr == null) {
            select2Field.getModel().setObject(null);
            getModel().setObject(null);
            return;
        }
        if(!curr.containedIn(choiceMementos)) {
            if(!choiceMementos.isEmpty()) {
                final ObjectAdapterMemento newAdapterMemento = choiceMementos.get(0);
                select2Field.getModel().setObject(newAdapterMemento);
                getModel().setObject(newAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK));
            } else {
                select2Field.getModel().setObject(null);
                getModel().setObject(null);
            }
        }
    }

}
TOP

Related Classes of org.apache.isis.viewer.wicket.ui.components.widgets.entitylink.EntityLinkSelect2Panel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.