4

I am trying to write an event so that when a component is saved, some of its fields are copied in the localized versions of the component.

My algorithm is as follows: I first find the root component (i.e. in the publication where it's created), then scan down the BP and obtain a list of localized components. Then I loop over those components, copying the fields from the saved component (on which the event is fired) to mirror the fields.

The problem I am having is that I only want to run through the list once, so if the component is localized three times in child publications I would expect three local copies to be updated, but there are actually many more iterations happening. I understand the problem; everytime I call .save() the event system is triggered once again (breaking the foreach loop).

The basics of the event are as follows (note the code is very simplified, excluding the ItemFields copying etc):

public ContentCreationEvents()
{
    EventSystem.Subscribe<Component, SaveEventArgs>(SyncProductOfferLocalizedComponents, EventPhases.TransactionCommitted); 
}

public void SyncProductOfferLocalizedComponents(Component comp, SaveEventArgs args, EventPhases phases)
{
    if(comp.Schema.Title == "Product Offer")
    {
        // Get the saved component (comps) fields to be mirrored.

        // .. Retrieve all localized components into a list here .. 
        foreach(Component localComponent in localizedComponents)
        {
            // Mirror the fields via ItemFields (overwrite existing fields)

            bool checkedOut = localComponent.TryCheckOut();
            if(checkedOut)
            {
                localComponent.Content() = updateFields.toXml();
                localComponent.Save();
                localComponent.CheckIn();
            }
        }
    }
}

My questions are: What stops this from being an infinite loop? It seems to me like everytime we call save, the event system should continuously be triggered, yet it is not. The loop is executing by about a multiple of 3/4 compared to what I would expect.

My second question is: how can I loop through the components, updating their fields without re-triggering the event system which causes us to needlessly loop over components that have already been saved. Perhaps I am missing a fatal flaw in my design; please feel free to recommend a new strategy or possibly a different event phase to trigger on.

  • May be a bad programming, but you may handle it through managing flags (I already said flags are not good programming) – Pankaj Gaur Feb 25 '14 at 6:20
  • It all depends on how you retrieve your localized components. A check on whether the component is a local copy or not should be sufficient in this case. Please show some more of that part of the code... – Reinder Wit Feb 25 '14 at 7:28
3

This could be achieved in two different ways.

  1. Using SaveEventArgs.EventStack you can determine if the event is being fired in context of another event. You can enumerate over the stack and check for the appropriate subject and EventArgs. While checking for the subject I think it will be best to compare the Id.ItemId otherwise the publication part of the Id will not match.
  2. Another option would be to set the Id.ItemId in the Session.ContextData dictionary before starting your processing in the handler and clearing it at the end. Also you should first check if the same Id.ItemId is already in the Session.ContextData before trying to set it and process the eventhandler.
  • 1
    Seeing how the use-case of TS would simply never require the event to be fired as a result of the event handler making a change the EventStack option seems the appropriate solution in this case (if this event is triggered as the result of another event instead of a human action exit the event handler immediately). – Bjørn van Dommelen Feb 25 '14 at 12:21
  • Why not just check for being localized or not? If the component is not a shared item (i.e. it's the parent component), call a method that will get all localized children and update those; if the component is localized, you know you don't have to call that particular method so exit... – Reinder Wit Feb 25 '14 at 14:12
  • @ReinderWit did not understand what you meant by checking for being localized. AFAIU there are already localized items of the component and the objective is to update/propagate the changes to all local copies. And as the event being handled in Component Save, updating those localized component is also going to fire Component Save event which is what is being attempted to be handled. – Likhan Feb 25 '14 at 15:29
  • Yes, but in the Save event for the localized items, you can check and see that they're localized components, so you know you don't have to call the update method. So: the parent item is saved; the check is done and all localized items are updated and saved; those localized items will enter the save event too; but since we know they are the localized versions, we don't need to run the updating mechanism. – Reinder Wit Feb 25 '14 at 21:13
  • I recently had to do a similar thing, even with an update mechanism for when a localized item is saved (to get data from the parent item) and this was not an issue (I did have to use asynchronous events, because it was taking quite long). I just don't see why this should be done so overly complicated... or maybe I've just dont understood the problem correctly ;) – Reinder Wit Feb 25 '14 at 21:16
2

Update: the following isn't correct as, Likhan and Nick point out, the ContextVariables are only available in the current Save() event and the args param is another instance.

  • also worth noting is you can't pass the ContextVariables between events fired by different processes, e.g. cmshost and publisher can't share ContextVariables

In terms of business logic I guess the issue is that some level of recursion is being hit (I can't find reference to the recursion-level setting in the documentation) but the program flow seem to be as follows:

    comp1.save()
[1]     check for localized instances... 
            found 1 of 3                                                            
                update compA                                                        
                    compA.save() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                                                                                        | 
[1]                     check for localized instances... <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                            found 0 of 0  
            found 2 of 3 
                update compB                                                           
                    compB.save() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                                                                                        | 
[1]                     check for localized instances... <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                            found 0 of 0  

We can add checks to the ContextVariables as shown here and decide not to trigger the update/save loop as required

comp1.save()
    check for localized instances... <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        found 1 of 3                                                                              |  |
            //ContextVariables can be strings/objects etc.                                        |  |
            if(compA.pubID NOT IN (args.ContextVariables["ignoreLocalizedComps"].ToString();)) {  |  |
                args.ContextVariables.Add("ignoreLocalizedComps", compA.pubID);                   |  |
                update compA                                                                      |  |
                compA.save() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  |
            }                                                                                        |
                                                                                                     |
        found 2 of 3                                                                                 |
            if(compB.pubID NOT IN (args.ContextVariables["ignoreLocalizedComps"].ToString();)) {     |
                args.ContextVariables.Add("ignoreLocalizedComps", compB.pubID);                      |
                update compA                                                                         |
                compA.save() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
            }

Note that using this simple method you could also select to ignore components based on metadata/publication etc. before you perform the update.

In terms of exact program flow I'd consider the case where you may have hundreds of localized components so would need to ensure it was as efficient as possible.

  • I do not think the TcmEventArgs.ContextVariables are copied/cloned/available in other events triggered by operation in source event handler. – Likhan Feb 25 '14 at 12:59
  • Likhan is right. ContextVariables in this case is null. EventStack is the way to go. – Nickoli Roussakov Feb 25 '14 at 13:56
0

May be a bad program, but should work for you:

bool flag = true;
public void SyncProductOfferLocalizedComponents(Component comp, SaveEventArgs args, EventPhases phases)
{
    if(comp.Schema.Title == "Product Offer" && flag)
    {
        flag = false;   
        // Get the saved component (comps) fields to be mirrored.

        // .. Retrieve all localized components into a list here .. 
        foreach(Component localComponent in localizedComponents)
        {
            // Mirror the fields via ItemFields (overwrite existing fields)

            bool checkedOut = localComponent.TryCheckOut();
            if(checkedOut)
            {
                localComponent.Content() = updateFields.toXml();
                localComponent.Save();
                localComponent.CheckIn();
            }
        }
        flag = true
    }
}
  • How will you persist the "flag" variable across events? – Nickoli Roussakov Feb 25 '14 at 13:57
0

As I understand, you want this to be called only when the parent item (which is not localized) is updated. You may add the blueprint check (i.e., check whether the component is locally created and is not localized).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.