/**
* Mail autocomplete
* Author: nuintun
* Modifications by Minor Hotels 2024
* $(selector).mailtip({
* mails: [], // mails
* onselected: function(mail){}, // callback on selected
* width: 'auto', // popup tip's width
* offsetTop: -1, // offset top relative default position
* offsetLeft: 0, // offset left relative default position
* zIndex: 10 // popup tip's z-index
* });
*/
'use strict';
(function ($){
// invalid email char test regexp
var INVALIDEMAILRE = /[^\u4e00-\u9fa5\.\-+_a-zA-Z0-9]/;
// is support oninput event
var hasInputEvent = 'oninput' in document.createElement('input');
// is ie 9
var ISIE9 = /MSIE 9.0/i.test(window.navigator.appVersion || window.navigator.userAgent);
/**
* is a number
* @param value
* @returns {boolean}
*/
function isNumber(value){
return typeof value === 'number' && isFinite(value);
}
/**
* create popup tip
* @param input
* @param config
* @returns {*}
*/
function createTip(input, config){
var tip = null;
// only create tip and binding event once
if (!input.data('data-mailtip')) {
var wrap = input.parent();
// set parent node position
!/absolute|relative/i.test(wrap.css('position')) && wrap.css('position', 'relative');
// off input autocomplete
input.attr('autocomplete', 'off');
var offset = input.offset();
var wrapOffset = wrap.offset();
tip = $('
');
// insert tip after input
input.after(tip);
// set tip style
tip.css({
top: offset.top - wrapOffset.top + input.outerHeight() + config.offsetTop,
left: offset.left - wrapOffset.left + config.offsetLeft,
width: config.width === 'input' ? input.outerWidth() - tip.outerWidth() + tip.width() : config.width
});
// when width is auto, set min width equal input width
if (config.width === 'auto') {
tip.css('min-width', input.outerWidth() - tip.outerWidth() + tip.width());
}
// binding event
tip.on('mouseenter mouseleave click', 'li', function (e){
var selected = $(this);
switch (e.type) {
case 'mouseenter':
selected.addClass('hover');
break;
case 'click':
var mail = selected.attr('title');
input.val(mail).focus();
config.onselected.call(input[0], mail);
break;
case 'mouseleave':
selected.removeClass('hover');
break;
default:
break;
}
});
// when on click if the target element not input, hide tip
$(document).on('click', function (e){
if (e.target === input[0]) return;
tip.hide();
});
input.data('data-mailtip', tip);
}
return tip || input.data('data-mailtip');
}
/**
* create mail list item
* @param value
* @param mails
* @returns {*}
*/
function createItems(value, mails){
var mail;
var domain;
var items = '';
var atIndex = value.indexOf('@');
var hasAt = atIndex !== -1;
if (hasAt) {
domain = value.substring(atIndex + 1);
value = value.substring(0, atIndex);
}
for (var i = 0, len = mails.length; i < len; i++) {
mail = mails[i];
if (hasAt && mail.indexOf(domain) !== 0) continue;
items += '' + value + '@' + mail + '
';
}
// active first item
return items.replace(' tip.offset().top + tip.prop('offsetHeight') - itemActive.prop('clientHeight')) {
tip.find('li.active')[0].scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}
}
/**
* toggle tip
* @param tip
* @param value
* @param mails
*/
function toggleTip(tip, value, mails){
var atIndex = value.indexOf('@');
// if input text is empty or has invalid char or begin with @ or more than two @, hide tip
if (!value
|| atIndex === 0
|| atIndex !== value.lastIndexOf('@')
|| INVALIDEMAILRE.test(atIndex === -1 ? value : value.substring(0, atIndex))
|| atIndex === -1) {
tip.hide();
} else {
var items = createItems(value, mails);
// if has match mails show tip
if (items) {
tip.html(items).show();
} else {
tip.hide();
}
let input = tip.parent().find('input'),
wrap = tip.parent(),
offset = input.offset(),
wrapOffset = wrap.offset();
// Reset styling
tip.css({
'top': offset.top - wrapOffset.top + input.outerHeight() + 'px',
});
// When opening the pop up, check if it is off screen and put it above the input if true
if(tip[0].getBoundingClientRect().bottom > window.innerHeight) {
tip.css({
top: offset.top - wrapOffset.top - tip.outerHeight() + 'px'
});
}
}
}
/**
* exports
* @param config
* @returns {*}
*/
$.fn.mailtip = function (config){
var defaults = {
mails: [
'qq.com', '163.com', 'sina.com',
'gmail.com', '126.com', '139.com',
'189.com', 'sohu.com', 'msn.com',
'hotmail.com', 'yahoo.com', 'yahoo.com.cn'
],
onselected: $.noop,
width: 'auto',
offsetTop: -1,
offsetLeft: 0,
zIndex: 10
};
config = $.extend({}, defaults, config);
config.zIndex = isNumber(config.zIndex) ? config.zIndex : defaults.zIndex;
config.offsetTop = isNumber(config.offsetTop) ? config.offsetTop : defaults.offsetTop;
config.offsetLeft = isNumber(config.offsetLeft) ? config.offsetLeft : defaults.offsetLeft;
config.onselected = $.isFunction(config.onselected) ? config.onselected : defaults.onselected;
config.width = config.width === 'input' || isNumber(config.width) ? config.width : defaults.width;
return this.each(function (){
// input
var input = $(this);
// tip
var tip = createTip(input, config);
// binding key down event
input.on('keydown', function (e){
// if tip is visible do nothing
if (tip.css('display') === 'none') return;
switch (e.keyCode) {
// backspace
case 8:
// shit! ie9 input event has a bug, backspace do not trigger input event
if (ISIE9) {
input.trigger('input');
}
break;
// tab
case 9:
tip.hide();
break;
// up
case 38:
e.preventDefault();
changeActive(tip, true);
break;
// down
case 40:
e.preventDefault();
changeActive(tip);
break;
// enter
case 13:
e.preventDefault();
var mail = tip.find('li.active').attr('title');
input.val(mail).focus();
tip.hide();
config.onselected.call(this, mail);
break;
// esc
case 27:
tip.hide();
break;
default:
break;
}
});
// binding input or propertychange event
if (hasInputEvent) {
input.on('input', function (){
toggleTip(tip, this.value, config.mails);
});
} else {
input.on('propertychange', function (e){
if (e.originalEvent.propertyName === 'value') {
toggleTip(tip, this.value, config.mails);
}
});
}
// shit! ie9 input event has a bug, backspace do not trigger input event
if (ISIE9) {
input.on('keyup', function (e){
if (e.keyCode === 8) {
toggleTip(tip, this.value, config.mails);
}
});
}
});
};
}(jQuery));