2015-08-17 17:00:26 -07:00
/ * *
* @ file
* Autocomplete based on jQuery UI .
* /
( function ( $ , Drupal ) {
2015-10-21 21:44:50 -07:00
'use strict' ;
2015-08-17 17:00:26 -07:00
var autocomplete ;
/ * *
* Helper splitting terms from the autocomplete value .
*
* @ function Drupal . autocomplete . splitValues
*
* @ param { string } value
2016-04-20 09:56:34 -07:00
* The value being entered by the user .
2015-08-17 17:00:26 -07:00
*
* @ return { Array }
2016-04-20 09:56:34 -07:00
* Array of values , split by comma .
2015-08-17 17:00:26 -07:00
* /
function autocompleteSplitValues ( value ) {
// We will match the value against comma-separated terms.
var result = [ ] ;
var quote = false ;
var current = '' ;
var valueLength = value . length ;
var character ;
for ( var i = 0 ; i < valueLength ; i ++ ) {
character = value . charAt ( i ) ;
if ( character === '"' ) {
current += character ;
quote = ! quote ;
}
else if ( character === ',' && ! quote ) {
result . push ( current . trim ( ) ) ;
current = '' ;
}
else {
current += character ;
}
}
if ( value . length > 0 ) {
result . push ( $ . trim ( current ) ) ;
}
return result ;
}
/ * *
* Returns the last value of an multi - value textfield .
*
* @ function Drupal . autocomplete . extractLastTerm
*
* @ param { string } terms
2016-04-20 09:56:34 -07:00
* The value of the field .
2015-08-17 17:00:26 -07:00
*
* @ return { string }
2016-04-20 09:56:34 -07:00
* The last value of the input field .
2015-08-17 17:00:26 -07:00
* /
function extractLastTerm ( terms ) {
return autocomplete . splitValues ( terms ) . pop ( ) ;
}
/ * *
* The search handler is called before a search is performed .
*
* @ function Drupal . autocomplete . options . search
*
* @ param { object } event
2016-04-20 09:56:34 -07:00
* The event triggered .
2015-08-17 17:00:26 -07:00
*
* @ return { bool }
2016-04-20 09:56:34 -07:00
* Whether to perform a search or not .
2015-08-17 17:00:26 -07:00
* /
function searchHandler ( event ) {
var options = autocomplete . options ;
2016-12-07 12:19:38 -08:00
if ( options . isComposing ) {
return false ;
}
2015-08-17 17:00:26 -07:00
var term = autocomplete . extractLastTerm ( event . target . value ) ;
// Abort search if the first character is in firstCharacterBlacklist.
if ( term . length > 0 && options . firstCharacterBlacklist . indexOf ( term [ 0 ] ) !== - 1 ) {
return false ;
}
// Only search when the term is at least the minimum length.
return term . length >= options . minLength ;
}
/ * *
* JQuery UI autocomplete source callback .
*
* @ param { object } request
2016-04-20 09:56:34 -07:00
* The request object .
2015-08-17 17:00:26 -07:00
* @ param { function } response
2016-04-20 09:56:34 -07:00
* The function to call with the response .
2015-08-17 17:00:26 -07:00
* /
function sourceData ( request , response ) {
var elementId = this . element . attr ( 'id' ) ;
if ( ! ( elementId in autocomplete . cache ) ) {
autocomplete . cache [ elementId ] = { } ;
}
/ * *
* Filter through the suggestions removing all terms already tagged and
* display the available terms to the user .
*
* @ param { object } suggestions
2016-04-20 09:56:34 -07:00
* Suggestions returned by the server .
2015-08-17 17:00:26 -07:00
* /
function showSuggestions ( suggestions ) {
var tagged = autocomplete . splitValues ( request . term ) ;
var il = tagged . length ;
for ( var i = 0 ; i < il ; i ++ ) {
var index = suggestions . indexOf ( tagged [ i ] ) ;
if ( index >= 0 ) {
suggestions . splice ( index , 1 ) ;
}
}
response ( suggestions ) ;
}
/ * *
* Transforms the data object into an array and update autocomplete results .
*
* @ param { object } data
2016-04-20 09:56:34 -07:00
* The data sent back from the server .
2015-08-17 17:00:26 -07:00
* /
function sourceCallbackHandler ( data ) {
autocomplete . cache [ elementId ] [ term ] = data ;
// Send the new string array of terms to the jQuery UI list.
showSuggestions ( data ) ;
}
// Get the desired term and construct the autocomplete URL for it.
var term = autocomplete . extractLastTerm ( request . term ) ;
// Check if the term is already cached.
if ( autocomplete . cache [ elementId ] . hasOwnProperty ( term ) ) {
showSuggestions ( autocomplete . cache [ elementId ] [ term ] ) ;
}
else {
var options = $ . extend ( { success : sourceCallbackHandler , data : { q : term } } , autocomplete . ajax ) ;
$ . ajax ( this . element . attr ( 'data-autocomplete-path' ) , options ) ;
}
}
/ * *
* Handles an autocompletefocus event .
*
* @ return { bool }
2016-04-20 09:56:34 -07:00
* Always returns false .
2015-08-17 17:00:26 -07:00
* /
function focusHandler ( ) {
return false ;
}
/ * *
* Handles an autocompleteselect event .
*
* @ param { jQuery . Event } event
2016-04-20 09:56:34 -07:00
* The event triggered .
2015-08-17 17:00:26 -07:00
* @ param { object } ui
2016-04-20 09:56:34 -07:00
* The jQuery UI settings object .
2015-08-17 17:00:26 -07:00
*
* @ return { bool }
2016-04-20 09:56:34 -07:00
* Returns false to indicate the event status .
2015-08-17 17:00:26 -07:00
* /
function selectHandler ( event , ui ) {
var terms = autocomplete . splitValues ( event . target . value ) ;
// Remove the current input.
terms . pop ( ) ;
// Add the selected item.
2015-10-21 21:44:50 -07:00
if ( ui . item . value . search ( ',' ) > 0 ) {
2015-08-17 17:00:26 -07:00
terms . push ( '"' + ui . item . value + '"' ) ;
}
else {
terms . push ( ui . item . value ) ;
}
event . target . value = terms . join ( ', ' ) ;
// Return false to tell jQuery UI that we've filled in the value already.
return false ;
}
/ * *
* Override jQuery UI _renderItem function to output HTML by default .
*
2016-04-20 09:56:34 -07:00
* @ param { jQuery } ul
* jQuery collection of the ul element .
2015-08-17 17:00:26 -07:00
* @ param { object } item
2016-04-20 09:56:34 -07:00
* The list item to append .
2015-08-17 17:00:26 -07:00
*
2016-04-20 09:56:34 -07:00
* @ return { jQuery }
* jQuery collection of the ul element .
2015-08-17 17:00:26 -07:00
* /
function renderItem ( ul , item ) {
2015-10-21 21:44:50 -07:00
return $ ( '<li>' )
. append ( $ ( '<a>' ) . html ( item . label ) )
2015-08-17 17:00:26 -07:00
. appendTo ( ul ) ;
}
/ * *
* Attaches the autocomplete behavior to all required fields .
*
* @ type { Drupal ~ behavior }
2016-04-20 09:56:34 -07:00
*
* @ prop { Drupal ~ behaviorAttach } attach
* Attaches the autocomplete behaviors .
* @ prop { Drupal ~ behaviorDetach } detach
* Detaches the autocomplete behaviors .
2015-08-17 17:00:26 -07:00
* /
Drupal . behaviors . autocomplete = {
attach : function ( context ) {
// Act on textfields with the "form-autocomplete" class.
var $autocomplete = $ ( context ) . find ( 'input.form-autocomplete' ) . once ( 'autocomplete' ) ;
if ( $autocomplete . length ) {
// Allow options to be overriden per instance.
var blacklist = $autocomplete . attr ( 'data-autocomplete-first-character-blacklist' ) ;
$ . extend ( autocomplete . options , {
firstCharacterBlacklist : ( blacklist ) ? blacklist : ''
} ) ;
// Use jQuery UI Autocomplete on the textfield.
$autocomplete . autocomplete ( autocomplete . options )
2016-07-07 09:44:38 -07:00
. each ( function ( ) {
2016-04-07 11:19:57 -07:00
$ ( this ) . data ( 'ui-autocomplete' ) . _renderItem = autocomplete . options . renderItem ;
} ) ;
2016-12-07 12:19:38 -08:00
// Use CompositionEvent to handle IME inputs. It requests remote server on "compositionend" event only.
$autocomplete . on ( 'compositionstart.autocomplete' , function ( ) {
autocomplete . options . isComposing = true ;
} ) ;
$autocomplete . on ( 'compositionend.autocomplete' , function ( ) {
autocomplete . options . isComposing = false ;
} ) ;
2015-08-17 17:00:26 -07:00
}
} ,
detach : function ( context , settings , trigger ) {
if ( trigger === 'unload' ) {
$ ( context ) . find ( 'input.form-autocomplete' )
. removeOnce ( 'autocomplete' )
. autocomplete ( 'destroy' ) ;
}
}
} ;
/ * *
* Autocomplete object implementation .
*
* @ namespace Drupal . autocomplete
* /
autocomplete = {
cache : { } ,
// Exposes options to allow overriding by contrib.
splitValues : autocompleteSplitValues ,
extractLastTerm : extractLastTerm ,
// jQuery UI autocomplete options.
/ * *
* JQuery UI option object .
*
* @ name Drupal . autocomplete . options
* /
options : {
source : sourceData ,
focus : focusHandler ,
search : searchHandler ,
select : selectHandler ,
renderItem : renderItem ,
minLength : 1 ,
// Custom options, used by Drupal.autocomplete.
2016-12-07 12:19:38 -08:00
firstCharacterBlacklist : '' ,
// Custom options, indicate IME usage status.
isComposing : false
2015-08-17 17:00:26 -07:00
} ,
ajax : {
dataType : 'json'
}
} ;
Drupal . autocomplete = autocomplete ;
} ) ( jQuery , Drupal ) ;