Search

What the Quote?

"Spain used to be all powerful. Now they just nap all the time."

Laura Tripcony

"Under Bob Balaban's beard, there is not a chin. There is only another API."

Nathan T Freeman

"You're like a lint brush."

Laura Tripcony

« a potential evolution in XPages: custom expression language resolvers | Main| I will be speaking at Lotusphere 2011 »

extending XspInputText to add placeholder support

Category xpages
Last week, Jake called attention to the usability flaw in web forms that specify a default value for fields but clear that value on focus without checking first to see if the field's value has since been changed from the default. He included a code example demonstrating a way to perform such a check, ensuring that a user's input will not be programmatically deleted if they later return to the same field.

A couple readers proposed an alternate approach: using the "placeholder" attribute as described in the HTML5 specification. Browsers that support this attribute display the value of the attribute within the field when the field has no value - even if it used to - but at a lower opacity. Hence, the user is given a visual indication that this text provides a hint for what to enter in the field, not a value that is already assigned to the field. In other words, the behavior is quite similar to the way the Lotus Notes client renders the "Field Hint" property for a field on a Form element: the only significant difference is that, upon exiting a Notes field, the hint does not reappear, even if the field is still (or again) empty; as previously mentioned, browsers that support the placeholder attribute will again display its value when the focus leaves the field if that field has no value. Browsers that do not support this attribute simply ignore it, so users of those browsers will not see the hint, but will also not encounter errors. This is a great example of "progressive enhancement": users of older or less standards-compliant browsers can still use all of the site's functionality, but as those users upgrade / replace their browser, they have a richer experience without any additional effort on your part.

Yesterday, Tommy posted a demonstration of using hidden inputs to glean the values of "passthru" input tags, which allows him to specify HTML5 attributes on the passthru tags. He created a custom control that allows this technique to be reusable.

The remainder of this post will demonstrate how to create an extension to the native Edit Box component to add support for HTML5 attributes like placeholder.

The good news, for those of you who are currently unable to upgrade to 8.5.2 or to install component extension libraries (or both), is that this technique requires neither: this extension can be implemented in 8.5.1 (in all likelihood, even 8.5.0), and the instructions that follow assume you will be implementing them in "single-NSF" mode. If you'd rather deploy such an extension as part of a component library, IBM has included documentation for how to bundle the files you're about to see into such a library in the Extension Library project on OpenNTF.

Step 1: defining the component

Every extension component is defined by, at a minimum, one Java class that specifies the properties and methods of the component. Let's take a look at such a class:

  1. package com.timtripcony.xsp.component.html5;
  2.  
  3. import javax.faces.context.FacesContext;
  4. import javax.faces.el.ValueBinding;
  5. import com.ibm.xsp.component.xp.XspInputText;
  6.  
  7. public class Html5InputText extends XspInputText {
  8.  private String _placeHolder;
  9.  
  10.  public Html5InputText() {
  11.  
  12.  }
  13.  
  14.  public String getPlaceholder() {
  15.   if (null != this._placeHolder) {
  16.    return this._placeHolder;
  17.   }
  18.   ValueBinding _vb = getValueBinding("placeholder");
  19.   if (_vb != null) {
  20.    return (java.lang.String) _vb.getValue(getFacesContext());
  21.   } else {
  22.    return null;
  23.   }
  24.  }
  25.  
  26.  public void setPlaceholder( String _placeHolder) {
  27.   this._placeHolder = _placeHolder;
  28.  }
  29.  
  30.  public String getRendererType() {
  31.   return "com.timtripcony.xsp.Html5Input";
  32.  }
  33.  
  34.  public void restoreState(FacesContext _context, Object _state) {
  35.   Object _values[] = (Object[]) _state;
  36.   super.restoreState(_context, _values[0]);
  37.   this._placeHolder = (String) _values[1];
  38.  }
  39.  
  40.  public Object saveState(FacesContext _context) {
  41.   Object _values[] = new Object[2];
  42.   _values[0] = super.saveState(_context);
  43.   _values[1] = getPlaceholder();
  44.   return _values;
  45.  }
  46.  
  47. }
  48.  


As you can see, our class declaration states that we're extending XspInputText. This is the Java class associated with the native Edit Box component. Hence, by extending that class, we're declaring that we want something that inherits all of its functionality but will have certain additional characteristics of its own.

In this example, we're really just looking to add one additional attribute: placeholder. So we declare a private String to store it, and a "getter" and "setter" method to, respectively, retrieve and change the value for the attribute. The syntax of the getter may look a bit odd, but this follows the exact syntax IBM uses whenever they define a getter for a component attribute. What this syntax essentially does is allows the attribute to support both dynamic and static values; in the case of placeholder, this will allow the attribute to be evaluated correctly whether we use EL or just type in a value.

In addition to providing a getter and setter, we want the value of the attribute to be maintained across requests (for example, if a partial refresh event targets this component or one that contains it). We do this by overriding the saveState and restoreState methods of the parent class. In saveState, we create a generic Object array, call the saveState method as it is defined in the parent class and store the result in the first index of the array, and then store any additional properties we want to maintain in additional indices... since we're only adding one additional property, we only need one additional index. In restoreState, we reverse the process: we pass the first index of the restored array to the parent method, then retrieve the placeholder from the second index. In deep component hierarchies, then, the serialized version of each component becomes a nested array of arrays, which seems complicated to maintain, except that in each class we only have to worry about the properties defined at that level. This state serialization concept is what allows the XPage runtime (as of 8.5.2) flexibility regarding the performance priorities for each application: speed, scalability, or a "happy medium".

One last note about the component class: strictly speaking, the rendererType attribute should be handled in precisely the same way as the placeholder (i.e. private String, getter/setter, and state management), but as an explanation of providing support for multiple renderers is best left for another day, this example simply hardcodes a single renderer ID.

Step 2: defining a renderer

Now that we've defined what this component is, we'll define what it should look like.

  1. package com.timtripcony.xsp.renderkit.html_basic;
  2.  
  3. import java.io.IOException;
  4. import javax.faces.component.UIInput;
  5. import javax.faces.context.FacesContext;
  6. import javax.faces.context.ResponseWriter;
  7. import com.ibm.commons.util.StringUtil;
  8. import com.ibm.xsp.renderkit.html_basic.InputTextRenderer;
  9. import com.timtripcony.xsp.component.html5.Html5InputText;
  10.  
  11. public class Html5InputTextRenderer extends InputTextRenderer {
  12.  
  13.  protected void writeTagHtmlAttributes(FacesContext context,
  14.    UIInput component, ResponseWriter writer, String inputValue)
  15.    throws IOException {
  16.   super.writeTagHtmlAttributes(context, component, writer, inputValue);
  17.   Html5InputText html5Component = (Html5InputText) component;
  18.   String placeHolder = html5Component.getPlaceholder();
  19.   if (StringUtil.isNotEmpty(placeHolder)) {
  20.    writer.writeAttribute("placeholder", placeHolder, "placeholder");
  21.   }
  22.  }
  23.  
  24. }


This bit is even easier than defining the component itself. As before, there's already a class that does almost everything we want, so we just extend it to add the new functionality. In this case, we're extending InputTextRenderer, which already includes all of the logic for turning a standard Edit Box into HTML: writing out attributes, binding events, and so forth. Hence, all we need to do is override the writeTagHtmlAttributes method. We start by calling the parent method, which writes all of the standard attributes. Then we determine whether the placeholder attribute has a value and, if it does, write that value to the tag being rendered. That's it.

Step 3: telling Designer the component exists

Finally, we need to alert Designer (and the XPage runtime) to the existence of our new control. This is done by creating one XML file and modifying another.

Create a file with a .xsp-config extension. The actual filename is arbitrary; as long as the file extension is correct, the filename does not matter. Furthermore, you can theoretically place this file anywhere within the NSF folder structure and Designer will find it. However, I recommend placing these files in /WebContent/WEB-INF. Since the file we'll soon be modifying is already there, it just makes things easier to keep track of.

Since I'm bumping up against the 32KB summary limit, I've posted the example xsp-config file as a separate page. The namespace URI and prefix are, again, arbitrary, as long as the relationship between the two is maintained. For instance, you could actually add your own components to the xp or xc namespace, as long as you specify the same URI that IBM does, but please, please don't. That would ultimately just cause confusion. In this example, I'm defining "html5" as the prefix, so I thought it fitting to use the URL of the HTML5 spec as the namespace URI. Hopefully, most of the remaining contents of the file are self-explanatory, but I specifically want to call attention to the base-component-type tag. This tells Designer that this component supports all of the properties an Edit Box supports, in addition to the new property it defines. There are all manner of additional items that can be defined in this file, but, again... another day.

Once we've saved this file, Designer immediately knows this component exists, so we can add one to our page; once we do, and select it, the All Properties pane will allow us to set a value for the placeholder attribute. In the Source pane, it will look a little something like this:

<html5:inputText id="myInputText" placeholder="Hello World!"></html5:inputText>


Finally, open up the faces-config.xml file in /WebContent/WEB-INF, and add the following lines:

<render-kit>
 <renderer>
  <component-family>javax.faces.Input</component-family>
  <renderer-type>com.timtripcony.xsp.Html5Input</renderer-type>
  <renderer-class>com.timtripcony.xsp.renderkit.html_basic.Html5InputTextRenderer</renderer-class>
 </renderer>
</render-kit>


This associates any component with the rendererType we hardcoded in Step 1 with the renderer we defined in Step 2. Whenever the runtime needs to render the component we've defined, it now knows to use our renderer.

So that's all there is to it. I realize this probably seems like a lot of work just to get an XPage to add a few additional characters to the HTML output, but once you've gotten used to defining components, creating a new one only takes a few minutes. Furthermore, when you want to add properties to a component you've already defined, all you have to do is tweak the corresponding files. For instance, you could take this example component and add support for additional attributes with just a few lines of code. Given the alternatives, such as waiting for IBM to add the features you want in a subsequent release, or simply settling for what has already shipped, it's well worth the extra effort... especially when you start writing components that don't even exist out of the box.

Comments

Gravatar Image1 - Thanks Tim. That's a good simple sample for how people can use the Extensibility API. There are some more simple samples on OpenNTF: { Link }

Gravatar Image2 - Excelent.

Thanks tim

Gravatar Image3 - Great Post. It would be awesome to have an Eclipse plugin that would simplify this process and allow the config to be added programmatically. Any thoughts on what features would be good to add into a plugin like that?

Gravatar Image4 - Toby, that's a great idea... check out this article from Maire Kehoe:
{ Link }

It describes in great detail what elements can be defined in an xsp-config file. The format of this documentation makes me suspect strongly that somewhere out there, a schema exists for this file format. If this is true, and I could get my hands on it, I could theoretically generate an EMF model that would make programmatic generation/manipulation of the xsp-config files a breeze. The model would even allow generation of a default editor. I'll give some thought to what other features would be useful, but if we can get hold of both schemas (xsp-config and faces-config), it shouldn't be terribly difficult to contribute a context menu item: right-click the component class and select "Generate Component Definition". :)

Gravatar Image5 - Tim,
I really enjoy your xPages solutions, but sometimes they tend to be too complicated. at least to complicated to implement. (for me Emoticon)

I tried to implement this, but can not see the placeholder attribute in the all properties pane.

I probably have done something wrong but can't figure it out
I did notice you have a reference in your blog post to a package which you never created in the blogpost, (or am I missing something.
com.timtripcony.xsp.component.html5.Html5InputText


anyway, here is what I did

- I have created a package (com.notessidan.se.xsp.Html5) in WebContent/Web-inf/src, build path have been set and is working as I have other classes in there working
- in the above package I have two classes, (HTML5InputText.java), and (HTML5InputTexRenderer.java)
- I created the file html5.xsp-config and changed the component tags to:
<component-type>com.notessidan.se.xsp.Html5</component-type>
<component-class>com.notessidan.se.xsp.Html5.Html5InputText</component-class>

- I tried to place the file html5.xsp-config in different places in the package explorer, now it is in the Webcontent\web-inf\lib\html5.xsp-config



I provided a few screenshots here

{ Link }



What could possibly I be wrong?



Thanks
Thomas

Gravatar Image6 - Hello Again Tim. I solved it!

Gravatar Image9 - Great!

But for a novice, like myself, what do I put where?

Some simple instructions for that would be a nice addition and/or a sample download.


While I'm at it: HTML5 supports adding your own attributes as long as you start them with: "data-", this is extremely convenient. Possible to achieve this?


Thanks!

/J

Gravatar Image13 - Thanks for bloggers share, we are a best discount north face jackets on sale store, if you want to buy <a href={ Link } north face jackets</a> and get free shipping, please going to our discount north face online store, Don't hesitate, We are your best choice. We have professional pre-sale and after-sale service team, You can be at ease completely purchase.

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)