Showing posts with label Sharepoint. Show all posts
Showing posts with label Sharepoint. Show all posts

Tuesday, December 8, 2015

Cannot create SharePoint 2010 site collections

You heard that right... Fixing the ability to create site collections within SharePoint.  Recently, I was tasked to get 3 environments in sync (DEV/TEST/PROD).  Part of this task was to set up User Profiles and My Sites with Self-Service Site Creation enabled.

Setting up DEV and TEST went rather smoothly.  At that point, I should have known there was a hidden whammie that would ruin a whole weekend of mine. I'm so naive sometimes...

Cannot Create Site Collections Error

This madness came to the forefront after setting up My Sites.  One of the last steps is to create a site collection with a template of My Sites Host.  While trying to create this site collection, I get a stack trace in my face:

Failed to load receiver assembly "Microsoft.Office.Server.Powerpoint.Web.MOSSHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" for feature "PowerPointServer" (ID: 5709298b-1876-4686-b257-f101a923f58d).: System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Office.Server.Powerpoint.Web.MOSSHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' or one of its dependencies. The system cannot find the file specified. File name: 'Microsoft.Office.Server.Powerpoint.Web.MOSSHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) at System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) at System.Reflection.Assembly.Load(String assemblyString) at Microsoft.SharePoint.Administration.SPFeatureDefinition.get_ReceiverObject() WRN: Assembly binding logging is turned OFF. To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There is some performance penalty associated with assembly bind failure logging. To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog]. &ErrorCorrelationId=c01e4e3a-1a39-401d-9b67-5a38961a7bd9

Clearly there is errant code running that isn't in the other two environments.  Reading the error, it's easy to discern there's a problem with that assembly.  Looking into the GAC, I noticed that this .dll didn't even exist.


I said this to myself... 

It's early in the outage window, so I jumped into triage mode.  Googling that error, there is not much out there at all.  That was disheartening and a punch in the gut.  There's nothing like fixing an error that didn't come up in DEV or TEST and nothing of value on the net to fix it.

Looking more into the error, it's easy to see there's a feature that is running this code:

Failed to load receiver assembly "Microsoft.Office.Server.Powerpoint.Web.MOSSHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" for feature "PowerPointServer"

Based on that feature being available to the farm, I was able to discern that Office Web Apps has been installed.  But why isn't this working and why is the .dll missing from the GAC?  Reading over the uninstallation steps for Office Web Apps, it became very apparent that this component was previously installed and then removed.  For some reason, the features are still activated within the farm. GREAT!!!  There goes my weekend...

Find and Remove the Office Web Apps Feature

Before last weekend, I hadn't much knowledge of installing or configuring Office Web Apps, so needless to say, I treaded lightly.  First up was to find the feature that I thought was the culprit.  Using this PowerShell command, I was able to peruse the features within the farm:

Get-SPFeature -Limit All | Out-File -FilePath "$pwd\spfeatures.log" 

Armed with this, it's easy to find the OfficeWebApps feature.  But that feature is scoped to a Site!

OfficeWebApps    0c504a5c-bcea-4376-b05e-cbca5ced7b4f     Site   

That cannot be the problem since I don't even have a site collection! So, I tried to create a site collection in a different web app, thinking maybe it was something with the My Site stuff.  NO BUENO.  Same stack trace in my face...  Directly below this feature in the log is another feature that looked very promising:

OfficeWebAppsStapling  fb67f269-fd1d-4f9a-af0b-50f5755e19d7  Farm   

The name of this feature and since this is scoped to the farm, I got the warm and fuzzies.  Since Office Web Apps wasn't in Programs and Features and the .dll was missing from the GAC, I decided that I could safely uninstall this feature from the farm altogether.

Uninstall-SPFeature -Identity "fb67f269-fd1d-4f9a-af0b-50f5755e19d7"

Once this was uninstalled, I hit paydirt.  I was able to create site collections again!  

As an aside, this farm hadn't created a new site collection in well over 2 years.  Check the created dates on your own farm sometime.

Get-SPSite -Limit All | % { $_.RootWeb.Created }

Beware of using -Limit All, it may cause a degradation in performance.

Another Office Web Apps Error

It was only a small victory in the lengthy weekend battle.  Now that the site collection is created, it's only natural to enjoy the fruits of you labor.  Upon visiting the site, I'm thrown and error with a Correlation ID.  TO THE ULS!!!

Error initializing Safe control - Assembly:Microsoft.Office.SharePoint.ClientExtensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c TypeName: Microsoft.Office.SharePoint.ClientExtensions.Publishing.TakeListOfflineRibbonControl Error: Could not load type 'Microsoft.Office.SharePoint.ClientExtensions.Publishing.TakeListOfflineRibbonControl' from assembly 'Microsoft.Office.SharePoint.ClientExtensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'.

What we have here is ANOTHER .dll missing from the GAC.  If you've been following along, you're probably yelling the answer out.  Uninstall the OTHER Office Web Apps feature.  I originally chose not too, since I was treading lightly. :)

Uninstall-SPFeature -Identity "0c504a5c-bcea-4376-b05e-cbca5ced7b4f"

I'm now able to visit the My Sites site collection!  Now it's time to click on My Content and create my own personal site collection. No whammies, no whammies, no whammies!!!

UGH!!! I can't get that lucky... Another stack trace in my face.


My Sites Error

After clicking My Content, the site collection was being provisioned and started the best Mr. Burns impression I can.  Not so fast...










There has been an error creating the personal site. Contact your site administrator for more information.

Unfortunately, this doesn't come with a Correlation ID, but the ULS will tell all.  Cracking open ULS Viewer and watching the ULS log live, I found........  You guessed it ANOTHER missing .dll that's related to Office Web Apps.  Are you starting to find a theme within this post?

The following exception occurred: Failed to load receiver assembly "Microsoft.Office.Excel.Server.HostExtensionWac, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" for feature "ExcelServerEdit" (ID: b3da33d0-5e51-4694-99ce-705a3ac80dc5).: System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Office.Excel.Server.HostExtensionWac, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' or one of its dependencies. The system cannot find the file specified.  File name: 'Microsoft.Office.Excel.Server.HostExtensionWac, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'    
 at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)    
 at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)    
 at System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)    
 at System.Reflection.Assembly.Load(String assemblyString)    
 at Microsoft.SharePoint.Administration.SPFeatureDefinition.get_ReceiverObject()    WRN: Assembly binding logging is turned OFF.  To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.  Note: There is some performance penalty associated with assembly bind failure logging.  To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

This one made me pause a little bit more, because it's not clear if this feature is a default feature of the farm.  Treading lightly, I opted to simply disable this one and not uninstall completely, just in case.

Disable-SPFeature –identity "ExcelServerEdit" -URL http://webapp

Wrap up

I'm not sure how or why Office Web Apps was removed, but the features that come with this component were still activated and made for an interesting weekend, nonetheless.  I was able to find some great resources out there for My Sites troubleshooting.  If you ever run into issues with this, I'd definitely take a look at these two links:
As for Office Web Apps, I found an update that lists what I would assume to be most of the .dlls that can be found within.  It helped me along the way:

I hope this helps someone save some precious weekend hours...

Tuesday, April 15, 2014

Large Lists, BCS, Excel REST, JSOM, CSVs and Office 365 OH MY!

An interesting challenge came across my desk last week:
"How can we import a CSV into Office 365 and use that data to tag other items within the site.  The CSV currently has ~7600 rows and is expected to grow.  We'll also need to re-import the CSV on an ad-hoc basis when the data changes."
The last part of that was the real issue.  There was simply no easy way of doing that.  After trying a few things, falling flat on my face, I'm prepared to share my exploration into the different options.

Each have their own merits and pitfalls.  This post will examine each and try to shed some light on the pitfalls I've found using each of these.

BCS

Wiring up an ECT on Office 365 can be a little finicky. I initially had some issues due to 2 BDC Models that were created for the same ECT.  After calling in the eagle-eyed BCS guru, +Fabian Williams, I got squared away.

Immediately after that, I could tell that I was not going to be able to use BCS for what I needed.  In Office 365, there is a hard limit of 2000 items that can be retrieved.  Ironically, the error message that is displayed is not a supported cmdlet for Office 365.

Office 365 BCS Throttle Error
Adding insult to injury, I decided to run a simple test using JSOM.  I wanted to clarify if JSOM would provide me PagingInfo with a BCS List.  Using the code below, you'd expect line 68 to produce a value instead of nothing at all.


Since BCS will not work and due to the limitation of the API, I had to look for another solution.

Custom List

Using a custom list initially worked great.  I'm able to use JSOM, query the list for 5000 items per trip to the server, AND get PagingInfo.  Using the code below works great for this scenario.


Importing the Excel into Office 365 is relatively straightforward and will work for most needs.  The file I used had about ~7600 rows of data.  After importing the file, I noticed the Server Resource Quota was tapped.

Office 365 Server Utilization
So using this approach has 2 problems.  I will not be able to do a mass import again of my data (the list already exists) and the Server Resource Quota points are exhausted.

Excel REST

This seemed like a cool way of getting around the limitations above, so I dove in to find out if this will work for my needs.  After all, I'm allowed to have a *lot* of rows in Excel and I'll be able to easily update the file, since it's in a document library.  Using the code below, I ran into a showstopper though.


There is a hard limit in the API set at 500 rows.  That would be painfully slow to get all of the items or even worse; a user may try to use the form control while this is still querying for data.

Excel REST API - 500 row limit
So that leaves us with our raw data that was exported from SQL and given to us to use.

CSV

Updating the CSV will be easy, since it will be stored in a document library.  Now all we need to be able to do is make sense of it.  Using the code below, I'm able to parse the CSV and create an array of objects that I need to pass off to another library.  Also note the use of localStorage.  This is a nice way to cache the data and prevent the retrieval/processing of the data client-side on every page load.  If the CSV is updated, simply clear the browser cache and you'll get the latest and greatest.

Conclusion

All approaches have their merits and pitfalls... BCS and PagingInfo, I'm looking at you!  If the ad-hoc mass-import wasn't needed, then using list driven data would have been my choice.  If I used that approach, I would have still used localStorage though.  It makes sense to cache the processed data since it'll not change very much.  Since my solution works client-side, I'll have to take into consideration the amount of time this takes to render.  I'm getting good performance out of the CSV approach, so I'm going to stick with it for the time being.

Friday, January 10, 2014

Fire Workflows with Initiation Parameters using #SPServices

Firing Workflows using Javascript, I've never had to pass in Initiation Parameters.  This post takes a look at how to do just that and provides some code that will allow easy use of Workflows in Javascript.

Setup Workflow

The workflow has to be set for Manual starting, otherwise this will not work.  Also, to pass in parameters to the workflow, you'll need to have Initiation Variable(s) within the workflow. I've only fiddled with Number and Single Line of Text fields, so if you use other column types, feel free to share your experience.

Workflow Initiation Parameters

This workflow is simply logging the variables out to the Workflow History.  Easy peazy...

Workflow Parameters

Looking over the documentation for SPServices and StartWorkflow, I found some examples that were a great starting point.  After fiddling a bit with 1 field, I decided to test this a little more.  I created a column with spaces in the Name field *gasp*.  I only did this to see how to handle this programmatically, so a word to the wise: Friends don't let friends create columns with spaces...

Reading over the examples, if you have multiple parameters, it says you have to change from passing the column name to this weird pattern: 

<Data><Parameter1>" + parameter1 + "</Parameter1><Parameter2>" + parameter2 + "</Parameter2></Data>

I've found this to not work at all for me at all [sad_panda]... Back to the drawing board, I guess. Then an idea came to me.  Since I'm targeting a column with a space in it, I tried what normally happens to spaces in Static Names: _x0020_.  So, I tried this next:

<Data><TextField>Will it blend?</TextField><With_x0020_Spaces>42</With_x0020_Spaces></Data>

However, this didn't work either!  Very curious to find a resolution, I set out to find out why this didn't work...  Using SPD (SharePoint Designer), you are able to view the files generated by the workflow. Opening up the XML file as text, you can clearly see that SPD removed the space in the Static Name.

Workflow wfconfig.xml
Within this file, all of the Initiation Parameters are visible and it's now easy to tell what's exactly going on.

Workflow Parameter Names
For all of this to work while using multiple parameters, you have to use the exact Static Name as defined in the XML.  The workflow parameters below work just fine for me now.

<Data><TextField>Will it blend?</TextField><WithSpaces>42</WithSpaces></Data>

Code to Fire the Workflow

This function will handle the pain of getting a workflow to fire.  All you need to know is the correct URL, the workflow name, and the workflow parameters ( if any ).

*** Update *** I took my original idea and made it more or less a plug-in for SPServices. Add this function to the SPServices source and it'll work without any issues. Original function:

Friday, November 22, 2013

Use Server Variables In XSLT To Find The Root Site

There’s no way to tell what the site’s Root Web URL will be, nor the protocol used on any given site, so here’s a quick rundown of how to get the Root Web URL using XSLT:

Variable Setup

This example requires these two server variables.
  • SERVER_NAME
  • HTTPS

ServerVariable - SERVER_NAME
Here’s a screenshot of what it looks like in SPD.  With these in place we'll be able to determine the Root Web URL dynamically, so this could be used on any site and the code will know exactly what the URL of the root web is.





XSLT Code

I've commented the key parts to this simple example, so if you want to just take those pieces you can.  Doing this through the GUI, it adds two <ParameterBinding />’s to your code.  Later in the code, you’ll see two variables that are used:
  • httpProtocol
  • sitePath
These variables are put together to form the URL of the Root Web.

<WebPartPages:DataFormWebPart runat="server" Description="" PartOrder="2" HelpLink="" AllowRemove="True" IsVisible="True" AllowHide="True" UseSQLDataSourcePaging="True" ExportControlledProperties="True" DataSourceID="" Title="" ViewFlag="8" NoDefaultStyle="TRUE" AllowConnect="True" FrameState="Normal" PageSize="-1" PartImageLarge="" AsyncRefresh="True" ExportMode="All" Dir="Default" DetailLink="" ShowWithSampleData="True" ListId="" ListName="" FrameType="None" PartImageSmall="" IsIncluded="True" SuppressWebPartChrome="False" AllowEdit="True" ManualRefresh="False" ChromeType="None" AutoRefresh="False" AutoRefreshInterval="60" AllowMinimize="True" ViewContentTypeId="" InitialAsyncDataFetch="False" MissingAssembly="Cannot import this Web Part." HelpMode="Modeless" ID="" ConnectionID="00000000-0000-0000-0000-000000000000" AllowZoneChange="True" IsIncludedFilter="" __MarkupType="vsattributemarkup" __WebPartId="" __AllowXSLTEditing="true" WebPart="true" Height="" Width=""><ParameterBindings>
                        <ParameterBinding Name="dvt_apos" Location="Postback;Connection"/>
                        <ParameterBinding Name="ManualRefresh" Location="WPProperty[ManualRefresh]"/>
                        <ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
                        <ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/>
                       
                        <!-- Set these two variables up within your DVWP/XLV Web Part -->
                        <ParameterBinding Name="HTTPS" Location="ServerVariable(HTTPS)" DefaultValue=""/>
                        <ParameterBinding Name="SERVER_NAME" Location="ServerVariable(SERVER_NAME)" DefaultValue=""/>
                    </ParameterBindings>
<DataFields>
@myField,myField;</DataFields>
<Xsl>
    <xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
        <xsl:output method="html" indent="no"/>
        <xsl:decimal-format NaN=""/>
        <xsl:param name="dvt_apos">&apos;</xsl:param>
        <xsl:param name="ManualRefresh"></xsl:param>
        <xsl:param name="webUrl" />
        <xsl:param name="HTTPS" />
        <xsl:param name="SERVER_NAME" />
        <xsl:variable name="dvt_1_automode">0</xsl:variable>
        <xsl:template match="/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
            <xsl:call-template name="dvt_1"/>
        </xsl:template>
        <xsl:template name="dvt_1">                       <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
            <xsl:variable name="dvt_RowCount" select="count($Rows)"/>
            <xsl:variable name="IsEmpty" select="$dvt_RowCount = 0" />
            <xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0"/>
            <!-- Use this variable to determine the protocol used by the site -->
            <xsl:variable name="httpProtocol">
                <xsl:choose>
                    <xsl:when test="normalize-space($HTTPS) = 'on'">https://</xsl:when>
                    <xsl:otherwise>http://</xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <!-- This variable concats all of these variables together -->
            <!-- Example sitePath output: http://iOnline247.me/ -->
            <xsl:variable name="sitePath">
                <xsl:value-of select="string(normalize-space(concat(concat(normalize-space($httpProtocol), normalize-space($SERVER_NAME)), '/')))" />
            </xsl:variable>   
                   
                    <!-- {...SNIP...} -->
        </xsl:template>
    </xsl:stylesheet>
</Xsl>

The end result is a nice URL.  Here’s a sample output: http://iOnline247.me/
I'm not sure why the protocol isn't straightforward to get.  I may have missed something obvious, but I didn't see any other way of determining the protocol.  If there's an easier way, feel free to let me know.

Thursday, March 21, 2013

RowLimit and CAML Query

I keep forgetting this, so here's a little note for me:

"<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='Author'/><Value Type='Integer'><UserID/></Value></Eq></Where><OrderBy><FieldRef Name='Created' Ascending='False' /></OrderBy></Query><RowLimit>1</RowLimit></View>"

There you have it... A valid usage of RowLimit within a CAML Query.

Thursday, March 14, 2013

jQueryUI Autocomplete with SharePoint Drop Downs

SharePoint Drop Downs Suck!

I can't go anywhere without having this UX nightmare on my hands.  How many times have you started typing in these drop downs, then tab away expecting the same UX you have, LIKE EVERYWHERE ELSE ON THE INTERNET?  The problem is, these drop downs are inconsistent in their behavior and even render completely different in Internet Explorer. If there are 20+ items within the list, you can guarantee calls to your helpdesk.  I've spent enough time working on this problem; enough to offer up some code so you can handle this yourself too.  Hopefully, you find it useful.

Search by Type UX

This has been blogged about a bunch... Consider this horse completely beaten and dead at this point, but nothing I've seen out there handles SharePoint drop downs the way I do with this code.  Without further adieu, the codez:



<link type="text/css" rel="stylesheet" href="//code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.min.css">

<script src="//code.jquery.com/jquery-1.8.3.min.js"></script>
<script src="//code.jquery.com/ui/1.10.2/jquery-ui.min.js"></script>

<script>
function getFormDDL( columnName ) {
var $ddl = $("select[title='" + columnName + "']"),
choices,
choiceArray,
index = 1
; //local vars
if ( $ddl.length > 0 ) {
$ddl.SPOptions = $ddl.find("option").map(function() {
var $el = $(this);
return {
label: $el.text(),
id: $el.val()
}; 
}).get();
$ddl.SPComplexDdl = false;
} else {
$ddl = $("input[title='" + columnName + "']");
choices = $ddl.attr("choices");
choiceArray = choices.split("|");
$ddl.SPOptions = [];
for ( ; index < choiceArray.length; index = index + 2 ) {
$ddl.SPOptions.push({
label: choiceArray[ index - 1 ],
id: choiceArray[ index ]
});
}
$ddl.SPComplexDdl = true;
}
return $ddl;
}
function setFormDdl( $ddl, lookupVal ) {
var choices,
choiceArray,
hiddenInput,
index
;

if ( $ddl.SPComplexDdl ) {
choices = $ddl.attr("choices");
hiddenInput = $ddl.attr("optHid");
$("input[id='" + hiddenInput + "']").val( lookupVal );

choiceArray = choices.split("|");
for ( index = 1; index < choiceArray.length; index = index + 2 ) {
if ( choiceArray[ index ] == lookupVal ) {
$ddl.val( choiceArray[ index - 1 ] );
}
}
} else {
$ddl.val( lookupVal );
}
}
/*****
*
* Main processing
*
******/

$( document ).ready(function() {
                // To use this, just add as many Display Names you want that are Drop Downs on your form!
var columnNames = [ "Full Name", "City" ]
;

// Don't touch... It works as is. :)
$.each( columnNames, function( index, columnName ) {
var $ddl = getFormDDL( columnName ),
$wrapper = $("<div class='sp-planet-autocomplete'>"),
idName = columnName.replace(/ /g, "-"),
$input = $("<input class='ms-long' id='" + idName + "' />")
; //local vars
// debugger;
// When ddl is complex, we need to hide the whole shebang.  Might as well do it for a simple ddl too.
$ddl.closest("span[dir='none']").hide();
$wrapper.append( $input );
$ddl.closest("td").prepend( $wrapper );
// Find autocomplete options here.
// http://jqueryui.com/autocomplete/
$input.autocomplete({
source: $ddl.SPOptions,
        minLength: 0,
          select: function( event, ui ) {
setFormDdl( $ddl, ui.item.id );
}
});

// If selection is made and then text is deleted, this will prevent that. This ain't my first rodeo...
$input.on("blur", function( el ) {
// debugger;
if( el.currentTarget.value.trim() === "" ) {
setFormDdl( $ddl, 0 );
}
});
});
});

</script>


SharePoint Drop Downs = Search by Type Now!

Now this is a cool alternative to the standard ho-hum.  It's safe to say that users know exactly what they are looking for when there are 20+ options to choose from.  With that in mind, providing a search by type UX feels natural.  Also as an added bonus, jQueryUI gives you: up and down keyboard navigation and enter/tab selection.  Having this enabled just FEELS good!

Search by Type Usage

The columNames variable is the only variable you have to change.  Everything else is handled by jQuery/jQueryUI magic under the hood.  The only caveat to my code is that it currently doesn't handle multiple selections.  My current needs haven't forced me to pursue this, but if you find it necessary, I may revisit it.  I'd be more inclined to do so, *if* it was profitable. :-)

Feel free to let me know how this code works out for you.  It *should* just work.

Tuesday, February 12, 2013

Setting field values using CSOM client side - Another look

Last night after hitting publish, I enjoyed a long ride home on the metro...  I was able to catch up on some of my reading.  It's a relaxing part of my day and sometimes exciting because I get to grind away on concepts that I'm working on.  Last night did not disappoint.

spUtils - setColumnVal


As I said in the previous post, I've already tackled this problem, however, I didn't really like the implementation.  So, here's my bright idea... Toggle the library back to use setColumnVal and see what the XML looks like under the hood.  Doing just that, here's what's produced( I've snipped this for brevity ):


<Method Name="SetFieldValue" Id="26" ObjectPathId="21">
  <Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
<Property Name="LookupValue" Type="String">DEV\Administrator</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
<Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
<Property Name="LookupValue" Type="String">DEV\spUser</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
</Parameter>
</Method>


So based on that, it's easy to see the people picker XML has to be an array of objects.  Let's give that a shot now using this code mixed with parseAndSetFieldValue.

spUtils - parseAndSetFieldValue revisited


spUtils.updateListItems({
listName: "spUtils",
updates : {
1 : {
"Title" : spUtils.isoDate(),
"AssignedTo" : [
{
LookupValue: "DEV\\Administrator",
LookupId: -1
},
{
LookupValue: "DEV\\spUser",
LookupId: -1
}
]
}
},
success: function( data, ctx ) {
debugger;
}
});

Using the code above produces this XML ( snipped as well for brevity ):

<Method Name="ParseAndSetFieldValue" Id="44" ObjectPathId="21">
  <Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object Type="Dictionary">
<Property Name="LookupValue" Type="String">DEV\Administrator</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
<Object Type="Dictionary">
<Property Name="LookupValue" Type="String">DEV\spUser</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
</Parameter>
  </Parameters>
</Method>


It's remarkably close to the XML that actually works.  The only thing that is different is the Object Type.  Sadly, this is all that it takes for this to FAIL.  Yep, that's right...  Trying to be smarter than the average bear, let's give it another shake.  This time, I'm going to take some code out of the setColumnVal method and drop it into an array.  Take a look at this:

spUtils.updateListItems({
listName: "spUtils",
updates : {
1 : {
"Title" : spUtils.isoDate(),
"AssignedTo" : [  SP.FieldUserValue.fromUser("DEV\\Administrator"),
  SP.FieldUserValue.fromUser("DEV\\spUser")
]
}
},
success: function( data, ctx ) {
debugger;
}
});


This in turn produces XML that *should* work!

<Method Name="ParseAndSetFieldValue" Id="44" ObjectPathId="21">
  <Parameters>
  <Parameter Type="String">AssignedTo</Parameter>
  <Parameter Type="Array">
  <Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
  <Property Name="LookupValue" Type="String">DEV\Administrator</Property>
<Property Name="LookupId" Type="Number">-1</Property>
  </Object>
  <Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
<Property Name="LookupValue" Type="String">DEV\spUser</Property>
<Property Name="LookupId" Type="Number">-1</Property>
  </Object>
  </Parameter>
  </Parameters>
</Method>


The only difference this time is the Method Name attribute.  Sadly, even this FAILS! I was going to continue with using numbers, but with this being a show stopper, I'm convinced I've researched this thoroughly enough.  This may be different in SP2013, it simply doesn't work in SP2010, therefore unreliable.

What's next?


Since I need the context of the list item to set its values when using the .update() method, it's not feasible to change what I have currently.  To set lookups and people picker values in CSOM, you have to use the code I've already written.  Guess it's time I start documenting the API, eh?

Monday, February 11, 2013

Setting field values using CSOM client side

A rather long time ago, I wrote some code to handle creation and updates of items when using Client Object Model.  This bit of code works very well however it's always made me feel like the API I've built had warts. Take a look for yourself.  This method allows for anyone to modify Lookup values as well as People Picker values.  Using this, you can also set multiple values without a problem.  That's the good...  The ugly is for this to work, it's necessary to tell spUtils the column type.  This ended up looking something like this:


/***************************************
Test 26 ~ updateListItems - Updates item's lookup and people picker column
***************************************/
spUtils.updateListItems({
listName : "Project Tasks",
updates : {
111 : { // the key is the item ID
"RelatedProject{L}" : spUtils.isoDate(),
"AssignedTo{P}" : 1,

     "Title" : "Hello, World!"
}
},
success : function() { debugger; }
});


// End codez


Notice the appended characters that represent the column type. This is fine but at the end of the day not very user friendly...  I've always thought I could do better.  This blog post will explore what I've found inside the SP Namespace and what my options are to fix it.

SP.Debug.js to the Rescue?

When looking at a listItem object in my debugger, I found a rather coy method.  It literally screamed at me: "Put me in the game coach!"... Totally looked over this one when building CRUD into spUtils.  With that said, I've made the change under the hood to use this method instead.

parseAndSetFieldValue: function(fieldName, value_) {ULS5Vl:;
        this.get_fieldValues()[fieldName] = value_;
        var $v_0 = new SP.ClientActionInvokeMethod(this, 'ParseAndSetFieldValue', [ fieldName, value_ ]);
        if ((this.get_context())) {
            this.get_context().addQuery($v_0);
        }
},


For posterity, here's the raw bits of the function call. Nothing too exciting here since it's really a wrapper for the ClientActionInvokeMethod. Come to think of it, what isn't a wrapper for ClientActionInvokeMethod inside SP.js?

Is parseAndSetFieldValue really up to snuff?

For me it was truly a magical moment coming across this method.  As of matter of fact, I made a note of it when I did to come back someday and put the spotlight directly on it...  As of a result, here are my findings in raw format ( I've highlighted the important pieces of XML ):


<!-- Test 1
[ "AssignedTo", ["DEV\\Administrator", "DEV\\spuser"] ]
XML sent to Server using Array of strings
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:10:13Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object Type="String">DEV\Administrator</Object>
<Object Type="String">DEV\spuser</Object>
</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="17" ObjectPathId="9">
<Parameters>
<Parameter Type="String">RelatedProject</Parameter>
<Parameter Type="Number">2</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="18" ObjectPathId="9" />
<Query Id="19" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
<Property Name="RelatedProject" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid look-up value. A look-up field contains invalid data. Please check the value and try again. 


<!-- Test 2 
[ "AssignedTo", "DEV\\Administrator, DEV\\spuser" ]
XML sent to Server using comma separated values inside a string.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:16:15Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="String">DEV\Administrator; DEV\spuser</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid data has been used to update the list item. The field you are trying to update may be read only. 

<!-- Test 3
[ "AssignedTo", "DEV\\Administrator;#DEV\\spuser" ]
XML sent to Server using the old semi-colon bang delimiter.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:28:42Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="String">DEV\Administrator;#DEV\spuser</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid look-up value.  A look-up field contains invalid data. Please check the value and try again. 

<!-- Test 4 
[ "AssignedTo", "DEV\\Administrator; DEV\\spuser" ]
XML sent to Server using text similar to typing into the control manually.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:32:14Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="String">DEV\Administrator; DEV\spuser</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid data has been used to update the list item. The field you are trying to update may be read only. 


<!-- Test 5
[ "AssignedTo", 1 ]
XML sent to Server using a single user id.  Works as a string as well.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:37:00Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Number">1</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Holy shit! It works....


<!-- Test 6
[ "AssignedTo", [ 1, 17 ] ]
XML sent to Server using an array of numbers.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:40:36Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object Type="Number">17</Object>
<Object Type="Number">1</Object>
</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Well, it doesn't fail... However the column is set to null.


Well that just sucks now doesn't it?  Using that method, I can ONLY use the user ID AND ONLY one user ID.  What a waste of time.  The method should really be called: parseValuesAndWasteMatthewsTime().

What about GetList?

I could query the current site, cache all of the list information and then figure out the column types on the fly.
While I know I could pull this off, I really don't think it's a great option.  CSOM is asynchronous in nature, which means I'd have to nest everything AFTER the initial call to get the list information.  That would be worse than what is already there...

What's next?

Since this inefficiency cannot be overcome in SP2010, I'm going to press on with the API I currently have because it simply works...  I'll continue to look for better ways to do this, as this is primarily why I've never documented spUtils.  I figured the API would change once I found a different way and didn't want to deal with the overhead of dealing with that.

Well since I feel like I've given the SP Namespace a fair shake, it's time to clean up the code and start vetting it for usage within SP2013.  Look for much more capabilities to be baked into this coming soon!

Example: spUtils.startWorkflow();  // This will not use web services to accomplish this. I'm trying to rely on SP.js for everything.

Happy coding!