Real Life Example with SPServices: Telephone List Gadget

<UPDATE date=”2011-02-14″>: As of jQuery 1.5, single quotes are *required* around z:row or any other similar node selector. This was actually “required” in previous versions of jQuery, but not enforced.

$(xData.responseXML).find("[nodeName='z:row']")

</UPDATE>

Kevin Carbonaro posted a neat little gadget example to the SPServices Discussions today, and I thought it was slick enough that it deserved a blog post. The pundits out there may look at this and say “Well, he should have done it with…” or “That’s not best practice because…”. Hey, even I wrote back to Kevin and said that I would probably have done this with a Data View Web Part (DVWP) instead. But it works for Kevin and his business users, so it’s a winner no matter how you look at it. If you’ve got SharePoint hosted in the cloud with Microsoft’s BPOS or one of the great other hosting companies out there like FPWeb or Rackspace, then this type of approach may well be optimal for you. Kevin was able to build this gadget with jQuery and SPServices and it meets *his* organization’s governance goals. Score.

Let me let Kevin explain it from his Discussions post:

Hi All,

Have been working on a telephone list gadget to put on our intranet and finally it is working, however I am still not sure about the correct way to get z:row ( see related post). [Note: The post now contains the answer for the best way to access the z:row namespace and I’ve updated the code below accordingly.]

The telephone list gadget consists of a dropdown list being populated from a SP list called Telephone List.

When an dropdown item is selected an ajax call is sent to the server to get just the columns for that item.

They are then displayed underneath the dropdown.

jQuery and SPServices are stored in a SP list as attachments and referenced using their SP url. The code is placed in a Content Editor Web Part. All functions and variables have a prefix of sda_ so that they do not conflict with any other similarly named ones. It is important not to load the referenced libraries more than once from other code on the same page.

P.S. This code will not work as is – i.e. you need to create your own SP list with columns and modify the code accordingly ( urls,column names, etc ), but it will surely help by giving examples and ideas for your own project.

and from the email exchange Kevin and I had separately, after I suggested that I would probably have used a DVWP:

Actually I did not know of any easier method to create such a functionality until you mentioned DVWP but as I am not a proficient SP designer user, it was really not an option for me. As well as proving to my boss that it can be done, it paves the way for more complex interactive gadgets. Internally, this gadget has been a number one hit. Employees (incl. the receptionist) are now using it instead of printing the list. So I guess I am contributing to the ‘Save a Tree’ campaign :)

Here’s the code:

// Reference jquery and spservices libraries stored as attachments in a SP list
<script src="/Lists/Site%20Code/Attachments/20/jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="/Lists/Site%20Code/Attachments/27/jquery.SPServices-0.5.6.min.js" type="text/javascript"></script>
<script type="text/javascript">

$(document).ready(function()
{
 sda_initialize();
}); // ready

function sda_initialize() {
 sda_unpopulateDropdown();
 sda_filterquery = "";
 sda_viewfields = "<ViewFields><FieldRef Name='Title' /><FieldRef Name='Last_x0020_Name' /></ViewFields>";
 sda_thenwhat = 'populatedropdown';
 sda_getmyitems(sda_filterquery,sda_viewfields,sda_thenwhat);
} // sda_initialize

function sda_populateDropdown(sda_currentrow) {
 sda_firstname = sda_currentrow.attr("ows_Title");
 sda_lastname = sda_currentrow.attr("ows_Last_x0020_Name");
 if (sda_lastname == null) {
  sda_lastname = "";
 } // if
 sda_selectHtml = '<option value="' + sda_firstname + '*SPLIT*' + sda_lastname + '">' + sda_firstname + ' ' + sda_lastname + '</option>';
 $("#sda_Select").append(sda_selectHtml);
} // populatedropdown

function sda_unpopulateDropdown(sda_currentrow) {
 $("select[id$=sda_Select] > option").remove();
 sda_selectHtml = '<Option value="Select...">Select...</Option>';
 $("#sda_Select").append(sda_selectHtml);
} // sda_unpopulateDropdown

function sda_showDetails(sda_currentrow) {
 var sda_role = '', sda_dept = '', sda_deptraw = '', sda_extension = '', sda_mobile = '', sda_email = '';

 sda_role = sda_currentrow.attr("ows_Role");
 sda_deptraw = sda_currentrow.attr("ows_Unit");
 sda_extension = sda_currentrow.attr("ows_Extension");
 sda_mobile = sda_currentrow.attr("ows_Mobile");
 sda_email = sda_currentrow.attr("ows_Email");

 if (sda_role == null) { sda_role = '' };
 if (sda_deptraw == null) {
    sda_deptraw = '';
  } // if
 else {
  sda_dept = sda_deptraw.substring(2,sda_deptraw.length-2);
 } // else

 if (sda_extension == null) { sda_extension = '' };
 if (sda_mobile == null) { sda_mobile = '' };
 if (sda_email == null) { sda_email = '' };

 sda_detailsHtml = 'Role: ' + sda_role + '<br />' + 'Group: ' + sda_dept + '<br />' + 'Ext: ' + sda_extension + '<br />' + 'Mob: ' + sda_mobile + '<br />' + 'Email: <a href="<a href="mailto:'">mailto:'</a> + sda_email + '">' + sda_email + '</a>';
 $("#sda_Details").append(sda_detailsHtml);
} // showdetails

function sda_clearDetails(sda_currentrow) {
 $("#sda_Details").empty();
} // cleardetails

function sda_getmyitems(sda_filterquery,sda_viewfields,sda_thenwhat) {
 $().SPServices({
  operation: "GetListItems",
  webURL: "/Units/CorporateServices",
  listName: "Telephone List",
  CAMLQuery: sda_filterquery,
  CAMLViewFields: sda_viewfields,
  completefunc: function (sda_xData, sda_Status) {
   switch(sda_thenwhat)
    {
   case 'populatedropdown':
    sda_unpopulateDropdown();
    $(sda_xData.responseXML).find("[nodeName=z:row]").each(function() {
       sda_populateDropdown($(this));
    }); // each
    break;
   case 'getdetails':
    sda_clearDetails();
    $(sda_xData.responseXML).find("[nodeName=z:row]").each(function() {
     sda_showDetails($(this));
    }); // each
      break;
   default:
   } // switch
  } // completefunc
 }); // spservices
}

function sda_getSelectedInfo(sda_getfullname) {
 var sda_fullname = (sda_getfullname.options[sda_getfullname.selectedIndex].value);
 if (sda_fullname != "Select...") {
  var sda_splitResult = sda_fullname.split("*SPLIT*");
  sda_getfirstname = sda_splitResult[0];
  sda_getlastname = sda_splitResult[1];
  sda_filterquery = '<Query><Where><And><Eq><FieldRef Name="Title" /><Value Type="Text">' + sda_getfirstname + '</Value></Eq><Eq><FieldRef Name="Last_x0020_Name" /><Value Type="Text">' + sda_getlastname + '</Value></Eq></And></Where></Query>';
  sda_viewfields = '<ViewFields><FieldRef Name="Title" /><FieldRef Name="Last_x0020_Name" /><FieldRef Name="Role" /><FieldRef Name="Unit" /><FieldRef Name="Extension" /><FieldRef Name="Mobile" /><FieldRef Name="Email" /></ViewFields>';
  sda_thenwhat = 'getdetails';
  sda_getmyitems(sda_filterquery,sda_viewfields,sda_thenwhat);
 } // if
} // getselectedinfo

</script>

<div id="sda_Header" style="text-align: center;"> <b>Telephone List</b> </div>
<div id="sda_Pre" style="margin: 5px;">
  <select id="sda_Select" onChange="sda_getSelectedInfo(this);">
    <Option value="Select...">Select...</Option>
  </select>
</div>
<div id="sda_Details" style="margin: 5px; text-align: left;"></div>
<div id="sda_Footer" style="text-align: right; margin-right: 8px;"> <a href="/Units/CorporateServices/Lists/Telephone%20List/AllItems.aspx" target="_blank">View Full Staff Directory</a> </div>

I’m sure that Kevin would be interested in your ideas and comments. I’m happy to share such a nice use of SPServices!

9 Comments

  1. Marc, personally I would have done it differently. Oh, you said it already…

    My takeaways from this post:
    – there’s not just one way to do things in SharePoint
    – the idea itself (here a neat interactive phone list) is as much important as the way to do it
    – “proving that it can be done” is an essential step. Later you can always step back and find better ways to do it (I often use SPServices this way).

    A side comment: how about wrapping the responseXML parsing in a SPServices function, and make this transparent for the users?

    Reply
    • Christophe:

      There are definitely multiple ways to skin a cat, as they say. This might continue to be the most viable solution for Kevin over time or he might change his tack later. The key is that it works for *him* and that it’s reliable. Note that it doesn’t look like there are a lot of people in the list, so volume would not seem to be an issue here (though the Web Services are very efficient).

      What do you mean about parsing the responseXML?

      M.

      Reply
      • For example:
        function ProcessResponseItems([field1,field2,field3],func){
        $(sda_xData.responseXML).find(“[nodeName=z:row]”).each(
        func($(this).attr(field1),$(this).attr(field2),$(this).attr(field3));
        );
        }

        Where field1 is an attribute (e.g. ows_title), and func is the post-processing function (ShowDetails for example).

        This is just the rough idea, the important point being that the find(“[nodeName=z:row]”) method is pre-built in a function, and the user doesn’t have to look for the correct way to parse responseXML.

        Reply
  2. I am trying to put this on our Sharepoint site but am not having any luck. None of the names in my contact list are showing up in the dropdown. Any ideas?

    Code:

    $(document).ready(function()
    {
    sda_initialize();
    }); // ready

    function sda_initialize() {
    sda_unpopulateDropdown();
    sda_filterquery = “”;
    sda_viewfields = “”;
    sda_thenwhat = ‘populatedropdown’;
    sda_getmyitems(sda_filterquery,sda_viewfields,sda_thenwhat);
    } // sda_initialize

    function sda_populateDropdown(sda_currentrow) {
    sda_firstname = sda_currentrow.attr(“ows_FirstName”);
    sda_lastname = sda_currentrow.attr(“ows_Title”);
    if (sda_lastname == null) {
    sda_lastname = “”;
    } // if
    sda_selectHtml = ” + sda_firstname + ‘ ‘ + sda_lastname + ”;
    $(“#sda_Select”).append(sda_selectHtml);
    } // populatedropdown

    function sda_unpopulateDropdown(sda_currentrow) {
    $(“select[id$=sda_Select] > option”).remove();
    sda_selectHtml = ‘Select…’;
    $(“#sda_Select”).append(sda_selectHtml);
    } // sda_unpopulateDropdown

    function sda_showDetails(sda_currentrow) {
    var sda_JobTitle = ”, sda_Company = ”, sda_WorkPhone = ”, sda_CellPhone = ”, sda_email = ”;

    sda_role = sda_currentrow.attr(“ows_JobTitle”);
    sda_deptraw = sda_currentrow.attr(“ows_Company”);
    sda_extension = sda_currentrow.attr(“ows_WorkPhone”);
    sda_mobile = sda_currentrow.attr(“ows_CellPhone”);
    sda_email = sda_currentrow.attr(“ows_Email”);

    if (sda_role == null) { sda_role = ” };
    if (sda_deptraw == null) {
    sda_deptraw = ”;
    } // if
    else {
    sda_dept = sda_deptraw.substring(2,sda_deptraw.length-2);
    } // else

    if (sda_extension == null) { sda_extension = ” };
    if (sda_mobile == null) { sda_mobile = ” };
    if (sda_email == null) { sda_email = ” };

    sda_detailsHtml = ‘Role: ‘ + sda_role + ” + ‘Group: ‘ + sda_dept + ” + ‘Ext: ‘ + sda_extension + ” + ‘Mob: ‘ + sda_mobile + ” + ‘Email: <a href="mailto:’ + sda_email + ‘”>’ + sda_email + ‘‘;
    $(“#sda_Details”).append(sda_detailsHtml);
    } // showdetails

    function sda_clearDetails(sda_currentrow) {
    $(“#sda_Details”).empty();
    } // cleardetails

    function sda_getmyitems(sda_filterquery,sda_viewfields,sda_thenwhat) {
    $().SPServices({
    operation: “GetListItems”,
    webURL: “/contacts/lists”,
    listName: “Contacts”,
    CAMLQuery: sda_filterquery,
    CAMLViewFields: sda_viewfields,
    completefunc: function (sda_xData, sda_Status) {
    switch(sda_thenwhat)
    {
    case ‘populatedropdown’:
    sda_unpopulateDropdown();
    $(sda_xData.responseXML).find(“[nodeName=z:row]”).each(function() {
    sda_populateDropdown($(this));
    }); // each
    break;
    case ‘getdetails’:
    sda_clearDetails();
    $(sda_xData.responseXML).find(“[nodeName=z:row]”).each(function() {
    sda_showDetails($(this));
    }); // each
    break;
    default:
    } // switch
    } // completefunc
    }); // spservices
    }

    function sda_getSelectedInfo(sda_getfullname) {
    var sda_fullname = (sda_getfullname.options[sda_getfullname.selectedIndex].value);
    if (sda_fullname != “Select…”) {
    var sda_splitResult = sda_fullname.split(“*SPLIT*”);
    sda_getfirstname = sda_splitResult[0];
    sda_getlastname = sda_splitResult[1];
    sda_filterquery = ” + sda_getfirstname + ” + sda_getlastname + ”;
    sda_viewfields = ”;
    sda_thenwhat = ‘getdetails’;
    sda_getmyitems(sda_filterquery,sda_viewfields,sda_thenwhat);
    } // if
    } // getselectedinfo

    Telephone List

    Select…

    View Full Staff Directory

    Reply
    • Matt,
      Apologies for not replying earlier, but was busy with some of the usual project deadlines at work. If you still have not solved it yet, here is what you can try;

      In sda_initialize function I have set a default value to sda_viewfields. I am not sure if the CAMLView value is mandatory in SPServices (Marc may correct me on this), but you may try setting it as;

      sda_viewfields = “”;

      What I would suggest first if the above did not work, is to get a simple script to read from your contact list and display it in an html unordered list. That way you can confirm that something is being output to the screen.

      Let me know if you still have problems.

      Reply
  3. Hi there,

    I am new to this type of programming and would very much like to get this gadget to work for our internal SharePoint site.

    I have set it up pretty much out of the box. basic out of the box Contacts library with all it’s default columns.

    I placed the CEWP with the code in it as well as set the paths to the two js files needed.

    Even changed the URL for the list and name to Contacts.

    All i get i the drop down and nothing populating it…

    Is there a “idiot” step by step version for me to figure out what i did wrong or what I still need to change?

    thanks and looking forward in getting this working… this website has given me lots of cool ideas to try out…

    Cheers
    Mark

    ps.. Is there also a way to pre-filter this list if you only want to show contacts from a certain group? (if i place a choice column with group names to filter the contacts based on group)

    Reply
  4. hi

    wanted to thank kevin and marc for this awesome piece of code.
    implemented this on my site and had a bit of a hick up where the z:row issue would not return any data. but saw another post right here and used SPFilterNode which resolved my issue.

    before i could finish my deliverable though, I need to substitute the dynamic dropdown created here with a lookup dropdown on the NewForm.aspx.

    any update on that?

    Reply

Have a thought or opinion?