[Flow UI] allow anonymous to create entity

Hello,
how do I allow anonymous to register in entity Person?

Kind regards
Swara

Hi,

You need to create a view that can be accessed anonymously, e.g. by adding @AnonymousAllowed annotation, e.g.:

registration-view.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<view xmlns="http://jmix.io/schema/flowui/view"
      title="msg://registrationView.title">
    <layout justifyContent="CENTER" alignItems="CENTER" classNames="jmix-login-main-layout">
        <vbox id="registrationFormWrapper"
              width="22.5em"
              alignItems="CENTER"
              classNames="registration-form-layout">

            <h2 text="msg://registrationHeader.text"/>

            <formLayout>
                <textField id="firstNameField" label="msg://firstNameField.title"/>
                <textField id="lastNameField" label="msg://lastNameField.title"/>
                <textField id="usernameField" label="msg://usernameField.title"/>
                <textField id="emailField" label="msg://emailField.title"/>
                <passwordField id="passwordField" label="msg://passwordField.title"/>
            </formLayout>

            <button id="registerBtn" text="msg://registerBtn.text" themeNames="primary" width="100%"/>

            <button id="alreadyRegisteredBtn" text="msg://alreadyRegisteredBtn.text" themeNames="tertiary small"/>

        </vbox>
    </layout>
</view>

RegistrationView.java

import com.company.demo.app.RegistrationService;
import com.company.demo.view.login.LoginView;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import io.jmix.flowui.ViewNavigators;
import io.jmix.flowui.component.textfield.JmixPasswordField;
import io.jmix.flowui.component.textfield.TypedTextField;
import io.jmix.flowui.view.*;
import org.springframework.beans.factory.annotation.Autowired;

@AnonymousAllowed
@Route(value = "registration")
@ViewController("RegistrationView")
@ViewDescriptor("registration-view.xml")
public class RegistrationView extends StandardView {

    @ViewComponent
    private TypedTextField<String> firstNameField;
    @ViewComponent
    private TypedTextField<String> lastNameField;
    @ViewComponent
    private TypedTextField<String> usernameField;
    @ViewComponent
    private TypedTextField<String> emailField;
    @ViewComponent
    private JmixPasswordField passwordField;

    @Autowired
    private ViewNavigators viewNavigators;
    @Autowired
    private RegistrationService registrationService;

    @Subscribe("registerBtn")
    public void onRegisterBtnClick(final ClickEvent<Button> event) {
        // some exception handling is needed in case username
        // or email are already taken or password too weak
        registrationService.register(
                firstNameField.getValue(),
                lastNameField.getValue(),
                usernameField.getValue(),
                emailField.getValue(),
                passwordField.getValue()
        );

        navigateToLoginView();

    }

    @Subscribe("alreadyRegisteredBtn")
    public void onAlreadyRegisteredBtnClick(final ClickEvent<Button> event) {
        navigateToLoginView();
    }

    private void navigateToLoginView() {
        viewNavigators.view(LoginView.class)
                .navigate();
    }
}

and a service to delegate user creation:

RegistrationService

import com.company.demo.entity.User;
import io.jmix.core.UnconstrainedDataManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class RegistrationService {

    private final UnconstrainedDataManager unconstrainedDataManager;
    private final PasswordEncoder passwordEncoder;

    public RegistrationService(UnconstrainedDataManager unconstrainedDataManager,
                               PasswordEncoder passwordEncoder) {
        this.unconstrainedDataManager = unconstrainedDataManager;
        this.passwordEncoder = passwordEncoder;
    }

    public void register(String firstName, String lastName, String username, String email, String password) {
        // check of  username or email are already taken or password too weak
        User user = unconstrainedDataManager.create(User.class);
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(passwordEncoder.encode(password));

        unconstrainedDataManager.save(user);
    }
}

IN order to open this view, you can use the forgotPassword button from loginForm with custom text, e.g.:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<view xmlns="http://jmix.io/schema/flowui/view"
      focusComponent="login"
      title="msg://LoginView.title">
    <layout justifyContent="CENTER" alignItems="CENTER" classNames="jmix-login-main-layout">
        <loginForm id="login"
                   rememberMeVisible="true"
                   forgotPasswordButtonVisible="true">
            <form title="msg://loginForm.headerTitle"
                  username="msg://loginForm.username"
                  password="msg://loginForm.password"
                  rememberMe="msg://loginForm.rememberMe"
                  submit="msg://loginForm.submit"
                  forgotPassword="msg://loginForm.register"/>
            <errorMessage title="msg://loginForm.errorTitle"
                          message="msg://loginForm.badCredentials"/>
        </loginForm>
    </layout>
</view>

and handle its click

@Route(value = "login")
@ViewController("LoginView")
@ViewDescriptor("login-view.xml")
public class LoginView extends StandardView implements LocaleChangeObserver {

    ...

    @Autowired
    private ViewNavigators viewNavigators;

    ...

    @Subscribe("login")
    public void onLoginForgotPassword(final AbstractLogin.ForgotPasswordEvent event) {
        viewNavigators.view(RegistrationView.class)
                .navigate();
    }

    @Override
    public void localeChange(LocaleChangeEvent event) {
        ...
        form.setForgotPassword(messageBundle.getMessage("loginForm.register"));
        ...
    }
}

I also added custom styles for register form:

.registration-form-layout {
  padding: var(--lumo-space-l);
  background: var(--lumo-base-color) linear-gradient(var(--lumo-tint-5pct), var(--lumo-tint-5pct));
}

You can find the above code in the demo project attached:

flowui-user-registration-sample.zip (399.5 KB)

Regards,
Gleb

1 Like

Thank you very much for your solution.
But in order for the validations to apply, I have to define it manually within the view, because none of the validations of the Entity itself are validated and the row for the table is added empty.