Reputation: 513
I have seen how we can provide default conditions and default actions to newly created bucket items. I also know that we can create a custom rule for building path based on custom date field.
But, how can we set the item path when the date field is and the is saved. Consider an example. We have a bucket item template say "News" which has a date field say "Release Date". We have the settings where on item creation, the item path has the creation date like "/News/2015/09/16/item1". Now, we need to have some logic where we can change the path when the "release date" of "item1" is updated and the item is Saved.
How can we update the item path when item's release date is updated and item is Saved !! do i need to implement some logic in OnItemSaved() method ?
I already went through posts on GeekHive
Upvotes: 3
Views: 2559
Reputation: 7485
Building on some of the answers, here's the most readable / performant solution for most use cases:
using Sitecore.Buckets.Extensions;
using Sitecore.Buckets.Managers;
using Sitecore.Data.Events;
using Sitecore.Diagnostics;
using System;
using Sitecore.Data.Items;
using Sitecore.Events;
namespace XXXX.Project.Web.Infrastructure.Pipelines
{
public class MoveItemIntoBucketOnSave
{
public void OnItemSaved(object sender, EventArgs args)
{
Assert.IsNotNull(sender, "sender is null");
Assert.IsNotNull(args, "args is null");
var savedItem = Event.ExtractParameter(args, 0) as Item;
if (savedItem == null || savedItem.Database.Name.ToLower() != "master" || !savedItem.IsItemBucketable())
{
return;
}
// WARNING: see update below about EventDisabler
using (new EventDisabler())
{
if (!BucketManager.IsItemContainedWithinBucket(savedItem))
{
return;
}
var bucketItem = savedItem.GetParentBucketItemOrParent();
if (!bucketItem.IsABucket())
{
return;
}
// If you want to sync the entire bucket
// BucketManager.Sync(bucketItem);
BucketManager.MoveItemIntoBucket(savedItem, bucketItem);
}
}
}
}
I'm not worried about there being any empty bucket folders after this operation since they will be cleaned up during a full bucket sync, and content authors would not typically be traversing the bucket tree as they should be using search.
Here's the config:
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:saved">
<handler type="XXXX.Project.Web.Infrastructure.Pipelines.MoveItemIntoBucketOnSave, XXXX.Project.Web" method="OnItemSaved" />
</event>
</events>
</sitecore>
</configuration>
UPDATE: I do not recommend using the EventDisabler
. If you add a new page and then try to add a rendering to the page and specify a datasource for it, the datasource locations won't resolve because Sitecore still thinks the path of newly created item is a direct child of the bucket item, rather than wherever the item was moved to within the bucket. See this question for more information.
UPDATE 2: Note that this method will get called twice when a new bucketed item is created. You should think very carefully about what this means for you, and if you should add any other checks prior to calling any other code within this method.
Upvotes: 2
Reputation: 4266
The simplest way to do this would be to hook into the item:saved event and sync the bucket in there. The following code is untested:
public class ItemSavedEventHandler
{
public void Execute(object sender, ItemSavedEventArgs args)
{
Assert.IsNotNull(sender, "sender is null");
Assert.IsNotNull(args, "args is null");
// WARNING: using the events disabler is not recommended in this case.
// If you do this, the path of the item in question will not be updated and will cause issues when you edit the page and try to specify rendering data sources (the locations won't resolve)
using (new EventsDisabler())
{
var parameter = args.Item;
if (!BucketManager.IsItemContainedWithinBucket(paremeter))
{
return;
}
var bucketItem = parameter.GetParentBucketItemOrParent();
if (!bucketItem.IsABucket())
{
return;
}
BucketManager.Sync(bucketItem);
}
}
}
On a bucket with a lot of items, this will considerably slow down the save process tho.
Upvotes: 4
Reputation: 973
You can achieve this by programmatically moving the bucketed item to the root of the bucket with the BucketManager
. Doing this will force it to reevaluate the bucket rules and reorganize it:
BucketManager.MoveItemIntoBucket(bucketedItem, bucketItem);
Note that this is different from BucketManager.Sync(bucketItem)
because it does not sync the whole bucket, but instead handles just the single item that was changed.
In our solutions, we typically create an item:saved
event handler to do this automatically:
using Sitecore.Buckets.Managers;
using Sitecore.Buckets.Util;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Events;
using System;
using System.Text.RegularExpressions;
namespace Custom.Events.ItemSaved
{
public class ReorganizeBucketedItemInBucket
{
public void OnItemSaved(object sender, EventArgs args)
{
var bucketedItem = Event.ExtractParameter(args, 0) as Item;
// If we don't have an item or we're not saving in the master DB, ignore this save
if (bucketedItem == null || !"master".Equals(bucketedItem.Database?.Name, StringComparison.OrdinalIgnoreCase))
return;
if (!bucketedItem.TemplateID.Equals(new ID("{bucketed-item-template-id}"))) return;
var itemChanges = Event.ExtractParameter(args, 1) as ItemChanges;
// If there were no changes or the changes didn't include the date field, ignore this save
if (itemChanges == null || !itemChanges.HasFieldsChanged || !itemChanges.IsFieldModified(new ID("{field-id-of-date-field}")))
return;
Item bucketItem = bucketedItem.Axes.SelectSingleItem($"{EscapePath(bucketedItem.Paths.FullPath)}/ancestor-or-self::*[@@templateid = '{{bucket-container-template-id}}']");
// If this item isn't in a bucket (or is in a bucket of another, unexpected type), ignore it
if (bucketItem == null) return;
Item parent = bucketedItem.Parent;
BucketManager.MoveItemIntoBucket(bucketedItem, bucketItem);
// Delete empty ancestor bucket folders
while (parent != null && !parent.HasChildren && parent.TemplateID == BucketConfigurationSettings.BucketTemplateId)
{
Item tempParent = parent.Parent;
parent.Delete();
parent = tempParent;
}
}
/// <summary>
/// Wraps each segment of a sitecore path with "#"'s
/// </summary>
public string EscapePath(string path)
{
return Regex.Replace(path, @"([^/]+)", "#$1#").Replace("#*#", "*");
}
}
}
And don't forget your patch config, of course:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:saved">
<handler type="Custom.Events.ItemSaved.ReorganizeBucketedItemInBucket, Custom.Events" method="OnItemSaved"></handler>
</event>
</events>
</sitecore>
</configuration>
Upvotes: 2
Reputation: 2096
If I understood you right, you want your bucket item path to be based on date updated rather than created? Am I right with that?
If yes, that it is not going to be a straightforward thing to do. I see the following approach to implement that.
Configure your bucket to be organised by update date, not created (you mentioned you already know how to configure that behavior). Every Sitecore item derived from Standard Template should have Statistics section where is __Updated field (with two underscores a the beginning) that automatically updates on each item save by corresponding event. You should use that field.
Once done, sync all existing items to apply that bucketing items paths.
Handle item:saved event
Within item:saved event handler: unbucket that particular item and re-bucket that item again (with item:unbucket and item:bucket commands)
Your that particular item will be bucketed implementing your bucketing path rule.
Hope that helps!
Upvotes: 3
Reputation: 897
You'll need to implement a pipeline processor. You can do this by adding the following into a .config file in your App_Code/Include folder.
<processors>
<saveUI>
<processor mode="on" type="Namespace.ClassName, Your.Assembly" patch:after="processor[last()]" />
</saveUI>
</processor
You'll also need to implement that class - there's nothing special about it except that it must have a public Process method with a Sitecore.Pipelines.Save.SaveArgs parameter.
namespace CustomFunctions
{
public class SaveAction
{
public void Process(SaveArgs args)
{
// There's a collection of items
// I'm not sure what the situation where there's more than one item is though.
var items = args.SavedItems;
var bucket = SomeFunctionToGetParent(items);
BucketManager.Sync(items);
}
}
}
I've never actually implemented this, but I think my code should give you an idea of how to get started - though this pipeline processor would be called every time an item is saved, so you need efficient checking to make sure that the item needs to have your bucket syncing processor used.
Upvotes: 0