Check if an item has been previously approved and approve again

Sometimes, you’ll have to check in an event receiver if an item has been previously approved and then do an action.

The following extension will return a boolean wether or not the item has been approved in the past or not.

public static bool HasBeenPreviouslyApproved(this SPListItem item)
{
    bool hasBeenPreviouslyApproved = false;
    if (item.Versions.Count > 1)
    {
        var prevPublishedVer = item.Versions.Cast<SPListItemVersion>().Where(v => v.Level == SPFileLevel.Published).LastOrDefault();
        if (prevPublishedVer != null)
        {
            hasBeenPreviouslyApproved = true;
        }
    }
    return hasBeenPreviouslyApproved;
}

As an example you can put the state back to approved if a modification was made. By default this always goes back to pending when an item gets edited, but in some cases you want to keep the state to approved.

if (properties.ListItem.HasBeenPreviouslyApproved())
{
    using (var web = properties.OpenWeb())
    {
        this.EventFiringEnabled = false;
        properties.ListItem.ModerationInformation.Status = SPModerationStatusType.Approved;
        properties.ListItem.SystemUpdate(false);
        this.EventFiringEnabled = true;
    }
}

Please note that when you use this code in an event receiver, it only works in the ItemUpdated event.

As a result, an announcement which was once approved, will always be approved, until a content editor decides to change the approval status back to pending or decline.

announcement approved

Prevent items from being deleted using an event receiver

One way to prevent item deletion is creating a custom permission level, where the item deletion permission is unchecked. When you use this method to prevent item deletion, you’ll have to break the permission inheritance of your list. Plus, you’ll create the permission level for the whole site collection. If that’s a problem you can also use an event receiver.

The following code snippet will prevent item deletion and also notify the user of what happened. If you simply use the properties.Cancel = true; property, then the item is prevented from deletion, but the user isn’t notified.

By setting the properties.Status = SPEventReceiverStatus.CancelWithError; property, we tell SharePoint to generate an error message. The properties.ErrorMessage = “This item cannot be deleted; Contact your administrator”; property defines the message the user sees.

/// <summary>
/// An item is being deleted.
/// </summary>
public override void ItemDeleting(SPItemEventProperties properties)
{
    base.ItemDeleting(properties);
    properties.Status = SPEventReceiverStatus.CancelWithError;
    properties.ErrorMessage = "This item cannot be deleted; Contact your administrator";
    properties.Cancel = true;
}

The above snippet generates the following error when a user tries to delete an item in a list where the event receiver is active:

error screenshot from event receiver

Passing the result from a dialog to the parent page

Sometimes you want to communicate with the parent page of your dialog in order to pass on the result of your work. Thankfully the SP.UI.ModalDialog framework contains the commonModalDialogClose method. The javascript CSOM has all the tools in place to construct the following application:

  • Add a button to the ribbon. This button will also open the dialog.
  • Create an application page that will be used as a dialog. This application page has a text box and a button.
  • Add javascript to capture the result of the application page, in this case just text, and put it where our cursor resides on the parent page.

First things first, we’ll add a button to our ribbon.

ribbonbutton

Since there are many tutorials available, I won’t go into detail here. Simply add an empty element to your project and replace the contents with this one:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="bramnuyts.InsertButton"
    Location="CommandUI.Ribbon"
    Title="InsertRibbon Button">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Groups._children">
          <Group
            Id="Ribbon.EditingTools.CPInsert.InsertGroup"
            Sequence="1"
            Description="Insert"
            Title="Insert"
            Template="Ribbon.Templates.Flexible2"
            >
            <Controls Id="Ribbon.EditingTools.CPInsert.InsertGroup.Controls">
              <Button Id="Ribbon.EditingTools.CPInsert.InsertGroup.InsertButton"
                Command="InsertCommand"
                Image16by16="/_layouts/images/removeStylesHS.png"
                Image32by32="/_layouts/images/removeStylesHH.png"
                LabelText="Insert"
                TemplateAlias="o2" />
            </Controls>
          </Group>
        </CommandUIDefinition>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Scaling._children">
          <MaxSize
            Id="Ribbon.EditingTools.CPInsert.InsertGroup.MaxSize"
            Sequence="15"
            GroupId="Ribbon.EditingTools.CPInsert.InsertGroup"
            Size="LargeLarge" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
          Command="InsertCommand"
          CommandAction="javascript:insert();" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
  <CustomAction
    Id="InsertCommand.Script"
    Location="ScriptLink"
    ScriptSrc ="/_layouts/DialogResult/insert.js" />
</Elements>

I added the javascript in a separate javascript file “insert.js”. I like the ScriptLink approach as it doesn’t dirty the elements.xml file. However there are some reported issues when using ScriptLinks so go for the approach you feel is the most safe.

insert.js

Inside my insert.js file I have 2 methods: addElement(txt) and insert(). The “insert()” method will be responsible for handling the click on the button and creating the dialog. The “addElement(txt)” method will be responsible for adding content to the rich content page. Once again SharePoint is there to help us, this time it’s the “RTE” class, which points to the Rich Text Editor. By using the “RTE.Cursor.get_range()” method, we’re able to determine where a user has put his cursor.

function addElement(txt) {
    var range = RTE.Cursor.get_range();
    range.deleteContent();
    var selection = range.parentElement();
    if (!selection) {
        return;
    }
    var span = selection.ownerDocument.createElement('span');
    span.innerText = txt;
    range.insertNode(span);
    RTE.Cursor.get_range().moveToNode(span);
    RTE.Cursor.update();
}

function insert() {
    var options = {
        url: SP.Utilities.Utility.getLayoutsPageUrl('/DialogResult/insert.aspx'),
        title: 'Insert',
        allowMaximize: false,
        showClose: true,
        width: 300,
        height: 150,
        dialogReturnValueCallback: Function.createDelegate(null, function (result, returnValue) {
            if (result == SP.UI.DialogResult.OK) {
                    addElement(returnValue);
            }
        })
    };

    SP.UI.ModalDialog.showModalDialog(options);
}

Now that we have our button and the javascript to handle the dialog, we have to create our dialog page.

insert.aspx

I added a text box and a button to the main contentplaceholder:

insert.aspx.main

Add the “Button1_Click” OnClick method to the button and add the following code to the code behind:

protected void Button1_Click(object sender, EventArgs e)
{
    int result = 1; // Result = OK
    string returnValue = TextBox1.Text;
    Page.Response.Clear();
    Page.Response.Write(String.Format(
        CultureInfo.InvariantCulture, 
        "<script type=\"text/javascript\">window.frameElement.commonModalDialogClose({0}, {1});</script>", 
        new object[] { 
            result, 
            String.IsNullOrEmpty(returnValue) ? "null" : String.Format("\"{0}\"", returnValue) 
        }));
    Page.Response.End();
}

The code will close the dialog window, pass “OK” as result and text from the text box as return value.

When you deploy the code (make sure that your package contains the button) you’ll see the following button appear when you edit your rich content page:

result ribbon

When you click the button the dialog page will appear:

result dialog

When you insert some “test text” and click insert, your text will show up where you left your cursor:

result test text

Add an event receiver to a specific list programmatically

Creating an event receiver allows you to catch events for specific list types. Such as custom lists, document libraries, announcements, … When we want to catch an event for a specific list, the standard procedure for creating an event receiver changes a bit.

There’s an article on MSDN which describes a method by editing the elements.xml file of an event receiver project. However, the same result can be achieved with code.

Basically, an event receiver template will create a feature file which holds a reference to the elements.xml file from the event receiver project. Inside this elements.xml file, the properties of the event receiver are defined, such as the type of list it will be bound to. We want to bypass the elements.xml file and manually attach the event receiver to a specific list’s event receivers. The binding itself will be written in a feature event receiver.

Start out with creating a new SharePoint 2010 project and select the Event Receiver template. Fill in any desired properties and click on OK:

new event receiver project

Next, fill in the site where the list is at you want to attach the event receiver. Also choose “farm solution” as the desired trust level.

link farm solution

Click next, choose the event receiver settings. I’ve chosen for “List Item Events” where “An item was added” should be handled. The event source isn’t important, since we’ll override the binding:

event receiver settings

When finish is clicked, Visual Studio will create the files based on the template and the chosen settings. A feature will be created with a reference to the EventReceiver1 project. Inside this project an elements.xml file and a code file will be created. The elements.xml file holds the settings of the event receiver. The code file holds the code that will be executed:

event receiver solution explorer

Whenever you want to add more events that have to be captured, click on the EventReceiver1 project and look at the properties window. Here you can enable or disable events that have to be captured. Note that when you disable an already enabled event, the code inside the code file will not be removed. This is a safety built-in for when you accidentally disable an event:

event receiver properties

The next step is removing the connection between the feature and the event receiver project. By doing this we avoid that the elements.xml file is used to bind the event receiver. Double click on the feature file, select the EventReceiver1 project and click on the “<” arrow to remove the project from the feature:

remove event receiver from feature

Save the modification, and right-click the feature file to add a feature event receiver:

add event receiver

If it’s not already open, double-click the feature event receiver code file to open it up. Next, uncomment both FeatureActivated and FeatureDeactivating code blocks.

First, declare two constants in your feature class to keep constancy:

        const string _listName = "test";
        const SPEventReceiverType _eventType = SPEventReceiverType.ItemAdded;

Next, write the following code in the FeatureActivated block:

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWeb oWeb = (SPWeb)properties.Feature.Parent;
            oWeb.Lists[_listName].EventReceivers.Add(_eventType, Assembly.GetExecutingAssembly().FullName, "EventReceiverProject1.EventReceiver1.EventReceiver1");
        }

You’ll notice that the Assembly namespace isn’t recognized so add the following line in your using statements:

using System.Reflection;

The last parameter of the Add() function can be found in the elements.xml file of the EventReceiver1 project. When you open this file you will see the 2 tags that are also referenced here in our code:

        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceiverProject1.EventReceiver1.EventReceiver1</Class>

$SharePoint.Project.AssemblyFullName$ is replaced by Assembly.GetExecutingAssembly().FullName in the code.

The following code will remove the event receiver that was added:

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWeb oWeb = (SPWeb)properties.Feature.Parent;

            SPList oList = oWeb.Lists[_listName];
            for (int i = oList.EventReceivers.Count - 1; i >=0 ; i--)
            {
                if (oList.EventReceivers[i].Type.Equals(_eventType))
                {
                    try
                    {
                        oList.EventReceivers[i].Delete();
                    }
                    catch (Exception e)
                    {
                        // Write to logs
                    }
                }
            }
        }