despite my problem is similar with this
the proposed solution did not work for me.
Let me explain what happens.
As the title suggests unhiglight is called right after highlight.
and this happens only under one scenario only:
using remote method of email.
If the ajax call returns true(after hitting the submit button)(email does not exist) error class is added and immediately replaced by valid class in the fields that are not filled(required)...
if I hit submit again the problem does not appear...if some fields are not filled error class is attached properly.
What differentiates first from second submit(and if email is not changed) is the fact that ajax call for the email is not made.
So if ajax call is made(for the email) and returns true the above problem appears.
The code for the showerrors method found in the above Soverflow post does not help.
I must emphasize that if I remove the remote method for the email the problem is fixed
here is the code inside validate():
$('#bizuserform').validate({
showErrors: function (errorMap, errorList) {
var errors = this.numberOfInvalids();
if (errors) {
var message = errors == 1 ? '1 field is miissing!!!' : errors + ' fields are missing!!!';
$("#error_general span").empty().html(message);
$("#error_general").show();
} else {
$("#error_general").hide();
}
this.defaultShowErrors();
},
highlight: function(element, errorClass, validClass) {
if($(element).attr("id")=='staffclient_yes')
{
$('#staffclient_yes').parent().addClass(errorClass);
}
if($(element).attr("id")=='medical')
{
$('#medical').parent().addClass(errorClass);
}
$(element).addClass(errorClass).removeClass(validClass);
},
unhighlight: function(element, errorClass, validClass) {
if($(element).attr("id")=='staffclient_yes')
{
$('#staffclient_yes').parent().removeClass(errorClass);
}
if($(element).attr("id")=='medical')
{
$('#medical').parent().removeClass(errorClass);
}
$(element).removeClass(errorClass).addClass(validClass);
},
onfocusout: false,
submitHandler: function(form,event) {
event.preventDefault();
//ajax request here...ommited for brevity
},
rules:{
name: {
required: function () {
return $('#coname').is(':blank');
},
normalizer: function( value ) { return $.trim( value );}
},
lastname: {
required: function () {
return $('#coname').is(':blank');
},
normalizer: function( value ) { return $.trim( value );}
},
coname: {
required: function () {
return $('#name,#lastname').is(':blank');
},
normalizer: function( value ) { return $.trim( value );}
},
buztype:{required:true
},
med_spec:{
required: function(element) {return $('#medical').is(':checked');}
},
password:{
required:true,
password:true,
normalizer: function( value ) { return $.trim( value );}
},
address:{ required: true,
normalizer: function( value ) { return $.trim( value );}
},
city:{ required: true,
normalizer: function( value ) { return $.trim( value );}
},
municipality:{ required: true,
normalizer: function( value ) { return $.trim( value );}
},
email:{required: true,
email: true,
remote: {
url: "email_validity.php",//if I remove the remote method here problem is fixed
type: "post"
},
normalizer: function( value ) { return $.trim( value );}
},
phone:{required:true,
normalizer: function( value ) { return $.trim( value );},
digits: true,
rangelength: [10, 14]
},
staff_to_client:{
required: "#staf_eng:visible"
}
},
messages:{
buztype:'',
staff_to_client:'',
med_spec:'',
coname:"",
name:'',
lastname:'',
password:{required:""
},
address:'',
city:'',
municipality:'',
email:{required:"",
email:'not valid email',
remote:'email allready exists.'
},
phone:{required:"",
rangelength:'phonenumber messasge.',
digits:'only digits here'
},
staff_to_client:{
required: ""
}
}
});
I have one problem that I can't understand. I've built many web applications under jQuery UI and with jQuery UI Autocomplete plugin
$("#autocompleteObchod").autocomplete({
source: function( request, response ) {
console.log('ok');
$.ajax({
url: "/eshops/default/search",
dataType: "json",
data: {
q: request.term
},
success: function( data ) {
response($.map(data, function (item) {
return {
label: item.name,
value: item.name,
id: item.uuid
};
}));
}
} );
},
minLength: 2,
select: function( event, ui ) {
$('#form-eshopId').val(ui.item.id);
}
} );
This code works on my localhost.
But when I deploy my application via Dokku (heroku), autocomplete wont work. Nothing. No event triggered, no error in console, no error with javascript. Nothing. Do you guys had similar problem ? I'm out of energy with this.
If you want to check my application check http://dev.dobravec.info/organizations/
And try search something at first inline search component.
You have something wrong with you jquery ui version.
First of all I tried to debug the code of your autocomplete code version. It never fires out because it never receives arguments from you. For script your call is like calling autocomplete({}) - does nothing.
What the hell I thought - somebody broke autocomplete?
I went to jquery ui cdn, copied autocomplete function and pasted it to console. And initialized autocomplete on your input again. And it works fine.
So I dont know why, I dont know who - but somebody has edited your version of autocomplete. Get the new one and enjoy.
Try for yourself:
First ctrl+v jquery ui autocomplete function to console:
/*!
* jQuery UI Autocomplete 1.12.0
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
//>>label: Autocomplete
//>>group: Widgets
//>>description: Lists suggested words as the user is typing.
//>>docs: http://api.jqueryui.com/autocomplete/
//>>demos: http://jqueryui.com/autocomplete/
//>>css.structure: ../../themes/base/core.css
//>>css.structure: ../../themes/base/autocomplete.css
//>>css.theme: ../../themes/base/theme.css
$.widget( "ui.autocomplete", {
version: "1.12.0",
defaultElement: "<input>",
options: {
appendTo: null,
autoFocus: false,
delay: 300,
minLength: 1,
position: {
my: "left top",
at: "left bottom",
collision: "none"
},
source: null,
// Callbacks
change: null,
close: null,
focus: null,
open: null,
response: null,
search: null,
select: null
},
requestIndex: 0,
pending: 0,
_create: function() {
// Some browsers only repeat keydown events, not keypress events,
// so we use the suppressKeyPress flag to determine if we've already
// handled the keydown event. #7269
// Unfortunately the code for & in keypress is the same as the up arrow,
// so we use the suppressKeyPressRepeat flag to avoid handling keypress
// events when we know the keydown event was used to modify the
// search term. #7799
var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
nodeName = this.element[ 0 ].nodeName.toLowerCase(),
isTextarea = nodeName === "textarea",
isInput = nodeName === "input";
// Textareas are always multi-line
// Inputs are always single-line, even if inside a contentEditable element
// IE also treats inputs as contentEditable
// All other element types are determined by whether or not they're contentEditable
this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
this.isNewMenu = true;
this._addClass( "ui-autocomplete-input" );
this.element.attr( "autocomplete", "off" );
this._on( this.element, {
keydown: function( event ) {
if ( this.element.prop( "readOnly" ) ) {
suppressKeyPress = true;
suppressInput = true;
suppressKeyPressRepeat = true;
return;
}
suppressKeyPress = false;
suppressInput = false;
suppressKeyPressRepeat = false;
var keyCode = $.ui.keyCode;
switch ( event.keyCode ) {
case keyCode.PAGE_UP:
suppressKeyPress = true;
this._move( "previousPage", event );
break;
case keyCode.PAGE_DOWN:
suppressKeyPress = true;
this._move( "nextPage", event );
break;
case keyCode.UP:
suppressKeyPress = true;
this._keyEvent( "previous", event );
break;
case keyCode.DOWN:
suppressKeyPress = true;
this._keyEvent( "next", event );
break;
case keyCode.ENTER:
// when menu is open and has focus
if ( this.menu.active ) {
// #6055 - Opera still allows the keypress to occur
// which causes forms to submit
suppressKeyPress = true;
event.preventDefault();
this.menu.select( event );
}
break;
case keyCode.TAB:
if ( this.menu.active ) {
this.menu.select( event );
}
break;
case keyCode.ESCAPE:
if ( this.menu.element.is( ":visible" ) ) {
if ( !this.isMultiLine ) {
this._value( this.term );
}
this.close( event );
// Different browsers have different default behavior for escape
// Single press can mean undo or clear
// Double press in IE means clear the whole form
event.preventDefault();
}
break;
default:
suppressKeyPressRepeat = true;
// search timeout should be triggered before the input value is changed
this._searchTimeout( event );
break;
}
},
keypress: function( event ) {
if ( suppressKeyPress ) {
suppressKeyPress = false;
if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
event.preventDefault();
}
return;
}
if ( suppressKeyPressRepeat ) {
return;
}
// Replicate some key handlers to allow them to repeat in Firefox and Opera
var keyCode = $.ui.keyCode;
switch ( event.keyCode ) {
case keyCode.PAGE_UP:
this._move( "previousPage", event );
break;
case keyCode.PAGE_DOWN:
this._move( "nextPage", event );
break;
case keyCode.UP:
this._keyEvent( "previous", event );
break;
case keyCode.DOWN:
this._keyEvent( "next", event );
break;
}
},
input: function( event ) {
if ( suppressInput ) {
suppressInput = false;
event.preventDefault();
return;
}
this._searchTimeout( event );
},
focus: function() {
this.selectedItem = null;
this.previous = this._value();
},
blur: function( event ) {
if ( this.cancelBlur ) {
delete this.cancelBlur;
return;
}
clearTimeout( this.searching );
this.close( event );
this._change( event );
}
} );
this._initSource();
this.menu = $( "<ul>" )
.appendTo( this._appendTo() )
.menu( {
// disable ARIA support, the live region takes care of that
role: null
} )
.hide()
.menu( "instance" );
this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
this._on( this.menu.element, {
mousedown: function( event ) {
// prevent moving focus out of the text field
event.preventDefault();
// IE doesn't prevent moving focus even with event.preventDefault()
// so we set a flag to know when we should ignore the blur event
this.cancelBlur = true;
this._delay( function() {
delete this.cancelBlur;
// Support: IE 8 only
// Right clicking a menu item or selecting text from the menu items will
// result in focus moving out of the input. However, we've already received
// and ignored the blur event because of the cancelBlur flag set above. So
// we restore focus to ensure that the menu closes properly based on the user's
// next actions.
if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
this.element.trigger( "focus" );
}
} );
},
menufocus: function( event, ui ) {
var label, item;
// support: Firefox
// Prevent accidental activation of menu items in Firefox (#7024 #9118)
if ( this.isNewMenu ) {
this.isNewMenu = false;
if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
this.menu.blur();
this.document.one( "mousemove", function() {
$( event.target ).trigger( event.originalEvent );
} );
return;
}
}
item = ui.item.data( "ui-autocomplete-item" );
if ( false !== this._trigger( "focus", event, { item: item } ) ) {
// use value to match what will end up in the input, if it was a key event
if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
this._value( item.value );
}
}
// Announce the value in the liveRegion
label = ui.item.attr( "aria-label" ) || item.value;
if ( label && $.trim( label ).length ) {
this.liveRegion.children().hide();
$( "<div>" ).text( label ).appendTo( this.liveRegion );
}
},
menuselect: function( event, ui ) {
var item = ui.item.data( "ui-autocomplete-item" ),
previous = this.previous;
// Only trigger when focus was lost (click on menu)
if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
this.element.trigger( "focus" );
this.previous = previous;
// #6109 - IE triggers two focus events and the second
// is asynchronous, so we need to reset the previous
// term synchronously and asynchronously :-(
this._delay( function() {
this.previous = previous;
this.selectedItem = item;
} );
}
if ( false !== this._trigger( "select", event, { item: item } ) ) {
this._value( item.value );
}
// reset the term after the select event
// this allows custom select handling to work properly
this.term = this._value();
this.close( event );
this.selectedItem = item;
}
} );
this.liveRegion = $( "<div>", {
role: "status",
"aria-live": "assertive",
"aria-relevant": "additions"
} )
.appendTo( this.document[ 0 ].body );
this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
// Turning off autocomplete prevents the browser from remembering the
// value when navigating through history, so we re-enable autocomplete
// if the page is unloaded before the widget is destroyed. #7790
this._on( this.window, {
beforeunload: function() {
this.element.removeAttr( "autocomplete" );
}
} );
},
_destroy: function() {
clearTimeout( this.searching );
this.element.removeAttr( "autocomplete" );
this.menu.element.remove();
this.liveRegion.remove();
},
_setOption: function( key, value ) {
this._super( key, value );
if ( key === "source" ) {
this._initSource();
}
if ( key === "appendTo" ) {
this.menu.element.appendTo( this._appendTo() );
}
if ( key === "disabled" && value && this.xhr ) {
this.xhr.abort();
}
},
_isEventTargetInWidget: function( event ) {
var menuElement = this.menu.element[ 0 ];
return event.target === this.element[ 0 ] ||
event.target === menuElement ||
$.contains( menuElement, event.target );
},
_closeOnClickOutside: function( event ) {
if ( !this._isEventTargetInWidget( event ) ) {
this.close();
}
},
_appendTo: function() {
var element = this.options.appendTo;
if ( element ) {
element = element.jquery || element.nodeType ?
$( element ) :
this.document.find( element ).eq( 0 );
}
if ( !element || !element[ 0 ] ) {
element = this.element.closest( ".ui-front, dialog" );
}
if ( !element.length ) {
element = this.document[ 0 ].body;
}
return element;
},
_initSource: function() {
var array, url,
that = this;
if ( $.isArray( this.options.source ) ) {
array = this.options.source;
this.source = function( request, response ) {
response( $.ui.autocomplete.filter( array, request.term ) );
};
} else if ( typeof this.options.source === "string" ) {
url = this.options.source;
this.source = function( request, response ) {
if ( that.xhr ) {
that.xhr.abort();
}
that.xhr = $.ajax( {
url: url,
data: request,
dataType: "json",
success: function( data ) {
response( data );
},
error: function() {
response( [] );
}
} );
};
} else {
this.source = this.options.source;
}
},
_searchTimeout: function( event ) {
clearTimeout( this.searching );
this.searching = this._delay( function() {
// Search if the value has changed, or if the user retypes the same value (see #7434)
var equalValues = this.term === this._value(),
menuVisible = this.menu.element.is( ":visible" ),
modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
this.selectedItem = null;
this.search( null, event );
}
}, this.options.delay );
},
search: function( value, event ) {
value = value != null ? value : this._value();
// Always save the actual value, not the one passed as an argument
this.term = this._value();
if ( value.length < this.options.minLength ) {
return this.close( event );
}
if ( this._trigger( "search", event ) === false ) {
return;
}
return this._search( value );
},
_search: function( value ) {
this.pending++;
this._addClass( "ui-autocomplete-loading" );
this.cancelSearch = false;
this.source( { term: value }, this._response() );
},
_response: function() {
var index = ++this.requestIndex;
return $.proxy( function( content ) {
if ( index === this.requestIndex ) {
this.__response( content );
}
this.pending--;
if ( !this.pending ) {
this._removeClass( "ui-autocomplete-loading" );
}
}, this );
},
__response: function( content ) {
if ( content ) {
content = this._normalize( content );
}
this._trigger( "response", null, { content: content } );
if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
this._suggest( content );
this._trigger( "open" );
} else {
// use ._close() instead of .close() so we don't cancel future searches
this._close();
}
},
close: function( event ) {
this.cancelSearch = true;
this._close( event );
},
_close: function( event ) {
// Remove the handler that closes the menu on outside clicks
this._off( this.document, "mousedown" );
if ( this.menu.element.is( ":visible" ) ) {
this.menu.element.hide();
this.menu.blur();
this.isNewMenu = true;
this._trigger( "close", event );
}
},
_change: function( event ) {
if ( this.previous !== this._value() ) {
this._trigger( "change", event, { item: this.selectedItem } );
}
},
_normalize: function( items ) {
// assume all items have the right format when the first item is complete
if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
return items;
}
return $.map( items, function( item ) {
if ( typeof item === "string" ) {
return {
label: item,
value: item
};
}
return $.extend( {}, item, {
label: item.label || item.value,
value: item.value || item.label
} );
} );
},
_suggest: function( items ) {
var ul = this.menu.element.empty();
this._renderMenu( ul, items );
this.isNewMenu = true;
this.menu.refresh();
// Size and position menu
ul.show();
this._resizeMenu();
ul.position( $.extend( {
of: this.element
}, this.options.position ) );
if ( this.options.autoFocus ) {
this.menu.next();
}
// Listen for interactions outside of the widget (#6642)
this._on( this.document, {
mousedown: "_closeOnClickOutside"
} );
},
_resizeMenu: function() {
var ul = this.menu.element;
ul.outerWidth( Math.max(
// Firefox wraps long text (possibly a rounding bug)
// so we add 1px to avoid the wrapping (#7513)
ul.width( "" ).outerWidth() + 1,
this.element.outerWidth()
) );
},
_renderMenu: function( ul, items ) {
var that = this;
$.each( items, function( index, item ) {
that._renderItemData( ul, item );
} );
},
_renderItemData: function( ul, item ) {
return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
},
_renderItem: function( ul, item ) {
return $( "<li>" )
.append( $( "<div>" ).text( item.label ) )
.appendTo( ul );
},
_move: function( direction, event ) {
if ( !this.menu.element.is( ":visible" ) ) {
this.search( null, event );
return;
}
if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
this.menu.isLastItem() && /^next/.test( direction ) ) {
if ( !this.isMultiLine ) {
this._value( this.term );
}
this.menu.blur();
return;
}
this.menu[ direction ]( event );
},
widget: function() {
return this.menu.element;
},
_value: function() {
return this.valueMethod.apply( this.element, arguments );
},
_keyEvent: function( keyEvent, event ) {
if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
this._move( keyEvent, event );
// Prevents moving cursor to beginning/end of the text field in some browsers
event.preventDefault();
}
},
// Support: Chrome <=50
// We should be able to just use this.element.prop( "isContentEditable" )
// but hidden elements always report false in Chrome.
// https://code.google.com/p/chromium/issues/detail?id=313082
_isContentEditable: function( element ) {
if ( !element.length ) {
return false;
}
var editable = element.prop( "contentEditable" );
if ( editable === "inherit" ) {
return this._isContentEditable( element.parent() );
}
return editable === "true";
}
} );
$.extend( $.ui.autocomplete, {
escapeRegex: function( value ) {
return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
},
filter: function( array, term ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
return $.grep( array, function( value ) {
return matcher.test( value.label || value.value || value );
} );
}
} );
// Live region extension, adding a `messages` option
// NOTE: This is an experimental API. We are still investigating
// a full solution for string manipulation and internationalization.
$.widget( "ui.autocomplete", $.ui.autocomplete, {
options: {
messages: {
noResults: "No search results.",
results: function( amount ) {
return amount + ( amount > 1 ? " results are" : " result is" ) +
" available, use up and down arrow keys to navigate.";
}
}
},
__response: function( content ) {
var message;
this._superApply( arguments );
if ( this.options.disabled || this.cancelSearch ) {
return;
}
if ( content && content.length ) {
message = this.options.messages.results( content.length );
} else {
message = this.options.messages.noResults;
}
this.liveRegion.children().hide();
$( "<div>" ).text( message ).appendTo( this.liveRegion );
}
} );
var widgetsAutocomplete = $.ui.autocomplete;
And then, run your script again from console
$("#autocompleteObchod").autocomplete({
source: function( request, response ) {
console.log('ok');
$.ajax({
url: "/eshops/default/search",
dataType: "json",
data: {
q: request.term
},
success: function( data ) {
response($.map(data, function (item) {
return {
label: item.name,
value: item.name,
id: item.uuid
};
}));
}
} );
},
minLength: 2,
select: function( event, ui ) {
$('#form-eshopId').val(ui.item.id);
}
} );
Prior to 4.2 we could override the default MCE gallery template with this code:
var galleryview = wp.mce.views.get('gallery');
galleryview.getContent = function(){
this.template = media.template( 'editor-gallery-dtbaker-flexslider' );
return this.template(this.shortcode.attrs.named);
}
this no longer works. Is there a way to override the default 'gallery' view without re-registering it? This works (by copying and modifying the default code from mce-view.js) but I feel there should be a better way than re-registering the entire view.
Is this possible after the mce view rewrite? https://core.trac.wordpress.org/ticket/31412
Here's code that works in 4.2:
add_action( 'admin_print_footer_scripts', 'my_admin_footer_scripts', 100 );
function my_admin_footer_scripts(){ ?>
<script type="text/javascript">
( function( $ ) {
// start code copied from mce-view.js
var postID = $( '#post_ID' ).val() || 0;
var custom_gallery = _.extend( {}, {
edit: function( text, update ) {
var media = wp.media[ this.type ],
frame = media.edit( text );
this.pausePlayers && this.pausePlayers();
_.each( this.state, function( state ) {
frame.state( state ).on( 'update', function( selection ) {
update( media.shortcode( selection ).string() );
} );
} );
frame.on( 'close', function() {
frame.detach();
} );
frame.open();
},
state: [ 'gallery-edit' ],
template: wp.media.template( 'editor-gallery' ),
initialize: function() {
var attachments = wp.media.gallery.attachments( this.shortcode, postID ),
attrs = this.shortcode.attrs.named,
self = this;
if(typeof attrs.slider != 'undefined'){
switch(attrs.slider){
case 'flexslider':
this.template = wp.media.template( 'editor-gallery-dtbaker-flexslider' );
break;
case 'flex':
this.template = wp.media.template( 'editor-gallery-dtbaker-flex' );
break;
default:
// leave the existing template (editor-gallery)
}
}
attachments.more()
.done( function() {
attachments = attachments.toJSON();
_.each( attachments, function( attachment ) {
if ( attachment.sizes ) {
if ( attrs.size && attachment.sizes[ attrs.size ] ) {
attachment.thumbnail = attachment.sizes[ attrs.size ];
} else if ( attachment.sizes.thumbnail ) {
attachment.thumbnail = attachment.sizes.thumbnail;
} else if ( attachment.sizes.full ) {
attachment.thumbnail = attachment.sizes.full;
}
}
} );
self.render( self.template( {
attachments: attachments,
columns: attrs.columns ? parseInt( attrs.columns, 10 ) : wp.media.galleryDefaults.columns
} ) );
} )
.fail( function( jqXHR, textStatus ) {
self.setError( textStatus );
} );
}
} );
wp.mce.views.unregister('gallery');
wp.mce.views.register('gallery',custom_gallery);
})(jQuery);
<?php }
I have tried this as well and no go:
var dtbaker_gallery = wp.mce.views.get('gallery');
dtbaker_gallery = dtbaker_gallery.extend({
template: wp.media.template( 'editor-gallery-dtbaker-flexslider' )
});
Normal Navigation of Tab button get stuck over autocomplete type text box in Mozilla Firefox browser. On Tab key press it's not moving to next element. Working fine in IE and Chrome. Can anyone assist me to fix this?
My Jquery Code is:
(function( $, undefined ) {
$.widget( "ui.autocomplete", {
options: {
appendTo: "body",
delay: 300,
minLength: 1,
position: {
my: "left top",
at: "left bottom",
collision: "none"
},
source: null
},
_create: function() {
var self = this,
doc = this.element[ 0 ].ownerDocument;
this.element
.addClass( "ui-autocomplete-input" )
.attr( "autocomplete", "off" )
// TODO verify these actually work as intended
.attr({
role: "textbox",
"aria-autocomplete": "list",
"aria-haspopup": "true"
})
.bind( "keydown.autocomplete", function( event ) {
if ( self.options.disabled ) {
return;
}
var keyCode = $.ui.keyCode;
switch( event.keyCode ) {
case keyCode.PAGE_UP:
self._move( "previousPage", event );
break;
case keyCode.PAGE_DOWN:
self._move( "nextPage", event );
break;
case keyCode.UP:
self._move( "previous", event );
// prevent moving cursor to beginning of text field in some browsers
event.preventDefault();
break;
case keyCode.DOWN:
self._move( "next", event );
// prevent moving cursor to end of text field in some browsers
event.preventDefault();
break;
case keyCode.ENTER:
case keyCode.NUMPAD_ENTER:
// when menu is open or has focus
if ( self.menu.element.is( ":visible" ) ) {
event.preventDefault();
}
//passthrough - ENTER and TAB both select the current element
case keyCode.TAB:
if ( !self.menu.active ) {
return;
}
self.menu.select( event );
break;
case keyCode.ESCAPE:
self.element.val( self.term );
self.close( event );
break;
default:
// keypress is triggered before the input value is changed
clearTimeout( self.searching );
self.searching = setTimeout(function() {
// only search if the value has changed
if ( self.term != self.element.val() ) {
self.selectedItem = null;
self.search( null, event );
}
}, self.options.delay );
break;
}
})
.bind( "focus.autocomplete", function() {
if ( self.options.disabled ) {
return;
}
self.selectedItem = null;
self.previous = self.element.val();
})
.bind( "blur.autocomplete", function( event ) {
if ( self.options.disabled ) {
return;
}
clearTimeout( self.searching );
// clicks on the menu (or a button to trigger a search) will cause a blur event
self.closing = setTimeout(function() {
self.close( event );
self._change( event );
}, 150 );
});
this._initSource();
this.response = function() {
return self._response.apply( self, arguments );
};
//this.menu.element.hide();
this.menu = $( "<ul></ul>" )
.addClass( "ui-autocomplete" )
.appendTo( $( this.options.appendTo || "body", doc )[0] )
// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
.mousedown(function( event ) {
// clicking on the scrollbar causes focus to shift to the body
// but we can't detect a mouseup or a click immediately afterward
// so we have to track the next mousedown and close the menu if
// the user clicks somewhere outside of the autocomplete
var menuElement = self.menu.element[ 0 ];
if ( event.target === menuElement ) {
setTimeout(function() {
$( document ).one( 'mousedown', function( event ) {
if ( event.target !== self.element[ 0 ] &&
event.target !== menuElement &&
!$.ui.contains( menuElement, event.target ) ) {
self.close();
}
});
}, 1 );
}
// use another timeout to make sure the blur-event-handler on the input was already triggered
setTimeout(function() {
clearTimeout( self.closing );
}, 13);
})
.menu({
focus: function( event, ui ) {
var item = ui.item.data( "item.autocomplete" );
if ( false !== self._trigger( "focus", null, { item: item } ) ) {
// use value to match what will end up in the input, if it was a key event
if ( /^key/.test(event.originalEvent.type) ) {
self.element.val( item.value );
}
}
},
selected: function( event, ui ) {
var item = ui.item.data( "item.autocomplete" ),
previous = self.previous;
// only trigger when focus was lost (click on menu)
if ( self.element[0] !== doc.activeElement ) {
self.element.focus();
self.previous = previous;
}
if ( false !== self._trigger( "select", event, { item: item } ) ) {
self.element.val( item.value );
}
self.close( event );
self.selectedItem = item;
},
blur: function( event, ui ) {
// don't set the value of the text field if it's already correct
// this prevents moving the cursor unnecessarily
if ( self.menu.element.is(":visible") &&
( self.element.val() !== self.term ) ) {
self.element.val( self.term );
}
}
})
.zIndex( this.element.zIndex() + 1 )
// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
.css({ top: 0, left: 0 })
.hide()
.data( "menu" );
if ( $.fn.bgiframe ) {
this.menu.element.bgiframe();
}
},
destroy: function() {
this.element
.removeClass( "ui-autocomplete-input" )
.removeAttr( "autocomplete" )
.removeAttr( "role" )
.removeAttr( "aria-autocomplete" )
.removeAttr( "aria-haspopup" );
this.menu.element.remove();
$.Widget.prototype.destroy.call( this );
},
_setOption: function( key, value ) {
$.Widget.prototype._setOption.apply( this, arguments );
if ( key === "source" ) {
this._initSource();
}
if ( key === "appendTo" ) {
this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
}
},
_initSource: function() {
var array,
url;
if ( $.isArray(this.options.source) ) {
array = this.options.source;
this.source = function( request, response ) {
response( $.ui.autocomplete.filter(array, request.term) );
};
} else if ( typeof this.options.source === "string" ) {
url = this.options.source;
this.source = function( request, response ) {
$.getJSON( url, request, response );
};
} else {
this.source = this.options.source;
}
},
search: function( value, event ) {
value = value != null ? value : this.element.val();
if ( value.length < this.options.minLength ) {
return this.close( event );
}
clearTimeout( this.closing );
if ( this._trigger("search") === false ) {
return;
}
return this._search( value );
},
_search: function( value ) {
this.term = this.element
.addClass( "ui-autocomplete-loading" )
// always save the actual value, not the one passed as an argument
.val();
this.source( { term: value }, this.response );
},
_response: function( content ) {
if ( content.length ) {
content = this._normalize( content );
this._suggest( content );
this._trigger( "open" );
} else {
this.close();
}
this.element.removeClass( "ui-autocomplete-loading" );
},
close: function( event ) {
clearTimeout( this.closing );
if ( this.menu.element.is(":visible") ) {
this._trigger( "close", event );
this.menu.deactivate();
}
},
_change: function( event ) {
if ( this.previous !== this.element.val() ) {
this._trigger( "change", event, { item: this.selectedItem } );
}
},
_normalize: function( items ) {
// assume all items have the right format when the first item is complete
if ( items.length && items[0].label && items[0].value ) {
return items;
}
return $.map( items, function(item) {
if ( typeof item === "string" ) {
return {
label: item,
value: item
};
}
return $.extend({
label: item.label || item.value,
value: item.value || item.label
}, item );
});
},
_suggest: function( items ) {
var ul = this.menu.element
.empty()
.zIndex( this.element.zIndex() + 1 ),
menuWidth,
textWidth;
this._renderMenu( ul, items );
// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
this.menu.deactivate();
this.menu.refresh();
this.menu.element.show().position( $.extend({
of: this.element
}, this.options.position ));
menuWidth = ul.width( "" ).outerWidth();
textWidth = this.element.outerWidth();
ul.outerWidth( Math.max( menuWidth, textWidth ) );
},
_renderMenu: function( ul, items ) {
var self = this;
$.each( items, function( index, item ) {
self._renderItem( ul, item );
});
},
_renderItem: function( ul, item) {
return $( "<li></li>" )
.data( "item.autocomplete", item )
.append( $( "<a></a>" ).text( item.label ) )
.appendTo( ul );
},
_move: function( direction, event ) {
if ( !this.menu.element.is(":visible") ) {
this.search( null, event );
return;
}
if ( this.menu.first() && /^previous/.test(direction) ||
this.menu.last() && /^next/.test(direction) ) {
this.element.val( this.term );
this.menu.deactivate();
return;
}
this.menu[ direction ]( event );
},
widget: function() {
return this.menu.element;
}
});
$.extend( $.ui.autocomplete, {
escapeRegex: function( value ) {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
},
filter: function(array, term) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
return $.grep( array, function(value) {
return matcher.test( value.label || value.value || value );
});
}
});
}( jQuery ));