i have this html code:
<div >
online users
<span >
<i ></i>
<span >Ster</span>
</span>
<span >
<i ></i>
<span >dimitris</span>
</span>
<!-- ... more users... -->
</div>
this code showing which users are online. I need a code to change the icons "i" before users names. Every user have specific icon.
i have try with this jquery code:
<script>
$(document).ready(function(){
$('.online-users span.user-name').each(function(){
$(this:contains('Ster')).siblings('i').toggleClass('fa-solid fa-user-gear');
$(this:contains('dimitris')).siblings('i').toggleClass('fa-solid fa-user-pen');
});
});
</script>
But nothing change. Any idea what is wrong with my code?
CodePudding user response:
The main error in your code was using a selector like this:
$(this:contains('Ster'))
You need to pass a string to the jQuery $
function if you want to return the result of a css selector.
I refactored better your code to achieve the desired result, so that there's a dedicated function to change the style, toggling those fontawesome css classes, of the <i>
element next to an element containing the username you wish to style.
The selector I used to fetch the .user-name
element containing a given string was:
$(`.online-users > .user-online > .user-name:contains('${username}')`)
That's a template string (and it's wrapped by upticks instead of single quotes or double quotes). It looks for the element .user-name
in that defined hierarchy and containing the value hold in the username
variable.
When document is ready, each username found gets styled like that.
...But the painful truth:
Your approach doesn't play well with every scenario. Consider if you have a list of users where there's one containing the other like: user
and user2
. In that case the :contains
selector will hit twice on the same element and will screw the style because you were toggling the class and not just adding it.
EDIT: Later I better factored 4 modes to deal with the problem where one of them doesn't have issue with usernames sharing the same root.
$(document).ready(function(){
toggleUsersIconStyle(4);
});
/**
* Toggles the classes fa-solid and fa-user-gear to the <i> tags
* matching this selector ran for each of the entry in the usernames list:
* `.online-users > .user-online > .user-name:contains('${username}') i.fa`
*
* Notes: it's the easiest approach but it relies on :contains so it won't be an exact match.
* Plus it loops the usernames list delegating the elements search to css
*/
function styleUsers(usernames){
debugger;
usernames.forEach(username=>{
//in our dreams this selector should return one element only
//but there's always the chance that multiple usernames have the same root
$(`.online-users > .user-online > .user-name:contains('${username}')`)
.each((i, el)=>{ $(el).siblings('i').toggleClass('fa-solid fa-user-gear') });
});
}
/**
* Toggles the classes fa-solid and fa-user-gear to the <i> tag
* included in all the elements .online-users > .user-online
* if the inner .user-name text content perfectly matches with any
* included in the usernames list passed as argument
*
* Notes: it loops through all the online element and makes the most accurate match
*/
function styleUsersAccurate(usernames){
const users = $('.online-users > .user-online > .user-name');
for(const user of users){
const username = $(user).text();
if(usernames.includes(username)){
$(user).siblings('i.fa').toggleClass('fa-solid fa-user-gear');
}
}
}
/**
* Toggles the classes fa-solid and fa-user-gear to the <i> tag
* hold inside the .user-online element containing the passed username
*/
function styleUser(username){
$(`.online-users > .user-online > .user-name:contains('${username}')`)
.siblings('i')
.toggleClass('fa-solid fa-user-gear');
}
function toggleUsersIconStyle(mode){
if(mode === 1)
//styleUsersAccurate approach
styleUsersAccurate(['Ster','dimitris']);
else if(mode === 2)
//styleUsers approach
styleUsers(['Ster','dimitris']);
else if(mode === 3)
//styleUser approach (styling only the username listed in the array)
['Ster','dimitris'].forEach((username)=>{
styleUser(username);
});
else if(mode === 4)
//styleUser approach (styling every single user existing in the dom)
$('.online-users span.user-name').each((i, username)=>{
styleUser( $(username).text() );
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" />
<div >
online users:
<span >
<i ></i>
<span >Ster</span>
</span>
<span >
<i ></i>
<span >dimitris</span>
</span>
<!-- ... more users... -->
</div>
CodePudding user response:
The problem with your own code is:
$(this:contains('Ster'))
The :contains()
jQuery selector works – as does a CSS Selector – as part of a string, whereas you've attempted to couple it to a DOM node (this
), which results in a syntax error:
Uncaught SyntaxError: missing ) after argument list
You could work your way around this – if you wanted to – by using:
// we need to wrap this in a jQuery Object to enable
// the use of jQuery methods:
$(this)
// and then we can use filter() to check if the
// <string> is present within the element and
// if so we can then chain further methods:
.filter(':contains("<string>")')
Which gives the following approach:
$(document).ready(function() {
$('.online-users span.user-name').each(function() {
let userName = $(this).text();
$(this).filter(':contains("dimitris")').prev('i').toggleClass('fa-solid fa-user-pen');
$(this).filter(':contains("Ster")').prev('i').toggleClass('fa-solid fa-user-gear');
});
});
*,
::before,
::after {
box-sizing: border-box;
font-size: 16px;
margin: 0;
padding: 0;
}
.online-users,
.user-online {
border: 1px solid currentColor;
display: grid;
gap: 0.25em;
padding-block: 0.25em;
padding-inline: 0.5em;
}
.online-users {
inline-size: fit-content;
margin-block: 1em;
margin-inline-start: 1em;
}
.user-online {
grid-template-columns: repeat(2, max-content);
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8 y gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
online users
<span >
<i ></i>
<span >Ster</span>
</span>
<span >
<i ></i>
<span >dimitris</span>
</span>
<!-- ... more users... -->
</div>
Or, instead of filter()
, we could instead use the .is()
method, which returns a Boolean (true
/false
) if the element matches, or does not match, the supplied CSS selector:
$(document).ready(function() {
$('.online-users span.user-name').each(function() {
let userName = $(this).text();
if ($(this).is(':contains("dimitris")')) {
$(this).prev('i').toggleClass('fa-solid fa-user-pen');
} else if ($(this).is(':contains("Ster")')) {
$(this).prev('i').toggleClass('fa-solid fa-user-gear');
}
});
});
*,
::before,
::after {
box-sizing: border-box;
font-size: 16px;
margin: 0;
padding: 0;
}
.online-users,
.user-online {
border: 1px solid currentColor;
display: grid;
gap: 0.25em;
padding-block: 0.25em;
padding-inline: 0.5em;
}
.online-users {
inline-size: fit-content;
margin-block: 1em;
margin-inline-start: 1em;
}
.user-online {
grid-template-columns: repeat(2, max-content);
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8 y gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
online users
<span >
<i ></i>
<span >Ster</span>
</span>
<span >
<i ></i>
<span >dimitris</span>
</span>
<!-- ... more users... -->
</div>
However it's worth noting that both of the previous approaches require you to code to explicitly check for each user-name, and hard-code those user-names and matching icons into the code. Not only does this require an update to the code each time a user is added, or removed and sets or changes their chosen icon the code itself has to check for every user; this leads to bloated code that's difficult to maintain.
Instead, I'd suggest the following approach, which requires one Object to be updated (which can be automatically generated on the back-end), and otherwise lets the code handle the checks automatically, this code has explanatory comments in the code:
// An object mapping user-names to the icons to show for
// the given user:
const userIcons = {
Ster: 'fa-user-gear',
dimitris: 'fa-user-pen'
}
$(document).ready(function() {
// selecting the <span > elements within the
// '.online-users' element, and iterating over that collection
// with the each() method:
$('.online-users span.user-name').each(function() {
// caching the current user-name, from the text of the current
// element and removing leading/trailing white-space with
// String.prototype.trim():
let userName = $(this).text().trim();
// accessing the previous <i> element sibling:
$(this).prev('i')
// and toggling the listed class-names, which uses a
// a template literal to always toggle the 'fa-solid'
// class-name, and interpolating the result of the
// userIcons[userName] expression:
.toggleClass(`fa-solid ${userIcons[userName]}`);
});
});
*,
::before,
::after {
box-sizing: border-box;
font-size: 16px;
margin: 0;
padding: 0;
}
.online-users,
.user-online {
border: 1px solid currentColor;
display: grid;
gap: 0.25em;
padding-block: 0.25em;
padding-inline: 0.5em;
}
.online-users {
inline-size: fit-content;
margin-block: 1em;
margin-inline-start: 1em;
}
.user-online {
grid-template-columns: repeat(2, max-content);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8 y gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<div >
online users
<span >
<i ></i>
<span >Ster</span>
</span>
<span >
<i ></i>
<span >dimitris</span>
</span>
<!-- ... more users... -->
</div>
Or, in plain JavaScript:
// As before, an Object used to map the user-specific icon(s)
// to a named user:
const userIcons = {
Ster: 'fa-user-gear',
dimitris: 'fa-user-pen'
}
// an alternative to jQuery's $(document).ready():
window.addEventListener('DOMContentLoaded', ()=>{
// retrieving a NodeList of all <span > elements
// within the '.online-users' element, and iterating over that
// NodeList with NodeList.prototype.forEach() and its anonymous
// (Arrow) function:
document.querySelectorAll('.online-users span.user-name').forEach(
// passing in a reference to the current Node of the NodeList
// over which we're iterating:
(el)=>{
// caching the user-name from the current node, and
// removing leading/trailing white-space with String.prototype.trim():
let userName = el.textContent.trim(),
// caching a reference to the previousElementSibling node:
previousSibling = el.previousElementSibling;
// if the previous element matches the selector (here: 'i') supplied:
if (previousSibling.matches('i')) {
// we then use the Element.classList API to add the listed
// comma-separated, class-names:
previousSibling.classList.add('fa-solid', userIcons[userName]);
}
});
});
*,
::before,
::after {
box-sizing: border-box;
font-size: 16px;
margin: 0;
padding: 0;
}
.online-users,
.user-online {
border: 1px solid currentColor;
display: grid;
gap: 0.25em;
padding-block: 0.25em;
padding-inline: 0.5em;
}
.online-users {
inline-size: fit-content;
margin-block: 1em;
margin-inline-start: 1em;
}
.user-online {
grid-template-columns: repeat(2, max-content);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8 y gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<div >
online users
<span >
<i ></i>
<span >Ster</span>
</span>
<span >
<i ></i>
<span >dimitris</span>
</span>
<!-- ... more users... -->
</div>
References: