codevise/pageflow

View on GitHub
package/src/ui/views/inputs/NumberInputView.js

Summary

Maintainability
A
2 hrs
Test Coverage
import Marionette from 'backbone.marionette';

import {inputView} from '../mixins/inputView';

/**
 * Input view for a number.
 *
 * See {@link inputView} for further options.
 *
 * @param {Object} [options]
 *
 * @param {string} [options.locale]
 * Locale used to fomat and parse numbers.
 *
 * @class
 */
export const NumberInputView = Marionette.ItemView.extend({
  mixins: [inputView],

  template: () => `
    <label>
      <span class="name"></span>
      <span class="inline_help"></span>
    </label>
    <input type="text" dir="auto" />
  `,

  ui: {
    input: 'input'
  },

  events: {
    'change': 'onChange'
  },

  initialize() {
    this.parser = new NumberParser(this.options.locale);
  },

  onRender: function() {
    this.load();
    this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
  },

  onChange: function() {
    this.save();
    this.load();
  },

  onClose: function() {
    this.save();
  },

  save: function() {
    const inputValue = this.ui.input.val();
    this.model.set(this.options.propertyName, this.parser.parse(inputValue) || 0);
  },

  load: function() {
    const input = this.ui.input;
    const value = this.model.get(this.options.propertyName) || 0;

    input.val(value.toLocaleString(this.options.locale, {useGrouping: false}));
  },

  displayValidationError: function(message) {
    this.$el.addClass('invalid');
    this.ui.input.attr('title', message);
  },

  resetValidationError: function(message) {
    this.$el.removeClass('invalid');
    this.ui.input.attr('title', '');
  }
});

class NumberParser {
  constructor(locale) {
    const format = new Intl.NumberFormat(locale);
    const parts = format.formatToParts(12345.6);
    const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i));
    const index = new Map(numerals.map((d, i) => [d, i]));

    this._group = new RegExp(`[${parts.find(d => d.type === "group").value}]`, "g");
    this._decimal = new RegExp(`[${parts.find(d => d.type === "decimal").value}]`);
    this._numeral = new RegExp(`[${numerals.join("")}]`, "g");
    this._index = d => index.get(d);
  }

  parse(string) {
    string = string.trim()
                   .replace(this._group, "")
                   .replace(this._decimal, ".")
                   .replace(this._numeral, this._index);
    return string ? +string : NaN;
  }
}