2016-01-30 10:42:46 -05:00
( function ( factory ) {
if ( typeof define === 'function' && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ 'jquery' ] , factory ) ;
} else if ( typeof module === "object" && module . exports ) {
var $ = require ( 'jquery' ) ;
module . exports = factory ( $ ) ;
} else {
// Browser globals
factory ( jQuery ) ;
}
} ( function ( jQuery ) {
2016-01-19 12:11:40 -05:00
/ * !
* jQuery . textcomplete
*
* Repository : https : //github.com/yuku-t/jquery-textcomplete
* License : MIT ( https : //github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE)
* Author : Yuku Takahashi
* /
if ( typeof jQuery === 'undefined' ) {
throw new Error ( 'jQuery.textcomplete requires jQuery' ) ;
}
+ function ( $ ) {
'use strict' ;
var warn = function ( message ) {
if ( console . warn ) { console . warn ( message ) ; }
} ;
2016-01-30 10:42:46 -05:00
var id = 1 ;
2016-01-19 12:11:40 -05:00
$ . fn . textcomplete = function ( strategies , option ) {
var args = Array . prototype . slice . call ( arguments ) ;
return this . each ( function ( ) {
2016-01-30 10:42:46 -05:00
var self = this ;
2016-01-19 12:11:40 -05:00
var $this = $ ( this ) ;
var completer = $this . data ( 'textComplete' ) ;
if ( ! completer ) {
2016-01-30 10:42:46 -05:00
option || ( option = { } ) ;
option . _oid = id ++ ; // unique object id
completer = new $ . fn . textcomplete . Completer ( this , option ) ;
2016-01-19 12:11:40 -05:00
$this . data ( 'textComplete' , completer ) ;
}
if ( typeof strategies === 'string' ) {
if ( ! completer ) return ;
args . shift ( )
completer [ strategies ] . apply ( completer , args ) ;
if ( strategies === 'destroy' ) {
$this . removeData ( 'textComplete' ) ;
}
} else {
// For backward compatibility.
// TODO: Remove at v0.4
$ . each ( strategies , function ( obj ) {
$ . each ( [ 'header' , 'footer' , 'placement' , 'maxCount' ] , function ( name ) {
if ( obj [ name ] ) {
completer . option [ name ] = obj [ name ] ;
warn ( name + 'as a strategy param is deprecated. Use option.' ) ;
delete obj [ name ] ;
}
} ) ;
} ) ;
2016-01-30 10:42:46 -05:00
completer . register ( $ . fn . textcomplete . Strategy . parse ( strategies , {
el : self ,
$el : $this
} ) ) ;
2016-01-19 12:11:40 -05:00
}
} ) ;
} ;
} ( jQuery ) ;
+ function ( $ ) {
'use strict' ;
// Exclusive execution control utility.
//
// func - The function to be locked. It is executed with a function named
// `free` as the first argument. Once it is called, additional
// execution are ignored until the free is invoked. Then the last
// ignored execution will be replayed immediately.
//
// Examples
//
// var lockedFunc = lock(function (free) {
// setTimeout(function { free(); }, 1000); // It will be free in 1 sec.
// console.log('Hello, world');
// });
// lockedFunc(); // => 'Hello, world'
// lockedFunc(); // none
// lockedFunc(); // none
// // 1 sec past then
// // => 'Hello, world'
// lockedFunc(); // => 'Hello, world'
// lockedFunc(); // none
//
// Returns a wrapped function.
var lock = function ( func ) {
var locked , queuedArgsToReplay ;
return function ( ) {
// Convert arguments into a real array.
var args = Array . prototype . slice . call ( arguments ) ;
if ( locked ) {
// Keep a copy of this argument list to replay later.
// OK to overwrite a previous value because we only replay
// the last one.
queuedArgsToReplay = args ;
return ;
}
locked = true ;
var self = this ;
args . unshift ( function replayOrFree ( ) {
if ( queuedArgsToReplay ) {
// Other request(s) arrived while we were locked.
// Now that the lock is becoming available, replay
// the latest such request, then call back here to
// unlock (or replay another request that arrived
// while this one was in flight).
var replayArgs = queuedArgsToReplay ;
queuedArgsToReplay = undefined ;
replayArgs . unshift ( replayOrFree ) ;
func . apply ( self , replayArgs ) ;
} else {
locked = false ;
}
} ) ;
func . apply ( this , args ) ;
} ;
} ;
var isString = function ( obj ) {
return Object . prototype . toString . call ( obj ) === '[object String]' ;
} ;
2016-01-30 10:42:46 -05:00
var isFunction = function ( obj ) {
return Object . prototype . toString . call ( obj ) === '[object Function]' ;
} ;
2016-01-19 12:11:40 -05:00
var uniqueId = 0 ;
function Completer ( element , option ) {
this . $el = $ ( element ) ;
this . id = 'textcomplete' + uniqueId ++ ;
this . strategies = [ ] ;
this . views = [ ] ;
this . option = $ . extend ( { } , Completer . _getDefaults ( ) , option ) ;
2016-04-14 20:02:54 -04:00
if ( ! this . $el . is ( 'input[type=text]' ) && ! this . $el . is ( 'input[type=search]' ) && ! this . $el . is ( 'textarea' ) && ! element . isContentEditable && element . contentEditable != 'true' ) {
2016-01-19 12:11:40 -05:00
throw new Error ( 'textcomplete must be called on a Textarea or a ContentEditable.' ) ;
}
if ( element === document . activeElement ) {
// element has already been focused. Initialize view objects immediately.
this . initialize ( )
} else {
// Initialize view objects lazily.
var self = this ;
this . $el . one ( 'focus.' + this . id , function ( ) { self . initialize ( ) ; } ) ;
}
}
Completer . _getDefaults = function ( ) {
if ( ! Completer . DEFAULTS ) {
Completer . DEFAULTS = {
appendTo : $ ( 'body' ) ,
zIndex : '100'
} ;
}
return Completer . DEFAULTS ;
}
$ . extend ( Completer . prototype , {
// Public properties
// -----------------
id : null ,
option : null ,
strategies : null ,
adapter : null ,
dropdown : null ,
$el : null ,
// Public methods
// --------------
initialize : function ( ) {
var element = this . $el . get ( 0 ) ;
// Initialize view objects.
this . dropdown = new $ . fn . textcomplete . Dropdown ( element , this , this . option ) ;
var Adapter , viewName ;
if ( this . option . adapter ) {
Adapter = this . option . adapter ;
} else {
2016-04-14 20:02:54 -04:00
if ( this . $el . is ( 'textarea' ) || this . $el . is ( 'input[type=text]' ) || this . $el . is ( 'input[type=search]' ) ) {
2016-01-19 12:11:40 -05:00
viewName = typeof element . selectionEnd === 'number' ? 'Textarea' : 'IETextarea' ;
} else {
viewName = 'ContentEditable' ;
}
Adapter = $ . fn . textcomplete [ viewName ] ;
}
this . adapter = new Adapter ( element , this , this . option ) ;
} ,
destroy : function ( ) {
this . $el . off ( '.' + this . id ) ;
if ( this . adapter ) {
this . adapter . destroy ( ) ;
}
if ( this . dropdown ) {
this . dropdown . destroy ( ) ;
}
this . $el = this . adapter = this . dropdown = null ;
} ,
2016-04-14 20:02:54 -04:00
deactivate : function ( ) {
if ( this . dropdown ) {
this . dropdown . deactivate ( ) ;
}
} ,
2016-01-19 12:11:40 -05:00
// Invoke textcomplete.
trigger : function ( text , skipUnchangedTerm ) {
if ( ! this . dropdown ) { this . initialize ( ) ; }
text != null || ( text = this . adapter . getTextFromHeadToCaret ( ) ) ;
var searchQuery = this . _extractSearchQuery ( text ) ;
if ( searchQuery . length ) {
var term = searchQuery [ 1 ] ;
// Ignore shift-key, ctrl-key and so on.
2016-04-14 20:02:54 -04:00
if ( skipUnchangedTerm && this . _term === term && term !== "" ) { return ; }
2016-01-19 12:11:40 -05:00
this . _term = term ;
this . _search . apply ( this , searchQuery ) ;
} else {
this . _term = null ;
this . dropdown . deactivate ( ) ;
}
} ,
fire : function ( eventName ) {
var args = Array . prototype . slice . call ( arguments , 1 ) ;
this . $el . trigger ( eventName , args ) ;
return this ;
} ,
register : function ( strategies ) {
Array . prototype . push . apply ( this . strategies , strategies ) ;
} ,
// Insert the value into adapter view. It is called when the dropdown is clicked
// or selected.
//
// value - The selected element of the array callbacked from search func.
// strategy - The Strategy object.
2016-01-30 10:42:46 -05:00
// e - Click or keydown event object.
select : function ( value , strategy , e ) {
this . _term = null ;
this . adapter . select ( value , strategy , e ) ;
2016-01-19 12:11:40 -05:00
this . fire ( 'change' ) . fire ( 'textComplete:select' , value , strategy ) ;
this . adapter . focus ( ) ;
} ,
// Private properties
// ------------------
_clearAtNext : true ,
_term : null ,
// Private methods
// ---------------
// Parse the given text and extract the first matching strategy.
//
// Returns an array including the strategy, the query term and the match
// object if the text matches an strategy; otherwise returns an empty array.
_extractSearchQuery : function ( text ) {
for ( var i = 0 ; i < this . strategies . length ; i ++ ) {
var strategy = this . strategies [ i ] ;
var context = strategy . context ( text ) ;
if ( context || context === '' ) {
2016-01-30 10:42:46 -05:00
var matchRegexp = isFunction ( strategy . match ) ? strategy . match ( text ) : strategy . match ;
2016-01-19 12:11:40 -05:00
if ( isString ( context ) ) { text = context ; }
2016-01-30 10:42:46 -05:00
var match = text . match ( matchRegexp ) ;
2016-01-19 12:11:40 -05:00
if ( match ) { return [ strategy , match [ strategy . index ] , match ] ; }
}
}
return [ ]
} ,
// Call the search method of selected strategy..
_search : lock ( function ( free , strategy , term , match ) {
var self = this ;
strategy . search ( term , function ( data , stillSearching ) {
if ( ! self . dropdown . shown ) {
self . dropdown . activate ( ) ;
}
if ( self . _clearAtNext ) {
// The first callback in the current lock.
self . dropdown . clear ( ) ;
self . _clearAtNext = false ;
}
2016-01-30 10:42:46 -05:00
self . dropdown . setPosition ( self . adapter . getCaretPosition ( ) ) ;
self . dropdown . render ( self . _zip ( data , strategy , term ) ) ;
2016-01-19 12:11:40 -05:00
if ( ! stillSearching ) {
// The last callback in the current lock.
free ( ) ;
self . _clearAtNext = true ; // Call dropdown.clear at the next time.
}
} , match ) ;
} ) ,
// Build a parameter for Dropdown#render.
//
// Examples
//
// this._zip(['a', 'b'], 's');
// //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }]
2016-01-30 10:42:46 -05:00
_zip : function ( data , strategy , term ) {
2016-01-19 12:11:40 -05:00
return $ . map ( data , function ( value ) {
2016-01-30 10:42:46 -05:00
return { value : value , strategy : strategy , term : term } ;
2016-01-19 12:11:40 -05:00
} ) ;
}
} ) ;
$ . fn . textcomplete . Completer = Completer ;
} ( jQuery ) ;
+ function ( $ ) {
'use strict' ;
2016-01-30 10:42:46 -05:00
var $window = $ ( window ) ;
2016-01-19 12:11:40 -05:00
var include = function ( zippedData , datum ) {
var i , elem ;
var idProperty = datum . strategy . idProperty
for ( i = 0 ; i < zippedData . length ; i ++ ) {
elem = zippedData [ i ] ;
if ( elem . strategy !== datum . strategy ) continue ;
if ( idProperty ) {
if ( elem . value [ idProperty ] === datum . value [ idProperty ] ) return true ;
} else {
if ( elem . value === datum . value ) return true ;
}
}
return false ;
} ;
var dropdownViews = { } ;
$ ( document ) . on ( 'click' , function ( e ) {
var id = e . originalEvent && e . originalEvent . keepTextCompleteDropdown ;
$ . each ( dropdownViews , function ( key , view ) {
if ( key !== id ) { view . deactivate ( ) ; }
} ) ;
} ) ;
2016-01-30 10:42:46 -05:00
var commands = {
SKIP _DEFAULT : 0 ,
KEY _UP : 1 ,
KEY _DOWN : 2 ,
KEY _ENTER : 3 ,
KEY _PAGEUP : 4 ,
KEY _PAGEDOWN : 5 ,
KEY _ESCAPE : 6
} ;
2016-01-19 12:11:40 -05:00
// Dropdown view
// =============
// Construct Dropdown object.
//
// element - Textarea or contenteditable element.
function Dropdown ( element , completer , option ) {
2016-01-30 10:42:46 -05:00
this . $el = Dropdown . createElement ( option ) ;
2016-01-19 12:11:40 -05:00
this . completer = completer ;
this . id = completer . id + 'dropdown' ;
this . _data = [ ] ; // zipped data.
this . $inputEl = $ ( element ) ;
this . option = option ;
// Override setPosition method.
if ( option . listPosition ) { this . setPosition = option . listPosition ; }
if ( option . height ) { this . $el . height ( option . height ) ; }
var self = this ;
2016-01-30 10:42:46 -05:00
$ . each ( [ 'maxCount' , 'placement' , 'footer' , 'header' , 'noResultsMessage' , 'className' ] , function ( _i , name ) {
2016-01-19 12:11:40 -05:00
if ( option [ name ] != null ) { self [ name ] = option [ name ] ; }
} ) ;
this . _bindEvents ( element ) ;
dropdownViews [ this . id ] = this ;
}
$ . extend ( Dropdown , {
// Class methods
// -------------
2016-01-30 10:42:46 -05:00
createElement : function ( option ) {
2016-01-19 12:11:40 -05:00
var $parent = option . appendTo ;
if ( ! ( $parent instanceof $ ) ) { $parent = $ ( $parent ) ; }
2016-01-30 10:42:46 -05:00
var $el = $ ( '<ul></ul>' )
. addClass ( 'dropdown-menu textcomplete-dropdown' )
. attr ( 'id' , 'textcomplete-dropdown-' + option . _oid )
. css ( {
2016-01-19 12:11:40 -05:00
display : 'none' ,
left : 0 ,
position : 'absolute' ,
zIndex : option . zIndex
2016-01-30 10:42:46 -05:00
} )
. appendTo ( $parent ) ;
2016-01-19 12:11:40 -05:00
return $el ;
}
} ) ;
$ . extend ( Dropdown . prototype , {
// Public properties
// -----------------
$el : null , // jQuery object of ul.dropdown-menu element.
$inputEl : null , // jQuery object of target textarea.
completer : null ,
footer : null ,
header : null ,
id : null ,
maxCount : 10 ,
placement : '' ,
shown : false ,
data : [ ] , // Shown zipped data.
className : '' ,
// Public methods
// --------------
destroy : function ( ) {
// Don't remove $el because it may be shared by several textcompletes.
this . deactivate ( ) ;
this . $el . off ( '.' + this . id ) ;
this . $inputEl . off ( '.' + this . id ) ;
this . clear ( ) ;
2016-04-14 20:02:54 -04:00
this . $el . remove ( ) ;
2016-01-19 12:11:40 -05:00
this . $el = this . $inputEl = this . completer = null ;
delete dropdownViews [ this . id ]
} ,
render : function ( zippedData ) {
var contentsHtml = this . _buildContents ( zippedData ) ;
var unzippedData = $ . map ( this . data , function ( d ) { return d . value ; } ) ;
if ( this . data . length ) {
2016-04-14 20:02:54 -04:00
var strategy = zippedData [ 0 ] . strategy ;
if ( strategy . id ) {
this . $el . attr ( 'data-strategy' , strategy . id ) ;
} else {
this . $el . removeAttr ( 'data-strategy' ) ;
}
2016-01-19 12:11:40 -05:00
this . _renderHeader ( unzippedData ) ;
this . _renderFooter ( unzippedData ) ;
if ( contentsHtml ) {
this . _renderContents ( contentsHtml ) ;
2016-01-30 10:42:46 -05:00
this . _fitToBottom ( ) ;
2016-04-14 20:02:54 -04:00
this . _fitToRight ( ) ;
2016-01-19 12:11:40 -05:00
this . _activateIndexedItem ( ) ;
}
this . _setScroll ( ) ;
2016-01-30 10:42:46 -05:00
} else if ( this . noResultsMessage ) {
this . _renderNoResultsMessage ( unzippedData ) ;
2016-01-19 12:11:40 -05:00
} else if ( this . shown ) {
this . deactivate ( ) ;
}
} ,
2016-01-30 10:42:46 -05:00
setPosition : function ( pos ) {
2016-01-19 12:11:40 -05:00
// Make the dropdown fixed if the input is also fixed
// This can't be done during init, as textcomplete may be used on multiple elements on the same page
// Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed
var position = 'absolute' ;
// Check if input or one of its parents has positioning we need to care about
2016-01-30 10:42:46 -05:00
this . $inputEl . add ( this . $inputEl . parents ( ) ) . each ( function ( ) {
2016-01-19 12:11:40 -05:00
if ( $ ( this ) . css ( 'position' ) === 'absolute' ) // The element has absolute positioning, so it's all OK
return false ;
if ( $ ( this ) . css ( 'position' ) === 'fixed' ) {
2016-04-14 20:02:54 -04:00
pos . top -= $window . scrollTop ( ) ;
pos . left -= $window . scrollLeft ( ) ;
2016-01-19 12:11:40 -05:00
position = 'fixed' ;
return false ;
}
} ) ;
2016-04-14 20:02:54 -04:00
this . $el . css ( this . _applyPlacement ( pos ) ) ;
2016-01-19 12:11:40 -05:00
this . $el . css ( { position : position } ) ; // Update positioning
return this ;
} ,
clear : function ( ) {
this . $el . html ( '' ) ;
this . data = [ ] ;
this . _index = 0 ;
2016-01-30 10:42:46 -05:00
this . _$header = this . _$footer = this . _$noResultsMessage = null ;
2016-01-19 12:11:40 -05:00
} ,
activate : function ( ) {
if ( ! this . shown ) {
this . clear ( ) ;
this . $el . show ( ) ;
if ( this . className ) { this . $el . addClass ( this . className ) ; }
this . completer . fire ( 'textComplete:show' ) ;
this . shown = true ;
}
return this ;
} ,
deactivate : function ( ) {
if ( this . shown ) {
this . $el . hide ( ) ;
if ( this . className ) { this . $el . removeClass ( this . className ) ; }
this . completer . fire ( 'textComplete:hide' ) ;
this . shown = false ;
}
return this ;
} ,
isUp : function ( e ) {
return e . keyCode === 38 || ( e . ctrlKey && e . keyCode === 80 ) ; // UP, Ctrl-P
} ,
isDown : function ( e ) {
return e . keyCode === 40 || ( e . ctrlKey && e . keyCode === 78 ) ; // DOWN, Ctrl-N
} ,
isEnter : function ( e ) {
var modifiers = e . ctrlKey || e . altKey || e . metaKey || e . shiftKey ;
return ! modifiers && ( e . keyCode === 13 || e . keyCode === 9 || ( this . option . completeOnSpace === true && e . keyCode === 32 ) ) // ENTER, TAB
} ,
isPageup : function ( e ) {
return e . keyCode === 33 ; // PAGEUP
} ,
isPagedown : function ( e ) {
return e . keyCode === 34 ; // PAGEDOWN
} ,
2016-01-30 10:42:46 -05:00
isEscape : function ( e ) {
return e . keyCode === 27 ; // ESCAPE
} ,
2016-01-19 12:11:40 -05:00
// Private properties
// ------------------
_data : null , // Currently shown zipped data.
_index : null ,
_$header : null ,
2016-01-30 10:42:46 -05:00
_$noResultsMessage : null ,
2016-01-19 12:11:40 -05:00
_$footer : null ,
// Private methods
// ---------------
_bindEvents : function ( ) {
2016-01-30 10:42:46 -05:00
this . $el . on ( 'mousedown.' + this . id , '.textcomplete-item' , $ . proxy ( this . _onClick , this ) ) ;
this . $el . on ( 'touchstart.' + this . id , '.textcomplete-item' , $ . proxy ( this . _onClick , this ) ) ;
2016-01-19 12:11:40 -05:00
this . $el . on ( 'mouseover.' + this . id , '.textcomplete-item' , $ . proxy ( this . _onMouseover , this ) ) ;
this . $inputEl . on ( 'keydown.' + this . id , $ . proxy ( this . _onKeydown , this ) ) ;
} ,
_onClick : function ( e ) {
var $el = $ ( e . target ) ;
e . preventDefault ( ) ;
e . originalEvent . keepTextCompleteDropdown = this . id ;
if ( ! $el . hasClass ( 'textcomplete-item' ) ) {
$el = $el . closest ( '.textcomplete-item' ) ;
}
var datum = this . data [ parseInt ( $el . data ( 'index' ) , 10 ) ] ;
2016-01-30 10:42:46 -05:00
this . completer . select ( datum . value , datum . strategy , e ) ;
2016-01-19 12:11:40 -05:00
var self = this ;
// Deactive at next tick to allow other event handlers to know whether
// the dropdown has been shown or not.
2016-01-30 10:42:46 -05:00
setTimeout ( function ( ) {
self . deactivate ( ) ;
if ( e . type === 'touchstart' ) {
self . $inputEl . focus ( ) ;
}
} , 0 ) ;
2016-01-19 12:11:40 -05:00
} ,
// Activate hovered item.
_onMouseover : function ( e ) {
var $el = $ ( e . target ) ;
e . preventDefault ( ) ;
if ( ! $el . hasClass ( 'textcomplete-item' ) ) {
$el = $el . closest ( '.textcomplete-item' ) ;
}
this . _index = parseInt ( $el . data ( 'index' ) , 10 ) ;
this . _activateIndexedItem ( ) ;
} ,
_onKeydown : function ( e ) {
if ( ! this . shown ) { return ; }
2016-01-30 10:42:46 -05:00
var command ;
if ( $ . isFunction ( this . option . onKeydown ) ) {
command = this . option . onKeydown ( e , commands ) ;
}
if ( command == null ) {
command = this . _defaultKeydown ( e ) ;
}
switch ( command ) {
case commands . KEY _UP :
e . preventDefault ( ) ;
this . _up ( ) ;
break ;
case commands . KEY _DOWN :
e . preventDefault ( ) ;
this . _down ( ) ;
break ;
case commands . KEY _ENTER :
e . preventDefault ( ) ;
this . _enter ( e ) ;
break ;
case commands . KEY _PAGEUP :
e . preventDefault ( ) ;
this . _pageup ( ) ;
break ;
case commands . KEY _PAGEDOWN :
e . preventDefault ( ) ;
this . _pagedown ( ) ;
break ;
case commands . KEY _ESCAPE :
e . preventDefault ( ) ;
this . deactivate ( ) ;
break ;
}
} ,
_defaultKeydown : function ( e ) {
2016-01-19 12:11:40 -05:00
if ( this . isUp ( e ) ) {
2016-01-30 10:42:46 -05:00
return commands . KEY _UP ;
2016-01-19 12:11:40 -05:00
} else if ( this . isDown ( e ) ) {
2016-01-30 10:42:46 -05:00
return commands . KEY _DOWN ;
2016-01-19 12:11:40 -05:00
} else if ( this . isEnter ( e ) ) {
2016-01-30 10:42:46 -05:00
return commands . KEY _ENTER ;
2016-01-19 12:11:40 -05:00
} else if ( this . isPageup ( e ) ) {
2016-01-30 10:42:46 -05:00
return commands . KEY _PAGEUP ;
2016-01-19 12:11:40 -05:00
} else if ( this . isPagedown ( e ) ) {
2016-01-30 10:42:46 -05:00
return commands . KEY _PAGEDOWN ;
} else if ( this . isEscape ( e ) ) {
return commands . KEY _ESCAPE ;
2016-01-19 12:11:40 -05:00
}
} ,
_up : function ( ) {
if ( this . _index === 0 ) {
this . _index = this . data . length - 1 ;
} else {
this . _index -= 1 ;
}
this . _activateIndexedItem ( ) ;
this . _setScroll ( ) ;
} ,
_down : function ( ) {
if ( this . _index === this . data . length - 1 ) {
this . _index = 0 ;
} else {
this . _index += 1 ;
}
this . _activateIndexedItem ( ) ;
this . _setScroll ( ) ;
} ,
2016-01-30 10:42:46 -05:00
_enter : function ( e ) {
2016-01-19 12:11:40 -05:00
var datum = this . data [ parseInt ( this . _getActiveElement ( ) . data ( 'index' ) , 10 ) ] ;
2016-01-30 10:42:46 -05:00
this . completer . select ( datum . value , datum . strategy , e ) ;
this . deactivate ( ) ;
2016-01-19 12:11:40 -05:00
} ,
_pageup : function ( ) {
var target = 0 ;
var threshold = this . _getActiveElement ( ) . position ( ) . top - this . $el . innerHeight ( ) ;
this . $el . children ( ) . each ( function ( i ) {
if ( $ ( this ) . position ( ) . top + $ ( this ) . outerHeight ( ) > threshold ) {
target = i ;
return false ;
}
} ) ;
this . _index = target ;
this . _activateIndexedItem ( ) ;
this . _setScroll ( ) ;
} ,
_pagedown : function ( ) {
var target = this . data . length - 1 ;
var threshold = this . _getActiveElement ( ) . position ( ) . top + this . $el . innerHeight ( ) ;
this . $el . children ( ) . each ( function ( i ) {
if ( $ ( this ) . position ( ) . top > threshold ) {
target = i ;
return false
}
} ) ;
this . _index = target ;
this . _activateIndexedItem ( ) ;
this . _setScroll ( ) ;
} ,
_activateIndexedItem : function ( ) {
this . $el . find ( '.textcomplete-item.active' ) . removeClass ( 'active' ) ;
this . _getActiveElement ( ) . addClass ( 'active' ) ;
} ,
_getActiveElement : function ( ) {
return this . $el . children ( '.textcomplete-item:nth(' + this . _index + ')' ) ;
} ,
_setScroll : function ( ) {
var $activeEl = this . _getActiveElement ( ) ;
var itemTop = $activeEl . position ( ) . top ;
var itemHeight = $activeEl . outerHeight ( ) ;
var visibleHeight = this . $el . innerHeight ( ) ;
var visibleTop = this . $el . scrollTop ( ) ;
if ( this . _index === 0 || this . _index == this . data . length - 1 || itemTop < 0 ) {
this . $el . scrollTop ( itemTop + visibleTop ) ;
} else if ( itemTop + itemHeight > visibleHeight ) {
this . $el . scrollTop ( itemTop + itemHeight + visibleTop - visibleHeight ) ;
}
} ,
_buildContents : function ( zippedData ) {
var datum , i , index ;
var html = '' ;
for ( i = 0 ; i < zippedData . length ; i ++ ) {
if ( this . data . length === this . maxCount ) break ;
datum = zippedData [ i ] ;
if ( include ( this . data , datum ) ) { continue ; }
index = this . data . length ;
this . data . push ( datum ) ;
html += '<li class="textcomplete-item" data-index="' + index + '"><a>' ;
2016-01-30 10:42:46 -05:00
html += datum . strategy . template ( datum . value , datum . term ) ;
2016-01-19 12:11:40 -05:00
html += '</a></li>' ;
}
return html ;
} ,
_renderHeader : function ( unzippedData ) {
if ( this . header ) {
if ( ! this . _$header ) {
this . _$header = $ ( '<li class="textcomplete-header"></li>' ) . prependTo ( this . $el ) ;
}
var html = $ . isFunction ( this . header ) ? this . header ( unzippedData ) : this . header ;
this . _$header . html ( html ) ;
}
} ,
_renderFooter : function ( unzippedData ) {
if ( this . footer ) {
if ( ! this . _$footer ) {
this . _$footer = $ ( '<li class="textcomplete-footer"></li>' ) . appendTo ( this . $el ) ;
}
var html = $ . isFunction ( this . footer ) ? this . footer ( unzippedData ) : this . footer ;
this . _$footer . html ( html ) ;
}
} ,
2016-01-30 10:42:46 -05:00
_renderNoResultsMessage : function ( unzippedData ) {
if ( this . noResultsMessage ) {
if ( ! this . _$noResultsMessage ) {
this . _$noResultsMessage = $ ( '<li class="textcomplete-no-results-message"></li>' ) . appendTo ( this . $el ) ;
}
var html = $ . isFunction ( this . noResultsMessage ) ? this . noResultsMessage ( unzippedData ) : this . noResultsMessage ;
this . _$noResultsMessage . html ( html ) ;
}
} ,
2016-01-19 12:11:40 -05:00
_renderContents : function ( html ) {
if ( this . _$footer ) {
this . _$footer . before ( html ) ;
} else {
this . $el . append ( html ) ;
}
} ,
2016-01-30 10:42:46 -05:00
_fitToBottom : function ( ) {
var windowScrollBottom = $window . scrollTop ( ) + $window . height ( ) ;
var height = this . $el . height ( ) ;
if ( ( this . $el . position ( ) . top + height ) > windowScrollBottom ) {
this . $el . offset ( { top : windowScrollBottom - height } ) ;
}
} ,
2016-04-14 20:02:54 -04:00
_fitToRight : function ( ) {
// We don't know how wide our content is until the browser positions us, and at that point it clips us
// to the document width so we don't know if we would have overrun it. As a heuristic to avoid that clipping
// (which makes our elements wrap onto the next line and corrupt the next item), if we're close to the right
// edge, move left. We don't know how far to move left, so just keep nudging a bit.
var tolerance = 30 ; // pixels. Make wider than vertical scrollbar because we might not be able to use that space.
while ( this . $el . offset ( ) . left + this . $el . width ( ) > $window . width ( ) - tolerance ) {
this . $el . offset ( { left : this . $el . offset ( ) . left - tolerance } ) ;
}
} ,
2016-01-30 10:42:46 -05:00
_applyPlacement : function ( position ) {
2016-01-19 12:11:40 -05:00
// If the 'placement' option set to 'top', move the position above the element.
if ( this . placement . indexOf ( 'top' ) !== - 1 ) {
// Overwrite the position object to set the 'bottom' property instead of the top.
position = {
top : 'auto' ,
bottom : this . $el . parent ( ) . height ( ) - position . top + position . lineHeight ,
left : position . left
} ;
} else {
position . bottom = 'auto' ;
delete position . lineHeight ;
}
if ( this . placement . indexOf ( 'absleft' ) !== - 1 ) {
position . left = 0 ;
} else if ( this . placement . indexOf ( 'absright' ) !== - 1 ) {
position . right = 0 ;
position . left = 'auto' ;
}
return position ;
}
} ) ;
$ . fn . textcomplete . Dropdown = Dropdown ;
2016-01-30 10:42:46 -05:00
$ . extend ( $ . fn . textcomplete , commands ) ;
2016-01-19 12:11:40 -05:00
} ( jQuery ) ;
+ function ( $ ) {
'use strict' ;
// Memoize a search function.
var memoize = function ( func ) {
var memo = { } ;
return function ( term , callback ) {
if ( memo [ term ] ) {
callback ( memo [ term ] ) ;
} else {
func . call ( this , term , function ( data ) {
memo [ term ] = ( memo [ term ] || [ ] ) . concat ( data ) ;
callback . apply ( null , arguments ) ;
} ) ;
}
} ;
} ;
function Strategy ( options ) {
$ . extend ( this , options ) ;
if ( this . cache ) { this . search = memoize ( this . search ) ; }
}
2016-01-30 10:42:46 -05:00
Strategy . parse = function ( strategiesArray , params ) {
return $ . map ( strategiesArray , function ( strategy ) {
var strategyObj = new Strategy ( strategy ) ;
strategyObj . el = params . el ;
strategyObj . $el = params . $el ;
return strategyObj ;
2016-01-19 12:11:40 -05:00
} ) ;
} ;
$ . extend ( Strategy . prototype , {
// Public properties
// -----------------
// Required
match : null ,
replace : null ,
search : null ,
// Optional
2016-04-14 20:02:54 -04:00
id : null ,
2016-01-19 12:11:40 -05:00
cache : false ,
context : function ( ) { return true ; } ,
index : 2 ,
template : function ( obj ) { return obj ; } ,
idProperty : null
} ) ;
$ . fn . textcomplete . Strategy = Strategy ;
} ( jQuery ) ;
+ function ( $ ) {
'use strict' ;
var now = Date . now || function ( ) { return new Date ( ) . getTime ( ) ; } ;
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// `wait` msec.
//
// This utility function was originally implemented at Underscore.js.
var debounce = function ( func , wait ) {
var timeout , args , context , timestamp , result ;
var later = function ( ) {
var last = now ( ) - timestamp ;
if ( last < wait ) {
timeout = setTimeout ( later , wait - last ) ;
} else {
timeout = null ;
result = func . apply ( context , args ) ;
context = args = null ;
}
} ;
return function ( ) {
context = this ;
args = arguments ;
timestamp = now ( ) ;
if ( ! timeout ) {
timeout = setTimeout ( later , wait ) ;
}
return result ;
} ;
} ;
function Adapter ( ) { }
$ . extend ( Adapter . prototype , {
// Public properties
// -----------------
id : null , // Identity.
completer : null , // Completer object which creates it.
el : null , // Textarea element.
$el : null , // jQuery object of the textarea.
option : null ,
// Public methods
// --------------
initialize : function ( element , completer , option ) {
this . el = element ;
this . $el = $ ( element ) ;
this . id = completer . id + this . constructor . name ;
this . completer = completer ;
this . option = option ;
if ( this . option . debounce ) {
this . _onKeyup = debounce ( this . _onKeyup , this . option . debounce ) ;
}
this . _bindEvents ( ) ;
} ,
destroy : function ( ) {
this . $el . off ( '.' + this . id ) ; // Remove all event handlers.
this . $el = this . el = this . completer = null ;
} ,
// Update the element with the given value and strategy.
//
// value - The selected object. It is one of the item of the array
// which was callbacked from the search function.
// strategy - The Strategy associated with the selected value.
select : function ( /* value, strategy */ ) {
throw new Error ( 'Not implemented' ) ;
} ,
// Returns the caret's relative coordinates from body's left top corner.
getCaretPosition : function ( ) {
var position = this . _getCaretRelativePosition ( ) ;
var offset = this . $el . offset ( ) ;
2016-04-14 20:02:54 -04:00
// Calculate the left top corner of `this.option.appendTo` element.
var $parent = this . option . appendTo ;
if ( $parent ) {
if ( ! ( $parent instanceof $ ) ) { $parent = $ ( $parent ) ; }
var parentOffset = $parent . offsetParent ( ) . offset ( ) ;
offset . top -= parentOffset . top ;
offset . left -= parentOffset . left ;
}
2016-01-19 12:11:40 -05:00
position . top += offset . top ;
position . left += offset . left ;
return position ;
} ,
// Focus on the element.
focus : function ( ) {
this . $el . focus ( ) ;
} ,
// Private methods
// ---------------
_bindEvents : function ( ) {
this . $el . on ( 'keyup.' + this . id , $ . proxy ( this . _onKeyup , this ) ) ;
} ,
_onKeyup : function ( e ) {
if ( this . _skipSearch ( e ) ) { return ; }
this . completer . trigger ( this . getTextFromHeadToCaret ( ) , true ) ;
} ,
// Suppress searching if it returns true.
_skipSearch : function ( clickEvent ) {
switch ( clickEvent . keyCode ) {
2016-04-14 20:02:54 -04:00
case 9 : // TAB
2016-01-30 10:42:46 -05:00
case 13 : // ENTER
2016-01-19 12:11:40 -05:00
case 40 : // DOWN
case 38 : // UP
return true ;
}
if ( clickEvent . ctrlKey ) switch ( clickEvent . keyCode ) {
case 78 : // Ctrl-N
case 80 : // Ctrl-P
return true ;
}
}
} ) ;
$ . fn . textcomplete . Adapter = Adapter ;
} ( jQuery ) ;
+ function ( $ ) {
'use strict' ;
// Textarea adapter
// ================
//
// Managing a textarea. It doesn't know a Dropdown.
function Textarea ( element , completer , option ) {
this . initialize ( element , completer , option ) ;
}
$ . extend ( Textarea . prototype , $ . fn . textcomplete . Adapter . prototype , {
// Public methods
// --------------
// Update the textarea with the given value and strategy.
2016-01-30 10:42:46 -05:00
select : function ( value , strategy , e ) {
2016-01-19 12:11:40 -05:00
var pre = this . getTextFromHeadToCaret ( ) ;
var post = this . el . value . substring ( this . el . selectionEnd ) ;
2016-01-30 10:42:46 -05:00
var newSubstr = strategy . replace ( value , e ) ;
if ( typeof newSubstr !== 'undefined' ) {
if ( $ . isArray ( newSubstr ) ) {
post = newSubstr [ 1 ] + post ;
newSubstr = newSubstr [ 0 ] ;
}
pre = pre . replace ( strategy . match , newSubstr ) ;
this . $el . val ( pre + post ) ;
this . el . selectionStart = this . el . selectionEnd = pre . length ;
2016-01-19 12:11:40 -05:00
}
} ,
2016-04-14 20:02:54 -04:00
getTextFromHeadToCaret : function ( ) {
return this . el . value . substring ( 0 , this . el . selectionEnd ) ;
} ,
2016-01-19 12:11:40 -05:00
// Private methods
// ---------------
_getCaretRelativePosition : function ( ) {
2016-04-14 20:02:54 -04:00
var p = $ . fn . textcomplete . getCaretCoordinates ( this . el , this . el . selectionStart ) ;
return {
top : p . top + this . _calculateLineHeight ( ) - this . $el . scrollTop ( ) ,
left : p . left - this . $el . scrollLeft ( )
} ;
2016-01-19 12:11:40 -05:00
} ,
2016-04-14 20:02:54 -04:00
_calculateLineHeight : function ( ) {
var lineHeight = parseInt ( this . $el . css ( 'line-height' ) , 10 ) ;
if ( isNaN ( lineHeight ) ) {
// http://stackoverflow.com/a/4515470/1297336
var parentNode = this . el . parentNode ;
var temp = document . createElement ( this . el . nodeName ) ;
var style = this . el . style ;
temp . setAttribute (
'style' ,
'margin:0px;padding:0px;font-family:' + style . fontFamily + ';font-size:' + style . fontSize
) ;
temp . innerHTML = 'test' ;
parentNode . appendChild ( temp ) ;
lineHeight = temp . clientHeight ;
parentNode . removeChild ( temp ) ;
2016-01-19 12:11:40 -05:00
}
2016-04-14 20:02:54 -04:00
return lineHeight ;
2016-01-19 12:11:40 -05:00
}
} ) ;
$ . fn . textcomplete . Textarea = Textarea ;
} ( jQuery ) ;
+ function ( $ ) {
'use strict' ;
var sentinelChar = '吶' ;
function IETextarea ( element , completer , option ) {
this . initialize ( element , completer , option ) ;
$ ( '<span>' + sentinelChar + '</span>' ) . css ( {
position : 'absolute' ,
top : - 9999 ,
left : - 9999
} ) . insertBefore ( element ) ;
}
$ . extend ( IETextarea . prototype , $ . fn . textcomplete . Textarea . prototype , {
// Public methods
// --------------
2016-01-30 10:42:46 -05:00
select : function ( value , strategy , e ) {
2016-01-19 12:11:40 -05:00
var pre = this . getTextFromHeadToCaret ( ) ;
var post = this . el . value . substring ( pre . length ) ;
2016-01-30 10:42:46 -05:00
var newSubstr = strategy . replace ( value , e ) ;
if ( typeof newSubstr !== 'undefined' ) {
if ( $ . isArray ( newSubstr ) ) {
post = newSubstr [ 1 ] + post ;
newSubstr = newSubstr [ 0 ] ;
}
pre = pre . replace ( strategy . match , newSubstr ) ;
this . $el . val ( pre + post ) ;
this . el . focus ( ) ;
var range = this . el . createTextRange ( ) ;
range . collapse ( true ) ;
range . moveEnd ( 'character' , pre . length ) ;
range . moveStart ( 'character' , pre . length ) ;
range . select ( ) ;
2016-01-19 12:11:40 -05:00
}
} ,
getTextFromHeadToCaret : function ( ) {
this . el . focus ( ) ;
var range = document . selection . createRange ( ) ;
range . moveStart ( 'character' , - this . el . value . length ) ;
var arr = range . text . split ( sentinelChar )
return arr . length === 1 ? arr [ 0 ] : arr [ 1 ] ;
}
} ) ;
$ . fn . textcomplete . IETextarea = IETextarea ;
} ( jQuery ) ;
// NOTE: TextComplete plugin has contenteditable support but it does not work
// fine especially on old IEs.
// Any pull requests are REALLY welcome.
+ function ( $ ) {
'use strict' ;
// ContentEditable adapter
// =======================
//
// Adapter for contenteditable elements.
function ContentEditable ( element , completer , option ) {
this . initialize ( element , completer , option ) ;
}
$ . extend ( ContentEditable . prototype , $ . fn . textcomplete . Adapter . prototype , {
// Public methods
// --------------
// Update the content with the given value and strategy.
// When an dropdown item is selected, it is executed.
2016-01-30 10:42:46 -05:00
select : function ( value , strategy , e ) {
2016-01-19 12:11:40 -05:00
var pre = this . getTextFromHeadToCaret ( ) ;
var sel = window . getSelection ( )
var range = sel . getRangeAt ( 0 ) ;
var selection = range . cloneRange ( ) ;
selection . selectNodeContents ( range . startContainer ) ;
var content = selection . toString ( ) ;
var post = content . substring ( range . startOffset ) ;
2016-01-30 10:42:46 -05:00
var newSubstr = strategy . replace ( value , e ) ;
if ( typeof newSubstr !== 'undefined' ) {
if ( $ . isArray ( newSubstr ) ) {
post = newSubstr [ 1 ] + post ;
newSubstr = newSubstr [ 0 ] ;
}
pre = pre . replace ( strategy . match , newSubstr ) ;
range . selectNodeContents ( range . startContainer ) ;
range . deleteContents ( ) ;
2016-04-14 20:02:54 -04:00
// create temporary elements
var preWrapper = document . createElement ( "div" ) ;
preWrapper . innerHTML = pre ;
var postWrapper = document . createElement ( "div" ) ;
postWrapper . innerHTML = post ;
// create the fragment thats inserted
var fragment = document . createDocumentFragment ( ) ;
var childNode ;
var lastOfPre ;
while ( childNode = preWrapper . firstChild ) {
lastOfPre = fragment . appendChild ( childNode ) ;
}
while ( childNode = postWrapper . firstChild ) {
fragment . appendChild ( childNode ) ;
}
// insert the fragment & jump behind the last node in "pre"
range . insertNode ( fragment ) ;
range . setStartAfter ( lastOfPre ) ;
2016-01-30 10:42:46 -05:00
range . collapse ( true ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
2016-01-19 12:11:40 -05:00
}
} ,
// Private methods
// ---------------
// Returns the caret's relative position from the contenteditable's
// left top corner.
//
// Examples
//
// this._getCaretRelativePosition()
// //=> { top: 18, left: 200, lineHeight: 16 }
//
// Dropdown's position will be decided using the result.
_getCaretRelativePosition : function ( ) {
var range = window . getSelection ( ) . getRangeAt ( 0 ) . cloneRange ( ) ;
var node = document . createElement ( 'span' ) ;
range . insertNode ( node ) ;
range . selectNodeContents ( node ) ;
range . deleteContents ( ) ;
var $node = $ ( node ) ;
var position = $node . offset ( ) ;
position . left -= this . $el . offset ( ) . left ;
position . top += $node . height ( ) - this . $el . offset ( ) . top ;
position . lineHeight = $node . height ( ) ;
2016-01-30 10:42:46 -05:00
$node . remove ( ) ;
2016-01-19 12:11:40 -05:00
return position ;
} ,
// Returns the string between the first character and the caret.
// Completer will be triggered with the result for start autocompleting.
//
// Example
//
// // Suppose the html is '<b>hello</b> wor|ld' and | is the caret.
// this.getTextFromHeadToCaret()
// // => ' wor' // not '<b>hello</b> wor'
getTextFromHeadToCaret : function ( ) {
var range = window . getSelection ( ) . getRangeAt ( 0 ) ;
var selection = range . cloneRange ( ) ;
selection . selectNodeContents ( range . startContainer ) ;
return selection . toString ( ) . substring ( 0 , range . startOffset ) ;
}
} ) ;
$ . fn . textcomplete . ContentEditable = ContentEditable ;
} ( jQuery ) ;
2016-01-30 10:42:46 -05:00
2016-04-14 20:02:54 -04:00
// The MIT License (MIT)
//
// Copyright (c) 2015 Jonathan Ong me@jongleberry.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// https://github.com/component/textarea-caret-position
( function ( ) {
// The properties that we copy into a mirrored div.
// Note that some browsers, such as Firefox,
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
// so we have to do every single property specifically.
var properties = [
'direction' , // RTL support
'boxSizing' ,
'width' , // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
'height' ,
'overflowX' ,
'overflowY' , // copy the scrollbar for IE
'borderTopWidth' ,
'borderRightWidth' ,
'borderBottomWidth' ,
'borderLeftWidth' ,
'borderStyle' ,
'paddingTop' ,
'paddingRight' ,
'paddingBottom' ,
'paddingLeft' ,
// https://developer.mozilla.org/en-US/docs/Web/CSS/font
'fontStyle' ,
'fontVariant' ,
'fontWeight' ,
'fontStretch' ,
'fontSize' ,
'fontSizeAdjust' ,
'lineHeight' ,
'fontFamily' ,
'textAlign' ,
'textTransform' ,
'textIndent' ,
'textDecoration' , // might not make a difference, but better be safe
'letterSpacing' ,
'wordSpacing' ,
'tabSize' ,
'MozTabSize'
] ;
var isBrowser = ( typeof window !== 'undefined' ) ;
var isFirefox = ( isBrowser && window . mozInnerScreenX != null ) ;
function getCaretCoordinates ( element , position , options ) {
if ( ! isBrowser ) {
throw new Error ( 'textarea-caret-position#getCaretCoordinates should only be called in a browser' ) ;
}
var debug = options && options . debug || false ;
if ( debug ) {
var el = document . querySelector ( '#input-textarea-caret-position-mirror-div' ) ;
if ( el ) { el . parentNode . removeChild ( el ) ; }
}
// mirrored div
var div = document . createElement ( 'div' ) ;
div . id = 'input-textarea-caret-position-mirror-div' ;
document . body . appendChild ( div ) ;
var style = div . style ;
var computed = window . getComputedStyle ? getComputedStyle ( element ) : element . currentStyle ; // currentStyle for IE < 9
// default textarea styles
style . whiteSpace = 'pre-wrap' ;
if ( element . nodeName !== 'INPUT' )
style . wordWrap = 'break-word' ; // only for textarea-s
// position off-screen
style . position = 'absolute' ; // required to return coordinates properly
if ( ! debug )
style . visibility = 'hidden' ; // not 'display: none' because we want rendering
// transfer the element's properties to the div
properties . forEach ( function ( prop ) {
style [ prop ] = computed [ prop ] ;
} ) ;
if ( isFirefox ) {
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
if ( element . scrollHeight > parseInt ( computed . height ) )
style . overflowY = 'scroll' ;
} else {
style . overflow = 'hidden' ; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
}
div . textContent = element . value . substring ( 0 , position ) ;
// the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
if ( element . nodeName === 'INPUT' )
div . textContent = div . textContent . replace ( /\s/g , '\u00a0' ) ;
var span = document . createElement ( 'span' ) ;
// Wrapping must be replicated *exactly*, including when a long word gets
// onto the next line, with whitespace at the end of the line before (#7).
// The *only* reliable way to do that is to copy the *entire* rest of the
// textarea's content into the <span> created at the caret position.
// for inputs, just '.' would be enough, but why bother?
span . textContent = element . value . substring ( position ) || '.' ; // || because a completely empty faux span doesn't render at all
div . appendChild ( span ) ;
var coordinates = {
top : span . offsetTop + parseInt ( computed [ 'borderTopWidth' ] ) ,
left : span . offsetLeft + parseInt ( computed [ 'borderLeftWidth' ] )
} ;
if ( debug ) {
span . style . backgroundColor = '#aaa' ;
} else {
document . body . removeChild ( div ) ;
}
return coordinates ;
}
if ( typeof module != 'undefined' && typeof module . exports != 'undefined' ) {
module . exports = getCaretCoordinates ;
} else if ( isBrowser ) {
window . $ . fn . textcomplete . getCaretCoordinates = getCaretCoordinates ;
}
} ( ) ) ;
2016-01-30 10:42:46 -05:00
return jQuery ;
} ) ) ;