Showing posts with label sharepoint. Show all posts
Showing posts with label sharepoint. Show all posts

Friday, January 17, 2014

Hide Specific Buttons on a SharePoint 2010 Ribbon Using Javascript

I recently had a project where I needed to have the user take action on SharePoint data using a custom form, and then have a workflow handle the updates into another library, thus preventing the user from directly managing the main body of data. Contribute permissions were required because SharePoint Workflows don't have the ability to 'run as' admin or service or any other user or to set permissions on an item in the workflow. Both of those would be great additions to the SharePoint Workflow foundation.

So I used a combination of custom forms, specific permission levels, audience targeted web parts and other means to show the user only what they need to see and create "security through obscurity."

Contribute(edit) permissions are required in this case, but I wanted to allow a user to "view" a list item without having the prominent "Manage" ribbon group and the "Edit" button at the top of the form page (dispform.aspx, etc...)

There are fairly easy ways to hide the ribbon from certain users which work well but it's not granular enough and my permissions on this site are not that simple. It is also possible to create a custom ribbon and/or code and deploy to achieve these things.

BUT:
I wanted an easy, quick, and non-invasive means of shutting off certain buttons. So here is what I did.

The ribbon is split up into tabs. Each tab can have one or more groups of controls, and each group can hold one or more controls.




It's easy to find the id of any one of these and then use javascript to edit the object.

In Internet Explorer, hit F12 (developer tools) or use Firebug or some other equivalent in another browser.
Find the ribbon using the click tool or search for "<div class="loaded" id="RibbonContainer">"


You can see the structure of the whole ribbon there along with all of the element IDs. Pay attention to the CSS classes for an easy way to pick out the pieces.



OK, so anyone familiar with javascript knows that you can grab the object and set a style parameter like this:
object.style.parameter = value;
So it is logical that to hide the group, I just set object.style.display = 'none'; and it will disappear.

Not too complex, except that SharePoint ribbons are loaded when needed, not on page load, so you can't just put some javascript at the end of the page and hide the element. So first you need to make sure the object exists. If it does not exist, wait and try it again in some period of time. And lastly, if you don't want a looping script, so set a counter to make sure it only tries a few times before completely giving up.

Here is my example using the "Clipboard" group

<script language="javascript">
var cntr = 0; //set counter
function hidegroup(){
    if(document.getElementById('Ribbon.EditingTools.CPEditTab.Clipboard') != null){ //check to make sure object exists
        document.getElementById('Ribbon.EditingTools.CPEditTab.Clipboard').style.display = 'none'; //hide it
    }else if(cntr<6){//does not exist, check counter
        cntr++;//increment counter
        setTimeout('hidegroup()', 1000); //wait 1 sec and then repeat
    }
}
</script>
  • The counter starts at zero and once it runs 5 times, it quits.
  • If the object exists, it hides it. I noticed that if you set innerHTML to an empty string, the other buttons may not function.
  • If the object doesn't exist it increases the counter by one and then waits 1,000 miliseconds and then calls the function again.
To use this on a page, edit and place a content editor web part on the page under the form web part, edit the HTML of the content editor web part and put the script into the HTML field.



Set the web part to hidden (Edit Web Part -> layout (section) -> Hidden) so that nobody sees it. Hidden web parts are still on the page and the javascript will run just fine.

The last step is to use the web part's audience targeting field (in the "Advanced" section) to decide who will see the web part. The targeting can be done on groups and/or users. Whoever is targeted will have the web part, and whoever has the web part will have the ribbon group disabled.



This is perfect for a form like dispform.aspx or editform.aspx that automatically displays the ribbon. The only delay is in loading the ribbon. Other pages have ribbons that load contextually whenever the user interacts with a control that has a ribbon. Removing controls or groups for those pages could be done by removing the counter, which would cause a constant loop waiting for the element to show up.



For me, this works perfectly because it was a quick fix, can be targeted to a SharePoint security group, can remove specific items, and is all done in the browser.

Disclaimer: this is by no means the best way, but it is very simple and quick.


Friday, February 15, 2013

CAML, IntApp, SharePoint


I just got done doing a bunch of work in IntApp Integration Builder where I had to do things like reading/updating SharePoint lists and libraries.

This post/blog is never going to be highly detailed in any one area. I have no interest in explaining CAML, XML, IntApp, web services, logic, etc. There are plenty of resources online to find out how to write a CAML query. The reason I write this stuff down at all is because I have spent a lot of time looking for solutions that have been solved by others... but they don't usually share the information. Lots of questions with few answers. The goal here is to share the answers that I've found so someone else doesn't have to go through the try/fail/try again steps to get the answer.


If you use IntApp, they have a lot of great resources. You'll need to sign up on their customer community portal, but you can then find templates for solutions. This is the barebones "here is how we did this" kind of template that lets you skip past the first 20% of the setup.

So here are the basics:

SharePoint Web Services:
Every MS SharePoint site has a set of web services.
You can access them via URL by adding "_vti_bin/lists.asmx" to the end of your site URL like this:
http://yoursite/_vti_bin/lists.asmx
You'll need to us WSDL format (web services description language) so you add "?wsdl" to the end of the URL.
http://yoursite/_vti_bin/lists.asmx?wsdl
"Lists.asmx" is one of many web services. Lists.asmx gives you access to the lists in your SharePoint site. A library is a sub-type of list, so from now on, I'll just call it a list.

On to the CAML:
(Collaborative Application Markup Language)


You'll need a SOAP (Simple Object Access Protocol) wrapper around your query.
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
     <soap12:Body>

          Your web service call goes here
     </soap12:Body>
</soap12:Envelope>
 You'll also need the web service call. Each web service has multiple functions. I'll use "GetListItems" as the example. The name should be self-explanatory... it "Get"s a "List"s "Items". At least one piece of information you need... "listName" is the GUID of  the list or library that you wish to query.

 <GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">
     <listName>_YOURLISTGUID_</listName>
     <viewName>_YOURVIEWGUID_</viewName>
     Your CAML query goes here (or somewhere in the "GetListItems" tag.) The order shouldn't matter)
     <ViewFields>
          <FieldRef Name="ID" />
          <FieldRef Name="Title" />
          <FieldRef Name="yourfield" />
          ...etc
     </ViewFields>
     <rowLimit>0</rowLimit>
</GetListItems>

Quirk #1:
When querying a SharePoint web service, the "ViewFields" don't matter. You need to have the tag there, and it can be empty "<ViewFields></ViewFields>", but don't bother listing the fields you want to have returned in the dataset. The fields that are returned are defined in the view.
The "viewName" GUID is optional. If you leave it out, the list's "default" view will be used and you will only be able to access the fields that are returned in that default view. So if you've added a custom column into your list and it's not in the default view, you will need to add it there or create another view containing the fields you need and then use "viewName" to tell the web service which view to return.

** How you find your SharePoint list or view GUID? **

OK, So new we just need a query. There a lot of good resources online to figure out how to write a CAML query.

Quirk #2:
Normally your CAML query is wrapped in this:
<Query>...</Query>
With these web services you'll need to put an extra <query> tag (lower-case "q") wrapping the whole thing, like this:
<query><Query>...</Query></query>
If you don't do this, your query will be ignored, the web service will be called, and your dataset will be all values from the view sorted in the default sort order for the view that you've chosen (or the default view if you've not set a viewName GUID). If you've got data returned and the filter or sort doesn't work, this is probably why.


By the way the "<rowLimit>" tag works just fine. Use "0" to return all, or another number to limit the dataset to that number of records.


Here are some MORE TIPS for working with IntApp Relationship Builder and SharePoint web services.

SharePoint Web Service Dataset Basics

More useful information:
The "GetListItems" function of the http://yoursite/_vti_bin/lists.asmx?wsdl SharePoint web service returns XML and you need to have this path to access the returned data:
soap:Envelope/soap:Body/GetListItemsResponse/GetListItemsResult/listitems/rs:data/z:row
The nodes involved (and therefore the path) will differ depending on which web service and function you are calling.

If you work with XML you'll understand nodes and how to decipher the path.
Logically, XML is simply nested data. the first node is soap:Envelope. Inside that is soap:Body, inside that is GetListItemsResponse, and so on.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetListItemsResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
            <GetListItemsResult>
                <listitems xmlns:s='uuid:yourGUID' xmlns:dt='uuid:yourGUID' xmlns:rs='urn:schemas-microsoft-com:rowset' xmlns:z='#RowsetSchema'>
                    <rs:data ItemCount="X">
                        <z:row ows_FieldName1='FieldValue' ows_FieldName2='FieldValue'/>
                        <z:row... etc>
                    <rs:data>
                <listitems>
            </GetListItemsResult>
        </GetListItemsResponse>
    </soap:Body>
</soap:Envelope>
Tips:
  • SharePoint prepends "ows_" onto the field names.  i.e. "Title" becomes "ows_Title".
  • Any space in the field name is replaced with "_x0020_". i.e. "My Field" becomes "My_x0020_Field".
  • Calculated fields return "string;#" before the value. i.e. a value of "Jimmy" becomes "string;#Jimmy" and you'll have to strip that or at least compensate for it before you use the data.

Here is a good post on SharePoint field naming conventions over at Web BorG. 

SharePoint GUIDs


A GUID is a Globally Unique Identifier.
Here is how you find your SharePoint list or view GUID:
The easiest way is to go to the list's default view (SP2010) and hover over the "Add Document"/Add New Item" link on the bottom of the page. In the browser status bar you'll see the URL for that link. That URL contains the bare GUID for the list.

http://yoursite/_layouts/Upload.aspx?List={EFG33460-E1F4-4E78-A2F8-CDEG5BE0420B}&RootFolder=

Your GUID is in curly brackets: {EFG33460-E1F4-4E78-A2F8-CDEG5BE0420B}

If you don't have that link to click on, you can go to the list settings, copy the URL and see the GUID in its HTML encoded form in the "List" querystring variable (List=listGUID):
http://yoursite/_layouts/listedit.aspx?List=%7BEFG33460%2DE1F4%2D4E78%2DA2F8%2DCDEG5BE0420B%7D
It takes 5 seconds to manually transform this to a valid GUID. %7B = "{", %7D = "}", and %2D = "-".

List=%7BEFG33460%2DE1F4%2D4E78%2DA2F8%2DCDEG5BE0420B%7D
becomes
List={EFG33460-E1F4-4E78-A2F8-CDEG5BE0420B}

You can get your "view" GUID this way:
Go to the list settings, scroll all the way down to the views, and get the URL from your view (right-click "copy shortcut" or just click on it and copy the page URL). The view GUID is stored in its HTML encoded form in the "View" querystring variable (View=viewGUID). You can then manually decode that using the same logic as described above.
http://yoursite/_layouts/ViewEdit.aspx?List=%7B1637E544%2D5183%2D417B%2DB9DC%2DCD0E2E4EDEE1%7D&View=%7B7E510143%2DD7EA%2D4CFD%2D9A4D%2D65FF5AE909CC%7D&Source=

So.... This
View=%7B7E510143%2DD7EA%2D4CFD%2D9A4D%2D65FF5AE909CC%7D
becomes
View={7E510143-D7EA-4CFD-9A4D-65FF5AE909CC}