/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.core.gui.control.generic.ajax.autocompletion; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.olat.core.dispatcher.mapper.Mapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.impl.Form; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.components.htmlheader.jscss.CustomJSFormItem; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; /** * * Description:<br> * The AutoCompleterController provides an input field with a live-AJAX feed * from the database. While typing, after entering a configurable amount of * characters, the system performs a server side search and shows a list of * search results the user can choose from. * <p> * This controller uses ExtJS javascript library to implement the feature * <p> * Fires: an EntriesChosenEvent which contain the chosen entry/entries as * strings * <P> * Initial Date: 06.10.2006 <br> * * @author Felix Jost, FLorian Gnägi */ public class FlexiAutoCompleterController extends FormBasicController { protected static final String COMMAND_SELECT = "select"; protected static final String COMMAND_CHANGE = "change"; protected static final String JSNAME_INPUTFIELD = "o_autocomplete_input"; protected static final String JSNAME_DATASTORE = "autocompleterDatastore"; protected static final String JSNAME_COMBOBOX = "autocompleterCombobox"; protected static final String AUTOCOMPLETER_NO_RESULT = "AUTOCOMPLETER_NO_RESULT"; private Mapper mapper; private ListProvider gprovider; private boolean allowNewValues; private boolean formElement; /** * Constructor to create an auto completer controller * * @param ureq * The user request object * @param wControl * The window control object * @param provider * The provider that can be called to return the search-results * for a given search query * @param noResults * The translated value to display when no results are found, * e.g. "no matches found" or "-no users found-". When a NULL * value is provided, the controller will use a generic message. * @param showDisplayKey * true: show the key for each record; false: don't show the key, * only the value * @param inputWidth * The input field width in characters * @param minChars * The minimum number of characters the user has to enter to * perform a search * @param label */ public FlexiAutoCompleterController(UserRequest ureq, WindowControl wControl, ListProvider provider, String noresults, final boolean showDisplayKey, int inputWidth, int minChars, String label) { super(ureq, wControl, "autocomplete"); this.gprovider = provider; this.allowNewValues = false; setupAutoCompleter(ureq, flc, noresults, showDisplayKey, inputWidth, minChars, label); setFormElement(true); } public FlexiAutoCompleterController(UserRequest ureq, WindowControl wControl, ListProvider provider, String noresults, final boolean showDisplayKey, final boolean allowNewValues, int inputWidth, int minChars, String label, Form externalMainForm) { super(ureq, wControl, LAYOUT_CUSTOM, "autocomplete", externalMainForm); this.gprovider = provider; this.allowNewValues = allowNewValues; setupAutoCompleter(ureq, flc, noresults, showDisplayKey, inputWidth, minChars, label); setFormElement(true); } protected FlexiAutoCompleterController(UserRequest ureq, WindowControl wControl, int layout, String customLayoutPageName, Form externalMainForm) { super(ureq, wControl, layout, customLayoutPageName, externalMainForm); } protected void setListProvider(ListProvider provider) { this.gprovider = provider; } protected void setAllowNewValues(boolean allowNewValues) { this.allowNewValues = allowNewValues; } public boolean isFormElement() { return formElement; } public void setFormElement(boolean formElement) { this.formElement = formElement; if(formElement) { flc.contextPut("formElementClass", "b_form_element"); } else { flc.contextPut("formElementClass", ""); } } protected void setupAutoCompleter(UserRequest ureq, FormLayoutContainer layoutCont, String noresults, boolean showDisplayKey, int inputWidth, int minChars, String label) { String noResults = (noresults == null ? translate("autocomplete.noresults") : noresults); // Configure displaying parameters layoutCont.add("typeahead", new CustomJSFormItem("typeahead", new String[] { "js/jquery/typeahead/typeahead.bundle.min.js" })); if (label != null) { layoutCont.contextPut("autocompleter_label", label); } layoutCont.contextPut("showDisplayKey", Boolean.valueOf(showDisplayKey)); layoutCont.contextPut("inputWidth", Integer.valueOf(inputWidth)); layoutCont.contextPut("minChars", Integer.valueOf(minChars)); layoutCont.contextPut("flexi", Boolean.TRUE); layoutCont.contextPut("inputValue", ""); layoutCont.getComponent().addListener(this); // Create a mapper for the server responses for a given input mapper = new AutoCompleterMapper(noResults, showDisplayKey, gprovider); // Add mapper URL to JS data store in velocity String fetchUri = registerMapper(ureq, mapper); final String fulluri = fetchUri; // + "/" + fileName; layoutCont.contextPut("mapuri", fulluri+"/autocomplete.json"); } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { // } /** * This dispatches component events... * * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) */ public void event(UserRequest ureq, Component source, Event event) { if (source == flc.getComponent()) { String value = getSearchValue(ureq); flc.contextPut("inputValue", value); if (event.getCommand().equals(COMMAND_SELECT)) { doSelect(ureq); } else if (event.getCommand().equals(COMMAND_CHANGE)) { if(allowNewValues) { fireEvent(ureq, new NewValueChosenEvent(value)); } else { super.event(ureq, source, event); } } } else { super.event(ureq, source, event); } } protected void doSelect(UserRequest ureq) { List<String> selectedEntries = new ArrayList<String>(); // init empty result list String key = ureq.getParameter(AutoCompleterMapper.PARAM_KEY); if (key == null) { // Fallback to submitted input field: the input field does not contain // the key but the search value itself String searchValue = getSearchValue(ureq); if (searchValue == null) { // log error because something went wrong in the code and send empty list as result logError("Auto complete JS code must always send 'key' or the autocomplete parameter!", null); getWindowControl().setError(translate("autocomplete.error")); return; } else if (searchValue.equals("") || searchValue.length() < 3) { getWindowControl().setWarning(translate("autocomplete.not.enough.chars")); return; } // Create temporary receiver and perform search for given value. AutoCompleterListReceiver receiver = new AutoCompleterListReceiver("-", false); gprovider.getResult(searchValue, receiver); JSONArray result = receiver.getResult(); // Use key from first result if (result.length() > 0) { try { JSONObject object = result.getJSONObject(0); key = object.getString(AutoCompleterMapper.PARAM_KEY); } catch (JSONException e) { logError("Error while getting json object from list receiver", e); key = ""; } } else { key = ""; } } // Proceed with a key, empty or valid key key = key.trim(); if (!key.equals("") && !key.equals(AUTOCOMPLETER_NO_RESULT)) { // Normal case, add entry selectedEntries.add(key); } else if (key.equals(AUTOCOMPLETER_NO_RESULT)) { return; } doFireSelection(ureq, selectedEntries); } protected void doFireSelection(UserRequest ureq, List<String> selectedEntries) { fireEvent(ureq, new EntriesChosenEvent(selectedEntries)); } protected String getSearchValue(UserRequest ureq) { String searchValue = ureq.getParameter(flc.getId(JSNAME_INPUTFIELD)); return searchValue; } @Override protected void formOK(UserRequest ureq) { // } /** * @see org.olat.core.gui.control.DefaultController#doDispose() */ @Override protected void doDispose() { // } }