mirror of
https://gitee.com/jmix/cuba.git
synced 2024-12-04 20:28:00 +08:00
PL-6049 Web SuggestionField implementation
This commit is contained in:
parent
da76bfbd68
commit
323f8d383a
@ -1246,6 +1246,16 @@
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="suggestionFieldComponent">
|
||||
<xs:complexContent>
|
||||
<xs:extension base="baseFieldComponent">
|
||||
<xs:attribute name="asyncSearchDelayMs" type="xs:integer"/>
|
||||
<xs:attribute name="minSearchStringLength" type="xs:integer"/>
|
||||
<xs:attribute name="suggestionsLimit" type="xs:integer"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- PickerField -->
|
||||
<xs:complexType name="pickerFieldComponent">
|
||||
<xs:complexContent>
|
||||
@ -2351,6 +2361,7 @@
|
||||
|
||||
<xs:element name="lookupField" type="lookupFieldComponent"/>
|
||||
<xs:element name="searchField" type="searchFieldComponent"/>
|
||||
<xs:element name="suggestionField" type="suggestionFieldComponent"/>
|
||||
<xs:element name="pickerField" type="pickerFieldComponent"/>
|
||||
<xs:element name="lookupPickerField" type="lookupPickerFieldComponent"/>
|
||||
<xs:element name="searchPickerField" type="searchPickerFieldComponent"/>
|
||||
|
@ -76,6 +76,7 @@ public class LayoutLoaderConfig {
|
||||
config.register(TimeField.NAME, TimeFieldLoader.class);
|
||||
config.register(DatePicker.NAME, DatePickerLoader.class);
|
||||
config.register(LookupField.NAME, LookupFieldLoader.class);
|
||||
config.register(SuggestionField.NAME, SuggestionFieldLoader.class);
|
||||
config.register(PickerField.NAME, PickerFieldLoader.class);
|
||||
config.register(ColorPicker.NAME, ColorPickerLoader.class);
|
||||
config.register(LookupPickerField.NAME, LookupPickerFieldLoader.class);
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2017 Haulmont.
|
||||
*
|
||||
* 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.haulmont.cuba.gui.xml.layout.loaders;
|
||||
|
||||
import com.haulmont.cuba.gui.components.SuggestionField;
|
||||
|
||||
public class SuggestionFieldLoader extends AbstractFieldLoader<SuggestionField> {
|
||||
|
||||
@Override
|
||||
public void createComponent() {
|
||||
resultComponent = (SuggestionField) factory.createComponent(SuggestionField.NAME);
|
||||
loadId(resultComponent, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadComponent() {
|
||||
super.loadComponent();
|
||||
|
||||
loadAsyncSearchDelayMs();
|
||||
loadMinSearchStringLength();
|
||||
loadSuggestionsLimit();
|
||||
}
|
||||
|
||||
private void loadSuggestionsLimit() {
|
||||
if (element.attribute("suggestionsLimit") != null) {
|
||||
String suggestionsLimit = element.attributeValue("suggestionsLimit");
|
||||
resultComponent.setSuggestionsLimit(Integer.parseInt(suggestionsLimit));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMinSearchStringLength() {
|
||||
if (element.attribute("minSearchStringLength") != null) {
|
||||
String minSearchStringLength = element.attributeValue("minSearchStringLength");
|
||||
resultComponent.setMinSearchStringLength(Integer.parseInt(minSearchStringLength));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAsyncSearchDelayMs() {
|
||||
if (element.attribute("asyncSearchDelayMs") != null) {
|
||||
String asyncSearchDelayMs = element.attributeValue("asyncSearchDelayMs");
|
||||
resultComponent.setAsyncSearchDelayMs(Integer.parseInt(asyncSearchDelayMs));
|
||||
}
|
||||
}
|
||||
}
|
@ -71,5 +71,7 @@ public class CubaSuggestionFieldConnector extends AbstractFieldConnector {
|
||||
if (stateChangeEvent.hasPropertyChanged("text")) {
|
||||
widget.setValue(getState().text, false);
|
||||
}
|
||||
|
||||
widget.setReadonly(isReadOnly());
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.EventTarget;
|
||||
import com.google.gwt.dom.client.Style;
|
||||
import com.google.gwt.event.dom.client.*;
|
||||
import com.google.gwt.event.logical.shared.*;
|
||||
import com.google.gwt.event.shared.HandlerRegistration;
|
||||
@ -69,13 +68,15 @@ public class CubaSuggestionFieldWidget extends Composite implements HasEnabled,
|
||||
// search query
|
||||
protected String prevQuery;
|
||||
|
||||
public boolean iePreventBlur = false;
|
||||
|
||||
protected List<Suggestion> suggestions = new ArrayList<>();
|
||||
|
||||
public CubaSuggestionFieldWidget() {
|
||||
textField = GWT.create(VTextField.class);
|
||||
initTextField();
|
||||
|
||||
suggestionsContainer = new SuggestionsContainer();
|
||||
suggestionsContainer = new SuggestionsContainer(this);
|
||||
suggestionsPopup = new CubaSuggestionFieldWidget.SuggestionPopup(suggestionsContainer);
|
||||
|
||||
suggestionTimer = new CubaSuggestionFieldWidget.SuggestionTimer();
|
||||
@ -197,6 +198,14 @@ public class CubaSuggestionFieldWidget extends Composite implements HasEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReadonly() {
|
||||
return textField.isReadOnly();
|
||||
}
|
||||
|
||||
public void setReadonly(boolean readonly) {
|
||||
textField.setReadOnly(readonly);
|
||||
}
|
||||
|
||||
protected void cancelSearch() {
|
||||
if (suggestionTimer != null) {
|
||||
suggestionTimer.cancel();
|
||||
@ -267,22 +276,31 @@ public class CubaSuggestionFieldWidget extends Composite implements HasEnabled,
|
||||
protected void handleOnBlur(BlurEvent event) {
|
||||
removeStyleName(MODIFIED_STYLENAME);
|
||||
|
||||
if (!suggestionsPopup.isShowing()) {
|
||||
resetComponentState();
|
||||
return;
|
||||
}
|
||||
|
||||
EventTarget eventTarget = event.getNativeEvent().getRelatedEventTarget();
|
||||
if (eventTarget == null) {
|
||||
resetComponentState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Element.is(eventTarget)) {
|
||||
Widget widget = WidgetUtil.findWidget(Element.as(eventTarget), null);
|
||||
if (widget != suggestionsContainer) {
|
||||
if (BrowserInfo.get().isIE()) {
|
||||
if (iePreventBlur) {
|
||||
textField.setFocus(true);
|
||||
iePreventBlur = false;
|
||||
} else {
|
||||
resetComponentState();
|
||||
}
|
||||
} else {
|
||||
if (!suggestionsPopup.isShowing()) {
|
||||
resetComponentState();
|
||||
return;
|
||||
}
|
||||
|
||||
EventTarget eventTarget = event.getNativeEvent().getRelatedEventTarget();
|
||||
if (eventTarget == null) {
|
||||
resetComponentState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Element.is(eventTarget)) {
|
||||
Widget widget = WidgetUtil.findWidget(Element.as(eventTarget), null);
|
||||
if (widget != suggestionsContainer) {
|
||||
resetComponentState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,23 +385,6 @@ public class CubaSuggestionFieldWidget extends Composite implements HasEnabled,
|
||||
popupOuterPadding = WidgetUtil.measureHorizontalPaddingAndBorder(getElement(), 2);
|
||||
}
|
||||
|
||||
Widget popup = getWidget();
|
||||
|
||||
Element containerFirstChild = popup.getElement().getFirstChild().cast();
|
||||
int naturalMenuWidth = containerFirstChild.getOffsetWidth();
|
||||
final int textFieldWidth = textField.getOffsetWidth();
|
||||
|
||||
if (naturalMenuWidth < textFieldWidth) {
|
||||
popup.setWidth((textFieldWidth - popupOuterPadding) + "px");
|
||||
containerFirstChild.getStyle().setWidth(100, Style.Unit.PCT);
|
||||
naturalMenuWidth = textFieldWidth;
|
||||
}
|
||||
|
||||
if (BrowserInfo.get().isIE()) {
|
||||
int rootWidth = naturalMenuWidth - popupOuterPadding;
|
||||
getContainerElement().getStyle().setWidth(rootWidth, Style.Unit.PX);
|
||||
}
|
||||
|
||||
int top;
|
||||
int left;
|
||||
|
||||
@ -398,6 +399,10 @@ public class CubaSuggestionFieldWidget extends Composite implements HasEnabled,
|
||||
top -= topMargin;
|
||||
}
|
||||
|
||||
Widget popup = getWidget();
|
||||
Element containerFirstChild = popup.getElement().getFirstChild().cast();
|
||||
final int textFieldWidth = textField.getOffsetWidth();
|
||||
|
||||
offsetWidth = containerFirstChild.getOffsetWidth();
|
||||
if (offsetWidth + getPopupLeft() > Window.getClientWidth() + Window.getScrollLeft()) {
|
||||
left = textField.getAbsoluteLeft() + textFieldWidth + Window.getScrollLeft() - offsetWidth;
|
||||
|
@ -29,6 +29,8 @@ import com.google.gwt.user.client.Event;
|
||||
import com.google.gwt.user.client.ui.UIObject;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwt.user.client.ui.impl.FocusImpl;
|
||||
import com.haulmont.cuba.web.toolkit.ui.client.suggestionfield.CubaSuggestionFieldWidget;
|
||||
import com.vaadin.client.BrowserInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -41,14 +43,17 @@ public class SuggestionsContainer extends Widget {
|
||||
protected final Element container;
|
||||
protected SuggestionItem selectedSuggestion;
|
||||
|
||||
public SuggestionsContainer() {
|
||||
protected final CubaSuggestionFieldWidget suggestionFieldWidget;
|
||||
|
||||
public SuggestionsContainer(CubaSuggestionFieldWidget suggestionFieldWidget) {
|
||||
this.suggestionFieldWidget = suggestionFieldWidget;
|
||||
container = DOM.createDiv();
|
||||
|
||||
final Element outer = FocusImpl.getFocusImplForPanel().createFocusable();
|
||||
DOM.appendChild(outer, container);
|
||||
setElement(outer);
|
||||
|
||||
sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONFOCUS | Event.ONKEYDOWN);
|
||||
sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONFOCUS | Event.ONKEYDOWN);
|
||||
addDomHandler(event ->
|
||||
selectItem(null), BlurEvent.getType());
|
||||
|
||||
@ -139,6 +144,13 @@ public class SuggestionsContainer extends Widget {
|
||||
|
||||
SuggestionItem item = findItem(DOM.eventGetTarget(event));
|
||||
switch (DOM.eventGetType(event)) {
|
||||
case Event.ONMOUSEDOWN: {
|
||||
if (BrowserInfo.get().isIE()) {
|
||||
suggestionFieldWidget.iePreventBlur = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Event.ONCLICK: {
|
||||
if (event.getButton() == NativeEvent.BUTTON_LEFT) {
|
||||
performItemCommand(item);
|
||||
|
@ -21,6 +21,9 @@ import com.haulmont.cuba.web.toolkit.ui.client.suggestionfield.CubaSuggestionFie
|
||||
import com.haulmont.cuba.web.toolkit.ui.client.suggestionfield.CubaSuggestionFieldState;
|
||||
import com.vaadin.data.util.converter.Converter;
|
||||
import com.vaadin.event.FieldEvents;
|
||||
import com.vaadin.server.AbstractErrorMessage;
|
||||
import com.vaadin.server.CompositeErrorMessage;
|
||||
import com.vaadin.server.ErrorMessage;
|
||||
import com.vaadin.server.KeyMapper;
|
||||
import com.vaadin.ui.AbstractField;
|
||||
import elemental.json.Json;
|
||||
@ -48,6 +51,7 @@ public class CubaSuggestionField extends AbstractField<Object> {
|
||||
protected int suggestionsLimit = 10;
|
||||
|
||||
public CubaSuggestionField() {
|
||||
setValidationVisible(false);
|
||||
serverRpc = new CubaSuggestionFieldServerRpc() {
|
||||
@Override
|
||||
public void searchSuggestions(String query) {
|
||||
@ -112,6 +116,19 @@ public class CubaSuggestionField extends AbstractField<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorMessage getErrorMessage() {
|
||||
ErrorMessage superError = super.getErrorMessage();
|
||||
if (!isReadOnly() && isRequired() && isEmpty()) {
|
||||
ErrorMessage error = AbstractErrorMessage.getErrorMessageForException(
|
||||
new com.vaadin.data.Validator.EmptyValueException(getRequiredError()));
|
||||
if (error != null) {
|
||||
return new CompositeErrorMessage(superError, error);
|
||||
}
|
||||
}
|
||||
return superError;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setTextViewConverter(Converter<String, ?> converter) {
|
||||
this.textViewConverter = (Converter<String, Object>) converter;
|
||||
|
@ -11,14 +11,27 @@ $cuba-suggestionfield-edit-color: scale-color(lighten($v-focus-color, 35%), $sat
|
||||
}
|
||||
}
|
||||
|
||||
.#{$primary-stylename}-error {
|
||||
@include valo-textfield-error-style;
|
||||
}
|
||||
|
||||
.#{$primary-stylename}.v-readonly,
|
||||
.#{$primary-stylename}-error.v-readonly {
|
||||
@include valo-textfield-readonly-style;
|
||||
}
|
||||
|
||||
.#{$primary-stylename}-popup {
|
||||
@include valo-selection-overlay-style;
|
||||
min-width: 200px;
|
||||
max-width: 400px;
|
||||
overflow-y: auto;
|
||||
outline: 0px;
|
||||
}
|
||||
|
||||
.#{$primary-stylename}-item {
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
line-height: $v-selection-item-height;
|
||||
|
Loading…
Reference in New Issue
Block a user