Home » Commands » Delete Sitecore Items Using a Deletion Basket

Delete Sitecore Items Using a Deletion Basket

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

For the past week, I’ve been battling a nasty strain of the rhinovirus — don’t worry, that’s just the fancy medical term for what is known as the common cold — albeit there appears to be nothing common about this cold. I think I have a frankencold (I just made up this word, and might submit it to Merriam-Webster for inclusion in the English dictionary).

In my sickened state — perhaps it could be classified as a state of frenzy — I started pondering over strange feature ideas. The ‘Dislike’ button was one idea that came to mind. It would serve as the antithesis to the ‘Like’ button found on most social networking outlets. Such a feature would definitely be a whimsical thing to build, although might foment more trouble than it’s worth.

Another idea that came to mind was a deletion basket in the Sitecore client. It would be similar in theme to a shopping cart found on most e-commerce websites. Users would queue items in their deletion basket, and delete them after they are finished adding items — a checkout step for the lack of a better term.

The latter idea seemed useful — most importantly fun — so I decided to build it.

I first created a Deletion Basket class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Data;
using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Utilities.Items.Base
{
    public interface IDeletionBasket
    {
        int Count();

        bool IsEmpty();

        IEnumerable<Item> GetItemsToDelete();

        bool Contains(Item item);

        void Add(Item item);

        void Remove(Item item);

        void DeleteAll();

        void Clear();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;

using Sitecore.Sandbox.Utilities.Items.Base;

namespace Sitecore.Sandbox.Utilities.Items
{
    public class DeletionBasket : IDeletionBasket
    {
        private static volatile IDeletionBasket current;
        private static object lockObject = new Object();

        public static IDeletionBasket Current
        {
            get
            {
                if (current == null)
                {
                    lock (lockObject)
                    {
                        if (current == null)
                            current = new DeletionBasket();
                    }
                }

                return current;
            }
        }

        private IList<Item> _ItemsToDelete;
        private IList<Item> ItemsToDelete
        {
            get
            {
                if (_ItemsToDelete == null)
                {
                    _ItemsToDelete = new List<Item>();
                }

                return _ItemsToDelete;
            }
        }

        private DeletionBasket()
        {
        }

        public int Count()
        {
            return ItemsToDelete.Count();
        }

        public bool IsEmpty()
        {
            return !ItemsToDelete.Any();
        }

        public IEnumerable<Item> GetItemsToDelete()
        {
            return ItemsToDelete;
        }

        public bool Contains(Item item)
        {
            return FindItemInBasket(item) != null;
        }

        private Item FindItemInBasket(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return FindItemInBasketByID(item.ID);
        }

        private Item FindItemInBasketByID(ID id)
        {
            AssertID(id);
            return FindItemsInBasketByID(id).FirstOrDefault();
        }

        private IEnumerable<Item> FindItemsInBasketByID(ID id)
        {
            AssertID(id);
            return ItemsToDelete.Where(i => i.ID == id);
        }

        private static void AssertID(ID id)
        {
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(id), "id", "ID must be set!");
        }

        public void Add(Item item)
        {
            Item itemInBasket = FindItemInBasket(item);
            if (itemInBasket == null)
            {
                ItemsToDelete.Add(item);
                AddChildren(item);
            }
        }

        private void AddChildren(Item item)
        {
            if (!item.HasChildren)
            {
                return;
            }

            foreach (Item child in item.Children)
            {
                Add(child);
            }
        }

        public void Remove(Item item)
        {
            Item itemInBasket = FindItemInBasket(item);
            if (itemInBasket != null)
            {
                ItemsToDelete.Remove(itemInBasket);
            }
        }

        public void DeleteAll()
        {
            Sitecore.Context.ClientPage.Start(this, "ConfirmAndDeleteAll", new ClientPipelineArgs());
        }

        private void ConfirmAndDeleteAll(ClientPipelineArgs args)
        {
            ShowConfirmationDialogIfApplicable(args);
            DeleteAllIfApplicable(args);
        }

        private void ShowConfirmationDialogIfApplicable(ClientPipelineArgs args)
        {
            if (!args.IsPostBack)
            {
                Context.ClientPage.ClientResponse.YesNoCancel(string.Format("Are you sure you want to delete the {0} item(s) in your Deletion Basket?", Count()), "200", "200");
                args.WaitForPostBack();
            }
        }

        private void DeleteAllIfApplicable(ClientPipelineArgs args)
        {
            bool canDelete = args.IsPostBack && args.Result == "yes";
            if (canDelete)
            {
                foreach (Item itemToDelete in ItemsToDelete)
                {
                    DeleteItem(itemToDelete);
                }

                Clear();
            }
        }

        private static void DeleteItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            if (Settings.RecycleBinActive)
            {
                item.Recycle();
            }
            else
            {
                item.Delete();
            }
        }

        public void Clear()
        {
            ItemsToDelete.Clear();
        }
    }
}

The above class stores and deletes items from a list, and prompts users ascertaining if they truly want to delete items within the deletion basket.

Only one instance of the above class can exist — I employed the Singleton pattern for this purpose — to keep the code simple for this blog post, although it probably would make more sense to make baskets session aware — have different baskets for different sessions.

Next, I created a command to add or remove an item from the deletion basket — depending on whether the item is in the basket:

using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Shell.Framework.Commands;

using Sitecore.Sandbox.Utilities.Items;
using Sitecore.Sandbox.Utilities.Items.Base;

namespace Sitecore.Sandbox.Commands
{
    class ToggleItemInDeletionBasket : Command
    {
        private static readonly Delete DeleteCommand = new Delete();

        public override void Execute(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            ToggleInDeletionBasketIfEnabled(commandContext);
        }

        private void ToggleInDeletionBasketIfEnabled(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            if (QueryState(commandContext) == CommandState.Enabled)
            {
                ToggleInDeletionBasketAndRefresh(GetItem(commandContext));
            }
        }

        public override CommandState QueryState(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            return DeleteCommand.QueryState(commandContext);
        }

        private static void ToggleInDeletionBasketAndRefresh(Item item)
        {
            ToggleInDeletionBasket(item);
            RefreshItem(item);
        }

        private static void ToggleInDeletionBasket(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            IDeletionBasket deletionBasket = DeletionBasket.Current;

            if(deletionBasket.Contains(item))
            {
                deletionBasket.Remove(item);
            }
            else
            {
                deletionBasket.Add(item);
            }
        }

        private static Item GetItem(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            Assert.ArgumentNotNull(commandContext.Items, "commandContext.Items");
            Assert.ArgumentCondition(commandContext.Items.Count() > 0, "commandContext.Items", "There must be at least one item in the array!");
            return commandContext.Items.FirstOrDefault();
        }

        public override string GetHeader(CommandContext commandContext, string header)
        {
            if (DeletionBasket.Current.Contains(GetItem(commandContext)))
            {
                return Translate.Text("Remove from Deletion Basket");
            }

            return Translate.Text("Add to Deletion Basket");
        }

        private static void RefreshItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            Context.ClientPage.ClientResponse.Timer(string.Format("item:load(id={0})", item.ID), 1);
        }
    }
}

The text of the command is different when the item is in the basket versus when it is not.

The command also reloads the item in the Sitecore client — this is done to update the state of the deletion basket buttons in the ribbon.

Now, we need a command to delete items queued in the deletion basket. The ‘Empty Deletion Basket’ command does just that:

using System.Linq;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Shell.Framework.Commands;

using Sitecore.Sandbox.Utilities.Items;
using Sitecore.Sandbox.Utilities.Items.Base;

namespace Sitecore.Sandbox.Commands
{
    public class EmptyDeletionBasket : Command
    {
        public override void Execute(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            EmptyDeletionBasketIfEnabled(commandContext);
        }

        private void EmptyDeletionBasketIfEnabled(CommandContext commandContext)
        {
            if (QueryState(commandContext) == CommandState.Enabled)
            {
                DeleteAllItemsInDeletionBasketAndRefresh();
            }
        }

        public override CommandState QueryState(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");

            if (DeletionBasket.Current.IsEmpty())
            {
                return CommandState.Hidden;
            }

            return base.QueryState(commandContext);
        }

        private static void DeleteAllItemsInDeletionBasketAndRefresh()
        {
            Item firstItemParent = GetFirstItemParent();
            DeleteAllItemsInDeletionBasket();
            RefreshItem(firstItemParent);
        }

        private static void DeleteAllItemsInDeletionBasket()
        {
            DeletionBasket.Current.DeleteAll();
        }

        private static Item GetFirstItemParent()
        {
            Item itemForRefresh = DeletionBasket.Current.GetItemsToDelete().FirstOrDefault();
            if (itemForRefresh != null && itemForRefresh.Parent != null)
            {
                return itemForRefresh.Parent;
            }

            return null;
        }

        private static void RefreshItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            Context.ClientPage.ClientResponse.Timer(string.Format("item:load(id={0})", item.ID), 1);
        }

        public override string GetHeader(CommandContext commandContext, string header)
        {
            IDeletionBasket deletionBasket = DeletionBasket.Current;
            return Translate.Text(string.Format("Delete {0} Item(s)", deletionBasket.Count()));
        }
    }
}

This command also reloads the Sitecore client — using the the parent item of the first item flagged for deletion (you can’t reload an item that was already deleted). This is also done to refresh the state of the buttons in the ribbon.

Plus, the command is hidden when there are no items in the deletion basket.

We shouldn’t force users to only add and delete items in their basket. We should also allow them to clear out their baskets, in case they change their mind on what they want to delete. I created a ‘Clear Deletion Basket’ command for this very reason:

using System.Linq;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Web.UI.Sheer;

using Sitecore.Sandbox.Utilities.Items;

namespace Sitecore.Sandbox.Commands
{
    public class ClearDeletionBasket : Command
    {
        public override void Execute(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");
            ClearDeletionBasketIfEnabled(commandContext);
        }

        private void ClearDeletionBasketIfEnabled(CommandContext commandContext)
        {
            if (QueryState(commandContext) == CommandState.Enabled)
            {
                ClearAllItemsInDeletionBasketAndRfresh();
            }
        }

        public override CommandState QueryState(CommandContext commandContext)
        {
            Assert.ArgumentNotNull(commandContext, "commandContext");

            if (DeletionBasket.Current.IsEmpty())
            {
                return CommandState.Disabled;
            }

            return base.QueryState(commandContext);
        }

        private static void ClearAllItemsInDeletionBasketAndRfresh()
        {
            Item lastItem = GetLastItem();
            ClearAllItemsInDeletionBasket();
            RefreshItem(lastItem);
        }

        private static void ClearAllItemsInDeletionBasket()
        {
            DeletionBasket.Current.Clear();
        }

        private static Item GetLastItem()
        {
            Item itemForRefresh = DeletionBasket.Current.GetItemsToDelete().LastOrDefault();
            if (itemForRefresh != null)
            {
                return itemForRefresh;
            }

            return null;
        }

        private static void RefreshItem(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            Context.ClientPage.ClientResponse.Timer(string.Format("item:load(id={0})", item.ID), 1);
        }
    }
}

In order to use our commands above, we have to define them in /App_Config/Commands.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

	<!-- There's a bunch of commands up here -->
	
	<command name="item:toggleitemindeletionbasket" type="Sitecore.Sandbox.Commands.ToggleItemInDeletionBasket,Sitecore.Sandbox"/>
	<command name="item:cleardeletionbasket" type="Sitecore.Sandbox.Commands.ClearDeletionBasket,Sitecore.Sandbox"/>
	<command name="item:emptydeletionbasket" type="Sitecore.Sandbox.Commands.EmptyDeletionBasket,Sitecore.Sandbox"/>
</configuration>

Now that our commands are defined in /App_Config/Commands.config, we can now wire them up in the Core database.

I’ve wired up the ‘Empty Deletion Basket’ button:

Empty-Deletion-Basket-Core

Followed by wiring up the ‘Clear Deletion Basket’ dropdown button:

Clear-Deletion-Basket-Core

Next, I set up the ‘Toggle Item In Deletion Basket’ item context menu button:

Toggle-Item-In-Deletion-Basket-Core

Let’s see how we did.

I’ve switched back over to the master database, picked an item at random, and right-clicked. There’s our new ‘Toggle Item In Deletion Basket’ button:

Toggle-Not-In-Deletion-Basket

The appropriate wording is displayed since the item is not in the Deletion Basket.

I’ve clicked the ‘Toggle Item In Deletion Basket’ button in the item context menu, and then right-clicked on the item again to launch it again:

Toggle-In-Deletion-Basket

We see that button’s text has changed to convey that this item is in the deletion basket, and can be removed from the deletion basket by clicking the item context menu button again.

Plus, the deletion basket buttons in the ribbon have magically appeared.

I’ve clicked the dropdown on the deletion basket button in the ribbon, and we now see the ‘Clear Deletion Basket’ button:

Clear-Deletion-Basket

I’m going to add a bunch of items to our deletion basket:

Add-A-Bunch-Of-Items-Deletion-Basket

They’ve all been added. Let’s get rid of them:

chosen-items-basket-delete

Doh — I’ve been blocked by an instrusive confirmation box:

chosen-items-basket-delete-confirmation

I’ve clicked ‘Yes’, and now the items are gone:

chosen-items-basket-deleted

That was all in good fun.

Time to investigate adding a ‘Dislike’ button to the popular social media channels. 😉

Advertisement

3 Comments

  1. This is really a very nice idea, although I was thinking of doing something similar for publishing items 🙂
    By the way, what if one of the items you have in the basket is referenced by another item in the system? it would be cool to have a warning about it and tell you what is the referencing item.
    I think I would investigate that in my free time (if I can have any), and tell you what I came up with 🙂

    • Thanks Mohammad!

      Building something like this for items to publish is a great idea! I wish I had thought of it. 😉

      Please Let me know how you make out in your ventures building a publishing ‘shopping cart’.

      I have not tested this for referenced items. I agree a dialog should be shown in that context.

      Mike

  2. […] Delete Sitecore Items Using a Deletion Basket […]

Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: