Quantcast
Channel: Marc D Anderson's Blog » jQuery library for SharePoint Web Services
Viewing all 109 articles
Browse latest View live

Coming Soon: Caching in SPServices

$
0
0

For a long time, I’ve been reluctant to try to add caching to SPServices. The main reason is that I couldn’t think of a reliable way to do it that wasn’t going to cause potential issues and it also had to be backward compatible. Well, I think I’ve cracked that nut. In the current alpha of SPServices (v0.7.2ALPHA3), I’ve implemented a caching capability.

In my SPServices development environment, I have a ridiculous page which has far more calls to SPServices than you are likely to ever implement on one page yourself. It’s the page I use to test all of the potential combinations of calls that you might ever want to make to ensure that they work consistently. My testing is certainly not always foolproof, but this page has served me well to get most of the obvious kinks out for every version since the beginning of the library. (It runs in WSS 3.0, so I start there and move up the chain to test things with cross-version capabilities.)

Just to show how ridiculous it is, here are some counts:

Function Number of Calls
SPFilterDropdown 3
SPArrangeChoices 2
SPSetMultiSelectSizes 2
SPCascadeDropdowns 3
SPLookupAddNew 5
SPDisplayRelatedInfo 4
SPAutocomplete 1

At first glance, that may not seem so bad, but it takes 47 calls to various Web Service operations, and therefore 47 AJAX calls, just to load the page. It can take anywhere from 5-8 seconds on average, though sometimes it’s far worse.

After implementing the caching capability, I’ve cut that down from 47 to 27 calls, which is a 43% reduction, if I’m doing my math right. Even better, the load time is now consistently around 3.5 seconds. (I’m timing things in Firefox because Firebug makes it easy.)

Better yet is the performance improvement I see when I start changing values in the dropdowns, which causes more calls. The way I’ve implemented this, I’m caching the results for every single unique SOAP message SPServices sends to the server. This means that I’m not trying to cache all the items in any list, but only the XML which is returned for each request. Since each request has a unique “fingerprint” in the form of the SOAP message itself, I have a key to use to store the cached XML object and a way to retrieve it. The jQuery .data() function is what powers this simple little idea.

The way this works is somewhat clumsy: you only get a benefit if you make a call that is exactly the same as a prior call. However, that’s not as uncommon as you think, especially under the covers in the value added functions like SPCascadeDropdowns and most of the others above.  Many of the value added functions must call GetList for one reason or another, and just caching those calls cuts out something like 15% of the calls in my bloated page.

Now that I’ve posted this new alpha, I *really* need some help regression testing. Right now, the caching is all or nothing. There’s a default option called cacheXML, and it’s set to true in the alpha, so everything that can be cached will be cached. (When I release this functionality for real, the option will be set to false so that nothing works differently in existing applications.)

So here’s how you can help. Download the alpha and drop it into a test environment where you are already making SPServices calls. Try some tests with the alpha as it is, and then with the cacheXML option set to false. With Fiddler or Firebug, you should be able to see the SOAP traffic and get a gauge on what sort of improvement you might be able to gain. Let me know what you see and whether you have any problems. You can pass cacheXML: false into any call to SPServices if you need to turn off the caching for that call. Try some debugging, too, because I don’t want to make anything work differently than it has in the past.

The idea is simple, but it may well cause problems I haven’t thought about. Let me know how things go as a comment to this post, or better, in the SPServices Discussions, where it’s easier to post and format code. I think you’re going to like this!


10,000 Downloads of SPServices v0.7.1

$
0
0

Today, July 6 at about 4pm EDT, SPServices v0.7.1 received its 10,000th download Because I’m amused by this sort of thing, I was watching on my iPhone from my friend’s beach house.

The animated GIF below shows it as I saw it happen. Notice that Toby Mai (@GrumpyTech) tweeted me right after he did the ceremonial 10,000th download.

Because Toby was the lucky 10,000th downloader, he has received a lifetime free subscription to SPServices. Enjoy, Toby!

It’s fun to save these little milestones for SPServices here on my blog for posterity. Thanks to everyone who has helped out along the way!

SPServices 1.7.1 10000

@ Glad it was you. The 10,000th downloader gets a free lifetime subscription!
@sympmarc
Marc D Anderson
That's project lifetime 53,032 downloads; 10,000 for the latest version (since 13 Feb). http://t.co/T9HswsnK #SPServices
@sympmarc
Marc D Anderson

SPServices v0.7.2 is Almost Ready for Beta

$
0
0

It’s been a bit too long since I’ve released a new version of SPServices. It was mid-February when I released v0.7.1, and I’ve been adding new functionality and squashing bugs since then. At this point, I believe that I have a pretty stable beta ready.

If you have the time and the inclination, I’d truly appreciate any testing you could do. If you’re already using SPServices and could pop this new version into your test environment to look for regressions that would be great. Unfortunately a few regressions crept into v0.7.1 and I didn’t catch them until too long after I released it.

I’m going to release the beta in a few days and this time let it stand as a beta for a few weeks. There are some pervasive changes in this release that I think may be really useful for everyone, but since there have been almost 13,000 downloads of v0.7.1, I want to be sure that I take care of anything that might be a problem before general release – or as Microsoft would say, RTM.

The two impressive things in this release – if I do say so myself, and I do – are caching and a new function called SPFindPeoplePicker.

The caching I’ve added into this version of SPServices is somewhat rudimentary, but at the same time it can save a decent amount of traffic back and forth with the server. It uses jQuery’s .data() functions to cache the XML for each request which has the option cacheXML: true. This is basically brute force. If cacheXML: true, then the returned XML is saved as a data object on the body of the page with the request XML as the key. If you make the exact same call again, then the request is fulfilled from that cache. Note that it’s “exactly the same call”. This means that the request XML has to match exactly.

I’ve set the option to true for some of the internal calls to the Web Services operations to speed things up when you use functions multiple times. For instance, quite a few of the value-added functions make a call to GetList to get details about the current list, and there’s no need to make that call more than once during the page life. If you are creating more than one cascade with SPCascadeDropdowns, for example, you’ll see an immediate improvement.

The SPFindPeoplePicker function helps you find and set People Picker columns. (The documentation is rudimentary at the moment. Don’t worry; I’ll improve it before the release.) These little buggers are problem enough as it is, and selecting them and manipulating them with jQuery is something that a lot of people want to do. Add to that the fact that the People Picker is rendered differently in IE than in other browsers, and you have a challenge not unlike simple vs. complex dropdowns.

There are other goodies in this release, and you can see the full list of enhancements and improvements on the download page. Note the link to the Issue Tracker items for this release. (Issue Tracker is a horrible name – some of this stuff is simply new functionality.)

As I’m getting ready for the release, I’ve been looking back at some of the feedback and suggestions I’ve gotten since the release of v0.7.1, of course. Particularly gratifying are the reviews that people have left for that version. I’ve captured them below. If you’re not using SPServices as part of your SharePoint development toolkit, maybe you should be!

image

SPServices v0.7.2 Countdown, Updates, and Futures

$
0
0

Inukshuk near Golden, BC, CanadaHello, all! I was away on a fantastic trip to Western Canada.I posted BETA1 before I left and there have already been over 500 downloads. I’m back and ready to push out the v0.7.2 release of SPServices as soon as I work through the feedback I received while I was gone.

I haven’t seen any significant issues except that I heard that SPServices wouldn’t work with jQuery 1.8.x. As usual, the jQuery team made some subtle changes - no doubt improving the library overall – that had an impact on SPServices. Luckily, this one was a pretty easy fix (for those of you interested in details, here’s the issue in the Issue Tracker), and it seems to work just fine with the earlier versions of jQuery I’ve tested as well. Of course, I’m just one guy, so any testing that you can do with the BETA2 that I posted last week would be great for the SPServices community. If there’s one thing I’d ask of anyone who uses SPServices extensively, it’s help in testing new versions.

Another thing I wanted to comment on was a question from Paul Tavares (@paul_tavares) due to a comment I made in the SPServices Discussions.

@ just saw a post on SPServices forum from you that stated SP SOAP API is deprecated in 2013. What's the alternative? REST API?

Notice

By the way, the Discussions is the right place to start any threads on SPServices, NOT the Documentation pages. Codeplex doesn’t give me any good way to track comments on the Documentation, so odds are I’m going to miss your question or comments – there are a lot of documentation pages and I can’t check them all regularly. Also, starting with an item in the Issue Tracker is often not the best thing because code posted there cannot be formatted.

Microsoft has officially said that the ASP.NET (SOAP) Web Services have been deprecated in SharePoint 2013 – thanks to Rene Modery (@modery) for the link.

Two API sets are still supported in the SharePoint 2013 Preview framework for backward compatibility, but we recommend that you not use them for new projects: the ASP.NET (asmx) web services, and direct Remote Procedure Calls (RPC) calls to the owssvr.dll file.

So while the SOAP Web Services are deprecated, they still exist in SharePoint 2013, though they will go away sometime in the future. However, that future could be quite a way off (2016? 2019?) because earlier versions of Office rely on the SOAP Web Services to interact with SharePoint. The Microsoft replacement to SOAP is the REST interface, along with the Client Side Object Model (CSOM). In SharePoint 2010, the coverage of functionality in those two options is limited, however. In 2013, they have been significantly extended, but I haven’t yet seen (or done) a side-by-side comparison with SPServices and REST and CSOM in SharePoint 2013. I believe that the SOAP Web Services will still have better coverage in SharePoint 2013.

All that said, I believe there’s still a “long tail” for SPServices, at the very least. People are still using SharePoint 2007 and 2010 and will be for a long time. This is certainly true of my client base, as well as in the wilds, based on my conversations at conferences. Upgrades are very expensive – hardware, licensing, but most importantly time – and many organizations are making damn good use of the existing versions of SharePoint.

This means that SPServices isn’t going to become useless anytime soon. I’m going to continue maintaining it and extending it. I still see it as an extremely viable development tool for SharePoint. One of its best qualities is that it works as you move from SharePoint 2010 to SharePoint 2010 and now SharePoint 2013. You may need to tweak your code, but you won’t need to rewrite it. None of Microsoft’s client side tools can do that for you!

Stay tuned for more updates about the release, and let me know if you have any issues or feedback.

Generic Error Handling in SPServices

$
0
0

Recently, I got this question in the SPServices Discussions:

I’m new to SPServices, and I was wondering if it is possible to define some kind of out-of-the-box error handling procedure, like this:

$().SPServices ({
operation: "DoSomething",
[option1: value1],
[option2: value2],
    // ...
success: function (xData) {
        alert ("The operation has been completed!");
        // some processing code ...
    },
fail: function (jqXHR) {
        alert ("Something went wrong!");
        // some fallback code ....
    }
});

The only thing I am aware of is that you can do something like this:

completefunc: function (xData, Status) {
    if (Status == "Success") {
        alert ("The operation has been completed!");
    } else {
        alert ("Something went wrong!");
    }
}

However, this solution will also execute the success-branch when the server returns an XML-document containing error messages. Above that, I don’t think it’s that elegant because you would have to check the Status == “Success” every time you define a new request, which looks a little bit like overkill.

Any suggestions?

By the way: really love this project!

I was on vacation with only my iPhone (my first laptop-free vacation in probably 10 years!) so I waited until I returned to answer. The question is similar to many others I’ve received since I started working on SPServices.

Oh, I’d love to add something like this! Unfortunately, it’s not that simple.

There are two main layers where an error can occur:

  1. In the AJAX call itself. This really only occurs if the endpoint (SharePoint) isn’t responding, almost always due to the servers being down. SOAP may be an old, crufty standard, but it’s damn reliable.
  2. In the Web Service itself. Unfortunately there is almost zero consistency in the implementations. No two operations respond in quite the same way, and there’s really very little in common across the Web Services. Some operations return only one error message regardless what you’ve done wrong, and others return 5 or 6, but inconsistently. Some responses contain heavily nested XML while others contain a single element with many attributes. The only real commonality is that they return XML.

In all of my use of SPServices, I’ve found a few truths.

  1. SharePoint always responds if it’s up and running.
  2. The only time a Web Service call fails is if you pass it bad data or poorly formed XML.
  3. If you get a valid response, then the operation succeeded.

Early on I decided that my job in SPServices is to reduce the likelihood of 2. By eliminating the need to construct the SOAP envelopes, SOAP headers, etc., a huge number of errors simply never happen. It’s up to you to test the XML coming back to see if it contains what you expect.

While I could add error handling for some of the core operations, I would have to assume quite a few things about what you are doing. For instance, you might be calling GetListItems specifically to see if you get zero items back, and that might be a success to you.

What I *have* done is to add the debug mode to many of the “value-added” functions. Because I know what ought to be coming back when I make calls to the various Web Services, I can reasonably provide some error messages that are useful for you to use in setting things up. Things like this debug message:

SPCascadeDropdowns Debug Message Example
Which brings me to another important point: what should we do when there is an error? I’ve vowed to myself never to write an error message like “An error has occurred. Please contact your administrator.” (What error? Is it important? Do I need to do something to fix it? Who’s my administrator? How do I contact them? What do I tell them?) Even something with a correlation ID which can only be deciphered by an administrator is too obtuse. It’s the user who needs to know what to do if there’s a problem.

Given the myriad ways people use SPServices, I can’t possibly predict what they are building or how it should behave. It is first a toolkit and second a power user tool. The core of SPServices should give a developer lean tools that can use how they see fit. Generic error handling isn’t going to help them much because some of the edge conditions are good in some cases and bad in others. In the value-added functions, I intend the debug mode to help power users to get things set up, and then things should be humming and they should turn it off.

But what do we tell the user if there’s a problem? “I couldn’t write that item. Please try again”? Remember that the SOAP Web Services are *extremely* reliable. I can’t even recall an instance where I’ve seen a call to an operation fail when it’s passed good data and the servers are awake. We can assume that calls are going to work.

What do you think about all of this? How would you suggest I improve SPServices error handling?

SharePoint Designer 2013′s Missing Design View – More Thoughts

$
0
0

I’ve written previously about SharePoint Designer 2013′s Missing Design View here on my blog as well as on the Microsoft forums and elsewhere.. My goal in writing about this and making a lot of noise about it is not to simply complain. (We have way more complaining about everything out here on the InterWebz than we already need.) People need to understand what not having the Design or Split View in SharePoint Designer means if they upgrade to 2013 so that they can make good decisions.

At this point, I’m going to be going with the assumption that the net-net  answer is “hire more developers”. (Note: Assumption)

If the Code View is the only available view in SharePoint Designer, and existing functionality has been implemented with XSL-driven Web Parts, such as Content Query Web Parts (CQWPs) and Data View Web Parts (DVWPs), then there are two main options:

  1. Maintain the DVWP in Code View, which requires an XSL able developer. This is not at all easy. The XSL that SharePoint Designer generates – it is essentially playing a code generator role here – is horrible. There are some reasons for this that are legitimate: in order to be prepared for that next button you click on to change what you want to display in a DVWP, SharePoint Designer includes all sorts of stubs in its XSL For years, I’ve simply thrown away most of the SharePoint Designer -generated XSL and written my own because of this. However, altering existing DVWPs that have been implemented by power users become a development task going forward with 2013 even if a developer wasn’t needed before. It’ll have to be direct customization of the XSL in the Code View.
  2. Rearchitect the solution to use client-side processing. This is where Microsoft is clearly heading. The client-side push that started in SharePoint 2010 continues in 2013. Microsoft has expanded the Client Side Object Model (CSOM) and the REST Services. In many cases, the exact same functionality we have used DVWPs to build can be built with this new paradigm, but there will, of course, be cases where we have to make some trade-offs. Regardless, it becomes a developer task, as I can’t see many power users choosing to learn scripting.

If you follow me at all, you know that I love using client-side processing.with SharePoint to greatly improve the user experience (UX). The tools of the trade here are CSS, JavaScript and the many things that people have built to provide extended functionality, like jQuery, SPServices, and so many libraries, plugins, and add-ons it’s impossible to count. This is a great thing for SharePoint. As I’ve been saying for years now, it opens up SharePoint development to herds of new people who already have those Web development skills

But I also see a huge downside to this rush to client-siide development. The reason I coined the term “SharePoint’s Middle Tier” (OK, OK, I know most people hate the name – I can’t say it without the disclaimer) is that we do processing on the server *and* the client. We choose what makes the most sense in load distribution and put each chunk in the right place. Using a DVWP to do the heavy lifting of getting the initial load of data onto the page often covers a high percentage of use cases. Then we layer script (the other code) onto the page to provide behaviors and interactivity with the data without postbacks. By pushing for client-side processing exclusively, we lose that capability to architect well-balanced solutions (without cracking open Visual Studio – which Microsoft has started to discourage).

The biggest issue here is that we go from minimal error assistance – which is only shown in the Design or Split Views – to zero error assistance. Even a well-trained XSL developer or an XSL monkee like me will get zero error messages other than the essentially useless

Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Windows SharePoint Services-compatible HTML editor such as Microsoft Office SharePoint Designer. If the problem persists, contact your Web server administrator.

error message in the browser and have to figure out what error we’ve introduced by feel.

Design and Split View for most of us who do this kind of work has nothing to do with “design”. The argument that SharePoint 2013′s new Design Manager allows us to design with something like Dreamweaver is somewhat specious. Instead, we use SharePoint Designer to develop real solutions using markup, DVWPs, script, etc. The Design View is where SharePoint Designer shows us what is going on. When I intentionally created a error in a DVWP to get the text for the error above, SharePoint Designer was telling me “This Web Part does not have a valid XSLT stylesheet: Error: A name was started with an invalid character. <<< “. That’s useful – I had added a couple extra < signs and it told me that I had. We totally lose that with no Design or Split View. The upshot of this is that there will be no reasonable way to build this type of solution or maintain existing ones. QED

Thus, we end up at option 2, which is a rearchitecting all the existing solutions which rely on development using the Design or Split Views. IT shops have used these methods as much as power users, at least at my clients, so it is my firm belief that this issue is going to be big and pervasive.

So, what’s the upshot of all of this? Well, if you plan to upgrade to SharePoint 2013 and have used the development methods I talk about here, then keep your eyes open to the level of effort it may require. Do a very careful inventory of solutions that you already have in place that were built using SharePoint Designer and think about what you will do with them in 2013. It’s not hopeless, but it’s going to be a lot of work.

jQuery Library for SharePoint Web Services (SPServices) v0.7.2 Released

$
0
0

Today I’m releasing SPServices v0.7.2. If you are using an earlier version of SPServices, I suggest that you upgrade to this version, as you will most likely see some performance improvements, especially if you use multiple value-added functions on the same forms pages.

Thanks to everyone who downloaded the beta over the last month or so and provided feedback. The most important piece of feedback I received was an incompatibility with jQuery 1.8.x, which I was able to fix and get into this release.

Caching

I’ve added a caching option into this version of SPServices and it can save a decent amount of traffic back and forth with the server, depending on how you are using things. It uses jQuery’s .data() functions to cache the XML for each request which has the option cacheXML: true. This is basically brute force. If cacheXML: true, then the returned XML is saved as a data object on the body of the page with the request XML as the key. If you make the exact same call again, then the request is fulfilled from that cache. Note that it has to be the “exact same call”. This means that the request XML has to match exactly.

I’ve set the option to true for some of the internal calls to the Web Services operations to speed things up when you use functions multiple times. For instance, quite a few of the value-added functions make a call to GetList to get details about the current list, and there’s no need to make that call more than once during the page life. If you are creating more than one cascade with SPCascadeDropdowns, for example, you’ll see an immediate improvement. However, since I can’t predict how many values you may have coming back from GetListItems calls, I haven’t turned on caching pervasively. I didn’t want to break things in an effort to be more efficient. Some client machines may not have the horsepower to cache large volumes of data, etc. If you know your data and your client machine capabilities well, you can set the option to true in the defaults – $().SPServices.defaults.cacheXML = true; – which will affect all Web Service operation calls in the current page lifecycle.

You can use the cacheXML: true option with any Web Service operation in SPServices Core, though as you can imagine some operations will benefit more than others.

SPFindPeople Picker

The SPFindPeoplePicker function helps you find and set People Picker columns. These little buggers are problem enough as it is, and selecting them and manipulating them with jQuery is something that a lot of people want to do. Add to that the fact that the People Picker is rendered differently in IE than in other browsers, and you have a challenge not unlike simple vs. complex dropdowns.

SPFindPeoplePicker allows you to get or set the values for People Pickers. When you call it with the DisplayName of a People Picker, the function returns an object which contains information about the People Picker’s structure nd current value. For instance, calling the function like this:

var salesRep = $().SPFindPeoplePicker({
  peoplePickerDisplayName: "Sales Rep",
  valueToSet: "APPTIXemullerbeck53"
});

returns an object which contains the following:

SPFindPeoplePicker Object

peoplePickerDisplayName

The DisplayName of the People Picker in the form.

row

This is reference to the table row which contains the People Picker. This can be useful if you want to hide or show the row conditionally based on some user action.

contents

The full contents of the div[name='upLevelDiv'] element.

currentValue

The current value set in the People Picker. If you pass a value into the function, it will be set and returned in this string. If there are multiple people specified, they are returned separated by semicolons, as in the People Picker display.

checkNames

This is a reference to the checkNames img tag in the People Picker. It’s used by the function to initiate resolution of a Person or Group value by firing the click event on it. Once you have this reference, you can do the same.

dictionaryEntries

If the browser is Internet Explorer, then this object will contain the full dictionary entry values for each user or group in the People Picker value. If the browser is not IE, then the function calls GetUserInfo to retrieve similar values to mirror the dictionary entry structure.

Other Release Notes

There are other goodies in this release, and you can see the full list of enhancements and improvements on the download page. Note the link to the Issue Tracker items for this release.

For posterity, the release notes are included below. You can check out all the changes (the release pages have a character limit for the release notes) in the Issue Tracker.

New Functionality

Alpha Issue Tracker Item Function Operation Description
ALPHA1 10071 $().SPServices.SPXmlToJson NA Enhanced userToJsonObject for use when ExpandUserField = True
ALPHA3 10074 $().SPServices.SPFindPeoplePicker NA Add SPFindPeoplePicker Function
ALPHA4 10070 $().SPServices.SPXmlToJson NA SPXmlToJson – calculated fields and “blank” xml attributes
ALPHA5 9969 $().SPServices.SPGetStaticFromDisplay and $().SPServices.SPGetDisplayFromStatic NA Getting multiple columns in one SPGetStaticFromDisplay call

ALPHA3 – Added caching capability to the SPServices core AJAX function to make it available across all SPServices value added functions and Web Services operations.

ALPHA7- Fixes to SPFindPeoplePicker to work with non-IE browsers.

New Operations

Alpha Web Service Operation Options MSDN Documentation Issue Tracker Item
ALPHA1 Diagnostics SendClientScriptErrorReport message, file, line, client, stack, team, originalFileType SharePointDiagnostics.SendClientScriptErrorReport Method 10028
ALPHA2 People ResolvePrincipals principalKeys, principalType, addToUserInfoList People.ResolvePrincipals Method 10062
ALPHA5 Lists AddListFromFeature listName, description, featureID, templateID Lists.AddListFromFeature Method 10064
ALPHA5 SpellCheck SpellCheck chunksToSpell, declaredLanguage, useLad SpellChecker.SpellCheck Method 10063
ALPHA5 Lists various NA see work item 9884

ALPHA2 – Added these Sites Web Service operations: CreateWeb, DeleteWeb, GetSite, GetSiteTemplates

Bug Fixes and Efficiency

Alpha Issue Tracker Item Function Description
ALPHA1 10061 $().SPServices.SPDisplayRelatedInfo Not Properly Handling UserMulti Columns
ALPHA1 9926 $().SPServices.SPCascadeDropDowns ‘&’ symbol values
ALPHA1 9925 NA dropdownCtl bug in Italian Sites
ALPHA5 9931 $().SPServices.SPGetLastItemId missing reference

SharePoint Designer 2013′s Missing Design View – It’s Official

$
0
0

Well, Keenan Newton did a post on the Microsoft SharePoint Team Blog entitle Changes to the Design View in SharePoint Designer 2013 yesterday that makes it official: the Design View in SharePoint Designer 2013 is not just missing, it’s not coming back. I’m reproducing Keenan’s post below, but you should also read it in situ, as the comments are already piling up, and they aren’t positive.

Hi, I’m Keenan Newton, a senior product marketing manager on the SharePoint team.

We’re making some changes to the Design View in SharePoint Designer 2013, and I wanted to talk about the reasoning behind the changes.

With SharePoint Server 2013 embracing new web standards for client side rendering of pages such as JavaScript, JSON, and OData, there is no longer a need to support a visual web page editor within SharePoint Designer. With that in mind, we removed the ability to visually edit pages in SharePoint Designer 2013 because its page editor is designed to only understand the unique features of a SharePoint web page. With our support of new web standards, any web page designer can now be used for editing web pages in SharePoint Server 2013. This includes form customization, conditional formatting of page content, layout, theming and branding. To simplify the process of integrating customized SharePoint pages, SharePoint Server 2013 includes a new feature called the SharePoint Design Manager. This feature enables a web designer to export a web page from SharePoint, customize it, and then import it back into SharePoint, all right from the SharePoint site.

SharePoint Designer 2013 will continue to support site, workflow, list, library, and external data customization and configuration. However, we will look for opportunities to leverage SharePoint itself as the primary tool for customization and configuration tasks.

I’m not going to be politic about it: this is going to hurt Microsoft and it’s a horrible decision. The things you may be able to use Design Manager to do and workflows are not what the majority of people use SharePoint Designer for. Code View will do those same people virtually zero good, and there’s no replacement strategy.

Here are some of my specific beefs with the “official” stance that Keenan puts forth.

…we removed the ability to visually edit pages in SharePoint Designer 2013 because its page editor is designed to only understand the unique features of a SharePoint web page.

Exactly! The reason we choose to use SharePoint Designer is *because* it understands ”the unique features of a SharePoint web page”. Nothing else does. For better or worse, SharePoint 2013′s forms, for example, are the same as they have been in 2007 and 2010, based on the List Form Web Part. No other editor knows what that is. SharePoint Designer does.

…any web page designer can now be used for editing web pages in SharePoint Server 2013.

This has always been the case, but who has decided to use Notepad or anything else in the past? We’ve always had the option to copy aspx page content out into some other editor to work on it, and that seems to be what Microsoft is recommending now. SharePoint Designer was and is the only IDE that understands SharePoint’s page structures, controls, and Data Sources. With any other tool, we have to know ourselves how to set up a Data Source in a DVWP or which controls to add to take advantage of SharePoint-specific functionality. In the newly neutered SharePoint Designer, many of the ribbon buttons actually serve no purpose anymore. Things like Conditional Formatting, DVWP formatting options, etc. don’t function unless we write our own code first. On top of that, there is no longer a surface where SharePoint Designer can show us errors in that code. Errors, cryptic or not, have always been shown in the Design (or Split) View. Now we will be flying blind.

To simplify the process of integrating customized SharePoint pages, SharePoint Server 2013 includes a new feature called the SharePoint Design Manager…

Maybe Microsoft believes that the Designer word in SharePoint Designer means that people only use it for design. As you and I know, design is only one small piece of how we use SharePoint Designer. Design Manager also is only useful if you want to apply a design via a master page or page layout to an entire Site Collection that has publishing features enabled. It doesn’t do us any good on individual pages where we want to make small customizations in the design or structure for single-location functionality.

All in all, I’m very disappointed in Microsoft’s decision on this one. As I’ve been telling people, I’ll be fine. As a consultant, I can keep working with SharePoint 2007 and 2010 for a long time and make a decent living at it. I can even learn to work in the Code View exclusively in SharePoint Designer 2013. I’m worried about all of the unsung heroes out there in SharePointland who have been using SharePoint Designer to build solutions that their organizations actually use, rather than what IT typically jams down their throats. Those of us who believe in user empowerment and citizen development have lost a battle this day.

Important!

For another great slant on all of this, check out Michal Pisarek’s (@michalpisarek) great post from yesterday SharePoint 2013 Design View Changes and Change Management 101. In it, he makes some excellent points about how the change management around all of this has been abysmal.

Where Should SPServices Go Next? Common Question at SPC12

$
0
0

This is just a quick post I’d like to use to gather your thoughts on where SPServices should go from here.

At The Microsoft SharePoint Conference in Las Vegas, going on right now, many people I’m talking to are asking me about my plans. I’ve been mulling it over for a few months now, and I have my own ideas. But I’d love to hear where you’d like to see SPServices fit into the SharePoint 2013 landscape. Let me know what you think in the comments, or over on Facebook here or here.

Here’s a photo of Jeff Teper speaking at the keynote today. I had a great view from the front row, center.

20121112-211403.jpg

Is Client Side Scripting a Good Way to Access SharePoint Data?

$
0
0

People reach out to me all the time to ask my advice on how to accomplish certain things with SharePoint. Sometimes I can help, and sometimes their questions are way beyond me. One of the benefits of being reasonably well known in the SharePoint space is that people tend to come to me with questions that are up my alley.

When the questions aren’t something I can handle, I usually suggest that they use one of the public forums out there. My favorite is SharePoint StackExchange and in a distant second, the MSDN Forums for SharePoint.

Other times, either due to how they found me or based on the content of their questions, things stay in an email exchange.

The other day, my friend and colleague Christina Wheeler (@cwheeler76) passed along some questions from someone she knew [I've edited each part of the exchange a little for clarity and anonymity]:

I was wondering if you have a good resource within your network that might have published a blog about the different ways to access Sharepoint list data (client side scripting using something like SPServices or server side scripting using a deployable web part). Also, are there any other ways to accomplish this? I know the data view web part is another method, but it has its limitations.

A good example would be wanting to combine data from a SharePoint list and an external list and display both back to the browser window. I thought SPServices would be an excellent alternative to the data view web part, because it wouldn’t require taking the server down to deploy or redeploy. However, there are concerns coming from my team that client side is not as fast, secure etc. So I am just looking to see if there are any resources out there that have addressed this as it relates directly to Sharepoint.

Well, she certainly arrived in the right place with those questions. Since SPServices is mine, I can certainly answer any questions about it better than most people. The first thing I did was refer her to the post that Mark Rackley (@MrAckley) and I wrote together called When to Choose SPServices vs. the Client Side Object Model (CSOM). That post has gotten a lot of reads and Mark and I still think it covers the topic pretty well.

That prompted the next set of questions:

Thank you so very much for pointing me to all this valuable information! [...] mentioned the SPServices [...], and from then, I was hooked. Honestly, it was the number one thing I took away [...] because of its potential to get around some Sharepoint limitations. To give you an example, we have a [...] calendar that is managed under the IT site for the Sharepoint intranet. We like to display a summary of the daily or upcoming downtime to appear on the home page. The only way to display this data from another sub site (before) was with an RSS feed. The problem with the RSS feed is when we get into reoccurring events on the Sharepoint calendar. Now, I abandoned the RSS feed in favor of SPServices and it works beautifully.

My problem comes in with trying to get everyone on board that client scripting is okay to use for returning SharePoint list data. This argument is a perfect example. I am familiar with both client side scripting (javascript, xslt, jquery) and server side scripting with C#, but I don’t claim to be an expert. The argument that has been presented to me is that server side scripting is preferred because:

1.) It’s more secure

2.) It’s browser independent

3.) It’s better from a performance perspective

I tend to disagree that server side scripting is always the better answer for Sharepoint solutions. For one, we are not talking about writing an ERP payroll system where I plan to return thousands and thousands of records, so the amount of data I plan to return from a SharePoint list is minimal enough that I don’t think performance is a concern. For browser independence, that’s why I use jquery. And I don’t really know the answer about security. I think it comes down to what I want to do with the javascript. I am talking about using js to return a Sharepoint list. In this scenario, there is no increased threat of any kind by choosing to use client side javascript over a custom deployable web part written in C#. The case I am trying to argue is that using client side scripting to access a Sharepoint list is now possible, and in some cases, more desirable because it is easier to test, deploy, update and would not require any server downtime. I would just like to know your thoughts on this, or perhaps any resources…another blog or white paper you wrote that touches on best practices for when to use client side scripting vs server side development? Please bear with me because I have only been using Sharepoint for 6 months. Are there any other options to displaying Sharepoint list data other than using client side scripting like SPServices or CSOM, or to write an ASP.net page or deploy a custom web part that uses the Sharepoint Object model reference? I’m sure this is such an obvious answer that I have never really understood the answer to!

We plan to debate this issue tomorrow and I am just trying to make sure I understand the concerns/risks about coding for Sharepoint, specifically. There are lots of resources that touch on when to use client side scripting vs server side coding, but I think it’s not so black and white when it comes to working with Sharepoint.

And my reply was this:

You’re spot on in your thinking. This is a classic “it depends” scenario, of course. A good architect looks at all of the possible tools to solve the problem and decides where and when to use each one. Client side scripting is an excellent option for some things and just downright a bad idea for others.

The security argument is moot. The Web Services fully respect the permission model. If the user can’t access something through the UI, they can’t through the Web Services, either. QED. In a way, it’s even *more* secure than server side code since it’s impossible to elevate permissions, which many developers do as a common practice whether it’s needed or not.

Here are some more posts which may be helpful [each here on my blog]:

http://sympmarc.com/2011/05/27/elevating-permissions-with-sharepoints-web-services/

http://sympmarc.com/2011/06/06/using-script-to-hide-content-not-always-a-good-idea/

http://sympmarc.com/2011/05/18/sharepoint-myths-part-2/

Finally, the best argument that you can make is that Microsoft is going to a distributed, client side development model in SharePoint 2013, relying on (you guessed it) JavaScript and jQuery much of the time. If they are baking it into the product set by expanding their version of the API (versus mine with SPServices) how can someone argue that client side development is flat out a bad idea?

I don’t know if this little exchange will be useful for anyone else or not. However, it seems like every time I answer questions like there, it helps me to crystalize my thinking a little more. If it’s helpful to others, great. Otherwise, at least I had a little fun writing it up.

SPServices Stories #1 – How to Start a Workflow on Multiple Items in a List

$
0
0

Introduction

Given the fact that so many people are using SPServices these days, I wanted to start posting some of the great things that people are doing with it out there.

If you have a story about how you are using SPServices that you would like to tell, ping me using the contact form. Your story doesn’t even have to include code, though people love to see examples. I’m always interested in what impact using SPServices may have had on your development philosophy, time to market with solutions, hiring practices, really anything that you feel SPServices has enabled you to do.

We can remove any identifying details if you feel that need to do so, but I’d like these stories to show off what *you* have done, so it’s great if you can take credit. I reserve the right to do a little editing for clarity, but otherwise you can write your own story. I’m also happy to help.

The first guest post is from fellow SharePoint MVP, Alberto Diaz Martin, who lives in Spain. Thanks to Alberto for kicking this series off!

Important!

You can also read this post in Spanish on Alberto’s blog SharePoint 2010. Iniciar un flujo de trabajo en múltiples elementos de una lista

How to Start a Workflow on Multiple Items on a List

In SharePoint 2010 we have the option to select multiple items on a list. When you select several items, the ribbon allows you to Delete Items and Send Alerts, but where is the Workflow command?

To start a workflow on a list item, you have to go through the Start workflow page and if the workflow has an initialization form, you also have to enter the parameters. Because of this, SharePoint doesn’t allow users to start a workflow on multiple items simultaneously. But why not do so when we have a parameter-less workflow?

I think this is a missing feature on SharePoint 2010 because we can certainly do it using the SharePoint API or Web Services without any problems. What can we do to provide this capability to our users?

First, we need to create a Ribbon command using a Custom Action and in this action we will have two options to try to start the workflow. The first one uses an application page by passing the selected items as parameters and uses the server API to start the process. The second, and more flexible and elegant option is using JavaScript and the SharePoint Web Services to start each workflow per item.

SPServices Workflow Ribbon Custom Action

SharePoint Web Services are a horrible way to talk with SharePoint [ed: I disagree, but everyone is entitled to their opinion!] because the Web Services use XML to get and put parameters and options, and it’s not easy working with XML in JavaScript.

SPServices to the Rescue!!

As you know, SPServices is a jQuery library which encapsulates SharePoint Web Services with jQuery to make it easy to call them. SPServices has a Workflow namespace with some powerful operations and we can use StartWorkflow to start an item workflow, even if it has parameters.

It is so easy to use, you only need the Item URL, the workflow template Id and, if required, the workflow parameters.

$().SPServices({
  operation: "StartWorkflow",
  item: currentItemURL,
  templateId: workflowGUID,
  workflowParameters: workflowParams,
  async: true,
  completefunc: function () {
    SP.UI.Notify.addNotification("Workflow process started on selected item.", false);
  }
});

To get the workflow template Id, we have another function called GetTemplatesForItem that returns all the associated workflows for an item. All we have to do is get all the templates and find our workflow by name.

$().SPServices({
  operation: "GetTemplatesForItem",
  item: itemURL,
  async: true,
  completefunc: function (xData, Status) {
    var currentItemURL = this.item;
    $(xData.responseXML).find("WorkflowTemplates > WorkflowTemplate").each(function (i, e) {
      if ($(this).attr("Name") == "Invoice Approve") {
        var guid = $(this).find("WorkflowTemplateIdSet").attr("TemplateId");
        if (guid != null) {
          workflowGUID = "{" + guid + "}";
          //in this point, we have our workflow Id and we have to call the starting method
        }
      }
    }
  }
})

Now, we have to traverse the selected items in the custom action method, and for each item call the SPServices StartWorkflow method. Something like this:

function StarSignWorkflow(listId) {
  RemoveAllStatus(true);
  waitDialog = SP.UI.ModalDialog.showWaitScreenWithNoClose('Starting approval workflow process on selected item', 'Please, wait until we finished this long operation.', 76, 400);

  //Get the selected items
  clientContext = new SP.ClientContext.get_current();
  var web = clientContext.get_web();
  var list = web.get_lists().getById(listId);
  var items = SP.ListOperation.Selection.getSelectedItems(ctx);
  totaSelItems = items.length;

  //Because only have items Id, we need to use Client Object Model to get EncodeAbsUrl.
  var query = new SP.CamlQuery();
  var queryString = '';
  for (index in items) {
    var valueString = '' + items[index].id + '';
    queryString = queryString + valueString;
  }
  query.set_viewXml(queryString + '');
  this.collListItems = list.getItems(query);
  clientContext.load(collListItems, 'Include(EncodedAbsUrl)');

  //In the success callback, we’ll have all the selected items with the absolute url.
  clientContext.executeQueryAsync(Function.createDelegate(this, this.onInitProcessSuccess), Function.createDelegate(this, this.onInitProcessFail));
}

function onInitProcessSuccess() {
  var listItemEnumerator = this.collListItems.getEnumerator();

  //If our workflow has default init param, we can provide it in this way to run workflow with default values.
  var workflowParams = "<data>" +
    "" +
    "" +
    "</data>";
  try {
    var counter = 1;
    var total = totaSelItems;

    //Traverse all the selected items
    while (listItemEnumerator.moveNext()) {
      var oListItem = listItemEnumerator.get_current();
      var itemURL = oListItem.get_item('EncodedAbsUrl');
      var workflowGUID = null;

      //Before start the workflow, we used GetTemplatesForItem to get Workflow Template Id.
      $().SPServices({
        operation: "GetTemplatesForItem",
        item: itemURL,
        async: true,
        completefunc: function (xData, Status) {
          var currentItemURL = this.item;
          $(xData.responseXML).find("WorkflowTemplates > WorkflowTemplate").each(function (i, e) {
            if ($(this).attr("Name") == "Invoice Approve") {
              var guid = $(this).find("WorkflowTemplateIdSet").attr("TemplateId");
              if (guid != null) {
                workflowGUID = "{" + guid + "}";
                $().SPServices({
                  operation: "StartWorkflow",
                  item: currentItemURL,
                  templateId: workflowGUID,
                  workflowParameters: workflowParams,
                  async: true,
                  completefunc: function () {
                    if (total == counter) {
                      if (waitDialog != null) {
                        waitDialog.close();
                      }
                      SP.UI.Notify.addNotification("Started workflow approved process for selected invoice.", false);
                      window.location.reload();
                    }
                  counter++;
                }
              });
            }
          }
        });
      }
    });
  }
  } catch (e) {
    if (waitDialog != null) {
      waitDialog.close();
    }
    AddStatus("There is an exception. Error: " + e.message, "red");
  }
}

As you can see, you have an easy way to provide an easy way to start a process on multiple items at the same time. Thanks to SPServices, working with SharePoint client side is more flexible and easy.

AlbertoDiazMartinAlberto Diaz Martin
MVP SharePoint
adiazcan@hotmail.com
@adiazcan
http://geeks.ms/blogs/adiazmartin

SPServices Compatibility Issues with the Minified Version of jQuery 1.9.0

$
0
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.

SPServices Stories #2 – Charting List Data with HighCharts

$
0
0

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 Stories #3 – AddWebPart Method of the WebPartPages Web Service

$
0
0
This entry is part 3 of 3 in the series SPServices Stories

Introduction

Eric Alexander (@ejaya2) should be no stranger to those of you who have visited NothingButSharePoint‘s Stump the Panel forums. If you’ve ever visited those forums, odds are very good that Eric has helped you out. He’s a stalwart member of the SharePoint support community.

Eric wrote a post on his blog about using SPServices to add Web Parts to existing pages that I thought was worth sharing as an SPServices Story.

AddWebPart Method of the WebPartPages Web Service

There are a lot of documentation black holes out there in the Sharepoint land and it seems that the WebPartPages web service is one of them.

In a project I’m currently working on, there is a need to automate a project creation process that is very manual to something more automated. Fortunately we have Nintex Workflow to handle situations like this.​ Part of this workflow is to provision a new subsite after the project is approved by the project manager. Nintex makes this easy with their LazyApproval feature and create site action.

What I needed to do was upon site creation from a template, add some web parts to the home page. This is where I was running into the huge documentation gap. I turned to one of my goto libraries, SPServices, to protoype the actual calls before porting it over to my workflow. Fortunately someone had tried to do this same thing in the past and provided a working example for adding a content editor web part. That is described here and worked no problem. My issue is I need to add list view web parts of document libraries and lists. I tried many things over the span of a couple days to tweak that to get my web parts onto the page. No dice.

Today I stumbled upon a post by Glyn Clough that filled that documentation black hole.

I need to use the List View Web Part markup like this for lists:

<?xml version="1.0" encoding="utf-8" ?>
<webParts>
 <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
  <metaData>
   <type name="Microsoft.SharePoint.WebPartPages.XsltListViewWebPart, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
   <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
  </metaData>
  <data>
   <properties>
    <property name="ListUrl" type="string">Lists/CustomList</property>
    <property name="ExportMode" type="exportmode">All</property>
   </properties>
  </data>
 </webPart>
</webParts>

and like this for document libraries:

<?xml version="1.0" encoding="utf-8" ?>
<webParts>
 <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
  <metaData>
   <type name="Microsoft.SharePoint.WebPartPages.XsltListViewWebPart, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
   <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
  </metaData>
  <data>
   <properties>
    <property name="ListUrl" type="string">Library</property>
    <property name="ExportMode" type="exportmode">All</property>
   </properties>
  </data>
 </webPart>
</webParts>

Once escaped and passed into my SPServices function I had web parts on my web part page.

SPServices Code

Eric’s post doesn’t have the SPServices call in his post, so I thought I’d provide an example here. This is built from the example from the documentation on the SPServices Codeplex site for AddWebPart. It adds an XLV Web Part showing the default view of my Tasks list to the home page of my root site and shows me an alert with the results.

var str = "&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;" +
"&lt;webParts&gt;" +
 "&lt;webPart xmlns=&quot;http://schemas.microsoft.com/WebPart/v3&quot;&gt;" +
  "&lt;metaData&gt;" +
   "&lt;type name=&quot;Microsoft.SharePoint.WebPartPages.XsltListViewWebPart, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c&quot; /&gt;" +
   "&lt;importErrorMessage&gt;Cannot import this Web Part.&lt;/importErrorMessage&gt;" +
  "&lt;/metaData&gt;" +
  "&lt;data&gt;" +
   "&lt;properties&gt;" +
    "&lt;property name=&quot;ListUrl&quot; type=&quot;string&quot;&gt;Lists/Tasks&lt;/property&gt;" +
    "&lt;property name=&quot;ExportMode&quot; type=&quot;exportmode&quot;&gt;All&lt;/property&gt;" +
   "&lt;/properties&gt;" +
  "&lt;/data&gt;" +
 "&lt;/webPart&gt;" +
"&lt;/webParts&gt;";

$().SPServices({
  operation: "AddWebPart",
  webPartXml: str,
  pageUrl: "/SitePages/Home.aspx",
  storage: "Shared",
  async: true,
  completefunc: function (xData, Status) {
    alert("Status: " + Status + " xData: " + xData.responseText);
  }
});

SPServices Compatibility Issues with jQuery 1.9.1

$
0
0

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.


SPServices Stories #4 – Using SPServices to Process Files from a Non-Microsoft Source

$
0
0
This entry is part 4 of 4 in the series SPServices Stories

Introduction

SPServices Stories #4 comes to us from Michael Broschat. Michael claims he’s just a “regular old contractor, nothing special”. He lives in the DC metro area (Northern Virginia, specifically), although he’s originally a West Coast boy.

Using SPServices to Process Files from a Non-Microsoft Source

Our system involves email messages sent to a SharePoint library by a non-Microsoft forms application. Although this particular application normally saves its results as XML files, it cannot _send_ those results anywhere (say, the SharePoint library). It can send the results as delimited strings, however, without field names.

I developed a scheme whereby I provide the field structure in an array (arFields) then match it with the field values derived from the delimited-string email. I then write same to a list.

So, three steps in a four-step process have email looking something like this:

smithxx@us.here*BP*11/01/2012*11/19/2012*18*1 - Sent to Supervisor

The final step contains much more information, for a total of 25 fields. The endpoint list includes all 25 fields, of course. Typically, a user entry will comprise four entries but accommodation must also be made for changes made while the four-step process operates. Once a step 4 has been received, any further attempts to augment this process for that user are ignored.

This JavaScript code (with SPServices, jQuery) runs via an enabling button on the list display page. But even before the button appears, the first routine has run. This routine looks in the library for any entries that have not yet been exported. If any, the button is enabled and its label includes the number of records to read and create in an associated list.

$(document).ready(function() {
  jQuery.support.cors = true;
  $().SPServices({
    operation:  "GetListItems",
    listName: "{D3795486-9926-424E-8F14-59BE5DB65BA8}",	//dev  GFSSEForm
    CAMLViewFields: "<ViewFields><FieldRef Name='LinkFilename'/><FieldRef Name='Exported' /></ViewFields>",
    completefunc: function (xData, Status) {
      $(xData.responseXML).SPFilterNode('z:row').each(function() {
        var thisFSObjType = parseInt($(this).attr("ows_FSObjType").split(";#")[1]);
        if(thisFSObjType === 0) {
          if(parseInt($(this).attr("ows_Exported"))===0) iRecordsToExport++;
        }  //if this is a file name
      });  //each()
      if(iRecordsToExport > 0) {
        $("#btnInitiate").html('There are ' + iRecordsToExport + ' records to export...');
        $("#btnInitiate").prop('disabled', false);
      } else {
        $("#btnInitiate").html('No records to export...'); //evidently, once we're off IE7
        $("#btnInitiate").prop('disabled', true);
      }
    }  //completefunc
  });  //SPServices
});  //document.ready

When the Records to Export button is launched, more or less the same code runs again, but this time I want to see only those email entries that have not already been dealt with (ie, Exported = false). This is done via CAML. I collect the library IDs of all items I’m writing in this batch (by the way, there is an interesting distinction between dealing with a batch and dealing with the set of records already written to the list), along with a couple other values, and create an array of IDs. The rest of the processing in this application is from that array.

return {
  //the only public method; handler for the web page button (enabled when at least one library item has not been exported)
  processRecord: function () {
    //this filter restricts the selection just to items that haven't been exported;
    var myQueryOptions = "<QueryOptions />";
    var myQuery = "<Query><OrderBy><FieldRef Name='Created_x0020_Date' /></OrderBy><Where><Eq><FieldRef Name='Exported' /><Value Type='Integer'>0</Value></Eq></Where></Query>";
    // now sorts by Created, ascending.
    $().SPServices({
      operation:  "GetListItems",
      async: false,
      listName: "{D3795486-9926-424E-8F14-59BE5DB65BA8}",	//dev  GFSSEForm
      CAMLViewFields: "<ViewFields><FieldRef Name='LinkFilename'/><FieldRef Name='Exported' /></ViewFields>",
      CAMLQuery: myQuery,
      CAMLQueryOptions: myQueryOptions,
      completefunc: function (xData, Status) {
        $(xData.responseXML).SPFilterNode('z:row').each(function() {
          //pick up some metadata from the SharePoint library
          //gather ows_ID='14', ows_FSObjType='14;#0', ows_Created_x0020_Date='14;#2012-11-20 06:55:13'
          var thisFSObjType = parseInt($(this).attr("ows_FSObjType").split(";#")[1]);
          idCurrent = $(this).attr("ows_ID");	//available globally
          dateCurrentCreated = $(this).attr("ows_Created_x0020_Date").split(";#")[1];	//available globally
          var formFilename = $(this).attr("ows_LinkFilename");
          if(thisFSObjType == 0) {
            arIDs.push([idCurrent, dateCurrentCreated, formFilename]);
          }  //if this is a file name
        });  //each(); arIDs is an array built from looking at all non-Exported items in the library
        //actually, you need to know the contents before you can decide whether there are any duplicate entries.
        //	ordering by Created, we're processing all entries in order of their entry into the library
      }  //completefunc
    });  //SPServices
    //here, do the Ajax call against the array of arrays; async:false is no longer used with $.ajax, deferred
    //	taking over that function; 2 Jan took the following routine out from completefunc; seems to save a stack level
    $.each(arIDs, function(index, value) {
      var promise = $.ajax({
        type:"GET",
        url:"GFSSEForm/" + arIDs[index][2],		//for SP2010 and above, full path is needed for the library name
        dataType:"text"
      });
      //the promise code executes when the file has been opened by AJAX
      promise.done(doStuff);	//magically passes the data along, too
      promise.fail(function() {alert("failed to open this eform: " + arIDs[index][2]);});
    });  //each
  }
};	// public method processRecord() [return]

By this point, the email file in the SharePoint library has been opened and its contents are ready to process.

There are two ways for email to get into the library: sent by the non-Microsoft forms application and sent directly via a client’s Outlook. The former way gets encoded (binary64), whereas the latter way does not. Both must be parsed for usable content but the encoded email must first be decoded. I use the following routine to handle both:

//	Sets the global variable: arValues; deals with both base64-encoded email and also non-encoded email
//	CRLF = \r\n
function decodeEmail(block) {
  var iBase64 = block.indexOf("base64");
  if(iBase64 > -1) {
    var startBlock = iBase64 + 6;
    var endBlock = block.indexOf("\n--", startBlock);
    var emailBlock = $.trim(block.substring(startBlock, endBlock));
    var strEmail = emailBlock.replace(new RegExp("\\n", "g"), "");
    var strDecoded = Base64.decode(strEmail);
    strDecoded = stripHTML(strDecoded);
    var iLong = strDecoded.indexOf("\r\n\r\n");	//intended for non-SMTP messages
    if(iLong > -1) {
      //take up to first \r\n
      strDecoded = strDecoded.substring(0,iLong+2);
    }
    arValues = strDecoded.split("*");
  } else {
    //	charset="us-ascii"; charset=utf-8; charset="gb2312"
    //here if there was no "base64" in the message; perhaps you should look for [charset="]us-ascii["]
    // this routine greatly strengthened 24 Jan 2012
    var iTrueStart = block.indexOf("quoted-printable");	//24 Jan fine; whole routine looks good
    var iTrueStart2 = iTrueStart + 16;
    var endBlock = block.indexOf("\n--", iTrueStart);
    var strBlock2 = $.trim(block.substring(iTrueStart2, endBlock));
    var newBlock = strBlock2.replace("=\r\n", "");		//kill all CRLFs
    var newBlock2 = newBlock.replace(/\<.*\>/g, "");  //a weird <mailto...> string in one message
    //you could have just called your own stripHTML()!
    var newBlock3 = newBlock2.replace(/=\r\n/g, "");	//one last holdout: =CRLF
    arValues = newBlock3.split("*");

  }
}

In my experience, getting values from functions does not always work within this environment (JavaScript within SharePoint). I have had to rely upon global variables (which have created their own problems at times). When that email decoding code runs, it places the parsed values into a global array: arValues, which is then used by the various routines that follow.

doStuff runs when the promise has been fulfilled. In other words, it only runs when data from the email is in hand. It sends the record off to writeNew or, if the number of field values does not match the current field template, stops the record from being processed.

function doStuff(data) {
  decodeEmail(data);	//sets global arValues, regardless of email type
  arFields = [];	//to ensure correct value within the batch loop
  if(arValues.length == 7) arFields = arFields100;
  if(arValues.length == 25) arFields = arFields200;
  if(arFields.length > 0) {
    boolIgnore = false;	//ensures correct starting point for each email
    iGlobal++;
    arValues[2] = dateFormat(arValues[2]);	//watch for changes in this default field order
    arValues[3] = dateFormat(arValues[3]);
    if(arValues.length === arFields.length) {
      var strArguments = arValues.join();
      writeNew(strArguments);	//wait until this routine before handling a dupe;
    } else alert("Number of values differed from number of form fields; not written." + arFields.length + " fields, " + arValues.length + " values (" + arValues + ")");
  } // was arFields set? If not, just return and let it pass
}  // doStuff()

writeNew simply uses SPServices to write the email contents to the list. It does this by preparing an array value to contain the values in the proper manner for UpdateListItems::New. After writing the record, I call setUpdated to modify the library entry, passing the library ID, which setUpdated uses to access the library metadata.

// strArguments is arValues rendered as string
function writeNew(strArguments) {
  $("#divId").ajaxError( function(event, request, settings, exception) {
    $(this).append("Error here: " + settings.url + ", exception: " + exception);
  });
  var iFields = arFields.length;
  var i = 0;
  var strTest = "";
  var strField = "";
  var vpairs = [];
  var strPairs = "";
  var arValues2 = strArguments.split(',');
  for(i=0; i<iFields; i++) {
    strTest = String(arValues2[i]);
    if(strTest.length > 255) strTest = strTest.substring(0,255);
    strField = arFields[i];
    vpairs.push([strField,strTest]);
  }
  //check to see whether this email address is in HoldMe; if so, processing stops, but run setUpdated(arIDs[idIndex][0]) and advance the index
  notInHoldMe(vpairs[0][1]);	//sets global value regarding presence in HoldMe list
  if(!inHoldMe) {
    var jsDate = getJSDate();		//picks up date values from arValues
    vpairs[4][1]=jsDate;
    $().SPServices({
      operation: "UpdateListItems",
      batchCmd: "New",
      async: false,
      listName: "{2D9F4CDB-A5F0-4EED-8996-C26FB2D08294}",  //development list GFSSVerified
      valuepairs: vpairs,
      completefunc: function(xData, Status) {
        if(Status == 'success') {
          //'success' is a relative term; you must also examine any error text, to see whether an error occurred
          var strError = $(xData.responseXML).SPFilterNode('ErrorText').text();
          if(strError != "") {
            $("#showErrors").append("<p>Error adding: " + $(xData.responseXML).SPFilterNode('z:row').attr("ows_Title") + " " + strError + "</p>");
          } else setUpdated(arIDs[idIndex][0]);  //possibly delete the row at this point
          idIndex++;
          if(vpairs[6][1].substring(0,1) == "4") setLocked(vpairs[0][1]);	// ie, after writing Step 4
          //record has been written; now find out whether it was a duplicate
          else findExisting(vpairs[0][1], vpairs[6][1].substring(0,1));
        } else alert("error: " + xData.responseText);
      }	//completefunc
    }); //SPServices
  }	//if not locked
  else {
    alert("The record for " + vpairs[0][1] + " is locked...");
    setUpdated(arIDs[idIndex][0]);
    idIndex++;
  }
}  // writeNew()

All records are to be written to the list, but it will happen that some records are duplicates (because a later action changes the previous action). In that case, the earlier record needs to be marked as ‘orphan’. The original idea was to simply delete the record but someone wanted to keep it. Therefore, I need to filter orphans from various stages of processing. The routine called findExisting deals with this issue. I use CAML to filter orphans.

function findExisting(user, action) {
  $("#divId").ajaxError( function(event, request, settings, exception) {
    $(this).append("Error in findExisting: " + settings.url + ", exception: " + exception + "<br>");
  });
  var queryOptions = "<QueryOptions />";
  var query = "<Query><Where><And><And><Eq><FieldRef Name='Title' /><Value Type='Text'>" + user + "</Value></Eq><BeginsWith><FieldRef Name='col07x' /><Value Type='Text'>" + action + "</Value></BeginsWith></And><Neq><FieldRef Name='col26x' /><Value Type='Integer'>1</Value></Neq></And></Where></Query>";  //col26x is 'Orphan'
  //CAML looks for existing items having same name and action, ignoring any that have already been marked as orphans
  $().SPServices({
    operation:  "GetListItems",
    async: false,		//required!!!!!
    listName: "{2D9F4CDB-A5F0-4EED-8996-C26FB2D08294}",  //development list GFSSVerified
    CAMLViewFields: "<ViewFields><FieldRef Name='Title'/></ViewFields>",
    CAMLQuery: query,
    CAMLQueryOptions: queryOptions,
    completefunc: function (xData, Status) {
      var iCount = parseInt($(xData.responseXML).SPFilterNode("rs:data").attr("ItemCount"));
      if(iCount > 1) {	//you're here because this value _at least_ was written
        //within this batch, there are multiples of this user/action; pass the multiple IDs
        var arDupIDs = [];
        var iDupID = 0;
        // routine examines each entry in arDupIDs, and replaces any value with lesser; ends up with earliest entry, which is then orphaned
        // limitation here is that it only--practically speaking--handles two instances; three or more would lose all but one
        $(xData.responseXML).SPFilterNode('z:row').each(function() {
          iDupID = parseInt($(this).attr("ows_ID"));
          arDupIDs.push(iDupID);
          if(arDupIDs.length > 1) {
            if(iDupID < arDupIDs[0]) arDupIDs[0] = iDupID;
          }
        });
        orphanGFSSRow(arDupIDs[0]);
      }	// if at least one
    }	//completefunc
  });  //SPServices
}	//findExisting()

The completefunc routine looks only at items that have duplicates. It determines the earliest item, then sends off its ID for marking as orphan.

SPServices is also used to lock an account (by placing the email address in a separate list), by checking for existence of the email address currently being processed in the Locked list. One function sets the lock, while notInHoldMe() queries the lock list.

SPServices Stories #5 – Gritter and Sharepoint: Integrate Nifty Notifications in Your Intranet!

$
0
0
This entry is part 5 of 6 in the series SPServices Stories

Introduction

This SPServices Story comes to use from Rick El-Darwish (@RtfulDodg3r) in Geneva, Switzerland. For those of you who aren’t familiar with it, Gritter is a Growl-like notification jQuery plugin. That’s a mouthful, but try the links and you’ll quickly get the picture. If you’re an OS X user, then you’ll recognize the capability right away.

Proof of concept Gritter SharepointProof of concept Gritter Sharepoint hover

Sure, SharePoint has a notification scheme built into it – Showing transient messages using the SharePoint 2010 Notification Area – SharePoint 2010 UI tip #2 (thanks, as always, @waldekm) – and there is at least one Codeplex project – SPNotifications - that provides similar functionality. However, the built-in notifications are limited in functionality and the SPNotifications requires server-side code. The script-based approach has a much smaller footprint and the content can easily be maintained by an end user. (Getting developers out of the mix with content ought to always be the goal.) Also, by using open source jQuery-based alternatives, you always have the option to expand the functionality to meet your own needs.

By using SPServices and Gritter together, Rick was able to create very user-friendly notifications on SharePoint pages which were driven by list-based content. Rick starts out with a simple example and shows you how to set up the list-driven approach. With a little extrapolation, I’m sure you will be able to see quite a few additional uses for this approach in your own environments.

You can see Rick’s original post on his blog, Forensic Aspirations, Inferred Logic: Rick’s tale of FAIL.

Gritter and Sharepoint: Integrate Nifty Notifications in Your Intranet!

I’m working with a client on building up a project management tool in SharePoint; one thing that’s really piqued their interest is the concept of getting short flashes of information when their staff logs into the landing page. I think it’s a lovely idea, and I’m intend on giving them this functionality – but how, you might ask? There doesn’t seem to be any SharePoint feature for doing that!

We tend to forget that SharePoint is, above all, a web-based solution. This means that, with a little ingenuity (and sometimes a lot of sweat and blood), you can integrate some of the latest, coolest web features into your existing SharePoint. Fortunately, notifications are not too complicated. In this short article, we’re going to walk through creating very cool notifications using Gritter, a jQuery-based “plugin”, with Sharepoint.

Step One: Create a Sandbox

This may be as simple as creating a new page in your Site Pages repository. I seriously recommend implementing a proof-of-concept rather than work on your production page… If you’re not familiar with these libraries, the last place you want to test things out is on your production work, as easy as these implementations may seem.

Apart from your page, your sandbox will need a few extra files. These you can either place in the Site Assets repository of your PoC portal, or in the Site Assets repo of your root. The latter has the benefit of being accessible to your entire audience (or at least I assume so, it will depend on your permissions). The files that you need are the latest minified version of jQuery, the latest version of Gritter, and the latest version of SPServices (double-check these pages for compatibility issues, of course – if Gritter or SPServices tell you that they won’t work with certain versions of jQuery, don’t use those versions…)

When downloading Gritter, you’ll notice that it is a zip file that has several folders and files. I recommend that you keep those in one single place in your Site Assets. I find it’s easiest to use SharePoint Designer to do that.

Step Two: Add Your jQuery References

Now that you have a sandbox, you can start working with it. In case you’re wondering, this section is assuming that you’re working with SharePoint Designer (SPD) to do your work.

Open your sandbox page in SPD, editing it in Advanced Mode. Locate the PlaceHolderMain placeholder and add the references to your script files:

<!– Inserted by Rick – references to jQuery scripts –>
<!– These are the references to jQuery and SPServices. –>
<script type="text/javascript" src="../SiteAssets/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="../SiteAssets/jquery.SPServices-0.7.2.min.js"></script>
<!– These are the references to Gritter: –>
<link rel="stylesheet" type="text/css" href="../SiteAssets/gritter/css/jquery.gritter.css" />
<script type="text/javascript" src="../SiteAssets/gritter/js/jquery.gritter.min.js"></script>
<!– End Rick insertions. –>

You can test that the libraries loaded correctly by firing up Chrome, navigating to your PoC page, and opening your Console (F12). In the Console, type the following:

$
$(document).SPServices
$.gritter

If any of these return ‘undefined’, review your references, make sure the files are uploaded in the correct location.

Step Three: Setting up your notifications!

OK, now we know that all the necessary libraries are loaded. Time to develop notifications. Always develop incrementally, testing your code one chunk at a time. To that effect, here’s code that you should insert after the above script blocks:

<!– Here’s a test notification –>
<script type="text/javascript">
$(document).ready( function() {
  $.gritter.add({
    title: "This is a test notification",
    text: "This will fade after some time"
  });
});
</script>

If that works, you know that Gritter is functional for static content! Now it’s time to pull the real notifications from a list — this is where SPServices comes in. Before we proceed, we need something to pull information from: create a custom list with a single title for your PoC, “Latest Activities” for instance. Then, you will call the GetListItems function using SPServices.

The following code replaces your test notification:

<!– Code for notifications –>
<script type="text/javascript">

//This function throws up the notification:
function notify(curTitle, curContent) {
  $.gritter.add({
    title: curTitle,
    text: curContent
  });
}

//This retrieves the latest item of your Latest Activities.
function getLastActivity() {
  $(document).SPServices({
    operation: "GetListItems",
    async: false,
    listName: "Latest Activities",
    CAMLRowLimit: 1,
    CAMLQuery: "<Query><OrderBy><FieldRef Name=’Created’ Ascending=’False’ /></OrderBy></Query>",
    CAMLViewFields: "<ViewFields><FieldRef Name=’Title’ /></ViewFields>",
    completefunc: function(xData, Status) {
      $(xData.responseXML).SPFilterNode("z:row").each(function() {
        notify(‘For your information…’, $(this).attr("ows_Title"));
      });
    }
  });
}

//On document load, throw up the notification:
$(document).ready( function() {
  getLastActivity();
});
</script>

Et voilà — Gritter and SharePoint notifications in a nutshell! Your page will load and, once loaded, will call the getLastActivity function. getLastActivity pulls the latest item from the Latest Activities list (we use the CAMLQuery parameter to order by create date, and the CAMLRowLimit parameter to only return one parameter), and use a callback function to call the notify() function. The notify function is what is responsible for rendering the Gritter notification.

Happy notifying!

Rick.

SPServices Stories #6 – Custom Quizzing System

$
0
0
This entry is part 6 of 6 in the series SPServices Stories

Introduction

Many times, SPServices Stories play out on the field of the Codeplex Discussions. As someone posts questions about how to build some of the components of their solution, the overall plan comes into focus.

Over the last week or so, I’ve been helping Oli Howson (howsono on Codeplex) in the discussions here and here. As the bits and pieces came to together for me, I thought that Oli’s work in progress would make a great SPServices Story, especially since he took the trouble to write up what he was trying to accomplish in the discussion thread.

Oli’s project is in process, and it’s certainly possible that he will make changes from here. However, I thought it would be useful to post things “as-is” so that everyone could see how he’s going about it. If he makes any significant changes, we’ll try to post them back as well.

If you have any comments for Oli about how you might do things differently, I’m sure he’d be interested. I know I would be.

Oli’s entire bespoke page is shown below, so you get to see the markup, the script, everything.

Custom Quizzing System

I am a teacher – running the ICT and Computer Science department of a South London Academy – we teach both disciplines to 11-18 year olds. For key-stage 3 (Y7, 8 and 9) we have for the last few years set homework on our VLE (Virtual Learning Environment) which had a built-in testing system. Due to that being about the only part of the VLE that wasn’t naff, it was recently retired and a new SharePoint system brought in. It’s got a few bespoke bits, and I am not an admin. Now I’m faced with the dilemma: I have 555-ish students needing their homework setting every week, I deliver all my learning resources via the SharePoint system, but there is no built-in quizzing system. Yes – there are surveys, but they don’t mark and have their own foibles. So I built this JavaScript-based quizzing system.

Methodology

The teacher in charge of setting homework that week creates a multiple-choice quiz on a stand-alone piece of client software I wrote in Delphi. This then creates an array string which it pastes into the quiz template (with a relevant name) and copies the file to the relevant document store in the SharePoint server. The teacher then just creates a link from the homework page to the relevant quiz, and when the kids hit the quiz the results go into the relevant list (created with the same name as the quiz). The difficult bit was making sure that the list was created the first time the quiz is run. The idea is the teacher hits the quiz once the link is up to make sure it has worked. When they submit, it creates the list, adds the columns, and updates the view. The second time it tests if the list exists (it does now!) and just inserts their score, which it also shows to the kids and then closes the window.

Well, I think I’m there! I’m going to get this beta tested by a group of Year 9 students tomorrow, but I’ll put the code below for reference to anyone that might find it interesting. I’m sure I’ve made loads of faux-pas as I’ve written a grand total of about three things in JavaScript, and have very limited knowledge of SharePoint.

Wheeee :)

<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8" />
<title>jQuery UI Tabs - Default functionality</title>
<link rel="stylesheet" href="mertonshared/computerstudies/homework/Documents/includes/jquery-ui.css" />
<script language="javascript" src="/mertonshared/computerstudies/homework/Documents/includes/jquery-1.8.1.js"></script>
<script language="javascript" src="/mertonshared/computerstudies/homework/Documents/includes/jquery-ui.js"></script>
<script language="javascript" src="/mertonshared/computerstudies/homework/Documents/includes/jquery.SPServices-0.7.2.js" type="text/javascript"></script>

<script>
//////////////////////////////////////////////////////////
// Quizzer v1.0.2                                       //
// Change Log:                                          //
//  - 1.0.2 Removed some pointless alerts for ordinary  //
//           users, reenabled windows.close(), added    //
//           some useful comments.                      //
//  - 1.0.1 Added view change to show all fields        //
// Currently available:                                 //
//  - Multiple choice                                   //
//  - Unlimited questions                               //
//  - Four options per question                         //
// Future Plans                                         //
//  - Varying number of options per question            //
//  - Missing word completion                           //
//  - Include YouTube clip                              //
//////////////////////////////////////////////////////////

var startseconds;
var filename;
var score;

function checkiflistexists() {
  //alert('checking existance of list: '+filename);
  $().SPServices ({
    operation: "GetList",
    listName:  filename,
    completefunc: function (xData, Status) {
      if (Status == 'success') {
        // alert ('Exists - dont create - just insert data');
        insertdata();
      } else {
        // alert('Dont exist');
        $().SPServices({
          operation: "AddList",
          listName: filename,
          description: "List created for quiz: "+filename,
          templateID: 100,
          completefunc: function (xData, Status) {
            alert('Trying to create list');
            alert(Status);
            alert('Now add fields');
            var nfields = "<Fields><Method ID='1'><Field Type='Text' DisplayName='Score' ResultType='Text'></Field></Method><Method ID='2'><Field Type='Text' DisplayName='TimeTaken' ResultsType='Text'></Field></Method><Method ID='3'><Field Type='Text' DisplayName='CompletedOn' ResultsType='Text'></Field></Method></Fields>";
            $().SPServices({
              operation: 'UpdateList',
              listName: filename,
              newFields: nfields,
              completefunc: function(xData, Status) {
                tp1 = xData.responseText;
                tp4 = tp1.indexOf("errorstring");
                if (tp4 < 0) {
                  alert("Fields created! - Update View");
                  var viewname = "";
                  $().SPServices({
                    operation: "GetViewCollection",
                    async: false,
                    listName: filename,
                    completefunc: function (xData, Status) {
                      alert('Complete Func - GewViewCollection');
                      $(xData.responseXML).find("[nodeName='View']").each(function() {
                        var viewdisplayname = $(this).attr("DisplayName");
                        if (viewdisplayname=="AllItems") {
                          viewname = $(this).attr("Name");
                          return false;
                        }
                      });
                    }
                  });
                  alert('Ok - done GetViewCollection - now update the view');
                  var viewfields = "<ViewFields><FieldRef Name=\"Title\" /><FieldRef Name=\"Score\" /><FieldRef Name=\"TimeTaken\" /><FieldRef Name=\"CompletedOn\" /></ViewFields>";
                  $().SPServices({
                    operation: 'UpdateView',
                    async: false,
                    listName: filename,
                    viewName: viewname,
                    viewFields: viewfields,
                    completefunc: function(xData, Status) {
                      alert('Trying to update view');
                      alert(Status);
                      alert('Updated view - now add data!');
                      insertdata();
                    }
                  });
                } else {
                  // Error creating fields!
                  alert("Error creating fields!");
                }
              }
            });
          }
        });
      }
    }
  });
}

function insertdata() {
  var thisUserName = $().SPServices.SPGetCurrentUser({
    fieldName: "Title",
    debug: false
  });
  var endseconds = new Date().getTime() / 1000;
  endseconds = endseconds - startseconds;
  var d = new Date();
  var dd = d.toDateString();
  var dt = d.toTimeString();
  $().SPServices({
    operation: "UpdateListItems",
    async: false,
    batchCmd: "New",
    listName: filename,
    valuepairs: [["Title", thisUserName],["Score",score],["TimeTaken",Math.round(endseconds).toString()+" seconds"],["CompletedOn",dd+" "+dt]],
    completefunc: function (xData, Status) {
      //alert('Trying to add data');
      if (Status == 'success') {
        inserted();
      } else {
        alert(Status+' : There was a problem inserting your score into the database. Please notify Mr Howson!');
        inserted();
      }
    }
  });
  alert('You achieved a score of '+score);
}

function checkanswers() {
  var form = document.getElementById('answers');
  score = 0;
  for (var i = 0; i < form.elements.length; i++ ) {
    if (form.elements[i].type == 'radio') {
      if (form.elements[i].checked == true) {
        if (questions[form.elements[i].name.substring(9)-1][questions[form.elements[i].name.substring(9)-1].length-1] == form.elements[i].value) {
          score++;
        }
      }
    }
  }
  var url = window.location.pathname;
  filename = url.substring(url.lastIndexOf('/')+1);
  filename = filename.substring(0,filename.lastIndexOf('.'));
  $(document).ready(function() {
    checkiflistexists();
  });
}

function starttime() {
  startseconds = new Date().getTime() / 1000;
}

function inserted() {
  window.close();
}

$(function() {
 $("#tabs").tabs();
});

var questions = new Array;
 //The section below should be uncommented when not testing - this will be replaced by the client
 // side application with the questions array.
//[INSERTQUESTIONS]

//The following questions can be uncommented for testing purposes
questions[0] = ['q1','a','b','c',1];
questions[1] = ['q2','d','e','f',2];
questions[2] = ['q3','g','h','i',3];
</script>
</head>

<body onload="starttime()">
  <div id="tabs">
    <ul>
      <script language="JavaScript">
        for (var i = 0; i< questions.length; i++) {
          document.write('<li><a href="#tabs-'+(i+1)+'">Question '+(i+1)+'</a></li>');
        }
        document.write('<li><a href="#tabs-'+(i+1)+'">Summary</a></li>');
      </script>
    </ul>
    <form name="answers" id="answers">
      <script language="JavaScript">
        for (var i = 0; i < questions.length; i++) {
          document.write('<div id="tabs-'+(i+1)+'">');
          document.write(' <p>'+questions[i][0]+'</p>');
          for(var j = 1; j < questions[i].length-1; j++) {
            document.write(' <p><input type="radio" name="question-'+(i+1)+'" value="'+j+'">'+questions[i][j]+'<br></p>');
          }
          document.write('</div>');
        }
        document.write('<div id="tabs-'+(i+1)+'">');
        document.write(' <p><input type="submit" onclick="checkanswers(); return false;"></p>');
        document.write('</div>');
      </script>
    </form>
  </div>
</body>
</html>

SPServices Stories #7 – Example Uses of SPServices, JavaScript and SharePoint

$
0
0
This entry is part 7 of 7 in the series SPServices Stories

Introduction

I’ve been aware that John Liu (@johnnliu) is a fan of SPServices for some time now. He occasionally tweets about things he’s up to, and they always sound intriguing.

Recently, I asked him if he’d like to share any of his own SPServices Stories, and he did a post for me on his blog. In the post, John shows three great examples of how you can slide SPServices under some truly impressive functionality that greatly improves the overall SharePoint user experience.

There’s another SPServices Story coming up from Dan Stoll (@_danstoll) which goes into some of the details on John’s first example below.

Without further ado, here’s John’s first SPServices Story. I expect that John will have a few more SPServices Stories to share with us over time.

Example uses of SPServices, JavaScript and SharePoint

I wanted to write about spservices.codeplex.com from Marc D Anderson – we’ve found ourselves using this really special library time and again across different projects to talk back to SharePoint quickly.

Starting Workflows

Here’s a page from one of our Process Wiki articles.

Process Wiki Example

  • We have a special “Contributor-only” Web Part on the right.
  • It shows the various workflow status’ on the current page, as traffic light bubbles.
  • The “Certify Process Page” calls a JavaScript function that calls StartWorkflow via SPServices.
  • The workflow is a Nintex workflow and triggers a significant multistage approval process.  But you can use StartWorkflow to start SharePoint workflows as well.

Getting List Data, Lots of List Data

Here’s our task list, represented as a task board.

Task Dashboard

  • This one is completely done with SPServices to get the list items
  • Convert the objects to JSON using SPServices.SPXmlToJson
  • Then binding the objects to UI via Knockout
  • There’s jQuery UI’s drag and drop in play, so we can change the Task’s status by dragging the task across a different column.
  • Update task using SPServices’ UpdateListItems call.
  • And some nice CSS.
  • This particular page also runs via SharePoint 2010′s OData listdata.svc, but is completely viable with SPServices on SP2007 as well.

Getting User Profiles via Search

Here’s our People page.

People Page

  • First, get SharePoint to index your people.
  • Use SPServices to call SharePoint search to return a bunch of people, including their picture (one would say, especially their picture).
  • Here I use Knockout to render the pictures.  When clicked, each one opens that user’s My Site page.
  • There’s a filter box on the top right, as well as “fake” refinements on the left hand side that allows us to re-query SharePoint search for filtered people.
  • One possible idea here would be to use SPServices’ User Profile Service support and talk directly to the User Profile service if you want to skip the search service.

Summary

A quick post of 3 recent JavaScript customizations that heavily used SPServices.  Hope that give you guys a lot of ideas.  Let me know what you guys think.

SPServices Stories #8 – CEWP, Nintex, jQuery, SPServices and the Client API

$
0
0
This entry is part 8 of 8 in the series SPServices Stories

Introduction

I’ve been keeping a list of older posts about using SPServices that I’ve seen on the InterWebz for that day when I finally got around to doing something like SPServices Stories. This one is from Dan Stoll (@_danstoll) at Nintex, and was originally published on Dan’s blog back in February, 2012 as CEWP, Nintex, jQuery, SPServices and the Client API.

To me, the fact that someone at Nintex would choose to use SPServices to accomplish something with workflows is pretty telling, in a positive way. The folks over at Nintex know what they are doing, and if they choose to use SPServices as spackle to fill in some of the gaps in SharePoint, then it’s testament to the value of it.

In SPServices Stories #7, John Liu showed an example of starting a workflow with SPServices, and here Dan gives us some of the details under the covers.

CEWP, Nintex, jQuery, SPServices and the Client API

I had a requirement that required a not so difficult solution but tricky.. The first requirement was that a webpart had to be embedded in to a publishing page layout that was used over multiple sites in the farm. The next requirement was to only show this webpart to a select group of people.. Easy right ? Using audiences should work a treat…. Wrong. . Embedding the CEWP you can still put in the audience but it doesn’t work because it isn’t in a webpart zone… Ok, so let’s put a webpart zone in.. Pop in my custom CEWP webpart.. The audience set.. but still no joy.. Audience is working but because my webpart is in a webpart zone, it doesn’t show up.

So now I’m faced with .. “How do I get a default webpart to appear on 500 + pages that already exist?? ” Back to square one.. There has to be a way of telling a Div to hide if you aren’t part of a group of some kind. So let’s look at this.. Here is an extract of my page layout

<div class="certifiedPanel" style="display: none;">
  <div class="article-before-wp">
    <h3 class="ms-standardheader ms-WPTitle">
      <span>Details</span>
    </h3>
  </div>
  <div class="article-meta article-wp">
  <table style="width: 100%;">
    <tbody>
      <tr>
        <th class="style1" style="width: 36%;">Contact:</th>
        <td id="pubContact"></td>
      </tr>
      <tr>
        <th class="style1" style="width: 36%;">Last Modified:</th>
        <td id="modifiedOn"></td>
      </tr>
      <tr style="display: none;">
        <th style="width: 36%;">Last Review Date:</th>
        <td id="lastReviewedOn"></td>
      </tr>
      <tr>
        <th class="style1" style="width: 36%;">Next Review
        Date:</th>
        <td id="reviewedOn"></td>
      </tr>
      <tr>
        <th class="style1" style="width: 36%;">Certified Date:</th>
        <td id="certifiedOn"></td>
      </tr>
      <tr>
        <th class="style1" style="width: 36%;">HYway DOCID:</th>
        <td id="docId"></td>
      </tr>
      <tr>
        <th class="style1" style="width: 36%;">Reference ID:</th>
        <td id="refId"></td>
      </tr>
    </tbody>
  </table>Managers PanelNoneUse for formatted text, tables, and
  images.true2NormaltruetruetruetruetruetruetrueModeless
  <dir>Default</dir>Cannot import this Web
  Part.true00000000-0000-0000-0000-000000000000g_73d6de6b_445e_4d9a_8443_c05b20548336/OurProcesses/Documents/startworkflow.txt</div>
  <div class="article-after-wp"></div>
</div>

You’ll see in the first line, I have included a “display:none” style on the DIV called “certifiedPanel” The rest of it, well the first half of the Certified Panel shows a few fields from the content type, eg Modified Date, ID etc etc.. The second half is the CEWP that has a text file as it’s source of content ‘startworkflow.txt’ .. This is where part 2 of the story begins..

The other requirements is that this “Panel” had to show indicators as to the phases of the document, it also had to have a link to the version history of the document and there also had to be a couple of workflows that could be executed against the page (content type) from this panel.. Firstly, let me show you the panel

The 3 indicators shown here, show the manager at a quick glance that the document isn’t certified (Yellow) because the review date has passed, it isn’t ready for Review either (Red) as the Contact hasn’t been filled in. The only parameter that is ok is that the Review date as been set with “something” (Green)

hy_details-300x300

As you can see the document has been modified since being certified so this document is now no longer certified as changes may have been made to the content…

Ok so how did we do this.. I’ll post the full contents of the startworkflow.txt file and we can work from there. (the Startworkflow.txt) is used with the Content Editor Webpart to display on the page

[Ed: I've split the HTML and JavaScript sections for better readability.]

var $ = jQuery;
$(document).ready(SetPageIndicators);
function SetPageIndicators() {

  //get the certified and reviewed indicators
  var certifiedOn = new Date($(&quot;#certifiedOn&quot;).text());
  var hasCertDate = !isNaN(certifiedOn);
  var modifiedOn = new Date($(&quot;#modifiedOn&quot;).text());
  var hasModDate = !isNaN(modifiedOn);
  var reviewOn = new Date($(&quot;#reviewedOn&quot;).text());
  var hasReview = !isNaN(reviewOn);
  var lastReviewedOn = new Date($(&quot;#lastReviewedOn&quot;).text());
  var haslastReviewedOn = !isNaN(lastReviewedOn);
  var baseUrl = &quot;/Style%20Library/hyway/Images/&quot;;

  //set certified icon
  var certImg = $(&quot;#certImg&quot;);
  if (!hasCertDate)
    certImg.attr(&quot;src&quot;, baseUrl + &quot;statusRed.png&quot;);
  if (hasCertDate)
    certImg.attr(&quot;src&quot;, baseUrl + &quot;statusGreen.png&quot;);
  if ((hasModDate &amp;&amp; hasCertDate) &amp;&amp; (modifiedOn.setDate(+1) &gt; certifiedOn))
    certImg.attr(&quot;src&quot;, baseUrl + &quot;statusYellow.png&quot;);

  //set lastReviewedOn icon
  var lastReviewedOnImg = $(&quot;#lastReviewedOnImg&quot;);
  if (!haslastReviewedOn)
    lastReviewedOnImg.attr(&quot;src&quot;, baseUrl + &quot;statusRed.png&quot;);
  if (haslastReviewedOn)
    lastReviewedOnImg.attr(&quot;src&quot;, baseUrl + &quot;statusGreen.png&quot;);
  if (lastReviewedOn &gt; certifiedOn)
    lastReviewedOnImg.attr(&quot;src&quot;, baseUrl + &quot;statusYellow.png&quot;);

  //set review icon
  var reviewImg = $(&quot;#reviewImg&quot;);
  if (!hasReview) {
    reviewImg.attr(&quot;src&quot;, baseUrl + &quot;statusRed.png&quot;);
  } else {
    if (reviewOn &gt; new Date()) {
      reviewImg.attr(&quot;src&quot;, baseUrl + &quot;statusGreen.png&quot;);
    } else {
      reviewImg.attr(&quot;src&quot;, baseUrl + &quot;statusYellow.png&quot;);
    }
  }

  //Hide the Panel from those who don't need it
  ExecuteOrDelayUntilScriptLoaded(function () {
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();

    // change ID based on
    // http://devserver/OurProcesses/_layouts/people.aspx?MembershipGroupId=551
    // http://server/OurProcesses/_layouts/people.aspx?MembershipGroupId=752
    var group = web.get_siteGroups().getById(551);
    ctx.load(group);
    var users = group.get_users();
    ctx.load(users);
    var user = web.get_currentUser();
    ctx.load(user);
    ctx.executeQueryAsync(Function.createDelegate(this, function () {

        // success
        for (var i = 0; i &lt; users.get_count(); i++) {
          var u = users.get_item(i);
          //alert(u.get_loginName());
          if (u.get_loginName() == user.get_loginName()) {
            //alert(&quot;found you&quot;);

            $(&quot;.certifiedPanel&quot;).show();
          }
        }
      }), Function.createDelegate(this, function () {}));
  }, &quot;sp.js&quot;);
}
function StartWorkflow(ItemURL, WorkflowName) {
  var waitDialog = SP.UI.ModalDialog.showWaitScreenWithNoClose('working on it….', 'Please wait while Gnomes get this sorted for you...', 76, 330);

  // start the workflow manually, then take the TemplateID from the URL of the Workflow starting form
  // this is no good since it changes when you republish workflow...
  // get TemplateID first for item.
  $().SPServices({
    operation : &quot;GetTemplatesForItem&quot;,
    item : ItemURL,
    async : false,
    completefunc : function (data, status) {
      var workflowTemplateID;
      if (status == &quot;success&quot;) {
        $(data.responseXML).find(&quot;WorkflowTemplates &gt; WorkflowTemplate&quot;).each(function (i, e) {

          // hard coded workflow name
          if ($(this).attr(&quot;Name&quot;) == WorkflowName) {
            var guid = $(this).find(&quot;WorkflowTemplateIdSet&quot;).attr(&quot;TemplateId&quot;);
            if (guid != null) {
              workflowTemplateID = &quot;{&quot; + guid + &quot;}&quot;;
            }
          }
        });
      } else {

        // error can't find template
        alert(status + &quot; : &quot; + data.responseText);
        return;
      }

      // start workflow now with obtained templateID. Note this must run within the completeFunc of the first webservice call
      $().SPServices({
        operation : &quot;StartWorkflow&quot;,
        item : ItemURL,
        templateId : workflowTemplateID,
        workflowParameters : &quot;&lt;root /&gt;&quot;,
        completefunc : function (data, status) {
          waitDialog.close();
          if (status == &quot;error&quot;) {
            alert(status + &quot; : &quot; + data.responseText);
          } else {
            document.location.reload();
          }
        }
      });
    }
  });
}
function certImg_onclick() {}

function ShowVersionHistory() {
  if (!_spPageContextInfo) {
    return;
  }
  var options = {
    tite : &quot;Versions&quot;,
    url : _spPageContextInfo.webServerRelativeUrl + &quot;/_layouts/Versions.aspx?list=&quot; + _spPageContextInfo.pageListId + &quot;&amp;ID=&quot; + _spPageContextInfo.pageItemId,
    allowMaximize : false,
    showClose : true,
    width : 800,
    height : 500,
  };
  SP.UI.ModalDialog.showModalDialog(options);
}
<table style="width: 100%">
  <tr>
    <td>
    <img id="certImg" alt="Page Certified Indicator" src=""
    onclick="return certImg_onclick()" height="20px"
    width="20px" />Certified</td>
    <td>
      <a href="javascript:StartWorkflow(document.location, 'Super Stamp')">
      Certify Process Page</a>
    </td>
  </tr>
  <tr>
    <td>
      <div>
      <img id="reviewImg" alt="Page Review Indicator" src=""
      height="20px" width="20px" />Reviewed</div>
    </td>
    <td>
      <a href="#" onclick="javascript:ShowVersionHistory();">View
      Modification History</a>
    </td>
  </tr>
  <tr>
    <td>
    <img id="lastReviewedOnImg"
    alt="Page Ready for Review Indicator" src=""
    onclick="return lastReviewedOnImg_onclick()" height="20px"
    width="20px" />Ready for Review</td>
    <td>
      <a href="javascript:StartWorkflow(document.location, 'ReadyForReview')">
      Ready for Review</a>
    </td>
  </tr>
</table>

As you can probably guess there is are a couple of parts to this txt file..

  1. The first being jquery switching the indicators depending on the rules set and the values of certain date fields.
  2. The second part is using the client API where we are setting the ID of the SP group (note you can’t have any nested SP or AD groups here) that has access to this panel.. This then sets the DIV certifiedPanel display to show.
  3. Part 3 is using SPServices http://spservices.codeplex.com/ to call Nintex Reusable Workflows that are bound to the custom content type that I am using for these Publishing Pages. As Nintex Workflow use the same infrastructure as SharePoint workflows, the rich feature set of SPServices, allow me to start my Nintex Workflows and a whole host of other things.

These workflows not only update the Page in question but also assign tasks to the review committee, and maintain a centralised list of all pages that are currently under review, / are due for review or don’t comply to the companies guidelines..

On approval of the task, the pages are certified within the companies “Processes” site. The JavaScript indicators reflect its new status, and the document is removed form the centralised management list.

Viewing all 109 articles
Browse latest View live