2020年1月13日

HTML vs. JavaScript Input Validation

If you want to validate a form input and so it only accepts integers, here is an easy way in HTML5:

<input type="number" pattern="[0-9]*" />

However, users can still type something invalid:

  1. In Firefox, you can basically type anything, like “fsielfs”.
  2. In Chrome, you can type numbers with dots, like “1..2.2.2”.

The input will have error appearance. But if you override the default appearance, you have to style it with :invalid selector:

input[type="number"]:invalid {
    border-color: red;
}

But for JavaScript, it is almost a black box:

  1. It won’t trigger change event.
  2. value property is empty.
  3. validity property only provides a couple of boolean flags.

So JavaScript can neither know what the user inputs or correct them. Why?

By specifying type="number" and pattern="[0-9]*", you leave the validation job to browser. When the input value is invalid, it will urge users to correct them. JavaScript can know the input is invalid by checking e.target.validity. However, it won’t tell JavaScript more details. HTML form validation is very friendly for screen reader and it is easy to implement. It is already a part of web standard. All these cannot be easily achieved by JavaScript-only form validation.

But if we still want to validate and filter the input with JavaScript…

Solution 1: use type="text".

Now you can capture every character that is typed. You can validate e.target.value in change event and filter out anything that you don’t like. This is for sure the most powerful yet easy solution.

The disadvantage is that, when users typing in mobile devices, the normal keyboard will pop up instead the convenient number keyboard.

Solution 2: listen to input event.

Unlike change event, input event will be triggered whenever the content of input box is modified. You can check the content in e.data and store it.

The disadvantage is that, you don’t know where the cursor is. When users type something, you know what they typed but not where they inserted these content. When users hit backspace or delete key, you won’t know how many characters they deleted and where they deleted these characters. So you should know it is not 100% reliable.

In some cases, you can combine it with change event. You store every valid state. If something invalid was input, revert to previous state. However, not every input is always valid during typing. Email address is a good example.

Note: you should not use keydown or keyup because the input may from clipboard, voice control or virtual keyboard, which doesn’t trigger keydown or keyup events.

In conclusion, yes, you can do JavaScript-only form validation, but I won’t recommend it. Both solutions I write above have their limitations and are against the principles of web standard. Combining both HTML and JavaScript form validation can bring you a much better result.

2019年10月5日

2019年10月2日

Code Journey #11

Highlights for the last month: HiDPI bug fixes and emulator packaging.

KDE:

  • Kompare HiDPI [patch]
  • Filelight HiDPI [patch]
  • KSysGuard HiDPI, except the sensor graphy [patch]
  • Font manager HiDPI [patch]
  • enablefont and disablefont icon for font manager [patch]
  • KWallet HiDPI [patch]
  • KWin HiDPI [patch]
  • Krita splash screen HiDPI [patch]
  • Kate/KonsolePart dual screen rendering issue [bug] [patch]
  • Spectale Dual Screen issue [bug] [patch]

openSUSE:

  • Update python-PyMuPDF package and fix linking issue
  • Update arcanist package and submit to Factory [request]
  • Submit PCSX2 package to Factory [request]
  • Update retroarch package to work out-of-box [request 1] [request 2]
  • Create retroarch-assets package [request]
  • Create retroarch-joypad-autoconfig package [request]
  • Create libretro-core-info package [request]
  • Create libretro-database package [request]
  • Create libretro-mame2000 core package [request]
  • Create libretro-genesis-plus-gx core package [request]
  • Create libretro-flycast core package [request]
  • Create libretro-yabause core package [request]

2019年9月11日

Code Journey #10

KDE:

  • Fixed JuK folder dialog always open on start
  • Translate tech base
  • POTD doesn’t change daily
  • Bluetooth headset not detected when auto connect. Identify it as Linux kernel bug. Report to upstream

openSUSE:

  • Update Wiki FAQ page, still in progress
  • Chameleon theme: new navbar and footer design
  • Chameleon theme: dark mode
  • Apply theme to doc.opensuse.org
  • Update wiki theme

2019年7月20日

2019年6月26日

Code Journey #8

openSUSE

Wiki

  • Update FAQ pages

Plasma theme

  • Update openSUSE color scheme
  • Improve panel transparency

opi

  • Fix system version number space issue in Leap 15.1

Geeko Store

  • OBS/PMBS search API integration
  • RPM package listing

Packaging

  • Update php-composer
  • Create npm2rpm
  • Packaging npmjs-gulp-cli
  • Packaging npmjs-create-react-app
  • Packaging npmjs-webpack-cli
  • Update patterns-base, remove dejavu-fonts recommendation,to improve emoji support

KDE

Translation

  • KDE Connect

2019年6月5日

Code Journey #7

openSUSE

opi

  • Fixed hard-coded credential issue. Use a proxy server to send API requests
  • Support Packman package search
  • Resolved dependency not found issue

chameleon

  • Added theme document web page
  • Support hot reload during development
  • Added dark mode buttons and form controls
  • Improve theme style

Packaging

  • Update python-PyMuPDF

Other

Rabbit Lyrics

  • Support multiple lyrics blocks

2019年6月2日

Fix React issues with Google Translate

Note: we are only talking about Google Translate function in Chrome or Chromium based browsers. Other translate plugins or software don’t necessarily work with this solution. And this solution doesn’t solve all issues, just a part of them.

When does it happen

You have a component:

const Button = ({children, icon, isLoading, ...rest}) => (
  <button {...rest}>
    {icon}
    {children}
    {isLoading && <Loader/>}
  </button>
)
...

<Button icon="+">Hello World</Button>

Which is rendered to:

<button>+Hello World</button>

If an element (<button> in this case) contains multiple rendered string variables, they become text nodes in React’s VDOM and HTML DOM. However, Google Translate doesn’t care about text nodes and wrap them inside a <font> element:

<button><font>+你好世界</font><button>

Since they are not text nodes anymore, synchronization between React and DOM was broken. The button content will not be updated anymore.

How to fix it

Simply avoid this situation. Write every {variable} inside an element, as the only child.

const Button = ({children, icon, isLoading, ...rest}) => (
  <button {...rest}>
    <span className="button__icon">{icon}</span>
    <span className="button__text">{children}</span>
    {isLoading && <Loader/>}
  </button>
)
...

<Button icon="+">Hello World</Button>

Now every variable is synchronized with the <span> element, not text nodes. Inserting <font> elements doesn’t break the connection.

Another situation is that when you mix text nodes with elements.

<p>
  Copyright 2016-{new Date().getFullYear()}
</p>

Should be converted to:

<p>
  <span>Copyright 2016-</span>
  <span>{new Date().getFullYear()}</span>
</p>

Just make sure: a text node or string variable must be the only child of an element.

Event target

Another issue is that the click event target will change. It might be <font> elements instead of <button>. Carefully check everywhere you use e.target, considering they might not be the button, input or other elements you expected.

A further suggestion is to avoid using any e.target reference. Here is a piece of legacy code:

const {sizes, onChange} = this.props
return (
  <div>
    {sizes.map(size => (
      <button
        key={size}
        name="size"
        value={size}
        onClick={e => this.props.onChange(e.target.value)}>
        {size.toUpperCase()}
      </button>
    ))}
  </div>
)

Can be changed to:

const {sizes, onChange} = this.props
return (
  <div>
    {sizes.map(size => (
      <button
        key={size}
        name="size"
        value={size}
        onClick={e => this.props.onChange(size)}>
        {size.toUpperCase()}
      </button>
    ))}
  </div>
)

If you use third-party components from a library

In this case, you usually cannot modify the component as you want.

You can try to use <span>your string</span> instead of "your string" as component properties.

Report to the library maintainers and make a PR with above methods.

If the element doesn’t support child elements

Some components doesn’t support inner wrappers, like <option>. If you put <span> inside <option>, React will give you warnings in console, even though the rendering works. So you probably don’t want to do it.

For example you have:

<select>
  {fruites.map(f => <option value={f.name}>{f.name} - {f.price}</option>)}
</select>

Change it to:

<select>
  {fruites.map(f => {
    const label = f.name + ' - ' + f.price;
  	return <option value={f.name}>{label}</option>
  })}
</select>

If you want to disable Google Translate

Even if you do all above, something can still go wrong. If you don’t have time to waste and just want to disable Google Translate, it is simple:

<body class="notranslate">

2019年5月5日

Code Journey #6

To avoid too many short posts, I will summarize my work weekly in future. Post date will be the every Sunday.

openSUSE

opi

  • Install Packman codecs, VS Code, Skype
  • PMBS search function research (once get a user account, it is ready to go)

Packaging

  • Update adobe-sourcehansans-fonts and adobe-sourcehanserif-fontswith additional provides for languages

KDE

Translation

  • Keep core translation 100%
  • Translate Kdenlive
  • Synchronize translation to SVN

2019年5月1日

Code Journey #5

openSUSE

opi

  • First version released
  • Packaging and submit to openSUSE Factory

Packaging

  • Update adobe-sourcehansans-fonts
  • Update adobe-sourcehanserif-fonts
  • Update google-noto-emoji-fonts