| Apache Wicket > Framework Documentation > Reference library > How to do things in Wicket > Forms > Adding Dynamic Field Prompts or Hints |
Sometimes, it is useful for text to be displayed when a user is in a form element, and hidden when they leave the field. An example would be a prompt which offers help about the expected field contents, but which generally doesn't need to be displayed, since displaying them all at once would clutter up the interface.
This article will cover how to achieve this in your wicket page.
Three basic ways to achieve this functionality are:
I believe this can be achieved using AjaxFormComponentUpdatingBehavior. However, after reviewing the cons, I chose not to implement it in this manner. Someone else can certainly update this if they have had success using this method.
You could use any javascript library here. I am going to demonstrate using the Mootools library because it is small, modular, and does what is needed. Feel free to expand this with other options if you are familiar with them.
Here is a brief overview of the steps we will need to complete to implement this functionality:
And in detail:
var Hints = new Class({
getOptions: function(){ return { onShow: function(tip){ tip.setStyle('visibility', 'visible'); }, onHide: function(tip){ tip.setStyle('visibility', 'hidden'); }, maxTitleChars: 50, showDelay: 100, hideDelay: 100, className: 'tool', offsets: {'x': 0, 'y': 3}, sizeOffset: { 'x': false, 'y': true }, fixed: true }; },
build: function(el){ el.myTitle=false; el.myText=el.getAttribute("tooltip"); el.addEvent('focus', function(event) { this.locate(el); this.start(el); }.bindWithEvent(this)); el.addEvent('blur', this.end.bindWithEvent(this)); },
locate: function(el){ // Locate where the event came from. In our case: // position is the upper left corner of the input that gained focus // size is the size of the input that gained focus, in px var pos=el.getPosition(); var size=el.getSize(); // By default, position hint at top left corner of input, // and offset by our offset amounts. var left = pos['x'] + this.options.offsets['x']; var top = pos['y'] + this.options.offsets['y']; // If user specified that a sizeOffset should be used, additionally // offset the hint by the size of the control that gained focus if (this.options.sizeOffset['x']){ left = left + size['size']['x']; } if (this.options.sizeOffset['y']){ top = top + size['size']['y']; } // Assign the hint location this.toolTip.setStyle('left', left + 'px'); this.toolTip.setStyle('top', top + 'px'); },
Hints.implement(new Events); Hints.implement(new Options);
var Hints = new Class({ getOptions: function(){ return { onShow: function(tip){ tip.setStyle('visibility', 'visible'); }, onHide: function(tip){ tip.setStyle('visibility', 'hidden'); }, maxTitleChars: 50, showDelay: 100, hideDelay: 100, className: 'tool', offsets: {'x': 0, 'y': 3}, sizeOffset: { 'x': false, 'y': true }, fixed: true }; }, initialize: function(elements, options){ this.setOptions(this.getOptions(), options); this.toolTip = new Element('div').addClass(this.options.className+'-tip').setStyles({ 'position': 'absolute', 'top': '0', 'left': '0', 'visibility': 'hidden' }).injectInside(document.body); this.wrapper = new Element('div').injectInside(this.toolTip); $each(elements, function(el){ this.build($(el)); }, this); if (this.options.initialize) this.options.initialize.call(this); }, build: function(el){ el.myTitle=false; el.myText=el.getAttribute("tooltip"); el.addEvent('focus', function(event) { this.locate(el); this.start(el); }.bindWithEvent(this)); el.addEvent('blur', this.end.bindWithEvent(this)); }, start: function(el){ this.wrapper.setHTML(''); if (el.myTitle){ new Element('span').injectInside( new Element('div').addClass(this.options.className+'-title').injectInside(this.wrapper) ).setHTML(el.myTitle); } if (el.myText){ new Element('span').injectInside( new Element('div').addClass(this.options.className+'-text').injectInside(this.wrapper) ).setHTML(el.myText); } $clear(this.timer); this.timer = this.show.delay(this.options.showDelay, this); }, end: function(event){ $clear(this.timer); this.timer = this.hide.delay(this.options.hideDelay, this); event.stop(); }, locate: function(el){ // Locate where the event came from. In our case: // position is the upper left corner of the input that gained focus // size is the size of the input that gained focus, in px var pos=el.getPosition(); var size=el.getSize(); // By default, position hint at top left corner of input, // and offset by our offset amounts. var left = pos['x'] + this.options.offsets['x']; var top = pos['y'] + this.options.offsets['y']; // If user specified that a sizeOffset should be used, additionally // offset the hint by the size of the control that gained focus if (this.options.sizeOffset['x']){ left = left + size['size']['x']; } if (this.options.sizeOffset['y']){ top = top + size['size']['y']; } // Assign the hint location this.toolTip.setStyle('left', left + 'px'); this.toolTip.setStyle('top', top + 'px'); }, show: function(){ this.fireEvent('onShow', [this.toolTip]); }, hide: function(){ this.fireEvent('onHide', [this.toolTip]); } }); Hints.implement(new Events); Hints.implement(new Options);
| MooTools 1.2 Note that when using current (1.2) MooTools, you´d have to replace any setHTML( set('html',
|
<input class="hinted" wicket:id="usernameField" id="username" type="text"/>
<input class="hinted" wicket:id="usernameField" id="username" type="text" tooltip="Remember, usernames are case sensitive."/>
<head> ... <script src="Hints.v0.5.js"></script> </head>
<head> ... <script src="Hints.v0.5.js"></script> <script type="text/javascript"> window.addEvent('domready', function(){ var myHints = new Hints($$('.hinted'), { offsets: {'x': 10, 'y': 4}, sizeOffset: {'x':true, 'y':false } }); }); </script> </head>
Lets start off by creating a base behavior for all other mootools behaviors. This behavior will include the base mootools.v1.00.js file in the page.
Create package wicket.mootools and wicket.mootools.res. The res package will contain various resources. Put the mootools.v1.00.js into that package.
Now the behavior:
package wicket.mootools; import wicket.Component; import wicket.ResourceReference; import wicket.behavior.AbstractBehavior; import wicket.markup.ComponentTag; import wicket.markup.html.IHeaderContributor; import wicket.markup.html.IHeaderResponse; public class AbstractMooToolsBehavior extends AbstractBehavior implements IBehavior { // create a reference to the base mootools javascript file. // we use JavascriptResourceReference so that the included file will have its comments stripped and gzipped. private static final ResourceReference MOOTOOLS_JS = new JavascriptResourceReference(AbstractMooToolsBehavior.class, "res/mootools.v1.00.js"); public void renderHead(IHeaderResponse response) { // include the mootools js in the page's head response.renderJavascriptReference(MOOTOOLS_JS); } /** helper method that hooks into mootools ondomready event */ protected String executeOnWindowDomReady(String script) { StringBuilder builder = new StringBuilder(script.length() + 61); builder.append("<script>window.addEvent('domready', function(){\n"); builder.append(script); builder.append("\n});</script>"); return builder.toString(); } }
Now lets take a look at our specific case of adding tooltips. The tooltips need a background image called bubble.png, put that into the res package. They also require a bit of css, here is what it looks like:
<style>
.tool-tip {color: #fff; width: 516px; z-index: 13000;}
.tool-title {font-weight: bold; font-size: 11px; margin: 0; padding: 8px 8px 4px; background: url(bubble.png) top left;}
.tool-text {margin: 0; font-size: 11px; padding: 4px 8px 8px; background: url(bubble.png) bottom right;}
</style>
Notice the css references bubble.png, so we will need to somehow make the css point to the right url. Lets template the css, so we can substitute the right url at runtime. Create a file in the res package called ToolTipsCssTemplate.css with the following contents:
<style>
.tool-tip {color: #fff; width: 516px; z-index: 13000;}
.tool-title {font-weight: bold; font-size: 11px; margin: 0; padding: 8px 8px 4px; background: url(${bubble-url}) top left;}
.tool-text {margin: 0; font-size: 11px; padding: 4px 8px 8px; background: url(${bubble-url}) bottom right;}
</style>
We substituted the reference to bubble.png with ${bubble-url} variable, we will make wicket replace it for us at runtime with the url for the image.
Copy Hints.v0.5.js into the res package
Now lets create the tooltips behavior
package wicket.mootools; import java.util.Map; import wicket.RequestCycle; import wicket.ResourceReference; import wicket.markup.html.IHeaderResponse; import wicket.util.collections.MicroMap; import wicket.util.resource.PackagedTextTemplate; public class MooToolsFixedTooltips extends AbstractMooToolsBehavior { private static final ResourceReference BUBBLE_PNG = new ResourceReference(AbstractMooToolsBehavior.class, "res/bubble.png"); private static final ResourceReference FIXEDTIPS_JS = new JavascriptResourceReference(AbstractMooToolsBehavior.class, "res/Hints.v0.5.js"); /** render the necessary javascript to enable tooltips */ @Override public void renderHead(IHeaderResponse response) { // allow the base behavior to do what it needs to super.renderHead(response); // include a link to our modified tooltips js response.renderJavascriptReference(FIXEDTIPS_JS); // include the css response.renderString(css()); response .renderString(executeOnWindowDomReady("var myHints = new Hints($$('.hinted'), " +"{offsets: {'x': 10, 'y': 4}, sizeOffset: {'x':true, 'y':false }")); } /** create the css string with the properly replaced bubble-url variable */ private String css() { // load the css template we created form the res package PackagedTextTemplate template = new PackagedTextTemplate(MooToolsFixedTooltips.class, "res/ToolTipsCssTemplate.css"); // create a variable subsitution map Map<String, CharSequence> params = new MicroMap<String, CharSequence>("bubble-url", RequestCycle.get().urlFor( BUBBLE_PNG)); // perform subsitution and return the result return template.asString(params); } }
Now all you need to do is add this behavior to the page or to any component and add appropriate tooltip and class to any tag.