Trim surrounding whitepaces from "type:tel" field

Hi,

is there a blueprint directive to trim the input of an type:tel field?

fields:
  phoneNumber:
    type: tel

Input (with surrounding whitespaces): 01245 12345

Results in phoneNumber: ‘ 01245 12345 ‘, but wanted is: phoneNumber: 01245 12345(without surrounding whitespaces)

What can i do to solve this?

I wonder if this field type should not be trimmed generally. For what reason would you want whitespaces before or after a phone number?

Thanks and have a nice day.

I dont think there is a built in way for this, however you can use the PHP trim() function to get rid of the spaces before and after the feild value when you out put it in the template.

Thanks for your reply, yes I already do it this way, but for some pedantic reasons, I want a clean as possible content file. I will implement a hook to trim the input.

I had a similiar issue. There is also the fact that there can be invisible unicode characters. For example, when copy-pasting from Apple Contacts or other sources, which my client was doing a lot and that lead to a bunch of difficulties with both validation and when using the numbers in some places. I’ll share my solution in case others find it useful.

You can create a custom validator for your phone field to check for unwanted spaces, but that might not be a great user experience, especially when there are invisible marks involved.

So I ended up doing a custom field extending the default ‘tel’ field, replacing the save method. But I didn’t just want to remove all spaces and odd characters. I needed all phone numbers to be formatted in a standardized way (with spaces and dashes in the correct position and country code added if not provided). I share the code here, but phone number standards varies by country and I only needed to deal with swedish number patterns.

index.php

<?php 
/*
  Clean up phone numbers and add swedish country code if not provided 
   
  Invisible characters removed
  Ignores invisible or non-printing Unicode characters often found when copying from Apple Contacts or similar sources.
  Characters removed by this regex:
  - U+00A0  : NO-BREAK SPACE
  - U+2000–U+200F : Various invisible spaces and directional marks
  - U+202A–U+202E : Bidirectional formatting codes
  - U+2066–U+2069 : Bidirectional isolates
  - U+FEFF : ZERO WIDTH NO-BREAK SPACE (aka BOM - Byte Order Mark)
  - U+FFF9–U+FFFB : Interlinear annotation controls (rare but safe to strip) 
    
  */

Kirby::plugin('tamburlane/telExtended', [
  'fields' => [
    'telX' => [
      'extends' => 'tel',
      'save' => function ($value) {
       
       // Step 1: Clean invisible characters and trim whitespace
       $value = preg_replace('/[\x{00A0}\x{2000}-\x{200F}\x{202A}-\x{202E}\x{2066}-\x{2069}\x{FEFF}\x{FFF9}-\x{FFFB}]/u', '', $value);
       $value = trim($value);
       
       // Step 2: Remove all formatting characters (spaces, dashes, parentheses)
       $number = preg_replace('/[^\d+]/', '', $value);
       
       // Step 3: Convert leading 0 to +46 (assume swedish country code)
       if (preg_match('/^0(\d{6,9})$/', $number, $m)) {
         $number = '+46' . $m[1];
       }
       
       // Step 4: Format known Swedish number types
       
       // Mobile numbers: 07X-XXX XX XX (always 9 digits after +46 7X)
       if (preg_match('/^\+467(\d)(\d{3})(\d{2})(\d{2})$/', $number, $m)) {
         return "+46 7{$m[1]}-{$m[2]} {$m[3]} {$m[4]}";
       }
       
       // Stockholm landline: 08-XX XX XX
       if (preg_match('/^\+468(\d{2})(\d{2})(\d{2})$/', $number, $m)) {
         return "+46 8-{$m[1]} {$m[2]} {$m[3]}";
       }
       
       // Stockholm landline: 08-XXX XX X
       if (preg_match('/^\+468(\d{3})(\d{2})(\d)$/', $number, $m)) {
         return "+46 8-{$m[1]} {$m[2]} {$m[3]}";
       }
       
       // Stockholm landline: 08-XXX XXX
       if (preg_match('/^\+468(\d{3})(\d{3})$/', $number, $m)) {
         return "+46 8-{$m[1]} {$m[2]}";
       }
       
       // Landlines with 2-digit area code (e.g. Göteborg: 031)
       // Format: 031-XX XX XX
       if (preg_match('/^\+46(\d{2})(\d{2})(\d{2})(\d{2})$/', $number, $m)) {
         return "+46 {$m[1]}-{$m[2]} {$m[3]} {$m[4]}";
       }
       
       // Format: 031-XXX XX
       if (preg_match('/^\+46(\d{2})(\d{3})(\d{2})$/', $number, $m)) {
         return "+46 {$m[1]}-{$m[2]} {$m[3]}";
       }
       
       // Format: 031-XX XX
       if (preg_match('/^\+46(\d{2})(\d{2})(\d{2})$/', $number, $m)) {
         return "+46 {$m[1]}-{$m[2]} {$m[3]}";
       }
       
       // Landlines with 3-digit area code (e.g. Gotland: 0498)
       // Format: 0498-XXX XX X
       if (preg_match('/^\+46(\d{3})(\d{3})(\d{2})(\d)$/', $number, $m)) {
         return "+46 {$m[1]}-{$m[2]} {$m[3]} {$m[4]}";
       }
       
       // Format: 0498-XXX XX
       if (preg_match('/^\+46(\d{3})(\d{3})(\d{2})$/', $number, $m)) {
         return "+46 {$m[1]}-{$m[2]} {$m[3]}";
       }
       
       // Format: 0498-XX XX
       if (preg_match('/^\+46(\d{3})(\d{2})(\d{2})$/', $number, $m)) {
         return "+46 {$m[1]}-{$m[2]} {$m[3]}";
       }
       
       // Fallback: return cleaned number without formatting
       return $number;
      }
    ]
  ]
]); 

index.js

panel.plugin("tamburlane/telExtended", {
    fields: {
        telX: {
            extends: "k-tel-field"
        }
    }
});

Then, I made a custom panel form validator that checks min/max length and allowed characters, while ignoring whitespace and invisible characters instead of complaining about them (as the field plugin will clean those on save anyway).

<?php

use Kirby\Toolkit\V;

Kirby::plugin("tamburlane/validators", [
  "validators" => [
    /* A loose check, pretty forgiving
     * 7-17 chars
     * 0-9, +, -, space
     */
    "phone" => function ($value) {
     // Spaces, invisible spaces, formatting marks etc are ignored so we can count length and check that remaining characters looks like a phone number

      $value = preg_replace('/[\x{00A0}\x{2000}-\x{200F}\x{202A}-\x{202E}\x{2066}-\x{2069}\x{FEFF}\x{FFF9}-\x{FFFB}]/u', '', $value);
      //dump(bin2hex($value));
      if (  
        V::minLength($value, 7) && V::maxLength($value, 17) 
        && (V::startsWith($value, '+') || V::startsWith($value, '0'))
        ) 
      {
        return V::match($value, '/^([0-9 +-])+$/i');
      }
      else {
        return false;
      }
    }
  ],
]);

Hey Tim, better late than never… Thanks for your input. I’m actually not sure how I solved it, but this looks fine. :slight_smile:

1 Like