/* * Copyright 2017 * Ubiquitous Knowledge Processing (UKP) Lab * Technische Universität Darmstadt * * Licensed 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 de.tudarmstadt.ukp.inception.ui.kb.stmt; import static de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaBehavior.visibleWhen; import static org.apache.commons.lang3.StringUtils.isNotEmpty; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.event.Broadcast; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.spring.injection.annot.SpringBean; import org.eclipse.rdf4j.model.IRI; import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.select.BootstrapSelect; import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxButton; import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxLink; import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaBehavior; import de.tudarmstadt.ukp.inception.kb.KnowledgeBaseService; import de.tudarmstadt.ukp.inception.kb.graph.KBProperty; import de.tudarmstadt.ukp.inception.kb.graph.KBQualifier; import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; import de.tudarmstadt.ukp.inception.ui.core.Focusable; import de.tudarmstadt.ukp.inception.ui.kb.event.AjaxQualifierChangedEvent; public class QualifierEditor extends Panel { private static final long serialVersionUID = -4152363403483032196L; private static final String CONTENT_MARKUP_ID = "content"; private @SpringBean KnowledgeBaseService kbService; private IModel<KnowledgeBase> kbModel; private IModel<KBQualifier> qualifier; private Component content; public QualifierEditor(String id, IModel<KnowledgeBase> aKbModel, IModel<KBQualifier> aQualifier) { super(id, aQualifier); setOutputMarkupId(true); kbModel = aKbModel; qualifier = aQualifier; boolean isNewQualifier = qualifier.getObject().getProperty() == null; if (isNewQualifier) { EditMode editMode = new EditMode(CONTENT_MARKUP_ID, qualifier, isNewQualifier); RequestCycle.get() .find(AjaxRequestTarget.class) .ifPresent(target -> target.focusComponent(editMode.getFocusComponent())); content = editMode; } else { content = new ViewMode(CONTENT_MARKUP_ID, qualifier); } add(content); } private class EditMode extends Fragment implements Focusable { private static final long serialVersionUID = 2333017379066971404L; private Component initialFocusComponent; /** * Creates a new fragement for editing a qualifier.<br> * The editor has two slightly different behaviors, depending on the value of * {@code isNewQualifier}: * <ul> * <li>{@code !isNewQualifier}: Save button commits changes, cancel button discards unsaved * changes, delete button removes the qualifier from the statement.</li> * <li>{@code isNewQualifier}: Save button commits changes (creates a new qualifier in the * statement), cancel button removes the qualifier from the UI, delete button is not visible * .</li> * </ul> * * @param aId * markup ID * @param aQualifier * qualifier model * @param isNewQualifier * whether the qualifier being edited is new, meaning it has no corresponding * qualifier in the KB backend */ public EditMode(String aId, IModel<KBQualifier> aQualifier, boolean isNewQualifier) { super(aId, "editMode", QualifierEditor.this, aQualifier); IModel<KBQualifier> compoundModel = CompoundPropertyModel.of(aQualifier); Form<KBQualifier> form = new Form<>("form", compoundModel); DropDownChoice<KBProperty> type = new BootstrapSelect<>("property"); type.setChoiceRenderer(new ChoiceRenderer<>("uiLabel")); type.setChoices(kbService.listProperties(kbModel.getObject(), false)); type.setRequired(true); type.setOutputMarkupId(true); form.add(type); initialFocusComponent = type; form.add(new TextField<>("language")); Component valueTextArea = new TextArea<String>("value"); form.add(valueTextArea); form.add(new LambdaAjaxButton<>("create", QualifierEditor.this::actionSave)); form.add(new LambdaAjaxLink("cancel", t -> { if (isNewQualifier) { QualifierEditor.this.actionCancelNewQualifier(t); } else { QualifierEditor.this.actionCancelExistingQualifier(t); } })); form.add(new LambdaAjaxLink("delete", QualifierEditor.this::actionDelete) .setVisibilityAllowed(!isNewQualifier)); add(form); } @Override public Component getFocusComponent() { return initialFocusComponent; } } private class ViewMode extends Fragment { private static final long serialVersionUID = 6771056914040868827L; public ViewMode(String aId, IModel<KBQualifier> aQualifier) { super(aId, "viewMode", QualifierEditor.this, aQualifier); CompoundPropertyModel<KBQualifier> compoundModel = new CompoundPropertyModel<>( aQualifier); add(new Label("property", aQualifier.getObject().getProperty().getUiLabel())); add(new Label("language", compoundModel.bind("language")).add( LambdaBehavior.onConfigure(_this -> _this.setVisible(isNotEmpty(aQualifier.getObject().getLanguage()))))); add(new Label("value", LoadableDetachableModel.of(() -> getLabel(compoundModel.getObject())))); LambdaAjaxLink editLink = new LambdaAjaxLink("edit", QualifierEditor.this::actionEdit); editLink.add(visibleWhen(() -> kbModel.map(kb -> !kb.isReadOnly()) .orElse(false).getObject())); add(editLink); } } private String getLabel(KBQualifier aKbQualifier) { if (aKbQualifier == null) { return null; } if (aKbQualifier != null && aKbQualifier.getValueLabel() != null) { return aKbQualifier.getValueLabel(); } if (aKbQualifier.getValue() instanceof IRI) { return ((IRI) aKbQualifier.getValue()).getLocalName(); } return String.valueOf(aKbQualifier.getValue()); } private void actionDelete(AjaxRequestTarget aTarget) { kbService.deleteQualifier(kbModel.getObject(), qualifier.getObject()); AjaxQualifierChangedEvent deleteEvent = new AjaxQualifierChangedEvent(aTarget, qualifier .getObject(), this, true); send(getPage(), Broadcast.BREADTH, deleteEvent); } private void actionEdit(AjaxRequestTarget aTarget) { KBQualifier shallowCopy = new KBQualifier(qualifier.getObject()); IModel<KBQualifier> shallowCopyModel = Model.of(shallowCopy); EditMode editMode = new EditMode(CONTENT_MARKUP_ID, shallowCopyModel, false); content = content.replaceWith(editMode); aTarget.focusComponent(editMode.getFocusComponent()); aTarget.add(this); } private void actionSave(AjaxRequestTarget aTarget, Form<KBQualifier> aForm) { KBQualifier modifiedQualifier = aForm.getModelObject(); kbService.upsertQualifier(kbModel.getObject(), modifiedQualifier); qualifier.setObject(modifiedQualifier); actionCancelExistingQualifier(aTarget); } private void actionCancelNewQualifier(AjaxRequestTarget aTarget) { // send a delete event to trigger the deletion in the UI AjaxQualifierChangedEvent deleteEvent = new AjaxQualifierChangedEvent(aTarget, qualifier.getObject(), this, true); send(getPage(), Broadcast.BREADTH, deleteEvent); } private void actionCancelExistingQualifier(AjaxRequestTarget aTarget) { content = content.replaceWith(new ViewMode(CONTENT_MARKUP_ID, qualifier)); aTarget.add(this); } }