PL-6049 Web SuggestionField implementation

This commit is contained in:
Daniil Tsarev 2017-02-03 23:02:25 +04:00
parent da76bfbd68
commit 323f8d383a
8 changed files with 155 additions and 36 deletions

View File

@ -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"/>

View File

@ -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);

View File

@ -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));
}
}
}

View File

@ -71,5 +71,7 @@ public class CubaSuggestionFieldConnector extends AbstractFieldConnector {
if (stateChangeEvent.hasPropertyChanged("text")) {
widget.setValue(getState().text, false);
}
widget.setReadonly(isReadOnly());
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;