Autocomplete migrated to jquery-ui autocomplete
No longer bundled with the library (as jquery-ui is bundled with the main software, including the autocomplete part). Rewrites had to be made to migrate, as several API things had changed when jquery-ui took over the library. Currently (like before, right?) this only autocompletes in the end of the current textarea. So you can't jump back in the middle of a text and autocomplete a user or group. This is a serious deficiency, though not a regression.
This commit is contained in:
parent
d480ed42d1
commit
8935a2f866
|
@ -1,53 +0,0 @@
|
||||||
(function(SN, $) {
|
|
||||||
|
|
||||||
var origInit = SN.Init.NoticeFormSetup;
|
|
||||||
SN.Init.NoticeFormSetup = function(form) {
|
|
||||||
origInit(form);
|
|
||||||
|
|
||||||
// Only attach to traditional-style forms
|
|
||||||
var textarea = form.find('.notice_data-text:first');
|
|
||||||
if (textarea.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullName(row) {
|
|
||||||
if (typeof row.fullname == "string" && row.fullname != '') {
|
|
||||||
return row.nickname + ' (' + row.fullname + ')';
|
|
||||||
} else {
|
|
||||||
return row.nickname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var apiUrl = $('#autocomplete-api').attr('data-url');
|
|
||||||
textarea.autocomplete(apiUrl, {
|
|
||||||
multiple: true,
|
|
||||||
multipleSeparator: " ",
|
|
||||||
minChars: 1,
|
|
||||||
formatItem: function(row, i, max){
|
|
||||||
row = eval("(" + row + ")");
|
|
||||||
// the display:inline is because our INSANE stylesheets
|
|
||||||
// override the standard display of all img tags for no
|
|
||||||
// good reason.
|
|
||||||
var div = $('<div><img style="display:inline; vertical-align: middle"> <span></span></div>')
|
|
||||||
.find('img').attr('src', row.avatar).end()
|
|
||||||
.find('span').text(fullName(row)).end()
|
|
||||||
return div.html();
|
|
||||||
},
|
|
||||||
formatMatch: function(row, i, max){
|
|
||||||
row = eval("(" + row + ")");
|
|
||||||
return row.nickname;
|
|
||||||
},
|
|
||||||
formatResult: function(row){
|
|
||||||
row = eval("(" + row + ")");
|
|
||||||
switch(row.type)
|
|
||||||
{
|
|
||||||
case 'user':
|
|
||||||
return '@' + row.nickname;
|
|
||||||
case 'group':
|
|
||||||
return '!' + row.nickname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
})(SN, jQuery);
|
|
|
@ -43,15 +43,7 @@ class AutocompletePlugin extends Plugin
|
||||||
if (common_logged_in()) {
|
if (common_logged_in()) {
|
||||||
$action->element('span', array('id' => 'autocomplete-api',
|
$action->element('span', array('id' => 'autocomplete-api',
|
||||||
'data-url' => common_local_url('autocomplete')));
|
'data-url' => common_local_url('autocomplete')));
|
||||||
$action->script($this->path('jquery-autocomplete/jquery.autocomplete.pack.js'));
|
$action->script($this->path('js/autocomplete.go.js'));
|
||||||
$action->script($this->path('Autocomplete.js'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEndShowStatusNetStyles($action)
|
|
||||||
{
|
|
||||||
if (common_logged_in()) {
|
|
||||||
$action->cssLink($this->path('jquery-autocomplete/jquery.autocomplete.css'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,9 @@
|
||||||
* @category Plugin
|
* @category Plugin
|
||||||
* @package StatusNet
|
* @package StatusNet
|
||||||
* @author Craig Andrews <candrews@integralblue.com>
|
* @author Craig Andrews <candrews@integralblue.com>
|
||||||
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
* @copyright 2008-2009 StatusNet, Inc.
|
* @copyright 2008-2009 StatusNet, Inc.
|
||||||
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
|
* @copyright 2009-2013 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||||
* @link http://status.net/
|
* @link http://status.net/
|
||||||
*/
|
*/
|
||||||
|
@ -40,11 +41,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||||
* @category Plugin
|
* @category Plugin
|
||||||
* @package StatusNet
|
* @package StatusNet
|
||||||
* @author Craig Andrews <candrews@integralblue.com>
|
* @author Craig Andrews <candrews@integralblue.com>
|
||||||
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||||
* @link http://status.net/
|
* @link http://status.net/
|
||||||
*/
|
*/
|
||||||
class AutocompleteAction extends Action
|
class AutocompleteAction extends Action
|
||||||
{
|
{
|
||||||
|
protected $needLogin = true;
|
||||||
|
|
||||||
private $result;
|
private $result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,12 +84,12 @@ class AutocompleteAction extends Action
|
||||||
{
|
{
|
||||||
return '"' . implode(':', array($this->arg('action'),
|
return '"' . implode(':', array($this->arg('action'),
|
||||||
common_user_cache_hash(),
|
common_user_cache_hash(),
|
||||||
crc32($this->arg('q')), //the actual string can have funny characters in we don't want showing up in the etag
|
crc32($this->arg('term')), //the actual string can have funny characters in we don't want showing up in the etag
|
||||||
$this->arg('limit'),
|
$this->arg('limit'),
|
||||||
$this->lastModified())) . '"';
|
$this->lastModified())) . '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepare($args)
|
protected function prepare($args)
|
||||||
{
|
{
|
||||||
// If we die, show short error messages.
|
// If we die, show short error messages.
|
||||||
StatusNet::setApi(true);
|
StatusNet::setApi(true);
|
||||||
|
@ -97,29 +101,30 @@ class AutocompleteAction extends Action
|
||||||
// TRANS: Client exception in autocomplete plugin.
|
// TRANS: Client exception in autocomplete plugin.
|
||||||
throw new ClientException(_m('Access forbidden.'), true);
|
throw new ClientException(_m('Access forbidden.'), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->groups=array();
|
$this->groups=array();
|
||||||
$this->users=array();
|
$this->users=array();
|
||||||
$q = $this->arg('q');
|
$term = $this->arg('term');
|
||||||
$limit = $this->arg('limit');
|
$limit = $this->arg('limit');
|
||||||
if($limit > 200) $limit=200; //prevent DOS attacks
|
if($limit > 200) $limit=200; //prevent DOS attacks
|
||||||
if(substr($q,0,1)=='@'){
|
if(substr($term,0,1)=='@'){
|
||||||
//user search
|
//user search
|
||||||
$q=substr($q,1);
|
$term=substr($term,1);
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->limit($limit);
|
$user->limit($limit);
|
||||||
$user->whereAdd('nickname like \'' . trim($user->escape($q), '\'') . '%\'');
|
$user->whereAdd('nickname like \'' . trim($user->escape($term), '\'') . '%\'');
|
||||||
if($user->find()){
|
if($user->find()){
|
||||||
while($user->fetch()) {
|
while($user->fetch()) {
|
||||||
$this->users[]=clone($user);
|
$this->users[]=clone($user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(substr($q,0,1)=='!'){
|
if(substr($term,0,1)=='!'){
|
||||||
//group search
|
//group search
|
||||||
$q=substr($q,1);
|
$term=substr($term,1);
|
||||||
$group = new User_group();
|
$group = new User_group();
|
||||||
$group->limit($limit);
|
$group->limit($limit);
|
||||||
$group->whereAdd('nickname like \'' . trim($group->escape($q), '\'') . '%\'');
|
$group->whereAdd('nickname like \'' . trim($group->escape($term), '\'') . '%\'');
|
||||||
if($group->find()){
|
if($group->find()){
|
||||||
while($group->fetch()) {
|
while($group->fetch()) {
|
||||||
$this->groups[]=clone($group);
|
$this->groups[]=clone($group);
|
||||||
|
@ -129,9 +134,10 @@ class AutocompleteAction extends Action
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle($args)
|
protected function handle()
|
||||||
{
|
{
|
||||||
parent::handle($args);
|
parent::handle();
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
foreach($this->users as $user){
|
foreach($this->users as $user){
|
||||||
$profile = $user->getProfile();
|
$profile = $user->getProfile();
|
||||||
|
@ -143,8 +149,9 @@ class AutocompleteAction extends Action
|
||||||
$avatar = Avatar::defaultImage(AVATAR_MINI_SIZE);
|
$avatar = Avatar::defaultImage(AVATAR_MINI_SIZE);
|
||||||
}
|
}
|
||||||
$results[] = array(
|
$results[] = array(
|
||||||
'nickname' => $user->nickname,
|
'value' => '@'.$profile->nickname,
|
||||||
'fullname'=> $profile->fullname,
|
'nickname' => $profile->nickname,
|
||||||
|
'label'=> $profile->getFancyName(),
|
||||||
'avatar' => $avatar,
|
'avatar' => $avatar,
|
||||||
'type' => 'user'
|
'type' => 'user'
|
||||||
);
|
);
|
||||||
|
@ -157,14 +164,13 @@ class AutocompleteAction extends Action
|
||||||
$avatar = User_group::defaultLogo(AVATAR_MINI_SIZE);
|
$avatar = User_group::defaultLogo(AVATAR_MINI_SIZE);
|
||||||
}
|
}
|
||||||
$results[] = array(
|
$results[] = array(
|
||||||
|
'value' => '!'.$group->nickname,
|
||||||
'nickname' => $group->nickname,
|
'nickname' => $group->nickname,
|
||||||
'fullname'=> $group->fullname,
|
'label'=> $group->getFancyName(),
|
||||||
'avatar' => $avatar,
|
'avatar' => $avatar,
|
||||||
'type' => 'group');
|
'type' => 'group');
|
||||||
}
|
}
|
||||||
foreach($results as $result) {
|
print json_encode($results);
|
||||||
print json_encode($result) . "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
1.0.2
|
|
||||||
-----
|
|
||||||
* Fixed missing semicolon
|
|
||||||
|
|
||||||
1.0.1
|
|
||||||
-----
|
|
||||||
* Fixed element creation (<ul> to <ul/> and <li> to </li>)
|
|
||||||
* Fixed ac_even class (was ac_event)
|
|
||||||
* Fixed bgiframe usage: now its really optional
|
|
||||||
* Removed the blur-on-return workaround, added a less obtrusive one only for Opera
|
|
||||||
* Fixed hold cursor keys: Opera needs keypress, everyone else keydown to scroll through result list when holding cursor key
|
|
||||||
* Updated package to jQuery 1.2.5, removing dimensions
|
|
||||||
* Fixed multiple-mustMatch: Remove only the last term when no match is found
|
|
||||||
* Fixed multiple without mustMatch: Don't select the last active when no match is found (on tab/return)
|
|
||||||
* Fixed multiple cursor position: Put cursor at end of input after selecting a value
|
|
||||||
|
|
||||||
1.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* First release.
|
|
Binary file not shown.
Before Width: | Height: | Size: 673 B |
|
@ -1,48 +0,0 @@
|
||||||
.ac_results {
|
|
||||||
padding: 0px;
|
|
||||||
border: 1px solid black;
|
|
||||||
background-color: white;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 99999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ac_results ul {
|
|
||||||
width: 100%;
|
|
||||||
list-style-position: outside;
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ac_results li {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
cursor: default;
|
|
||||||
display: block;
|
|
||||||
/*
|
|
||||||
if width will be 100% horizontal scrollbar will apear
|
|
||||||
when scroll mode will be used
|
|
||||||
*/
|
|
||||||
/*width: 100%;*/
|
|
||||||
font: menu;
|
|
||||||
font-size: 12px;
|
|
||||||
/*
|
|
||||||
it is very important, if line-height not setted or setted
|
|
||||||
in relative units scroll will be broken in firefox
|
|
||||||
*/
|
|
||||||
line-height: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ac_loading {
|
|
||||||
background: white url('indicator.gif') right center no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ac_odd {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ac_over {
|
|
||||||
background-color: #0A246A;
|
|
||||||
color: white;
|
|
||||||
}
|
|
|
@ -1,759 +0,0 @@
|
||||||
/*
|
|
||||||
* Autocomplete - jQuery plugin 1.0.2
|
|
||||||
*
|
|
||||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
|
||||||
*
|
|
||||||
* Dual licensed under the MIT and GPL licenses:
|
|
||||||
* http://www.opensource.org/licenses/mit-license.php
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*
|
|
||||||
* Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
;(function($) {
|
|
||||||
|
|
||||||
$.fn.extend({
|
|
||||||
autocomplete: function(urlOrData, options) {
|
|
||||||
var isUrl = typeof urlOrData == "string";
|
|
||||||
options = $.extend({}, $.Autocompleter.defaults, {
|
|
||||||
url: isUrl ? urlOrData : null,
|
|
||||||
data: isUrl ? null : urlOrData,
|
|
||||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
|
|
||||||
max: options && !options.scroll ? 10 : 150
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
// if highlight is set to false, replace it with a do-nothing function
|
|
||||||
options.highlight = options.highlight || function(value) { return value; };
|
|
||||||
|
|
||||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
|
||||||
options.formatMatch = options.formatMatch || options.formatItem;
|
|
||||||
|
|
||||||
return this.each(function() {
|
|
||||||
new $.Autocompleter(this, options);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
result: function(handler) {
|
|
||||||
return this.bind("result", handler);
|
|
||||||
},
|
|
||||||
search: function(handler) {
|
|
||||||
return this.trigger("search", [handler]);
|
|
||||||
},
|
|
||||||
flushCache: function() {
|
|
||||||
return this.trigger("flushCache");
|
|
||||||
},
|
|
||||||
setOptions: function(options){
|
|
||||||
return this.trigger("setOptions", [options]);
|
|
||||||
},
|
|
||||||
unautocomplete: function() {
|
|
||||||
return this.trigger("unautocomplete");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$.Autocompleter = function(input, options) {
|
|
||||||
|
|
||||||
var KEY = {
|
|
||||||
UP: 38,
|
|
||||||
DOWN: 40,
|
|
||||||
DEL: 46,
|
|
||||||
TAB: 9,
|
|
||||||
RETURN: 13,
|
|
||||||
ESC: 27,
|
|
||||||
COMMA: 188,
|
|
||||||
PAGEUP: 33,
|
|
||||||
PAGEDOWN: 34,
|
|
||||||
BACKSPACE: 8
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create $ object for input element
|
|
||||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
|
|
||||||
|
|
||||||
var timeout;
|
|
||||||
var previousValue = "";
|
|
||||||
var cache = $.Autocompleter.Cache(options);
|
|
||||||
var hasFocus = 0;
|
|
||||||
var lastKeyPressCode;
|
|
||||||
var config = {
|
|
||||||
mouseDownOnSelect: false
|
|
||||||
};
|
|
||||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
|
|
||||||
|
|
||||||
var blockSubmit;
|
|
||||||
|
|
||||||
// prevent form submit in opera when selecting with return key
|
|
||||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
|
|
||||||
if (blockSubmit) {
|
|
||||||
blockSubmit = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
|
||||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
|
||||||
// track last key pressed
|
|
||||||
lastKeyPressCode = event.keyCode;
|
|
||||||
switch(event.keyCode) {
|
|
||||||
|
|
||||||
case KEY.UP:
|
|
||||||
event.preventDefault();
|
|
||||||
if ( select.visible() ) {
|
|
||||||
select.prev();
|
|
||||||
} else {
|
|
||||||
onChange(0, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.DOWN:
|
|
||||||
event.preventDefault();
|
|
||||||
if ( select.visible() ) {
|
|
||||||
select.next();
|
|
||||||
} else {
|
|
||||||
onChange(0, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.PAGEUP:
|
|
||||||
event.preventDefault();
|
|
||||||
if ( select.visible() ) {
|
|
||||||
select.pageUp();
|
|
||||||
} else {
|
|
||||||
onChange(0, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.PAGEDOWN:
|
|
||||||
event.preventDefault();
|
|
||||||
if ( select.visible() ) {
|
|
||||||
select.pageDown();
|
|
||||||
} else {
|
|
||||||
onChange(0, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// matches also semicolon
|
|
||||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
|
|
||||||
case KEY.TAB:
|
|
||||||
case KEY.RETURN:
|
|
||||||
if( selectCurrent() ) {
|
|
||||||
// stop default to prevent a form submit, Opera needs special handling
|
|
||||||
event.preventDefault();
|
|
||||||
blockSubmit = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KEY.ESC:
|
|
||||||
select.hide();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(onChange, options.delay);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}).focus(function(){
|
|
||||||
// track whether the field has focus, we shouldn't process any
|
|
||||||
// results if the field no longer has focus
|
|
||||||
hasFocus++;
|
|
||||||
}).blur(function() {
|
|
||||||
hasFocus = 0;
|
|
||||||
if (!config.mouseDownOnSelect) {
|
|
||||||
hideResults();
|
|
||||||
}
|
|
||||||
}).click(function() {
|
|
||||||
// show select when clicking in a focused field
|
|
||||||
if ( hasFocus++ > 1 && !select.visible() ) {
|
|
||||||
onChange(0, true);
|
|
||||||
}
|
|
||||||
}).bind("search", function() {
|
|
||||||
// TODO why not just specifying both arguments?
|
|
||||||
var fn = (arguments.length > 1) ? arguments[1] : null;
|
|
||||||
function findValueCallback(q, data) {
|
|
||||||
var result;
|
|
||||||
if( data && data.length ) {
|
|
||||||
for (var i=0; i < data.length; i++) {
|
|
||||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
|
|
||||||
result = data[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( typeof fn == "function" ) fn(result);
|
|
||||||
else $input.trigger("result", result && [result.data, result.value]);
|
|
||||||
}
|
|
||||||
$.each(trimWords($input.val()), function(i, value) {
|
|
||||||
request(value, findValueCallback, findValueCallback);
|
|
||||||
});
|
|
||||||
}).bind("flushCache", function() {
|
|
||||||
cache.flush();
|
|
||||||
}).bind("setOptions", function() {
|
|
||||||
$.extend(options, arguments[1]);
|
|
||||||
// if we've updated the data, repopulate
|
|
||||||
if ( "data" in arguments[1] )
|
|
||||||
cache.populate();
|
|
||||||
}).bind("unautocomplete", function() {
|
|
||||||
select.unbind();
|
|
||||||
$input.unbind();
|
|
||||||
$(input.form).unbind(".autocomplete");
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function selectCurrent() {
|
|
||||||
var selected = select.selected();
|
|
||||||
if( !selected )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var v = selected.result;
|
|
||||||
previousValue = v;
|
|
||||||
|
|
||||||
if ( options.multiple ) {
|
|
||||||
var words = trimWords($input.val());
|
|
||||||
if ( words.length > 1 ) {
|
|
||||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
|
|
||||||
}
|
|
||||||
v += options.multipleSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
$input.val(v);
|
|
||||||
hideResultsNow();
|
|
||||||
$input.trigger("result", [selected.data, selected.value]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(crap, skipPrevCheck) {
|
|
||||||
if( lastKeyPressCode == KEY.DEL ) {
|
|
||||||
select.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentValue = $input.val();
|
|
||||||
|
|
||||||
if ( !skipPrevCheck && currentValue == previousValue )
|
|
||||||
return;
|
|
||||||
|
|
||||||
previousValue = currentValue;
|
|
||||||
|
|
||||||
currentValue = lastWord(currentValue);
|
|
||||||
if ( currentValue.length >= options.minChars) {
|
|
||||||
$input.addClass(options.loadingClass);
|
|
||||||
if (!options.matchCase)
|
|
||||||
currentValue = currentValue.toLowerCase();
|
|
||||||
request(currentValue, receiveData, hideResultsNow);
|
|
||||||
} else {
|
|
||||||
stopLoading();
|
|
||||||
select.hide();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function trimWords(value) {
|
|
||||||
if ( !value ) {
|
|
||||||
return [""];
|
|
||||||
}
|
|
||||||
var words = value.split( options.multipleSeparator );
|
|
||||||
var result = [];
|
|
||||||
$.each(words, function(i, value) {
|
|
||||||
if ( $.trim(value) )
|
|
||||||
result[i] = $.trim(value);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastWord(value) {
|
|
||||||
if ( !options.multiple )
|
|
||||||
return value;
|
|
||||||
var words = trimWords(value);
|
|
||||||
return words[words.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// fills in the input box w/the first match (assumed to be the best match)
|
|
||||||
// q: the term entered
|
|
||||||
// sValue: the first matching result
|
|
||||||
function autoFill(q, sValue){
|
|
||||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
|
||||||
// if the last user key pressed was backspace, don't autofill
|
|
||||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
|
|
||||||
// fill in the value (keep the case the user has typed)
|
|
||||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
|
||||||
// select the portion of the value not typed by the user (so the next character will erase)
|
|
||||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function hideResults() {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(hideResultsNow, 200);
|
|
||||||
};
|
|
||||||
|
|
||||||
function hideResultsNow() {
|
|
||||||
var wasVisible = select.visible();
|
|
||||||
select.hide();
|
|
||||||
clearTimeout(timeout);
|
|
||||||
stopLoading();
|
|
||||||
if (options.mustMatch) {
|
|
||||||
// call search and run callback
|
|
||||||
$input.search(
|
|
||||||
function (result){
|
|
||||||
// if no value found, clear the input box
|
|
||||||
if( !result ) {
|
|
||||||
if (options.multiple) {
|
|
||||||
var words = trimWords($input.val()).slice(0, -1);
|
|
||||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
$input.val( "" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (wasVisible)
|
|
||||||
// position cursor at end of input field
|
|
||||||
$.Autocompleter.Selection(input, input.value.length, input.value.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
function receiveData(q, data) {
|
|
||||||
if ( data && data.length && hasFocus ) {
|
|
||||||
stopLoading();
|
|
||||||
select.display(data, q);
|
|
||||||
autoFill(q, data[0].value);
|
|
||||||
select.show();
|
|
||||||
} else {
|
|
||||||
hideResultsNow();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function request(term, success, failure) {
|
|
||||||
if (!options.matchCase)
|
|
||||||
term = term.toLowerCase();
|
|
||||||
var data = cache.load(term);
|
|
||||||
// recieve the cached data
|
|
||||||
if (data && data.length) {
|
|
||||||
success(term, data);
|
|
||||||
// if an AJAX url has been supplied, try loading the data now
|
|
||||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
|
|
||||||
|
|
||||||
var extraParams = {
|
|
||||||
timestamp: +new Date()
|
|
||||||
};
|
|
||||||
$.each(options.extraParams, function(key, param) {
|
|
||||||
extraParams[key] = typeof param == "function" ? param() : param;
|
|
||||||
});
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
// try to leverage ajaxQueue plugin to abort previous requests
|
|
||||||
mode: "abort",
|
|
||||||
// limit abortion to this input
|
|
||||||
port: "autocomplete" + input.name,
|
|
||||||
dataType: options.dataType,
|
|
||||||
url: options.url,
|
|
||||||
data: $.extend({
|
|
||||||
q: lastWord(term),
|
|
||||||
limit: options.max
|
|
||||||
}, extraParams),
|
|
||||||
success: function(data) {
|
|
||||||
var parsed = options.parse && options.parse(data) || parse(data);
|
|
||||||
cache.add(term, parsed);
|
|
||||||
success(term, parsed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
|
||||||
select.emptyList();
|
|
||||||
failure(term);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function parse(data) {
|
|
||||||
var parsed = [];
|
|
||||||
var rows = data.split("\n");
|
|
||||||
for (var i=0; i < rows.length; i++) {
|
|
||||||
var row = $.trim(rows[i]);
|
|
||||||
if (row) {
|
|
||||||
row = row.split("|");
|
|
||||||
parsed[parsed.length] = {
|
|
||||||
data: row,
|
|
||||||
value: row[0],
|
|
||||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
};
|
|
||||||
|
|
||||||
function stopLoading() {
|
|
||||||
$input.removeClass(options.loadingClass);
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
$.Autocompleter.defaults = {
|
|
||||||
inputClass: "ac_input",
|
|
||||||
resultsClass: "ac_results",
|
|
||||||
loadingClass: "ac_loading",
|
|
||||||
minChars: 1,
|
|
||||||
delay: 400,
|
|
||||||
matchCase: false,
|
|
||||||
matchSubset: true,
|
|
||||||
matchContains: false,
|
|
||||||
cacheLength: 10,
|
|
||||||
max: 100,
|
|
||||||
mustMatch: false,
|
|
||||||
extraParams: {},
|
|
||||||
selectFirst: true,
|
|
||||||
formatItem: function(row) { return row[0]; },
|
|
||||||
formatMatch: null,
|
|
||||||
autoFill: false,
|
|
||||||
width: 0,
|
|
||||||
multiple: false,
|
|
||||||
multipleSeparator: ", ",
|
|
||||||
highlight: function(value, term) {
|
|
||||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
|
|
||||||
},
|
|
||||||
scroll: true,
|
|
||||||
scrollHeight: 180
|
|
||||||
};
|
|
||||||
|
|
||||||
$.Autocompleter.Cache = function(options) {
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
var length = 0;
|
|
||||||
|
|
||||||
function matchSubset(s, sub) {
|
|
||||||
if (!options.matchCase)
|
|
||||||
s = s.toLowerCase();
|
|
||||||
var i = s.indexOf(sub);
|
|
||||||
if (i == -1) return false;
|
|
||||||
return i == 0 || options.matchContains;
|
|
||||||
};
|
|
||||||
|
|
||||||
function add(q, value) {
|
|
||||||
if (length > options.cacheLength){
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
if (!data[q]){
|
|
||||||
length++;
|
|
||||||
}
|
|
||||||
data[q] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function populate(){
|
|
||||||
if( !options.data ) return false;
|
|
||||||
// track the matches
|
|
||||||
var stMatchSets = {},
|
|
||||||
nullData = 0;
|
|
||||||
|
|
||||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
|
||||||
if( !options.url ) options.cacheLength = 1;
|
|
||||||
|
|
||||||
// track all options for minChars = 0
|
|
||||||
stMatchSets[""] = [];
|
|
||||||
|
|
||||||
// loop through the array and create a lookup structure
|
|
||||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
|
|
||||||
var rawValue = options.data[i];
|
|
||||||
// if rawValue is a string, make an array otherwise just reference the array
|
|
||||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
|
|
||||||
|
|
||||||
var value = options.formatMatch(rawValue, i+1, options.data.length);
|
|
||||||
if ( value === false )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var firstChar = value.charAt(0).toLowerCase();
|
|
||||||
// if no lookup array for this character exists, look it up now
|
|
||||||
if( !stMatchSets[firstChar] )
|
|
||||||
stMatchSets[firstChar] = [];
|
|
||||||
|
|
||||||
// if the match is a string
|
|
||||||
var row = {
|
|
||||||
value: value,
|
|
||||||
data: rawValue,
|
|
||||||
result: options.formatResult && options.formatResult(rawValue) || value
|
|
||||||
};
|
|
||||||
|
|
||||||
// push the current match into the set list
|
|
||||||
stMatchSets[firstChar].push(row);
|
|
||||||
|
|
||||||
// keep track of minChars zero items
|
|
||||||
if ( nullData++ < options.max ) {
|
|
||||||
stMatchSets[""].push(row);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// add the data items to the cache
|
|
||||||
$.each(stMatchSets, function(i, value) {
|
|
||||||
// increase the cache size
|
|
||||||
options.cacheLength++;
|
|
||||||
// add to the cache
|
|
||||||
add(i, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate any existing data
|
|
||||||
setTimeout(populate, 25);
|
|
||||||
|
|
||||||
function flush(){
|
|
||||||
data = {};
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
flush: flush,
|
|
||||||
add: add,
|
|
||||||
populate: populate,
|
|
||||||
load: function(q) {
|
|
||||||
if (!options.cacheLength || !length)
|
|
||||||
return null;
|
|
||||||
/*
|
|
||||||
* if dealing w/local data and matchContains than we must make sure
|
|
||||||
* to loop through all the data collections looking for matches
|
|
||||||
*/
|
|
||||||
if( !options.url && options.matchContains ){
|
|
||||||
// track all matches
|
|
||||||
var csub = [];
|
|
||||||
// loop through all the data grids for matches
|
|
||||||
for( var k in data ){
|
|
||||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
|
||||||
// this prevents duplicates
|
|
||||||
if( k.length > 0 ){
|
|
||||||
var c = data[k];
|
|
||||||
$.each(c, function(i, x) {
|
|
||||||
// if we've got a match, add it to the array
|
|
||||||
if (matchSubset(x.value, q)) {
|
|
||||||
csub.push(x);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return csub;
|
|
||||||
} else
|
|
||||||
// if the exact item exists, use it
|
|
||||||
if (data[q]){
|
|
||||||
return data[q];
|
|
||||||
} else
|
|
||||||
if (options.matchSubset) {
|
|
||||||
for (var i = q.length - 1; i >= options.minChars; i--) {
|
|
||||||
var c = data[q.substr(0, i)];
|
|
||||||
if (c) {
|
|
||||||
var csub = [];
|
|
||||||
$.each(c, function(i, x) {
|
|
||||||
if (matchSubset(x.value, q)) {
|
|
||||||
csub[csub.length] = x;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return csub;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$.Autocompleter.Select = function (options, input, select, config) {
|
|
||||||
var CLASSES = {
|
|
||||||
ACTIVE: "ac_over"
|
|
||||||
};
|
|
||||||
|
|
||||||
var listItems,
|
|
||||||
active = -1,
|
|
||||||
data,
|
|
||||||
term = "",
|
|
||||||
needsInit = true,
|
|
||||||
element,
|
|
||||||
list;
|
|
||||||
|
|
||||||
// Create results
|
|
||||||
function init() {
|
|
||||||
if (!needsInit)
|
|
||||||
return;
|
|
||||||
element = $("<div/>")
|
|
||||||
.hide()
|
|
||||||
.addClass(options.resultsClass)
|
|
||||||
.css("position", "absolute")
|
|
||||||
.appendTo(document.body);
|
|
||||||
|
|
||||||
list = $("<ul/>").appendTo(element).mouseover( function(event) {
|
|
||||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
|
|
||||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
|
|
||||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
|
||||||
}
|
|
||||||
}).click(function(event) {
|
|
||||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
|
||||||
select();
|
|
||||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
|
||||||
input.focus();
|
|
||||||
return false;
|
|
||||||
}).mousedown(function() {
|
|
||||||
config.mouseDownOnSelect = true;
|
|
||||||
}).mouseup(function() {
|
|
||||||
config.mouseDownOnSelect = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if( options.width > 0 )
|
|
||||||
element.css("width", options.width);
|
|
||||||
|
|
||||||
needsInit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function target(event) {
|
|
||||||
var element = event.target;
|
|
||||||
while(element && element.tagName != "LI")
|
|
||||||
element = element.parentNode;
|
|
||||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
|
||||||
if(!element)
|
|
||||||
return [];
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveSelect(step) {
|
|
||||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
|
|
||||||
movePosition(step);
|
|
||||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
|
|
||||||
if(options.scroll) {
|
|
||||||
var offset = 0;
|
|
||||||
listItems.slice(0, active).each(function() {
|
|
||||||
offset += this.offsetHeight;
|
|
||||||
});
|
|
||||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
|
|
||||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
|
|
||||||
} else if(offset < list.scrollTop()) {
|
|
||||||
list.scrollTop(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function movePosition(step) {
|
|
||||||
active += step;
|
|
||||||
if (active < 0) {
|
|
||||||
active = listItems.size() - 1;
|
|
||||||
} else if (active >= listItems.size()) {
|
|
||||||
active = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function limitNumberOfItems(available) {
|
|
||||||
return options.max && options.max < available
|
|
||||||
? options.max
|
|
||||||
: available;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fillList() {
|
|
||||||
list.empty();
|
|
||||||
var max = limitNumberOfItems(data.length);
|
|
||||||
for (var i=0; i < max; i++) {
|
|
||||||
if (!data[i])
|
|
||||||
continue;
|
|
||||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
|
|
||||||
if ( formatted === false )
|
|
||||||
continue;
|
|
||||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
|
|
||||||
$.data(li, "ac_data", data[i]);
|
|
||||||
}
|
|
||||||
listItems = list.find("li");
|
|
||||||
if ( options.selectFirst ) {
|
|
||||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
|
|
||||||
active = 0;
|
|
||||||
}
|
|
||||||
// apply bgiframe if available
|
|
||||||
if ( $.fn.bgiframe )
|
|
||||||
list.bgiframe();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
display: function(d, q) {
|
|
||||||
init();
|
|
||||||
data = d;
|
|
||||||
term = q;
|
|
||||||
fillList();
|
|
||||||
},
|
|
||||||
next: function() {
|
|
||||||
moveSelect(1);
|
|
||||||
},
|
|
||||||
prev: function() {
|
|
||||||
moveSelect(-1);
|
|
||||||
},
|
|
||||||
pageUp: function() {
|
|
||||||
if (active != 0 && active - 8 < 0) {
|
|
||||||
moveSelect( -active );
|
|
||||||
} else {
|
|
||||||
moveSelect(-8);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pageDown: function() {
|
|
||||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
|
|
||||||
moveSelect( listItems.size() - 1 - active );
|
|
||||||
} else {
|
|
||||||
moveSelect(8);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hide: function() {
|
|
||||||
element && element.hide();
|
|
||||||
listItems && listItems.removeClass(CLASSES.ACTIVE);
|
|
||||||
active = -1;
|
|
||||||
},
|
|
||||||
visible : function() {
|
|
||||||
return element && element.is(":visible");
|
|
||||||
},
|
|
||||||
current: function() {
|
|
||||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
|
|
||||||
},
|
|
||||||
show: function() {
|
|
||||||
var offset = $(input).offset();
|
|
||||||
element.css({
|
|
||||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
|
|
||||||
top: offset.top + input.offsetHeight,
|
|
||||||
left: offset.left
|
|
||||||
}).show();
|
|
||||||
if(options.scroll) {
|
|
||||||
list.scrollTop(0);
|
|
||||||
list.css({
|
|
||||||
maxHeight: options.scrollHeight,
|
|
||||||
overflow: 'auto'
|
|
||||||
});
|
|
||||||
|
|
||||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
|
|
||||||
var listHeight = 0;
|
|
||||||
listItems.each(function() {
|
|
||||||
listHeight += this.offsetHeight;
|
|
||||||
});
|
|
||||||
var scrollbarsVisible = listHeight > options.scrollHeight;
|
|
||||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
|
|
||||||
if (!scrollbarsVisible) {
|
|
||||||
// IE doesn't recalculate width when scrollbar disappears
|
|
||||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selected: function() {
|
|
||||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
|
|
||||||
return selected && selected.length && $.data(selected[0], "ac_data");
|
|
||||||
},
|
|
||||||
emptyList: function (){
|
|
||||||
list && list.empty();
|
|
||||||
},
|
|
||||||
unbind: function() {
|
|
||||||
element && element.remove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
$.Autocompleter.Selection = function(field, start, end) {
|
|
||||||
if( field.createTextRange ){
|
|
||||||
var selRange = field.createTextRange();
|
|
||||||
selRange.collapse(true);
|
|
||||||
selRange.moveStart("character", start);
|
|
||||||
selRange.moveEnd("character", end);
|
|
||||||
selRange.select();
|
|
||||||
} else if( field.setSelectionRange ){
|
|
||||||
field.setSelectionRange(start, end);
|
|
||||||
} else {
|
|
||||||
if( field.selectionStart ){
|
|
||||||
field.selectionStart = start;
|
|
||||||
field.selectionEnd = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,116 +0,0 @@
|
||||||
/**
|
|
||||||
* Ajax Queue Plugin
|
|
||||||
*
|
|
||||||
* Homepage: http://jquery.com/plugins/project/ajaxqueue
|
|
||||||
* Documentation: http://docs.jquery.com/AjaxQueue
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$(function(){
|
|
||||||
jQuery.ajaxQueue({
|
|
||||||
url: "test.php",
|
|
||||||
success: function(html){ jQuery("ul").append(html); }
|
|
||||||
});
|
|
||||||
jQuery.ajaxQueue({
|
|
||||||
url: "test.php",
|
|
||||||
success: function(html){ jQuery("ul").append(html); }
|
|
||||||
});
|
|
||||||
jQuery.ajaxSync({
|
|
||||||
url: "test.php",
|
|
||||||
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
|
|
||||||
});
|
|
||||||
jQuery.ajaxSync({
|
|
||||||
url: "test.php",
|
|
||||||
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<ul style="position: absolute; top: 5px; right: 5px;"></ul>
|
|
||||||
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* Queued Ajax requests.
|
|
||||||
* A new Ajax request won't be started until the previous queued
|
|
||||||
* request has finished.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Synced Ajax requests.
|
|
||||||
* The Ajax request will happen as soon as you call this method, but
|
|
||||||
* the callbacks (success/error/complete) won't fire until all previous
|
|
||||||
* synced requests have been completed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
|
|
||||||
var ajax = $.ajax;
|
|
||||||
|
|
||||||
var pendingRequests = {};
|
|
||||||
|
|
||||||
var synced = [];
|
|
||||||
var syncedData = [];
|
|
||||||
|
|
||||||
$.ajax = function(settings) {
|
|
||||||
// create settings for compatibility with ajaxSetup
|
|
||||||
settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
|
|
||||||
|
|
||||||
var port = settings.port;
|
|
||||||
|
|
||||||
switch(settings.mode) {
|
|
||||||
case "abort":
|
|
||||||
if ( pendingRequests[port] ) {
|
|
||||||
pendingRequests[port].abort();
|
|
||||||
}
|
|
||||||
return pendingRequests[port] = ajax.apply(this, arguments);
|
|
||||||
case "queue":
|
|
||||||
var _old = settings.complete;
|
|
||||||
settings.complete = function(){
|
|
||||||
if ( _old )
|
|
||||||
_old.apply( this, arguments );
|
|
||||||
jQuery([ajax]).dequeue("ajax" + port );;
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery([ ajax ]).queue("ajax" + port, function(){
|
|
||||||
ajax( settings );
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
case "sync":
|
|
||||||
var pos = synced.length;
|
|
||||||
|
|
||||||
synced[ pos ] = {
|
|
||||||
error: settings.error,
|
|
||||||
success: settings.success,
|
|
||||||
complete: settings.complete,
|
|
||||||
done: false
|
|
||||||
};
|
|
||||||
|
|
||||||
syncedData[ pos ] = {
|
|
||||||
error: [],
|
|
||||||
success: [],
|
|
||||||
complete: []
|
|
||||||
};
|
|
||||||
|
|
||||||
settings.error = function(){ syncedData[ pos ].error = arguments; };
|
|
||||||
settings.success = function(){ syncedData[ pos ].success = arguments; };
|
|
||||||
settings.complete = function(){
|
|
||||||
syncedData[ pos ].complete = arguments;
|
|
||||||
synced[ pos ].done = true;
|
|
||||||
|
|
||||||
if ( pos == 0 || !synced[ pos-1 ] )
|
|
||||||
for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
|
|
||||||
if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
|
|
||||||
if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
|
|
||||||
if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
|
|
||||||
|
|
||||||
synced[i] = null;
|
|
||||||
syncedData[i] = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return ajax.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
|
@ -1,10 +0,0 @@
|
||||||
/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
|
|
||||||
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
|
||||||
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
|
||||||
*
|
|
||||||
* $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
|
|
||||||
* $Rev: 2447 $
|
|
||||||
*
|
|
||||||
* Version 2.1.1
|
|
||||||
*/
|
|
||||||
(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
|
|
3558
plugins/Autocomplete/jquery-autocomplete/lib/jquery.js
vendored
3558
plugins/Autocomplete/jquery-autocomplete/lib/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,163 +0,0 @@
|
||||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
|
||||||
/* ---------->>> global settings needed for thickbox <<<-----------------------------------------------------------*/
|
|
||||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
|
||||||
*{padding: 0; margin: 0;}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
|
||||||
/* ---------->>> thickbox specific link and font settings <<<------------------------------------------------------*/
|
|
||||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
|
||||||
#TB_window {
|
|
||||||
font: 12px Arial, Helvetica, sans-serif;
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_secondLine {
|
|
||||||
font: 10px Arial, Helvetica, sans-serif;
|
|
||||||
color:#666666;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_window a:link {color: #666666;}
|
|
||||||
#TB_window a:visited {color: #666666;}
|
|
||||||
#TB_window a:hover {color: #000;}
|
|
||||||
#TB_window a:active {color: #666666;}
|
|
||||||
#TB_window a:focus{color: #666666;}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
|
||||||
/* ---------->>> thickbox settings <<<-----------------------------------------------------------------------------*/
|
|
||||||
/* ----------------------------------------------------------------------------------------------------------------*/
|
|
||||||
#TB_overlay {
|
|
||||||
position: fixed;
|
|
||||||
z-index:100;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
height:100%;
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.TB_overlayMacFFBGHack {background: url(macFFBgHack.png) repeat;}
|
|
||||||
.TB_overlayBG {
|
|
||||||
background-color:#000;
|
|
||||||
filter:alpha(opacity=75);
|
|
||||||
-moz-opacity: 0.75;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
|
|
||||||
* html #TB_overlay { /* ie6 hack */
|
|
||||||
position: absolute;
|
|
||||||
height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_window {
|
|
||||||
position: fixed;
|
|
||||||
background: #ffffff;
|
|
||||||
z-index: 102;
|
|
||||||
color:#000000;
|
|
||||||
display:none;
|
|
||||||
border: 4px solid #525252;
|
|
||||||
text-align:left;
|
|
||||||
top:50%;
|
|
||||||
left:50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
* html #TB_window { /* ie6 hack */
|
|
||||||
position: absolute;
|
|
||||||
margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_window img#TB_Image {
|
|
||||||
display:block;
|
|
||||||
margin: 15px 0 0 15px;
|
|
||||||
border-right: 1px solid #ccc;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
border-top: 1px solid #666;
|
|
||||||
border-left: 1px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_caption{
|
|
||||||
height:25px;
|
|
||||||
padding:7px 30px 10px 25px;
|
|
||||||
float:left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_closeWindow{
|
|
||||||
height:25px;
|
|
||||||
padding:11px 25px 10px 0;
|
|
||||||
float:right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_closeAjaxWindow{
|
|
||||||
padding:7px 10px 5px 0;
|
|
||||||
margin-bottom:1px;
|
|
||||||
text-align:right;
|
|
||||||
float:right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_ajaxWindowTitle{
|
|
||||||
float:left;
|
|
||||||
padding:7px 0 5px 10px;
|
|
||||||
margin-bottom:1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_title{
|
|
||||||
background-color:#e8e8e8;
|
|
||||||
height:27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_ajaxContent{
|
|
||||||
clear:both;
|
|
||||||
padding:2px 15px 15px 15px;
|
|
||||||
overflow:auto;
|
|
||||||
text-align:left;
|
|
||||||
line-height:1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_ajaxContent.TB_modal{
|
|
||||||
padding:15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_ajaxContent p{
|
|
||||||
padding:5px 0px 5px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_load{
|
|
||||||
position: fixed;
|
|
||||||
display:none;
|
|
||||||
height:13px;
|
|
||||||
width:208px;
|
|
||||||
z-index:103;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
* html #TB_load { /* ie6 hack */
|
|
||||||
position: absolute;
|
|
||||||
margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_HideSelect{
|
|
||||||
z-index:99;
|
|
||||||
position:fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color:#fff;
|
|
||||||
border:none;
|
|
||||||
filter:alpha(opacity=0);
|
|
||||||
-moz-opacity: 0;
|
|
||||||
opacity: 0;
|
|
||||||
height:100%;
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
* html #TB_HideSelect { /* ie6 hack */
|
|
||||||
position: absolute;
|
|
||||||
height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
#TB_iframeContent{
|
|
||||||
clear:both;
|
|
||||||
border:none;
|
|
||||||
margin-bottom:-1px;
|
|
||||||
margin-top:1px;
|
|
||||||
_margin-bottom:1px;
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
TODO
|
|
||||||
|
|
||||||
- test formatItem implementation that returns (clickable) anchors
|
|
||||||
- bug: handle del key; eg. type a letter, remove it using del, type same letter again: nothing happens
|
|
||||||
- handle up/down keys in textarea (prevent default while select is open?)
|
|
||||||
- docs: max:0 works, too, "removing" it(??)
|
|
||||||
- fix ac_loading/options.loadingClass
|
|
||||||
- support/enable request urls like foo/bar/10 instead of foo/q=10
|
|
||||||
- urlencode request term before passing to $.ajax/data; evaluate why $.ajax doesn't handle that itself, if at all; try with umlauts, russian/danish/chinese characeters (see validate)
|
|
||||||
- test what happens when an element gets focused programmatically (maybe even before then autcomplete is applied)
|
|
||||||
- check if blur on selecting can be removed
|
|
||||||
- fix keyhandling to ignore metakeys, eg. shift; especially important for chinese characters that need more then one key
|
|
||||||
- enhance mustMatch: provide event/callback when a value gets deleted
|
|
||||||
- handle tab key different then enter, eg. don't blur field or prevent default, just let it move on; in any case, no need to blur the field when selecting a value via tab, unlike return
|
|
||||||
- prevent redundant requests on
|
|
||||||
- superstring returned no result, no need to query again for substring, eg. pete returned nothing, peter won't either
|
|
||||||
- previous query mustn't be requested again, eg. pete returns 10 lines, peter nothing, backspace to pete should get the 10 lines from cache (may need TimeToLive setting for cache to invalidate it)
|
|
||||||
- incorporate improvements and suggestions by Hector: http://beta.winserver.com/public/test/MultiSuggestTest.wct
|
|
||||||
- json support: An optional JSON format, that assumes a certain JSON format as default and just looks for a dataType "json" to be activated; [records], where each record is { id:String, label:String, moreOptionalValues... }
|
|
||||||
- accept callback as first argument to let users implement their own dynamic data (no caching) - consider async API
|
|
||||||
- allow users to keep their incomplete value when pressing tab, just mimic the default-browser-autocomplete: tab doesn't select any proposed value -> tab closes the select and works normal otherwise
|
|
||||||
- small bug in your autocomplete, When setting autoFill:true I would expect formatResult to be called on autofill, it seems not to be the case.
|
|
||||||
- add a callback to allow decoding the response
|
|
||||||
- allow modification of not-last value in multiple-fields
|
|
||||||
@option Number size Limit the number of items to show at once. Default:
|
|
||||||
@option Function parse - TEST AND DOCUMENT ME
|
|
||||||
- add option to display selectbox on focus
|
|
||||||
|
|
||||||
$input.bind("show", function() {
|
|
||||||
if ( !select.visible() ) {
|
|
||||||
onChange(0, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
- reference: http://capxous.com/
|
|
||||||
- add "try ..." hints to demo
|
|
||||||
- check out demos
|
|
||||||
- reference: http://createwebapp.com/demo/
|
|
||||||
|
|
||||||
- add option to hide selectbox when no match is found - see comment by Ian on plugin page (14. Juli 2007 04:31)
|
|
||||||
- add example for reinitializing an autocomplete using unbind()
|
|
||||||
|
|
||||||
- Add option to pass through additional arguments to $.ajax, like type to use POST instead of GET
|
|
||||||
|
|
||||||
- I found out that the problem with UTF-8 not being correctly sent can be solved on the server side by applying (PHP) rawurldecode() function, which decodes the Unicode characters sent by GET method and therefore URL-encoded.
|
|
||||||
-> add that hint to docs and examples
|
|
||||||
|
|
||||||
But I am trying this with these three values: “foo bar”, “foo foo”, and “foo far”, and if I enter “b” (or “ba”) nothing matches, if I enter “f” all three do match, and if I enter “fa” the last one matches.
|
|
||||||
The problem seems to be that the cache is implemented with a first-character hashtable, so only after matching the first character, the latter ones are searched for.
|
|
||||||
|
|
||||||
xml example:
|
|
||||||
<script type="text/javascript">
|
|
||||||
function parseXML(data) {
|
|
||||||
var results = [];
|
|
||||||
var branches = $(data).find('item');
|
|
||||||
$(branches).each(function() {
|
|
||||||
var text = $.trim($(this).find('text').text());
|
|
||||||
var value = $.trim($(this).find('value').text());
|
|
||||||
//console.log(text);
|
|
||||||
//console.log(value);
|
|
||||||
results[results.length] = {'data': this, 'result': value, 'value': text};
|
|
||||||
});
|
|
||||||
$(results).each(function() {
|
|
||||||
//console.log('value', this.value);
|
|
||||||
//console.log('text', this.text);
|
|
||||||
});
|
|
||||||
//console.log(results);
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
$(YourOojHere).autocomplete(SERVER_AJAX_URL, {parse: parseXML});
|
|
||||||
</script>
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<ajaxresponse>
|
|
||||||
<item>
|
|
||||||
<text>
|
|
||||||
<![CDATA[<b>FreeNode:</b> irc.freenode.net:6667]]>
|
|
||||||
</text>
|
|
||||||
<value><![CDATA[irc.freenode.net:6667]]></value>
|
|
||||||
</item><item>
|
|
||||||
<text>
|
|
||||||
<![CDATA[<b>irc.oftc.net</b>:6667]]>
|
|
||||||
</text>
|
|
||||||
<value><![CDATA[irc.oftc.net:6667]]></value>
|
|
||||||
</item><item>
|
|
||||||
<text>
|
|
||||||
<![CDATA[<b>irc.undernet.org</b>:6667]]>
|
|
||||||
</text>
|
|
||||||
<value><![CDATA[irc.undernet.org:6667]]></value>
|
|
||||||
</item>
|
|
||||||
</ajaxresponse>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Hi all,
|
|
||||||
|
|
||||||
I use Autocomplete 1.0 Alpha mostly for form inputs bound to foreign
|
|
||||||
key columns. For instance I have a user_position table with two
|
|
||||||
columns: user_id and position_id. On new appointment form I have two
|
|
||||||
autocomplete text inputs with the following code:
|
|
||||||
|
|
||||||
<input type="text" id="user_id" class="ac_input" tabindex="1" />
|
|
||||||
<input type="text" id="position_id" class="ac_input" tabindex="2" />
|
|
||||||
|
|
||||||
As you can see the inputs do not have a name attribute, and when the
|
|
||||||
form is submitted their values are not sent, which is all right since
|
|
||||||
they will contain strings like:
|
|
||||||
|
|
||||||
'John Doe'
|
|
||||||
'Sales Manager'
|
|
||||||
|
|
||||||
whereas our backend expects something like:
|
|
||||||
|
|
||||||
23
|
|
||||||
14
|
|
||||||
|
|
||||||
which are the user_id for John Doe and position_id for Sales Manager.
|
|
||||||
To send these values I have two hidden inputs in the form like this:
|
|
||||||
|
|
||||||
<input type="hidden" name="user_id" value="">
|
|
||||||
<input type="hidden" name="position_id" value="">
|
|
||||||
|
|
||||||
Also I have the following code in the $().ready function:
|
|
||||||
|
|
||||||
$("#user_id").result(function(event, data, formatted) {
|
|
||||||
$("input[@name=user_id]").val(data[1]);
|
|
||||||
});
|
|
||||||
$("#position_id").result(function(event, data, formatted) {
|
|
||||||
$("input[@name=position_id]").val(data[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
As could be seen these functions stuff user_id and position_id values
|
|
||||||
(in our example 23 and 14) into the hidden inputs, and when the form
|
|
||||||
is submitted these values are sent:
|
|
||||||
|
|
||||||
user_id = 23
|
|
||||||
position_id = 14
|
|
||||||
|
|
||||||
The backend script then takes care of adding a record to our
|
|
||||||
user_position table containing those values.
|
|
||||||
|
|
||||||
I wonder how could the plugin code be modified to simplify the setup
|
|
||||||
by taking care of adding hidden inputs and updating the value of
|
|
||||||
hidden inputs as default behavior. I have successfully attempted a
|
|
||||||
simpler solution - writing a wrapper to perform these additional tasks
|
|
||||||
and invoke autocomplete as well. I hope my intention is clear enough,
|
|
||||||
if not, this is exactly the expected outcome:
|
|
||||||
|
|
||||||
Before:
|
|
||||||
|
|
||||||
<script type="text/javascript"
|
|
||||||
src="jquery.autocomplete-modified.js"></script>
|
|
||||||
<input type="text" name="user_id" class="ac_input" tabindex="1" />
|
|
||||||
|
|
||||||
After:
|
|
||||||
|
|
||||||
<input type="text" id="user_id" class="ac_input" tabindex="1" />
|
|
||||||
<input type="hidden" name="user_id" value="23">
|
|
||||||
|
|
||||||
|
|
||||||
Last word, I know this looks like a tall order, and I do not hope
|
|
||||||
someone will make a complete working mod for me, but rather would very
|
|
||||||
much appreciate helpful advise and directions.
|
|
||||||
|
|
||||||
Many thanks in advance
|
|
||||||
Majid
|
|
||||||
|
|
71
plugins/Autocomplete/js/autocomplete.go.js
Normal file
71
plugins/Autocomplete/js/autocomplete.go.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/** Init for Autocomplete (requires jquery-ui)
|
||||||
|
*
|
||||||
|
* @package Plugin
|
||||||
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
|
* @copyright 2013 Free Software Foundation, Inc.
|
||||||
|
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||||
|
* @link http://status.net/
|
||||||
|
*/
|
||||||
|
|
||||||
|
var origInit = SN.Init.NoticeFormSetup;
|
||||||
|
SN.Init.NoticeFormSetup = function(form) {
|
||||||
|
origInit(form);
|
||||||
|
|
||||||
|
// Only attach to traditional-style forms
|
||||||
|
var textarea = form.find('.notice_data-text:first');
|
||||||
|
if (textarea.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function termSplit(val) {
|
||||||
|
return val.split(/ \s*/);
|
||||||
|
}
|
||||||
|
function extractLast( term ) {
|
||||||
|
return termSplit(term).pop();
|
||||||
|
}
|
||||||
|
var apiUrl = $('#autocomplete-api').attr('data-url');
|
||||||
|
// migrated "multiple" and "multipleSeparator" from
|
||||||
|
// http://www.learningjquery.com/2010/06/autocomplete-migration-guide
|
||||||
|
textarea
|
||||||
|
.bind('keydown', function( event ) {
|
||||||
|
if ( event.keyCode === $.ui.keyCode.TAB &&
|
||||||
|
$( this ).data( "ui-autocomplete" ).menu.active ) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.autocomplete({
|
||||||
|
minLength: 1, // 1 is default
|
||||||
|
source: function (request, response) {
|
||||||
|
$.getJSON( apiUrl, {
|
||||||
|
term: extractLast(request.term)
|
||||||
|
}, response );
|
||||||
|
},
|
||||||
|
search: function () {
|
||||||
|
// custom minLength, we though we match the 1 below
|
||||||
|
var term = extractLast(this.value);
|
||||||
|
if (term.length <= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focus: function () {
|
||||||
|
// prevent value inserted on focus
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
select: function (event, ui) {
|
||||||
|
var terms = termSplit(this.value);
|
||||||
|
terms.pop(); // remove latest term
|
||||||
|
terms.push(ui.item.value); // insert
|
||||||
|
terms.push(''); // empty element, for the join()
|
||||||
|
this.value = terms.join(' ');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.data('ui-autocomplete')._renderItem = function (ul, item) {
|
||||||
|
return $('<li></li>')
|
||||||
|
.data('ui-autocomplete-item', item)
|
||||||
|
.append('<a><img style="display:inline; vertical-align: middle"><span /></a>')
|
||||||
|
.find('img').attr('src', item.avatar).end()
|
||||||
|
.find('span').text(item.label).end()
|
||||||
|
.appendTo(ul);
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user