So, I was making a platform for a suggestion channel. The idea was that there is a voting, and when it reaches a such cap, it auto accepts or rejects. I was also making staff can approve or reject. Well, i had a problem in this.
An image of this platform (I censored out the nicknames):
The image
When it reaches the approve cap (10 white check marks), it is supposed to delete the embed and post a new one meaning that the suggestion is approved.
Reproducible sample of code:
client.on("messageReactionAdd", function (messageReaction, member) {
if (member.bot) return;
if (messageReaction.message.channel.id == "516263179446124555") {
if (messageReaction.emoji.name == "โ
") {
if (messageReaction.count >= 10) {
messageReaction.message.channel.send("", {embed: {
title: "Suggestion Approved",
description: messageReaction.message.embeds[0].title + "\n" + messageReaction.message.embeds[0].description,
author: {
name: messageReaction.message.embeds[0].author.name,
icon_url: messageReaction.message.embeds[0].author.iconURL
},
color: 3394611,
footer: {
text: "Why: The message got 10 โ
reactions."
}
}})
}
messageReaction.message.delete();
}
if (messageReaction.emoji.name == "516258169035554817") {
if (messageReaction.count >= 8) {
messageReaction.message.channel.send("", {embed: {
title: "Suggestion sent to Staff",
description: messageReaction.message.embeds[0].title + "\n" + messageReaction.message.embeds[0].description,
author: {
name: messageReaction.message.embeds[0].author.name,
icon_url: messageReaction.message.embeds[0].author.iconURL
},
color: 16764006,
footer: {
text: "Why: The message got 8 <:neutral:516258169035554817> reactions."
}
}})
}
messageReaction.message.guild.channels.get("517331518843125760").send("", {embed: {
title: "Suggestion to check",
description: messageReaction.message.embeds[0].title + "\n" + messageReaction.message.embeds[0].description,
author: {
name: messageReaction.message.embeds[0].author.name,
icon_url: messageReaction.message.embeds[0].author.iconURL
},
color: 16764006,
footer: {
text: "Approving/disapproving this won't change the embed in <#516263179446124555>."
}
}})
messageReaction.message.delete();
}
if (messageReaction.emoji.name == "516258587845328906") {
if (messageReaction.count >= 7) {
messageReaction.message.channel.send("", {embed: {
title: "Suggestion Rejected",
description: messageReaction.message.embeds[0].title + "\n" + messageReaction.message.embeds[0].description,
author: {
name: messageReaction.message.embeds[0].author.name,
icon_url: messageReaction.message.embeds[0].author.iconURL
},
color: 16724736,
footer: {
text: "Why: The message got 7 <:bad:516258587845328906> reactions."
}
}})
}
}
if (messageReaction.emoji.name == "โ") {
var staffMemberReacted = false;
messageReaction.message.guild.members.forEach(function(GuildMember) {
if (messageReaction.users.keyArray().includes(GuildMember.user) && (GuildMember.roles.has("501752627709870080") || GuildMember.roles.has("493436150019784704"))) {
staffMemberReacted = true;
}
})
console.log("reached manapprove")
if (staffMemberReacted) {
messageReaction.message.channel.send("", {embed: {
title: "Suggestion Approved",
description: messageReaction.message.embeds[0].title + "\n" + messageReaction.message.embeds[0].description,
author: {
name: messageReaction.message.embeds[0].author.name,
icon_url: messageReaction.message.embeds[0].author.iconURL
},
color: 3394611,
footer: {
text: "Why: A owner or co-owner manually approved it."
}
}})
messageReaction.message.delete();
}
}
if (messageReaction.emoji.name == "517327626373824522") {
var staffMemberReacted = false;
messageReaction.message.guild.members.forEach(function(GuildMember) {
if (messageReaction.users.keyArray().includes(GuildMember.user) && (GuildMember.id || GuildMember.roles.find)) {
staffMemberReacted = true;
}
})
if (staffMemberReacted) {
messageReaction.message.channel.send("", {embed: {
title: "Suggestion Rejected",
description: messageReaction.message.embeds[0].title + "\n" + messageReaction.message.embeds[0].description,
author: {
name: messageReaction.message.embeds[0].author.name,
icon_url: messageReaction.message.embeds[0].author.iconURL
},
color: 16724736,
footer: {
text: "Why: A owner or co-owner manually rejected it."
}
}})
messageReaction.message.delete();
}
}
}
})
When adding a console.log of it reaching, heroku logs don't put out anything. I was looking for the problem for over a hour.
Please never do this.
Discord.js has a guide on what to do in this situation https://discordjs.guide/#/popular-topics/reactions?id=awaiting-reactions
I would follow their example and use message.awaitReactions To sum it up, use the filter to set the possible reactions you will consider, makes everything much easier.
This is from that link.
message.react('๐').then(() => message.react('๐'));
const filter = (reaction, user) => {
return ['๐', '๐'].includes(reaction.emoji.name) && user.id === message.author.id;
};
message.awaitReactions(filter, { max: 1, time: 60000, errors: ['time'] })
.then(collected => {
const reaction = collected.first();
if (reaction.emoji.name === '๐') {
message.reply('you reacted with a thumbs up.');
}
else {
message.reply('you reacted with a thumbs down.');
}
})
.catch(collected => {
console.log(`After a minute, only ${collected.size} out of 4 reacted.`);
message.reply('you didn\'t react with neither a thumbs up, nor a thumbs down.');
});
Related
I've been working to create a Purge Command in JavaScript based off this Purge Command written in TypeScript for reconlx's DJS v13 command handler (I modified a few bits to make all the handlers and whatnot function in DJS v14), however, there are some bugs and a few things I'm losing my mind over.
Firstly, everytime the command is enacted, this little nuisance of a message appears with the success confirmation embeds, and I don't really know how to deal with it.
Secondly, when I purge specifically by user and term, the wrong embed is sent and my bot will purge as normal. According to the source code, it (referring to my bot) should be sending the cleanPurge embed, but it instead sends the userPurge embed. Purging specifically by user works as intended, just with the annoying "thinking" message as an unwanted bonus.
Thirdly, when purging by term, I can only purge if I enact the command right after a message with the specified term. Here's an example (if you don't understand my wording).
// It is okay to purge during these situations
// cat in a hat
// cat in a hat
// cat in a hat
// /purge [amount: 3] {term: cat}
// The command will purge only if it is enacted right after the message with the specific term, however...
// The command won't purge during these situations
// cat in a hat
// cat in a hat
// cat in a hat
// SHITPOST
// /purge [amount: 3] {term: cat}
// The message "SHITPOST" is in-between the last message with the term "cat" and when I enact my command,
// therefore, for some reason, I can't purge any messages with my specified keyword before the message "SHITPOST".
// I can purge messages with the term "cat" AFTER the message "SHITPOST" until another message gets in the way.
// If you have no idea what I mean, a link to my bot's demo is below.
Purging without specifics DOESN'T bring up the "{Bot} is thinking..." message and works as intended.
If you're confused, here's a link to a demo of my bot's bugs
Any questions, please feel free to ask me, and sorry if my wording is shit.
Source code for purge.js
const { EmbedBuilder, ApplicationCommandOptionType, PermissionsBitField } = require("discord.js");
const { Command } = require("reconlx");
const ms = require("ms");
module.exports = new Command({
name: "purge",
description: "Deletes a specified number of messages",
userPermissions: PermissionsBitField.Flags.ManageMessages,
options: [
{
name: "amount",
description: "Number of messages to purge",
type: ApplicationCommandOptionType.Integer,
min_value: 1,
max_value: 100,
required: true,
},
{
name: "user",
description: "Purge this member's messages",
type: ApplicationCommandOptionType.User,
required: false,
},
{
name: "term",
description: "Purge messages with this specific term",
type: ApplicationCommandOptionType.String,
required: false,
},
{
name: "reason",
description: "Reason for purging",
type: ApplicationCommandOptionType.String,
required: false,
},
],
run: async ({ interaction }) => {
const amount = interaction.options.getInteger("amount");
const user = interaction.options.getUser("user");
const term = interaction.options.getString("term");
const reason = interaction.options.getString("reason") || "Unspecified";
const channel = interaction.channel
const messages = await interaction.channel.messages.fetch({
limit: amount + 1,
});
if (user) {
const userMessages = messages.filter((m) => m.author.id === user.id);
const purged = await interaction.channel.bulkDelete(userMessages, true);
const userPurge = new EmbedBuilder()
.setColor("#2F3136")
.setTitle(`๐งน PURGED!`)
.addFields(
{
name: `Cleared ${purged.size} message${
purged.size > 1 ? "s" : ""
}!`,
value: `Any messages from \`${user.tag}\` have been deleted from ${channel}!`
},
{
name: `User`,
value: `\`${user.tag}\``,
inline: true
},
{
name: `Term`,
value: `\`Unspecified\``,
inline: true
},
{
name: `Reason(s)`,
value: `\`${reason}\``,
inline: true
},
)
interaction.channel.send({ embeds: [userPurge] });
} else if (term) {
const filteredTerm = messages.filter((m) =>
m.content.toLowerCase().includes(term)
);
const purged = await interaction.channel.bulkDelete(filteredTerm, true);
const termPurge = new EmbedBuilder()
.setColor("#2F3136")
.setTitle(`๐งน PURGED!`)
.addFields(
{
name: `Cleared ${purged.size} message${
purged.size > 1 ? "s" : ""
}!`,
value: `Any messages with the term ||\`${term}\`|| have been deleted from ${channel}!`
},
{
name: `User`,
value: `\`Unspecified\``,
inline: true
},
{
name: `Term`,
value: `||\`${term}\`||`,
inline: true
},
{
name: `Reason(s)`,
value: `\`${reason}\``,
inline: true
},
)
interaction.channel.send({ embeds: [termPurge] });
} else if (term && user) {
const memberMessages = messages.filter((m) => m.author.id === member.id && m.content.toLowerCase().includes(term));
const purged = await interaction.channel.bulkDelete(memberMessages, true);
const cleanPurge = new EmbedBuilder()
.setColor("#2F3136")
.setTitle(`๐งน PURGED!`)
.addFields(
{
name: `Cleared ${purged.size} message${
purged.size > 1 ? "s" : ""
}!`,
value: `Any messages from \`${user.tag}\` with the term ||\`${term}\`|| have been deleted from ${channel}!`
},
{
name: `User`,
value: `\`${user.tag}\``,
inline: true
},
{
name: `Term`,
value: `||\`${term}\`||`,
inline: true
},
{
name: `Reason(s)`,
value: `\`${reason}\``,
inline: true
},
)
interaction.channel.send({ embeds: [cleanPurge] });
} else {
const purged = await interaction.channel.bulkDelete(messages, true);
const purge = new EmbedBuilder()
.setColor("#2F3136")
.setTitle(`๐งน PURGED!`)
.addFields(
{
name: `Cleared ${purged.size - 1} message${
purged.size > 1 ? "s" : ""
}!`,
value: `The specified number of messages have been deleted from ${channel}!`
},
{
name: `User`,
value: `\`Unspecified\``,
inline: true
},
{
name: `Term`,
value: `\`Unspecified\``,
inline: true
},
{
name: `Reason(s)`,
value: `\`Unspecified\``,
inline: true
},
)
interaction.channel.send({ embeds: [purge] });
}
},
});
Your video is still processing, so I couldn't watch it, but:
In the "cat"/"cat"/"cat"/"shitpost" situation, you'd need amount=4, because you're only fetching N + 1 messages. If you need want your bot to purge N last matching messages even if there are more in between (e.g. in a "a"/"b"/"a"/"b"/"a"/"b" situation where you want to purge all "b"s), you'd need to fetch more messages until you've purged enough. Another "good enough" option might be to fetch e.g. amount * 2 messages and hope it's enough.
In any case, you could start by de-quadriplicating your message filtering and embed building:
{
run: async ({ interaction }) => {
const { options, channel } = interaction;
const amount = options.getInteger("amount");
const user = options.getUser("user");
const term = options.getString("term");
const reason = options.getString("reason") || "Unspecified";
const lastNMessages = await channel.messages.fetch({
limit: amount + 1,
});
const filteredMessages = lastNMessages.filter((message) => {
if (user && message.author.id !== user.id) {
return false;
}
if (term && !message.content.toLowerCase().includes(term)) {
return false;
}
return true;
});
const purged = await channel.bulkDelete(filteredMessages, true);
const title = `Cleared ${purged.size} message${purged.size > 1 ? "s" : ""}!`;
const message = [
`${purged.size} messages`,
user ? ` from \`${user.tag}\`` : "",
term ? ` containing \`${term}\`` : "",
` have been deleted from ${channel}!`,
].join("");
const userPurge = new EmbedBuilder()
.setColor("#2F3136")
.setTitle(`๐งน PURGED!`)
.addFields(
{
name: title,
value: message,
},
{
name: `User`,
value: user ? `\`${user.tag}\`` : "Unspecified",
inline: true,
},
{
name: `Term`,
value: term ? `||\`${term}\`||` : `\`Unspecified\``,
inline: true,
},
{
name: `Reason(s)`,
value: `\`${reason}\``,
inline: true,
},
);
await channel.send({ embeds: [userPurge] });
},
}
I'm trying to make a Discord.js bot that will get data from the CS:GO server and shows it in embed that will be edited and updated (for example every 2 minutes). I still have some problems with the code, currently I stuck with the error "TypeError: Cannot read property 'edit' of undefined"
const Discord = require("discord.js");
var SourceQuery = require('sourcequery');
const client = new Discord.Client();
const config = require("./config.json");
client.on("ready", () => {
console.log(`bot is ready`);
});
client.on("ready", (message, embed) => {
function queryserver(ip, port, sendChannel) {
let sq = new SourceQuery(1000);
console.log(ip + " " + port);
sq.open(ip, port);
sq.getInfo(function (err, info) {
if (!err) {
sq.getPlayers(function (err, players) {
if (!err) {
console.log(sq.address);
var counter = 0;
playersname = "";
for (i = 0; i < players.length; i++) {
playersname = playersname + players[i].name + "\n";
if (counter == players.length - 1) {
client.channels.cache.get(sendChannel).send({
embed: {
color: 3066993,
fields: [{
name: "Server name",
value: "**" + info.name + "**"
},
{
name: "IP",
value: ip + ":" + port,
"inline": true
},
{
name: "Mapa",
value: info.map,
"inline": true
},
{
name: "Max slots",
value: info.maxplayers,
"inline": true
},
{
name: "Online",
value: info.players,
"inline": true
},
{
name: "Player list",
value: "```" + playersname + "```"
}
],
timestamp: new Date(),
footer: {
icon_url: client.user.avatarURL,
text: "Last update:"
}
}
});
}
counter++;
}
}
else {
console.log("Error...");
message.channel.send("Error...");
}
});
}
else {
console.log("Error...");
message.channel.send("Error...");
}
});
};
setInterval(function() {
queryserver(`216.52.148.47`, `27015`, `834355545879347233`);
message.edit(embed)
}, 2000);
});
client.login(config.token);
Can anyone help me?
My join.js Code is Below :-
I want the bot to auto self deafen when ever it joins any Voice Channel.
module.exports = {
name: "join",
description: "Joins your voice channel",
category: "music",
execute(message, args) {
if (message.member.voice.channel) {
message.member.voice.channel.join().then((connections) => {
message.channel.send({
embed: {
color: message.client.messageEmbedData.color,
author: {
name: "โ๏ธ Hey, i joined your voice channel",
},
timestamp: new Date(),
},
});
});
} else {
message.channel.send({
embed: {
color: message.client.messageEmbedData.color,
author: {
name: "โ You are not in a voice channel",
},
timestamp: new Date(),
},
});
}
},
};
I tried many way but can't implement .setSelfDeaf(true) .
Someone please assist?
You can use VoiceState#setSelfDeaf to achieve that. It's pretty easy to do so.
if (message.guild.me.voice.channel) { // Checking if the bot is in a VoiceChannel.
message.guild.me.voice.setSelfDeaf(true); // Using setSelfDeaf to self-deafen the bot.
};
module.exports = {
name: "join",
description: "Joins your voice channel",
category: "music",
execute(message, args) {
if (message.member.voice.channel) {
message.member.voice.channel.join().then((connections) => {
message.guild.me.voice.setSelfDeaf(true);
message.channel.send({
embed: {
color: message.client.messageEmbedData.color,
author: {
name: "โ๏ธ Hey, i joined your voice channel",
},
timestamp: new Date(),
},
});
});
} else {
message.channel.send({
embed: {
color: message.client.messageEmbedData.color,
author: {
name: "โ You are not in a voice channel",
},
timestamp: new Date(),
},
});
}
},
};
So I basically want to create a countdown for my discord ticket bot. If someone types in -close[...] the channel will be deleted after 10 seconds. But if the person who did this command types something in the channel the countdown will stop and the channel won't be deleted.
That works fine so far. But if I abort the countdown on every other message that I send to the channel the embed will be sent where it says "Countdown stopped" also if I type -close [...] again this message pops up but the channel will still be deleted after 10 seconds.
function closeTicket (_ticketid, channel, deleter, reason) {
var timer = setTimeout(function() {
channel.delete();
}, 10000);
channel.send({embed: {
color: 3447003,
author: {
name: client.user.username,
icon_url: client.user.avatarURL
},
title: ``,
description: "This ticket will close in 10 seconds. If this was a mistake type anything to stop the timer.",
fields: [{
name: "Thank you!",
value: "Thank you for using our ticket system! Good luck and have fun playing on our servers."
},
],
timestamp: new Date(),
footer: {
icon_url: client.user.avatarURL,
text: "Support Ticket System ยฉ H4rry#6701"
}
}
});
logTicketClosed(_ticketid, deleter, reason);
client.on('message', message => {
if(message.channel === channel && message.author === deleter && timer != null) {
clearTimeout(timer);
timer = null;
message.channel.send({embed: {
color: 3447003,
author: {
name: client.user.username,
icon_url: client.user.avatarURL
},
title: `Timer Stopped`,
description: "The timer has been stopped, the ticket will remain open.",
timestamp: new Date(),
footer: {
icon_url: client.user.avatarURL,
text: "Support Ticket System ยฉ H4rry#6701"
}
}});
}
});
return 0;
}
I got it to work now! I defined a new variable called timer_running which would be set to true when the timer starts and to false when it stops. That way I got it to work now.
function closeTicket (_ticketid, channel, deleter, reason) {
var timer_running = false;
var timer = setTimeout(function() {
channel.delete();
}, 10000);
timer_running = true;
channel.send({embed: {
color: 3447003,
author: {
name: client.user.username,
icon_url: client.user.avatarURL
},
title: ``,
description: "This ticket will close in 10 seconds. If this was a mistake type anything to stop the timer.",
fields: [{
name: "Thank you!",
value: "Thank you for using our ticket system! Good luck and have fun playing on our servers."
},
],
timestamp: new Date(),
footer: {
icon_url: client.user.avatarURL,
text: "Support Ticket System ยฉ H4rry#6701"
}
}
});
logTicketClosed(_ticketid, deleter, reason);
client.on('message', message => {
if(message.channel === channel && message.author === deleter && timer_running === true) {
clearTimeout(timer);
timer_running = false;
message.channel.send({embed: {
color: 3447003,
author: {
name: client.user.username,
icon_url: client.user.avatarURL
},
title: `Timer Stopped`,
description: "The timer has been stopped, the ticket will remain open.",
timestamp: new Date(),
footer: {
icon_url: client.user.avatarURL,
text: "Support Ticket System ยฉ H4rry#6701"
}
}});
}
});
}
I have this code that works perfect except for one thing. My names that need to be inputted have spaces that the code will not recognize. I've tried " " but its not working.
For example, when I type in the Additional Purchase in the input box it works great until I get to the "space" then my description disappears?
Any help would be greatly appreciated!
var data = [
{ // Reject Title
name: 'Additional Purchase',
// Reject Reason(s) w/Links
description:
'<div>MASTER ACOUNT STATUS IS CLOSED </div>'
},
{
name: 'Address Change', // Reject Title
description: // Reject Reasons with Links
'The Other Writer'
},
{
name: 'Asset Transfer', // Reject Title
description: 'The CodeIgniter Writer'
},
{
name: 'Banking and Options', // Reject Title
description: 'Made Up Person #1'
},
{
name: 'Beneficiary',
description: 'Another Made Up Person'
},
{
name: 'Close Account',
description: 'That Guy'
},
{
name: 'CWR',
description: 'That Guy'
},
{
name: 'Date of Birth',
description: 'That Guy'
},
{
name: 'DividendCapital Gains Maintenance',
description: 'That Guy'
},
{
name: 'Domestic Bank on File',
description: 'That Guy'
},
{
name: 'Duplicate Statements',
description: 'That Guy'
},
{
name: 'Full Transfer Residual Followup', // Reject Title
description: // Reject Reasons with Links
'The Other Writer'
},
{
name: 'Legal Document', // Reject Title
description: 'The CodeIgniter Writer'
},
{
name: 'Maintain Brokerage Account Link', // Reject Title
description: 'Made Up Person #1'
},
{
name: 'Maintain Control Person Client Information',
description: 'Another Made Up Person'
},
{
name: 'Maintain Mutual Fund Exch/Red Options',
description: 'That Guy'
},
{
name: 'Maintain SBS PLan',
description: 'That Guy'
},
{
name: 'Merge/Expand Rule Failure in Batch 1',
description: 'That Guy'
},
{
name: 'Micro Deposit Reject',
description: 'That Guy'
},
{
name: 'Money Movement',
description: 'That Guy'
},
{
name: 'Mutual Fund Freezes',
description: 'That Guy'
}
];
// Suggest section holder
var $suggestedHL = $('.suggest-holder');
// Suggestions UL
var $suggestedUL = $('ul', $suggestedHL);
// Suggestions LI
var $suggestedLI = $('li', $suggestedHL);
// Selected Items UL
var $selectedUL = $('#selected-suggestions');
// Keyboard Nav Index
var index = -1;
// Add a suggestion to the selected holder
function addSuggestion(el){
$selectedUL.append($('<li>' + el.find('.suggest-description').html() + '</li>'));
}
$('input', $suggestedHL).on({
keyup: function(e){
var m = false;
if(e.which == 38){
// Down arrow - Check that we've not tried to select before the first item
if(--index < 0){
index = 0;
}
// Set a variable to show that we've done some keyboard navigation
m = true;
}else if(e.which == 40){
// Up arrow - Check that index is not beyond the last item
if(++index > $suggestedLI.length - 1){
index = $suggestedLI.length-1;
}
// Set a variable to show that we've done some keyboard navigation
m = true;
}
// Check we've done keyboard navigation
if(m){
// Remove the active class
$('li.active', $suggestedHL).removeClass('active');
$suggestedLI.eq(index).addClass('active');
}else if(e.which == 27){
index = -1;
// Esc key
$suggestedUL.hide();
}else if(e.which == 13){
// Enter key
if(index > -1){
addSuggestion($('li.active', $suggestedHL));
index = -1;
$('li.active', $suggestedHL).removeClass('active');
}
}else{
index = -1;
// Clear the ul
$suggestedUL.empty();
// Cache the search term
$search = $(this).val();
// Search regular expression
$search = new RegExp($search.replace(/[^0-9a-z_]/i), 'i');
// Loop through the array
for(var i in data){
if(data[i].name.match($search)){
$suggestedUL.append($("<li><span class='suggest-name'>" + data[i].name + "</span><span class='suggest-description'>" + data[i].description + "</span></li>"));
}
}
// Show the ul
$suggestedUL.show();
}
if($(this).val() == ''){
$suggestedUL.hide();
}
},
keydown: function(e){
if(e.which == 38 || e.which == 40 || e.which == 13){
e.preventDefault();
}
},
focus: function(e){
if($(this).val() != ''){
$suggestedUL.show();
}
}
});
$suggestedHL.on('click', 'li', function(e){
addSuggestion($(this));
});
$('body').on('click', function(e) {
if (!$(e.target).closest('.suggest-holder li, .suggest-holder input').length) {
$suggestedUL.hide();
};
});
</script>
This will be solved if you add a space character (without quotes) to your regular expression. So turn this line:
$search = new RegExp($search.replace(/[^0-9a-z_]/i), 'i');
Into this:
$search = new RegExp($search.replace(/[^0-9a-z_ ]/i), 'i');
I've put together a quick test:
var data = [
{ // Reject Title
name: 'Additional Purchase',
// Reject Reason(s) w/Links
description: '<div>MASTER ACOUNT STATUS IS CLOSED </div>'
},
{
name: 'Address Change', // Reject Title
description: // Reject Reasons with Links
'The Other Writer'
},
{
name: 'Asset Transfer', // Reject Title
description: 'The CodeIgniter Writer'
},
{
name: 'Banking and Options', // Reject Title
description: 'Made Up Person #1'
},
{
name: 'Beneficiary',
description: 'Another Made Up Person'
},
{
name: 'Close Account',
description: 'That Guy'
},
{
name: 'CWR',
description: 'That Guy'
},
{
name: 'Date of Birth',
description: 'That Guy'
},
{
name: 'DividendCapital Gains Maintenance',
description: 'That Guy'
},
{
name: 'Domestic Bank on File',
description: 'That Guy'
},
{
name: 'Duplicate Statements',
description: 'That Guy'
},
{
name: 'Full Transfer Residual Followup', // Reject Title
description: // Reject Reasons with Links
'The Other Writer'
},
{
name: 'Legal Document', // Reject Title
description: 'The CodeIgniter Writer'
},
{
name: 'Maintain Brokerage Account Link', // Reject Title
description: 'Made Up Person #1'
},
{
name: 'Maintain Control Person Client Information',
description: 'Another Made Up Person'
},
{
name: 'Maintain Mutual Fund Exch/Red Options',
description: 'That Guy'
},
{
name: 'Maintain SBS PLan',
description: 'That Guy'
},
{
name: 'Merge/Expand Rule Failure in Batch 1',
description: 'That Guy'
},
{
name: 'Micro Deposit Reject',
description: 'That Guy'
},
{
name: 'Money Movement',
description: 'That Guy'
},
{
name: 'Mutual Fund Freezes',
description: 'That Guy'
}
];
// Suggest section holder
var $suggestedHL = $('.suggest-holder');
// Suggestions UL
var $suggestedUL = $('ul', $suggestedHL);
// Suggestions LI
var $suggestedLI = $('li', $suggestedHL);
// Selected Items UL
var $selectedUL = $('#selected-suggestions');
// Keyboard Nav Index
var index = -1;
// Add a suggestion to the selected holder
function addSuggestion(el) {
$selectedUL.append($('<li>' + el.find('.suggest-description').html() + '</li>'));
}
$('input', $suggestedHL).on({
keyup: function(e) {
var m = false;
if (e.which == 38) {
// Down arrow - Check that we've not tried to select before the first item
if (--index < 0) {
index = 0;
}
// Set a variable to show that we've done some keyboard navigation
m = true;
} else if (e.which == 40) {
// Up arrow - Check that index is not beyond the last item
if (++index > $suggestedLI.length - 1) {
index = $suggestedLI.length - 1;
}
// Set a variable to show that we've done some keyboard navigation
m = true;
}
// Check we've done keyboard navigation
if (m) {
// Remove the active class
$('li.active', $suggestedHL).removeClass('active');
$suggestedLI.eq(index).addClass('active');
} else if (e.which == 27) {
index = -1;
// Esc key
$suggestedUL.hide();
} else if (e.which == 13) {
// Enter key
if (index > -1) {
addSuggestion($('li.active', $suggestedHL));
index = -1;
$('li.active', $suggestedHL).removeClass('active');
}
} else {
index = -1;
// Clear the ul
$suggestedUL.empty();
// Cache the search term
$search = $(this).val();
// Search regular expression
$search = new RegExp($search.replace(/[^0-9a-z_\s]/i), 'i');
// Loop through the array
for (var i in data) {
if (data[i].name.match($search)) {
$suggestedUL.append($("<li><span class='suggest-name'>" + data[i].name + "</span><span class='suggest-description'>" + data[i].description + "</span></li>"));
}
}
// Show the ul
$suggestedUL.show();
}
if ($(this).val() == '') {
$suggestedUL.hide();
}
},
keydown: function(e) {
if (e.which == 38 || e.which == 40 || e.which == 13) {
e.preventDefault();
}
},
focus: function(e) {
if ($(this).val() != '') {
$suggestedUL.show();
}
}
});
$suggestedHL.on('click', 'li', function(e) {
addSuggestion($(this));
});
$('body').on('click', function(e) {
if (!$(e.target).closest('.suggest-holder li, .suggest-holder input').length) {
$suggestedUL.hide();
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="suggest-holder">
<input type="text">
<ul></ul>
</div>
(Try typing something like <code>additional purchase</code> or <code>date of birth</code>)