skip to Main Content

i have this html code:

<div class="online-users">
online users
    <span class="user-online">
         <i class="fa fa-circle"></i>
         <span class="user-name">Ster</span>
    </span>
    <span class="user-online">
         <i class="fa fa-circle"></i>
         <span class="user-name">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?

2

Answers


  1. 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 class="online-users">
      online users:
        <span class="user-online">
             <i class="fa fa-circle"></i>
             <span class="user-name">Ster</span>
        </span>
        <span class="user-online">
             <i class="fa fa-circle"></i>
             <span class="user-name">dimitris</span>
        </span>
        <!-- ... more users... -->
    </div>
    Login or Signup to reply.
  2. 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 class="online-users">
      online users
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">Ster</span>
      </span>
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">dimitris</span>
      </span>
      <!-- ... more users... -->
    </div>

    JS Fiddle demo.

    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 class="online-users">
      online users
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">Ster</span>
      </span>
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">dimitris</span>
      </span>
      <!-- ... more users... -->
    </div>

    JS Fiddle demo.

    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 class="user-name"> 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 class="online-users">
      online users
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">Ster</span>
      </span>
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">dimitris</span>
      </span>
      <!-- ... more users... -->
    </div>

    JS Fiddle demo.

    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 class="user-name"> 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 class="online-users">
      online users
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">Ster</span>
      </span>
      <span class="user-online">
        <i class="fa fa-circle"></i>
        <span class="user-name">dimitris</span>
      </span>
      <!-- ... more users... -->
    </div>

    JS Fiddle demo.

    References:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search