/* * 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.gui.api.component.autocomplete; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings; import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteTextField; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.xml.namespace.QName; /** * Autocomplete field for QNames. * * For now it assumes that local part of the QNames will be unique (e.g. for object classes) * * TODO: prefixes, URL formatting, etc. * * @author semancik * */ public abstract class AutoCompleteQNamePanel extends AbstractAutoCompletePanel { private static final long serialVersionUID = 1L; private static final String ID_INPUT = "input"; private Map<String, QName> choiceMap = null; public AutoCompleteQNamePanel(String id, final IModel<QName> model) { super(id); initLayout(model); } private void initLayout(final IModel<QName> model) { setOutputMarkupId(true); AutoCompleteSettings autoCompleteSettings = createAutoCompleteSettings(); final IModel<String> stringModel = new Model<String>() { @Override public void setObject(String object) { super.setObject(object); model.setObject(convertToQname(object)); } }; // The inner autocomplete field is always String. Non-string auto-complete fields are problematic final AutoCompleteTextField<String> input = new AutoCompleteTextField<String>(ID_INPUT, stringModel, String.class, autoCompleteSettings) { private static final long serialVersionUID = 1L; @Override protected Iterator<String> getChoices(String input) { return getIterator(input); } }; input.add(new OnChangeAjaxBehavior() { private static final long serialVersionUID = 1L; @Override protected void onUpdate(AjaxRequestTarget target) { String inputString = stringModel.getObject(); if (StringUtils.isBlank(inputString)) { QName modelObject = model.getObject(); if (modelObject != null) { model.setObject(null); AutoCompleteQNamePanel.this.onChange(target); } } else { QName inputQName = convertToQname(stringModel.getObject()); if (inputQName == null) { // We have some input, but it does not match any QName. Just do nothing. } else { QName modelObject = model.getObject(); if (inputQName.equals(modelObject)) { model.setObject(inputQName); AutoCompleteQNamePanel.this.onChange(target); } } } } }); add(input); } private Iterator<String> getIterator(String input) { Map<String, QName> choiceMap = getChoiceMap(); List<String> selected = new ArrayList<>(choiceMap.size()); for (Entry<String, QName> entry: choiceMap.entrySet()) { String key = entry.getKey(); if (StringUtils.startsWithIgnoreCase(key, input.toLowerCase())) { selected.add(key); } } return selected.iterator(); } private Map<String, QName> getChoiceMap() { if (choiceMap == null) { Collection<QName> choices = loadChoices(); choiceMap = new HashMap<>(); for (QName choice: choices) { // TODO: smarter initialization of the map choiceMap.put(choice.getLocalPart(), choice); } } return choiceMap; } private QName convertToQname(String input) { Map<String, QName> choiceMap = getChoiceMap(); return choiceMap.get(input); } public abstract Collection<QName> loadChoices(); protected void onChange(AjaxRequestTarget target) { // Nothing to do by default. For use in subclasses } @Override public FormComponent<String> getBaseFormComponent() { return (FormComponent<String>) get(ID_INPUT); } }