Managing Datasource children items from Experience Editor

Working with placeholders for inner component items is usually not an easy task for non-technical content editors or marketers, especially when having to do the repetitive task of:

  1. Finding the correct ‘Add here’ placeholder button
  2. Clicking on the correct rendering (when is usually one rendering)
  3. Selecting/Creating the datasource item while placing it under its parent component datasource item to keep things tidy (this form by itself is usually difficult enough for content editors)

The above implementation requires, of course, the use of dynamic placeholders in our views as the following snippet:

@*Two column Listing Container*@
<div class="row">
@Html.Sitecore().DynamicPlaceholder("two-col-listing-container")</div>

The bellow view gets placed inside the ‘two-col-listing-container’ using the Experience Editor:

@*Two column Listing Item*@
<div class="col-sm-6 content-listing>
@*Inner html with Field Renderers*@</div>

This works just fine, however, it is not intuitive and fairly difficult for non-technical end users.

 Enter Custom Experience Buttons (CEB)

I started researching other alternatives and came across the following CEB: Insert, Sort, Delete.

The Insert button simply allows me to create a new datasource child item underneath the main datasource item, which is perfect and from the other buttons’ names, things are going to be great.

InsertItem

The Sort button….amazingly, sorts the datasource children items, which is also perfect.

Sort Items

Finally, the delete button…. DOES NOT delete datasource children items but instead deletes the main datasource items with all its children. How inconvenient! Luckily you get this warning message before you delete all your work:

deleteDatasource

I can’t simply tell the end user: “you can create and sort items but not delete some of them. You’ll have to close the Experience Editor, open the Content Editor, navigate to the datasource item (that is if you can find it), find the needed child item and delete, and then get back to the Experience Editor”!

Customizing the Sort CEB

I started looking at the current Sort dialog’s xml and js and decided to try to customize it by adding a delete button right below the ‘Move Up’ and ‘Move Down’ buttons. To do that I did the following:

  • Copy the ‘Sort’ dialog folder from ‘\sitecore\shell\Applications\Dialogs’ to ‘\sitecore\shell\Override’. This allows me to edit the Sort dialog from the ‘Override’ folder without worrying about losing the original files.
  • Inside the ‘Sort’ folder (underneath ‘Override’ folder) open the ‘Sort.xml’ file and add the following new button element underneath the ‘MoveDown’ button:

 

<Border Class="commands-container" GridPanel.Class="commands-cell" GridPanel.VAlign="top">
  <Border>
    <Button ID="MoveUp" Click="javascript:scMoveUp()" Disabled="true" Header="Move Up"/>
  </Border>
  <Border>
    <Button ID="MoveDown" Click="javascript:scMoveDown()" Disabled="true" Header="Move Down"/>
  </Border>
  <Border>
    <Button ID="Delete" Click="javascript:scDelete()" Disabled="true" Header="Delete"/>
  </Border>
</Border>
  • Edit the header and Text to a more suitable text such as “Manage the content items” instead of “Sort the content items”
  • Edit the <Stylesheet Sort.css> and the <Script Sort.js> paths to the new ‘Override’ path
  • Finally, edit the <CodeBeside> type to the new class that we’ll be creating shortly
Sort.js
  • Open the Sort.js file, navigate to the ‘scUpdateMoveButtonsState’ function and edit it to enable/disable the new delete button accordingly:
function scUpdateMoveButtonsState() {  
  var moveUp = $("MoveUp");  
  var moveDown = $("MoveDown");  
  var deleteBtn = $("Delete");  //ID of the button added in Sort.xml
  var selectedItem = scGetSelectedItem();  
  if (!selectedItem) {    
    moveUp.disable();    
    moveDown.disable(); 
    deleteBtn.disable();    
    return;  
  }
  deleteBtn.enable();
  var next = scGetNextItem(selectedItem);  
  if (next) {    
    moveDown.enable();  
  }  
  else {    
    moveDown.disable();  
  }
  var previous = scGetPreviousItem(selectedItem);  
  if (previous) {    
    moveUp.enable();  
  }  else {    
    moveUp.disable();  
  }
};

  • Finally, add the following new function. It will get the ID of the currently selected datasource item and store it in a hidden element. Then it adds the ‘.deleted’ css class onto the selected item, which we will handle next in the Sort.css.
function scDelete() {    
  var deleteItem = $("deleteItem");//name of the form element that will hold the 'to be deleted' ids 
  if (!deleteItem) {        
    deleteItem = new Element("input", { type: "hidden", id: "deleteItem" });        
    var form = document.forms[0];        
    if (!form) {            
      return;        
    }
    form.appendChild(deleteItem);    
  } 
  scGetSelectedItem().addClassName("deleted");    
  var ids = $$(".deleted").map(function (item) { return item.id; }) || [];    
  var serialized = ids.join("|") || "";    
  deleteItem.value = serialized; 
  return false;
};
Sort.css
  • Open the Sort.css file and add the following new class right underneath the ‘.sort-item’ class. This will hide the items that got set as ‘deleted’:
.sort-item.deleted{ 
  display:none;
}
Code Behind

Add a new class in a suitable location in your project, reflect, get the code from ‘Sitecore.Shell.Applications.Dialogs.Sort.SortForm’ since it may be different with different Sitecore versions and place it in the new class. Don’t forget to edit the namespace and class name to match your project and the CodeBeside in the Sort.xml file.

We will be editing the ‘OnOK’ event only by reading the hidden element (deleteItem), getting the ‘to be deleted’ ids and actually deleting them:

protected override void OnOK(object sender, EventArgs args) { 
  Assert.ArgumentNotNull(sender, "sender"); 
  Assert.ArgumentNotNull(args, "args"); 
  ListString str = new ListString(WebUtil.GetFormValue("sortorder")); 
  ListString strToDelete = new ListString(WebUtil.GetFormValue("deleteItem")); 
  if (str.Count == 0 && strToDelete.Count == 0) { 
    base.OnOK(sender, args); 
  } 
  else { 
    if (strToDelete.Count > 0) 
      this.Delete(from i in strToDelete select ShortID.DecodeID(i)); 
    if (str.Count > 0) 
      this.Sort(from i in str select ShortID.DecodeID(i));
    SheerResponse.SetDialogValue("1"); 
    base.OnOK(sender, args); 
  } 
}

private void Delete(IEnumerable<ID> toDeleteList) { 
  Assert.ArgumentNotNull(toDeleteList, "toDeleteList");
  foreach (ID id in toDeleteList) { 
    ID idToFind = id;
    var itemToDelete = Sitecore.Data.Database.GetDatabase("master").GetItem(ID.Parse(idToFind));
    if (itemToDelete != null) {
      using (new SecurityDisabler()) { 
        itemToDelete.Delete(); 
      } 
    }
  } 
}
Updating Views

This new implementation requires changing the way the container view renders the children items. Instead of working with dynamic placeholders now, you’ll have to render the datasource’s children:

@{
  var item = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Item;
  if (item.IsDerived(Templates.TwoColumnListingContainer.ID) == false)
  {
    return;
  }
}
@foreach (Sitecore.Data.Items.Item colItem in item.Children.Where(w => w.IsDerived(Templates.ListingItem.ID))) 
{ 
 
@*Inner html with Field Renderers*@
}
Editing CEB in Sitecore
  • Go to sitecore’s core database, navigate to ‘/sitecore/content/Applications/WebEdit/Custom Experience Buttons’ and select the ‘Sort’ item
  • Rename the item to a more suitable name such as Manage
  • Edit the needed fields such as ‘Header’ (e.g. Manage Content), Tooltip (Manages the Content) and optionally the icon to a more suitable one.

At this point, you can go to your rendering and select the CEBs: ‘Insert’ and ‘Sort’/’Manage’ (or whatever your item’s new name is) and be able to insert, sort and delete datasource children items from the Experience Editor.

CEBs

Manage

This customization proved to be easier for end users since they’ll be able to use buttons and work with forms that they’re used to instead of having to work with placeholders and selecting/creating datasource items for each single datasource children items which are already new to them and have a high learning curve.

Leave a Reply

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 )

Twitter picture

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

Facebook photo

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

Connecting to %s