This post continues the series about the new field editing infrastructure introduced in Sitecore 6: I’ve talked about the renderField pipeline, and how you should be using renderField from .NET. Today is the example day. Let’s start with links. You can put sc:Link control directly on the aspx page: <sc:Link Field="Link" runat="server" /> This is a minimum set of parameters – you need to specify the field name at least. The control will use context item to get the field value. If you need any other item, you can use Datasource property in a declarative manner: <sc:Link Field="Link" DataSource="/sitecore/content/home" runat="server" /> To change the text of the link, you can use the Text property, but Link control is also able to render embedded content: <sc:Link Field="Link" DataSource="/sitecore/content/home" runat="server"> Link text </sc:Link> And of course it can render child controls: <sc:Link Field="Link" DataSource="/sitecore/content/home" runat="server"> <sc:Image MaxWidth="200" MaxHeight="200" Field="Image" runat="server" /> </sc:Link> The same using C#: var link = new Link {Field = "Link", Item = Sitecore.Context.Database.GetItem(someId)};
var image = new Sitecore.Web.UI.WebControls.Image {Field = "Image", MaxWidth = 200, MaxHeight = 200};
link.Controls.Add(image);
MainPanel.Controls.Add(link);
Notice that controls also have the Item property. The difference between Item and DataSource is that one is designed for API use and accepts Item class, and the other is designed for declarative use and accepts strings.
All of the examples above support Page Editor, including the image nested inside link.
I’m starting on twitter at http://twitter.com/a_rusakov – I’ll experiment with format and content, but the primary focus is still Sitecore and everything related to it. It will not interfere with this blog, but hopefully supplement it in a useful way. Twitter is a micro blogging platform, most known for limiting the length of one message (“update” or “tweet”) to 140 symbols, which dictates its own format. However twitter has also grown to a social platform with its unique flavor, supporting public conversations. If you don’t want to use twitter but would still like to subscribe to (“follow”) my updates, you can use RSS.
Sitecore 6 Update 6 is released, and the only fix is the support for uploading files with Flash 10 installed. Download. A little bit of insight: you probably heard that Adobe has introduced a new “security” rule in Flash 10 that says that Open file dialog can only be shown in a response to user interaction in flash. The rule effectively broke all javascript/flash uploaders, including the one we’ve used. The simplest hack around that rule is to overlay your HTML UI with a transparent flash object, and the updated flash-10-supporting libraries allow this overlay to be created using javascript. As a positive side effect of having to redo the upload UI, uploading from media folder should open somewhat faster now. I’ve also switched our underlying library from SWFupload to YUIupload, which seemed like a better supported project.
We’ve released a major overhaul of the poll module. Improvements: - Sitecore 6 support, including using Page Editor to modify existing polls
- New polls can be added using Sitecore 6 Page Editor
- Staging support
- AJAX voting
- Fully automated installation
- Simplified architecture: the module no longer uses an additional database, which also allowed us to completely remove the settings application
- Better looks
- Easier customization: we’re using more xhtml and css, and less tables. Instead of c# webcontrol code, we’re using an ascx sublayout that is much easier to tweak without having to compile the code
- Refactored code and reviewed design
The Poll module is part of Sitecore shared source program, and it stays that way. However we’ve dedicated our QA and development to reshape the module and release a clean and well tested Sitecore 6 version. We used the same open svn/trac server for entire development process, and you can see change history. From now on, the module continues its life as a shared source component, meaning that you can (and very welcome to) contribute. I was doing product management type of work on the project, while Michael Baranov gets all the praise for beautifying the code. My favorite feature is the ability to add new polls directly through the Sitecore 6 Page Editor. 1. Click “Insert Poll” 2. Setup poll using a pop-up wizard
3. Select a placeholder 4. Done. You can also edit existing polls from the Page Editor: Content layout is changed, so that you’re no longer required to store all polls in a single location. This setup beautifully supports multisite solutions. And bugs, lots of bugs were fixed: Hope you like the update. Downloads, documentation and source code are all available at http://trac.sitecore.net/Poll.
Changes: Issues fixed: -
Fixed: Cross site links were not working as expected. Such links now include the hostname of the target site and are generated relative to the start item of the target site. This functionality can be disabled by setting Rendering.SiteResolving to “false” in the web.config file. -
Fixed: a selected Publishing Target of an item was not taken into account when performing Publish operations. -
Fixed: The performance of item creation has been significantly improved. New features: - The FastQueryDescendantsDisabled setting has been added to the web.config file. It may be set to true to disable the ability to use fast query to select items through Ancestors/Descendants axes. This will give a small performance increase of item creation/moving/deletion.
Download.
Adobe released Flash player version 10 that breaks uploading in Sitecore. We’re working on a solution, do not upgrade to Flash 10 if you can. Another option is to set Upload.Classic = “true” option in web.config – this will replace Sitecore 6 upload with the one used in Sitecore 5, which doesn’t rely on flash.
One of the popular questions about Sitecore is how to get sublayout parameters. Sitecore 5 way of doing that involves advanced API – parsing layout definition for the current item, finding the rendering item, etc. Sitecore 6 makes this simple enough to remember – the parameter string is stored as a “sc_parameters” attribute of the sublayout. string rawParameters = Attributes[“sc_parameters”]; NameValueCollection parameters = Sitecore.Web.WebUtil.ParseUrlParameters(rawParameters);
Today I’ll go with the example suggested by Lars. Suppose we’re implementing a new money field type to store both the numeric amount and the currency (229 US Dollars). Both bits of information are stored in a single field using XML, which is a typical approach for Sitecore. I’ll skip the whole implementing a new field type part (no need to repeat SDN); lets say that the Sitecore Client part is already implemented, and editors are able to use the Content Editor to change field value. One problem remains – how to output money values on the website? The field contains XML, so we can’t just do sc:fld(‘money’, .). Different currencies have different signs which can go before or after the amount, which makes non-trivial rendering logic. The typical Sitecore 5 solution would be to implement a new XSL extension method and/or .NET web control. In Sitecore 6, however, you can use the renderField pipeline to make existing rendering methods properly render custom field types. Once again, this is a clean renderField pipeline: <renderField> <processor type="Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.ExpandLinks, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetLinkFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.RenderWebEditing, Sitecore.Kernel"/> </renderField> Most processors are responsible for rendering specific field types, plus there are some additional processors supporting Sitecore architecture. Following the naming convention, we’ll create a new GetMoneyFieldValue processor and place it after all Get* processors, but before the AddBeforeAndAfterValues: <processor type="Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel"/> <processor type="Money.GetMoneyFieldValue, Money" /> <processor type="Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel"/> The processor responsibility is to render HTML. I’m assuming that money field contains the following XML: <money currency=”USD” amount=”229.99” /> public class GetMoneyFieldValue {
public void Process(RenderFieldArgs args) {
if (args.FieldTypeKey != "money") {
return;
}
var doc = new XmlDocument();
doc.LoadXml(args.FieldValue);
string currency = doc.DocumentElement.GetAttribute("currency");
string amount = doc.DocumentElement.GetAttribute("amount");
string result = string.Empty;
if (currency == "USD") {
result = "$" + amount;
}
else if (currency == "DKK") {
result = amount + " kr.";
}
args.Result.FirstPart = result;
}
}
First, and most importantly, the processor has to check the type of the field being rendered. It’s only supposed to render money fields and ignore everything else. Then we retrieve the field value – notice the FieldValue property. The value is parsed, and depending on the currency we nicely render the monetary amount of either American dollars or Danish kroner. The result is stored in args.Result.FirstPart - money field is not supposed to be able to wrap other values, so nevermind the LastPart.
Why Bother?
Implementing the above processor gives website developers the ability to use standard Sitecore field rendering methods to properly render money fields. Developers can do sc:field(‘money’, .) or use the FieldRenderer class and never have a second thought.
In the last post I’ve mentioned the new ways of rendering fields using .NET code using web controls. Today I’ll focus on them more closely. The purpose for introducing these controls is two-fold: to allow .NET developers enjoy the same simplicity of rendering various Sitecore field types available in the XSL world, and to support the Page Editor. The most notable addition is the Sitecore.Web.UI.WebControls.FieldRenderer web control. This is basically a renderField pipeline wrapped in a web control form. You need to at least specify the field name and the control will do the rest. Optional parameters include the context item, html to render before and after the field and additional parameters. Another useful thing about FieldRenderer is that it’s also registered as a web control rendering in Sitecore, which allows power users to output different field types right from the Page Designer. The same FieldRenderer class also contains a static shortcut render method: Render(Item item, string fieldName [, string parameters]): string. This is the easiest way to render a field – use it when having web control is not desirable, and you’d rather just get result as a string. Along with FieldRenderer we’ve also introduced a number of web controls tailored to render specific field types: Sitecore.Web.UI.WebControls.Date Sitecore.Web.UI.WebControls.Image Sitecore.Web.UI.WebControls.Link Sitecore.Web.UI.WebControls.Text The benefit of these controls is that they provide strongly typed properties, similar to the ones available in their XSL counterparts. For example the Image web control has Alt, MaxHeight and MaxWidth properties. If you want to use these controls but still just get a string as a result, use the Sitecore.Web.HtmlUtil.RenderControl(Control):string convenience method. The most important thing to remember is that if you’re using .NET code to output field values, you have to use one of the above ways to support the Page Editor , because something like Response.Write(item[“FieldName”]) just won’t do it.
Sitecore’s renderField pipeline is a supporting pillar for the Page Editor. The pipeline ties together XSL and .NET field rendering code into one and provides a single place to affect how each field type (rich text, image, link) is rendered on the website. It doesn’t matter if the field is being output using XSL extension methods (sc:field, sc:image), XSL controls (<sc:text />, <sc:image />) or the new family of .NET web controls (Sitecore.Web.UI.WebControls.FieldRenderer, Date, Image, etc) – the renderField pipeline is always the one that provides the actual output. How does this support the Page Editor? Whenever a page is being run in the page editor mode, the renderField pipeline knows that and renders additional html/javascript around each field. How is the pipeline useful to you? The ability to hook into a field rendering process and modify the look of any field on a website is a powerful feature. For example you could completely change the way some or all images are rendered, or merely postprocess the rich text fields, like in the following example. This pipeline would also interest implementers of new field types, because it’s now possible to teach standard output methods to render third party fields types. The code below checks all links rendered as a part of the rich text field and adds the “external” CSS class to all external links. This can be useful to style external links differently, like using different colors or adding an icon next to them. public class MarkExternalLinks {
public void Process(RenderFieldArgs args) {
if (args.FieldTypeKey != "html" && args.FieldTypeKey != "rich text") {
return;
}
if (!args.Result.ToString().ToLower().Contains("<a")) {
return;
}
var firstPart = new HtmlDocument();
firstPart.LoadHtml(args.Result.FirstPart);
MarkLinks(firstPart);
args.Result.FirstPart = firstPart.DocumentNode.OuterHtml;
}
private void MarkLinks(HtmlDocument document) {
var nodes = document.DocumentNode.SelectNodes("//a");
if (nodes == null || nodes.Count == 0) {
return;
}
foreach (var node in nodes) {
if (node.GetAttributeValue("href", string.Empty).Contains("http")) {
var className = node.GetAttributeValue("class", string.Empty);
if (className.Length > 0) {
className += " ";
}
className += "external";
node.SetAttributeValue("class", className);
}
}
}
}
OK, what happens here? First, the check for the field type is performed. This processor is only designed to alter the output of html and rich text fields. If the field contains any links, output is being processed using the Html Agility Pack. The rendered field is stored in args.Result. The reason for having both FirstPart and LastPart is to render fields that can have other content embedded in them, such as general link; rich text fields only have the FirstPart.
Now the processor needs to be placed in web.config. I’m putting it just before the RenderWebEditing processor, so that it doesn’t affect the additional links rendered to support Page Editor. It’s also important that it comes after the GetLinkFieldValue processor:
<renderField> <processor type="Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.ExpandLinks, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetLinkFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel"/> <processor type="Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel"/> <processor type="Pipelines.MarkExternalLinks, Pipelines" /> <processor type="Sitecore.Pipelines.RenderField.RenderWebEditing, Sitecore.Kernel"/> </renderField>
Once again, here are the different ways of properly rendering a field:
- sc:field XSL extension method, and the family of field type specific methods such as the sc:image, sc:link, sc:date and so on
- XSL controls: <sc:text />, <sc:image />, <sc:date /> and so on
- The Sitecore.Web.UI.WebControls.FieldRenderer web control, and a family of field type specific web controls, such as Sitecore.Web.UI.WebControls.Image, Date, etc [new]
- The Sitecore.Web.UI.WebControls.FieldRenderer.Render() shortcut method. Use it when having the web control is not desirable, and you’d rather just have a string. [new]
It’s important to understand when the renderField pipeline is not used:
- sc:fld XSL extension method returns raw field value, bypassing the pipeline.
- Field values read using the Sitecore API (item[“FieldName”] or item.Fields[“FieldName”].Value) also return raw values.
- Any static html included in the aspx or ascs files is also not processed.
Consequently, these are also the ways of supporting the new Page Editor in Sitecore 6: as long as you output the field using any of the options from the first list so that the renderField is used, you’re automatically getting the full Page Editor support.
In the next post I’ll focus on the new FieldRenderer web control and the family of field controls, added to make rendering fields in .NET to be as easy as using XSL controls.
Sitecore comes with some keyboard shortcuts predefined. The purpose of this post is to compile a full list of these shortcuts and for a little extra, show how to define new ones. Global Shortcuts F9 – Publish F2 – Expose (tiles all windows so that you can select the one you need) Ctrl+/ – Focus in the startbar search
Content Editor Shortcuts Tabs (hover over each tab to reveal the shortcut): Alt+H – Home Alt+N – Navigate Alt+R – Review Alt+P – Publish Alt+V – Versions Alt+C – Configure Alt+E – Presentation Alt+S – Security Alt+I - View Some ribbon commands have direct shortcuts. Most important one is Alt+F1, which reveals the shortcuts assigned to ribbon buttons: F2 – Rename F7 – Validation F8 – Edit (Lock / Unlock)
Ctrl+S – Save Ctrl+D – Duplicate
Ctrl+Shift+F – Launch the search application Ctrl+Shift+Home – Move to the Home item Ctrl+Shift+Alt+L – Protect / Unprotect
Ctrl+Shift+Alt+Up – Sort Up Ctrl+Shift+Alt+Down – Sort Down
Assigning New Ribbon Shortcuts Each item representing a ribbon button has a KeyCode field, accepting a shortcut string. How to build this shortcut string? First, you need to know they keycode corresponding to each key. This is the code used by the javascript event model, and here you can find a list of these keycodes. Then, if Shift, Control or Alt buttons are involved in the shortcut, prepend the code with “s”, “c” and “a” respectively. Examples: Ctrl+S is translated into c83 - c for control, 83 for “s”. Ctrl+Shift+Alt+Down is sca40 – s for Shift, c for Control, a for Alt and 40 is the keycode for the down key.
As an exercise, lets assign a shortcut to start the page editor. Page Editor button belongs to the Publish chunk in the Content Editor ribbon. So switch to the core database, and go to the /sitecore/content/Applications/Content Editor/Ribbons/Chunks/Publish/Page Editor. The shortcut I want for the Page Editor is Ctrl+Shift+E. This translates into sc69 shortcut string (s for Shift, c for Control and 69 is the keycode of “e”), so write “sc69” in the KeyCode field.  That’s it. Not only pressing Ctrl+Shift+E will start the page editor from now on, but pressing Alt+F1 will also reveal the shortcut along with the ones that came predefined with Sitecore.
I’m sure some people have noticed that the Sitecore Package Designer contains a “Post Step” field. How can that be used and what for? The Post Step lets you input a method to be run after the package has been installed. To make it work, you need a class that either already exists on the target Sitecore or is in the assembly that is installed with the package. The class should implement Sitecore.Install.Framework.IPostStep interface that has a single RunPostStep(ITaskOutput output, NameValueCollection metaData) method. Your code will run in the “shell” site. What’s more important, it will run in the background thread, so you cannot use the ClientResponse or SheerResponse methods. Instead, the output parameter provides a few basic methods of interaction, such as showing the alert box or a confirm dialog. Below is a trivial example that renames the home item after the package installation: public class Sitecore6Patch2 : IPostStep {
public void Run(ITaskOutput output, NameValueCollection metaData) {
var home = Context.ContentDatabase.GetItem("/sitecore/content/home");
if (home != null) {
home.Name = "Home upgraded";
}
}
}
Note: the above example is for Sitecore 6. In Sitecore 5, your class should have a parameterless RunPostStep method instead, which is also supported in Sitecore 6 for compatibility purposes.
Have you seen or used the Sitecore Poll module? Got any complaints, suggestions or feature requests? Mail ar at sitecore dot net or leave them here in comments. The feedback will be put to good use. Go.
A little known fact is that Sitecore 6 comes with Prototype and Firebug lite preinstalled. Prototype is a mind saving javascript library, and firebug lite is a helpful javascript instrumentation console replacing dozens of alert(“I’m here”) calls. We’d do it for ourselves anyway, but what does it mean for you? Prototype Prototype javascript library is automatically included in Sitecore shell applications – both native, such as Content Editor, and the custom ones. This means you can safely use prototype both in your Sitecore customizations, such as custom fields, and in your own applications. (If you look closely enough, you’ll see that custom FieldTypes I’ve made for Crestone shamelessly use prototype whenever possible). Needless to say that prototype is great and saves a lot of brain cells if you do any javascript at all. Some great alternatives, such as jQuery, do exist – but if you don’t use anything at all, I really wonder why. If you’re new to prototype and javascript frameworks in general – they have very solid documentation at http://prototypejs.org/. Even more, prototype’s cousin – Scriptaculous, a javascript UI controls and effects library is also shipped with Sitecore, but is not included automatically. If you need it, it’s at /sitecore/shell/Controls/Lib/Scriptaculous. Firebug Lite Firebug lite is a cross browser javascript console – the fact that it works in Internet Explorer is of biggest interest to Sitecore developers. I’ve praised it before, but now it’s one key press away in Sitecore shell applications. Press F12 and the firebug lite console will pop up from the top: This means you can use console.log and other instrumentation methods to debug your javascript. Sitecore UI has a lot of IFrames, so pay attention where you click before opening the console – each IFrame is a separate javascript realm, and therefore has its own firebug console. The Website is Safe I’d like to stress again that both Prototype and Firebug lite are only included in the Sitecore shell applications. Technically, if the sitecore.js javascript file is loaded, the prototype and firebug are also inlcuded. We don’t add anything to the frontend sites – it’s your decision.
And in case you haven’t seen this elsewhere: Do not install the .NET 3.5 Service Pack 1 on any servers running Sitecore. Service pack updates the LosFormatter class which is responsible for viewstate serialization/deserialization in the ASP.NET, and it looks like they’ve introduced a subtle bug that hit us. An official release: Dear Sitecore Enthusiast, You are receiving this message because you are subscribed to the Sitecore Product Issues and Patches mailing list. On Monday, August 4th, Microsoft released the following service packs: Visual Studio 2008 SP1 and .NET 3.5 SP1. Sitecore has discovered that these service packs introduce a bug in the LosFormatter class (System.Web.UI.LosFormatter in System.Web.dll, used to serialize and deserialize an ASP.NET ViewState). This bug causes stability issues in Sitecore products. Sitecore has raised this as an urgent priority issue with Microsoft (case number : SRQ080813600454) and is working to help resolve this issue. In the meantime, PLEASE DO NOT INSTALL .NET 3.5 SP1 and Visual Studio 2008 SP1 on any server running a Sitecore product (including Sitecore WCMS, Intranet Portal, and Foundry) until further notice! Symptoms associated with installing either of these service packs: - Memory consumption increases dramatically and single core CPU usage goes up to 100% when opening the Access Viewer or Media Library applications.
- OutOfMemoryExceptions thrown in the Desktop and Content Editor.
- The browser becomes unresponsive when accessing Sitecore.
Please be aware that Microsoft may include this Service Pack as part of the monthly ‘Patch Tuesday’. Please take steps to avoid the automatic installation of these service packs. Please be aware that the final version of SQL Server 2008 will require .NET 3.5 SP1. If you have any questions about this issue, please contact Sitecore support. Best Regards, Sitecore Support Team.
renderItemTitle is another presentational pipeline introduced in Sitecore 6, allowing to modify the appearance of item blocks, or tiles, in the folder views. Folder view is what you see on the right: a default tab on folder items. You can also add it to any other item by adding it to the Editors list. Hooking into the renderItemTile allows changing the appearance of individual item tiles. Most often, however, customization scenarios include adding a few bits of information, relevant to all items or to specific item types. This is how the pipeline looks out of the box (simplified): <renderItemTile> <processor type="RenderFolderTile" /> <processor type="RenderTemplateTile" /> <processor type="RenderDefaultTile" /> </renderItemTile> The RenderFolderTile and RenderTemplateTile add information relevant to folders (a number of subitems) or templates (a number of usages). In this example, I’ll be adding information about a number of validation errors of the item. The processor has to inherit from Sitecore.Pipelines.RenderItemTile.RenderTileBase. This base class is already capable of rendering a default item tile, so I’m just slightly changing its behavior to also output a number of validation errors. public class RenderItemTile : RenderTileBase {
public override void Process(RenderItemTileArgs args) {
if (args.View != TileView.Tiles) {
return;
}
base.Process(args);
args.AbortPipeline();
}
protected override void RenderTileDetails(HtmlTextWriter output, RenderItemTileArgs args) {
base.RenderTileDetails(output, args);
var errorCount = GetErrorCount(args.Item);
if (errorCount == 0) {
return;
}
var message = errorCount > 1 ? "{0} errors".FormatWith(errorCount) : "1 error";
output.Write("<div class="\"scTileItemDetailsLine\"" style="color: red">");
output.Write(message);
output.Write("</div>");
}
int GetErrorCount(Item item) {
var errorCount = 0;
var validators = ValidatorManager.BuildValidators(ValidatorsMode.Gutter, item);
ValidatorManager.Validate(validators, new ValidatorOptions(false));
foreach(BaseValidator validator in validators) {
if (validator.Result >= ValidatorResult.Warning) {
errorCount++;
}
}
return errorCount;
}
}
A few pieces to notice:
- args.View defines the mode the pipeline runs in: Tiles, Large Icons, Small Icons, etc. Most of those are reserved for the future use and at the moment Sitecore only uses Tiles or IconOnly modes. In this customization, we’re after the Tiles mode.
- A relatively simple RenderTileDetails method is overriden. This method is only responsible only for the information to the right of the icon. The general shape and style of the tile, as well as the icon, are handled by other more complicated methods, which we’re not interested in.
- Note the usage of the new Validation API to validate the item: GetErrorCount method. Here I’m validating the item in the ‘Gutter’ (which is the old name for Quick Action Bar) mode. This means that the folder view will only run validators configured for the quick action bar (see the validation documentation on SDN and my post on validation for more)
- Once the tile is rendered, the pipeline must be aborted, or the RenderDefaultTile processor will kick in and do it’s job.
As with other pipelines, Sitecore only knows about generic data concepts such as templates, folders and items. We cannot provide the customizations specific to the business domain of each site, but you can. The renderItemTile pipeline can be used to add information that describes each specific item type best: a publication date of the news item, a number of comments of the blog post or a discount percentage for a sales partner. And because it’s a pipeline, you can even display different information to different types of users.
A quick hint today – did you know you can reorder items using drag and drop in Sitecore 6? Hold the “ALT” key and drag the item on any other item in the tree. The item you’ve dropped will then be placed before the target item.
(For introduction to the new publishing architecture, read the part 1: New Publishing Pipelines and Events in Sitecore 6) Today’s example is using the publish:itemProcessed event to publish related media items. A little background first. Imagine a new item structure being created, and some content items have an image field that uses items from the media library. Neither the content items nor the media items have been published yet. Now if the editor publishes the content item in a “single-item” mode (i.e. selects the publish in the ribbon, and not the incremental, smart or full publishing), the item will get published to the web database, but the media items will not. The event handler below listens to the publish:itemProcessed event, and if the content item had been published in the single-item mode and has links to media items, they get published too. public class ItemProcessedHandler {
public void ItemProcessed(object sender, EventArgs rawArgs) {
var args = rawArgs as ItemProcessingEventArgs;
Assert.IsNotNull(args, "args");
var context = args.Context;
// [1] publish mode check
if (context.PublishOptions.Mode != PublishMode.SingleItem) {
return;
}
var item = context.PublishHelper.GetSourceItem(context.ItemId);
if (item == null) {
return;
}
if (!item.Paths.IsContentItem) {
return;
}
// [2] Triggering publishing of the related item by running the publishItem pipeline again
var options = new PublishOptions(
context.PublishOptions.SourceDatabase,
context.PublishOptions.TargetDatabase,
PublishMode.SingleItem,
context.PublishOptions.Language,
context.PublishOptions.PublishDate);
var relatedMedia = GetRelatedMedia(item);
foreach(var mediaItem in relatedMedia) {
PublishItemPipeline.Run(mediaItem.ID, options);
}
}
// [3] related media items are retrieved using the link database
IList- GetRelatedMedia(Item item) {
var references = Globals.LinkDatabase.GetReferences(item);
var result = new List
- ();
foreach(var reference in references) {
if (reference.SourceDatabaseName != item.Database.Name) {
continue;
}
var referenceItem = item.Database.GetItem(reference.TargetItemID);
if (referenceItem == null) {
continue;
}
if (referenceItem.Paths.IsMediaItem) {
result.Add(referenceItem);
}
}
return result;
}
}
Notes:
- Check the publish mode – no need to intervene in the incremental or full publishes, Sitecore will publish everything needed by itself
- Programmatically running the publishItem pipeline for each media item. Higher level PublishManager API could have been used as well
- Link database is used to retrieve the related media items. Nothing new here.
To activate the event: <event name="publish:itemProcessed">
<handler type="Pipelines.Publishing.ItemProcessedHandler, Pipelines" method="ItemProcessed" />
</event>
Even as the live mode is used more often, publishing remains one of the most important parts of the Sitecore world. If you look to the right, you’ll see my ugly drawing designed to contrast with the beauty of the new publishing architecture Ole implemented for Sitecore 6. The entire publishing process is managed by the two pipelines: publish and publishItem. The publish pipeline runs once whenever a publish process is started from the user interface or programmatically. The pipeline collects all the items that need to be published, and runs the publishItem pipeline for each item separately. PublishItem pipeline decides how to perform the actual publishing. It also raises two very useful events: publish:itemProcessing and publish:itemProcessed. Each event receives the full set of current publishing settings and the item being published. When deciding between extending the publishItem pipeline and using the publishing events, I’d say use the events whenever reasonably possible. Publishing is still a low-level core process, so not having a chance to mess up the way the pipeline operates is good. I’ll illustrate the new extensibility with two examples, one for each event. Preventing an Item from Being Published As the name suggests, publish:itemProcessing event fires before the publishing is performed, and can be used to cancel the process. In this example, the publishing of protected items is forbidden. public class EventHandler {
public void ItemProcessing(object sender, EventArgs rawArgs) {
// [1] – retrieving the args.
var args = rawArgs as ItemProcessingEventArgs;
Assert.IsNotNull(args, "args");
// [2] – action check
if (args.Context.Action == PublishAction.DeleteTargetItem) {
return;
}
// [3] – retrieving the item being published
var item = args.Context.PublishHelper.GetSourceItem(args.Context.ItemId);
if (item == null) {
return;
}
// [4] – cancel publishing if the item is protected
if (item.Appearance.ReadOnly) {
args.Cancel = true;
}
}
}
Things to note:
- The way arguments are passed to the event handler. This differs from the Event.ExtractParameter that had to be used for older events.
- The args.Context.Action check. The publishItem pipeline (and therefore the events) are run for each publishing operation, even if the publishing actually results in the item being removed from the target (web) database. In this example I don’t want to prevent item deletion, so the handler bails out.
- The use of args.Context.PublishHelper.GetSourceItem() method to retrieve the item being published.
- The operation is canceled in a traditional .NET way by setting the args.Cancel to true.
To activate the event: <event name="publish:itemProcessing">
<handler method="ItemProcessing" type="Pipelines.Publishing.EventHandler, Pipelines" />
</event>
In the next post I’ll show how to publish related media items using the publish:itemProcessed event.
Sitecore 6 introduces a number of new pipelines. Some reflect new features added to the system, and some provide new customization opportunities for the previously existing features. Today's topic is the getContentEditorWarnings pipeline. Content Editor warnings are the yellow warning blocks that appear above sections and fields in the main Content Editor area. We have more than a dozen warnings supported out of the box, such as "the item is protected", "the item will not be published", etc. These warnings are specific to Sitecore - we don't know anything about the business domain of the site. However it is easy to add new warnings using the getContentEditorPipeline. As an example, I've created a warning that checks the updated date of the item, and warns if the item haven't been updated in 6 months or more: public class GetContentEditorWarnings {
public void Process(GetContentEditorWarningsArgs args) {
if (DateTime.Now - args.Item.Statistics.Updated > TimeSpan.FromDays(180)) {
var warning = args.Add();
warning.Title = "This item haven't been updated in a while";
warning.Text = "Consider revising the content.";
warning.AddOption("Set a reminder", "item:reminderset(id={0})".FormatWith(args.Item.ID));
}
}
}
The steps here a simple: title and text provide warning description to the user, and AddOptions allows setting up quick fixes. In this example, a “set a reminder” dialog will popup if “Set a reminder” is clicked.
To enable the warning, just add the new processor to the getContentEditorWarnings pipeline either by modifying web.config or using auto-include files (recommended).
<getContentEditorWarnings> .. <processor type="Pipelines.ContentEditor.GetContentEditorWarnings, Pipelines" /> </getContentEditorWarnings>
A warning can also be exclusive or fullscreen. Exclusive warnings do not allow other warnings to appear. If no exclusive warning is displayed, multiple “normal” warnings can be shown. If fullscreen warning is shown, it hides the usual editing interface of the Content Editor – sections and fields are hidden. Both warning modes can be activated by setting relevant properties of the GetContentEditorWarningsArgs.

In part 2 I've shown how to customize grouping of existing search results. Going one step further, it's also possible to inject additional search results. I don't suggest that you try to improve the way the items indexes and searched. Instead, you can increase usability by supporting special searches that are meaningful to your solution or module, or search external locations that are not indexed by Sitecore. I'll use workflow state searcher as an example: whenever a search query matches the name of any workflow state in the system, the items in that workflow state are added to the search results. How This Works Client searches are handled by the <search> pipeline: <search> <processor type="Sitecore.Pipelines.Search.IDResolver, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.PathResolver, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.UrlResolver, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.SecurityResolver, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.DatabaseResolver, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.SearchSystemIndex, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.CategorizeResults, Sitecore.Kernel" /> <processor type="Sitecore.Pipelines.Search.AddInstantOptions, Sitecore.Kernel" /> </search> You can see that special searches that Sitecore supports (see part 1) are implemented with separate processors. Adding a new processor in the pipeline will allow new search results to be added. The Codepublic class WorkflowSearchResolver { public void Process(SearchArgs args) { foreach (var workflow in args.Database.WorkflowProvider.GetWorkflows()) { foreach (var state in workflow.GetStates()) { if (state.DisplayName.Equals(args.TextQuery, StringComparison.CurrentCultureIgnoreCase)) { AddStateResults(args, workflow, state); return; } } } }
void AddStateResults(SearchArgs args, IWorkflow workflow, WorkflowState state) { foreach (var uri in workflow.GetItems(state.StateID)) { var item = args.Database.GetItem(uri); if (item == null) { continue; }
args.Result.AddResultToCategory(SearchResult.FromItem(item), state.DisplayName + " state"); } } }
The logic flow is simple: iterate through all workflow states, and if the query (args.TextQuery) matches the state name, get the items in the workflow state and add them to a category named <state name> state.
All search processors must complete before the search results are displayed, so considering performance is a good idea. For production implementation, caching the workflow state names will make performance impact of the new search processor negligible.
Note that the searches only display a limited number of results (unless the standalone search application is used). Therefore consider limiting a number of search results you inject, so that other processors can also add theirs. In this example, sorting the results is also a good idea: updated date or the date of workflow state change can be used, so that the most recent items appear first. I skip both steps to make the example simpler.
The new search will work both in Content Editor and startbar (instant search) scenarios. However you can limit it to either one by checking the args.Type and aborting if needed.
Try It Out
Add a new processor to the search pipeline. A spot before the standard search system index would be a good place:
<processor type="SearchExtensions.WorkflowSearchResolver, SearchExtensions" />
Now either open the Content Editor and search any workflow state name (such as Draft), or press Ctrl-/ to focus the startbar search and type workflow state name there. You should see a result similar to the screenshot at the beginning of this post.
This post is a part of series about new client search introduced in Sitecore 6:
Part 1: Overview Part 2: Categorization Part 3: Custom Search Results
Sitecore 6 client search is built for extensibility and the most common scenario is to customize result categorization. We ship with a number of default categories common for all Sitecore solutions - content items, media items (split into images and documents), layouts, system and user templates, workflows, etc. Each solution or module, however, has its own inherent categories. Solutions benefit from content categories like news, customers and partners. Modules have distinct item types: RSS feeds, mailing lists. All these categories can be setup in a declarative manner. There is a new section in web.config named search, which includes the categorizer definition: <categorizer type="Sitecore.Pipelines.Search.CategorizeResults+Categorizer, Sitecore.Kernel"> <Categories hint="raw:AddCategory"> <category path="/sitecore/content"/> <category displayName="Images"> <templateID>{F1828A2C-7E5D-4BBD-98CA-320474871548}</templateID> <templateID>{DAF085E8-602E-43A6-8299-038FF171349F}</templateID> <templateID>{C97BA923-8009-4858-BDD5-D8BE5FCCECF7}</templateID> <templateID>{EB3FB96C-D56B-4AC9-97F8-F07B24BB9BF7}</templateID> </category> <category displayName="Documents"> <templateID>{16692733-9A61-45E6-B0D4-4C0C06F8DD3C}</templateID> <templateID>{777F0C76-D712-46EA-9F40-371ACDA18A1C}</templateID> <templateID>{7BB0411F-50CD-4C21-AD8F-1FCDE7C3AFFE}</templateID> <templateID>{0603F166-35B8-469F-8123-E8D87BEDC171}</templateID> <templateID>{3DB3A3CA-A0A9-4228-994B-F70C8E99A1CE}</templateID> <templateID>{2A130D0C-A2A9-4443-B418-917F857BF6C9}</templateID> <templateID>{F57FB07D-332A-4934-AA67-0A629C5396E2}</templateID> <templateID>{CC80011D-8EAE-4BFC-84F1-67ECD0223E9E}</templateID> </category> <category path="/sitecore/media library"/> <category path="/sitecore/layout/devices"/> <category path="/sitecore/layout/layouts"/> <category path="/sitecore/layout/sublayouts"/> <category path="/sitecore/layout/renderings"/> <category path="/sitecore/layout"/> <category templateIDs="{455A3E98-A627-4B40-8035-E683A0331AC7}" displayName="Template Fields"/> <category templateIDs="{E269FBB5-3750-427A-9149-7AA950B49301}" displayName="Template Sections"/> <category path="/sitecore/templates/branches"/> <category path="/sitecore/templates/system" displayName="System Templates"/> <category path="/sitecore/templates"/> <category path="/sitecore/system/aliases"/> <category path="/sitecore/system/languages"/> <category path="/sitecore/system/workflows"/> <category path="/sitecore/system"/> <category path="/sitecore/content/applications/control panel" database="core"/> <category path="/sitecore/content/applications" database="core"/> </Categories> </categorizer>
The syntax is fairly self-explanatory. Declarative categories can be setup either using item path or template IDs. Whichever category matches first wins, so the more specific categories (Images and Documents) should go before the generic ones (Media Library).
It's important to understand that categories do not affect the indexes, but only how the results are grouped in the UI.
To setup a content category such as news: <category path="/sitecore/content/home/news" />. Make sure to add this line above the /sitecore/content category.
To setup a template category: <category templateIDs="{id-of-the-rss-feed-template}" displayName="RSS Feeds" />. Again, this category should go before the default ones.
This post is a part of series about new client search introduced in Sitecore 6:
Part 1: Overview Part 2: Categorization Part 3: Custom Search Results
Search-driven navigation is getting more and more popular as amount of data increases and it gets harder to organize and access it using the traditional structured approach. I'm also a big fan of the application launchers, having tried most of the tools on the Scott Hanselman's list and then some. In fact, if you're not on Vista and don't use any of those, I highly recommend trying a few. In Sitecore 6 we took a step in that direction, enhancing the UI with two large-scale search options. Content Editor Search Content Editor search sits right on top of the content tree. The simplest mode is the basic search: All search results are grouped into categories. Some categories are contextual (subitems), most are based on items paths and some use item templates (like Jpeg image). Clicking the result moves the Content Editor to the selected item. The search input can also be expanded to allow field-level search: The new search has a few special abilities, being able to recognize item IDs and paths: ID:
Item path (a few variations of paths are supported, you can omit /sitecore or /sitecore/content):
Startbar Search The familiar startbar search has also been upgraded, now operating in the live search mode: To enable keyboard-only searches, there is a new global shortcut: press Ctrl-/ and the startbar search will get focused so you can start typing immediately. Both searches operate using a new "Quick Search" index. If you don't get any results - rebuild the index using the same Rebuild the Search Index control panel applet. The startbar search has a few extra abilities compared to the Content Editor, being able to launch applications, provide shortcut to the user editor and database switcher. Application launcher (supports both traditional applications and control panel applets):
Users (the user name has to be an exact match, clicking the result opens the Edit User dialog):
Databases:
The application launcher makes the search really handy in the developer scenario. In the deployed website the ability to search through content is likely to provide more value to the editors. The geek beauty of the new search architecture is that it allows shell developers to hook in and provide own categorization, additional search results and implement new actions. In the following parts, I'll get to that. This post is a part of series about new client search introduced in Sitecore 6: Part 1: Overview Part 2: Categorization Part 3: Custom Search Results
Validation fixes are left for dessert. It's entirely possible to build great validation implementation into a site and never use fixes, but they make a perfect finishing touch. Show me the Fixes The setup: the Title field has a max length validator. If the field value is longer than 40 symbols, the validator displays an error even before the item is saved (no refresh required) Now right click the red square in the validator bar: Click "Trim" and the field value gets cut back to 40 symbols, and again - save operation is not required, content editor doesn't refresh. How to Make One Now back to our business requirement: we need to make sure no one spells Sitecore as "SiteCore". In part 3 we made a validator that checks for this situation, but it's also fairly easy to fix the spelling without human involvement. Step 1. Back to the validator definition at /sitecore/system/settings/validation rules/field rules. Under the validator item, create a new item based on the menu item template (as often, its easier to duplicate an existing fix, for example the one under the max length validator). There are two important fields to fill: Display Name and Message. Step 2. Register the validator:fixsitecore command. This should be familiar if you do any Sitecore client development. Open then /App_Config/commands.config file and add a new command: <command name="validator:fixsitecore" type="Validators.Actions.FixSitecore, Validators"/> Step 3. The code. Similar to other client commands, but inherit from Sitecore.Shell.Framework.Commands.ContentEditor.Validators.ValidatorCommand. Key points are noted in the comments: public class FixSitecore : ValidatorCommand {
public override void Execute(CommandContext context) {
// gets the validator. use it to access validator parameters, if needed.
var validator = GetValidator(context);
if (validator == null) {
return;
}
// get the field web control that we want to interact this. This really
// depends on the way the content editor field is implement. Remember
// that we deal with a simple single-line text field in this example.
var rawControl = GetControlToValidate(validator);
if (rawControl == null) {
return;
}
var control = rawControl as Sitecore.Web.UI.HtmlControls.Control;
if (control == null) {
return;
}
// The actual fix logic
control.Value = control.Value.Replace("SiteCore", "Sitecore");
// Re-run the validation, so that red light turns to green in the UI.
Validate();
}
}
The hardest part is having to deal with asp.net control structure. We cannot provide the familiar Sitecore item and field API here because validator bar validation runs in real time before the item is saved. This means that the field value might not even exist in the database, and is only available on the UI level.
This means that most fixes are tied to the field implementation. Changing the text field value is fairly easy. To do the same for the rich-text field, a different approach is required: javascript is probably the way to go, because rich text field embeds editor in an iframe.
Step 4. The result:
Summary
While not all validation fixes are straight-forward to implement, they can provide that finishing touch you need for a perfect validation system or a great demonstration.
This concludes the series of posts about new validation features implemented in Sitecore 6, hope you can put it to a good use soon.
Part 1: Introduction, configuration, validation types. Part 2: Error levels, built-in validators. Part 3: Making a custom validator. Part 4: Making a validator fix action.
Today we're building a custom field validator. The business requirement will be simple: a lot of people at Sitecore get upset when Sitecore is spelled as "SiteCore" (oh, the joy of rebranding). We'll be making a validator to detect the incorrect capitalization and make sure items containing such atrocity never get published. The Code Start with the code. Here's what you should keep in mind: - The class should inherit from Sitecore.Data.Validators.StandardValidator.
- The validator should be serializable: notice the [Serializable] attribute and serialization-supporting constructor.
- Evaluate() method is responsible for the actual validation. The field value is available through the ControlValidationValue property. Return ValidationResult.Valid if no errors are found.
- If there are errors, use the Text property to set the human-readable error description, and GetFailedResult method to return the default error value. This will allow solution architects to override the error level later (I've covered this in part 2).
- Use the GetMaxValidatorResult method to return the maximum error level the validator can result in. Sitecore needs this to decide if your validator can potentially block a UI operation and therefore it must wait for validator to execute before the operation starts.
Now to the code: [Serializable]
public class FieldValidator : StandardValidator {
public override string Name {
get {
return "Sitecore capitalization validator";
}
}
public FieldValidator() {}
public FieldValidator(SerializationInfo info, StreamingContext context) : base(info, context) {}
protected override ValidatorResult Evaluate() {
var value = ControlValidationValue;
if (string.IsNullOrEmpty(value)) {
return ValidatorResult.Valid;
}
< |