Update Miscellany 2 comments   |   Tags: ColdFusion 9   ORM   New Stuff   |   Sorta-Perma-Link

Wow, it's been far too long since I've posted.  I've been quite busy as of late, and have been putting the finishing touches on a couple of pretty decent sized projects.

But perhaps most interesting to followers of this site is the fact that I'll be working with a friend of mine over the next couple of months to completely revamp a site that we think will be pretty sweet in a couple of months.  But the coolest part is that we're going to be creating this site--as much as possible--using exclusively ColdFusion ORM. As we make progress, I'm going to do my best to regularly blog about our experiences as we tap into this extremely cool technology that is available in CF9.

So anyway, stay tuned...I hope to have some killer posts up very soon :)

CFSharePoint: Copy Items from One Folder to Another 0 comments   |   Tags: ColdFusion 9   SharePoint   CFSharePoint   |   Sorta-Perma-Link

In a previous post, I wrote about watching out for data type mismatches when using the CFSharePoint tag.  While this is all well and good, much of my post was based on  a misconception of how an "inout parameter" works.  While I can't claim great knowledge of this, I think I have a better understanding.

From what I understand, an inout parameter expects a specifically named parameter--let's say "Result"--to be passed in.  When you pass this parameter to the web service, the value, or "out", should be the variable that you want returned.  So if I want the "results" parameter to return its value as "finalResults", you could do something like:

results="finalResults"


In the last post, I was under the impression that the initial value of "results" HAD to be a ColdFusion data type, like a structure or array.  This is not correct.  Sure, I can pass in the appropriate data type with the "results" parameter; however, whatever structure I pass in will overwrite the "out".  This is why if you run the example I provided in the last post, copying an "About.doc" file from one folder to another will really just create a new "About.doc" with the value of the binary object I passed to it--"a."  This is because by passing in the binary object on the "in", I inadvertantly overwrote what would normally be returned in the "out".

So if you really want to use SharePoint web services to copy files from one folder to another, you'll have to combine two methods: GetItem and CopyIntoItems.  Fortunately, now that I know how inout parameters work, this is cake.

Let's look at the GetItem call real quick:

<cfsharepoint    action="getItem"
        domain="localhost/mysite"
        userName="xxxx"
        password="xxx"
        name="sourceDoc"
        wsdl="http://localhost/mysite/_vti_bin/Copy.asmx?wsdl"
        params="#{                                       

 

            URL="http://localhost/mysite/Shared%20Documents/About.doc",
            fields="sourcefields",
            stream="sourcestream",
            getItemResult="getItemResult"
        }#"
/>



Nothing much to this.  However, you should note a few things.  For "fields", notice how I'm not passing a ColdFusion variable.  Rather, I'm simply naming the "out" of the fields parameter.  The same is true for "stream."  Instead of declaring that this should be equal to some ColdFusion binary object, I'm simply naming what I want the "out" of this parameter to be.

Now, if run this and dump the results for fields and stream, I'll get a collection of methods for the FieldCollection, and the binary "stream" of the source URL I provided.  Sweet!

 

Of course, I think now you're onto what comes next.  Let's plop in the example from before:

<cfscript>
    function SmartArray(lItems,delim){
    var ArrayContainer=StructNew();
    ArrayContainer.string=ListToArray(lItems,delim);
    return ArrayContainer;
    }
</cfscript>
<cfscript>
    destinationUrls = smartArray("http://localhost/mysite/Shared%20Documents/Sub-Directory/About.doc",",");
</cfscript>

<cfsharepoint    action="CopyIntoItems"
        domain="localhost/mysite"
        userName="xxxx"
        password="xxxx"
        name="destDoc"
        wsdl="http://localhost/mysite/_vti_bin/Copy.asmx?wsdl"
        params="#{
            sourceUrl="http://localhost/mysite/Shared%20Documents/About.doc",
            destinationUrls="#destinationURLs#",
            fields="#sourcefields#",
            stream="#sourcestream#",
            copyIntoItemsResult="copyIntoResult",
            results="finalResults"
        }#"
/>


If you recall the last example, I was passing empty (or mostly empty) ColdFusion variables for the fields and stream parameters.  This was a mistake.  While the fields and stream parameters on the GetItem method are inout parameters, they are NOT such for CopyIntoItems.  Rather, these are run-of-the-mill parameters that don't return an "out," so obviously they need real data.  

Fortunately, because of our success with the GetItem method, we have the right information.  Using the "outs" provided from the fields and stream parameters in our first web service invocation, we can pass these real, correctly data-typed values to the fields and stream parameters of the CopyIntoItems method.

That's it!  Now we have successfully created a mechanism for copying files in SharePoint!

 

CFSharePoint: Watch Out for Data Type Mismatches 2 comments   |   Tags: ColdFusion 9   SharePoint   CFSharePoint   |   Sorta-Perma-Link

UPDATE:

Okay, if you really want to get confused, keep reading :).  Since posting this, I've learned some important things that clarify some misconceptions mentioned in this post.  I'm leaving it here only for historical purposes.

To get better answers, check out this post.

Hey, we all learn, right? :-)

--------------------------------------------------

In my recent posts about ColdFusion/SharePoint integration, I've been dealing with pretty vanilla web service operations.  In all of the examples, I've been passing very generic data types--mostly xml and strings.  No biggie.  It's like communicating with Twitter :)  That changes right now.

Tonight, a reader e-mailed me, asking about the _vti_bin/Copy.asmx collection of web services for SharePoint.  In a nutshell, these methods allow you to copy images from document library to document library (using CopyIntoItemsLocal) or from server to server (using CopyIntoItems). Conceptually, these web services are straightforward--specify the source file, destination, and you should be done, right?  

Well, it's a little trickier than that.  In these web services, each method calls for "out" parameters to be used.  According to M, "The out  method parameter keyword on a method parameter causes a method to refer to the same variable that was passed into the method. Any changes made to the parameter in the method will be reflected in that variable when control passes back to the calling method."  

This is mostly Greek to me, but in practicality it means two things (from what I understand...):  First, to get a result for X out parameter, you have to pass in X with the method call.  Second, X has to be of the same data type.

And there's the crux of the issue.  If a funky data type is defined in the WSDL--like the destinationURL "array of strings"--you will have to send in the right kind of data type to the method to keep the web service from getting pissed off and giving you the finger.

So back to the e-mail.  The person who e-mailed me was trying to use "CopyIntoItems" to move files around document libraries, but kept encountering a data type mismatch error whenever the web service was invoked.  Being very curious, I decided to look into this a bit, and I think I've got it figured out (or at least working!).

First, let's look at the requirements for the method:


  • SourceUrl: A String that contains the absolute source URL of the document to be copied.
  • Results: An array of CopyResult objects, passed as an out parameter.
  • Fields: An array of FieldInformation objects that define and optionally assign values to one or more fields associated with the copied document.
  • Stream: An array of Bytes that contain the document to copy using base-64 encoding.
  • DestinationUrls: An array of Strings that contain one or more absolute URLs specifying the destination location or locations of the copied document.

SourceUrl is no issue, simply a string to pass.  Another vanilla data type.

Results I have still not figured out.  It turns out, however, that if you're only interested in moving a document from one place to another, it is sufficient to pass an empty string for this.

Fields require a plain-Jane ColdFusion structure to contain the FieldInformation objects--I'm not changing them, so I'm not interacting with this whatsoever.

Ok, now it gets a little hairy. 

For Stream, the method calls for an "array of Bytes."  Fortunately, the ColdFusion docs provide a handy map to show what ColdFusion data type should be used for this--binary.  

The final parameter--DestinationUrls--is by far the most curious.  This parameter calls for an "Array of Strings".  If you're like me, you'll probably look at that and immediately try to create a variety of generic CF arrays and structs to get this to work.  However, it won't.  This parameter is not looking for an array full of strings as one might imagine.  Rather, it is expecting a structural member named "String" that contains an array of URLs.  

Fortunately, people smarter than me have already taken care of this.  Using the following bit of code which I stole from an answer-to-my-prayers-blog-post, the proper structure for this parameter can be easily created.

Ok, so that's the skinny.  Here's the final code I used to get a successfully file copy from one document library to another:


    function SmartArray(lItems,delim){
        var ArrayContainer=StructNew();
        ArrayContainer.string=ListToArray(lItems,delim);
        return ArrayContainer;
    }


    destinationUrls = smartArray("http://localhost/mysite/Shared%20Documents/How%20to%20develop%20a%20Firefox%20extension.doc",",");







                domain="localhost/mysite"
                userName="xxx"
                password="xxxxx"
                name="destDoc"
                wsdl="http://localhost/mysite/_vti_bin/Copy.asmx?wsdl"
                params="#{
                            sourceUrl="http://localhost/mysite/Shared%20Documents/Sub-Directory/How%20to%20develop%20a%20Firefox%20extension.doc",
                            destinationUrls="#destinationURLs#",
                            fields="#fields#",
                            stream="#stream#",
                            copyIntoItemsResult="",
                            results=""
                        }#"
/>


As I mentioned, this is quick fix, and I still have a lot of outstanding questions about how this works, what "results" can be leveraged to do, etc.  Feel free to chime in if you have better answers or some clever solutions to make this feel a little less hacky. :)

 

CFSharePoint: Adding Groups...and Users to Them :) 17 comments   |   Tags: ColdFusion 9   SharePoint   CFSharePoint   |   Sorta-Perma-Link
Enough of lists!  The last few posts, I've been dealing with the same old boring lists, and I've grown tired of that.  So today, I want to show a quick example of how to use some of the Users and Groups web services to handle user and group management.

So let's say I have a brand new user.  They have astounding technical skills, so I decide that I want to add her to a special group of users that I'll call "Super Users".  The only issue, however, is that this group does not exist.  So to check all the items off my list and call it a day, I need to:
  • Create the "Super Users" group;
  • Add Maria to this group; and
  • Add some missing user information about Maria
The first thing we need is to create the new SharePoint group.  This is easy:

<cfsharepoint     action="addgroup" 
        domain="localhost"  
        userName="un"
        password="pwd"
        name="theresult"
        wsdl="http://localhost/_vti_bin/UserGroup.asmx?wsdl"
        params="#{
            groupName="Super Users",
            ownerIdentifier="mycomp\joel",
            ownerType="user",
            defaultUserLoginName="mycomp\joel",
            description="A group of really cool, really powerful users :)"
            }#"
/>


Regarding the info being passed, there's nothing really to speak of.  However, you should notice that the method "addgroup" is not listed in the CF9 reference as being supported by the <cfsharepoint> tag (and believe me, a quick test reveals that they mean it!).  Fortunately, this isn't a big deal.  While the method may not be directly supported by CF9, you can always pass the full path to the correct WSDL file with the "wsdl" attribute in the <cfsharepoint> tag and it will work just fine :)  (BTW, the correct WSDL file can be determined by looking at the MSDN reference for the web service method in question).

Ok, so we've created our group.  Now we need to add Maria.

<cfsharepoint     action="addusertogroup" 
        domain="localhost"  
        userName="un"
        password="pwd"
        name="theresult"
        params="#{
            groupName="Super Users",
            userName="Maria",
            userLoginName="mycomp\Maria",
            userEmail="marai@singularityconcepts.com",
            userNotes="She's pretty funny!"
            }#"
/>


Again, super simple.  In the params, you simply pass the name of the group and some basic information about the person being added.

That's about it, nothing to it.  However, let's assume for the sake of this post dragging on just a bit longer that during the process of adding Maria to the Super Users group we made a mistake--let's say we mispelled her e-mail address.  No problem, simply invoke the "UpdateUserInfo" and pass the right information:

<cfsharepoint     action="updateuserinfo" 
        domain="localhost"  
        userName="un"
        password="pwd"
        name="theresult"
        wsdl="http://localhost/_vti_bin/UserGroup.asmx?wsdl"
        params="#{
            userName="Maria",
            userLoginName="mycomp\Maria",
            userEmail="maria@singularityconcepts.com",
            userNotes="She's pretty funny!"
        }#"
/>


As with the "AddGroup" method, the "UpdateUserInfo" is not currently supported by the <cfsharepoint> tag.  However, as before, accessing the method is as easy as passing the path to the WSDL file in the "wsdl" attribute.

So these examples have been pretty simple.  However, they continue to show how easy CF9 makes interacting with SharePoint--even unsupported methods are handled with great ease.  Yep, I've very happy :)
CFSharePoint: Adding List Items with Attachments 0 comments   |   Tags: ColdFusion 9   SharePoint   CFSharepoint   |   Sorta-Perma-Link
In my last post, I showed how simple it is to post a list item to a SharePoint list from Coldfusion 9.  In this post, I want to expand a bit, showing how to add an attachment to a newly created list item.  I also thought it would be a bit more fun to use form data, rather than hardcoding everything...so here goes :)

<cfif isDefined('FORM.submit')>
    <cfsavecontent variable="xml">
        <Batch OnError='Return'>
            <cfoutput>
                <Method ID='1' Cmd='New'>
                    <Field Name='ID'>New</Field>
                    <Field Name='Title'>#FORM.title#</Field>
                    <Field Name='Favorite_x0020_Color' #FORM.color#</Field>
                </Method>
            </cfoutput>
        </Batch>
    </cfsavecontent>
    <cfset newItem = xmlParse(xml)>
    <!---Be sure to get the actual field name from the URL, not from the display in SharePoint--->
                                
    <cfsharepoint     action="updatelistitems"
                domain="domain"  
                    userName="username"
                    password="password"
                name="theresult"
                params="#{
                    listName="AAC20C17-A87D-4985-BA1D-9DD511EE9B9A",
                    updates="#newItem#"
    }#"/>
    <cfset theID = theresult.result[2].ows_ID />

    <cfset attach = filereadbinary(FORM.myfile)>
    <cfsharepoint     action="addattachment"
                domain="domain"  
                    userName="username"
                    password="password"
                params="#{
                    listName="AAC20C17-A87D-4985-BA1D-9DD511EE9B9A",
                    listItemID="#theID#",
                    fileName="#filename#",
                    attachment="#attach#"
    }#"/>
</cfif>

<cfform name="myform" method="post" enctype="multipart/form-data" target="addlistwithattachment.cfm">
    Title:    <cfinput type="text" name="title" /><br />
    Fav Color:  <cfinput type="text" name="color" /><br />
    Filename:  <cfinput type="text" name="filename" /><br />
    File:  <cfinput type="file" name="myfile" id="myfile" /><br />
    <cfinput type="submit" name="submit" value="Submit" />
</cfform>


So really, here we have another easy bit of code.  The insert method (updatelistitems) is unchanged, except that I've tied the field values to actual form data.  

Once the item is created, you can access it's SharePoint ID from the "Result" structure that SharePoint returns from the updatelistitems method.  Here, you're looking for the "ows_ID" key--this contains the index ID of the new list item.

Note:  Depending on how you've set up the Batch object, the result structure will change.  For example, with a single Method for the Batch, the returned structure looks like:

Result-->Array-->Struct-->ows_ID.  

For multiple Methods, however, the structure changes slightly:

Results-->Result-->Array-->Struct-->ows_ID

Obviously, this is not hard to check for, but just an FYI :)

Once you have this ID, you can invoke the "addattachment" method.  As with the others, it's super easy to use.  Start by converting the file to a binary object by running "filereadbinary" on the actual file.  Now that this is done, you pass the listName (from before), the listItemID (this is the index ID of the new list item), the name of the file, and the binary object that contains the file.

That's it!  With just a few lines of code, we've established a whole process for creating new list items with attachments.  Obviously, this is a very simple example--in the real world, I should do some checking to see if a file has been submitted, that the result structure is correct, and many other things...However, I think this gives a pretty good start and shows really how simple interacting with SharePoint from ColdFusion 9 actually is.  

Have fun :)
CFSharePoint...Yes! 6 comments   |   Tags: ColdFusion 9   CFSharePoint   |   Sorta-Perma-Link
The other day, I downloaded the beta for ColdFusion 9.  While waiting for the download, I was looking through some of the marketing copy that Adobe had on their site about the new features for this release.  I about fell off my chair when I saw that not only has ColdFusion 9 increased connectivity to Microsoft's SharePoint, but they have even built a tag to wrap up the functionality into one nice little package: <cfsharepoint />.

This one little tag is incredibly powerful.  It knocks out all of the painful and cumbersome web services wrappings required to deal with SharePoint's WSDLs.  Besides having over 50 built-in functions to deal with lists, document libraries, and the like, you can optionally point to other (or custom) WSDLs to access whatever <cfsharepoint> can't get at by default. 

So after reading about this, I just HAD to try it out.  Not surprisingly, ColdFusion makes a routine task like retrieving a list extremely simple.  Here's my test:

<cfset viewFields = xmlParse("<ViewFields><FieldRef Name='Title'/><FieldRef Name='ID'/></ViewFields>")>
<cfset query = xmlParse("<Query><OrderBy><FieldRef Name='ID'/></OrderBy></Query>")>
<cfset queryOptions = xmlParse("<QueryOptions></QueryOptions>")>

<cfsharepoint
    action="getlistitems"
    domain="domain.com"
    name="spVar" 
    userName="myusername"
    password="mypassword"
    params="#{
        listName="546585D2-7AC4-493F-8DC5-F089B9EEB0AD",
        viewName="A8720E14-A847-4255-8527-055D2DD97868",
        query="#query#",
        rowLimit="20",
        viewFields="#viewFields#",
        queryOptions="#queryOptions#",
        webID=""
        }#"
/>


<cfset results="#spVar.listitems.data#">
<h1>Site Punch List</h1>
<ul>
    <cfloop index="i" from="1" to="#arrayLen(results)#">
        <li><cfoutput>#results[i].ows_Title#</cfoutput></li>
    </cfloop>
</ul>




This is very straightforward.  For supported web service methods, you simple specify the method in the "action" attribute of the <cfsharepoint> tag.  In this example, I simply hard-coded my log in credentials, but the tag optionally supports the "login" attribute which can accept a pre-built domain/username/password object.  After that, you just specify the required fields for the web service method (which can be found in Microsoft's web services docs), and you're good to go.

Simple.  The only tricky bit, really, is building out the XML required by some of Microsoft's methods.  However, Microsoft's documentation of the requirements for these is pretty good, so with a bit of reading you'll have no trouble at all.

UPDATE:  The other day, someone asked a very good question about how to retrive the list/view GUID to use in the cfsharepoint tag.  I'm sure there are better ways of doing this, but here's a quick and dirty way to do it.  Hope this helps!
ColdFusion 9...Here I Come! 0 comments   |   Tags: ColdFusion 9   Bolt   Centaur   |   Sorta-Perma-Link
Oh yeah!  I just received email confirmation today that I have been accepted to participate in the Centaur (ColdFusion 9) alpha release!  Additionally, I am going to be able to beta Bolt, a new Eclipse-based IDE being developed by Adobe to be used in conjunction with ColdFusion.

This is going to be fun :)