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

This entry is part 8 of 20 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.

Series Navigation<< SPServices Stories #7 – Example Uses of SPServices, JavaScript and SharePointSPServices Stories #9: Developing with Client-side Technologies: jQuery, REST, SPServices and jsRender >>

1 Comments

  1. I was thinking about a reverse strategy.

    Placing the webpart on a blank layout page (no master page) then loading it in to the page. Then using $.ajax perform the post request on the webpart, and reloading it on response if it has a form.

    I haven’t tested this theory yet, but I really like the ideas in this page.

    The basic idea is taking the minimal download strategy to a new level and SPServices can really help with this in 2010.

    I’ve implemented something similar in a layout page itself, but not actually embedding a control in a page using ajax.

    It would be a very fast and lightweight loading sharepoint.

    Reply

Leave a Reply