Keyboard events in XForms

A couple of weeks ago I came across a post by Stephen Pemberton on Mouse Events in XForms. It gave me the idea about capturing “key” events to enable my own forms to be a bit smoother. So, perhaps get the Esc key in an <xf:input/> to delete the contents, or Return to trigger a <xf:submission/>. Another was whether I could get the arrow keys to work in a <xf:repeat/>. Now I hasten to add, fiddling with the key events can break forms used on other devices like phones. In my case I’m only dealing with browsers in an office, so your milage may vary but the general idea may be of use.

Untitled

So, over a coffee, I put together a little demo on GitHub using XSLTForms 1.0 that you can run here.
I used Wikipedia as a source of my data, with a hat-tip to Alain Couteres  for his OpenSearch demo showing XSLTForms’ neat JSON handling that I used to get the submission working.
If you fire up the example, you’ll see that you can use Esc and Return in the search box, and if you click into the results, you’ll see you can use the up and down arrows to move in the list. If you get to the bottom of the list and use down-arrow again, you’ll trigger the next page. Up-arrow does the same for prev page. This is how it works:

First of all there is a little bit of XML and a <xf:submission/> that gets data from wikipedia using their api.
Note: Use text/jsonp as the media type. You need it so xsltforms can do it’s magic.

<xf:instance xmlns="" id="query">
 <data>
 <action>query</action>
 <list>search</list>
 <srprop>snippet</srprop>
 <srsearch>xforms</srsearch>
 <format>json</format>
 <sroffset>0</sroffset>
 <srlimit>10</srlimit>
 </data>
</xf:instance>
<xf:submission id="search" method="get" replace="instance"
 mode="synchronous" instance="results" ref="instance('query')"
 action="http://en.wikipedia.org/w/api.php" mediatype="text/jsonp"> 
</xf:submission>

Now, <srsearch/> is the field we’re querying for, so that’s bound in as an input. Normally, I’d use something like so:

<xf:input ref=’srsearch’ incremental=’false’>
<xf:send submission=”search” ev:event=”DOMActivate” />
</xf:input>

This gives you an input box that submits if you leave the field, press return etc. Also, normally there’d be a button as well, just in case. To alter this state of affairs, all I need to do is wrap the control I want to capture in an <xf:group/> and add some <ev:event/>s.
Note: You can’t put your event code inside the <xf:input/> which you’d think would be the place for it – it has to be wrapped to work.  The input now looks like this:

<xf:group ref="instance('query')" navindex="0">
<!-- ESC Key -->
 <xf:action ev:event="keydown" if="event('keyCode') = 27">
 <xf:setvalue ref="srsearch" value="''"/>
 </xf:action> 

 <!-- RETURN -->
 <xf:action ev:event="keydown" if="event('keyCode') = 13">
 <xf:dispatch targetid="searchme" name="DOMActivate"/>
 </xf:action>

 <xf:input ref='srsearch' incremental='false'/>
<xf:trigger id="searchme">
 <xf:label>Search</xf:label>
 <xf:action ev:event="DOMActivate">
 <xf:setvalue ref="instance('query')/sroffset" value="0"/>
 <xf:send submission="search" />
 </xf:action>
 </xf:trigger>

 </xf:group>

If you’re in the input , Esc will delete the contents and Return will trigger a search.  It’s worth noting that the key event is captured regardless and then the if statement acts to control the action, not the other way around. Since I’ve added a ‘Search’ <xf:trigger> now, I can use <xf:dispatch/> in my Return code to send the DOMActivate event to it. That way I don’t have to duplicate the submission code. It’s a great way to say, put prev/next buttons at the top and bottom of a results list, and just have the code in one set.

Next up is the results. I’ve use a standard <repeat/> with a CSS table layout again wrapped in a <xf:group/> with a couple of buttons to page through the results. Since the snippet element comes back as encoded html with the search term highlighted, I’ve used the media type flag in <xf:output/> to display it properly.

As with the input, I’ve caught the keys I want. For example, down-arrow:

<xf:action ev:event=”keydown” ev:defaultAction=”cancel”
if=”event(‘keyCode’) = 40″>
<xf:dispatch targetid=”list_next” name=”DOMActivate” if=”index(‘resultslist’) = 10″ />
<xf:setindex repeat=”resultslist” index=”index(‘resultslist’) +1″ if=”index(‘resultslist’) &lt;= 9″/>
</xf:action>

There are two actions here, driven by appropriate ‘if’ statements. The first calls the Next <trigger/> if you’re at the bottom of the list, i.e. index() = 10. The other increments the list index ‘if” you’re not at the bottom, i.e. index() <=9. Capturing up-arrow simply works in reverse.

There are a couple of things to note here:

  • The use of ‘navindex=1’ in the <xf:group/>. This makes it possible for the list, which isn’t a control, to get keystrokes. It won’t work without it.
  • Each <xf:event/> has ev:defaultAction=”cancel” set. This stops the event from propagating. Be careful if you use this with a input control as normal keys you don’t handle will get cancelled too.

That’s pretty much it and I hope it’s been useful as even if you don’t capture keys, it’s handy to be able to send events to <xf:trigger>s as they’re used so frequently. Enjoy.

Advertisements