1% Make sure the encoding of the characters `á` and `ö` is
    2% correctly understood.
    3:- encoding(utf8).
    4
    5:- module(autocomplete, [autocomplete//1]).

Autocomplete

jQuery based autocomplete widget

*/

   11:- use_module(library(http/html_head)).   12:- use_module(library(http/html_write)).   13
   14:- ensure_loaded(weblog(resources/resources)).   15:- use_module(library(http/js_write)).   16:- use_module(library(http/http_wrapper)).   17:- use_module(library(http/http_dispatch)).   18:- use_module(library(http/json_convert)).   19:- use_module(library(http/http_parameters)).   20:- use_module(library(http/http_json)).   21
   22:- html_meta autocomplete(1, ?, ?).
 autocomplete(+Generator:callable)// is det
Inserts an autocomplete input item

Generator is an arity n term that corresponds to an arity n+1 predicate.

autocomplete//1 will repeatedly query Generator for information and build up the autocomplete field. The final argument may be

*/

   45autocomplete(Generator) -->
   46	{
   47            (	call(Generator, id(ID))   ;   ID = 'tags' )
   48        },
   49	html([
   50	    \html_requires(jquery_ui),
   51	    \html_post(head, [\autocomplete_script(Generator)]),
   52	input([id=ID], [])]).
   53
   54autocomplete_script(Generator) -->
   55	{
   56            call(Generator, ajax),
   57            (	call(Generator, id(ID))   ;   ID = 'tags' ),
   58            ajax_path_name(Generator, PathName)
   59        },
   60	html([
   61	    \js_script( {| javascript(ID, PathName) ||
   62$(function() {
   63    $( "#"+ID ).autocomplete({
   64      source: PathName
   65    });
   66  });
   67|} )
   68	]).
   69
   70autocomplete_script(Generator) -->
   71	{
   72            call(Generator, accents),!,
   73            bagof(Choice, call(Generator, choice(Choice)), Choices),
   74            (	call(Generator, id(ID))   ;   ID = 'tags' )
   75        },
   76	html([
   77	    \js_script( {| javascript(Choices, ID) ||
   78  $(function() {
   79    var names = Choices;
   80
   81    var accentMap = {
   82      "á": "a",
   83      "ö": "o"
   84    };
   85    var normalize = function( term ) {
   86      var ret = "";
   87      for ( var i = 0; i < term.length; i++ ) {
   88        ret += accentMap[ term.charAt(i) ] || term.charAt(i);
   89      }
   90      return ret;
   91    };
   92
   93    $( "#"+ID ).autocomplete({
   94      source: function( request, response ) {
   95        var matcher = new RegExp( $.ui.autocomplete.escapeRegex( request.term ), "i" );
   96        response( $.grep( names, function( value ) {
   97          value = value.label || value.value || value;
   98          return matcher.test( value ) || matcher.test( normalize( value ) );
   99        }) );
  100      }
  101    });
  102  });
  103|} )
  104	]).
  105
  106autocomplete_script(Generator) -->
  107	{
  108            bagof(Choice, call(Generator, choice(Choice)), Choices),
  109            (	call(Generator, id(ID))   ;   ID = 'tags' )
  110        },
  111	html([
  112	    \js_script( {| javascript(Choices, ID) ||
  113$(function() {
  114    var availableTags = Choices;
  115    $( "#"+ID ).autocomplete({
  116      source: availableTags
  117    });
  118  });
  119|} )
  120	]).
  121
  122ajax_path_name(Generator, AjaxPath) :-
  123	(   call(Generator, id(ID)) ; ID = 'tags' ),
  124	http_current_request(Request),
  125	member(path(Path), Request),
  126	atomic_list_concat([Path, '/ajax/', ID], AjaxPath),
  127	ensure_ajax_handler_exists(Generator, AjaxPath).
  128
  129ensure_ajax_handler_exists(_, AjaxPath) :-
  130	http_dispatch:handler(AjaxPath, _, _, _),!.
  131ensure_ajax_handler_exists(Generator, AjaxPath) :-
  132	http_handler(AjaxPath, ajax_wrapper(Generator), []).
  133
  134:- meta_predicate ajax_wrapper(1, +).  135
  136ajax_wrapper(Generator, Request) :-
  137	http_parameters(Request, [
  138			    term(Term, [])]),
  139	findall(Choice, call(Generator, choice(Term, Choice)), Choices),
  140	prolog_to_json(Choices, JSONOut),
  141        reply_json(JSONOut)