In sitecore, if I add a new item to the master database(Unpublished), it does not show any indication regarding the published state.
For an example, if a user has added 10 items, he might get confused to figureout the items added by him which are pending for publishing.
Is there a way to identify newly added items as unpublished or in new and display a validation in the "Quick action bar"?
Never thought about this, but it's actually pretty easy to fix.
I created a GutterRenderer that indicates wether an item has been published to at least one, to all, or to none of the publishing targets.
EDIT: Added Click behaviour. When you click the gutter icon, the Publish dialog will be shown for that item.
First I will show you the code that I wrote for this and then I'll show you screenshots of the setup and the result.
Here is the code:
using System.Collections.Generic;
using System.Linq;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Shell.Applications.ContentEditor.Gutters;
namespace ParTech.Library.Gutters
{
public class PublicationStatus : GutterRenderer
{
private readonly ID publishingTargetsFolderId = new ID("{D9E44555-02A6-407A-B4FC-96B9026CAADD}");
private readonly ID targetDatabaseFieldId = new ID("{39ECFD90-55D2-49D8-B513-99D15573DE41}");
protected override GutterIconDescriptor GetIconDescriptor(Item item)
{
bool existsInAll = true;
bool existsInOne = false;
// Find the publishing targets item folder
Item publishingTargetsFolder = Context.ContentDatabase.GetItem(publishingTargetsFolderId);
if (publishingTargetsFolder == null)
{
return null;
}
// Retrieve the publishing targets database names
List<string> publishingTargetsDatabases = publishingTargetsFolder.GetChildren()
.Select(x => x[targetDatabaseFieldId])
.ToList();
// Check for item existance in publishing targets
publishingTargetsDatabases.ForEach(delegate(string databaseName)
{
if (Database.GetDatabase(databaseName).GetItem(item.ID) != null)
{
existsInOne = true;
}
else
{
existsInAll = false;
}
});
// Return descriptor with tooltip and icon
string tooltip = Translate.Text("This item has not yet been published");
string icon = "People/16x16/flag_red.png";
if (existsInAll)
{
tooltip = Translate.Text("This item has been published to all targets");
icon = "People/16x16/flag_green.png";
}
else if (existsInOne)
{
tooltip = Translate.Text("This item has been published to at least one target");
icon = "People/16x16/flag_yellow.png";
}
return new GutterIconDescriptor()
{
Icon = icon,
Tooltip = tooltip,
Click = string.Format("item:publish(id={0})", item.ID)
};
}
}
}
And this is how so set it up and how it will look once it's running:
Figure 1: Create a new Gutter item in the Core database:
Figure 2: Switch back to your Master database and activate the Gutter by right-clicking in the gutter area.
Figure 3: The Gutter now indicates the publication status of your items
From the top of my head it's not available out of the box. In the core database however, there's the definitions of the gutter, etc. You could create your own.
There's the 'published' field on items though, but I'm not sure if that takes different versions into account.
Maybe you can check the differences between the item in the master and web (i.e. Item doesn't exist or is different version in web, then it's waiting to be published).
Alternatively, have a read through this: http://webcmd.wordpress.com/2011/08/31/sitecore-ribbon-that-displays-published-state-of-an-item/
It'll explain how to check if an item is published as a ribbon.
Related
We are using the WizardForm xml control for implementing some sort of backend sitecore wizard. We want to add the controls to the pages (or even create new custom pages) on the fly, dynamically depending on the selection done in the previous page.
What I've already done: we called a parent control of the page control on the page (in ActivePageChanging event) and tried to add a new object of type "WizardDialogBaseXmlControl" there. But no new pages are displayed in the frontend. I still see the same number of pages in the browser's dev. tools that I added at design time in xml. I tried "SheerResponse.Redraw()", but that didn't help either.
My next attempt was to create some pages in the xml file at design time and just populate them with controls, but that doesn't work after the wizard has already loaded. Something like "ControlName.Controls.Add(new ControlName())" only works if it is called in the overridden method "OnLoad()".
This code doesn't work:
protected override bool ActivePageChanging(string page, ref string newpage)
{
if (newpage.Equals(Consts.PrototypeDetailsPageId))
{
if (IsFormItemSelected(out var formItem))
{
PrototypeDetailsPanel0.Controls.Add(new Literal("some text"));
}
else
{
SheerResponse.Alert("You must select a form item");
return false;
}
}
return base.ActivePageChanging(page, ref newpage);
}
How can I create a working wizard that adds pages and controls at runtime when they depend on changes on previous pages of the same wizard?
Sitecore WizardForm relies on the newpage parameter to process navigation between steps. So you can prepare alternative versions of wizard steps in advance and set one of them as newpage depending on values entered in the previous step. For example, this is how your code can look like:
protected override bool ActivePageChanging(string page, ref string newpage)
{
if (newpage.Equals(Consts.PrototypeDetailsPageId))
{
if (IsFormItemSelected(out var formItem))
{
newpage = "WizardPageWithAdditionalFields";
}
else
{
SheerResponse.Alert("You must select a form item");
return false;
}
}
return base.ActivePageChanging(page, ref newpage);
}
I also found it useful that wizard forms can be easily created with Interactive Dialogs from PowerShell Extensions.
Just as an alternative solution, here is an example of how you can display multiple dialogs to navigate users through a number of steps:
--Prepare step 1
$options = #{
"A"="a"
"B"="b"
}
$props = #{
Parameters = #(
#{Name="selectedOption"; Title="Choose an option"; Options=$options}
)
Title = "Step 1"
}
--Display step 1
$result = Read-Variable #props
if($result -ne "ok") { exit }
--Step 2
if($selectedOption -eq "Expected value") {
--Perform additional logic, for example modify #props to include additional steps
$props = #{
...
}
}
--Display step 2
$result = Read-Variable #props
I am updating sitecore treelist field type through code in background (when RTE is open for the same item) and it is not reflecting with the new values immediately. I had checked both in database and CMS.
After sometime it reflects in the field and the database as well.
Here are few things I checked:
Cleared sitecore and browser cache.
Added Cachemanager. Enabled=false and Cachemanager.Enabled=true when updating the item.
Checked the eventqueue table if the item save event is called.
Here is the code:
var article = master.GetItem(new ID(data.CurrentItemId));
if (article != null)
{
var referencedValues = article.Fields["Referenced articles"];
using (new SecurityDisabler())
{
//CacheManager.Enabled = false;
article.Editing.BeginEdit();
if (referencedValues != null)
referencedValues.Value = "{5A3D67DF-3917-4AC2-BDAF-69CDA1204C2A}";
article.Editing.EndEdit(true);
//CacheManager.Enabled = true;
}
}
I was playing around with dynamic placeholders and was struck by a prefilling concept.Is there a way to select a default rendering for one of my placeholders which would avoid the "select rendering" dialog in experience editor ??
Scenario: I have a rendeing called "PageHead" which has three renderings. One of them is a placeholder "PageTeaserPh" which currently allows two renderings: one is "PageTeaser" and second "PageTeaserWithImage". I want the placeholder "PageTeaserPh" to always have the rendering selected as "PageTeaser" and therefore avoid the dialog "Select rendering" .
I did some homework and was wondering if this is something related to Standard values (we can have it at template level; not sure for renderings though) and also i have heard of command template concept (not in-depth).
Any and all help appreciated.
You can have renderings assigned on standard values of templates, each new item would then have your PageTeaser rendering.
If you wanted to automate this process have a look at the <mvc.getXmlBasedLayoutDefinition> pipeline, we are injected common renderings by extending this pipeline.
Updated
I've found some code samples and blog posts that should help point you in the right direction for manipulating the layout details.
public void AddSublayoutToItem(string itemId, string sublayoutId)
{
using (new Sitecore.SecurityModel.SecurityDisabler())
{
if (Sitecore.Data.ID.IsID(itemId) && Sitecore.Data.ID.IsID(sublayoutId))
{
//Get the master database and get the item on which you want to add sublayout
Database masterDatabase = Database.GetDatabase("master");
Item item = masterDatabase.GetItem(Sitecore.Data.ID.Parse(itemId));
// Or you can also get Sitecore Item from Context Database as per your requirement
// Item item = Sitecore.Context.Database.GetItem(Sitecore.Data.ID.Parse(itemId));
if (item != null)
{
// Get the layout definitions and the device definition
LayoutField layoutField = new LayoutField(item.Fields[Sitecore.FieldIDs.LayoutField]);
LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value);
DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(Sitecore.Context.Device.ID.ToString());
//Create a RenderingDefinition and add the reference of sublayout or rendering
RenderingDefinition renderingDefinition = new RenderingDefinition();
renderingDefinition.ItemID = sublayoutId;
//Set placeholder where the rendering should be displayed
renderingDefinition.Placeholder = "content";
// Set the datasource of sublayout, if any
renderingDefinition.Datasource = "{24240FF2-B4AA-4EB2-B0A4-63E027934C38}";
// you can also set datasource of sublayout using Sitecore Path
// renderingDefinition.Datasource = "/sitecore/content/Home/Books";
//Add the RenderingReference to the DeviceDefinition
deviceDefinition.AddRendering(renderingDefinition);
// Save the layout changes
item.Editing.BeginEdit();
layoutField.Value = layoutDefinition.ToXml(); ;
item.Editing.EndEdit();
}
}
}
}
Taken from here - http://www.bugdebugzone.com/2014/06/how-to-add-sublayout-to-sitecore-item.html
Also a couple of other blogs on the topic
We've been trying to "componentize" our Sitecore solution as we move forward, in prep for transitioning to Page Editor usage (Woot! Finally!), but we're still practically working primarily with Page templates that may be inheritance-based composites of page specific fields, plus 1:many of these componentized templates. An example of how this looks in our solution is below -- Banner Feature Carousel and Featured Cartoon are some of these new components we're creating:
In the interest of trying to move away from using Sitecore.Context.Item (as I was recently reminded by this post) I've started filling in the Datasource template field on the sublayouts for the new components, and it seems like I've got the appropriate connections made between presentation details, the Sitecore sublayout and the .NET code file (as far as I can tell; again, we're newer to working this way).
I've also tried setting up a base class for these components as per this post by Nick Allen, but here's where I'm running into a problem: When I execute my code, this base class is finding the component Sublayout appropriately (the whole "this.Parent as Sublayout" thing) but, when I go to interrogate the Sublayout.Datasource property, it's an empty string. Here's my code (so far) for this base class:
public class ComponentBase : System.Web.UI.UserControl
{
private Sublayout Sublayout { get { return Parent as Sublayout; } }
public Item DataSourceItem
{
get
{
return Sublayout != null && !String.IsNullOrEmpty(Sublayout.DataSource) ?
Sitecore.Context.Database.GetItem(Sublayout.DataSource) : Sitecore.Context.Item;
}
}
}
I'm apparently missing some interplay between the Datasource Template field in the Sitecore sublayout, and how that actually translates to a datasource. Is it because these component templates are being used to compose Page templates? I was thinking that the datasource would just ultimately resolve to the Page template on which the component in question was currently being used, but perhaps that's my misunderstanding.
If anyone could give me any hints of things to check or point me to any resources I might use to get further, I'd appreciate it. I've done quite a bit of asking the Googs, myself, but am just not getting anything that's helping.
Thank you in advance, Sitecore friends!
It looks like you have configured the allowed templates for your sublayout in the steps you describe above. This basically tells Sitecore; 'allow user to select items based on these templates for this sublayout'. This alone does not set up the data source on items using the sublayout. You still need to go into the presentation details for any items using this sublayout, select the sublayout and then set its datasource property (within the content editor go to Presentation > Details > [Sublayout] > Data Source).
My answer to this question gives the source code needed to retrieve the datasource item and to iterate through the sitecore controls on your sublayout setting all of their Item propertys.
Here is the code:
public class SublayoutBase : UserControl
{
private Item _dataSource;
public Item DataSource
{
get
{
if (_dataSource == null)
{
if (Parent is Sublayout)
{
_dataSource =
Sitecore.Context.Database.GetItem(((Sublayout)Parent).DataSource);
}
if (_dataSource == null)
{
_dataSource = Sitecore.Context.Item;
}
}
return _dataSource;
}
}
protected override void OnLoad(EventArgs e)
{
foreach (Control c in Controls)
{
SetFieldRenderers(DataSource, c);
}
base.OnLoad(e);
}
private void SetFieldRenderers(Item item, Control control)
{
if (item != null)
{
var ctrl = control as Sitecore.Web.UI.WebControl;
if (ctrl != null && !string.IsNullOrEmpty(ctrl.DataSource))
{
//don't set the source item if the DataSource has already been set.
return;
}
if (control is FieldRenderer)
{
var fr = (FieldRenderer)control;
fr.Item = item;
}
else if (control is Image)
{
var img = (Image)control;
img.Item = item;
}
else if (control is Link)
{
var link = (Link)control;
link.Item = item;
}
else if (control is Text)
{
var text = (Text)control;
text.Item = item;
}
else
{
foreach (Control childControl in control.Controls)
{
SetFieldRenderers(item, childControl);
}
}
}
}
}
When you start using sublayouts on which you will set your datasource try to use the marketplace "Sublayout Parameter Helper". When you use the class they provide as base class and have set everything up right in your Sitecore Environment you will find yourself having a "this.DatasourceItem" -> which will contain your datasource Item, or the context Item if none is given.
http://marketplace.sitecore.net/en/Modules/Sub_Layout_Parameter_Helper.aspx
To find out more on how datasource works, try searching on http://sdn.sitecore.net. There is alot of documentation available that should get you in the right direction. Generally it will be enough to just add a datasource in the sublayout that you can access in your sublayout.
When you read a datasource from a sublayout all it will do is check if the datasource field on that sublayout is filled and will return you information regarding this datasource. So when you add a new component to a page using the page editor (for example a sublayout for a news article) and you have a datasource template defined and filled in during adding this components, you will see that when you check the presentation details on that page a sublyaout with a datasource has been added. The datasource will point to a folder in which you will find an item based on the datasource template you specified on your sublayout. You should also double check this, cause if no datasource is present on the added sublayout it will be obvious you can't access it from your code.
If you are using Sitecore 7 update 1 you can actually now use the attributes. From the Release Notes:
To make it easier to get the data source for a sublayout from
code-behind, the data source is now transferred to the sublayout
control in a "sc_datasource" attribute. (320768)
To get the data source from code, simply refer to
this.Attributes["sc_datasource"];
I am creating a custom item editor, and am using the following blog post as a reference for responding to the "save" event in the Content Editor, so that I do not need to create a second, confusing Save button for my users.
http://www.markvanaalst.com/sitecore/creating-a-item-editor/
I am able to save my values to the item, but the values in the normal Content tab are also being saved, overriding my values. I have confirmed this via Firebug. Is there a way to prevent this, or to ensure my save is always after the default save?
I have this in as a support ticket and on SDN as well, but wondering what the SO community can come up with.
Thanks!
Took a shot at an iframe-based solution, which uses an IFrame field to read and save the values being entered in my item editor. It needs to be cleaned up a bit, and feels like an interface hack, but it seems to be working at the moment.
In my item editor:
jQuery(function () {
var parentScForm = window.parent.scForm;
parentScForm.myItemEditor = window;
});
function myGetValue(field) {
var values = [];
jQuery('#myForm input[#name="' + field + '"]:checked').each(function () {
values.push(jQuery(this).val());
});
var value = values.join('|');
return value;
}
In my Iframe field:
function scGetFrameValue() {
var parentScForm = window.parent.scForm;
if (typeof (parentScForm.myItemEditor) != "undefined") {
if (typeof (parentScForm.myItemEditor.myGetValue) != "undefined") {
return parentScForm.myItemEditor.myGetValue("myLists");
}
}
return null;
}
In theory, I could have multiple fields on the item which are "delegated" to the item editor in this way -- working with the content editor save rather than trying to fight against it. I'm a little uneasy about "hitchhiking" onto the scForm to communicate between my pages -- might consult with our resident Javascript hacker on a better method.
Any comments on the solution?
EDIT: Blogged more about this solution