Multiple jQuery UI mentions autocomplete on the one page - javascript

I am having trouble getting multiple mentions autocomplete to work #mention - similar to FB etc.
I have looked on SO and found some good examples on doing a basic mentions autocomplete and have based mine on one, namely:
https://stackoverflow.com/a/17048788/2981412
I am however needing to get it to work across MULTIPLE (....infinite) textareas on the one page rather than just the one (which the above answer does well).
When I add it to more than one (using class selector, rather than ID) the additional ones do not display properly.
See my basic example with dummy data in this fiddle:
http://jsfiddle.net/yv8mn/3/
The top most textarea works fine, but the others show an empty drop down autocomplete menu.
Any ideas?
The JS...
function getCaretPosition (elem) {
// Initialize
var iCaretPos = 0;
// IE Support
if (document.selection) {
// Set focus on the element
elem.focus ();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange ();
// Move selection start to 0 position
oSel.moveStart ('character', -elem.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
}
// Firefox support
else if (elem.selectionStart || elem.selectionStart == '0')
iCaretPos = elem.selectionStart;
// Return results
return (iCaretPos);
}
function setCaretPosition(elem, caretPos) {
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();
}
}
}
function getTags(term, callback) {
var data = {
tags: [
{username: 'john', count: 1}, {username: 'michael', count: 1}
]
};
callback(data);
}
$(document).ready(function() {
$(".appendedInputButton").autocomplete({
source: function(request, response) {
var term = request.term;
var pos = getCaretPosition(this.element.get(0));
var substr = term.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
if (lastIndex >= 0){
var username = substr.substr(lastIndex + 1);
if (username.length && (/^\w+$/g).test(username)){
getTags(username, function(data) {
response($.map(data.tags, function(el) {
return {
username: el.username,
count: el.count
}
}));
});
return;
}
}
response({});
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var pos = getCaretPosition(this);
var substr = this.value.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
if (lastIndex >= 0){
var prependStr = this.value.substring(0, lastIndex);
this.value = prependStr + '#' + ui.item.username + this.value.substr(pos);
setCaretPosition(this, prependStr.length + ui.item.username.length + 1);
}
return false;
}
}).data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.data("ui-autocomplete-item", item)
.append("<a>" + item.username+" ("+ item.count+")</a>")
.appendTo(ul);
};
});

The dropdown menus are actually not empty, the list items are just very, very thin. Try clicking on one and you'll see that the information is inserted into the textarea.
So it seems everything works alright, except the styling for some reason.

I found the solution to my problem.
It firstly involved removing the following part of my code from the question:
.data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.data("ui-autocomplete-item", item)
.append("<a>" + item.username+" ("+ item.count+")</a>")
.appendTo(ul);
}
and replacing it with EITHER of these 2 solutions (both fixed it):
$.each($( "ui-autocomplete" ), function(index, item) {
$(item).data("ui-autocomplete")._renderItem = function (ul, item) {
return $("<li>")
.data("ui-autocomplete-item", item)
.append("<a>" + item.username+" ("+ item.count+")</a>")
.appendTo(ul);
};
});
OR
$.ui.autocomplete.prototype._renderItem = function (ul, item) {
return $("<li>")
.data("ui-autocomplete-item", item)
.append("<a>" + item.username+" ("+ item.count+")</a>")
.appendTo(ul);
};
}
Which would then result in the following code (if using the initial solution):
function getCaretPosition (elem) {
// Initialize
var iCaretPos = 0;
// IE Support
if (document.selection) {
// Set focus on the element
elem.focus ();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange ();
// Move selection start to 0 position
oSel.moveStart ('character', -elem.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
}
// Firefox support
else if (elem.selectionStart || elem.selectionStart == '0')
iCaretPos = elem.selectionStart;
// Return results
return (iCaretPos);
}
function setCaretPosition(elem, caretPos) {
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();
}
}
}
function getTags(term, callback) {
var data = {
tags: [
{username: 'john', count: 1}, {username: 'michael', count: 1}
]
};
callback(data);
}
$(document).ready(function() {
$(".appendedInputButton").autocomplete({
source: function(request, response) {
var term = request.term;
var pos = getCaretPosition(this.element.get(0));
var substr = term.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
if (lastIndex >= 0){
var username = substr.substr(lastIndex + 1);
if (username.length && (/^\w+$/g).test(username)){
getTags(username, function(data) {
response($.map(data.tags, function(el) {
return {
username: el.username,
count: el.count
}
}));
});
return;
}
}
response({});
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var pos = getCaretPosition(this);
var substr = this.value.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
if (lastIndex >= 0){
var prependStr = this.value.substring(0, lastIndex);
this.value = prependStr + '#' + ui.item.username + this.value.substr(pos);
setCaretPosition(this, prependStr.length + ui.item.username.length + 1);
}
return false;
}
});
$.each($( "ui-autocomplete" ), function(index, item) {
$(item).data("ui-autocomplete")._renderItem = function (ul, item) {
return $("<li>")
.data("ui-autocomplete-item", item)
.append("<a>" + item.username+" ("+ item.count+")</a>")
.appendTo(ul);
};
});
});

Related

want to display duplicate ranks selected along with exactly which ranks are duplicated using javascript/ jquery

Here we added the logic to display which ranks are duplicated but I also want to display exactly which rank is duplicated. Ranks are from 0 to 18.
function validate(sdd_ejrp_form)
{
var duplicateRanksDetected = false;
var validate_rank_str = ",";
jq("\[data-dom-id=gsc-containers\]").find("\[data-dom-id=gsc-container\]").each(function (index)
{ var jqThis = jq(this);
var gsc_uri = jqThis.attr("data-gsc-data-gsc-uri");
var prof_lvl_rank = jqThis.find("select\[name='prof-lvl-rank'\]").val();
jqThis.find("\[data-dom-id=proficiency-level\]").each(function (index2)
{
var btn = jq(this);
if (btn.hasClass("active"))
{
if (validate_rank_str.indexOf("," + prof_lvl_rank + ",") != -1)
{
duplicateRanksDetected = true;
return false;
} else
{
validate_rank_str += prof_lvl_rank + ",";
}
}
});
});
if (duplicateRanksDetected == true)
{
// here I want to display exactly which rank is duplicated
alert("Duplicate ranks detected!");
return false;
}
}
}
It's easy as 3 modifications:
function validate(sdd_ejrp_form) {
var duplicateRanksDetected = []; //create an array here
var validate_rank_str = ",";
jq("\[data-dom-id=gsc-containers\]").find("\[data-dom-id=gsc-container\]").each(function(index) {
var jqThis = jq(this);
var gsc_uri = jqThis.attr("data-gsc-data-gsc-uri");
var prof_lvl_rank = jqThis.find("select\[name='prof-lvl-rank'\]").val();
jqThis.find("\[data-dom-id=proficiency-level\]").each(function(index2) {
var btn = jq(this);
if (btn.hasClass("active")) {
if (validate_rank_str.indexOf("," + prof_lvl_rank + ",") != -1) {
duplicateRanksDetected.push(prof_lvl_rank); // fill the array here
return false;
} else {
validate_rank_str += prof_lvl_rank + ",";
}
}
});
});
if (duplicateRanksDetected.length > 0) {
// here I want to display exactly which rank is duplicated
alert(
"Duplicate ranks detected: " +
duplicateRanksDetected.join(', ') // display the result here
);
return false;
}
}

Extjs 6 - grid search

I try to apply this example with another way. When I try it on console.log it seems run without error but when I do it on function(){}, it turns error all grid method is undefined such:
Uncaught TypeError: Cannot call method 'getView' of undefine.
The function:
onTextFieldChange = function () {
var grid = Ext.getCmp('grid'),
value = Ext.getCmp('gridfield'),
view = grid.getView(),
columns = grid.getColumns();
view.refresh();
grid.searchValue = value.getValue();
grid.matches = [];
grid.currentIndex = null;
if (grid.searchValue !== null) {
grid.store.each(function (record, index) {
var node = view.getNode(record),
count = 0;
if (node) {
Ext.Array.forEach(columns, function (column) {
var cell = Ext.fly(node).down(column.getCellInnerSelector(), true),
matches,
cellHTML,
seen;
if (cell) {
matches = cell.innerHTML.match(grid.tagsRe);
cellHTML = cell.innerHTML.replace(grid.tagsRe, grid.tagsProtect);
cellHTML = cellHTML.replace(grid.searchRegExp, function (m) {
++count;
if (!seen) {
grid.matches.push({
record: record,
column: column
});
seen = true;
}
return '<span class="' + grid.matchCls + '" style="font-weight: bold;background-color: yellow;">' + m + '</span>';
}, grid);
Ext.each(matches, function (match) {
cellHTML = cellHTML.replace(grid.tagsProtect, match);
});
// update cell html
cell.innerHTML = cellHTML;
}
});
}
});
}
};
The event:
xtype: 'textfield',
name: 'searchField',
id: 'txtfield',
hideLabel: true,
width: 200,
change: onTextFieldChange()
Any suggestions?
I answer my own question if there is none, this is onTextFieldChange() method
onTextFieldChange = function () {
var me = Ext.getCmp('grid'),
count = 0;
me.view.refresh();
me.searchValue = getSearchValue();
me.indexes = [];
me.currentIndex = null;
if (me.searchValue !== null) {
me.searchRegExp = new RegExp(me.searchValue, 'g' + (me.caseSensitive ? '' : 'i'));
me.store.each(function (record, idx) {
var td = Ext.fly(me.view.getNode(idx)).down('td'),
cell, matches, cellHTML;
while (td) {
cell = td.down('.x-grid-cell-inner');
matches = cell.dom.innerHTML.match(me.tagsRe);
cellHTML = cell.dom.innerHTML.replace(me.tagsRe, me.tagsProtect);
// populate indexes array, set currentIndex, and replace wrap matched string in a span
cellHTML = cellHTML.replace(me.searchRegExp, function (m) {
count += 1;
if (Ext.Array.indexOf(me.indexes, idx) === -1) {
me.indexes.push(idx);
}
if (me.currentIndex === null) {
me.currentIndex = idx;
}
return '<span class="' + me.matchCls + '">' + m + '</span>';
});
// restore protected tags
Ext.each(matches, function (match) {
cellHTML = cellHTML.replace(me.tagsProtect, match);
});
// update cell html
cell.dom.innerHTML = cellHTML;
td = td.next();
if (me.currentIndex !== null) {
me.getSelectionModel().select(me.currentIndex);
}
}
}, me);
}
// no results found
if (me.currentIndex === null) {
me.getSelectionModel().deselectAll();
}
};

Jquery autocomplete with multiple values from remote JSON

I want to select multiple values by using the autocomplete()-plugin. The values I do receive from a remote JSON file. So far I can get 1 value but after that it fails. My goal is to select multiple values and store the ID, so I can post the values later on.
So far I have:
<input type="text" id="featured" autocomplete="on"></input>
<input type="hidden" value="" name="artist_id">
function split( val ) {
return val.split( /,\s*/ );
}
function extractLast( term ) {
return split( term ).pop();
}
$("#featured").bind( "keydown", function( event ) {
if ( event.keyCode === $.ui.keyCode.TAB && $( this ).autocomplete( "instance" ).menu.active ) {
event.preventDefault();
}}).autocomplete({
minLength: 0,
source: function( request, response ) {
$.ajax({
url: App.APIO+'/i/search/artist?name='+request.term+'&featured=true',
dataType: "json",
success: function(data) {
var result = $.map(data.data, function(artist) {
return {value: artist.name, label: artist.name, artist_id: artist.artist_id};
});
response( result );
}
});
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var item = split( this.value );
// remove the current input
item.pop();
// add the selected item
item.push( ui.item.value );
// add placeholder to get the comma-and-space at the end
item.push( "" );
this.value = item.join( ", " );
return false;
$('[name="artist_id"]').val(ui.item.artist_id); // somehow insert the artist_id here
}
});
Right now I can only enter 1 value and select it, so that it for example displays "Lady Gaga, " but after that I cant enter anything e.g nothing happens. I can see in my network tab that it tries to load
/artist?name=ladygaga%20%b&featured=true
if I forexample type "b" after selecting the first value...
So what am I doing wrong and can someone help me out please...
//At first, Fill in the variable "sourceList" with the json data
var sourceList = $.ajax //to fetch the data
//Create a global variable for accessing the selected values
var selectedItems= [];
//Define the multiselect autocomplete textbox as below :
$("#inputID")
.bind("keydown", function(event) {// don't navigate away from the field on tab when selecting an item
if (event.keyCode === $.ui.keyCode.TAB && $(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
})
.bind("blur", function(event) {
//var $autoCompleteInstance = $(this).autocomplete("instance");
var selectedObjects = [], currentText = $(this).html();
//if ($autoCompleteInstance.term !== undefined && $autoCompleteInstance.term !== "") {
if (currentText !== undefined && currentText !== "") {
$.each(currentText.split(","), function(index, currentValue) {
var temp = $.grep(sourceList, function(current) {
return current.name === currentValue.trim();
});
$.merge(selectedObjects, temp);
});
selectedItems = selectedObjects;
var valueField = [], textField = [];
$.each(selectedItems, function(i, e) { valueField.push(e.valueField) }); //Resource_Id
$.each(selectedItems, function(i, e) { textField.push(e.textField) });
this.value = valueField.join(", ");
$(this).attr("value", valueField.join(", "));
this.innerHTML = textField.length > 0 ? textField.join(", ") + ", " : "";
}
else {
this.innerHTML = this.value = ""; $(this).attr("value", "");
selectedItems = [];
}
})
.autocomplete({
minLength: 0,
source: function(request, response) {
// delegate back to autocomplete, but extract the last term
response($.ui.autocomplete.filter(sourceList, extractLast(request.term)));
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
selectedItems.push(ui.item);
var valueField = [], textField = [];
$.each(selectedItems, function(i, e) { valueField.push(e.valueField) }); //Resource_Id
$.each(selectedItems, function(i, e) { textField.push(e.textField) });
this.value = valueField.join(", ");
$(this).attr("value", valueField.join(", "));
this.innerHTML = textField.join(", ") + ", ";
event.preventDefault();
$(this).focus();
placeCaretAtEnd(document.getElementById("attendees"));
//return false;
}
})
.data("ui-autocomplete")._renderItem = function(ul, value) {
return $("<li></li>").data("item.autocomplete", value).append('<span value=' + value.ValueField + '>' + value.TextField + '</span>').appendTo(ul); //Resource_Id
};
Hope this helps :)

:contains case sensitive not working appropriately

I have added the code to overwrite the contains method however I am still not getting the right result. I have created a JFiddle to show the problem. http://jsfiddle.net/zjAyX/ I have also tried the code that works with the jQuery 1.8 which is the version of jQuery I'm using. Where do I place the code?
$(function() {
$.extend($.expr[":"], {
"containsIN": function(elem, i, match, array) {
return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
$("#searchInput").keyup(function () {
//split the current value of searchInput
var data = this.value.split(" ");
//create a jquery object of the rows
var jo = $("tbody").find("tr");
if (this.value == "") {
jo.hide();
return;
}
//hide all the rows
jo.hide();
//Recusively filter the jquery object to get results.
jo.filter(function (i, v) {
var $t = $(this);
for (var d = 0; d < data.length; ++d) {
if ($t.is(":contains('" + data[d] + "')")) {
return true;
}
}
return false;
})
//show the rows that match.
.show();
}).focus(function () {
this.value = "";
$(this).css({
"color": "black"
});
$(this).unbind('focus');
}).css({
"color": "#C0C0C0"
});
});
Use :containsIN() instead of contains() in your condition.
jo.filter(function (i, v) {
var $t = $(this);
for (var d = 0; d < data.length; ++d) {
if ($t.is(":containsIN('" + data[d] + "')")) {
return true;
}
}
return false;
})
Demo: Fiddle

getting jQuery ui autocomplete to work with #mentions only

I've had a look at a few questions, such as https://stackoverflow.com/a/7222592/2332251
I'm still having trouble reconciling it with the code I have.
At the moment the following works perfectly for searching a username as I start typing.
$(function() {
$("#appendedInputButton").autocomplete({
minLength: 2,
source: "searchusers.php"
});
});
The function in searchusers.php outputs the usernames from the database.
As I said, I'm having trouble making other #mention solutions work for me. I've tried copying over other solutions and swapping my details in but nothing seems to work.
So...
What do I need to do to my current autocomplete script to make it load only
when I initially type the '#' symbol?
I would really like to be able to have multiple #mentions in my posts
(optional) when autocomplete suggests usernames and when I select the username from the list I want it to appear in my post with the #symbol still appended to the front of the username e.g. "hello #john, the # symbol is still attached to your username"
If you need more info, please comment and I will provide more :)
Edit I'm just really unsure of the syntax to make it work. For example, using the example answer I posted above, I came up with (but it doesn't work):
function split(val) {
return val.split(/#\s*/);
}
function extractLast(term) {
return split(term).pop();
}
function getTags(term, callback) {
$.ajax({
url: "searchusers.php",
data: {
filter: term,
pagesize: 5
},
type: "POST",
success: callback,
jsonp: "jsonp",
dataType: "jsonp"
});
}
$(document).ready(function() {
$("#appendedInputButton")
// don't navigate away from the field on tab when selecting an item
.bind("keydown", function(event) {
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
event.preventDefault();
}
}).autocomplete({
source: function(request, response) {
if (request.term.indexOf("#") >= 0) {
$("#loading").show();
getTags(extractLast(request.term), function(data) {
response($.map(data.tags, function(el) {
return {
value: el.name,
count: el.count
}
}));
$("#loading").hide();
});
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var terms = split(this.value);
// remove the current input
terms.pop();
// add the selected item
terms.push(ui.item.value);
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join("");
return false;
}
}).data("autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.data("item.autocomplete", item)
.append("<a>" + item.label + " <span class='count'>(" + item.count + ")</span></a>")
.appendTo(ul);
};
});
Where do I insert searchusers.php, #appendedInputButton and other specific info? I hope this makes sense.
I will form an answer based on my comments.
First of all lets review the list of requirements:
autocomplete usernames started with # symbol
prepend usernames with # symbol
multiple #mentions in a text
edit any #mention anywhere in a text
to implement the last requirement we need some magic functions that i found on stackoverflow:
getCaretPosition - https://stackoverflow.com/a/2897229/2335291
setCaretPosition - https://stackoverflow.com/a/512542/2335291
Also to detect a username somewhere in the text we need to define some constraints for usernames. I assume that it can have only letters and numbers and test it with \w+ pattern.
The live demo you can find here http://jsfiddle.net/AU92X/6/ It always returns 2 rows without filtering just to demonstrate the behavior. In the listing below i've put the original getTags function from the question as it looks fine for me. Although i have no idea how searchusers.php works.
function getCaretPosition (elem) {
// Initialize
var iCaretPos = 0;
// IE Support
if (document.selection) {
// Set focus on the element
elem.focus ();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange ();
// Move selection start to 0 position
oSel.moveStart ('character', -elem.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
}
// Firefox support
else if (elem.selectionStart || elem.selectionStart == '0')
iCaretPos = elem.selectionStart;
// Return results
return (iCaretPos);
}
function setCaretPosition(elem, caretPos) {
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();
}
}
}
function getTags(term, callback) {
$.ajax({
url: "searchusers.php",
data: {
filter: term,
pagesize: 5
},
type: "POST",
success: callback,
jsonp: "jsonp",
dataType: "jsonp"
});
}
$(document).ready(function() {
$("#appendedInputButton").autocomplete({
source: function(request, response) {
var term = request.term;
var pos = getCaretPosition(this.element.get(0));
var substr = term.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
if (lastIndex >= 0){
var username = substr.substr(lastIndex + 1);
if (username.length && (/^\w+$/g).test(username)){
getTags(username, function(data) {
response($.map(data.tags, function(el) {
return {
value: el.name,
count: el.count
}
}));
});
return;
}
}
response({});
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var pos = getCaretPosition(this);
var substr = this.value.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
if (lastIndex >= 0){
var prependStr = this.value.substring(0, lastIndex);
this.value = prependStr + '#' + ui.item.value + this.value.substr(pos);
setCaretPosition(this, prependStr.length + ui.item.value.length + 1);
}
return false;
}
}).data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.data("ui-autocomplete-item", item)
.append("<a>" + item.label + " <span class='count'>(" + item.count + ")</span></a>")
.appendTo(ul);
};
});
I cannot add a comment, so I'm just going to add this as an answer.
I tried the code snippet you've provided and it worked great. The only problem I had was while editing the mention. I decided to edit from the middle of the mention, the autocomplete showed and I selected an item successfully. Only - it didn't delete the rest of the previous mention, only the letters before the cursor's position.
So I added something extra:
select: function(event, ui) {
var pos = comments.init.getCaretPosition(this);
var substr = this.value.substring(0, pos);
var lastIndex = substr.lastIndexOf('#');
var afterPosString = this.value.substring(pos, this.value.length);
var leftovers = afterPosString.indexOf(' ');
if (leftovers == -1)
leftovers = afterPosString.length;
if (lastIndex >= 0){
var prependStr = this.value.substring(0, lastIndex);
this.value = prependStr + '#' + ui.item.value + this.value.substr(pos + leftovers);
comments.init.setCaretPosition(this, prependStr.length + ui.item.value.length + 1);
}
return false;
}
I changed the select function a bit to cover the leftovers. Now, it's searching for the next " " occurrence and adds the length of everything before it to the replaced value.
Hope this helps :)

Categories

Resources