Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

290 insert text at caret #291

Merged
merged 2 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.vaadin.miki.demo.builders;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import org.vaadin.miki.demo.ContentBuilder;
import org.vaadin.miki.demo.Order;
import org.vaadin.miki.markers.CanModifyText;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.function.Consumer;

@Order(12)
public class CanModifyTextBuilder implements ContentBuilder<CanModifyText> {

@Override
public void buildContent(CanModifyText component, Consumer<Component[]> callback) {
final Button addTextFromServer = new Button("Replace selection", event ->
component.modifyText(String.format("(utc epoch time is %d)", LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)))
);
final HorizontalLayout layout = new HorizontalLayout(new Span("Select something (or not) in the field above, then press the button: "), addTextFromServer);
layout.setAlignItems(FlexComponent.Alignment.CENTER);
callback.accept(new Component[]{layout});
}
}
2 changes: 2 additions & 0 deletions superfields/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ An input field for entering localised `Integer` and `Long` numbers. Supports tho

These are the same components as their Vaadin counterparts, except they fully support text selection API. This means that the text contained in the components can be selected from server-side code and that changes to selection in the browser are sent to the server as events.

In addition to that, both components allow server-side initiated text change at caret position (or any selected range).

## Date fields

### `SuperDatePicker` and `SuperDateTimePicker`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.vaadin.miki.markers;

/**
* Marker interface for objects that can modify text at given coordinates.
* @author miki
* @since 2021-03-26
*/
@FunctionalInterface
public interface CanModifyText {

/**
* Modifies the text at given coordinates.
* @param replacement Text to put.
* @param from The starting index of what to replace.
* @param to The end index of what to replace.
*/
void modifyText(String replacement, int from, int to);

/**
* Modifies the text currently selected.
* @param replacement Text to put.
*/
default void modifyText(String replacement) {
this.modifyText(replacement, -1, -1);
}

/**
* Modifies the text currently selected - from the specified index to the end of current selection.
* @param replacement Text to put.
* @param from Starting index.
*/
default void modifyText(String replacement, int from) {
this.modifyText(replacement, from, -1);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.vaadin.miki.shared.text;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventBus;
import com.vaadin.flow.function.SerializableSupplier;
import org.vaadin.miki.markers.CanModifyText;
import org.vaadin.miki.markers.CanReceiveSelectionEventsFromClient;
import org.vaadin.miki.markers.CanSelectText;

/**
* A delegate to handle {@link CanModifyText} in various components.
* Extension of the {@link TextSelectionDelegate}, as the js function is in the same module, but should not be exposed to all components.
*
* @author miki
* @since 2021-03-26
*/
public class TextModificationDelegate<C extends Component & CanSelectText & CanReceiveSelectionEventsFromClient & CanModifyText>
extends TextSelectionDelegate<C>
implements CanModifyText {

/**
* Creates the delegate for a given component.
*
* @param source Source of all events, data, etc.
* @param eventBus Event bus to use for firing events. Typically, {@code source.getEventBus()}.
* @param stringValueSupplier Method to obtain current value of the component as a {@link String}.
*/
public TextModificationDelegate(C source, ComponentEventBus eventBus, SerializableSupplier<String> stringValueSupplier) {
super(source, eventBus, stringValueSupplier);
}

@Override
public void modifyText(String replacement, int from, int to) {
// the js function lives in text-selection-mixin.js, just because it was much easier
this.getSourceElement().getNode().runWhenAttached(ui -> ui.beforeClientResponse(this.getSource(), context ->
this.getSourceElement().callJsFunction("replaceText", this.getSource().getElement(), replacement, from, to)
));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ public TextSelectionDelegate(C source, ComponentEventBus eventBus, SerializableS
((HasValue<?, ?>) source).addValueChangeListener(event -> this.clearSelectionOnValueChange());
}

/**
* Gets the source component.
* @return Source component.
*/
protected final C getSource() {
return this.source;
}

/**
* Gets the element for the source component.
* @return The element.
*/
protected final Element getSourceElement() {
return this.sourceElement;
}

/**
* Sends information to the client side about whether or not it should forward text selection change events.
* @param value When {@code true}, client-side will notify server about changes in text selection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import com.vaadin.flow.shared.Registration;
import org.vaadin.miki.events.text.TextSelectionListener;
import org.vaadin.miki.events.text.TextSelectionNotifier;
import org.vaadin.miki.markers.CanModifyText;
import org.vaadin.miki.markers.CanSelectText;
import org.vaadin.miki.markers.WithHelper;
import org.vaadin.miki.markers.WithIdMixin;
import org.vaadin.miki.markers.WithLabelMixin;
import org.vaadin.miki.markers.WithPlaceholderMixin;
import org.vaadin.miki.markers.WithReceivingSelectionEventsFromClientMixin;
import org.vaadin.miki.markers.WithValueMixin;
import org.vaadin.miki.shared.text.TextSelectionDelegate;
import org.vaadin.miki.shared.text.TextModificationDelegate;

/**
* An extension of {@link TextArea} with some useful features.
Expand All @@ -28,12 +29,13 @@
@JsModule("./super-text-area.js")
@SuppressWarnings("squid:S110") // there is no way to reduce the number of parent classes
public class SuperTextArea extends TextArea implements CanSelectText, TextSelectionNotifier<SuperTextArea>,
CanModifyText,
WithIdMixin<SuperTextArea>, WithLabelMixin<SuperTextArea>, WithPlaceholderMixin<SuperTextArea>,
WithReceivingSelectionEventsFromClientMixin<SuperTextArea>,
WithHelper<SuperTextArea>,
WithValueMixin<AbstractField.ComponentValueChangeEvent<TextArea, String>, String, SuperTextArea> {

private final TextSelectionDelegate<SuperTextArea> delegate = new TextSelectionDelegate<>(this, this.getEventBus(), this::getValue);
private final TextModificationDelegate<SuperTextArea> delegate = new TextModificationDelegate<>(this, this.getEventBus(), this::getValue);

public SuperTextArea() {
}
Expand Down Expand Up @@ -97,6 +99,11 @@ public void select(int from, int to) {
this.delegate.select(from, to);
}

@Override
public void modifyText(String replacement, int from, int to) {
this.delegate.modifyText(replacement, from, to);
}

@Override
public Registration addTextSelectionListener(TextSelectionListener<SuperTextArea> listener) {
return this.delegate.addTextSelectionListener(listener);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import com.vaadin.flow.shared.Registration;
import org.vaadin.miki.events.text.TextSelectionListener;
import org.vaadin.miki.events.text.TextSelectionNotifier;
import org.vaadin.miki.markers.CanModifyText;
import org.vaadin.miki.markers.CanSelectText;
import org.vaadin.miki.markers.WithHelper;
import org.vaadin.miki.markers.WithIdMixin;
import org.vaadin.miki.markers.WithLabelMixin;
import org.vaadin.miki.markers.WithPlaceholderMixin;
import org.vaadin.miki.markers.WithReceivingSelectionEventsFromClientMixin;
import org.vaadin.miki.markers.WithValueMixin;
import org.vaadin.miki.shared.text.TextSelectionDelegate;
import org.vaadin.miki.shared.text.TextModificationDelegate;

/**
* An extension of {@link TextField} with some useful (hopefully) features.
Expand All @@ -28,12 +29,13 @@
@JsModule("./super-text-field.js")
@SuppressWarnings("squid:S110") // there is no way to reduce the number of parent classes
public class SuperTextField extends TextField implements CanSelectText, TextSelectionNotifier<SuperTextField>,
CanModifyText,
WithIdMixin<SuperTextField>, WithLabelMixin<SuperTextField>, WithPlaceholderMixin<SuperTextField>,
WithValueMixin<AbstractField.ComponentValueChangeEvent<TextField, String>, String, SuperTextField>,
WithHelper<SuperTextField>,
WithReceivingSelectionEventsFromClientMixin<SuperTextField> {

private final TextSelectionDelegate<SuperTextField> delegate = new TextSelectionDelegate<>(this, this.getEventBus(), this::getValue);
private final TextModificationDelegate<SuperTextField> delegate = new TextModificationDelegate<>(this, this.getEventBus(), this::getValue);

public SuperTextField() {
super();
Expand Down Expand Up @@ -113,4 +115,8 @@ public void setReceivingSelectionEventsFromClient(boolean receivingSelectionEven
this.delegate.setReceivingSelectionEventsFromClient(receivingSelectionEventsFromClient);
}

@Override
public void modifyText(String replacement, int from, int to) {
this.delegate.modifyText(replacement, from, to);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ export class TextSelectionMixin {
}
}

replaceText(src, text, from, to) {
console.log('TSM: replacing text '+text+' from '+from+' to '+to);
if (from < 0) {
from = src.selectionMixin.input.selectionStart;
}
if (to < 0) {
to = src.selectionMixin.input.selectionEnd;
}
src.selectionMixin.input.setRangeText(text, from, to);
// the above code does not trigger value changes
// so using the trick from clear-button handler
const inputEvent = new Event('input', { bubbles: true, composed: true });
const changeEvent = new Event('change', { bubbles: !src._slottedInput });
src.selectionMixin.input.dispatchEvent(inputEvent);
src.selectionMixin.input.dispatchEvent(changeEvent);
}

listenToEvents(inputComponent, webComponent, notifyServer) {
console.log('TSM: setting up text selection for component <'+webComponent.tagName+'>');
if (inputComponent === undefined) {
Expand Down