Referencing jQuery, jQueryUI, and SPServices from CDNs – Revisited

In my previous post entitled Referencing jQuery, jQueryUI, and SPServices from CDNs, I provided the references to quickly add jQuery, jQueryUI, and SPServices from the CDNs I typically use.

However, I made a bit of a faux pas in what I provided. It’s better to omit the protocol in the references. Browsers will simply use the current protocol, whether it be http: or https:, as needed. This means that you won’t have to worry about any issues down the road should you decide to implement SSL. It also means that the location your user happens to use to access the site doesn’t matter.

Here’s my updated set of references with the protocols omitted and updated to the versions I’m currently using:

<!-- Reference the jQueryUI theme's stylesheet on the Google CDN. Here we're using the "Start" theme -->
<link  type="text/css" rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.0/themes/start/jquery-ui.css" />
<!-- Reference jQuery on the Google CDN -->
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<!-- Reference jQueryUI on the Google CDN -->
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.0/jquery-ui.min.js"></script>
<!-- Reference SPServices on cdnjs (Cloudflare) -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/0.7.2/jquery.SPServices-0.7.2.min.js"></script>

SPServices Compatibility Issues with jQuery 1.9.1

Download jQuery 1.9.1Ouch. Last week, several alert SPServices users reported issues with jQuery 1.9.0. (See: SPServices Compatibility Issues with the Minified Version of jQuery 1.9.0) It turned out that the minified version of 1.9.0 had a comment at the end of it that caused any jQuery library which followed it to throw errors.

Today, I had a report of SPServices not working with jQuery 1.9.1, which was released yesterday.

There’s a bug in the jQuery bug tracker called Ajax request not returning responseXML that sums up the issue well. Not everyone uses the .ajax() function in jQuery, but if you use SPServices, you’re using it constantly whether you realize it or not. Since there is no responseXML object returned by 1.9.1, every call to SharePoint’s SOAP Web Services in SPServices will fail.

Needless to say, don’t update to jQuery 1.9.1.

If there’s a bright side in all of this, it’s that the issues with jQuery 1.9.0 and 1.9.1 aren’t issues in SPServices or in the way I’ve coded anything. As I said in a forum earlier today, before I knew about the 1.9.1 bug:

Don’t ever point to ‘latest’. There are frequently changes in new versions that break perfectly fine old code.

While most of the mainstream CDNs provide ways to point to the current version of script libraries, e.g., jQuery.latest.min.js, don’t be tempted. These latest two versions of jQuery would have broken all of the SPServices-based code in your SharePoint installations. Make that a part of your governance: no pointing to ‘latest’ versions of script libraries.

Herre’s hoping that 1.9.2 is a better release.

Refreshing a Page Section with a User-selected Interval Set with jQueryUI’s Slider

I’m still doing lots of work with SharePoint 2007, even while many of my compatriots have moved on almost exclusively to 2013 work. The new shiny stuff is fun and all, but just because it’s new doesn’t mean that all the good work is there. Both SharePoint 2007 and 2010 are still providing valuable collaborative toolsets in organizations out there.

When you work with an older version of the product, you get the benefit of hindsight as well as knowledge of new trends in user interfaces and development approaches.

One of my clients wanted to create and auto-refresh capability for a SharePoint 2007 page that they use as a sort of dashboard. Many of the people in the organization keep the page open all day, and hitting refresh all the time is tedious. They also forget sometimes and can mistakenly be looking at stale content.

In SharePoint 2010, Data View Web Parts (DVWPs) provide this capability simply by checking a box on the ribbon (which usually works). I’ve rarely seen anyone enable the feature in 2010, but it’s there. To make this work in 2007, I had to build it with script.

To improve the situation, and therefore the general user experience (UX), we decided to give the user control over the auto-refresh capability. It wasn’t that much extra work to do it this way, and by allowing them the set their own refresh interval, they feel that they have more control of the page. This makes them more satisfied with the solution, which increases adoption. All goodness.

Page Map

It took some iteration to decide how to lay things out ideally, so what I’m showing here is closer to the end result than the starting point. I was able to take advantage of a function I had written earlier called refreshElement, which I wrote about in my prior post entitled Refreshing a Web Part Without a Postback Using jQuery’s AJAX Function.

The first step is to ensure that we have references to jQuery, jQueryUI, and jQueryUI’s CSS in the page. I’m also using jQuery cookie to set a cookie with the value in the slider that the user sets, thus making it possible to persist the value between sessions. In this particular case, we have references to the script libraries in the master page because we’re using them pervasively.

Next, I added a little markup to the page. My thinking on this was that by adding the markup at the top on the PlaceHolderMain rather than where the slider actually ended up, I could ensure that the data which drives the refresh will always be in the same place that I always put my script references, thus easy to find. If we decide to put the slider somewhere else in the page, I can just change the script, with no need to change the markup in the page itself. (We’re using this capability in four sites at the moment, with more to come.) There’s a div containing the slider info and then a second div wrapped around the area of the page I’d like to refresh. The refresh div can really surround anything in the page. I’m using AJAX to grab the full page, and I can simply parse out whatever is in the div. In this case, it contains the complex DVWP and one other Web Part. Note that the first div has a data attribute (how very HTML5 of me) which “points” to the div we want to actually refresh.

</pre>
<div id="aaa-refresh-control" data-to-refresh="aaa-listing"></div>
<div id="aaa-listing"><!-- ... DVWP, etc. ... --></div>
<pre>

Next comes the script. Hopefully the comments inline are clear enough, but if you have questions feel free to ask them in the comments section.

$(document).ready(function() {

  // If there is an element with the id aaa-refresh-control, we'll refresh what it indicates at a user-specified interval
  var refreshControl = $("#aaa-refresh-control");
  var toRefresh = refreshControl.data("to-refresh");

  // If there is a request to refresh an element...
  if(toRefresh !== undefined) {

    $("div[id$='QuickLaunchNavigationManager']").append(refreshControl);

    // Add the elements to the page that we need for the functionality to work
    refreshControl.append("<div id='refreshInfo'>" +
      "<label for='refreshInterval'>Refresh every</label>  " +
      "<input type='text' id='refreshInterval'/>minutes " +
      "<label id='refreshTime'></label></div>");
    refreshControl.append("<div id='refreshSlider'></div>");
    refreshControl.append("<div id='refreshNow'></div>");

    // Get the refresh interval value the user has set by retrieving the cookie
    var refreshInterval = $.cookie("refreshInterval");

    // If the refreshInterval hasn't been set, default to 5 minutes
    if(refreshInterval == 0 || refreshInterval == undefined) refreshInterval = 5;
    $("#refreshInterval").val(refreshInterval);
    $("#refreshTime").html("Last refreshed: " + getNow());

    // Set up the refresh behavior using a timer
    var refreshListing = setInterval(function(){
      refreshElement(toRefresh, "", "Please wait...", "Refreshing...");
      $("#refreshTime").html("Last refreshed: " + getNow());
    }, 1000 * 60 * refreshInterval);
    $("#aaa-listing").css("clear", "both");

    // Adjusting the slider allows the user to set the refresh interval to their own needs
    $("#refreshSlider").slider({
      value: refreshInterval,
      min: 5,
      max: 60,
      step: 5,
      slide: function(event, ui) {
        $("#refreshInterval").val(ui.value);
      },
      change: function(event, ui) {

        // Set a cookie with the refresh interval so that we can reload it when we return
        $.cookie("refreshInterval", ui.value);
        clearInterval(refreshListing);

        // Re-initialize the timer
        refreshListing = setInterval(function(){
          refreshElement(toRefresh, "", "Please wait...", "Refreshing...");
          $("#refreshTime").html("Last refreshed: " + getNow());
        }, 1000 * 60 * ui.value);
        $("#aaa-listing").css("clear", "both");
      }
    });
  }

});

// Refreshes an element's contents on a user action, showing a modal dialog during the refresh
// elementId  The id of the container element
// qs      The Query String to append to the current URL
// msg      The message to show in the dialog
function refreshElement(elementId, qs, title, msg) {

  var elementObj = $("#" + elementId);
  var infoDialog = $("<div><div>" + msg + "</div><div class='aaa-please-wait'></div></div>").dialog({
    open: function(event, ui) {
      $(".ui-dialog-titlebar-close").hide();  // Hide the close X
      $(this).css("overflow-y", "visible");  // Fix for the scrollbar in IE
    },
    autoOpen: false,
    title: title,
    modal: true,
    position: { my: "center", at: "center", of: window }
  });
  infoDialog.dialog("open");

  elementObj.fadeOut("slow", function() {
    $.ajax({
      // Need this to be synchronous so we're assured of a valid value
      async: false,
      url: window.location.pathname + qs,
      complete: function (xData) {
        newHtml = $(xData.responseText).find("#" + elementId).html();
        elementObj.html(newHtml);
      }
    });

  }).fadeIn("slow", function() {
    infoDialog.dialog("close");
  });
}

// Helper function which gets the current time and formats it in HH:MM format
function getNow() {
  var d = new Date();
  var curr_hour = d.getHours();
  var curr_min = d.getMinutes() >= 10 ? d.getMinutes() : "0" + d.getMinutes();
  return curr_hour + ":" + curr_min;
}

And voila. We have a little slider right under the main links in the Quick Launch which the user can slide to set their own refresh interval. Every time the timer hits that interval, the dialog pops up with info about the refresh, the content is refreshed, and the time above the slider changes to tell the user how fresh the content is.
jQueryUI Slider

SPServices Stories #2 – Charting List Data with HighCharts

Introduction

This submission comes to us from an anonymous reader, who can’t publish the details under his (or her) name due to confidentiality issues. However, s/he has been able to generate some very useful charts with HighCharts using SharePoint list data as the underlying data sources.

Charting List Data with HighCharts

HighChartsIn the following code, the source list contains columns with Year (string), Month (1-12), and Value (number with decimals).

While HighCharts isn’t free, the licensing costs are quite reasonable. A similar approach would work with other charting engines out there which may be free.

The page has a Content Editor Web Part dropped into it with the Content Link pointing to a file containing the following:

<!-- jquery and spservices are in the masterpage here this is a CEWP noConflict is on-->

<script type="text/javascript"src="//code.highcharts.com/highcharts.js"></script>
<script type="text/javascript" src="//code.highcharts.com/modules/exporting.js"></script>
<script type="text/javascript">
function GetYearSeries(series, year)
{
  var gotOne = false;
  var seriesOptions;

  jQuery.each (series, function(index, dataItem) {
    if (dataItem.name === year)
    {
      seriesOptions = dataItem;
      gotOne=true;
    }
  });

  if (!gotOne)
  {
    seriesOptions = {
      name: year,
      data: [0,0,0,0,0,0,0,0,0,0,0,0]
    };

    series.push (seriesOptions);
  }

  return seriesOptions;

}

jQuery(function($) {

  var CamlQuery = "<Query><OrderBy><FieldRef Name='Year' /><FieldRef Name='Month' Ascending='False' /></OrderBy></Query>";

  $().SPServices({
    operation: "GetListItems",
    async: true,
    listName: "Sales",
    CAMLQuery: CamlQuery,
    CAMLViewFields: "<ViewFields><FieldRef Name='Year' /><FieldRef Name='Month' /><FieldRef Name='Value' /></ViewFields>",
    completefunc: GraphIt
  });

  function GraphIt(xmlResponse)
  {

    var options = {
      chart: {
        renderTo: 'container',
        type: 'column'
      },
      title: {
        text: 'Sales'
      },
      xAxis: {
        categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
      },
      yAxis: {
        title: {
          text: 'Value'
        }
      },
      series: []
    };

    $(xmlResponse.responseXML).SPFilterNode("z:row").each(function() {

      var seriesOptions = GetYearSeries(options.series, $(this).attr('ows_Year'));
      var month=parseInt($(this).attr('ows_Month'))-1;

      seriesOptions.data[month]=parseFloat($(this).attr('ows_Value'));
    });
    var chart = new Highcharts.Chart(options);

  }

});//docReady
</script>

<div id="container" style="min-width: 400px; height: 400px; margin: 0 auto"></div>

This generates a chart which looks something like this:

HighCharts Example

SPServices Compatibility Issues with the Minified Version of jQuery 1.9.0

Download jQuery 1.9.0Codeplex users tedka and danstaley have reported issues using SPServices with jQuery 1.9.0. You can read their issues here and here, respectively.

I’ve done some quick testing, and the problem seems to be with the minified version of jQuery 1.9.0, *not* 1.9.0 itself. When I use the non-minified version, my test pages perform just fine.

I’ve added a note to the home page of SPServices to this effect:

2013-01-29 – At this time, SPServices seems to work just fine with jQuery 1.9.0, but NOT with the minified version. If you need to use jQuery 1.9.0, please stick with the non-minified version or minify your own version.

When I create my own minified version of 1.9.0 using The JavaScript CompressorRater (I chose the first YUI Compressor 2.4.2 result, as I do when I minify SPServices), my minified version works fine, too.

At this point, I’m not sure what the exact problem is, but I’ll try to contact the jQuery team to see if there have been any other reports of issues. I’m not sure how well that will go, but at least there’s a workaround.

I found some other reports of issues which are caused by this line at the end of the minified version of jQuery 1.9.0:

//@ sourceMappingURL=jquery.min.map

While the source mapping capability sounds useful, having this line in the minified version causes another library following that line to throw errors, which is explained well on this thread at StackOverflow.

[important]This is a known issue with jQuery 1.9.0, which the team has already fixed for the next version. Here’s the bug in the jQuery bug tracker: http://bugs.jquery.com/ticket/13274[/important]