Custom hot keys in dialog and text editing

Hi,
I’m creating InputDialog with textarea and a few buttons: “Insert special symbol”, “OK”, “Cancel”.
So I have 2 questions:

  1. How to create hot keys for button “Insert special symbol”?
  2. How to insert special character to current cursor in textarea by pressing button? Is it only javascript? What is the best practices to add javascript?

Hello @uladzislau_chabatkou

InputDialogAction has a withShortcut(String shortcut) method that defines a shortcut for the action.

    @Inject
    protected Dialogs dialogs;
    @Inject
    protected Notifications notifications;

    @Subscribe("clickBtn")
    public void onClickBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withParameter(InputParameter.booleanParameter("boolParam"))
                .withActions(InputDialogAction.action("specialAction")
                        .withCaption("Insert special symbol")
                        .withShortcut("SHIFT-A")
                        .withHandler((inputDialogActionPerformed -> notifications.create()
                                .withCaption("Shortcut has pressed")
                                .show())))
                .show();
    }

Could you describe in more detail what you want to do?

Regards,
Gleb

Oh, thanks a lot. I have not seen this method

I have InputDialog with three buttons and textarea as described above. I want to insert some symbol in textarea on pressing button “Insert special symbol”.

Hello @uladzislau_chabatkou

You could use the below snippet to solve your problem:

    @Inject
    protected Dialogs dialogs;
    @Inject
    protected UiComponents uiComponents;

    @Subscribe("clickBtn")
    public void onClickBtnClick(Button.ClickEvent event) {
        dialogs.createInputDialog(this)
                .withParameter(InputParameter.parameter("textAreaParam")
                        .withField(() -> {
                                    TextArea<String> textArea = uiComponents.create(TextArea.class);
                                    textArea.setWidthFull();
                                    return textArea;
                                }
                        ))
                .withActions(InputDialogAction.action("specialAction")
                        .withCaption("Insert special symbol")
                        .withShortcut("SHIFT-A")
                        .withHandler(actionEvent -> {
                            Form form = (Form) actionEvent.getInputDialog().getDialogWindow().getComponentNN("form");
                            TextArea<String> textArea = (TextArea<String>) form.getComponentNN("textAreaParam");
                            textArea.setValue(textArea.getValue()+"$");
                        }))
                .show();
    }

Regards,
Gleb

Yes, something like that. It inserts symbol at the end of textarea. But can we insert symbol in the current cursor position?

Hello @uladzislau_chabatkou

The platform does not provide API to find out the position of the cursor within the TextAreacomponent. You can try to subscribe to TextInputField#TextChangeEvent with EAGER text change event mode, but this event will only occur when you enter a text. When moving the cursor, this event will not occur.

    @Inject
    protected Dialogs dialogs;
    @Autowired
    private UiComponents uiComponents;

    @Subscribe("clickBtn")
    public void onClickBtnClick(Button.ClickEvent event) {
        AtomicInteger cursorPosition = new AtomicInteger();
        dialogs.createInputDialog(this)
                .withParameter(InputParameter.parameter("textAreaParam")
                        .withField(() -> {
                                    TextArea<String> textArea = uiComponents.create(TextArea.class);
                                    textArea.setWidthFull();
                                    textArea.setTextChangeEventMode(TextInputField.TextChangeEventMode.EAGER);
                                    textArea.addTextChangeListener(textChangeEvent -> {
                                        cursorPosition.set(textChangeEvent.getCursorPosition());
                                    });
                                    return textArea;
                                }
                        ))
                .withActions(InputDialogAction.action("specialAction")
                        .withCaption("Insert special symbol")
                        .withShortcut("SHIFT-A")
                        .withHandler(actionEvent -> {
                            Form form = (Form) actionEvent.getInputDialog().getDialogWindow().getComponentNN("form");
                            TextArea<String> textArea = (TextArea<String>) form.getComponentNN("textAreaParam");
                            StringBuilder builder = new StringBuilder(textArea.getRawValue());
                            builder.insert(cursorPosition.get(), "$");
                            textArea.setValue(builder.toString());
                        }))
                .show();
    }

Regards,
Gleb

If you want to use JavaScript, you can define a custom styleName for textArea component and use JavaScript#eval() method:

    @Inject
    protected Dialogs dialogs;
    @Autowired
    private UiComponents uiComponents;

    @Subscribe("clickBtn")
    public void onClickBtnClick(Button.ClickEvent event) {
        AtomicInteger cursorPosition = new AtomicInteger();
        dialogs.createInputDialog(this)
                .withParameter(InputParameter.parameter("textAreaParam")
                        .withField(() -> {
                                    TextArea<String> textArea = uiComponents.create(TextArea.NAME);
                                    textArea.setWidthFull();
                                    textArea.setStyleName("js-text-area");
                                    return textArea;
                                }
                        ))
                .withActions(InputDialogAction.action("specialAction")
                        .withCaption("Insert special symbol")
                        .withShortcut("SHIFT-A")
                        .withHandler(actionEvent -> JavaScript.eval(String.format(
                                "var el = document.querySelector('.js-text-area');" +
                                        "var pos = el.selectionStart;" +
                                        "el.value = el.value.substr(0, pos) + '%s' + el.value.substr(pos);",
                                "$"))))
                .show();
    }

Or you can create a composite component that includes TextArea component and JavaScriptComponent:

JavaScriptTextArea

@CompositeDescriptor("js-text-area.xml")
public class JavaScriptTextArea extends CompositeComponent<HBoxLayout>
        implements Field<String>,
        CompositeWithCaption,
        CompositeWithDescription,
        CompositeWithContextHelp,
        CompositeWithHtmlCaption,
        CompositeWithHtmlDescription,
        CompositeWithIcon {

    public static final String NAME = "javaScriptTextArea";

    protected TextArea<String> textArea;
    protected JavaScriptComponent jsComponent;

    public JavaScriptTextArea() {
        addCreateListener(this::onCreate);
    }

    protected void onCreate(CreateEvent createEvent) {
        textArea = getInnerComponent("textArea");
        jsComponent = getInnerComponent("jsComponent");
        JavaScript.getCurrent().execute(jsComponent.getInitFunctionName() + "()");
    }

    @Override
    public boolean isRequired() {
        return textArea.isRequired();
    }

    @Override
    public void setRequired(boolean required) {
        textArea.setRequired(required);
    }

    @Override
    public String getRequiredMessage() {
        return textArea.getRequiredMessage();
    }

    @Override
    public void setRequiredMessage(String msg) {
        textArea.setRequiredMessage(msg);
    }

    @Override
    public void addValidator(Consumer<? super String> validator) {
        textArea.addValidator(validator);
    }

    @Override
    public void removeValidator(Consumer<String> validator) {
        textArea.removeValidator(validator);
    }

    @Override
    public Collection<Consumer<String>> getValidators() {
        return textArea.getValidators();
    }

    @Override
    public boolean isEditable() {
        return textArea.isEditable();
    }

    @Override
    public void setEditable(boolean editable) {
        textArea.setEditable(editable);
    }

    @Nullable
    @Override
    public String getValue() {
        return textArea.getValue();
    }

    @Override
    public void setValue(@Nullable String value) {
        textArea.setValue(value);
    }

    @Override
    public Subscription addValueChangeListener(Consumer<ValueChangeEvent<String>> listener) {
        return textArea.addValueChangeListener(listener);
    }

    @Override
    public void removeValueChangeListener(Consumer<ValueChangeEvent<String>> listener) {
        textArea.removeValueChangeListener(listener);
    }

    @Override
    public boolean isValid() {
        return textArea.isValid();
    }

    @Override
    public void validate() throws ValidationException {
        textArea.validate();
    }

    @Override
    public void setValueSource(@Nullable ValueSource<String> valueSource) {
        textArea.setValueSource(valueSource);
    }

    @Nullable
    @Override
    public ValueSource<String> getValueSource() {
        return textArea.getValueSource();
    }


    public void insertCharacter(String character) {
        jsComponent.callFunction("insertCharacter", character);
    }
}

JavaScriptTextAreaLoader

public class JavaScriptTextAreaLoader extends AbstractFieldLoader<JavaScriptTextArea> {
    @Override
    public void createComponent() {
        resultComponent = factory.create(JavaScriptTextArea.NAME);
        loadId(resultComponent, element);
    }
}

js-text-area.xml

<composite xmlns="http://schemas.haulmont.com/cuba/screen/composite.xsd">
    <hbox expand="textArea">
        <textArea id="textArea" stylename="js-text-area"/>
        <jsComponent id="jsComponent"
                     height="100%"
                     initFunctionName="com_company_sample_JavaScriptTextArea">
            <dependencies>
                <dependency path="vaadin://java-script-text-area-connector.js"/>
            </dependencies>
        </jsComponent>
    </hbox>
</composite>

ui-component.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<components xmlns="http://schemas.haulmont.com/cuba/components.xsd">
    <component>
        <name>javaScriptTextArea</name>
        <componentLoader>com.company.sample.web.components.jstextarea.JavaScriptTextAreaLoader</componentLoader>
        <class>com.company.sample.web.components.jstextarea.JavaScriptTextArea</class>
    </component>
</components>

ui-component.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns="http://schemas.company.com/demo/0.1/ui-component.xsd"
           attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           targetNamespace="http://schemas.company.com/demo/0.1/ui-component.xsd"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:layout="http://schemas.haulmont.com/cuba/screen/layout.xsd">

    <xs:element name="javaScriptTextArea">
        <xs:complexType>
            <xs:complexContent>
                <xs:extension base="layout:baseFieldComponent">
                </xs:extension>
            </xs:complexContent>
        </xs:complexType>
    </xs:element>
</xs:schema>

web-app.properties

cuba.web.componentsConfig = +com/company/sample/ui-component.xml

java-scipt-text-area-connector.js

com_company_sample_JavaScriptTextArea = function () {
    this.insertCharacter = function (character) {
        var textArea = document.querySelector(".js-text-area");
        var pos = textArea.selectionStart;
        textArea.value = textArea.value.substr(0, pos) + character + textArea.value.substr(pos);
    }
};

NewScreen

    @Inject
    protected Dialogs dialogs;
    @Autowired
    private UiComponents uiComponents;

    @Subscribe("clickBtn")
    public void onClickBtnClick(Button.ClickEvent event) {
        AtomicInteger cursorPosition = new AtomicInteger();
        dialogs.createInputDialog(this)
                .withParameter(InputParameter.parameter("textAreaParam")
                        .withField(() -> {
                                    JavaScriptTextArea textArea = uiComponents.create(JavaScriptTextArea.NAME);
                                    textArea.setWidthFull();
                                    return textArea;
                                }
                        ))
                .withActions(InputDialogAction.action("specialAction")
                        .withCaption("Insert special symbol")
                        .withShortcut("SHIFT-A")
                        .withHandler(actionEvent -> {
                            Form form = (Form) actionEvent.getInputDialog().getDialogWindow().getComponentNN("form");
                            JavaScriptTextArea  textArea = (JavaScriptTextArea) form.getComponentNN("textAreaParam");
                            textArea.insertCharacter("$");
                        }))
                .show();
    }

Regards,
Gleb