Adding an Auto-Completer to a non-workflow page
Contents
NOTE: THIS IS INCOMPLETE - CAN SOMEONE FILL IN THE DETAILS??
Known to work for EPrints 3.2
Using the EPrints application framework, we can create auto-completion for any input, but we need to understand how the system works, and build the pages by the rules.
How Auto-Completion works, in general
Understanding AJAX Auto-Completion is a bit of a circular process, so bear with me.
You need three things
- An Auto-Completer "engine" (a complex Javascript code-base)
- A script to do the lookup work & return stuff
- (x)html structure to accept the returned stuff
The process:
- When the user types into an input box, a javascript event is triggered which sends that input value to a script.
- The script returns an unordered list, which the Auto-Completer code displays for the user to select from
- The Javascript engine then fills in the identified inputs with the selected values.
A very simple example
Input
Here we have an input:
<input type="textfield" size="25" id="oarj_oname" name="organisation_name" onkeypress="return EPJS_block_enter( event )" />
There are two important factors here:
id="oarj_oname"
defines the identity of the element, which we need to read the "seed" value from, and fill in with the selected data. This element actually breaks down into two sub-parts:- "oarj" is the component
- "oname" gives the part of the components
onkeypress="return EPJS_block_enter( event )"
is the EPrints application magic incantation" to trigger some javascript when the user types something into the input box.
Input with return locations
What we also need the (x)html to receive the output of the mysterious javascript call:
<input type="textfield" onkeypress="return EPJS_block_enter( event )" size="25" id="oarj_oname" name="organisation name"> <div style="display: none;" id="oarj_oname_drop" class="ep_drop_target"></div> <div style="display: none;" id="oarj_oname_drop_loading" class="ep_drop_loading"> <img src="/style/images/loading.gif" alt="" style="border: 0pt none ;"> </div>
Some of this is more magic incantation
There are two div
s - one to accept the return from the AJAX magic, and one to display the "I'm doing stuff" graphic.
Their ids are declared in the Javascript call, however they should be derived from the id of the input field:
- "oarj_oname_drop" is the named element to receive the output of the javascript
- "xxxx_loading" is the element that displays the "I'm busy" graphic. It's name is specific: it is the same as the drop element, with
_loading
appended.
Input with return locations and javascript call
Finally, we need to append the bit that makes the Auto-Completer call:
<input type="textfield" onkeypress="return EPJS_block_enter( event )" size="25" id="oarj_oname" name="organisation name"> <div style="display: none;" id="oarj_oname_drop" class="ep_drop_target"></div> <div style="display: none;" id="oarj_oname_drop_loading" class="ep_drop_loading"> <img src="/style/images/loading.gif" alt="" style="border: 0pt none ;"> </div> <script type="text/javascript"> // <![CDATA[ ep_autocompleter( "oarj_oname", "oarj_oname_drop", "/cgi/get_orgs", {relative: "oarj_oname", component: "oarj" }, [ $("oarj_oname")], [], "&field=name") // ]]></script>
The javascript has multiple fields:
"oarj_oname"
is the id of the element that the Auto-Completer reads from"oarj_oname_drop"
is the id of the element the return data is put into"/cgi/get_orgs"
is the name of the script called to get the return data{relative: "oarj_oname", component: "oarj" }
is magic incantation - referred to as basenames in the javascript code, I give it the id of the element and the component level name[ $("oarj_oname")]
more magic incantation - width_of_these ??[]
would be the fields_to_send"&field=name"
is extra_params - a way of passing extra cgi parameters to the script
The Returned Data
The final part of this loop is the returned data. It doesn't particularly matter what is used to create this data, so long as the users client receives the data in the right format
With the EPrints Auto-Completer, the format for this return is fairly simple:
<ul> <li>Some informative text<br /> (with something else on a new line <ul> <li id="for:value:component:_oname">Fandangle</li> <li id="for:value:component:_diddle">Fruit Loops</li> <li id="for:value:component:_a1">Another One</li> <li id="for:value:component:_b2">Befuddled Too</li> </ul> </li> <li> ..... </li> </ul>
To break this down, we're looking at a list of returns, each of which has a list of "fill-in" items
Lets break the list down into parts:
<ul> <li>Some informative text<br /> (with something else on a new line</li> <li> Another entry </li> <li> The Third Way </li> <li> Option Four </li> </ul>
This is the list of items to display to the user - in this case, 4 items. What we don't have here is the data to be filled in, once a selection has been made. That looks like this:
<li>Some informative text<br /> (with something else on a new line <ul> <li id="for:value:component:_oname">Fandangle</li> <li id="for:value:component:_diddle">Fruit Loops</li> <li id="for:value:component:_a1">Another One</li> <li id="for:value:component:_b2">Befuddled Too</li> </ul> </li>
Each item has a sub-list, where each item has a magic incantation for it's id: for:value:component:
is the bit that the EPrints Auto-Completer code looks for, and _xxx
names the sub_element that the data does into.
Given the ep_autocompeter
call above, we have defined a component of oarj
. Thus:
Fandangle
puts "Fandangle" in the element Fruit Loops
puts "Fruit Loops" in the element Another One
puts "Another One" in the element Befuddled Too
puts "Befuddled Too" in the element
id="oarj_oname"
id="oarj_diddle"
id="oarj_a1"
id="oarj_b2"
Note: If the element does not exist, then the value is ignored... sensibly.
Creating the (x)html in an EPrints page
To make an Auto-Completer form within eprints, it's basically a case of assembling the DOM:
$d = $session->make_element("div"); $d->appendChild( $session->render_input_field( type=>"textfield", name=>"oarj_oname", id=>"oarj_oname", class=>"ep_form_text", size=>25 ) ); $d->appendChild( $session->make_element("div", class=>"ep_drop_target", id=>"oarj_oname_drop", style=>"display:none" ) ); $div = $session->make_element("div", class=>"ep_drop_loading", id=>"oarj_oname_drop_loading", style=>"display:none"); $div->appendChild( $session->make_element( "img", style=>"border:0", alt=>"", src=>"/style/images/loading.gif" ) ); $d->appendChild( $div ); $d->appendChild( $session->make_javascript( 'ep_autocompleter( "oarj_oname", "oarj_oname_drop", "/cgi/get_orgs", {relative: "oarj_oname", component: "oarj" }, [ $("oarj_oname")], [], "&field=name")' ) );
Making an EPrints page from a CGI script
Making a page from a CGI script is fairly easy:
#!/path/to/appropraite/perl use EPrints; my $session = EPrints::Session->new(); my( $page, $title ) = make_page( $session ); $session->build_page( $title, $page ); $session->send_page(); $session->terminate(); exit; sub make_page { my( $repository) = @_; my $page = $repository->make_doc_fragment; my $title = $repository->make_text( "OARJ Organisation management tool" ); ## Stuff to build return( $page, $title ); }
Making an EPrints page within the interface
For this, you need a new module in perl_lib/EPrints/Plugins/Screen/
- see (somewhere else)
The code to create the return stuff
Auto-Completers are fine, but they need something to look-up information and return stuff for the user to select from.
The important factors you need to know to write a script are:
- The value from the input field will be in a parameter called "q"
- You need to return data with an xml header
- You need to return an unordered list
- You can send back more data than you are going to use
An example
Here is my lookup from Open Access Repository Junction:
use EPrints; my $dbh = /// connect to database sub get_orgs { my ($dbh, $index_key, $index_value) = @_; my $db_return = /// a reference to a list of returns from a database query //// name, acronym, url my %data = (); # build up a lump of (x)html for each item foreach my $ref (@$db_return) { my $html = "<li>".$ref->[0]; $html .= "(".$ref->[1].")" if $ref->[1]; $html .= "[".$ref->[2]."]" if $ref->[2]; $html .= "<ul>"; $html .= "<li id='for:value:component:_oname'>".$ref->[0]."</li>"; $html .= "<li id='for:value:component:_ourl'>".$ref->[1]."</li>"; $html .= "<li id='for:value:component:_oacronym'>".$ref->[2]."</li>"; $html .= "</ul></li>\n"; $data{$ref->[2]} = $html; } my @html = (); if (!scalar keys %data) { push @html, "<!-- no matches -->"; return (join "\n", @html) } push @html, "<ul class='journals'>\n"; foreach my $id (keys %data) { push @html, "$data{$id}\n"; } push @html, "</ul>\n"; return (join "\n", @html) } my $session = EPrints::Session->new(); # we need the send an initial content-type print <<END; <?xml version="1.0" encoding="UTF-8" ?> END # then we send the fragment of html for the autocompleter my $value = ""; my $field = ""; $value = $session->param( "q" ); $field = $session->param( "field" ); print get_orgs($dbh, $field, $value); #print STDERR get_journals( $q ); $session->terminate;