Wednesday, 18 February 2015

XPages PhoneNumber Control - Part 1 Creating a Converter


This is the first of a multi-post series of creating a Phone Number control for XPages.

What good is a phone number if it isn't a valid one? and What good is a phone number if you don't know how to dial it from the country you are in? What if you want to restrict phone number entry to just one country? or maybe a just a few Countries?
In this series we will go through the steps of creating our own re-usable phone number entry/display control, that can be installed once, and re-used in every nsf.

Our first step will be to create our own Phone Number Converter. Our converter's task it to take the input string and convert it to a standard format for storing phone numbers.

Thanks to the nature of open source, someone else has already done the hard part for us we will be using google's libphonenumber library to convert, validate and manipulate phone numbers.

After this post we won't have our own control yet, but at least we will have a converter that we can use for a normal inputText control.

Lets get started, but before we do, a quick word on how to deploy this solution. I will be doing all the initial development within a single NSF. XPages Component development is much easier when done within an NSF as you can test your changes easily without package / reinstalling / restarting Domino Designer. In the last blog post I will take the code out of the NSF and put it in an XspLibrary OSGi plugin so that it can be reused throughout any NSF.
So, if you are only interested in deploying this solution for one NSF then you can just ignore the last 'plugin' post. If you want to deploy via a plugin, then pay attention to everything!

 Download and Install the libphonenumber library


The github repository for the library is googlei18n/libphonenumber. Have a look at the README file for this project for latest instruction on where to download the jar.
At the time of writing, it said to go to http://repo1.maven.org/maven2/com/googlecode/libphonenumber/libphonenumber/

7.0.2 was the latest version at time of writing



Then within the 7.0.2 folder I downloaded libphonenumber-7.0.2.jar



 Once you have the Jar import it into the Jars design element section


How will we use the library?


The libphonenumber library allows us to take a string that *might* be a phone number, attempt to parse it into a phone number and report if it is a valid one or not.
If it is a valid number then we could choose to format it many different ways.

The phone number library has the concept of a 'national' number and an 'international' number. The national number is the number you would dial if you are within the same country, and the international number is the number that you would dial from another country. Our converter will make sure that all numbers are stored as the international number.

For example here in Australia, the national and international number for Google's Sydney office
National Number : 02 9374 4000
International Number : +61 2 9374 4000

Many users would simply enter a 'national' number, without a country dialing code.
When users enter a national phone number we can provide a hint as to which country the number might be from so that we can correctly convert it into an international number.

Create Version 1 of the PhoneNumberConverter


To start with, our converter will try to convert the input to an international number, if there is no international dialing code included in the input string, will use Australia (AU) as the default code. Later on we will add options so that the default Country can be changed but for now we will hard code it as AU.

To create our converter, we create a new Java Class in the Code -> Java design elements.
The main point is that the class should implement the javax.faces.convert.Converter interface



So at this point we should have a blank converter that does nothing but make everything null, which is useless, so lets fix it.


The converter interface has 2 methods which we need to implement, both methods accept 3 parameters, the current FacesContext, the Component that the input belongs to, and the value to be converted.
  • getAsObject - Takes the user input string from the UIComponent and converts it to whatever you want for the model
  • getAsString - Takes the object from the model and converts it to the string required for the UI component
If there is a problem with conversion, we need to throw a ConverterException, which takes a FacesMessage as a parameter.

For our getAsString method, we don't need to do much at all. Our Phone number should already be stored in the model as a String (in International format), so no conversion should be required, we will simply return the String representation of the value to be converted.

For our getAsObject method, it is more complicated, we will try to parse the input string using the PhoneNumberUtil class from the libphonenumber library. If there is a problem parsing (e.g. has text) we will throw a ConverterException, if the number can be parsed, but does not seem to be valid then we will throw a ConverterException, otherwise we will return the International Format of the successfully parsed number.

Here is our PhoneNumberConverter Version 1:

package com.gregorbyte.xsp.converter;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.ibm.commons.util.StringUtil;

public class PhoneNumberConverter implements Converter {

 public Object getAsObject(FacesContext context, UIComponent component,
   String value) {

  if (StringUtil.isEmpty(value))
   return value;

  PhoneNumberUtil util = PhoneNumberUtil.getInstance();
  String defCountryCode = "AU";

  try {

   PhoneNumber number = util.parse(value, defCountryCode);

   if (!util.isValidNumber(number)) {

    FacesMessage fm = new FacesMessage();
    fm.setSummary("Phone Number is not valid for country code "
      + defCountryCode);
    fm.setDetail("The supplied Phone Number is not valid to the country code "
        + defCountryCode);
    fm.setSeverity(FacesMessage.SEVERITY_ERROR);

    throw new ConverterException(fm);

   } else {
    return util.format(number, PhoneNumberFormat.INTERNATIONAL);
   }

  } catch (NumberParseException e) {

   FacesMessage fm = new FacesMessage();
   fm.setSummary("The Phone Number entered is not in a valid format for "
       + defCountryCode);
   fm.setDetail(e.getMessage());
   fm.setSeverity(FacesMessage.SEVERITY_ERROR);

   throw new ConverterException(fm);
  }
 }

 public String getAsString(FacesContext context, UIComponent component,
   Object value) {

  if (value == null)
   return null;
  return value.toString();

 }

}

Register the Converter with Faces-Config

Our next step is to make sure the XPages runtime knows that we have a converter available to be used. We do this by a configuration entry of a  <converter> tag in the Faces-Config file.


We need to specify, the java class of the converter, and a converter Id  that we want the converter to be known by. I will choose the id 'gregorbyte.PhoneNumberConverter'for this one

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
 <converter>
  <converter-id>gregorbyte.PhoneNumberConverter
  </converter-id>
  <converter-class>com.gregorbyte.xsp.converter.PhoneNumberConverter
  </converter-class>
 </converter>
</faces-config>

Demo using the Converter via an <xp:converter> tag

Now we should be ready to test out the converter! To do this I created a simple XPage with an input text box (bound to a viewScope variable), a message control (to display conversion error message) and a submit button.

Take note of how we have attached the converter to the inputText control, using the <xp:converter> tag and using the converterId that was specified in the faces-config. Later on we will use a different method with our own custom tag to use instead but for now we are just using this <xp:converter> tag method.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

 <xp:label value="Phone Number" id="label1" for="inputText1"></xp:label>

 <xp:inputText id="inputText1" value="#{viewScope.phoneNumber}">
  <xp:this.converter>
   <xp:converter converterId="gregorbyte.PhoneNumberConverter">
   </xp:converter>
  </xp:this.converter>
 </xp:inputText>

 <xp:message id="message1" for="inputText1"></xp:message>

 <xp:button value="Submit" id="button1">
  <xp:eventHandler event="onclick" submit="true"
   refreshMode="complete">
  </xp:eventHandler>
 </xp:button>

</xp:view>

And here is what it looks like when rendered:

Testing

So lets try out a clearly invalid phone number which should fail

result after submitting:

Good! how about now I enter the Google Sydney office number, but I will not include the area code (02), there should not be enough information to validate the number and it should fail conversion.

and after submitting:

Excellent, now how about I enter the area code but exclude the international code (+61), this should work just fine because I have hard-coded the converter to use Australia as the default country.

and after submitting:

Perfect, it has validated the number and converted it to international format

Ok what now if I enter a number from another Country (using the full international number format), lets enter google's California number and see what happens

and after submitting:


Good there is no conversion error, the number had the international dialing code included so did not need to use the default country code to figure it out, and it has formatted the number into the international format.

Conclusion for Part 1 


So what have we done so far? We downloaded the libphonenumber library, created our first version of the converter and registered it with faces-config. We have tested it out using a simple XPage.

In the next post, we will create our own custom tag to be selected from the 'converters list' and we will allow the defaultCountryCode to be specified on the XPage instead of hardcoded into the converter.

I have put the code up on github under camac/XPagesPhoneNumberControl, I have created a tag part1 which should be all the code up until this point!

I hope you enjoyed the post and if you have any questions (or corrections!) please leave a comment!

2 comments:

  1. Great great post!! Thanks for posting. I started playing with this today.

    ReplyDelete
    Replies
    1. Thanks! I am glad to hear that it's helpful. I didn't know about the libphonenumber library before starting this little project and I think that it be very useful in lots of places

      Delete