Category Archives: JavaScript and jQuery

jQuery UI Autocomplete Widget Extension

jQuery UIMany of my personal and professional projects use the jQuery UI Autocomplete widget. Customers love being able to type a search term and have their choices limited to only those items in which they’re interested. The base widget, however, doesn’t provide all the functionality I need and ad hoc customizations have become tedious. So, I’ve decided to write an extension using the Widget Factory that brings together the features I use most often. My AutocompletePro widget is definitely a work in progress, but you can grab the latest version from GitHub.


Live Demo!

This is a jQuery UI widget and may not be suitable for some mobile devices. A jQuery mobile version will soon be posted to my development site!

Delete the current input text and start typing the title of one of your favorite jazz albums, perhaps “Jazz Samba”. You can also type in an artist’s name like Miles Davis or the year 1958. Then make a selection from the available recordings.


AutocompletePro adds several new options to the base Autocomplete:

  • autodrop: defines if the menu should open on focus
  • categoryfield: what field to use as the category
  • categorize: defines if the menu should be categorized
  • defaultvalue: sets the initial value
  • datasource: replaces the base source option
  • filter: sets a filter on the data source records
  • filtereddatasource: read only – the datasource that is being used by the widget
  • itemsshown: limits the number of hits returned
  • searchfields: array of fields names to be searched

And two new methods:

  • value: returns the selected item object
  • selectitem: selects an item given a set of elements

Demo Code GitHub

The datasource for this demo is structured as follows:

 
var data.items = [{ "category": "Traditional", "artist": "Benny Goodman", "label": "Live at Carnegie Hall 1938 [Live]", "year": "1950" },
  ...
{ "category": "Bebop", "artist": "Charlie Parker", "label": "Best of the Complete Savoy and Dial Studio Recordings [Compilation]", "year": "2002" }]

And the widget is created with these options:

$("#widgetbox").autocompletepro({
        autoFocus: true,
        itemsshown: 10,
        datasource: data.items,
        autodrop: true,
        searchfields: ["label", "artist", "year"],
        categorize: true,
        defaultvalue: [{ "artist": "Miles Davis", "label": "Kind of Blue" }],
        categoryfield: "category",
        filter: [{ "category": "Traditional" }, { "category": "Latin" }, { "category": "Cool" }, { "category": "Fusion" }, { "category": "Bebop" }]
});

HTML5 Dynamic Charts – JavaScript and the Canvas 2D Context

Now that the HTML5 and Canvas 2D specifications are feature complete (December 2012) and most companies have pushed IE9 to their clients, I wanted to take advantage of the canvas element's 2D Context to create dynamic charts - without using Flash, ActiveX, Web Parts, browser add-ins or anything else extraneous to HTML and JavaScript. The first addition to my JavaScript chart library was a pieChart object.

Go right to the full featured demo and download the source from my example site. Or continue reading for a detailed explanation of how I created the chart objects.

To start creating my charts, I'll define the base object from which I'll derive all my chart objects including the pieChart.

<script type="text/javascript" src="jquery-1.9.1.min.js"></script>
<script type="text/javascript">
    function chart() { }
    chart.prototype = {
        canvas: null,
        data: null,
        colors: null,
        currentColor: null,
        currentColorIndex: null,
        width: null,
        height: null,
        initChart: function(canvasWidth, canvasHeight, chartData, chartColors) {
            this.data = chartData.slice();
            this.colors = chartColors.slice();
            this.canvas = document.createElement('canvas');
            this.canvas.width = canvasWidth;
            this.canvas.height = canvasHeight;
            this.width = canvasWidth;
            this.height = canvasHeight;
        },
        randomColor: function() {
            var r = ('0' + (Math.random() * 256 | 0).toString(16)).slice(-2),
            g = ('0' + (Math.random() * 256 | 0).toString(16)).slice(-2),
            b = ('0' + (Math.random() * 256 | 0).toString(16)).slice(-2);
            return '#' + r + g + b;
        },
        resetColor: function() { this.currentColorIndex = -1; },
        moveToNextColor: function() {
            if (!this.colors) {//check for 'null' or 'undefined' color array. if so, use a random color
                this.currentColor = this.randomColor();
                return this.currentColor;
            }
            else {
                if (this.currentColorIndex == null) {
                    this.currentColorIndex = 0;
                }
                else {
                    this.currentColorIndex = this.currentColorIndex + 1
                    if ((this.currentColorIndex >= this.colors.length) || (this.currentColorIndex < 0)) { this.currentColorIndex = 0; }
                }
                this.currentColor = this.colors[this.currentColorIndex];
                return this.currentColor;
            }
        }
    }
</script>

Although I don't use jQuery (line 1) in the base chart object, I'll use it later to extend the prototype of each chart object. Lines 13 and 14 initialize the data and color arrays for the chart. Notice the slice() array method. Since passing a JavaScript array object behaves like a "pass by reference", I use slice() to create new arrays with the same values as the parameter arrays. Now I won't accidentally change the originals while I'm manipulating the chart's data or colors.

The pieChart object is defined as follows:

<script type="text/javascript">
function pieChart(chartData, chartDiameter, chartColors) {
        this.diameter = chartDiameter;
        this.initChart(chartDiameter, chartDiameter, chartData, chartColors);
        this.draw();
}
pieChart.prototype = {

        draw: function() {

            this.resetColor();
            this.canvas.width = this.diameter;
            this.canvas.height = this.diameter;
            var canvas = this.canvas;
            var amounts = this.data.slice();

            var context = canvas.getContext('2d');
            var x = canvas.width / 2;
            var y = canvas.height / 2;
            var radius = Math.min(x, y);
            var counterClockwise = false;

            var percentDenominator = 0;
            for (var i = 0; i < amounts.length; i++) { percentDenominator += amounts[i]; }
            for (var i = 1; i < amounts.length; i++) { amounts[i] += amounts[i - 1]; }
            for (var i = 0; i < amounts.length; i++) { amounts[i] = amounts[i] / percentDenominator * 2 * Math.PI; }

            var points = new Array();
            points.push([0, amounts[0]]);
            for (var i = 1; i < amounts.length; i++) { points.push([amounts[i - 1], amounts[i]]); }

            for (var i = 0; i < amounts.length; i++) {
                context.lineWidth = 1;
                context.beginPath();
                context.moveTo(x, y);
                context.arc(x, y, radius, points[i][0], points[i][1], false);
                context.lineTo(x, y);
                context.closePath();
                context.fillStyle = this.moveToNextColor();
                context.fill();
                context.strokeStyle = "#ffffff";
                context.stroke();
            }
        }
}
$.extend(pieChart.prototype, chart.prototype);
</script>

The idea is to change each data item into a running percentage of the total circumference of the pie (no pun intended - lines 23-26). Then we create a multidimensional array such that the end point of the first arc is the starting point of the next one (lines 28-30). We now draw consecutive arcs along the circle shown in Figure 1. Each arc has a radius that is equal to the radius of the pie chart and a length that is the percentage of the circumference correlating to the value of the input data item. The last line (56) of the script extends the pieChart with the prototype of the base chart object.

For example, [10,20,20,50] is transformed to
[0.10, 0.30, 0.50, 1.00] and then we create the points array
[ [0, 0.10*2Pi], [0.10*2Pi, 0.30*2Pi], [0.30*2Pi, 0.50*2Pi], [0.50*2Pi, 1.00*2Pi ]

Figure 1

Here's some sample code to create the chart when the document is ready and a function to call when you need to update it:

var myPie = new pieChart([10,20,20,50],200,["#993333","#669966","#FFCC66","#336699"]);
   
$(document).ready(function() { 
   $("div#myPieChart").html(myPie.canvas);
});

function updateChart(newData){
     myPie.data = newData;
     myPie.draw();
}

to Change the chart Data to [120,140,75,90]. Several other properties of the chart object can be set to change the pie chart. For example, setting the color property to an empty string will cause the chart to be drawn using random colors. See a full featured demo and download the source from my examples site. The source download contains bar and line charts in addition to the pie chart. I'm adding new charts all the time, so check back often!

Consuming a RESTful web service with jQuery and Ajax

In my last post, we created a REST WCF web service using Visual Studio 2010. In this article, we’ll write a client side web applicaiton that uses jQuery and Ajax to consume a REST web service.

First, create an HTML page that includes a reference to a jQuery library, a <div> tag where we’ll display the service output, a button that will fire the Ajax request to the web service and one that will clear the output <div>.


<html>
<head>
<title>Consume a REST Web Service</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.min.js"/>
</head>
<body>
<p><div id="myOutput">Output Goes Here</div><p>
<p><input id="myButton" value="Get Artist" type="submit" /></p>
<p><input id="myClearButton" value="Clear Artist" type="submit" /></p>


Next, add the jQuery to run when the Get Artist button is clicked.

<script type="text/javascript">
 $("#myButton").click(function() {
        var artistURL = "http://development.dlwelch.com/examples/restservice/JazzArtists.svc/json/Shirley";
        var returnData = "";
        $.ajax({
            type: "GET",
            dataType: "json",
            async: true,
            url: artistURL,
            error: function(request, status, error) { alert(request.responseText) },
            success: function(data) {
                $("div#myOutput").html(" ");
                returnData = "<table style='font-size:8pt;'><tr><th>Artist</th><th>Grammys</th></tr>";
                for (prop in data) {
                    if (!data.hasOwnProperty(prop)) { continue; }
                    if (data[prop] != null) {
                        for (var i = 0; i < data[prop].length; i++) {
                            returnData += "<tr><td>" + data[prop][i]["FirstName"] 
                                       + " " + data[prop][i]["LastName"] + "</td><td align='right'>" 
                                       + data[prop][i]["Grammy"] + "</td></tr>";
                        }
                    }
                }
                returnData = returnData + "</table>";
                $("div#myOutput").html(returnData);
            }
        });
        return (false);
    });
</script>

...

Code line 3 defines the URI of the resource, in our case, the artist Shirley. Line 6 indicates we are sending an HTTP GET request to the resource and line 7 says we’ll evaluate the response as JSON and send a JavaScript object back to our success function on line 11. Since a JavaScript object was returned, line 14 starts the loop to find the property holding the response data. Line 15 test the current property to see if it’s a built in property of a JavaScript object. If so, skip it. Once we reach the property with our data, we loop through all the artist objects and create our HTML string for the output <div>.

Finally, add the jQuery to clear the output and finish your HTML.

<script type="text/javascript">
 $("#myClearButton").click(function() {
        $("div#myOutput").html("Output Goes Here");
        return (false);
    });

</script>


</body>
</html>


Your page should look like this. Click on the Get Artist button to see it work!



You can see a working example and grab the code for Ajax calls of all four HTTP verbs on my development site in the WCF RESTful Web Service example.

JavaScript, jQuery and Ajax for reporting on SharePoint lists

In this post, I will show you how to query two SharePoint lists using jQuery and Ajax. We will then combine the results into a summary report. I originally created this example for SP 2007 with the requirement it be completed using only client side capabilities. In addition, the customer wanted a graphical representation of the data and a drill down to details for each record, without the use of custom web parts.

The first list contains master project data.

The second list is a project status log which is populated by a workflow on the Projects List. Each time the project status is changed, an item is created in this project status list.

Now we’ll create our report using client-side scripting. I used SharePoint Designer 2007 and created a new web page at the root of the site. But you can also use a Content Editor Web Part to hold your script.

First, add a <div> to your page that will display a loading image and notification while your Ajax runs. We’ll hide this <div> once it’s done.

<div id="wait">
<p align="center"><img src="GEARS_AN.GIF" alt="Please wait..." />
<br/>Generating report..
</p>
</div>

Then add another <div> where you will display your results. I wrapped mine in a <table> so I can easily add rows with more information later.

<table class="ms-vb" cellpadding="1" border="0">
<tr><td>
<div id="listLinks"> </div>
</td></tr> 
</table> 

Now for the script. Define two JavaScript objects. One for the project list items and one for the statuses. Add an .ajaxStop function which will run when all Ajax calls complete. The .unbind function prevents it from running again if a subsequent Ajax call is made after the document loads. Finally, define what should happen when the document is ready.

<script type="text/javascript">

 var myProjArray = new Object();
 var myStatusArray = new Object();

 $(document).ajaxStop(function() {
  $(this).unbind("ajaxStop");
  createprojectstable();
 });

 $(document).ready(function() {
  getProjects();
 });

When the document is ready getProjects() runs. Since CAML joins are only supported in SharePoint 2010+, I have to make an extra Ajax call for each project to get the status items. A single call could have been made to return all status items rather than making a call for each project returned by the first. However, that list may have been much larger than needed if we were to restrict the projects returned in the first CAML query by using a WHERE clause. Your particular circumstance will dictate which approach to take.

//****************************************************************************************
//get projects from the Projects List
function getProjects(){

 var thisurl = window.location.href;
 var i = thisurl.lastIndexOf("/");
 var asmxlocation = thisurl.substr(0,i)
 asmxlocation = asmxlocation + "/_vti_bin/lists.asmx";


 var soapEnv = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
     <soapenv:Body> \
     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
      <listName>Projects List</listName> \
      <viewFields> <ViewFields> \
        <FieldRef Name='ID' /><FieldRef Name='Title' /> <FieldRef Name='ProjStatus' /> <FieldRef Name='Created' /> <FieldRef Name='Team' /> \
      </ViewFields> </viewFields> \
      <rowLimit>2000</rowLimit> \
      <query> <Query> \
        <OrderBy><FieldRef Name='Team' Ascending='TRUE' /><FieldRef Name='Title' Ascending='TRUE' /></OrderBy> \
      </Query> </query> \
     </GetListItems> \
     </soapenv:Body> \
     </soapenv:Envelope>";

$.ajax({
url: asmxlocation,
type: "POST",
dataType: "xml",
data: soapEnv,
async: true,
complete: processResult,
contentType: "text/xml; charset=\"utf-8\""
});

}

//**************************************************************************************** 
//process results from projects query
function processResult(xData, status) {

$(xData.responseXML).find("z\\:row").each(function() { 

myProjArray[$(this).attr("ows_ID")] = [$(this).attr("ows_Title"),$(this).attr("ows_ProjStatus"),$(this).attr("ows_Created"),$(this).attr("ows_Team") ]; 

//CAML Joins are only available in SP 2010+ so we got to loop
getProjectStatus($(this).attr("ows_ID")); 

});
} 

//****************************************************************************************
//get status records for a specific project
function getProjectStatus(ProjID){

var thisurl = window.location.href;
var i = thisurl.lastIndexOf("/");
var asmxlocation = thisurl.substr(0,i)
asmxlocation = asmxlocation + "/_vti_bin/lists.asmx";


var soapEnvZ = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
    <soapenv:Body> \
    <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
      <listName>Project Status Log</listName> \
      <viewFields> <ViewFields> \
         <FieldRef Name='ID' /> <FieldRef Name='ProjectID' /> <FieldRef Name='Status' /> <FieldRef Name='Created' /> \
      </ViewFields> </viewFields> \
      <rowLimit>1000</rowLimit> \
      <query> <Query> \
        <Where> <Eq> <FieldRef Name='ProjectID' /> <Value Type='Counter'>" + ProjID + "</Value> </Eq> </Where> \
        <OrderBy><FieldRef Name='Created' Ascending='TRUE' /></OrderBy> \
      </Query> </query> \
    </GetListItems> \
    </soapenv:Body> \
    </soapenv:Envelope>"; 

$.ajax({
url: asmxlocation,
type: "POST",
dataType: "xml",
async: true,
data: soapEnvZ,
complete: processResultZ,
contentType: "text/xml; charset=\"utf-8\""
});
}

//****************************************************************************************
//process results from the status query
function processResultZ(xData, status) {
var statusarray = new Array();
var arraycounter = 0
var projID;

$(xData.responseXML).find("z\\:row").each(function() { 
   statusarray[arraycounter++] = [$(this).attr("ows_Status"),$(this).attr("ows_Created")];
   projID = parseInt($(this).attr("ows_ProjectID"));
   myStatusArray[projID] = statusarray;
}); 

}

Now that we have the projects and statuses objects with a common property, the ID of the project, we can display the data any way we wish. createprojectstable() runs when the Ajax calls have completed and creates the HTML and JavaScript for the reports. I created a horizontal bar representing the length of time in each status for each project. There is also a drop down text area of the status details for each project. This is just another table row that is hidden and made visible by clicking the table row containing the graph. Click on the different elements below to see a live demo.

 

Here’s the interesting parts of creating the report – looping through the properties of the project and statuses objects to build the output HTML. (Note that, even though they are named “array”, they are actually objects.) And finally, we’ll hide the loading image and notification <div>.

function createprojectstable(){

var outHtml = "..."

//create report headings

for (prop in myProjArray) { 
  if (!myProjArray.hasOwnProperty(prop)) { 
    continue; //The current property is not a direct property of p, so goto the next one. i.e. it is not a poperty we created but a built-in JavaScript     property 
  } 

  //create HTML for each project

  if (myStatusArray[prop] !== undefined){
    //and now create the status details for the project
  }
  outHtml += "..."
  $("#listLinks").append(outHtml); 
  $("#wait").hide(); 

  //create report footers and initiate any other report options
}

You can download the source of a working example. This one reads XML files instead of SharePoint lists but is essentially the same. I’ve only tested the code in IE 8 and Firefox 5 but it would probably be quick work to make it cross-browser compatible.