In the last post, we already talked about how to implement Bootstrap Offcanvas in Rails Hotwire.

We are building some product creation features. But, the amount of input still uses the number field, and we can improve that with the money field.

The money field means our text field is responsive to the customer input. It will do some real-time parsing from raw string to currency format.

We will add that feature with Stimulus.js.

money field

First, we need to create our Stimulus Money Controller. You can do that with runs this command in your terminal:

rails g stimulus money

And here’s the implementation:

// app/javascripts/controllers/money_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  toIdr(event){
    const onlyIntegerSubmittedValue = event.srcElement.value.split('').filter((el) => {
      return Number.isInteger(parseInt(el))
    }).join('')

    let idrInReversed = ''
    onlyIntegerSubmittedValue.split('').reverse().forEach((el, index) => {
      if(index % 3 === 0 && index !== 0) {
        idrInReversed += ','
      }
      idrInReversed += el
    })

    const idr = idrInReversed.split("").reverse().join('')
    event.srcElement.value = idr
  }
}

Yes, you can do it with gsub and pattern matching action, but in this blog post, I want to do it with some primitive data structure, only with basic string and array operation.

Then, we need to update our Rails view to integrate our stimulus controller with the actual text input:

<%# /app/views/products/_product.html.erb %>
<div class="mb-3">
  <%= form.label :amount, class: 'form-label' %>
  <div class="input-group">
    <span class="input-group-text">Rp</span>
    <%= form.text_field :amount, class: 'form-control', value: to_idr_number(product.amount), 
        data: { controller: 'money', action: 'keyup->money#toIdr' } %>
  </div>
</div>
# app/helpers/application_helper.rb
module ApplicationHelper
  def to_idr(number)
    number_to_currency(number, unit: 'Rp', locale: :id, precision: 0)
  end

  def to_idr_number(number)
    number_to_currency(number, unit: '', locale: :id, precision: 0)
  end
end

The last thing, we need to update our controller to handle the new input format:

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  # ...
  def product_params
    {
      name: params[:product][:name],
      amount: params[:product][:amount].to_s.delete(',')
    }
  end
end

Finally, we successfully created the money field feature for our application.

The MoneyController is not production used, but feel free to copy and paste the function to the production codebase.

Thank you for reading, and happy hacking!