$(function() {
  $('.poetsearch').typeahead({
    name: 'poets',
    remote: '/poet?q=%QUERY',
    valueKey: 'name'
  }).on('typeahead:selected', function(element, poet) {
    var poets = $(this).closest('form').find('.poets');

    var li = $('<li/>', {
      text: poet.name
    }).appendTo(poets);

    $('<input/>', {
      type: 'hidden',
      name: 'poets',
      value: poet.id
    }).appendTo(li);

    $(this).typeahead('setQuery', '');
  });
});
$(function() {

  var poetSearch = $('.poetsearch');
  var poetsList = poetSearch.closest('form').find('.poets');

  poetSearch.typeahead({
    name: 'poets',
    valueKey: 'name',
    remote: {
      url: '/poet?q=%QUERY',
      filter: function(poets) {
        return poets.filter(function(p) {
          return poetsList.find('input[value=' + p.id + ']').length === 0;
        });
      }
    }
  }).on('typeahead:selected', function(element, poet) {
    var li = $('<li/>', {
      text: poet.name
    }).appendTo(poetsList);

    $('<input/>', {
      type: 'hidden',
      name: 'poets',
      value: poet.id
    }).appendTo(li);

    $(this).typeahead('setQuery', '');
  });
});
Exclude poets from typeahead that have already been added to the poem.
Exclude poets from typeahead that have already been added to the poem.

Use the typeahead filter function to exclude items that are already in the list. This approach has the limitation of potentially returning fewer than 10 results. In a worst-case scenario, the top 10 poets starting with a given letter could be added to the poem, and 0 results would be returned! Another approach could be to pass the list of poets to the server and exclude them with a SQL query. This would probably be prohibatively expensive from a performance standpoint.
