# How to use Stimulus to create a real-time preview?

**URL:** <https://discuss.hotwired.dev/t/how-to-use-stimulus-to-create-a-real-time-preview/185>\
**Category:** General\
**Created:** [March 15, 2018, 9:07pm UTC](https://discuss.hotwired.dev/t/how-to-use-stimulus-to-create-a-real-time-preview/185 "2018-03-15T21:07:14Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![rathboma](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/rathboma/32/110_2.png) [@rathboma](https://discuss.hotwired.dev/u/rathboma)\
**Post date:** [March 15, 2018, 9:07pm UTC](https://discuss.hotwired.dev/t/how-to-use-stimulus-to-create-a-real-time-preview/185/1 "2018-03-15T21:07:14Z")

</div>

Hey folks,

I’m looking to build a simple form building interface where a user would edit attributes for form inputs (like label, placeholder, etc) and have them be represented in real-time in a form preview.

The created forms are rendered (currently) by rails when users share the link to the finished form, or embed with an iframe.

If I build this interface with Angular/React/Vue, then I pretty much have to rewrite the form display logic in that framework too, which I’d prefer not to do, as I like my rails templates as-is.

Has anyone done something like this with Stimulus? If so how?  
I’d assume I’d need to use Stimulus along with some sort of DOM binding library?

Thanks for any direction.

---

<div class="post-metadata">

**Author:** ![Alexandr\_K](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/alexandr_k/32/114_2.png) [@Alexandr\_K](https://discuss.hotwired.dev/u/Alexandr_K)\
**Post date:** [March 16, 2018, 8:47am UTC](https://discuss.hotwired.dev/t/how-to-use-stimulus-to-create-a-real-time-preview/185/2 "2018-03-16T08:47:04Z")

</div>

Hey there,

If I face this task I’ll do it following way:

- create controller and assign it to form or element few levels upper
- render blank preview on server and set `data-target`'s on it
- on inputs add `data-action`'s and handle all logic in controller, like on `input->your_controller#update_label`

---

<div class="post-metadata">

**Author:** ![happysalada](https://avatars.discourse-cdn.com/v4/letter/h/e19adc/32.png) [@happysalada](https://discuss.hotwired.dev/u/happysalada)\
**Post date:** [March 27, 2018, 3:13am UTC](https://discuss.hotwired.dev/t/how-to-use-stimulus-to-create-a-real-time-preview/185/3 "2018-03-27T03:13:23Z")

</div>

I have a signu form, here is the code for it (if you have any improvements ideas, they are welcome !)

```auto
import {Controller} from 'stimulus';

export default class extends Controller {
  static targets = [
    'name', 'nameLabel', 'nameError',
    'email', 'emailLabel', 'emailError',
    'password', 'passwordLabel', 'passwordError',
    'passwordConfirmation', 'passwordConfirmationLabel', 'passwordConfirmationError',
    'form']

  get validName() {
    return this.nameTarget.checkValidity();
  }

  showNameValidation() {
    if (this.validName) {
      this.nameLabelTarget.classList.remove('hidden');
      this.nameErrorTarget.classList.add('hidden');
    } else {
      this.nameLabelTarget.classList.add('hidden');
      this.nameErrorTarget.classList.remove('hidden');
      this.nameErrorTarget.innerHTML = 'name is required';
    }
  }

  get validEmail() {
    return this.emailTarget.checkValidity();
  }

  showEmailValidation() {
    if (this.validEmail) {
      this.emailLabelTarget.classList.remove('hidden');
      this.emailErrorTarget.classList.add('hidden');
    } else {
      this.emailLabelTarget.classList.add('hidden');
      this.emailErrorTarget.classList.remove('hidden');
      this.emailErrorTarget.innerHTML = 'invalid email';
    }
  }

  get validPassword() {
    return this.passwordTarget.checkValidity();
  }

  showPasswordValidation() {
    if (this.validPassword) {
      this.passwordLabelTarget.classList.remove('hidden');
      this.passwordErrorTarget.classList.add('hidden');
    } else {
      this.passwordLabelTarget.classList.add('hidden');
      this.passwordErrorTarget.classList.remove('hidden');
      this.passwordErrorTarget.innerHTML = 'Minimum password length is 6 characters';
    }
  }

  get validPasswordConfirmation() {
    return this.passwordConfirmationTarget.checkValidity() &&
      this.passwordConfirmationTarget.value === this.passwordTarget.value;
  }

  showPasswordConfirmationValidation() {
    if (this.validPasswordConfirmation) {
      this.passwordConfirmationLabelTarget.classList.remove('hidden');
      this.passwordConfirmationErrorTarget.classList.add('hidden');
    } else {
      this.passwordConfirmationLabelTarget.classList.add('hidden');
      this.passwordConfirmationErrorTarget.classList.remove('hidden');
      this.passwordConfirmationErrorTarget.innerHTML = 'Does not match password';
    }
  }

  get valid() {
    return this.validName &&
      this.validEmail &&
      this.validPassword &&
      this.validPasswordConfirmation;
  }

  showValidations() {
    this.showNameValidation();
    this.showEmailValidation();
    this.showPasswordValidation();
    this.showPasswordConfirmationValidation();
  }

  submit(event) {
    event.preventDefault();
    this.emailTarget.value = this.emailTarget.value.trim().toLowerCase();
    this.showValidations();
    if (this.valid) this.formTarget.submit();
  }
}

```

```auto
<%=
  form_for @changeset,
  registration_path(@conn, :create),
  [
    {:as, :registration},
    {:class, "form-validate login-form"},
    {:"data-controller", "signup-form"},
    {:"data-target", "signup-form.form"}
  ],
  fn f -> %>
  <div class="panel panel-body border-white no-panel-shadow">
    <div class="content-divider text-muted form-group"><span>Sign up</span></div>
    <div class="form-group has-feedback has-feedback-right">
      <label data-target="signup-form.nameLabel">
        Name: <span class="text-danger">*</span>
      </label>
      <label
        class="text-danger-700 pull-right"
        data-target="signup-form.nameError"
      >
        <%= error_tag f, :name %>
      </label>
      <%=
        text_input f,
        :name,
        [
          {:class, "form-control"},
          {:"data-target", "signup-form.name"},
          {:required, true},
          {:autofocus, true},
          {:"data-action", "input->signup-form#showNameValidation"}
        ]
      %>
      <div class="form-control-feedback" style="display: none;">
        <i class="icon-cross2 text-danger-700"></i>
      </div>
    </div>
    <div class="form-group has-feedback has-feedback-right">
      <label data-target="signup-form.emailLabel">
        Email: <span class="text-danger">*</span>
      </label>
      <label
        class="text-danger-700 pull-right"
        data-target="signup-form.emailError"
      >
        <%= error_tag f, :email %>
      </label>
      <%=
        email_input f,
        :email,
        [
          {:class, "form-control"},
          {:"data-target", "signup-form.email"},
          {:required, true},
          {:"required pattern", ~S'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$' },
          {:"data-action", "input->signup-form#showEmailValidation"}
        ]
      %>
    </div>
    <div class="form-group has-feedback has-feedback-right">
      <label data-target="signup-form.passwordLabel">
        Password: <span class="text-danger">*</span>
      </label>
      <label
        class="text-danger-700 pull-right"
        data-target="signup-form.passwordError"
      >
        <%= error_tag f, :password %>
      </label>
      <%=
        password_input f,
        :password,
        [
          {:class, "form-control"},
          {:"data-target", "signup-form.password"},
          {:required, true},
          {:minlength, "6"},
          {:"data-action", "input->signup-form#showPasswordValidation"}
        ]
      %>
    </div>
    <div class="form-group has-feedback has-feedback-right">
      <label data-target="signup-form.passwordConfirmationLabel">
        Confirm password: <span class="text-danger">*</span>
      </label>
      <label
        class="text-danger-700 pull-right"
        data-target="signup-form.passwordConfirmationError"
      >
        <%= error_tag f, :password_confirmation %>
      </label>
      <%=
        password_input f,
        :password_confirmation,
        [
          {:class, "form-control"},
          {:"data-target", "signup-form.passwordConfirmation"},
          {:required, true},
          {:"data-action", "input->signup-form#showPasswordConfirmationValidation"}
        ]
      %>
    </div>

    <div class="form-group login-options">
      <div class="row">
        <div class="col-sm-6">
          <label class="checkbox-inline">
            <input type="checkbox" id="remember" class="styled" name="remember" checked="checked">
            Remember
          </label>
        </div>

        <div class="col-sm-6 text-right">
          <a href="/passwords/new">Forgot password?</a>
        </div>
      </div>
    </div>

    <div class="form-group">
      <%=
        submit dgettext("coherence", "Sign up"),
        [
          {:class, "btn btn-primary btn-block"},
          {:"data-action", "click->signup-form#submit"}
        ]
      %>
    </div>

    <div class="content-divider text-muted form-group"><span>Already signed up?</span></div>
    <a href="/sessions/new" class="btn btn-default btn-block content-group">Login</a>
    <span class="help-block text-center no-margin">By continuing, you're confirming that you've read our <a href="#">Terms &amp; Conditions</a> and <a href="#">Cookie Policy</a></span>
  </div>
<% end %>

```

The html part is in a templating language, so you’ll have to convert that to html, but it’s pretty straightforward. Let me know if anything doesn’t make sense

---

<div class="post-metadata">

**Author:** ![rathboma](https://yyz1.discourse-cdn.com/flex027/user_avatar/discuss.hotwired.dev/rathboma/32/110_2.png) [@rathboma](https://discuss.hotwired.dev/u/rathboma)\
**Post date:** [February 27, 2019, 3:58pm UTC](https://discuss.hotwired.dev/t/how-to-use-stimulus-to-create-a-real-time-preview/185/4 "2019-02-27T15:58:57Z")

</div>

For those interested – I made a prototype, but actually ended up abandoning the stimulus version in favor of a simple Vue.js app.

There was so much rendering logic involved that using Vue + a Rails API ended up being much more simple.
