Monday, November 12, 2012

Sliding Divs to Mimic Smart Phone Navigation

I had a need today for a UI control that would slide from one div to another, and then back to the first.  It turns out that jQuery UI makes this possible in a few lines of jQuery.  See the fiddle here.  Just click on the div.  It will slide to div 2, click again, back to div 1.   This mimics the behavior you often see on smart phones and tablets.

Wednesday, June 13, 2012

SharePoint People Editor Client Validation - The Easy Way

There are quite a few blogs that discuss how to validate the SharePoint 2010 people editor on the client side. These range from very simple DOM checks to full blown custom solutions. The problem I encountered in looking at these is that most assume that the user will click the check names validate button on the control first, before the control is validated on the client.  I don't like this assumption, as it may or may not be valid, depending on the user.  Hence the solution below.  

My first thoughts were as follows.  When using a people editor manually, the steps taken are first to type in the user or group, then to click the validate button. Doing so makes an asynchronous call back to the server (internal ajax in the people editor I think, although Fiddler shows the entire page being posted... I will look at this further when time allows).  If the control is valid, great, proceed. Otherwise, the value would be manually fixed, and rechecked until it passed validation. Let's do just that, but in code.  

This requires two simple steps: 
  1. Programmatically click the People Editor check button.
  2. Determine if the control is valid after waiting for the check to finish.
Clicking the button is easy with jQuery.  First, in your aspx or ascx where the people editor exists, lets create a JavaScript variable for the people editor client id:

<script type="text/javascript" language="javascript"> var peId = '<%= pe.ClientID %>'; </script>
Now we can create a function that will click the people editor check names button for us, wait for the async response, and check if the control is valid.
 <script type="text/javascript" language="javascript">  
 function CustomValidatePeopleEditor() {  
   $("#" + peId + '_checkNames')[0].click();  
   setTimeout(function() {  
     if ($("#" + peId).find('*[isresolved="False"]').length > 0) {  
       // Do stuff for an invalid editor (only writing the the f12 console here)  
       console.log('false');  
   } else {  
       // Do stuff for a valid editor (only writing the the f12 console here)  
       console.log('true');  
     }  
   }, 500);  
 }  
 </script>  
Note that setting the timeout is not great, but should work even on an air card.  With a dial up connection you may be sol, but who uses dial up?  Still, just to be sure, an additional check should be made on the validity of the control at the time the form is posted.  You can do this in your submit code, on a domain object, in a helper class, or anywhere else that you see fit...
Pretty easy, no?

Friday, April 27, 2012

Google Drive Vs. Office 365 SharePoint

Unless you have been living under a rock, you have heard about this past weeks launch of Google's  Google Drive .  Although this product more closely competes with DropBox, is this product in direct contention with Office 365 SharePoint?  I was asked this by a client just the other day.  One of his SharePoint users made a claim to him that they were going to stop using overly complicated SharePoint, and start using Google Drive for file sharing.  Although I am already using DropBox for personal file storage, I do use and enjoy Gmail for personal email, and was excited to see what Google had come up with.  


Setting up  Google Drive was easy and intuitive, as most Google products are.  I was not really surprised to see that Google Drive is simply an extension of a relatively unchanged Google Docs.  It allows the download of googledrivesync.exe, which I downloaded and installed on Windows 7 Professional SP1 x64.  This creates a folder at "C:\Users\User Name\Google Drive" that works very similarly to DropBox.  Files and folders moved or copied into this location sync automatically with Google Docs, er, I mean Google Drive.  Edits to these files and folders can be performed locally or through Google's web apps.  Simple permissions and sharing management is done through the web interface.


Here are some of the things I liked about Google Drive:
  • 5GB for Free!
  • Easy to get up and running
  • Easy to use, especially if you are already familiar with Google
  • Access to your files on other devices
  • Can even download as Microsoft Office file types in the File menu under "Download As"
  • The drop down arrow in the search box within the web interface gives some nice filter options
  • Permission choices are simple, which is good for casual users (Can edit, Can comment, or Can view)
And here are some things I didn't like so much:
  • Google Drive does not directly integrate with Microsoft Office client products.  While casual users will find the features in Google web apps adequate, business users will be at a major disadvantage to peers working in client versions of Microsoft Office.
  • Testing a 5MB text file crashed my browser (tested in both IE 9 and Chrome), making online editing for files larger than a grocery list unrealistic.
  • Permission choices are too limited for corporate intranets (e.g. you cannot allow other users to manage permissions on documents that you are the owner of).
  • You have to have a google account to access a shared file.  I shared a file to my hotmail address, which when receiving, required me to login to google to follow the link to the document being shared.  You may be saying, "yea, but you would need access to SharePoint as well".  This is true, but in an office setting, your SharePoint login is tied to your AD account, so you already have that, for free, for certain.  As a side note, DropBox allows a right click on your file system to create a url that you can email to anyone with no special access required (love it!).
As to the point of this post, it is difficult to compare Office 365 SharePoint to Google Drive, as they are very different products.  Office 365 SharePoint is a platform that does many things, only one of which is file sharing.  In addition, Office 365 SharePoint is focused on business users, while Google Drive is focused on individual users.  Comparing just the Office 365 SharePoint file sharing capabilities to Google Drive, again, is hard to do, because they are built for two very different purposes.  Google drive is for storage and sharing of your personal files.  This is one repository of your files, yes, ONE repository, and yes, YOUR files.  This is great if you have a dozen or so folders with mortgage spreadsheets, personal resumes, family pictures, personal pdfs, video from the family vacation, and so on.  This does not scale at all if you have 20 business users trying to access and maintain 20 different files, each with a different owner.  What about 100 users and thousands of files?  What about thousands of users and tens of thousands of files?  As IT Professionals, we all know how hard it can be to get one user to do one task.  Now imagine having to get hundreds of different Google Drive users to each update certain files permissions and sharing every time a new user is introduced to a new team, or every time a user leaves a team.

Even after reading this or other blogs with similar opinions, I'm sure that some brave souls will try to use Google Drive for their enterprise sharing and storage system, and I wish them well, I really do.  But at the same time, I cringe to think about the painful mess they will find themselves in within no time at all, and hope that they don't run to their IT Director (possibly you) pleading for a bail out.  Office 365 SharePoint gives one central location for all users to collaborate.  This central location can be as simple as one repository (one site with one document library), or provide extremely customized experiences with many well managed, easy to find repositories (many site collections, with many sites, and with many document libraries). In SharePoint, trained site administrators are managing permissions on sites, document libraries, and maybe even individual documents.  This is so that the end business user can focus on the trials and tribulations of their own job, not on becoming a permission and sharing administrative wizard.  Using Google Drive to attempt large scale enterprise sharing would have business users spending their time sending individual invites each time a new user requests access to a document.  The documents of an organization will be impossible to moderate as they will be scattered across many users' Google accounts.  What happens when someone quits?  Their Google account goes with them, and so do the companies documents.  What about users that don't have Google accounts?  They may not want to have another username and password to remember.  In Office 365 SharePoint, some power users may start doing more advanced management of permissions on documents, but the keyword here is "may".  They don't have to, as the site owner should be performing these responsibilities, and will probably only give users permissions to do the same if they are capable and willing.

Is Google Drive in direct contention with Office 365 SharePoint?  No, not by a long shot.  If you use gmail for your personal email, and need a place to backup personal files for free, Google Drive is a great choice.  With that being said, I like my gmail for personal emails, but will continue to use DropBox for personal file backup and sharing due to its simplicity and superior windows integration.  SharePoint hands down offers a much more feature ready and enterprise based experience for file sharing within the business world.


For me, for now, I will continue to use SharePoint/Office 365 SharePoint for the enterprise, and DropBox for personal file storage and sharing.


Cheers!

Friday, March 23, 2012

Refresh the Parent showModalDialog

There are many blogs showing how to refresh the parent window when returning from a SharePoint modal window using SP.UI.ModalDialog.showModalDialog.  However, I could not find one that worked adequately  for my needs.  The use case discussed here is for launching an application page within a modal.  When the application page is submitted, the parent window should refresh.  When the modal is closed (i.e. x'd out of from upper right of the window), the parent page should not refresh.  In addition, the submit happens within an SPLongOperation using block in the code behind of the application page.  For some reason, calling the following at the end of the using statement did not work.


  operation.EndScript("window.frameElement.commitPopup();"); 


Hence the fix was performed in the JavaScript where the modal was originally launched from.  A callback function was added to the options of SP.UI.ModalDialog.showModalDialog.  This code is as follows:


  function refreshParent(result, target) {
    window.parent.location = window.location.href;
  }


The problem with this was that when the modal was canceled out of (i.e. not submitted), the parent would still refresh.  Although this is not the end of the world, it clearly lessens the user's experience.  By adding a check for the result, this can be remedied.  The entire script is as follows:


  function launchModal(url) {
    var options = SP.UI.$create_DialogOptions();
    options.url = url;
    options.dialogReturnValueCallback = Function.createDelegate(null, refreshParent);
    SP.UI.ModalDialog.showModalDialog(options);
  }


  function refreshParent(result, target) {
    if(result == 1) window.parent.location = window.location.href;
  }


Now the parent window refreshes as expected.

Monday, March 19, 2012

Get the Selected Date/Time within a SharePoint 2010 Calendar using jQuery

The selected date in a SharePoint 2010 calendar may be needed when writing custom solutions. This can be retrieved using JavaScript and jQuery.  First, we need to determine if we are in the month, week, or day scope of the calendar.  There is probably a better way to do this, but the following seems to work:



// make these global
var selectedDate = 'no date selected';
var selectedTime = 'no time selected';


// put the rest of the code in a function
function GetSelectedDate() {

  var dayScope = $('table.ms-acal-detail');
  var weekScope = $('table.ms-acal-detail');
  var monthScope = $('table.ms-acal-month');


  var isDayScope = dayScope.length > 0 ? dayScope.attr('summary').indexOf('Daily') > -1 : false;
  var isWeekScope = weekScope.length > 0 ? weekScope.attr('summary').indexOf('Weekly') > -1 : false;
  var isMonthScope = monthScope.length > 0 ? monthScope.attr('summary').indexOf('Monthly') > -1 : false;


Now lets get the selected element:


var selecteddateelement = $('.ms-acal-vitem');


Now that we know which view we are in, and have the selected element, we can determine the selected date if we are in the monthly scope.


  if(isMonthScope) {
var tditem = selecteddateelement.parent();
var tritem = selecteddateelement.parent().parent();
var prevtr = tritem.prev();
var indx = tritem.children().index(tditem) + 2;
var dttd = prevtr.children(':nth-child(' + indx + ')');
if(selecteddateelement.length > 0) selectedDate = dttd.attr('date');
  }

Next lets handle the weekly  scope .

  else if(isWeekScope) {
var weektritem = selecteddateelement.parent();
var weekdayindx = weektritem.children().index(selecteddateelement) + 1;
var weekselectedhalfhourstarttime = $('[class^=ms-acal-hour]').index(weektritem) / 2;
var weekdttd = $('.ms-acal-week-top').children(':nth-child(' + weekdayindx + ')');
if(weekdttd.length > 0) selectedDate = weekdttd.attr('date');
if(weekselectedhalfhourstarttime >= 0) selectedTime = weekselectedhalfhourstarttime;
  }

And finally the daily  scope .

  else if(isDayScope) {
var verbosedaydate = $('.ms-acal-display').text();
var daydatesplit = verbosedaydate.split(/,| /);
var month = daydatesplit[1];
var dayscopemonth = (new Date()).getMonth() + 1; // default to current month


if(month == "January") dayscopemonth = 1;
else if(month == "February") dayscopemonth = 2;
else if(month == "March") dayscopemonth = 3;
else if(month == "April") dayscopemonth = 4;
else if(month == "May") dayscopemonth = 5;
else if(month == "June") dayscopemonth = 6;
else if(month == "July") dayscopemonth = 7;
else if(month == "August") dayscopemonth = 8;
else if(month == "September") dayscopemonth = 9;
else if(month == "October") dayscopemonth = 10;
else if(month == "November") dayscopemonth = 11;
else if(month == "December") dayscopemonth = 12;


var dayscopeday = daydatesplit[2];
var dayscopeyear = daydatesplit[3];


selectedDate = dayscopemonth + '/' + dayscopeday + '/' + dayscopeyear;


var daytr = selecteddateelement.parent();
var dayselectedhalfhourstarttime = $('[class^=ms-acal-hour]').index(daytr) / 2;
if(dayselectedhalfhourstarttime >= 0) selectedTime = dayselectedhalfhourstarttime;
  }

Now the selectedDate and selectedTime will be populated.  Echo these to the console to see the values:

  console.log("selectedDate:    " + selectedDate); // change this to do something with the date
  console.log("selectedTime:    " + selectedTime); // change this to do something with the time
  // end the function
}

The selectedDate and selectedTime variables should be global variables.  This allows the preceding code to be put into a function, say "GetSelectedDate()" and bound to a click event on the calendar.  The issue with not doing this is if you are trying to call this code from a button or anchor click outside of the calendar, when clicked, first the calendar looses focus, then the ms-acal-vitem class is stripped.  The result is that nothing is selected when the code would run, coming back with "no date selected" every time.  Binding to click would look like this:



$(document).ready(function () {
$('.ms-acal-rootdiv').unbind("click").click(function () {
GetSelectedDate();
});
});



Doing so will set the global variables selectedDate and selectedTime every time a click is made.  Another thing you may want to do is create a function to set these variables to the current date and time.  This could be called first, then the preceding code, resulting in selectedDate and selectedTime always having a date populated.


It seems like a long way to go to get the selected date, and does not handle localization.  I would love to see what others are doing to solve this issue.  I know there are some xslt solutions out there too.


Cheers!