/*
* Copyright (c) 2010-2016 Evolveum
*
* 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 com.evolveum.midpoint.web.component.form.multivalue;
import static java.util.Arrays.asList;
import static java.util.Optional.ofNullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;
import org.apache.commons.collections.CollectionUtils;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.feedback.ComponentFeedbackMessageFilter;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import com.evolveum.midpoint.gui.api.component.BasePanel;
import com.evolveum.midpoint.gui.api.component.ObjectBrowserPanel;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.InOidFilter;
import com.evolveum.midpoint.prism.query.NotFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
/**
*
* @param <T> model/chosen object types
*/
public class MultiValueChoosePanel<T extends ObjectType> extends BasePanel<List<T>> {
private static final long serialVersionUID = 1L;
private static final Trace LOGGER = TraceManager.getTrace(MultiValueChoosePanel.class);
private static final String ID_SELECTED_ROWS = "selectedRows";
private static final String ID_TEXT_WRAPPER = "textWrapper";
private static final String ID_TEXT = "text";
private static final String ID_FEEDBACK = "feedback";
private static final String ID_EDIT = "edit";
private static final String ID_REMOVE = "remove";
private static final String ID_BUTTON_GROUP = "buttonGroup";
protected static final String MODAL_ID_OBJECT_SELECTION_POPUP = "objectSelectionPopup";
private List<QName> typeQNames;
private Class<? extends T> defaultType;
private Collection<Class<? extends T>> types;
public MultiValueChoosePanel(String id, IModel<List<T>> value, Collection<Class<? extends T>> types) {
this(id, value, null, false, types);
}
public MultiValueChoosePanel(String id, IModel<List<T>> value, Collection<Class<? extends T>> types, boolean multiselect) {
this(id, value, null, false, types, multiselect);
}
public MultiValueChoosePanel(String id, IModel<List<T>> chosenValues, List<PrismReferenceValue> filterValues, boolean required,
Collection<Class<? extends T>> types) {
this(id, chosenValues, filterValues, required, types, true);
}
public MultiValueChoosePanel(String id, IModel<List<T>> chosenValues, List<PrismReferenceValue> filterValues, boolean required,
Collection<Class<? extends T>> types, boolean multiselect) {
super(id, chosenValues);
setOutputMarkupPlaceholderTag(true);
this.types = types;
this.defaultType = userOrFirst(types);
// initialize typeQNames in onInitialize
LOGGER.debug("Init multi value choose panel with model {}", chosenValues);
initLayout(chosenValues, filterValues, required, multiselect);
}
@Override
protected void onInitialize() {
super.onInitialize();
// initialize types when component is in page and getPageBase() has meaning
this.typeQNames = WebComponentUtil.resolveObjectTypesToQNames(types,
getPageBase().getPrismContext());
typeQNames.sort((t1, t2) -> t1.getLocalPart().compareTo(t2.getLocalPart()));
}
private void initLayout(final IModel<List<T>> chosenValues, final List<PrismReferenceValue> filterValues,
final boolean required, final boolean multiselect) {
AjaxLink<String> edit = new AjaxLink<String>(ID_EDIT) {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
editValuePerformed(chosenValues.getObject(), filterValues, target, multiselect);
}
};
edit.setOutputMarkupPlaceholderTag(true);
add(edit);
ListView<T> selectedRowsList = new ListView<T>(ID_SELECTED_ROWS, chosenValues) {
@Override
protected void populateItem(ListItem<T> item) {
WebMarkupContainer textWrapper = new WebMarkupContainer(ID_TEXT_WRAPPER);
textWrapper.setOutputMarkupPlaceholderTag(true);
TextField<String> text = new TextField<String>(ID_TEXT, createTextModel(item.getModel())); //was value
text.add(new AjaxFormComponentUpdatingBehavior("blur") {
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget ajaxRequestTarget) {
}
});
text.setRequired(required);
text.setEnabled(false);
text.setOutputMarkupPlaceholderTag(true);
textWrapper.add(text);
FeedbackPanel feedback = new FeedbackPanel(ID_FEEDBACK, new ComponentFeedbackMessageFilter(text));
feedback.setOutputMarkupPlaceholderTag(true);
textWrapper.add(feedback);
initButtons(item, item);
item.add(textWrapper);
}
};
selectedRowsList.setReuseItems(true);
add(selectedRowsList);
}
protected ObjectQuery createChooseQuery(List<PrismReferenceValue> values) {
ArrayList<String> oidList = new ArrayList<>();
ObjectQuery query = new ObjectQuery();
if (oidList.isEmpty()) {
return null;
}
ObjectFilter oidFilter = InOidFilter.createInOid(oidList);
query.setFilter(NotFilter.createNot(oidFilter));
return query;
}
/**
* @return css class for off-setting other values (not first, left to the
* first there is a label)
*/
protected String getOffsetClass() {
return "col-md-offset-4";
}
protected IModel<String> createTextModel(final IModel<T> model) {
return new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
@Override
public String getObject() {
return ofNullable(model.getObject())
.map(ObjectType::getName)
.map(PolyString::getOrig)
.orElse(null);
}
};
}
protected void editValuePerformed(List<T> chosenValues, List<PrismReferenceValue> filterValues, AjaxRequestTarget target, boolean multiselect) {
ObjectBrowserPanel<T> objectBrowserPanel = new ObjectBrowserPanel<T>(
getPageBase().getMainPopupBodyId(), defaultType, typeQNames, multiselect, getPageBase(),
null, chosenValues) {
private static final long serialVersionUID = 1L;
@Override
protected void addPerformed(AjaxRequestTarget target, QName type, List<T> selected) {
getPageBase().hideMainPopup(target);
MultiValueChoosePanel.this.addPerformed(target, selected);
}
@Override
protected void onSelectPerformed(AjaxRequestTarget target, T focus) {
super.onSelectPerformed(target, focus);
if (!multiselect) {
// asList alone is not modifiable, you can't add/remove
// elements later
selectPerformed(target, new ArrayList<>(asList(focus)));
}
}
};
getPageBase().showMainPopup(objectBrowserPanel, target);
}
protected void selectPerformed(AjaxRequestTarget target, List<T> chosenValues) {
getModel().setObject(chosenValues);
choosePerformedHook(target, chosenValues);
target.add(MultiValueChoosePanel.this);
}
protected void addPerformed(AjaxRequestTarget target, List<T> addedValues) {
List<T> modelList = getModelObject();
if(modelList == null) {
modelList = new ArrayList<T>();
}
addedValues.removeAll(modelList); // add values not already in
modelList.addAll(addedValues);
getModel().setObject(modelList);
choosePerformedHook(target, modelList);
target.add(MultiValueChoosePanel.this);
}
public WebMarkupContainer getTextWrapperComponent(){
return (WebMarkupContainer)get(ID_TEXT_WRAPPER);
}
protected void initButtons(ListItem<T> item, WebMarkupContainer parent) {
WebMarkupContainer buttonGroup = new WebMarkupContainer(ID_BUTTON_GROUP); {
buttonGroup.setOutputMarkupId(true);
AjaxLink remove = new AjaxLink(ID_REMOVE) {
@Override
public void onClick(AjaxRequestTarget target) {
removeValuePerformed(target, item.getModelObject());
}
};
remove.add(new VisibleEnableBehaviour() {
@Override
public boolean isVisible() {
return isRemoveButtonVisible();
}
});
buttonGroup.add(remove);
}
parent.add(buttonGroup);
}
private boolean isRemoveButtonVisible() {
return true;
}
private void removeValuePerformed(AjaxRequestTarget target, T value) {
LOGGER.debug("Removing value {} from selected list", value);
getModelObject().remove(value);
removePerformedHook(target, value);
target.add(this);
}
protected void removePerformedHook(AjaxRequestTarget target, T value) {
}
/**
* A custom code in form of hook that can be run on event of choosing new
* object with this chooser component
*/
protected void choosePerformedHook(AjaxRequestTarget target, List<T> selected) {
}
private Class<? extends T> userOrFirst(Collection<Class<? extends T>> types) {
// ugly hack to select UserType as default if available
if(types == null) {
return null;
}
return types.stream()
.filter(type -> type == UserType.class)
.findFirst().orElse(
CollectionUtils.isNotEmpty(types) ? types.iterator().next() : null);
}
}