1
0
mirror of https://github.com/e107inc/e107.git synced 2025-08-01 12:20:44 +02:00

Bootstrap-suggest library added.

This commit is contained in:
Cameron
2021-02-24 15:36:16 -08:00
parent 5f085c9446
commit 827af45c03
5 changed files with 742 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 Jovanni Lo
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.

View File

@@ -0,0 +1,69 @@
bootstrap-suggest
============================
A bootstrap plugin for your mention needs.
![demo](demo.png "demo")
## V2
The version 2 of this plugin now supports bootstrap 4 and `contenteditable` that uses `jquery.caret` (optional).
## Install
Several quick start options are available:
- [download](https://github.com/lodev09/bootstrap-suggest/archive/v2.0.2.zip) latest release
- [npm](https://www.npmjs.com/package/bootstrap-suggest): `npm install --save bootstrap-suggest`
- [bower](https://bower.io): `bower install bootstrap-suggest`
** Make sure to link `bootstrap-suggest.js` and `bootstrap-suggest.css` to your project
## Usage
### Markup
```html
<div class="form-group">
<label for="comment">start typing with @</label>
<textarea class="form-control" rows="5" id="comment"></textarea>
</div>
```
### Data
```
var users = [
{username: 'lodev09', fullname: 'Jovanni Lo'},
{username: 'foo', fullname: 'Foo User'},
{username: 'bar', fullname: 'Bar User'},
{username: 'twbs', fullname: 'Twitter Bootstrap'},
{username: 'john', fullname: 'John Doe'},
{username: 'jane', fullname: 'Jane Doe'},
];
```
### Init
```javascript
$('#comment').suggest('@', {
data: users,
map: function(user) {
return {
value: user.username,
text: '<strong>'+user.username+'</strong> <small>'+user.fullname+'</small>'
}
}
})
```
## API
http://lodev09.github.io/bootstrap-suggest/#api
## Feedback
All bugs, feature requests, pull requests, feedback, etc., are welcome. Visit my site at [www.lodev09.com](http://www.lodev09.com "www.lodev09.com").
[![LICENSE MIT](https://img.shields.io/badge/Mail%20me%20at-lodev09%40gmail.com-green.svg)](mailto:lodev09@gmail.com)
## Credits
&copy; 2018 - Coded by Jovanni Lo / [@lodev09](http://twitter.com/lodev09)
## License
Released under the MIT license. See [LICENSE](LICENSE) file.
[![LICENSE MIT](https://img.shields.io/badge/license-MIT-red.svg)](http://opensource.org/licenses/MIT)

View File

@@ -0,0 +1,29 @@
/*!
* bootstra-suggest - v2.0.3 (https://github.com/lodev09/bootstrap-suggest#readme)
* Copyright 2013-2019 Jovanni Lo (lodev09@gmail.com)
* Licensed under MIT (https://github.com/lodev09/bootstrap-suggest/blob/master/LICENSE)
*/
.suggest {
/* position: relative; */
top: 7px;
z-index: 30;
text-align: left;
}
.suggest > .dropdown-menu {
margin-top: 15px;
position: absolute;
height: 200px;
overflow-y: scroll
}
.suggest > .dropdown-menu > a.dropdown-item {
border-top: 1px solid #eeeeee;
padding: 5px 10px;
}
.suggest > .dropdown-menu > a.dropdown-item:first-child {
border-top: 0;
}

View File

@@ -0,0 +1,592 @@
/* ===================================================
* bootstrap-suggest.js v2.0.3
* http://github.com/lodev09/bootstrap-suggest
* ===================================================
* Copyright 2019 Jovanni Lo @lodev09
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
(function ($) {
"use strict"; // jshint ;_;
var Suggest = function(el, key, options) {
var that = this;
this.$element = $(el);
this.$items = undefined;
this.options = $.extend(true, {}, $.fn.suggest.defaults, options, this.$element.data(), this.$element.data('options'));
this.key = key;
this.isShown = false;
this.query = '';
this._queryPos = [];
this._keyPos = -1;
this.$dropdown = $('<div />', {
'class': 'dropdown suggest ' + this.options.dropdownClass,
'html': $('<ul />', {'class': 'dropdown-menu', role: 'menu'}),
'data-key': this.key
});
this.load();
};
Suggest.prototype = {
__setListener: function() {
this.$element
.on('suggest.show', $.proxy(this.options.onshow, this))
.on('suggest.select', $.proxy(this.options.onselect, this))
.on('suggest.lookup', $.proxy(this.options.onlookup, this))
.on('keyup', $.proxy(this.__keyup, this));
return this;
},
__getCaretPos: function(posStart) {
// https://github.com/component/textarea-caret-position/blob/master/index.js
// 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',
'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'
];
var isFirefox = !(window.mozInnerScreenX == null);
var getCaretCoordinatesFn = function (element, position, recalculate) {
// 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
style.visibility = 'hidden'; // not 'display: none' because we want rendering
// transfer the element's properties to the div
$.each(properties, function (index, value) {
style[value] = computed[value];
});
if (isFirefox) {
style.width = parseInt(computed.width) - 2 + 'px'; // Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662
// 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'])
};
document.body.removeChild(div);
return coordinates;
}
return getCaretCoordinatesFn(this.$element.get(0), posStart);
},
__keyup: function(e) {
// don't query special characters
// http://mikemurko.com/general/jquery-keycode-cheatsheet/
var specialChars = [38, 40, 37, 39, 17, 18, 9, 16, 20, 91, 93, 36, 35, 45, 33, 34, 144, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 145, 19];
switch (e.keyCode) {
case 27: // escape
this.hide();
return;
case 13: // enter/return
return true;
}
if ($.inArray(e.keyCode, specialChars) !== -1) return;
var $el = this.$element,
val = $el.val(),
currentPos = this.__getSelection($el.get(0)).start;
for (var i = currentPos; i >= 0; i--) {
var subChar = $.trim(val.substring(i-1, i));
if (!subChar) {
this.hide();
break;
}
if (subChar === this.key && $.trim(val.substring(i-2, i-1)) == '') {
this.query = val.substring(i, currentPos);
this._queryPos = [i, currentPos];
this._keyPos = i;
this.lookup(this.query);
break;
}
}
},
__getVisibleItems: function() {
return this.$items ? this.$items.not('.d-none') : $();
},
__build: function() {
var elems = [], $item,
$dropdown = this.$dropdown,
that = this;
var blur = function(e) {
that.hide();
}
$dropdown
.on('click', 'li:has(a)', function(e) {
e.preventDefault();
that.__select($(this).index());
that.$element.focus();
})
.on('mouseover', 'li:has(a)', function(e) {
that.$element.off('blur', blur);
})
.on('mouseout', 'li:has(a)', function(e) {
that.$element.on('blur', blur);
});
this.$element.before($dropdown).on('blur', blur).on('keydown', function(e) {
var $visibleItems;
if (that.isShown) {
switch (e.keyCode) {
case 13: // enter key
$visibleItems = that.__getVisibleItems();
$visibleItems.each(function(index) {
if ($(this).is('.active'))
that.__select($(this).index());
});
return false;
break;
case 40: // arrow down
$visibleItems = that.__getVisibleItems();
if ($visibleItems.last().is('.active')) return false;
$visibleItems.each(function(index) {
var $this = $(this),
$next = $visibleItems.eq(index + 1);
//if (!$next.length) return false;
if ($this.is('.active')) {
if (!$next.is('.hidden')) {
$this.removeClass('active');
$next.addClass('active');
}
return false;
}
});
return false;
case 38: // arrow up
$visibleItems = that.__getVisibleItems();
if ($visibleItems.first().is('.active')) return false;
$visibleItems.each(function(index) {
var $this = $(this),
$prev = $visibleItems.eq(index - 1);
//if (!$prev.length) return false;
if ($this.is('.active')) {
if (!$prev.is('.hidden')) {
$this.removeClass('active');
$prev.addClass('active');
}
return false;
}
})
return false;
}
}
});
},
__mapItem: function(dataItem) {
var itemHtml, that = this,
_item = {
text: '',
value: ''
};
if (this.options.map) {
dataItem = this.options.map(dataItem);
if (!dataItem) return false;
}
if (dataItem instanceof Object) {
_item.text = dataItem.text || '';
_item.value = dataItem.value || '';
} else {
_item.text = dataItem;
_item.value = dataItem;
}
return $('<li />', {'data-value': _item.value}).html($('<a />', {
href: '#',
html: _item.text
})).addClass('dropdown-item');
},
__select: function(index) {
var $el = this.$element,
el = $el.get(0),
val = $el.val(),
item = this.get(index),
setCaretPos = this._keyPos + item.value.length + 1;
$el.val(val.slice(0, this._keyPos) + item.value + this.options.endKey + val.slice(this.__getSelection(el).start));
if (el.setSelectionRange) {
el.setSelectionRange(setCaretPos, setCaretPos);
} else if (el.createTextRange) {
var range = el.createTextRange();
range.collapse(true);
range.moveEnd('character', setCaretPos);
range.moveStart('character', setCaretPos);
range.select();
}
$el.trigger($.extend({type: 'suggest.select'}, this), item);
this.hide();
},
__getSelection: function (el) {
//in IE9 selectionStart will always be 9 if not focused(when selecting using the mouse)
el.focus();
return {
start: el.selectionStart,
end: el.selectionEnd
};
},
__buildItems: function(data) {
var $dropdownMenu = this.$dropdown.find('.dropdown-menu');
$dropdownMenu.empty();
if (data && data instanceof Array) {
for (var i in data) {
var $item = this.__mapItem(data[i]);
if ($item) {
$dropdownMenu.append($item);
}
}
}
return $dropdownMenu.find('li:has(a)');
},
__lookup: function(q, $resultItems) {
var active = $resultItems.eq(0).addClass('active');
this.$element.trigger($.extend({type: 'suggest.lookup'}, this), [q, $resultItems]);
if ($resultItems && $resultItems.length) {
this.show();
} else {
this.hide();
}
},
__filterData: function(q, data) {
var options = this.options;
this.$items.addClass('hidden');
this.$items.filter(function (index) {
// return the limit if q is empty
if (q === '') return index < options.filter.limit;
var $this = $(this),
value = $this.find('a:first').text();
if (!options.filter.casesensitive) {
value = value.toLowerCase();
q = q.toLowerCase();
}
return value.indexOf(q) != -1;
}).slice(0, options.filter.limit).removeClass('hidden active');
return this.__getVisibleItems();
},
get: function(index) {
if (!this.$items) return;
var $item = this.$items.eq(index);
return {
text: $item.children('a:first').text(),
value: $item.attr('data-value'),
index: index,
$element: $item
};
},
lookup: function(q) {
var options = this.options,
that = this,
data;
var provide = function(data) {
// verify that we're still "typing" the query (no space)
if (that._keyPos !== -1) {
if (!that.$items) {
that.$items = that.__buildItems(data);
}
that.__lookup(q, that.__filterData(q, data));
}
};
if (typeof this.options.data === 'function') {
this.$items = undefined;
data = this.options.data(q, provide);
} else {
data = this.options.data;
}
if (data && typeof data.promise === 'function') {
data.done(provide);
} else if (data) {
provide.call(this, data);
}
},
load: function() {
this.__setListener();
this.__build();
},
hide: function() {
this.$dropdown.removeClass('open show');
this.isShown = false;
if(this.$items) {
this.$items.removeClass('active');
}
this._keyPos = -1;
},
show: function() {
var $el = this.$element,
$dropdownMenu = this.$dropdown.find('.dropdown-menu'),
el = $el.get(0),
options = this.options,
caretPos,
position = {
top: 'auto',
bottom: 'auto',
left: 'auto',
right: 'auto'
};
if (!this.isShown) {
this.$dropdown.addClass('open show');
if (options.position !== false) {
caretPos = this.__getCaretPos(this._keyPos);
if (typeof options.position == 'string') {
switch (options.position) {
case 'bottom':
position.top = $el.outerHeight() - parseFloat($dropdownMenu.css('margin-top'));
position.left = 0;
position.right = 0;
break;
case 'top':
position.top = -($dropdownMenu.outerHeight(true) + parseFloat($dropdownMenu.css('margin-top')));
position.left = 0;
position.right = 0;
break;
case 'caret':
position.top = caretPos.top - el.scrollTop;
position.left = caretPos.left - el.scrollLeft;
break;
}
} else {
position = $.extend(position, typeof options.position === 'function' ? options.position(el, caretPos) : options.position);
}
$dropdownMenu.css(position);
}
this.isShown = true;
$el.trigger($.extend({type: 'suggest.show'}, this));
}
}
};
var old = $.fn.suggest;
// .suggest( key [, options] )
// .suggest( method [, options] )
// .suggest( suggestions )
$.fn.suggest = function(arg1) {
var arg2 = arguments[1],
arg3 = arguments[2];
var createSuggestions = function(el, suggestions) {
var newData = {};
$.each(suggestions, function(keyChar, options) {
var key = keyChar.toString().charAt(0);
// remove existing suggest
// $('.suggest.dropdown[data-key="'+key+'"]').remove();
newData[key] = new Suggest(el, key, typeof options === 'object' && options);
});
return newData;
};
return this.each(function() {
var that = this,
$this = $(this),
data = $this.data('suggest'),
suggestions = {};
if (typeof arg1 === 'string') {
if (arg1.length == 1) {
// arg1 as key
if (arg2) {
// arg2 is a function name
if (typeof arg2 === 'string') {
if (arg1 in data && typeof data[arg1][arg2] !== 'undefined') {
return data[arg1][arg2].call(data[arg1], arg3);
} else {
console.error(arg1 + ' is not a suggest');
}
} else {
// inline data determined if it's an array
suggestions[arg1] = $.isArray(arg2) || typeof arg2 === 'function' ? {data: arg2} : arg2;
// if key is existing, update options
if (data && arg1 in data) {
data[arg1].options = $.extend({}, data[arg1].options, suggestions[arg1]);
} else {
data = $.extend(data, createSuggestions(this, suggestions));
}
$this.data('suggest', data);
}
}
} else {
console.error('you\'re not initializing suggest properly. arg1 should have length == 1');
}
} else {
// arg1 contains set of suggestions
if (!data) $this.data('suggest', createSuggestions(this, arg1));
else if (data) {
// create/update suggestions
$.each(arg1, function(key, value) {
if (key in data === false) {
suggestions[key] = value;
} else {
// extend (update) options
data[key].options = $.extend({}, data[key].options, value);
}
});
$this.data('suggest', $.extend(data, createSuggestions(that, suggestions)))
}
}
});
};
$.fn.suggest.defaults = {
data: [],
map: undefined,
filter: {
casesensitive: false,
limit: 5
},
endKey: ' ',
dropdownClass: '',
position: 'caret',
// events hook
onshow: function(e) {},
onselect: function(e, item) {},
onlookup: function(e, item) {}
}
$.fn.suggest.Constructor = Suggest;
$.fn.suggest.noConflict = function () {
$.fn.suggest = old;
return this;
}
}( jQuery ));

View File

@@ -0,0 +1,31 @@
/*!
* bootstra-suggest - v2.0.1 (https://github.com/lodev09/bootstrap-suggest#readme)
* Copyright 2013-2019 Jovanni Lo (lodev09@gmail.com)
* Licensed under MIT (https://github.com/lodev09/bootstrap-suggest/blob/master/LICENSE)
*/
(function($){"use strict";var Suggest=function(el,key,options){var that=this;this.$element=$(el);this.$items=undefined;this.options=$.extend(true,{},$.fn.suggest.defaults,options,this.$element.data(),this.$element.data('options'));this.key=key;this.isShown=false;this.query='';this._queryPos=[];this._keyPos=-1;this.$dropdown=$('<div />',{'class':'dropdown suggest '+this.options.dropdownClass,'html':$('<ul />',{'class':'dropdown-menu',role:'menu'}),'data-key':this.key});this.load();};Suggest.prototype={__setListener:function(){this.$element.on('suggest.show',$.proxy(this.options.onshow,this)).on('suggest.select',$.proxy(this.options.onselect,this)).on('suggest.lookup',$.proxy(this.options.onlookup,this)).on('keyup',$.proxy(this.__keyup,this));return this;},__getCaretPos:function(posStart){var properties=['direction','boxSizing','width','height','overflowX','overflowY','borderTopWidth','borderRightWidth','borderBottomWidth','borderLeftWidth','paddingTop','paddingRight','paddingBottom','paddingLeft','fontStyle','fontVariant','fontWeight','fontStretch','fontSize','fontSizeAdjust','lineHeight','fontFamily','textAlign','textTransform','textIndent','textDecoration','letterSpacing','wordSpacing'];var isFirefox=!(window.mozInnerScreenX==null);var getCaretCoordinatesFn=function(element,position,recalculate){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;style.whiteSpace='pre-wrap';if(element.nodeName!=='INPUT')
style.wordWrap='break-word';style.position='absolute';style.visibility='hidden';$.each(properties,function(index,value){style[value]=computed[value];});if(isFirefox){style.width=parseInt(computed.width)-2+'px';if(element.scrollHeight>parseInt(computed.height))
style.overflowY='scroll';}else{style.overflow='hidden';}
div.textContent=element.value.substring(0,position);if(element.nodeName==='INPUT')
div.textContent=div.textContent.replace(/\s/g,"\u00a0");var span=document.createElement('span');span.textContent=element.value.substring(position)||'.';div.appendChild(span);var coordinates={top:span.offsetTop+parseInt(computed['borderTopWidth']),left:span.offsetLeft+parseInt(computed['borderLeftWidth'])};document.body.removeChild(div);return coordinates;}
return getCaretCoordinatesFn(this.$element.get(0),posStart);},__keyup:function(e){var specialChars=[38,40,37,39,17,18,9,16,20,91,93,36,35,45,33,34,144,112,113,114,115,116,117,118,119,120,121,122,123,145,19];switch(e.keyCode){case 27:this.hide();return;case 13:return true;}
if($.inArray(e.keyCode,specialChars)!==-1)return;var $el=this.$element,val=$el.val(),currentPos=this.__getSelection($el.get(0)).start;for(var i=currentPos;i>=0;i--){var subChar=$.trim(val.substring(i-1,i));if(!subChar){this.hide();break;}
if(subChar===this.key&&$.trim(val.substring(i-2,i-1))==''){this.query=val.substring(i,currentPos);this._queryPos=[i,currentPos];this._keyPos=i;this.lookup(this.query);break;}}},__getVisibleItems:function(){return this.$items?this.$items.not('.d-none'):$();},__build:function(){var elems=[],$item,$dropdown=this.$dropdown,that=this;var blur=function(e){that.hide();}
$dropdown.on('click','li:has(a)',function(e){e.preventDefault();that.__select($(this).index());that.$element.focus();}).on('mouseover','li:has(a)',function(e){that.$element.off('blur',blur);}).on('mouseout','li:has(a)',function(e){that.$element.on('blur',blur);});this.$element.before($dropdown).on('blur',blur).on('keydown',function(e){var $visibleItems;if(that.isShown){switch(e.keyCode){case 13:$visibleItems=that.__getVisibleItems();$visibleItems.each(function(index){if($(this).is('.active'))
that.__select($(this).index());});return false;break;case 40:$visibleItems=that.__getVisibleItems();if($visibleItems.last().is('.active'))return false;$visibleItems.each(function(index){var $this=$(this),$next=$visibleItems.eq(index+1);if($this.is('.active')){if(!$next.is('.hidden')){$this.removeClass('active');$next.addClass('active');}
return false;}});return false;case 38:$visibleItems=that.__getVisibleItems();if($visibleItems.first().is('.active'))return false;$visibleItems.each(function(index){var $this=$(this),$prev=$visibleItems.eq(index-1);if($this.is('.active')){if(!$prev.is('.hidden')){$this.removeClass('active');$prev.addClass('active');}
return false;}})
return false;}}});},__mapItem:function(dataItem){var itemHtml,that=this,_item={text:'',value:''};if(this.options.map){dataItem=this.options.map(dataItem);if(!dataItem)return false;}
if(dataItem instanceof Object){_item.text=dataItem.text||'';_item.value=dataItem.value||'';}else{_item.text=dataItem;_item.value=dataItem;}
return $('<li />',{'data-value':_item.value}).html($('<a />',{href:'#',html:_item.text})).addClass('dropdown-item');},__select:function(index){var $el=this.$element,el=$el.get(0),val=$el.val(),item=this.get(index),setCaretPos=this._keyPos+item.value.length+1;$el.val(val.slice(0,this._keyPos)+item.value+this.options.endKey+val.slice(this.__getSelection(el).start));if(el.setSelectionRange){el.setSelectionRange(setCaretPos,setCaretPos);}else if(el.createTextRange){var range=el.createTextRange();range.collapse(true);range.moveEnd('character',setCaretPos);range.moveStart('character',setCaretPos);range.select();}
$el.trigger($.extend({type:'suggest.select'},this),item);this.hide();},__getSelection:function(el){el.focus();return{start:el.selectionStart,end:el.selectionEnd};},__buildItems:function(data){var $dropdownMenu=this.$dropdown.find('.dropdown-menu');$dropdownMenu.empty();if(data&&data instanceof Array){for(var i in data){var $item=this.__mapItem(data[i]);if($item){$dropdownMenu.append($item);}}}
return $dropdownMenu.find('li:has(a)');},__lookup:function(q,$resultItems){var active=$resultItems.eq(0).addClass('active');this.$element.trigger($.extend({type:'suggest.lookup'},this),[q,$resultItems]);if($resultItems&&$resultItems.length){this.show();}else{this.hide();}},__filterData:function(q,data){var options=this.options;this.$items.addClass('hidden');this.$items.filter(function(index){if(q==='')return index<options.filter.limit;var $this=$(this),value=$this.find('a:first').text();if(!options.filter.casesensitive){value=value.toLowerCase();q=q.toLowerCase();}
return value.indexOf(q)!=-1;}).slice(0,options.filter.limit).removeClass('hidden active');return this.__getVisibleItems();},get:function(index){if(!this.$items)return;var $item=this.$items.eq(index);return{text:$item.children('a:first').text(),value:$item.attr('data-value'),index:index,$element:$item};},lookup:function(q){var options=this.options,that=this,data;var provide=function(data){if(that._keyPos!==-1){if(!that.$items){that.$items=that.__buildItems(data);}
that.__lookup(q,that.__filterData(q,data));}};if(typeof this.options.data==='function'){this.$items=undefined;data=this.options.data(q,provide);}else{data=this.options.data;}
if(data&&typeof data.promise==='function'){data.done(provide);}else if(data){provide.call(this,data);}},load:function(){this.__setListener();this.__build();},hide:function(){this.$dropdown.removeClass('open show');this.isShown=false;if(this.$items){this.$items.removeClass('active');}
this._keyPos=-1;},show:function(){var $el=this.$element,$dropdownMenu=this.$dropdown.find('.dropdown-menu'),el=$el.get(0),options=this.options,caretPos,position={top:'auto',bottom:'auto',left:'auto',right:'auto'};if(!this.isShown){this.$dropdown.addClass('open show');if(options.position!==false){caretPos=this.__getCaretPos(this._keyPos);if(typeof options.position=='string'){switch(options.position){case'bottom':position.top=$el.outerHeight()-parseFloat($dropdownMenu.css('margin-top'));position.left=0;position.right=0;break;case'top':position.top=-($dropdownMenu.outerHeight(true)+parseFloat($dropdownMenu.css('margin-top')));position.left=0;position.right=0;break;case'caret':position.top=caretPos.top-el.scrollTop;position.left=caretPos.left-el.scrollLeft;break;}}else{position=$.extend(position,typeof options.position==='function'?options.position(el,caretPos):options.position);}
$dropdownMenu.css(position);}
this.isShown=true;$el.trigger($.extend({type:'suggest.show'},this));}}};var old=$.fn.suggest;$.fn.suggest=function(arg1){var arg2=arguments[1],arg3=arguments[2];var createSuggestions=function(el,suggestions){var newData={};$.each(suggestions,function(keyChar,options){var key=keyChar.toString().charAt(0);newData[key]=new Suggest(el,key,typeof options==='object'&&options);});return newData;};return this.each(function(){var that=this,$this=$(this),data=$this.data('suggest'),suggestions={};if(typeof arg1==='string'){if(arg1.length==1){if(arg2){if(typeof arg2==='string'){if(arg1 in data&&typeof data[arg1][arg2]!=='undefined'){return data[arg1][arg2].call(data[arg1],arg3);}else{console.error(arg1+' is not a suggest');}}else{suggestions[arg1]=$.isArray(arg2)||typeof arg2==='function'?{data:arg2}:arg2;if(data&&arg1 in data){data[arg1].options=$.extend({},data[arg1].options,suggestions[arg1]);}else{data=$.extend(data,createSuggestions(this,suggestions));}
$this.data('suggest',data);}}}else{console.error('you\'re not initializing suggest properly. arg1 should have length == 1');}}else{if(!data)$this.data('suggest',createSuggestions(this,arg1));else if(data){$.each(arg1,function(key,value){if(key in data===false){suggestions[key]=value;}else{data[key].options=$.extend({},data[key].options,value);}});$this.data('suggest',$.extend(data,createSuggestions(that,suggestions)))}}});};$.fn.suggest.defaults={data:[],map:undefined,filter:{casesensitive:false,limit:5},endKey:' ',dropdownClass:'',position:'caret',onshow:function(e){},onselect:function(e,item){},onlookup:function(e,item){}}
$.fn.suggest.Constructor=Suggest;$.fn.suggest.noConflict=function(){$.fn.suggest=old;return this;}}(jQuery));