Albert Jan Schot
 

Batch Publish with the Ribbon

17

Aug

After creating my first ribbon control (blogged about that in this post), I wanted to learn a bit more about the client object model in SharePoint 2010. So due to that I decided to make a new ribbon button that would allow me to publish multiple items, using the Client Object model (allowing you to use the solution as a sandboxed solution.

I used almost the same code as in this post, only adding two new things; a custom action containing the script that would actually publish items, and some code so the button would only be enabled whenever there are one or more items selected:

  1:   <CustomAction Id="Ribbon.Documents.Custom.Scripts"
  2:       Location ="ScriptLink"
  3:       ScriptSrc="/_layouts/Portal/Portal.RibbonActions.js" />

 

and: 

  1: <CommandUIHandler
  2:  Command="PublishForMail_PublishButton"
  3:  CommandAction="javascript:PublishDocuments();"
  4:  EnabledScript="javascript:function oneOrMoreEnable() { 
  5:   var items = SP.ListOperation.Selection.getSelectedItems(); 
  6:   var ci = CountDictionary(items); 
  7:   return (ci > 0); 
  8:  } 
  9:  oneOrMoreEnable();" />

 

So the complete code for the ribbon looked like:

  1: <?xml version="1.0" encoding="utf-8"?>
  2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3:   <CustomAction
  4:     Id="Ribbon.Documents.Custom.BatchPublish"
  5:     Location="CommandUI.Ribbon"
  6:     RegistrationType="ContentType"
  7:     RegistrationId="0x01">
  8:     <CommandUIExtension>
  9:       <CommandUIDefinitions>
 10:         <CommandUIDefinition
 11:           Location="Ribbon.Documents.Groups._children">
 12:           <Group
 13:             Id="Ribbon.Documents.Custom"
 14:             Sequence="100"
 15:             Description="Custom Controls"
 16:             Title="Custom Controls"
 17:             Template="Ribbon.Documents.Custom.CustomTemplate">
 18:             <Controls Id="Ribbon.Documents.Custom.Controls">
 19:               <Button
 20:                 Id="Ribbon.Documents.Custom.Controls.BatchPublish"
 21:                 Sequence="5"
 22:                 Command="BatchPublish_PublishButton"
 23:                 Image16by16="/_layouts/images/Portal/publish16.png"
 24:                 Image32by32="/_layouts/images/Portal/publish32.png"
 25:                 ToolTipTitle="Release Selected Documents"
 26:                 ToolTipDescription="Publish a major version of all selected documents"
 27:                 LabelText="Release Selection"
 28:                 TemplateAlias="o1" />
 29:             </Controls>
 30:           </Group>
 31:         </CommandUIDefinition>
 32:         
 33:         <CommandUIDefinition
 34:             Location="Ribbon.Templates._children">
 35:           <GroupTemplate Id="Ribbon.Documents.Custom.CustomTemplate">
 36:             <Layout Title="LargeLarge">
 37:               <OverflowSection Type="OneRow" TemplateAlias="o1" DisplayMode="Large"/>
 38:               <OverflowSection Type="OneRow" TemplateAlias="o2" DisplayMode="Large"/>
 39:             </Layout>
 40:             <Layout Title="LargeMedium">
 41:               <OverflowSection Type="OneRow" TemplateAlias="o1" DisplayMode="Large"/>
 42:               <OverflowSection Type="ThreeRow" TemplateAlias="o2" DisplayMode="Medium"/>
 43:             </Layout>
 44:             <Layout Title="LargeSmall">
 45:               <OverflowSection Type="OneRow" TemplateAlias="o1" DisplayMode="Large"/>
 46:               <OverflowSection Type="ThreeRow" TemplateAlias="o2" DisplayMode="Small"/>
 47:             </Layout>
 48:             <Layout Title="MediumLarge">
 49:               <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Medium"/>
 50:               <OverflowSection Type="OneRow" TemplateAlias="o2" DisplayMode="Large"/>
 51:             </Layout>
 52:             <Layout Title="MediumMedium">
 53:               <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Medium"/>
 54:               <OverflowSection Type="ThreeRow" TemplateAlias="o2" DisplayMode="Medium"/>
 55:             </Layout>
 56:             <Layout Title="MediumSmall">
 57:               <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Medium"/>
 58:               <OverflowSection Type="ThreeRow" TemplateAlias="o2" DisplayMode="Small"/>
 59:             </Layout>
 60:             <Layout Title="SmallLarge">
 61:               <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Small"/>
 62:               <OverflowSection Type="OneRow" TemplateAlias="o2" DisplayMode="Large"/>
 63:             </Layout>
 64:             <Layout Title="SmallMedium">
 65:               <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Small"/>
 66:               <OverflowSection Type="ThreeRow" TemplateAlias="o2" DisplayMode="Medium"/>
 67:             </Layout>
 68:             <Layout Title="SmallSmall">
 69:               <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Small"/>
 70:               <OverflowSection Type="ThreeRow" TemplateAlias="o2" DisplayMode="Small"/>
 71:             </Layout>
 72:             <Layout Title="Popup" LayoutTitle="LargeLarge" />
 73:           </GroupTemplate>
 74:         </CommandUIDefinition>
 75: 
 76:         <CommandUIDefinition
 77:             Location="Ribbon.Documents.Scaling._children">
 78:           <MaxSize
 79:             Id="Ribbon.Documents.Scaling.Custom.MaxSize"
 80:             Sequence="15"
 81:             GroupId="Ribbon.Documents.Custom"
 82:             Size="LargeLarge" />
 83:         </CommandUIDefinition>
 84:       </CommandUIDefinitions>
 85:       
 86:       <CommandUIHandlers>
 87:         
 88:         <CommandUIHandler
 89:            Command="BatchPublish_PublishButton"
 90:            CommandAction="javascript:PublishDocuments();"
 91:            EnabledScript="javascript:function oneOrMoreEnable() { 
 92:                           var items = SP.ListOperation.Selection.getSelectedItems(); 
 93:                           var ci = CountDictionary(items); 
 94:                             return (ci > 0); 
 95:                           } 
 96:                           oneOrMoreEnable();" />
 97:       </CommandUIHandlers>
 98:     </CommandUIExtension>
 99:   </CustomAction>
100: 
101:   <CustomAction Id="Ribbon.Documents.Custom.Scripts"
102:       Location ="ScriptLink"
103:       ScriptSrc="/_layouts/Portal/Portal.RibbonActions.js" />
104: 
105: </Elements>

 

And as you could see we would call a function PublishDocuments(), so withing the Portal.RibbonActions.js we would need to have that function. From the context we can get the selected items, and the GUID of the current list, that allows you to create a SPList retrieving the SPListItems based on their ID and do the things you want, finally allowing you to check if actions succeeded and if not make a mention of it.

  1: PublishDocuments = function () {
  2:     var selecteditems = SP.ListOperation.Selection.getSelectedItems();
  3:     var currentListGuid = SP.ListOperation.Selection.getSelectedList();
  4: 
  5:     var context = SP.ClientContext.get_current();
  6:     var currentWeb = context.get_web();
  7:     var currentList = currentWeb.get_lists().getById(currentListGuid)
  8: 
  9:     var k;
 10: 
 11:     for (k in selecteditems) {
 12:         var listitem = currentList.getItemById(selecteditems[k].id);
 13:         var spfile = listitem.get_file();
 14: 
 15:         spfile.publish("Publish, so file can be send to client");            
 16:     }
 17:     
 18:     context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed));
 19: }
 20: 
 21: function success() {
 22:     SP.UI.Notify.addNotification('Succesfully published major versions'); 
 23: }
 24: function failed(sender, args) {
 25:     var statusId = SP.UI.Status.addStatus(args.get_message());
 26:     SP.UI.Status.setStatusPriColor(statusId, 'red'); 
 27:     latestId = statusId;
 28: }

 

As you can see we try a spfile.publish(), and then check if it was a success, and use the Notify option to show to a user that it was a success, if it failed we use the Status option that shows a red bar with the error.

 

As you can see with around 30 lines of JavaScript you can batch publish your files.

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot

jQuery Pager Update

23

Jul

A while ago I posted an custom version of a jQuery Pager, and the code of it. I was pretty stunned by the amount of hits that post got, so apparently its quite a hot topic.

However I found out that copying the code wasn’t so easy, and besides that there was a minor ‘bug’. With more then 10 pages the regex always stripped out only the first digid, redirecting the pages 10-19 to the first page, and 20-29 to the 2nd, and so on.

So hereby a new version (a zip this time), containing the fix: jquery.tablesorter.pager.zip (1,6 KB)

You can still use the following code to apply paging:

   1: <ul id="pager" class="pagination">
   2:     <li><a href="" class="first">first</a></li>  
   3:     <li><a href="" class="previous">previous</a></li>   
   4:     <li><span class="pagedisplay"></span></li> 
   5:     <li><a href="" class="next">next</a></li>  
   6:     <li><a href="" class="last">last</a></li>  
   7: </ul>

Share:

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot

jQuery Pager on a table

08

Jul

Most of you may know the popular posts on jquery and the use of it in SharePoint by Jan Tielens (he actually made a very nice post last day about the “search-as-you-type”. So jWuery can be used for good in SharePoint.

Today we needed to create a pager on a sortable table, and since we used this tablesorter. It would be convenient to use the pager that they provide, however it didn’t really meet our standards, so we decided to modify it a bit in order to provide a cleaner look.

   1:  (function($) {
   2:      $.extend({
   3:          tablesorterPager: new function() {
   4:   
   5:              function updatePageDisplay(table) {
   6:                  var c = table.config;
   7:                  var paginghtml = '';
   8:   
   9:                  var startoffset = Math.max(c.page - 1, 1);
  10:                  var endoffset = Math.min(c.page + 3, c.totalPages);
  11:   
  12:                  // hide paging controls if we only have one page
  13:                  if (c.totalPages == 1) {
  14:                      $(c.cssPageDisplay, c.container).hide();
  15:                  }
  16:                  else {
  17:                      $(c.cssPageDisplay, c.container).show();
  18:                  }
  19:   
  20:                  // remove old event handlers
  21:                  $(c.cssPageDisplay + ' a', c.container).unbind('click');
  22:   
  23:                  for (var i = startoffset; i <= endoffset; i++) {
  24:                      if (c.page + 1 == i) {
  25:                          paginghtml += '<a href="#page' + i + '"><strong>'
+ i + '</strong></a>';
  26:                      }
  27:                      else {
  28:                          paginghtml += '<a href="#page' + i + '">' + i + 
'</a>';
  29:                      }
  30:                  }
  31:   
  32:                  $(c.cssPageDisplay, c.container).html(paginghtml);
  33:   
  34:                  if (c.page == 0) {
  35:                      $(config.cssPrev, config.container).hide();
  36:                  }
  37:                  else {
  38:                      $(config.cssPrev, config.container).show();
  39:                  }
  40:   
  41:                  if (c.page == c.totalPages - 1) {
  42:                      $(config.cssNext, config.container).hide();
  43:                  }
  44:                  else {
  45:                      $(config.cssNext, config.container).show();
  46:                  }
  47:   
  48:                  // set up click handlers
  49:                  $(c.cssPageDisplay + ' a', c.container).click(function() {
  50:                      var pageno = /#page(\d)/.exec(this.href)[1];
  51:   
  52:                      table.config.page = pageno - 1; moveToPage(table);
  53:   
  54:                      return false;
  55:                  });
  56:              }
  57:   
  58:              function setPageSize(table, size) {
  59:                  var c = table.config;
  60:                  c.size = size;
  61:                  c.totalPages = Math.ceil(c.totalRows / c.size);
  62:                  c.pagerPositionSet = false;
  63:                  moveToPage(table);
  64:                  fixPosition(table);
  65:              }
  66:   
  67:              function fixPosition(table) {
  68:                  var c = table.config;
  69:                  if (!c.pagerPositionSet && c.positionFixed) {
  70:                      var c = table.config, o = $(table);
  71:                      if (o.offset) {
  72:                          c.container.css({
  73:                              top: o.offset().top + o.height() + 'px',
  74:                              position: 'absolute'
  75:                          });
  76:                      }
  77:                      c.pagerPositionSet = true;
  78:                  }
  79:              }
  80:   
  81:              function moveToFirstPage(table) {
  82:                  var c = table.config;
  83:                  c.page = 0;
  84:                  moveToPage(table);
  85:              }
  86:   
  87:              function moveToLastPage(table) {
  88:                  var c = table.config;
  89:                  c.page = (c.totalPages - 1);
  90:                  moveToPage(table);
  91:              }
  92:   
  93:              function moveToNextPage(table) {
  94:                  var c = table.config;
  95:                  c.page++;
  96:                  if (c.page >= (c.totalPages - 1)) {
  97:                      c.page = (c.totalPages - 1);
  98:                  }
  99:                  moveToPage(table);
 100:              }
 101:   
 102:              function moveToPrevPage(table) {
 103:                  var c = table.config;
 104:                  c.page--;
 105:                  if (c.page <= 0) {
 106:                      c.page = 0;
 107:                  }
 108:                  moveToPage(table);
 109:              }
 110:   
 111:   
 112:              function moveToPage(table) {
 113:                  var c = table.config;
 114:                  if (c.page < 0 || c.page > (c.totalPages - 1)) {
 115:                      c.page = 0;
 116:                  }
 117:   
 118:                  renderTable(table, c.rowsCopy);
 119:              }
 120:   
 121:              function renderTable(table, rows) {
 122:   
 123:                  var c = table.config;
 124:                  var l = rows.length;
 125:                  var s = (c.page * c.size);
 126:                  var e = (s + c.size);
 127:                  if (e > rows.length) {
 128:                      e = rows.length;
 129:                  }
 130:   
 131:   
 132:                  var tableBody = $(table.tBodies[0]);
 133:   
 134:                  // clear the table body
 135:   
 136:                  $.tablesorter.clearTableBody(table);
 137:   
 138:                  for (var i = s; i < e; i++) {
 139:   
 140:                      //tableBody.append(rows[i]);
 141:   
 142:                      var o = rows[i];
 143:                      var l = o.length;
 144:                      for (var j = 0; j < l; j++) {
 145:   
 146:                          tableBody[0].appendChild(o[j]);
 147:   
 148:                      }
 149:                  }
 150:   
 151:                  fixPosition(table, tableBody);
 152:   
 153:                  $(table).trigger("applyWidgets");
 154:   
 155:                  if (c.page >= c.totalPages) {
 156:                      moveToLastPage(table);
 157:                  }
 158:   
 159:                  updatePageDisplay(table);
 160:              }
 161:   
 162:              this.appender = function(table, rows) {
 163:   
 164:                  var c = table.config;
 165:   
 166:                  c.rowsCopy = rows;
 167:                  c.totalRows = rows.length;
 168:                  c.totalPages = Math.ceil(c.totalRows / c.size);
 169:   
 170:                  renderTable(table, rows);
 171:              };
 172:   
 173:              this.defaults = {
 174:                  size: 10,
 175:                  offset: 0,
 176:                  page: 0,
 177:                  totalRows: 0,
 178:                  totalPages: 0,
 179:                  container: null,
 180:                  cssNext: '.next',
 181:                  cssPrev: '.previous',
 182:                  cssFirst: '.first',
 183:                  cssLast: '.last',
 184:                  cssPageDisplay: '.pagedisplay',
 185:                  cssPageSize: '.pagesize',
 186:                  seperator: "/",
 187:                  positionFixed: false,
 188:                  appender: this.appender
 189:              };
 190:   
 191:              this.construct = function(settings) {
 192:   
 193:                  return this.each(function() {
 194:   
 195:                      config = $.extend(this.config, $.tablesorterPager.
defaults, settings);
 196:   
 197:                      var table = this, pager = config.container;
 198:   
 199:                      $(this).trigger("appendCache");
 200:   
 201:                      //config.size = parseInt($(".pagesize",pager).val());
 202:   
 203:                      /*$(config.cssFirst, pager).click(function() {
 204:                      moveToFirstPage(table);
 205:                      return false;
 206:                      });*/
 207:                      $(config.cssNext, pager).click(function() {
 208:                          moveToNextPage(table);
 209:                          return false;
 210:                      });
 211:                      $(config.cssPrev, pager).click(function() {
 212:                          moveToPrevPage(table);
 213:                          return false;
 214:                      });
 215:                      /*$(config.cssLast, pager).click(function() {
 216:                      moveToLastPage(table);
 217:                      return false;
 218:                      });*/
 219:                      /*$(config.cssPageSize,pager).change(function() {
 220:                      setPageSize(table,parseInt($(this).val()));
 221:                      return false;
 222:                      });*/
 223:                  });
 224:              };
 225:   
 226:          }
 227:      });
 228:      // extend plugin scope
 229:      $.fn.extend({
 230:          tablesorterPager: $.tablesorterPager.construct
 231:      });
 232:   
 233:  })(jQuery);                

As you can see not much changed, just some minor modifications that will allow you to ignore the selectable page-size, options to jump to last / first page, ignoring the showing of the paging when there are no pages to show, and the use of a ‘sliding’ window showing the current page, 2 pages two the left and two pages to the right of it.

Using it will be easy:

   1:  <ul id="pager" class="pagination">
   2:    <li><a href="" class="first">first</a></li>
   3:    <li><a href="" class="previous">previous</a></li>
   4:    <li><span class="pagedisplay"></span></li>
   5:    <li><a href="" class="next">next</a></li>
   6:    <li><a href="" class="last">last</a></li>
   7:  </ul>
Share:

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot

Debugging Masterpages

06

Feb

Have you ever spent time trying to debug a masterpage? Like every SharePoint developer I spent my days on the SharePoint platform, and since ‘everything’ you see in SharePoint is based on masterpages, it would be nice if they do what they should. However a lot of customers require customized masterpages, and a lot of these masterpages work just fine, and if they don’t you can easily debug them, or not.

Last week we had a nice issue; in IE6 (yea IE6) our masterpage didn’t show the navigation controls in the edit navigation page. Seems out the masterpage been missing a three divs:  

    <div id="zz1_CurrentNav_Data" style="display:none;"></div>

    <div id="zz1_TopNavigationMenu_Data" style="display:none;"></div>

    <div id="zz2_CurrentNav_Data" style="display:none;"></div>

 

Adding those divs (even with a display:none) will fix a lot of these errors. And there is a nice way to find out what other divs are required by SharePoint to show all data.  Since often you can see the error in the lower left of the screen (often something like: Scripts loading …), the underlying issue can be found in the JavaScript.  Just browse to the Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\1033 (make sure to change the 1033 to your regional code, so in my case it would be 1043 and make a copy of the BFORM.js, then edit the original one.  On rule 12418 you can find the var named L_PleaseWaitForScripts_Text this var is used to display errors, now start at the top of your document and search for it. You will see that its only used in the SPOnError_HandleErrors function, where you can find:

                     {
                                window.status=L_PleaseWaitForScripts_Text;
                     }

Just add a simple alert above the window.status that displays the msg passed to your function.

                     {
                                alert(msg);
                                window.status=L_PleaseWaitForScripts_Text;
                     }

 

Next time you open the page you want you will get a nice popup containing your error. Nice feature to debug your pages. (Though its only executed when the window.status is not complete)

 

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot

Virtual Earth 'nearest' location

22

Jan

Ever wonder what happens when you put your team from work in a locked room for 24 hours and ‘force’ them to make a site based on a certain ‘issue’. If you do take a look at Les 24 heures de Tam Tam (In Dutch though). Last Friday we had this ‘event’ for the first time and as you can on the pictures / movies we had a lot of fun.

It was for this event that I had brush up my Virtual Earth skills, since as a team we decided to make a small ‘route planner’ based on Virtual Earth. The idea was to create a new map, set up a layer containing a set of pushpins with the locations of all Snack corners, and based on the current location of the user show a route to the nearest Snack Corner. Pretty plain and simple we thought.  It happens to be a bit complicated though; the Virtual Earth API doesn’t have an option to get nearest pushpin, and getting the route for each pushpin and then deciding what the nearest location is would probably fail when there are more than 5 pushpins.

Then the old school math struck me: why not use Pythagoras do decide what the nearest pushpin is (though it might be not as precise as getting the Route and determine the exact distance,  but getting the overall distance it is a great improvement on performance).

So assuming you have a map, containing a set of pushpins (tip: check the veLayerSpec with GeoRSS), you can loop trough each pushpin and set the latitude and longitude to an array, and define two vars, one for the ID of the pushpin and one of the value of the Pushpin ( I used 999).  Then the next simple function  will be sufficient (comments inline):  

    function GetNearest(latlon)

    // latlon should be the location searchin from

    {  

        var temp = new String(latlon);

        var mySplitResult = temp.split(",")

       

        var searchLat = mySplitResult[0];

        var searchLon = mySplitResult[1];

       

        // latlon is striped in to two values

        // pushPinsLat is the lenght of the array that we will check our current lat/lon against

        // So we loop trough the array with pushpin locations

       for (var i=0, len=pushPinsLat.length; i<len; ++i )

       {

         

         var ppLat = pushPinsLat[i];

         var ppLon = pushPinsLon[i];

        

         // Math.abs returns an absolute value;

         var latcount = Math.abs(ppLat - searchLat);

         var loncount = Math.abs(ppLon - searchLon);

        

         //Math.pows gets our lat/lon raised to the power of 2

         var temp = Math.pow(latcount, 2);

         var temp2 = Math.pow(loncount, 2);

        

         // When we have the lat / lon we can do a square root

         // the dst will resemble the 'distance',

         var dst = Math.sqrt(temp+temp2);

        

         // Then only set the the pushpinVar containing the ID when the current ID is smaller

         // When were done looping trough the set ppID contains the pushpin ID nearest to current location

         if (dst < ppValue)

         {     

             ppValue = dst;

             ppID = latlon;

         }

       }

    }

 

So the math saved the day, since the actual getting of the route is easy; the API allows you to get a route with map.GetDirections(locations, options).  

Albert-Jan Schot schreef

Comments (0)

Albert-Jan Schot
Page 1 of 1 in the javascript category