/*
// Usage
FgEmojiPicker.init({
trigger: 'button',
position: ['bottom', 'right'],
dir: 'directory/to/json', (without json name)
preFetch: true, //load emoji json when function called
emit(emoji) {
console.log(emoji);
}
});
*/
const FgEmojiPicker = function (options) {
this.options = options;
this.emojiJson = null;
if (!this.options) {
return console.error('You must provide object as a first argument')
}
this.init = () => {
// Generate style
this.functions.generateStyle.call(this);
this.selectors.trigger = this.options.hasOwnProperty('trigger') ? this.options.trigger : console.error('You must proved trigger element like this - \'EmojiPicker.init({trigger: "selector"})\' ');
this.selectors.search = '.fg-emoji-picker-search input';
this.selectors.emojiContainer = '.fg-emoji-picker-grid'
this.emojiItems = undefined;
this.variable.emit = this.options.emit || null;
this.variable.removeOnSelection = this.options.removeOnSelection || false;
this.variable.closeButton = this.options.closeButton || true;
this.variable.position = this.options.position || null;
this.variable.dir = this.options.dir || '';
this.insertInto = this.options.insertInto || undefined;
if (!this.selectors.trigger) return;
this.bindEvents();
this.options && this.options.preFetch && this.functions.fetchEmojiData();
}
this.lib = (el = undefined) => {
return {
el: document.querySelectorAll(el),
on(event, callback, classList = undefined) {
if (!classList) {
this.el.forEach(item => {
item.addEventListener(event, callback.bind(item))
})
} else {
this.el.forEach(item => {
item.addEventListener(event, (e) => {
if (e.target.closest(classList)) {
callback.call(e.target.closest(classList), e)
}
})
})
}
}
}
},
this.variable = {
position: null,
dir: '',
categoryIcons: {
'smileys--people': ' ',
'animals--nature': ' ',
'travel--places': ' ',
'activities': ' ',
'objects': ' ',
'symbols': ' ',
'flags': ' ',
'search': ' ',
'close': ' ',
}
}
this.selectors = {
emit: null,
trigger: null,
emojiPicker: '.fg-emoji-picker',
emojiFooter: '.fg-emoji-picker-footer',
emojiBody: '.fg-emoji-picker-all-categories',
emojiHeader: '.fg-emoji-picker-categories',
closeButton: '.fg-emoji-picker-close-button'
}
this.bindEvents = () => {
this.lib('body').on('click', this.functions.removeEmojiPicker.bind(this));
this.lib('body').on('click', this.functions.emitEmoji.bind(this));
this.lib('body').on('click', this.functions.openEmojiSelector.bind(this), this.selectors.trigger);
this.lib('body').on('input', this.functions.search.bind(this), this.selectors.search);
// this.lib('body').on('keydown', this.functions.removeEmojiPicker.bind(this));
this.lib('body').on('click', this.destroy.bind(this), this.selectors.closeButton);
window.addEventListener('keydown', e => {
if (e.keyCode === 27) {
if (document.querySelector(this.selectors.emojiPicker)) {
document.querySelectorAll(this.selectors.emojiPicker).forEach(emoji => emoji.remove())
this.emojiItems = undefined
}
}
})
}
this.html = {
pickerBody: () => {
return `
`;
}
}
this.destroy = () => {
document.querySelectorAll(this.selectors.emojiPicker).forEach(p => p.remove())
this.emojiItems = undefined;
return true;
}
setCaretPosition = (field, caretPos) => {
var elem = field
if (elem != null) {
if (elem.createTextRange) {
var range = elem.createTextRange();
range.move('character', caretPos);
range.select();
} else {
if (elem.selectionStart) {
elem.focus();
elem.setSelectionRange(caretPos, caretPos);
} else {
elem.focus();
}
}
}
}
this.functions = {
// Put in place
putInPlace(myField, myValue) {
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = myValue;
} else if (myField.selectionStart || myField.selectionStart == "0") {
const startPos = myField.selectionStart;
const endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos) + myValue + myField.value.substring(endPos, myField.value.length);
setCaretPosition(myField, startPos + 2)
} else {
myField.value += myValue;
myField.focus()
}
},
// Search
search(e) {
const val = e.target.value;
if (!Array.isArray(this.emojiItems)) {
this.emojiItems = Array.from(e.target.closest(this.selectors.emojiPicker).querySelectorAll(`${this.selectors.emojiBody} li`));
}
this.emojiItems.filter(emoji => {
if (!emoji.getAttribute('data-name').match(val)) {
emoji.style.display = 'none'
} else {
emoji.style.display = ''
}
})
if (!val.length) this.emojiItems = undefined;
},
fetchEmojiData: () => {
fetch(`${this.variable.dir}full-emoji-list.json`)
.then(response => response.json())
.then(object => { this.emojiJson = object });
},
generateStyle() {
document.head.insertAdjacentHTML('beforeend', `
`)
},
removeEmojiPicker() {
const el = window.event.target;
const picker = document.querySelector(this.selectors.emojiPicker);
if (!el.closest(this.selectors.emojiPicker)) picker ? picker.remove() : false;
this.emojiItems = undefined
},
emitEmoji(e) {
const el = e.target;
if (el.tagName.toLowerCase() == 'a' && el.className.includes('fg-emoji-picker-item')) {
e.preventDefault();
let emoji = {
emoji: el.getAttribute('href'),
code: el.getAttribute('data-code')
}
if (this.variable.emit) this.variable.emit(emoji, this.triggerer)
// If insert into option exists
if (this.insertInto) this.functions.putInPlace(this.insertInto, emoji.emoji)
if (this.variable.removeOnSelection) {
const picker = document.querySelector(this.selectors.emojiPicker);
picker.remove();
}
}
},
// Open omoji picker
openEmojiSelector(e) {
let el = e.target.closest(this.selectors.trigger)
if (el) {
e.preventDefault();
// Bounding rect
// Trigger position and (trigger) sizes
let el = e.target.closest(this.selectors.trigger)
if (typeof this.variable.emit === 'function') this.triggerer = el
// Insert picker
const emojiPickerMain = new DOMParser().parseFromString(this.html.pickerBody.apply(this), 'text/html').body.firstElementChild;
document.body.insertAdjacentElement('afterbegin', emojiPickerMain);
let positions = {
buttonTop: e.pageY,
buttonWidth: el.offsetWidth,
buttonFromLeft: el.getBoundingClientRect().left,
bodyHeight: document.body.offsetHeight,
bodyWidth: document.body.offsetWidth,
windowScrollPosition: window.pageYOffset,
emojiHeight: emojiPickerMain.offsetHeight,
emojiWidth: emojiPickerMain.offsetWidth,
}
// Element position object
let position = {
top: emojiPickerMain.style.top = positions.buttonTop - positions.emojiHeight,
left: emojiPickerMain.style.left = positions.buttonFromLeft - positions.emojiWidth,
bottom: emojiPickerMain.style.top = positions.buttonTop,
right: emojiPickerMain.style.left = positions.buttonFromLeft + positions.buttonWidth
}
// Positioning emoji container top
if (this.variable.position) {
this.variable.position.forEach(elemPos => {
if (elemPos === 'right') {
emojiPickerMain.style.left = position[elemPos] + 'px';
} else if (elemPos === 'bottom') {
emojiPickerMain.style.top = position[elemPos] + 'px';
} else {
emojiPickerMain.style[elemPos] = position[elemPos] + 'px';
}
})
}
// Emoji Picker Promise
this.emojiPicker().then(emojiPicker => {
// Create node from
const node = new DOMParser().parseFromString(emojiPicker, 'text/html').body.firstElementChild;
emojiPickerMain.innerHTML = node.innerHTML;
const emojiFooter = emojiPickerMain.querySelector(this.selectors.emojiFooter);
const emojiBody = emojiPickerMain.querySelector(this.selectors.emojiBody)
const emojiPickerHeader = emojiPickerMain.querySelector(this.selectors.emojiHeader);
emojiPickerMain.querySelector('input').focus();
// Add event listener on click
document.body.querySelector(this.selectors.emojiPicker).onclick = function (e) {
e.preventDefault();
let scrollTo = (element, to, duration = 100) => {
if (duration <= 0) return;
var difference = to - element.scrollTop;
var perTick = difference / duration * 10;
setTimeout(function () {
element.scrollTop = element.scrollTop + perTick;
if (element.scrollTop === to) return;
scrollTo(element, to, duration - 10);
}, 10);
}
const el = e.target;
const filterLlnk = el.closest('a');
document.querySelectorAll('.fg-emoji-picker-categories li').forEach(item => item.classList.remove('active'))
if (filterLlnk && filterLlnk.closest('li') && filterLlnk.closest('li').getAttribute('data-index')) {
let list = filterLlnk.closest('li');
list.classList.add('active');
let listIndex = list.getAttribute('data-index');
scrollTo(emojiBody, emojiBody.querySelector(`#${listIndex}`).offsetTop - emojiPickerHeader.offsetHeight);
}
}
})
}
}
},
// Create emoji container / Builder engine
this.emojiPicker = () => {
let picker = `
`;
const closeBtn = this.variable.closeButton ? `${this.variable.categoryIcons.close} ` : '';
let categories = ``;
let categoriesInner = ``;
let outerUl = `%outerUL%
`;
let innerLists = ``;
let fetchData = null;
if (this.emojiJson) {
fetchData = new Promise((resolve, reject) => {
let index = 0;
// Loop through emoji object
let object = this.emojiJson;
for (const key in object) {
if (object.hasOwnProperty(key)) {
// Index count
index += 1;
let keyToId = key.split(' ').join('-').split('&').join('').toLowerCase();
const categories = object[key];
categoriesInner += `${this.variable.categoryIcons[keyToId]} `
innerLists += `
`;
}
}
let allSmiles = outerUl.replace('%outerUL%', innerLists)
let cats = categories.replace('%categories%', categoriesInner);
let pickerContainer = picker.replace('%pickerContainer%', allSmiles)
let data = pickerContainer.replace('%categories%', cats);
resolve(data);
});
} else {
fetchData = fetch(`${this.variable.dir}full-emoji-list.json`)
.then(response => response.json())
.then(object => {
// Index count
let index = 0;
this.emojiJson = object;
// Loop through emoji object
for (const key in object) {
if (object.hasOwnProperty(key)) {
// Index count
index += 1;
let keyToId = key.split(' ').join('-').split('&').join('').toLowerCase();
const categories = object[key];
categoriesInner += `${this.variable.categoryIcons[keyToId]} `
innerLists += `
`;
}
}
let allSmiles = outerUl.replace('%outerUL%', innerLists)
let cats = categories.replace('%categories%', categoriesInner);
let pickerContainer = picker.replace('%pickerContainer%', allSmiles)
let data = pickerContainer.replace('%categories%', cats);
return data;
})
}
return fetchData;
}
this.init();
}