Showing posts with label jQuery. Show all posts
Showing posts with label jQuery. Show all posts

Wednesday, December 28, 2011

easy double-click to edit related-list VF page

Often enough I am asked to make a custom VisualForce of a related list that is editable in a similar way to salesforce's native double click to edit. I have made them in a variety of ways, including adding images like salesforce's to indicate if a field is editable or locked etc. and some simpler ways like the one I am going to do now.

The Problem:
a sObject under Account(or any other obj) needs show on the Account page layout, but it also needs to be double-click to edit to make it easy for the reps to update key information on the fly without having to click into each sObject. (for this example we will use the Contact obj)


Setup:
First we need to create a controller extension so that it can sit in the page layout of the Account obj, and even though we are showing the contact obj, the extension needs to point to account otherwise it wont show up as an available VisualForce page to add on the layout.
ContactInLineEdit.cls
ContactInLineEdit.page

This is a standard controller extension setup, this allows the class to use the account id referenced in the URL when the page is accessed. Example page URL will be 'apex/ContactInLineEdit?id={!account.id}'

Now we should add the Contact list to the class so we can access it on the page in a apex:repeat, when we add it to the class, we only need to get the list on load of the page, and then again when we update the fields.
ContactInLineEdit.cls

On the page we will need to add the apex:form tag, along with an apex:outputpanel with an id so we can call it later in a rerender attribute on the update call. We will also add the CSS and jQuery files that will help us control the double-click to edit functionality. We then place the jQuery inside the outputpanel so when it rerenders it can reapply the jQuery to the page. I also prefer to use the jQuery.noConflict(); since I often run into issues with salesforce's JS conflicting with what I am trying to implement.
ContactInLineEdit.page

Now we need to add the apex:pagemessages so we can do error handling and we need to add the table for the apex:repeat to sit in. We will also add the apex:outputfiled's that will display in the related list on the page, we need to make sure that any field we reference here is also in the select query on the class.
ContactInLineEdit.page

Lets add in the loading icon for the update and a JS function to control it, and we should also add the edit fields to the table. To help control the conditional hiding and showing of the fields for the edit, we should add some div's with classes to reference in jQuery.
ContactInLineEdit.page
ContactInLineEdit.page

Lets add the double click functionality, first we need to add a class to the edit text in the action column  <span class="editLink">Edit</span> then we need to add a function so that everytime the nonEdit field (outputField) is double-clicked it will hide the outputField and show the inputField. We will also change the Edit text to Cancel and add a class to it so we know that row is in edit mode.

Now lets add the edit row/cancel row edits functionality, this will allow all editable fields in the row to be edited and if cancel is hit, it will undo all edits by retrieving the text from the hidden outputfield otherwise it will continue to show the updated text since we are just hiding and showing. We don't want to clear the fields cause that will potentially clear out the saved info if it gets saved by mistake. So by retrieving the info from the outputfield, we are able to set it back to what it was originally.
ContactInLineEdit.page

We will also need to update our edit action to use the new function, it will now look like this: <span class="editLink" onclick="EditMe(jQuery(this));">Edit</span>
At this point we need to make the update method and add it to the page, this will allow any edits made by the user to be saved, and then the list will rerender with the new information.
ContactInLineEdit.cls

ContactInLineEdit.page

And that it, simple. I didn't add it here, but field validation could be added to ensure its a proper email or phone number etc. In the style i made so that fields that were editable when the mouse hovered over it, the cursor would change to a pointer like it would when the mouse hovers over a link. This way the user knows the field is editable instead of having to add an image that could bog down the page load.

Questions?  
Twitter: @SalesForceGirl  or Facebook

Tuesday, April 26, 2011

Google User Org-Chart with Salesforce

Cloudspokes.com had a challenge to incorporate Google's user hierarchy chart in Salesforce on the user object. And even though I didn't win (totally should have lol) I thought I would share my version of the challenge.  
In mine, the Hierarchy chart is based off of the Manager Id of each User, instead of going into Roles and all that mess. But when I set it up for all users, THe chart produced was very long and made the page scroll, to fix that issue, I made it so it grouped by department, and by default show the current User's Department. Then I added a fixed width on the page so it wouldn't scroll, except inside the frame, and if the department drop-down is on the page it would always show on top right regardless of how long the chart is that gets created. Since not all managers where in the same department as their subordinates, I had to re-query for any managers that weren't in the list the of the users returned, other wise it showed as only an ID above the user, which looked bad lol.
I have set the page up to take a few different params(see below) which control options for the page, including hiding/showing a User table, a select list for departments, and the hiding/showing of the sidebar and header. 
Additional options lie in the JavaScript, there you have the ability to allow people to click on a user and see additional info on them(works/looks really good on iPad). Or control the Google chart options like allow HTML and the collapse feature. To see the  'onclick additional info' in action simply click once on a users name and it will pop up with a small modal of that info.

When trying to make this work, i found that apex and Google do not play nice, especially with re-render, so i had to make a lot of custom JavaScript to make it all work correctly, and i could not use Apex:output or have the JS in the re-render, otherwise it would spit out all the code to the page on re-render. 

Here is the JS
/*SALESFORCEGIRL ORGCHAT 2011*/

/*CONTROLS THE LOADING GIF*/
function Loading(b){
     if(b){
          $('.loadingIcon').removeClass('hideIt');
     }else{
          $('.loadingIcon').addClass('hideIt');
     }
}
/*PUTS THE USER LIST IN THE RIGHT FORMAT WITH OPTIONS FOR GOOGLE TO READ*/
function getUserList(userOptions, CurrentUserId, dptLabel,phLabel,rxtLabel,emLabel) {
     var users = [];
     var UserString = '';
     var uId = '';
     var uName = '';
     var uManId = '';
     var uDept = '';
     var uPhone = '';
     var uExt = '';
     var uEmail = '';                 
     $('#hiddenUserData tr.dataRow').each(function(){
          uId = $(this).find('td:eq(0)').text();
          uManId = $(this).find('td:eq(1)').text();
          uName = $(this).find('td:eq(2)').text();
          uDept = $(this).find('td:eq(3)').text();
          uPhone = $(this).find('td:eq(4)').text();
          uExt = $(this).find('td:eq(5)').text();
          uEmail = $(this).find('td:eq(6)').text();
      
          UserString = '<div class="UserInfo" onClick="ShowMoreInfo(this)">'+uName+'<br /><div class="Title" >'+uDept+'</div><br />';
      
          if (userOptions.showInfo.department || userOptions.showInfo.phone || userOptions.showInfo.ext || userOptions.showInfo.email) {
               UserString += '<div title="Click to hide" class="additionalInfo hideMe"><table><tbody><tr><td colspan="2">';
           
               UserString += '<div class="HeaderTitle">'+uName+'</div></td></tr>';
           
               if (userOptions.showInfo.department) {
                    UserString += '<tr><th>'+dptLabel+'</th><td>'+uDept+'</td></tr>';
               }
           
               if (userOptions.showInfo.phone) {
                    UserString += '<tr><th>'+phLabel+'</th><td>'+uPhone+'</td></tr>';
               }
           
               if (userOptions.showInfo.ext) {
                    UserString += '<tr><th>'+rxtLabel+'</th><td>'+uExt+'</td></tr>';
               }
           
               if (userOptions.showInfo.email) {
                    UserString += '<tr><th>'+emLabel+'</th><td>'+uEmail+'</td></tr>';
               }
           
               UserString += '</tbody></table></div>';
          }
          UserString += '</div>';
 
          users.push([{v: uId, f: UserString}, uManId, 'Click for additional info']);
     });                     
     return users;
}
/*USED TO TRIGGER THE ADDITIONAL INFO POP UP */
function Loading(b){
     if(b){
          $('.loadingIcon').removeClass('hideIt');
     }else{
          $('.loadingIcon').addClass('hideIt');
     }
}
/*USED TO SHOW MORE INFO IF USER IS CLICKED*/
function ShowMoreInfo(t){
     var item = $(t).find('.additionalInfo');
     if($(item).hasClass('hideMe')){
          $('.additionalInfo').addClass('hideMe');
          $(item).removeClass('hideMe');
     }else{
          $(item).addClass('hideMe');
     }
}
/*SETTING UP THE GOOGLE CHART*/
function initializeOrgChart(){
     google.load('visualization', '1', {packages:['orgchart']});
}
/*SETTING UP THE GOOGLE CHART*/
function getUserDataTable(userList){
     var dt = new google.visualization.DataTable();
       dt.addColumn('string', 'Name');
       dt.addColumn('string', 'Manager');
       dt.addColumn('string', 'ToolTip');
       dt.addRows(userList);
       return dt;
}
/*SETTING UP THE GOOGLE CHART*/
function getOrgChart(container,options,data){
     var chart = new google.visualization.OrgChart(container);
       chart.draw(data, options);
     return chart;
}
/*SETTING UP THE GOOGLE CHART*/
function drawOrgChart(container,options) {         
     /* DEFAULT OPTIONS STRUCTURE.
     var options = {
          users: null,                    
          chartOptions: {
               allowHtml: true,
               allowCollapse:true
          }
     };*/
       var data  = getUserDataTable(options.users);
       var chart = getOrgChart(container,options.chartOptions,data);
}

Then on the page:
<apex:page controller="sfgOrgChart" title="Org Chart" showHeader="{!bShowHeader}" sidebar="{!bShowSidebar}" standardstylesheets="false" action="{!SwitchList}">
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" />
        <apex:stylesheet value="{!URLFOR($Resource.OrgChart, '/style.css')}"/>
       
        <script type="text/javascript" language="javascript" src="{!URLFOR($Resource.OrgChart, '/js/jquery-1.4.2.min.js')}" ></script>
        <script type="text/javascript" language="javascript" src="{!URLFOR($Resource.OrgChart, '/js/jquery.tablesorter.min.js')}" ></script>
        <script type="text/javascript" language="javascript" src="{!URLFOR($Resource.OrgChart, '/js/sfgOrgChart.js')}" ></script>
        <script type='text/javascript' src='https://www.google.com/jsapi'></script>
    </head>
    <body>
        <apex:form id="form">
            <apex:outputpanel id="pageWrap">
                <apex:outputpanel id="ChooseDepartmentWrap" rendered="{!bShowDepartmentselectList}">
                    <div class="clear"></div>
                    <br />
                    <div class="floatL">
                        <apex:outputpanel rendered="{!NOT(ISBLANK(sDepartment))}">
                            <div class="HeaderTitle">Viewing Department:&nbsp;<apex:outputtext value="{!IF(bIsDept,sDepartment,'All Departments')}" /></div>
                        </apex:outputpanel>
                    </div>
                    <div class="floatR">
                        <apex:outputpanel rendered="{!bShowDepartmentSelectList}">
                            <strong>{!$ObjectType.User.Fields.Department.Label}:</strong>
                            <apex:selectList id="department" size="1" value="{!sDepartment}" styleClass="deptSelect"> 
                                <apex:selectOptions value="{!lstDepartmentOptions}" />
                                <apex:actionSupport event="onchange" onsubmit="Loading(true);$('#chart_div').html('');" action="{!SwitchList}" rerender="pageWrap" id="actionSupportForDeptartment" oncomplete="drawOrgChart($('#chart_div')[0],Go('{!$User.Id}'));ShowTableChart();Loading(false);" />
                            </apex:selectList>
                        </apex:outputpanel>
                    </div>
                    <!-- the loading icon -->
                    <div class="loadingIcon hideIt"><apex:image id="loadingImage" value="{!URLFOR($Resource.OrgChart, 'images/loader_24x26.gif')}" width="24" height="26"/></div>
                    <div class="clear"></div>
                </apex:outputpanel>
                <div id="chart_div"></div>
                <br />
                <div>
                    <table id="hiddenUserData" class="list tablesorter hideMe">
                        <thead class="rich-table-thead">
                        <tr class="headerRow">
                            <th colspan="1" scope="col">Id</th>
                            <th colspan="1" scope="col">Manager Id</th>
                            <th colspan="1" scope="col">Name</th>
                            <th colspan="1" scope="col">Department</th>
                            <th colspan="1" scope="col">Phone</th>
                            <th colspan="1" scope="col">Ext</th>
                            <th colspan="1" scope="col">Email</th>
                        </tr>
                        </thead>
                        <tbody>
                        <apex:repeat value="{!lstOfUsers}" var="u"><!-- Table to get the data from and to show if they wish it with sorting -->
                            <tr class="dataRow" id="{!u.id}">
                                <td>{!JSENCODE(u.id)}</td>
                                <td>{!JSENCODE(u.ManagerId)}</td>
                                <td>{!JSENCODE(u.name)}</td>
                                <td>{!JSENCODE(u.department)}</td>
                                <td>{!JSENCODE(u.phone)}</td>
                                <td>{!JSENCODE(u.extension)}</td>
                                <td>{!JSENCODE(u.email)}</td>
                            </tr>
                        </apex:repeat>
                    </tbody></table>
                </div>
        </apex:outputpanel>
        </apex:form>
        <script type="text/javascript" language="javascript">
          
            initializeOrgChart();  // LOAD API IMPORTANT TO DO THIS.          
            var ShowTable = {!bShowUserTable};
            var dptLabel = '{!$ObjectType.User.Fields.Department.Label}';
            var phLabel = '{!$ObjectType.User.Fields.Phone.Label}';
            var rxtLabel = '{!$ObjectType.User.Fields.Extension.Label}';
            var emLabel = '{!$ObjectType.User.Fields.Email.Label}';
          
            var winWidth = $(window).width() - 75 +'px !important;overflow-y:hidden;';
            var isiPad = navigator.userAgent.match(/iPad/i) != null;
            if(isiPad){
                winWidth = '100% !important;'
            }
              
            function Go(UserId){
              
                // THE ADDITIONAL INFO OPTIONS
               // IF ALL ARE FALSE WILL NOT SHOW ADDITIONAL INFO
                var userOptions = {
                      showInfo: {
                            department: true,      // show dept in additional info box
                            phone: true,           // show phone in additional info box
                            ext: true,             // show ext in additional info box
                            email: true            // show email in additional info box
            }
                };       
           //GOOGLE CHART OPTIONS
           var options = {
                 users: getUserList(userOptions,UserId, dptLabel,phLabel,rxtLabel,emLabel),
                 chartOptions: {               // default google chart options
                       allowHtml: true,        // allow html
                       allowCollapse:true      // allow collapsing 
                 }
           };                

            function ShowTableChart(){
                if(ShowTable){
                    $("table").tablesorter({
                        headers: {
                            0: {sorter: 'text'},
                            1: {sorter: 'text'},
                            2: {sorter: 'text'},
                            3: {sorter: 'text'},
                            4: {sorter: 'digit'},
                            5: {sorter: 'digit'},
                            6: {sorter: 'text'}
                        }
                    });
                    $('#hiddenUserData').removeClass('hideMe');
                }         
            }
            $(document).ready(function(){                 
                //DRAW CHART WITH DATA AND MAKE SURE IT WONT OVERFLOW
                $('#chart_div').attr('style','width:'+winWidth+' margin:5px auto; padding:10px 5px;');
                drawOrgChart($('#chart_div')[0],Go('{!$User.Id}'));
                $('.deptSelect').val('{!sDepartment}');
                //IF SHOW-TABLE START TABLE SORTER AND SHOW TABLE
                ShowTableChart();
            });
        </script>
    </body>
    </html>

</apex:page>
Example: If the dept param(URL_KEY_SHOWDEPT) is passed with URL_DEFAULT_VALUE_SHOWALL it will show all Users under all Departments, and if SHOW_SELECT_DEPT is set to true, then it will also show a Department select list so it is easy to change between departments. If you click on a User in the Chart it will display additional information on that user including: Name, Phone, Title, Email, Department, Extension. This can be turned off/on and/or you can set which info shows by customizing the JavaScript options. If all options are false, then the additional info will not show. 

NOTE: Department select list will only show if a param is passed and the SHOW_SELECT_DEPT is true...this is not the same for the other params.. see below

In the Common class there are a few variables that you can change to change the defaults of the page:
     
     // keys to look for in the url
     public static final string URL_KEY_SHOWDEPT = 'dept'; 
     public static final string URL_KEY_SHOWSIDEBAR = 'side';
     public static final string URL_KEY_SHOWHEADER = 'top';
     public static final string URL_KEY_SHOWUSER = 'user';

     // the show all param value
     public static final string URL_DEFAULT_VALUE_SHOWALL = 'all';

     // if true and param is passed will show select list 
     public static final boolean SHOW_SELECT_DEPT = true;

     // limit on the user Query
     public static final integer USER_SEARCH_LIMIT = 400;

     // show the header
     public static final boolean SHOW_HEAD = true;

     // show the side bar
     public static final boolean SHOW_SIDE = false;

     // show the table of users currently in the google charts
     public static final boolean SHOW_USER_LIST = true;

EXAMPLE URLs:

-shows Users by current users dept (nothing was passed so it defaults to Current Users Dept)
-shows header (nothing was passed so it defaults to SHOW_HEAD )
-hides sidebar (nothing was passed so it defaults to SHOW_SIDE )
-hides User Table (nothing was passed so it defaults to SHOW_USER_LIST )

URL_KEY_SHOWDEPT
-shows all Dept's and all Users(active) and also shows dept select list (if SHOW_SELECT_DEPT is true)

-shows Users in support dept and also shows dept select list (if SHOW_SELECT_DEPT is true)

-shows Users by current users dept and also shows dept select list (if SHOW_SELECT_DEPT is true)

URL_KEY_SHOWSIDEBAR 
-shows side bar

-hides side bar

URL_KEY_SHOWHEADER 
-shows header

-hides header

URL_KEY_SHOWSIDEBAR 
-shows User Table

-hides User Table



Questions? 

Twitter: @SalesForceGirl  or Facebook


Friday, March 18, 2011

Queue-Member Edit Customizations

Using the standard Salesforce Queue edit pages for users can be somewhat tiresome, especially if you need to make mass changes. Not to mention that the amount of page refreshes it requires to edit more than 5 users, takes enough time to make you want to throw your keyboard. So to get around that, making a custom page to stream line the process.


 

In my Org I created a User-List page which shows each user by department, in a sortable list/table(more about this & how to make it here) that allows for easy access to a users contact info. From this table, Manager's/TeamLead's/VP's, can click 'edit' next to a user and get taken to a QueueMember edit page witch shows all Queues and whether or not the user belongs to it. Then I also made it so the Edit part of the page was as easy as a click and the page wouldn't need to be refreshed at all.

What this would entail is a userId passed to the page so we know what User we are talking about. Then it would need to get a list of all Queues and then ones only specific for that user. If they wanted to add the user to the Queue, the Queue Id would need to be passed back to the controller, and the same for removing the user from the Queue. Then we update the page with the new User specific Queues and repeat.

public with sharing class QueueMemberEditCon {

      public string sUserName{get;set;}
      public string sQueueId{get;set;}
      public string sUserId{get;set;}
      
      public List<GroupMember> lstFullGroupMemberList{get{returnQueueMemberHelper.FindGroupIdsFromMembers(sUserId);}set;}
      public List<QueueSobject> lstFullQueueList{get{returnQueueMemberHelper.FindQueueList();}set;}
      
      public Pagereference loadUser()
      {
            sUserId = GlobalHelper.UrlParam('UserId');
            if(GlobalHelper.CheckForNotNull(sUserId))
            {
                  sUserName = QueueMemberHelper.UserName(sUserId);
            }
            return null;
      }
      public pageReference RemoveUserFromQueue()
      {
            sQueueId = GlobalHelper.UrlParam('QueueId');
            sUserId = GlobalHelper.UrlParam('UserId');
            if(GlobalHelper.CheckForNotNull(sQueueId) && GlobalHelper.CheckForNotNull(sUserId))
            {
                  QueueMemberHelper.RemoveUserFromQueue(sQueueId, sUserId);
            }
            return null;
      }
      
      public pageReference AddUserToQueue()
      {
            sQueueId = GlobalHelper.UrlParam('QueueId');
            sUserId = GlobalHelper.UrlParam('UserId');
            if(GlobalHelper.CheckForNotNull(sQueueId) && GlobalHelper.CheckForNotNull(sUserId))
            {
                  QueueMemberHelper.AddUserToQueue(sQueueId, sUserId);
            }
            return null;
      }
}

All the work is done in my helper class, but it still only has a few methods in it. The first one retrieves the entire Queue list for the sObject Case, where as the next one Finds the one that are specific to the UserId that is passed. This could technically be split into two methods, but that's up to you. Then of course there is the RemoveUserFromQueue/AddUserToQueue methods which are void but could be booleans so we can base the next steps on whether or not the method was successful, but I decided against it for now. And finally the UserName method which should explain itself. 

public with sharing class QueueMemberHelper {
      /// <summary>
    /// FINDS LIST OF QUEUES UNDER CASE
    /// </summary>
    /// <returns>LIST OF QUEUES</returns>
      public static List<QueueSobject> FindQueueList()
      {
            QueueSobject[] q = [Select Id, QueueId, Queue.Name From QueueSobject whereSobjectType = 'Case' limit 100];
            return q;
      }
      /// <summary>
    /// FINDS USER IN ALL GROUP MEMEBER ITEMS
    /// </summary>
    /// <param name="sInUserId">USER ID</param>
    /// <returns>LIST OF QUEUES</returns>
      public static List<GroupMember> FindGroupIdsFromMembers(string sInUserId)
      {
            if(GlobalHelper.CheckForNotNull(sInUserId))
            {
                  List<string> sList = new List<string>();
                  List<GroupMember> gList = new List<GroupMember>();                  
                  try
                  {
                        GroupMember[] gm = [Select Id, GroupId, Group.Name, UserOrGroupId FromGroupMember Where UserOrGroupId =:sInUserId limit 100];
                        if(GlobalHelper.CheckForNotNull(gm))
                        {
                              for(GroupMember g : gm)
                              {
                                    if(!GlobalHelper.ContainsItem(g.Group.Name, sList))   
                                    {
                                          gList.add(g);
                                    }
                              }
                              return GlobalHelper.AddItemsToList(gList);
                        }
                  }
                  catch(exception e)
                  {
                        system.debug('Oops... QueueMemberHelper - FindGroupIdsFromMember - Error: ' + e);
                        return null;
                  }
            }
            return null;
      }
      /// <summary>
    /// FINDS USER NAME
    /// </summary>
    /// <param name="sInUserId">USER ID</param>
    /// <returns>USER NAME</returns>
      public static string UserName(string sInUserId)
      {
            try
            {
                  User u = [select Name from User where id=:sInUserId limit 1];
                  return u.Name;
            }
            catch(exception e)
            {
                  system.debug('UserInfoListHelper - UserName - error: '+e);
            }     
            return null;
      }
      /// <summary>
    /// REMOVED USER FROM QUEUE MEMBER
    /// </summary>
    /// <param name="sInQueueId">QUEUE ID</param>
    /// <param name="sInUserId">USER ID</param>
      public static void RemoveUserFromQueue(string sInQueueId, string sInUserId)
      {
            try
            {
                  GroupMember gm = [Select Id, GroupId, UserOrGroupId From GroupMember whereUserOrGroupId =:sInUserId and GroupId =:sInQueueId limit 1];
                  if(gm != null)
                  {
                        try
                        {
                              delete gm;
                        }
                        catch(exception e)
                        {
                              system.debug('Oops... QueueMemberHelper - RemoveUserFromQueue - Error: ' + e);
                        }
                  }
            }
            catch(exception e)
            {
                  system.debug('Oops... QueueMemberHelper - RemoveUserFromQueue - Error: ' + e);
            }
            
      }
      /// <summary>
    /// ADDS USER TO QUEUE MEMBER
    /// </summary>
    /// <param name="sInQueueId">QUEUE ID</param>
    /// <param name="sInUserId">USER ID</param>
      public static void AddUserToQueue(string sInQueueId, string sInUserId)
      {
            GroupMember gm = new GroupMember(
                  GroupId = sInQueueId,
                  UserOrGroupId = sInUserId
            );
            try
            {
                  insert gm;
            }
            catch(exception e)
            {
                  system.debug('Oops... QueueMemberHelper - AddUserToQueue - Error: ' + e);
            }
      }
}

So on the Page I used the jQuery table sorter(more info here), and in the apex:repeat parts, you will see I do some 'ninja' to make sure that it displays the correct info. In the apex:repeat I use rendered="{!IF(q.Queue.Name == u.Group.Name, true, false)}" to ensure that it only shows that they have that Queue if the two lists match. Then I use jQuery to Hide or Show the 'Add' or 'Remove' links based on if that field has content.

<apex:page controller="QueueMemberEditCon" standardStylesheets="false" title="Queue Member Edit"action="{!loadUser}">
<apex:stylesheet value="{!URLFOR($Resource.UserList, '/NewTQC.css')}"/>
<apex:stylesheet value="{!URLFOR($Resource.UserList, '/style.css')}"/>
<script type="text/javascript" language="javascript" src="{!URLFOR($Resource.UserList, '/js/jquery1_4.js')}" ></script>
<script type="text/javascript" language="javascript" src="{!URLFOR($Resource.UserList, '/js/jquery.tablesorter.min.js')}" ></script>
<apex:form >
<div id="pageWrap" style="width:85%; margin:auto;">
<apex:outputPanel id="TicketListWrap" styleClass="Wrap">
      <script language="javascript" type="text/javascript">
            function Loading(b)
            {
                  if(b){
                        $('.loadingIcon').removeClass('hideIt');
                  }else{
                        $('.loadingIcon').addClass('hideIt');
                  }
            }
            function handleMembers()
            {
                  $('.memberCol').each(function(){
                  if($(this).find('.IsMember').text().trim() == 'Remove')
                  {
                        $(this).find('.IsMember').removeClass('hideMe');
                  }else{
                        $(this).find('.NotMemeber').removeClass('hideMe');
                  }
                });
            }
            $(document).ready(function(){
                  $("table").tablesorter({
                    headers: {
                        0: {sorter: 'text'},
                        1: {sorter: 'text'},
                        2: {sorter: 'text'},
                        3: {sorter: 'text'},
                        4: {sorter: 'text'},
                        5: {sorter: 'text'},
                        6: {sorter: 'text'
                    }
                });
                handleMembers();
            });   
      </script>
      <apex:outputpanel id="queueList">
            <div class="clear"></div>
            <div id="newTicketHeader" class="floatL">
                  Showing Queue's for&nbsp;{!sUserName}
            </div>
            <div class="floatR">
                  <apex:outputlink value="/apex/UserInfoList" onClick="Loading(true);">Back to Users</apex:outputlink>
            </div>
            <div class="loadingIcon hideIt"><apex:image id="loadingImage" value="{!URLFOR($Resource.UserList, 'images/loader_24x26.gif')}" width="24" height="26"/></div>
            <div class="clear" ></div>
            <table id="Usertable" class="list tablesorter" cellspacing="1" style="width:85%; margin:auto;">
            <thead class="rich-table-thead">
                  <tr class="headerRow">
                        <th colspan="1" scope="col">Queues</th>
                        <th colspan="1" scope="col">Is Member</th>
                        <th colspan="1" scope="col">Action</th>
                  </tr>
            </thead>
            <tbody>
                  <apex:repeat value="{!lstFullQueueList}" var="q" id="QueueListRepeater">
                  <tr class="dataRow">
                        <td>{!q.Queue.Name}</td>
                        <td>
                              <apex:repeat value="{!lstFullGroupMemberList}" var="u">
                                    <apex:outputpanel rendered="{!IF(q.Queue.Name == u.Group.Name, true, false)}">
                                          Yes
                                    </apex:outputpanel>
                              </apex:repeat>
                        </td>
                        <td class="memberCol">
                              <div class="hideMe IsMember">
                              <apex:repeat value="{!lstFullGroupMemberList}" var="u">
                                    <apex:outputpanel rendered="{!IF(q.Queue.Name == u.Group.Name, true, false)}">
                                          <apex:CommandLink value="Remove"onClick="Loading(true);" action="{!RemoveUserFromQueue}" rerender="TicketListWrap"oncomplete="Loading(false);">
                                                <apex:param name="QueueId" value="{!q.QueueId}"/>
                                          </apex:CommandLink>
                                    </apex:outputpanel>
                              </apex:repeat>
                              </div>
                              <div class="hideMe NotMemeber">
                                    <apex:CommandLink value="Add" onClick="Loading(true);"action="{!AddUserToQueue}" rerender="TicketListWrap" oncomplete="Loading(false);">
                                          <apex:param name="QueueId" value="{!q.QueueId}"/>
                                    </apex:CommandLink>
                              </div>
                        </td>
                  </tr>
                  </apex:repeat>
            </tbody></table>
      </apex:outputpanel>
      </apex:outputpanel>
</div>
</apex:form>
</apex:page>

Questions? 
Twitter: @SalesForceGirl  or Facebook