Quantcast
Channel: Project Lifecycle
Viewing all 178 articles
Browse latest View live

Enhancing Lucene Search

$
0
0

So I'm attending the "Enhancing search for Lucene" because I've been using Lucene lightly for years but haven't had the time to fully vet it for more mainstream use. I love it for its speed and flexibility but I have lingering questions about the memory/cpu costs while rebuilding large indexes on production servers. Overall I just want to get a feel about how other people are using it and any pitfalls they've run into.    

The talk starts with a use case which as a developer always makes sense because there's a certain level of nuance that is hard to replicate with hypotheticals. The challenges they focused on were indexing new information quickly and being able to filters useful information from large document counts with little or no meta data.    

To help users filter their large pool of documents, they're using additive filters in the right column on information like date ranges and authors which can be used in combination to further refine the result list. They're also using lazy loading of more detailed result information to decrease the html footprint and inherently the page load time as well as caching those individual requests to improve repeated use.    

When the presenters dove into their breakdown they revealed that they were using their own custom search indexer which is a great example of the flexibility of Lucene. They also did a great job of breaking up their filter code to be more modular by creating an interface that all filters should implement. The sublayout data source is how they're able to differentiate pages that will be reusing the search functionality with different result sets. One of the most interesting pieces is the user interface within Sitecore that actually allows an editor to configure a new search page. They get a lot of points for accommodating that level of user control.    

Overall it was a nice solution. They broke down their solution in a very MVC way. I also did get my question answered: they never needed to run a full index rebuild and only relied on the incremental updates from the publishing events which for me was all I needed to hear. I have a plan for further integration with Lucene in a number of ways and this was able to put my assuage my fears about the continual maintenance in a products environment.


Sitecore Mobile Framework

$
0
0

Sitecore's "ultra cool mobile framework" will prove to be a defining moment in their ability to surf the frenzy that is the internet. With an exploding population of netizens hailing from their mobile devices those who can't adapt will undoubtedly pay for it with less engaged, more frustrated users dropping off. Developing a mobile strategy can be a difficult task but Sitecore looks to be poised to help developers handle that task.    

The SDK will be built using JQuery and JSON which should give developers a lot to leeway to develop solutions far a variety of mobile devices.    

iOS, being as dominant as it is, has an Objective-C library which will support querying items over http/s, caching, security access and even uploading media. That's a lot of power straight out of the gate so anyone who will be using this should have a lot of flexibility in developing a wide array of applications. The library will also support gestures like swiping, navigation and hooking into phone functions with JavaScript, such as adding new contacts, which will quickly gain it popular support. I'm a little surprised to see the use of xslt sublayouts (shudder) but I guess it's a free country.    

If, as I imagine a plurality of developers will be, you're going to be supporting more than just iOS, you're still going to have access to the web service that the Objective-C library is using, you'll just have to build your own device specific controls or rely on more generic functionality.    

The "Item Web API" will provide JSON results from REST requests and will be the workhorse for your mobile application. The API will be available as an installable module which will enable your system for these mobile queries. Accessing secure content will be handled by using security passed through two HTTP headers. The module will also install specific security settings that will allow you to specify which items can or cannot be accessed through the API. That's a very nice touch as well as the support for RSA encryption of the security credentials if you're not using SSL, although it is recommended that you do so.   

 The API will, as you would hope, will support language through querystring parameters, although i didn't hear anything about versions, but i may have missed it given the speed we're moving through this talk. While querying items you will also be able to set the "scope" which is what Sitecore is using to describe the related items to return. The parent, self and children will be supported and their order can be set. XPath and Fast queries will be supported through the query string as well which can either be a great asset or a liability but the freedom is yours to choose. Uploading to the media library should prove to be a very valuable tool for enabling user generated content or even developing really slick content editor apps.    

The future of mobile enabled Sitecore sites looks bright given the level of commitment already shown in their initial push. Future use will definitely dictate how this will expand but overall I'm really impressed with what they've provided so far.

Sitecore Multiple Site Solutions

$
0
0

Multiple-site solutions are one facet of the Sitecore ecosystem that I'm intimately familiar with so I'm really looking forward to see how other developers have solved the problems that tend to crop up.    

The talk began by focusing on problems that multiple sites produce such as user/group management, deployment problems across multiple regions and disparate code management.    

For as long as I've been a Sitecore dev I haven't heard a lot about Foundry 4  but it looks like this will be my opportunity. It looks like the focus of foundry is really churning out sites quickly, which I did know but it looks like they've beefed up the amount of customization for each site through individualized domains and styling but apparently it's not suitable for the high level of customization typical in most sites.    

The other solution provided will be to create a custom tool optimized for stamping out and managing not just sites with the exact same form as Foundry might do but a will create a much more extensible site system. There will be a core "shared" system and then for each site a duplicate set of the layout, template, system and media library folders, but despite the complex system a solution manager will use, standard content editors will see the standard content tree and will be able to work with no dramatic changes. This is not quite like anything I've ever seen before and I'm really just trying to wrap my mind around what exactly I'm looking at. Tim Ward has a new as of right now who is me.    

Now this "Site Manager" not only supports creating stamps types for a site but will also go so far as how each site type will be deployed. This piece alone got a lot stunned looks from the audience because of how complex the sample deployment process was. The tool will tailor packages for this deployment process so that you're no longer packaging up anything and everything but a much leaner, focused package. A huge gain if you've ever struggled with deploying a site across a complex network of servers.     

Tim didn't stop there though, there is also a tie-in with Sitecore Rocks. That's right, if you're already making you're life easier using that tool you'll be glad to see this level of support extended.        

Now it's really late in the day and my ability to keep up with a presentation this complex is waning but clearly this took a lot of time and effort by a highly talented cyborg named Tim Ward. When Sitecore posts these talks online after the convention I'd recommend following up on this to unearth a truly impressive solution which will serve to inspire a host of other ideas.

Keynote With Gary Vaynerchuk

$
0
0

I'm back at it at Sitecore Symposium day 3 in the keynote session with Gary Vaynerchuk. Still a little groggy from last night at Haze tearing it up with all the die hard Sitecore employees, partners and clients.     

Gary opened with a joke about how few people at the conference knew who he was despite his large twitter following. He followed up with a brief history of his innate business prowess leading into how he methodically approaches business and sales. His skill isn't just in taking advantage of the bleeding edge of technological innovation to increase sales through focused customer engagement, but also in having an impressive sense of humor.     

Gary begins forming the body of his argument about the importance of social media not just because of the power of the technology but more importantly, because of it's ability to improve human engagement. This, of course, is the corpus of sales and marketing. He was able to relate his understanding of the growing landscape of information rich social media site to give a meaningful talk to the audience given that Sitecore's major focuses is the customer engagement platform. He clearly has a masterful understanding of fostering powerful customer relationships evidenced by his explanation of his book "The Thank You Economy". He deftly works the crowd with his anecdotes and strategy of engaging the audience throughout his talk. He makes a point to reiterate the importance of great story telling for good business vs. the abusive or tone-deaf marketing strategies. He himself practices what he preaches as his story telling ability is engrossing a large swath of a sleep deprived audience. Sadly all of what he's saying about an appreciative business model should be common sense but is often held back by risk averse leaders. Here's hoping he reaches people on a deeper level to affect change in their respective industries.     

However few people in attendence knew him before he started, there will be a near non-existent list of people who won't remember him now.

Using the Powershell With Sitecore

$
0
0

Sitecore and the powershell is a talk I've been looking forward to for a while. I'm a one man team so my life revolves around automation. The thought of being able to streamline my process and shave time off the labor intensive tasks that I engage in regularly, easily keeps my attention.     

So the powershell is a windows utility that is built on top of the .NET library and provides a deep level of access to the system. The presenter is using, as an example, the task of installing a Sitecore instance through a powershell script. It's can be a common and time consuming task especially if you work in a company where rapid prototyping is needed for building proof-of-concept sites. I don't but still, it's good to know.   

The presenter then takes it a step further by showing how you would apply the power of automated server setup to remote servers by making authenticated requests to multiple end points. He wraps up by going over a few pitfalls associated with scripting in powershell such as knowing to return powershell objects to the console so that you have a better context of information to pull from so that results aren't too vague.     

The powershell can be accessed from within Sitecore Rocks giving you yet another reason to download and install it. To be able to dive in, you'll really need to catch up on powershell commands which you can read up on the secrets of powershell ebook. There's also a few other things you're really going to need to be familiar with as well to use this tool effectively such as working with serialized content, configuring client powershell access and writing custom powershell scripts. It's an incredibly powerful but it does assume you have a fairly comprehensive understanding of the technology you're working with.

Velir Field Suite Module

$
0
0

I'm catching the talk on the field suite module presented by Tim Braga from Velir. I'm all about anything that improves the default set of fields provided out of the box from Sitecore. He was Also good enough to post the field suite module to the shared source modules.      

The first field he chose to deal with the multilist field. It becomes immediately obvious that there are some shortcomings in its usability. The list of selected items don't offer much in the way of context for where those items may be located in the content tree, whether or not they're published or, god forbid, the difference between multiple items that are named the same. The improvements made by the field suite is like offering a cold glass of water to a man stranded in a desert. There's an additional subnavigation bar with select all/deselect all buttons. There's also buttons that allow you to edit or directly browse to the item selected in the list. The plain text items are replaced with template icons, a gutter that indicates the publish status, which through a few additional clicks also allows you to publish, and the content path available on hover. The buttons that control which items are selected are also replaced by a much more user friendly plus/minus buttons next to each item allowing a quick easy way to shift items from either column.    

The other fields that've been augmented are the ImageField, the DropLink, the DropTree, the TreeList and TreeListEx. Basically all the newer, more complex fields that you'll find yourself using a lot. Each field has its own treatment but you get the basic gist that this isn't just lipstick; there's a lot of added value.      

After seeing what has been built, I'm left with the feeling that things are going to much better in terms of usability. I'm already sold on this tool. I will definitely be installing this the minute I get back to my desk and I'd recommend anyone else do the same if you've been left wanting more from your multilist. You can find it over on the Sitecore Marketplace.

Item Buckets by Tim Ward

$
0
0

tim wardAfter the last mind blowing talk by Tim Ward I decided it would be prudent to catch this talk about breaking the million item mark with content buckets. As if simply storing this much information wasn't enough of a challenge, he was also trying to solve the problem of quickly querying this data set based on a bit of meta information.   

Ultimately there are a lot of ways you can approach this kind of problem with varying degrees of success, but like the precocious individual he is, he decided on a custom solution called "Item Buckets".     

The item buckets are parent nodes that work in concert with child items whose templates have been enabled to support buckets. The visual feedback you get is that these newly "bucketized" items are now replaced by a transparent icon that indicates the children are now managed by the bucket system. Non-bucketized items (I realize that I'm completely butchering the english language here) will still display as normal. This indicates that their location in the tree is no longer important because accessing/editing these items is now handled through a custom editing tab with a search box on the parent bucket item. Simply describing it as a search form falls far short of the sheer power contained in this neatly displayed form. The form itself has a host of power search commands whose state is indicated by a simplified and manageable icon within the text field once its been selected. The snappy response from a system that's querying over nine million items is really impressive. Clearly, lucene search indexes are the workhorses driving this beast. Removing this large data set from the typical editing field access means you'll be unable to edit these items in the way you're used to but this has also been nicely handled by allowing you to not only edit the items in the search results but also allows you to edit multiple items at one time. Another issue he brought up was how to handle the urls for these items that are being stored in an arbitrary folder structure. The way to handle this will depend on what the end user is comfortable with, but the examples he gave was to folder by date down to the second or even using an item's short ID and process the link with a custom Link Manager entry.   

There's really a whole lot more features that I'm unable to document while I'm furiously trying to process everything I'm seeing but the solution is really slick and very thorough. As you'd expect everything is componetized and is customizable. As a project it's been released on the Sitecore Marketplace so get it while its hot.

Include File References Patch Issue

$
0
0

*Update 2

I also found that there was an issue with the <using> statements as well that required an attribute to be added. When I opened the Configure-Assign (Insert Options) or tried to add from template, I would get the following exception:

The type or namespace name 'EventArgs' could not be found.

Once I added an arbitrary attribute to the <using> tag it was fine.

*Update 1

So Sitecore Support got back to me pretty quickly and told me that if I just add an attribute that makes the reference unique that's how to solve the issue. Here's the relevant code:

<?xml version="1.0"?><configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">	<sitecore>		<ui>			<references>				<reference comment="MyLibrary">/bin/MyLibrary.dll</reference>			</references>		</ui>	</sitecore></configuration>

The system I manage was originally built on Sitecore 5.3 and not all the latest and greatest methodologies are being used so when I have time I try to update the system. This time, while working to migrate all the customizations to the web.config to an include file I ran into a slight snag. My longtime friend and confidant, the ever charming System Exception or collegially known as the Yellow Screen of Death:

The type 'System.ComponentModel.IComponent' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

Usually they're not too difficult to root out but this one really confused me. I mean who doesn't have the 'System' library referenced. I'm pretty sure it's included by default. Anyway after finding nothing useful on google I was left to my only option. Go back to the original web.config and move the changes over line by line until I hit the issue.

Sure enough the issue resided with the <references> section. Now looking back I guess I should've guessed this would be the problem but hindsite, well, it just is what it is. I checked the rendered config on the show config page [/sitecore/admin/showconfig.aspx] and found that my inserted reference was showing up before the System.dll reference. Ah, that's the issue. It's not being included in the right order. Okay so I just need to put it last and I'll be fine.

Now typically when you patch config something you're telling it to insert before or after something but in this case there are no attributes to use to tell it where to insert. I know I read that I could tell it what position to insert into by giving it the position. It's not optimal but let's just see. To my chagrin, this didn't work. I checked the rendered config on the show config page again and it was showing up correctly but it was still throwing the error. Ok, so let's try and add an attribute like I've seen on John West's blog and insert it after that item using the attribute query. It was rendered right but still throwing the error.

With my options running out my only remaining choice was to just put it back into the web.config. Not what I wanted to do but it did work. For the time being I'll be adding a ticket to the support site to make sure Sitecore knows about it. Who knows maybe it's already a known issue.


Site Specific Insert Option Rules For Sitecore

$
0
0

I recently had the need to add a new template type for a single site on my multi-site system and wanted to be able to provide insert options for this template on content folders only in this site. The content folders are a global template and are shared across multiple sites so adding this new template type to the insert options of the content folder was not a viable option.

I know, from previous experience that rules engine would be great solution for this but there were a few additional constraints that I had which the default settings didn't provide for. I wanted to limit the scope of the rule to work only on items in that site. Using the default setup I would have to specify that the selected item would need to be an ancestor of the web root item but this isn't a preferable option since I would need to add this condition to every rule.

I wanted to store the rules at least in folders separated by site and I figured I could use this separation to know when to run the rules and save the added conditions. By default the rules for all insert options are stored in a single folder within Sitecore in /sitecore/system/rules/insert options/rules, which means that rules for all sites is in one folder. This isn't optimal because the list could potentially become very long and difficult to know which rules are for which sites. Also I wanted to store the rules for each individual site separately so that when a site is taken down and removed from the system all the rules would be easy to remove along with the site specific layouts, sublayouts and templates. I potentially could add subfolders to the insert rules. I would have to override the default processor so that it would look recursively into folders but I would also have to rely on finding the folder to match the site name which could be flaky since someone could misspell the folder. I decided to store the rules for the site below the site root at the same level at the home node for the site.

site specific rules folder

Now that I had decided where to store the rule I had to figure out the best way to apply them. I could either add the rules to the template or override the processor. Adding the rules to the template meant that it would be cluttered with rules from all sites and would require maintenance if/when sites were added or removed. I decided that the best option was to override the processor. This would allow me to query for the rules from the site start path by matching it to the current item and then add any insert options that are stored there.

I found that the pipeline processor uiGetMasters/RunRules is what needs to be overridden. I pulled the source from a decompiler and noticed that it was directly looking for the global folder. I dug a little deeper and found that its taking all the children of the folder and passing it to an overload of the GetRules methods that takes an IEnumerable<Item>. From here I knew that I still wanted to include the global items, I just wanted to add a few more things to the enumeration. I used the context item that is passed in with the mastersArgs and checked the ancestors for a site root node. If one is found then I look through its children for a rules folder and then check the rules folder for any insert option rules in its ancestors. I then add these to the list and then run rules like it was originally.

Here's the code for my override of the RunRules class: 

using System;using System.Collections.Generic;using System.Linq;using System.Text;using Sitecore.Pipelines.GetMasters;using Sitecore.Data.Items;using Sitecore.Diagnostics;using Sitecore.SecurityModel;using Sitecore.Rules.InsertOptions;using Sitecore.Rules;namespace MySite.Pipelines.GetMasters{	public class RunRules	{		protected string RulesFolder = "RulesFolder";		protected string InsertOptionsRule = "{664E5035-EB8C-4BA1-9731-A098FCC9127A}";		protected string Website = "WebsiteRoot";		// Methods		public void Process(GetMastersArgs args) {						//check if null			Assert.ArgumentNotNull(args, "args");			if (args.Item == null)				return;			//init start list			List<Item> insertRuleItems = new List<Item>();						//skip permissions			using (new SecurityDisabler()) {				//get the default rules				Item DefaultRules = args.Item.Database.GetItem("/sitecore/system/Settings/Rules/Insert Options/Rules");				if (DefaultRules != null)					insertRuleItems.AddRange(DefaultRules.Children);				//get any site specific rules.				Item SiteRoot = GetSiteItem(args.Item);				if (SiteRoot != null) {					Item RulesFolder = ChildByTemplate(SiteRoot, this.RulesFolder);					if(RulesFolder != null) {						var rules = from val in RulesFolder.Axes.GetDescendants()									where IsTemplateID(val, this.InsertOptionsRule)									select val;						if (rules.Any())							insertRuleItems.AddRange(rules);					}				}			}			//if you find any rules then use them.			if (insertRuleItems.Any()) {				//setup context				InsertOptionsRuleContext ruleContext = new InsertOptionsRuleContext {					Item = args.Item,					InsertOptions = args.Masters				};				//get those rules				RuleList<InsertOptionsRuleContext> rules = RuleFactory.GetRules<InsertOptionsRuleContext>(insertRuleItems, "Rule");				if (rules != null) {					rules.Run(ruleContext);				}			}		}		/// <summary>		/// This gets the first child matching the provided template name		/// </summary>		public static Item ChildByTemplate(Item Parent, string Templatename) {			IEnumerable<Item> children = from child in Parent.GetChildren() 										 where child.TemplateName.Equals(Templatename) 										 select child;			return (children.Any()) ? children.First() : null;		}		/// <summary>		/// this checks if the current item's template id matches the given template id		/// </summary>		protected static bool IsTemplateID(Item item, string targetTemplateId) {			return item.TemplateID.ToString().Equals(targetTemplateId);		}		/// <summary>		/// this gets any website root item from the ancestors of the given item, if there is one.		/// </summary>		protected Item GetSiteItem(Item item) {			return item.Axes.SelectSingleItem("ancestor-or-self::*[@@templatename='" + this.Website + "']");		}	}}


 
I wanted to note that I embedded the insert options under the rules folder because I wanted to leave the door open to the possibility that more types of rules other than insert options could be supported for the sites.

After I'd implemented this I found the All About Insert Options article by John West which was a good source of information as well.

Adding Fields To The Link Database

$
0
0

This article details how to add fields to the Link Database, how Sitecore uses the FieldTypes.config to add items to the Link Database, issues with the DelimitedField type and how to create your own custom type to use in the FieldTypes.config.

Adding Fields to the Link Database

Setting up new or custom fields is pretty straight forward. In my initial research I found this article by John West to be quite helpful. There's two parts to getting a field setup with the link db: setting up the config entries and rebuilding the link db. You can add entries to the FieldTypes.config file or your own custom config file if you've got one setup. The config entries are laid out like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">	<sitecore> 		<fieldTypes> 			<fieldType name="Field Name" type="Namespace.Class, BinaryName" /> 		</fieldTypes> 	</sitecore> </configuration> 

Then you've got to rebuild the Link Database by going into the Sitecore 'start' menu and opening up the control panel. Select the 'Database' section and then click on the 'Rebuild the Link Database' option. You'll get to pick which databases to run against. Typically you'll want to choose 'Master' or 'Web'. This will go through your content tree and check all fields so it may take a while. It may also fail so be prepared for that. Future incremental changes to the field entries will be handled by events defined in the Sitecore config.

How Sitecore uses the FieldTypes.config

The config entry has two attributes: 'name' and 'type'. The 'name' value should match your field name since it will be used to tell Sitecore which fields it should handle. The 'type' value on the other hand doesn't have to match your class. The 'type' does have a few requirements that aren't detailed anywhere I'm aware of. I'll list them below and explain in more detail after. The 'type' is used largely to reflect how the value is stored, not how is displayed. Based on all existing sitecore types it appears this 'type' should be a subclass of the Sitecore.Data.Fields.CustomField. The field also needs to have a constructor that takes a parameter whose type is Sitecore.Data.Fields.Field. This class will also need override and implement the CustomField's ValidateLinks method since it is an empty method.

So to better explain how Sitecore is using this config definition to add the field to the Link Database let's use the item:saved event. When you edit, for example, a Treelist field, and click the save button, the value is stored as a pipe delimited list of item ids. This event also triggers the item:saved pipeline event. By default, Sitecore's FieldTypes.config uses a Sitecore.Data.Fields.MultilistField to manage the Treelist field. This first calls the method Sitecore.Links.ItemEventHandler.OnItemSaved. It begins by using the Sitecore.Links.ItemLinks class to loop through all the fields on the item you're saving and checks to see if each field has an entry in the FieldTypes.config. With those that do, the entry from the config is used to create an object of the type specified with a constructor that takes a single Sitecore.Data.Fields.Field argument by way of the Sitecore.Reflection.ReflectionUtil. If the field provided doesn't have a constructor that matches that condition, it will be skipped. After the list of fields is compiled they are looped through again and their ValidateLinks method is called. This method will add the links it finds to a list that will be then added to the Link Database.

Some Issues with the Sitecore.Data.Fields.DelimitedField

While I was working to get the Field Suite fields added to the Link Database, I attempted to use the existing FieldTypes.config entries. For example I used the Sitecore.Data.Fields.MultilistField for the Field Suite Treelist since it was used for the Sitecore Treelist field. What I found when I began debuggging was that when I had set the source field for the Field Suite Treelist it would not be added to the link db. I pinpointed the issue being the definition for the Sitecore.Data.Fields.DelimitedField.GetDatabase() method. The logic checks the 'Source' field for a 'DataSource'. If that is found it will also try to find the 'Databasename' from the 'Source' field which makes sense. The problem is that if it doesn't find a 'Databasename' it fails to fallback on the default InnerField.Database value. It will instead return null even though there is a valid value to return. The only way to work around this is to extend the Delimited Field and override the ValidateLinks and GetDatabase methods and return the InnerField.Datbase by default. I later found an article by Alex Shyba that details this workaround. I've also submitted a support ticket to sitecore to let them know of the issue so it'll likely be patched in future releases.

Adding a Custom Type to the FieldType.config

Since I'm currently using the Field Suite fields and I needed to patch the previous issue so I began looking into the fields that Sitecore was using. I ended up building two classes to support the fields: IDField and IDListField. For the IDField I researched how the Sitecore.Data.Fields.ReferenceField and LookupField worked since they both managed fields that stored a single item id as a string. For the IDListField I researched the Sitecore.Data.Fields.Multilist and DelimitedFields. I largely used what I found in both sets of classes and trimmed out what I didn't need and simplified their logic. There are really only a few things that your custom class will need which I've explained above but want to reiterate:

  • It should be a subclass of the Sitecore.Data.Fields.CustomField class.
  • It should override the CustomField ValidateLinks method.
  • It should have a constructor that takes a Sitecore.Data.Fields.Field object as an argument.

Sitecore Caching Manager Updated

$
0
0

I've recently updated the Caching Manager module on the Sitecore Marketplace. The improvements were focused on the UI which I've made to mimic the native design of the content editor. It's a lot more compressed and easy to use. The different cache regions are broken into separate tabs and parts of the form are broken into sections. You can collapse the sections by double clicking on the title bar like you can in the content editor. There's use of Sitecore's icons and the buttons are better defined and the font size is smaller so that more fits on a screen.

There was also an update by Mark Ursino who merged all the functionality into one page for an even simpler deployment.

Overall I feel that this provides a much more understandable layout. I'm not sure how everyone else is using it but if you like the earlier version you will definitely like the update.

Also to note the project source has moved since the shuttering of the Trac site and it now lives on GitHub.

Here's some screenshots of the new layout:

Search Cache

Site Cache

Database Cache

Access Result Cache

Miscellaneous Cache

Sitecore Data Importer Updated

$
0
0

I recently spent some good quality time working to update the Data Importer Module. It's an incredibly useful tool and I wanted to address some feedback about it so that users can better understand what it can do and how you use it.

The module was originally developed to import content from existing websites into Sitecore. I had to pull all types of information from raw SQL databases used by other CMS'. It can save a lot of time and effort doing these imports because you create the import definitions they are stored in the Sitecore content tree and you can rerun it as needed. It helps when you realize that you need to tweak the information you've just imported and delete and re-import the content. It's also useful for when you've got to import a dataset initially to start a project and then import more later to keep up to date with what's been added to the existing site.

So for changes, I went through the module and updated UI so that it uses Sitecore's native design which should make it feel more at home. I merged the Sitecore import and SQL import dropdowns and now all import types are run in the same way and only the implementation of the class differentiates them. I also revamped the code a lot. I added better messaging so that users know if they've forgot to fill out fields and I dealt with a lot of choke points to catch exceptions caused by missing information that would force you to restart the module and otherwise become frustrated with using it. I also put a lot more thought into making the system more modular so that it's much more simple to add custom data providers and custom field types. Altogether it should be much easier to customize. As a final addition I added a import mapping for MySQL databases after building it for an example in one of the videos. There are MySql.Data binary files that are installed with the package to support this.

To ease the learning curve I also created some walkthrough videos at the advice of good friend, Mark Ursino, to help get you familiar with the module and how to work with it. Here's the rundown:

If you're interested in working with the source code it's moved to GitHub since Trac was shut down.

Here's a screenshots of the module's new UI:

data importer

Browser Language Detection In Sitecore

$
0
0

I recently had a request to detect the browser language for a multilingual site I support. Now this certainly isn't anything new and here's the canonical/obligatory John West post written about it. I'm not trying to rewrite what he's done. My solution is different and deals with different needs. The requirements I had were:

*set the context language to the browser language if the site has item versions in that language
*if the user changes the language through a dropdown selector maintain the new language settings

These seemingly innocuous requirements add more complexity than you'd expect. The challenge was really around maintaining the language once it's been set and to not continue changing to the browser settings. I accomplished this by only using the browser settings if the lan

guage cookie had not been set. This makes the detection more passive in that it's only really determined on the first page load. It prevents a lot of jumpy behavior between languages when someone browses back to the homepage which doesn't contain the language code in the url.

So to implement this I overrode the LanguageResolver in my include config:

<processor type="Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel">	<patch:attribute name="type">MyLibrary.Pipelines.HttpRequest.LanguageResolver, MyLibrary</patch:attribute></processor>

Next is the class that rewrites the functionality of the stock Sitecore LanguageResolver. I typically don't extend these classes since they may change over time and they also contain so many private methods that I basically end up having to copy most of it anyway. Also bear in mind that my system stores the supported languages for a site within the site's settings so I can compare against that stored list. This code was modified from my version to check to see if the homepage has a version of the language. This could be volatile since a site might not support a language just because there is a version of the homepage in that language. So you should consider modifying the GetBrowserLang() method to query the correct language versions for each of your sites if you can't make those assumptions.

This also uses a method of retrieving the home item for a site that assumes the SiteContext.RootPath and SiteContext.StartItem fit neatly together to make a sitecore item path. Make sure this is going to work for your system.

namespace MyLibrary.Pipelines.HttpRequest {		using Sitecore;	using Sitecore.Diagnostics;	using Sitecore.Globalization;	using System;	using System.Web;	using Sitecore.Pipelines.HttpRequest;	using System.Threading;	using System.Linq;	using Sitecore.Data.Managers;	using Sitecore.Data;	using Sitecore.Data.Fields;	using System.Collections.Generic;	using Sitecore.Sites;	using Sitecore.Data.Items;	using Sitecore.Security.Domains;	using Sitecore.Web.UI.Sheer;	using Sitecore.Layouts;	using Sitecore.Configuration;		public class LanguageResolver : HttpRequestProcessor {		private Language GetQStringLang(HttpRequest request) {			Language language;			string str = request.QueryString["sc_lang"];			if (string.IsNullOrEmpty(str))				return null;						if (!Language.TryParse(str, out language))				return null;						return language;		}		private Language GetBrowserLang(HttpRequest request) {						//get list of browser langs			string[] bLangs = request.UserLanguages;			if (bLangs == null || !bLangs.Any())				return null;			//get the list of supported langs for this site			Item homeItem = GetHomeItem();			if (homeItem == null)				return null;			//loop through browser languages and see if the site supports any			List<string> langIDs = new List<string>();			foreach (string s in bLangs) {				//split any browser weighting that is applied ie: nl;q0.5				string[] x = s.Split(new [] {";"}, StringSplitOptions.RemoveEmptyEntries);				string langCode = (x.Any()) ? x.First() : string.Empty;				if (string.IsNullOrEmpty(langCode))					continue;								//try to get the language				Language language = null;				if (!Language.TryParse(langCode, out language))					continue;								//then try to get the language item id from the language or two letter iso code				ID langID = LanguageManager.GetLanguageItemId(language, Sitecore.Context.Database);				if (ID.IsNullOrEmpty(langID)) {					//sometimes the language found is slightly different than official language item used in SC					language = LanguageManager.GetLanguage(language.CultureInfo.TwoLetterISOLanguageName);					langID = LanguageManager.GetLanguageItemId(language, Sitecore.Context.Database);				}				//checks to see if the language item is a valid sitecore item				if (ID.IsNullOrEmpty(langID))					continue;				//if you've got a version in this language then use that language				Item langVersion = Sitecore.Context.Database.GetItem(homeItem.ID, language);				if (langVersion != null)					return language;			}								return null;		}		private Language GetLanguageFromRequest(HttpRequest request) {			//check querystring			Language qsLang = GetQStringLang(request);			if (qsLang != null)				return qsLang;			//only check if you're on the frontend and the cookie hasn't been set			if (IsNotSitecore() 				&& Sitecore.Context.PageMode.IsNormal				&&!IsLanguageSetFromCookie()) {				//then check browser language settings				Language browserLang = GetBrowserLang(request);				if (browserLang != null)					return browserLang;			}			//default			return Context.Data.FilePathLanguage;		}		public override void Process(HttpRequestArgs args) {			Assert.ArgumentNotNull(args, "args");			Language languageFromRequest = this.GetLanguageFromRequest(args.Context.Request);			if (languageFromRequest != null) {				Context.Language = languageFromRequest;				Tracer.Info("Language changed to "" + languageFromRequest.Name + "" as the query string of the current request contains language (sc_lang).");			}		}		protected static Item GetHomeItem() {			SiteContext sc = Sitecore.Context.Site;			return Sitecore.Context.Database.GetItem(string.Format("{0}{1}", sc.RootPath, sc.StartItem));		}		protected static bool IsNotSitecore() {			return (!Sitecore.Context.Domain.Name.ToLower().Contains("sitecore"));		}		protected static bool IsLanguageSetFromCookie() {			string c = Sitecore.Web.WebUtil.GetCookieValue(Sitecore.Context.Site.GetCookieKey("lang"));			return (!String.IsNullOrEmpty(c));		}	}}

Linq to Sitecore

$
0
0

I wanted to write up an article that elaborates on a presentation I gave on some but not all of the new features that will be upcoming in Sitecore 7. Mostly I'm going to focus on the updates around Lucene search but I'll also touch on some of the other features as well. This will be a longer article so be warned.

I'd also like to preface this article by saying that one of the driving forces behind much of the change in Sitecore 7 is an increasing need to support large data sets. And don't dismiss this release thinking that there isn't a lot of value if you're system isn't that large. You don't need to be very big to benefit from a faster search utility or the new features it includes since it doesn't really take much more than a few thousand items to really find Lucene beneficial.

What's Changing

The new system is highly geared toward improved search and as a shining example of that is how the Item Buckets are now baked into the content editor. Buckets are a great way to manage large sets of content considering the suggested limit of around 100 child items per item in Sitecore. But let's also consider that aside from massive quantities of data, not all content needs to be stored structurally in a tree. There's plenty of use cases where you just need to store information and will deal with selecting and displaying it based on the value of the fields and not on how they're stored. to assist with this, you'll see that there are new editor tabs attached to what seems like all items in the tree, that allow you to search for content in the context of the item selected. They've also included new field types that allow users to search for items which is great because item lists can grow long fast inside those tiny [treelist, droplink etc.] windows.  

Hot Swappable Indexes

One of the major changes to Lucene is that you can now configure it to build the index in a separate folder so that it doesn't interfere with the existing index. If you've ever found yourself with no results or a filelock exception due to Lucene deleting the index before rebuilding, you'll be interested in this. It's not setup by default but you can configure it by changing the configuration setting for the Lucene Provider. Here's how it will look:

<indexes hint="list:AddIndex"><index id="content_index" type="Sitecore.ContentSearch.LuceneProvider.SwitchOnRebuildLuceneIndex, Sitecore.ContentSearch.LuceneProvider"> 

The way this works is that the two folders for the index will be named like so: index_folder, index_folder_sec. The "_sec" stands for secondary. The provider will use the most up to date folder and will rebuild into the other. It should be noted that after you make the change to use the SwitchOnRebuildLuceneIndex, you will need to perform a rebuild twice on the index as per the provided documentation.

Interchangeable Indexes

The next set of updates are regarding the indexing source itself. Lucene can be now replaced with an implementation of SOLR that can be setup and configured remotely and queried in it's vastness very quickly. I still have questions about if the Lucene indexer can be configured to run on a separate machine in a similar vein but I don't currently know. This of course will require some training time to understand how to set that up but if you're building something that needs that kind of scale I'd guess you'll just be glad it's possible.

The new API

There's also a new search API "Sitecore.ContentSearch" that's unofficially being called "Linq to Sitecore" and includes an "ORM" feature that allows you to cast your search results to your existing class-model structures (assuming you have one) such as Custom Item Generator or Glass Mapper. Actually the casting is similar to that of the Glass Mapper in how you decorate fields to identify which fields should be popuplated from corresponding fields in the search result. You may now have questions, as I do, about where Alex Shyba's amazing AdvancedDatabaseSearch/Sitecore.SearchContrib stands. I think this may end up replacing it but again I don't know. It seems like this new search has in many ways learned from what he did and taken it a bit further with it's syntax. I did find this bit in the documentation explaining that Sitecore does want to begin phasing out the older search methods:

"The Sitecore.Data.Indexing API was deprecated in Sitecore CMS 6.5 and in 7.0 it has been completely removed. The Sitecore.Search API works with Sitecore CMS 6 and 7, but is not recommended for new development in version 7.0. Developers should use the Sitecore.ContentSearch API when using Sitecore Search or Lucene search indexes in Sitecore 7.0."

Configuration and Code Samples

Okay so now that I've covered a bit about what's happening I'll get the "how" side of things. I wanted to see how much effort was involved in using the new search features to see if I would be completely relying on it for all my data querying needs or just supplementing the existing methods. I started by setting up a local instance and creating some templates, content items and a sublayout. The next piece is to change the search configuration file located at /App_Config/Includes/Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration to tell it to store my field values in the index by changing storageType="NO" to storageType="YES":

<fieldType fieldTypeName="single-line text" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.String"   settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" />

With that set you'll then need to run a rebuild of the index which is now available in many ways but I used the one available through the Control Panel in the Content Editor. Then I found an example of how to query from the "Developer's Guide To Item Buckets and Search" document provided to me by Sitecore. I've altered it a bit to show how to make the query more generic for use in returning results of any given type and to also see what it's limits were:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Security;
using Sitecore.Data;
using Sitecore.Data.Items;

namespace TestsiteSC7.Web.layouts {
	public partial class PeoplePage : System.Web.UI.Page {

		#region Utility Methods

		protected StringBuilder log = new StringBuilder();
		protected void Log(string title, string message) {
			log.AppendFormat("{0}:{1}<br/>", title, message);
		}

		#endregion Utility Methods

		#region Page Events

		protected void Page_Load(object sender, EventArgs e) {

			//show indexes
			//ShowIndexes();

			//demo hydration
			//GetPeople();

			//show all fields
			GetResult();

			//write log
			ltlOut.Text = log.ToString();
		}

		#endregion Page Events

		#region Search Methods

		protected void ShowIndexes() {
			Dictionary<string, ISearchIndex> indexes = ContentSearchManager.SearchConfiguration.Indexes;
			Log("Number of Indexes", indexes.Count.ToString());
			foreach (KeyValuePair<string, ISearchIndex> p in indexes)
				Log("Index", p.Key);
		}

		protected void GetPeople() {
			List<Person> people = PerformSearch<Person>();
			Log("Number of People", people.Count().ToString());
			foreach (Person i in people) {
				Log("name", i.Name);
				Log("address", i.Address);
				Log("occupation", i.Job);
				Log("_fullpath", i.Path);
				Log("parsedlanguage", i.ParsedLanguage);
				Log("_template", i.template);
				Log("_name", i.OtherName);
				Log("", "");
			}
		}

		protected void GetResult() {
			//filters down to a single result
			IEnumerable<MySearchResultItem> results = PerformSearch<MySearchResultItem>()
				.Where(a => a.Name.Equals("Jim"));
			Log("Number of results", results.Count().ToString());
			foreach (MySearchResultItem m in results) {
				Log(m.Name, string.Empty);
				foreach (KeyValuePair<string, string> f in m.fields)
					Log(f.Key, f.Value);
			}
		}

		private List<T> PerformSearch<T>() where T : AbstractResult, new() {
			//the fields are managed in:
			//			/App_Config/includes/Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration
			//this index is managed in:
			//			/App_Config/includes/Sitecore.ContentSearch.Lucene.Index.Master.config
			//this settings for indexing in:
			//			/App_Config/includes/Sitecore.ContentSearch.config
			var index = ContentSearchManager.GetIndex("sitecore_master_index");
			using (var context = index.CreateSearchContext(SearchSecurityOptions.EnableSecurityCheck)) {
				var queryable = context.GetQueryable<T>()
					.Where(a => a.template.Equals(AbstractResult.TemplateID));

				//work with items inside the context
				T instance = new T();
				//display count
				instance.HandleResults(queryable);

				//need to send to list before the using outside the context
				return queryable.ToList();
			}
			//exception thrown when working with IQueryable object outside of context
			//Accessing IndexSearchContext after Commit() called
		}

		#endregion Search Methods
	}

	public interface IResult {
		string template { get; set; }
		string Name { get; set; }
	}

	public abstract class AbstractResult : IResult {

		public string Name { get; set; }
		[IndexField("_template")]
		public string template { get; set; }

		public static readonly string TemplateID = "fc110b3df82c4b0eabc580a4f185ea1d";

		public abstract void HandleResults(IQueryable<AbstractResult> results);
	}

	public class Person : AbstractResult {

		#region properties

		[IndexField("_name")]
		public string OtherName { get; set; }

		public string ParsedLanguage { get; set; }

		[IndexField("_fullpath")]
		public string Path { get; set; }

		public string Address { get; set; }

		[IndexField("occupation")]
		public string Job { get; set; }

		#endregion properties

		public override void HandleResults(IQueryable<AbstractResult> results) {
			IEnumerable<Person> people = (IEnumerable<Person>)results;
			HttpContext.Current.Response.Write("People count:" + people.Count().ToString());
		}
	}

	public class MySearchResultItem : AbstractResult {

		#region properties

		// Fields
		public readonly Dictionary<string, string> fields = new Dictionary<string, string>();
		// Will match the myid field in the index
		public Guid MyId { get; set; }
		public int MyNumber { get; set; }
		public float MyFloatingPointNumber { get; set; }
		public double MyOtherFloatingPointNumber { get; set; }
		public DateTime MyDate { get; set; }
		public ShortID MyShortID { get; set; }
		public ID SitecoreID { get; set; }

		// Will be set with key and value for each field in the index document
		public string this[string key] {
			get {
				return this.fields[key.ToLowerInvariant()];
			}
			set {
				this.fields[key.ToLowerInvariant()] = value;
			}
		}

		#endregion properties

		public override void HandleResults(IQueryable<AbstractResult> results) {
			IEnumerable<MySearchResultItem> people = (IEnumerable<MySearchResultItem>)results;
			HttpContext.Current.Response.Write("Results count:" + people.Count().ToString());
		}
	}
}

What to Expect

I will state for the record that it doesn't take much effort to get results. In fact you may get more than you bargained for. By default you'll get all results in the index, which include everything and the kitchen sink. I ended up with standard values items as results even after filtering by template id. The key thing to note here is that Lucene is returning results and Sitecore is then using the field values to fill properties on the class you provide. It will fill your object with data whether or not it makes sense to. I guess this is no different than other ORMs but keep in mind that you will be best served by filtering out the results thoughtfully. What I will recommend is that you create several small indexes that are focused just on the template/content types you're trying to retrieve otherwise you may have other data comingling into your results. Making smaller indexes will also keep the amount of memory and CPU usage to a minimum when rebuilding indexes and will limit the scope of what is affected.

The Code Walkthrough

Now I'll delve a bit into the code itself. I've created an interface IResult, an abstract class AbstractResult and two classes: Person and MySearchResultItem (MySearchResultItem is actually pulled from the documentation). The use of the interface was to require that all results have certain fields. The abstract class was to give enough substance to a generic result class to allow the search method to call a method with the result set.

Sitecore is going to use the search result values to populate your classes after a search is run and you'll notice that on the Person class there are fields decorated to identify which values should be put into what property. You also won't have to add the decoration if the class property matches the name of a field in a result. I haven't yet tested how field names with spaces will be handled but I imagine that the spaces will just be removed or replaced.  Here's some different examples:

//setting Sitecore provided fields
[IndexField("_fullpath")]
public string Path { get; set; }

//fills itself by matching names
public string Address { get; set; }

//populating a template field into an unrelated property name
[IndexField("occupation")]
public string Job { get; set; }

It will help to become acquainted with the fields you have available to you. The MySearchResultItem has an example of property called fields that will get populated by Sitecore with all the field names/values. You can use this as a guide to see what's available to you. By default, it contains some Sitecore information like template id and item path among others:

public readonly Dictionary<string, string> fields = new Dictionary<string, string>();
// Will be set with key and value for each field in the index document
public string this[string key] {
	get {
		return this.fields[key.ToLowerInvariant()];
	}
	set {
		this.fields[key.ToLowerInvariant()] = value;
	}
}

When I got to actually operating the search, my goal was to push as much generic search functionality into a single method and allow the calls for each type to be extremely simple. Here's how the search is initiated in my examples.

List<Person> people = PerformSearch<Person>();
IEnumerable<MySearchResultItem> results = PerformSearch<MySearchResultItem>();

The PerformSearch is simple and reusable. It also takes a Generic type that is of type AbstractResult and has an empty constructor. Here's the signature for that method:

private List<T> PerformSearch<T>() where T : AbstractResult, new() {}

It starts by getting the index and setting up a using context. In this case the security is enabled so Sitecore will filter results with permissions in mind:

var index = SearchManager.GetIndex("sitecore_master_index");
using (var context = index.CreateSearchContext(SearchSecurityOptions.EnableSecurityCheck)) {

I'm using the built in master database index and I've filtered by the template id provided by the abstract class. Which does work well. The search and filter largely occur on a single line, which is also really quite impressive.

var queryable = context.GetQueryable<T>().Where(a => a.template.Equals(AbstractResult.TemplateID));

The GetQueryable<T> is the part where Sitecore will fill your class type with the result values and the .Where() is the Linq to Sitecore which does all the work in the Lucene/SOLR index. You will be able to use the alternate syntax as well:

var queryable = from T a in context.GetQueryable<T>
		where a.template.Equals(AbstractResult.TemplateID)
		select a;

The beauty here is that you're offloading a lot of the filtering directly to Lucene instead of having to sort/filter/trim a humungous list of items in the .NET then cache it. You should know that not all queryable methods are implemented but what is available certainly isn't a short list. The supported methods are: Where, Select, Take, Skip, Page, OrderBy, OrderByDescending, ThenBy, ThenByDescending, First, FirstOrDefault, ElementAt, Last, LastOrDefault, Single, SingleOrDefault, Any and Count. It's definitely got some teeth.

I also wanted to point out that you're really only able to manipulate the search result in it's current form while you're inside the using context. If you try to return an IEnumerable result and then try to loop through the results you'll get an exception like:

Accessing IndexSearchContext after Commit() called

This is likely because of how IEnumerable behaves in that the items are only queried when the GetEnumerator is called and in this case the objects that are used to crunch the data have been disposed of through the IDisposable using context. You do have two options to deal with this. You'll either want to pass in a handler method or have one available on your Type(T) class, as I did or you can convert the result set to a List<T>. This will force the data to be crunched while the context is still available. Here's the example of the handler being called on the AbstractResult class which is implemented separately in the subclasses.

T instance = new T();
instance.HandleResults(queryable);

I also return the result set as a List and do work on the results in the calling method.

return queryable.ToList();

My results are then iterated through and have some of the field data printed to a log for display:

Log("Number of People", people.Count().ToString());
foreach (Person i in people) {
	Log("name", i.Name);
	Log("address", i.Address);
	Log("occupation", i.Job);
	Log("_fullpath", i.Path);
	Log("parsedlanguage", i.ParsedLanguage);
	Log("_template", i.template);
	Log("_name", i.OtherName);
	Log("", "");
}

Hopefully this new search, with it's simplified API and multi-foldered index will be big enough improvements to gain the trust of the development community and get rapid adoption. I think you'll be able to rely on it whether you've got a small or large system, as it's quite fast and powerfull.

One More Thing

There is also a nice update to Sitecore regarding the caching properties on sublayouts. You will be able to configure the cache to clear when the index is updated. This may need some customization for multi-site instances since you (and I) may not want all html cache removed when you publish a single item.

Synopsis

Sitecore has now brought Lucene out of the shadows and integrated it into the system as a much more visible and reliable utility to build your solutions on. The new API is definitely a breath of fresh air and will hopefully, lower the barrier for entry for developers to configure and use it and allow us to build much more efficient applications.

Field Suite Images Field

$
0
0

I've been using the FieldSuite, which was built by Sitecore MVP, superhero and all around good guy, Tim Braga, for about 8 months now and have come to really like a lot of its featues but there was one field that I hadn't used: The "Field Suite Images Field". When I thought I'd finally look into it, the documentation had a few details about customizing the control but not about using the field which gave me the opportunity to do a quick write-up. So first I tinkered with it until I figured out how to get it working and then figured it would be a good idea to write up a quick post about it.

The basic premise is an extension of the built-in Sitecore image field which allows you to select a single image. With this Field Suite Images Field you can select multiple images, albeit,  one at a time.

First you'll want to have a media library folder with some images in it. I've also created a nested folder to show that it support it.

media library structure

When you add the field to a template, you'll want to set the field source to a media library folder where you'll want it to pull images from.

field definition

Once you've setup the template, next you'll create an item in the content tree and you'll see that the Images Field has a row of buttons to help manage the value.

field empty

If you click on the "Link Item" button this allows you to use a drop tree field to select an image.

field select item

After you select an image you click save and you'll return to the field value with the image set.

field save

field single selected

If you continue to "Link" more images you'll see them appear in a horizontal list with their publish status (another Field Suite feature) below.

field multiple selected

From here you can select any individual item in the field and edit, go to the item, remove the item or reorder the item in the list.

field image select

When it comes time to access the field value, it's stored as a pipe delimited list of Sitecore ID's so it should be fairly easy to work with.

field raw value


Sitecore Custom User Properties

$
0
0

In a recent Sitecore build I had to store the pages that a user saved as a "Favorite" throughout the site. The site was an intranet so everyone had a user within Sitecore, which helped. The next question was where to store the information. I wanted it to be stored along with local user data but wasn't sure if I was going to need to extend the user object and create extra fields or if I could leverage the existing structure. I ended up looking through a lot of the user properties and scouring the cookbooks and eventually I found what I was looking for in the security api cookbook: Custom Properties.

You can access and set custom properties on a Sitecore user. It stores these custom properties in a NameValueCollection so you can get and set the values with a key. With that settled, I knew I'd only need to be storing the item ID's for each page. I ended up creating a wrapper class to manage the key for the favorites and to help manage the string value as a comma-delimited list.

Here's the code I used:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using Sitecore.Data;using Sitecore.Data.Items;using Sitecore.Security.Accounts;namespace MyLibrary {	public static class UserExtensions {		public static readonly string custPropKey = "user-favorites";		public static readonly string custPropDelim = ",";		#region Add Favorite		public static void AddFavorite(this User u, ID pageID) {			AddFavorite(u, pageID.ToString());		}		public static void AddFavorite(this User u, Item page) {			AddFavorite(u, page.ID);		}		public static void AddFavorite(this User u, string pageID) {			IEnumerable<string> f = GetFavorites(u);			List<string> l = f.ToList();			//skip if already added			if (l.Contains(pageID))				return; 			l.Add(pageID);			SetFavorites(u, l);		}		#endregion Add Favorite		#region Remove Favorite		public static void RemoveFavorite(this User u, ID pageID) {			RemoveFavorite(u, pageID.ToString());		}		public static void RemoveFavorite(this User u, Item page) {			RemoveFavorite(u, page.ID);		}		public static void RemoveFavorite(this User u, string pageID) {			IEnumerable<string> f = GetFavorites(u);			List<string> l = f.ToList();			if (!l.Contains(pageID))				return; 			l.Remove(pageID);			SetFavorites(u, l);		}		#endregion Remove Favorite		#region Set Favorites		public static void SetFavorites(this User u, List<string> pageIDs) {			StringBuilder sb = new StringBuilder();			foreach (string s in pageIDs) {				if (sb.Length > 0)					sb.Append(custPropDelim);				sb.Append(s);			}			u.Profile.SetCustomProperty(custPropKey, sb.ToString());			u.Profile.Save();		}		#endregion Set Favorites		#region Get Favorites		public static IEnumerable<string> GetFavorites(this User u) {			string s = u.Profile.GetCustomProperty(custPropKey);			IEnumerable<string> split = s.Split(new string[] { custPropDelim }, StringSplitOptions.RemoveEmptyEntries).ToList();			return split;		}		public static IEnumerable<ID> GetFavoriteIDs(this User u) {			IEnumerable<string> s = GetFavorites(u);			return (s.Any()) ?				from string id in s				where ID.IsID(id)				select ID.Parse(id) 				: Enumerable.Empty<ID>();		}		public static IEnumerable<Item> GetFavoriteItems(this User u) {			IEnumerable<ID> s = GetFavoriteIDs(u);			List<Item> returnList = new List<Item>();			if (s.Any()) {				var items = from ID i in s							select Sitecore.Context.Database.Items[i];				foreach(Item i in items){					if(i != null)						returnList.Add(i);				}			}			return returnList;		}		#endregion Get Favorites		#region Is Favorite		public static bool IsFavorite(this User u, string pageID) {			IEnumerable<string> s = GetFavorites(u);			List<string> l = s.ToList();			return l.Contains(pageID);		}		public static bool IsFavorite(this User u, ID pageID) {			return IsFavorite(u, pageID.ToString());		}		public static bool IsFavorite(this User u, Item page) {			return IsFavorite(u, page.ID);		}		#endregion Is Favorite	}}

Now knowing this and looking back, there's a lot of other useful things you can store for a user to help manage users who access different sites but end up in the same extranet security domain. There's also a lot of registration information that can be stored without having to modify anything. Here's to forward thinking in design.

Sitecore Manager and Helper Classes

$
0
0

As a follow up to my Sitecore Util Classes post, I randomly did a search through the Sitecore 6.4.1 kernel for Manager and Helper classes and as it turns out this time I hit a gold mine. Like my previous article I'll only touch on them briefly because there's just too many to go into any real depth but I wanted to at least put it out on the internets to raise a little awareness on these hidden gems in Sitecore. I left out any Interfaces, Abstract classes or classes without any public methods. In total I found 55 manager classes and 15 helper classes.

Manager Classes

Sitecore.Caching.CacheManager
public static void ClearUserProfileCache(string userName)
public static Cache[] GetAllCaches()
public static Cache FindCacheByName(string name)
public static HtmlCache GetHtmlCache(SiteContext site)

Sitecore.Data.Archiving.ArchiveManager
public static Archive GetArchive(string archiveName, Database database)
public static int GetArchiveCount(Database database)
public static List<Archive> GetArchives(Database database)

Sitecore.Data.DataManager
public DataUri[] GetItemsInWorkflowState(WorkflowInfo info)
public WorkflowInfo GetWorkflowInfo(Item item)
public Item[] SelectItems(string query)

Sitecore.Data.Fields.FieldTypeManager
public static CustomField GetField(Field field)
public static FieldType GetFieldType(string name)

Sitecore.Data.Managers.HistoryManager
public static HistoryEntryCollection GetHistory(Database database, DateTime from, DateTime to)
public static void RegisterItemCreated(Item item)
public static void RegisterItemDeleted(Item item, ID oldParentId)

Sitecore.Data.Managers.IndexingManager
public static string GetUpdateJobName(Database database)
public static void UpdateIndex(Database database)

Sitecore.Data.Managers.ItemManager
public static Item AddFromTemplate(string itemName, ID templateId, Item destination)
public static bool DeleteItem(Item item)
public static ChildList GetChildren(Item item)
public static VersionCollection GetVersions(Item item, Language language)

Sitecore.Data.Managers.LanguageManager
public static Language GetLanguage(string name, Database database)
public static bool IsLanguageNameDefined(Database database, string languageName)
public static bool RegisterLanguage(string name)
public static Language DefaultLanguage

Sitecore.Data.Managers.TemplateManager
public static bool ChangeTemplate(Item item, TemplateChangeList changes)
public static Template GetTemplate(ID templateId, Database database)
public static TemplateDictionary GetTemplates(Database database)
public static bool IsFieldPartOfTemplate(ID fieldId, Item item)
public static bool IsTemplate(Item item)

Sitecore.Data.Managers.ThemeManager
public static string GetIconImage(Item item, int width, int height, string align, string margin)
public static string GetImage(string path, int width, int height)
public static string GetLanguageImage(Language language, Database database, int width, int height)

Sitecore.Data.Proxies.ProxyManager
public static Item CopyVirtualTreeRoot(Item item, Item target)
public static Item GetItem(ID itemId, Language language, Sitecore.Data.Version version, Database database)
public static Item GetRealItem(Item item, bool preserveState)
public static bool HasVirtualItems(Item item)

Sitecore.Data.Serialization.Manager
public static void DumpItem(Item item)
public static void DumpRole(string rolename)
public static void DumpTemplates(Database database)
public static Item LoadItem(string path, LoadOptions options)
public static Role LoadRole(string path)

Sitecore.Data.StandardValuesManager
public static string GetStandardValue(Field field)
public static bool IsStandardValuesHolder(Item item)

Sitecore.Data.Validators.ValidatorManager
public static ValidatorCollection BuildValidators(ValidatorsMode mode, Item item)
public static void UpdateValidators(ValidatorCollection validators)
public static void Validate(ValidatorCollection validators, ValidatorOptions options)

Sitecore.Eventing.EventManager
public static void QueueEvent<TEvent>(TEvent @event)
public static void RaiseQueuedEvents()
public static SubscriptionId Subscribe<TEvent>(Action<TEvent> eventHandler)
public static void Unsubscribe(SubscriptionId subscriptionId)

Sitecore.Events.Hooks.HookManager
public static void LoadAll()

Sitecore.Jobs.JobManager
public static Job GetJob(Handle handle)
public static bool IsJobRunning(string jobName)
private static void RemoveJob(Job job)
public static void Start(Job job)

Sitecore.Links.LinkManager
public static string ExpandDynamicLinks(string text)
public static UrlOptions GetDefaultUrlOptions()
public static string GetDynamicUrl(Item item, LinkUrlOptions options)
public static string GetItemUrl(Item item, UrlOptions options)
public static RequestUrl ParseRequestUrl(HttpRequest request)

Sitecore.Presentation.ControlManager
public static IWebControl GetControl(string controlUri, Dictionary<string, string> parameters)

Sitecore.Presentation.PresentationManager
public static ItemPresentation GetPresentation(Item item)

Sitecore.Publishing.DistributedPublishingManager
public static PublishStatus GetStatus(Handle statusHandle)
public static Handle QueuePublish(PublishOptions[] options)
public static void StartPublishing(StartPublishingRemoteEvent @event)
public static void UpdatePublishStatus()

Sitecore.Publishing.PreviewManager
public static string GetShellUser()
public static void ResetShellUser()
public static void SetUserContext()

Sitecore.Publishing.PublishManager
public static void AddToPublishQueue(Item item, ItemUpdateType updateType)
public static bool CleanupPublishQueue(DateTime to, Database database)
public static void ClearPublishQueue(Database database)
public static ItemList GetPublishingTargets(Database database)
public static PublishStatus GetStatus(Handle handle)
public static Handle RebuildDatabase(Database source, Database[] targets)

Sitecore.Resources.Media.MediaManager
public static Sitecore.Resources.Media.Media GetMedia(MediaItem item)
public static string GetMediaUrl(MediaItem item)
public static string GetThumbnailUrl(MediaItem item)
public static bool HasMediaContent(Item item)
public static bool IsMediaRequest(HttpRequest httpRequest)

Sitecore.Resources.Media.MediaPathManager
public static string GetExtension(string file)
public static string GetMediaFilePath(string itemPath, string extension)

Sitecore.Search.SearchManager
public static Index GetIndex(string id)
public static object GetObject(SearchResult hit)

Sitecore.Security.AccessControl.AccessRightManager
public static AccessRight GetAccessRight(string accessRightName)
public static AccessRightCollection GetAccessRights()
public static bool IsApplicable(AccessRight accessRight, ISecurable entity)

Sitecore.Security.AccessControl.AuthorizationManager
public static AccessResult GetAccess(ISecurable entity, Account account, AccessRight accessRight)
public static AccessRuleCollection GetAccessRules(ISecurable entity)
public static bool IsAllowed(ISecurable entity, AccessRight right, Account account)
public static bool IsDenied(ISecurable entity, AccessRight right, Account account)
public static void RemoveAccessRules(ISecurable entity)
public static void SetAccessRules(ISecurable entity, AccessRuleCollection rules)

Sitecore.Security.Accounts.RolesInRolesManager
public static void AddRolesToRole(IEnumerable<Role> memberRoles, Role targetRole)
public static IEnumerable<Role> FindRolesInRole(Role targetRole, string roleNameToMatch, bool includeIndirectMembership)
public static Role GetEveryoneRole()
public static IEnumerable<Account> GetRoleMembers(Role role, bool includeIndirectMembership)
public static bool IsUserInRole(User user, Role targetRole, bool includeIndirectMembership)

Sitecore.Security.Accounts.UserManager
public static int GetUserCount()
public static IFilterable<User> GetUsers()

Sitecore.Security.Authentication.AuthenticationManager
public static User BuildVirtualUser(string userName, bool isAuthenticated)
public static User GetActiveUser()
public static bool Login(string userName, string password, bool persistent)
public static void Logout()
public static void SetActiveUser(User user)

Sitecore.Security.Authentication.HttpAuthenticationManager
public static bool Authenticate(HttpRequest request)
public static bool IsAuthorized(HttpRequest request)

Sitecore.SecurityModel.DomainManager
public static void AddDomain(string domainName)
public static bool DomainExists(string domainName)
public static DomainConfiguration GetConfiguration()
public static Domain GetDomain(string name)
public static Domain GetDomainForUser(string userName)

Sitecore.SecurityModel.License.LicenseManager
public static void BlockSystem(string reason)
public static void ResetLicense()
public static long GetDatabaseSize(Database database)
public static int GetEditorUserCount()

Sitecore.Shell.Applications.ContentEditor.Galleries.GalleryManager
public static void GetGallerySize(string id, ref string width, ref string height)
public static void SetGallerySizes()

Sitecore.Shell.Applications.ContentEditor.Gutters.GutterManager
public static List<ID> GetActiveRendererIDs()
public static List<GutterRenderer> GetRenderers()
public static string Render(List<GutterRenderer> gutterRenderers, Item item)

Sitecore.Shell.Feeds.ClientFeedManager
public static ClientFeed GetFeed(string feedname)
public static SyndicationFeed GetFeedsAreDisabledFeed()

Sitecore.Shell.Framework.Commands.CommandManager
public static Command GetCommand(string name)
public static CommandState QueryState(Command command, CommandContext context)
public static void RegisterCommand(string name, Command command)

Sitecore.Shell.Framework.Commands.OpenSecurityManager
public override void Execute(CommandContext context)
public override CommandState QueryState(CommandContext context)

Sitecore.Shell.Framework.Commands.Shell.DomainManager
public override void Execute(CommandContext context)
public override CommandState QueryState(CommandContext context)

Sitecore.Shell.Framework.Commands.Shell.RoleManager
public override void Execute(CommandContext context)
public override CommandState QueryState(CommandContext context)

Sitecore.Shell.Framework.Commands.Shell.UserManager
public override void Execute(CommandContext context)
public override CommandState QueryState(CommandContext context)

Sitecore.Sites.SiteManager
public static Site GetSite(string siteName)
public static SiteCollection GetSites()

Sitecore.Syndication.FeedManager
public static PublicFeed GetFeed(Item item)
public static string GetFeedUrl(Item feedItem, bool includeUserAuthentication)
public static string Render(SyndicationFeed feed)

Sitecore.Visualization.LayoutManager
public static Rendering GetRendering(string fullName, Database database)

Sitecore.Visualization.RuleManager
public static void Execute(object thisObject)
public static RuleSet GetRuleSet(object thisObject)

Sitecore.Web.Authentication.TicketManager
public static string CreateTicket(string userName, string startUrl)
public static bool Relogin(string ticketId)

Sitecore.Web.UI.HtmlControls.PageScriptManager
public bool AddScripts
public bool AddStylesheets
public List<ScriptFile> ScriptFiles
public List<StylesheetFile> StylesheetFiles

Sitecore.Web.UI.WebControls.AjaxScriptManager
public void Dispatch(string parameters)
public ClientCommand Alert(string text)
public ClientCommand Download(string file)
public ClientCommand SetInnerHtml(string id, string value)

Sitecore.Web.UI.WebControls.ContinuationManager
public static void Register(string key, ISupportsContinuation @object)
public Pipeline Start(Pipeline pipeline, PipelineArgs args)

Sitecore.Web.UI.WebControls.MessageManager
public object SendMessage(Message message)
public object SendMessage(object sender, string message)

Sitecore.Web.UI.XamlSharp.ControlManager
public static Control GetControl(ControlUri uri)
public static string GetControlUrl(ControlName name)

Sitecore.Web.UI.XamlSharp.Xaml.XamlManager
public static AttributeAssigner GetAttributeAssigner(System.Xml.XmlAttribute attribute)
public static Compiler GetCompiler(ControlDefinition definition)
public static ControlCompiler GetControlCompiler(CompilerOptions options)
public static Sitecore.Web.UI.XamlSharp.Xaml.Extensions.Extensions GetExtensions()

Sitecore.Web.UI.XmlControls.ModeManager
public static ModeMatch Match(ArrayList modes, HashList<string, object> requested)

Sitecore.WordOCX.WordOCXUrlManager
public string GetDownloadLink()
public string GetEditLink()
public string GetPreviewLink()
public string GetUploadLink()

Helper Classes

Sitecore.Common.PagingHelper
public List<TOutput> GetRecords(int pageIndex, int pageSize, IEnumerable<TInput> inputList, GetElements<TInput, TOutput> getElements, out int totalRecords)

Sitecore.Configuration.ProviderHelper
public string GetRootAttribute(string attributeName)

Sitecore.Data.DataProviders.SqlServer.SqlServerHelper
public static int GetInt(SqlDataReader reader, int columnIndex)
public static long GetLong(SqlDataReader reader, int columnIndex)

Sitecore.Links.LinkDatabaseHelper
public static void UpdateLink(Item item)

Sitecore.Publishing.PublishHelper
public bool CanPublish(ID itemId, User user, ref string explanation)
public virtual PublishItemResult DeleteTargetItem(ID itemId)
public virtual Item GetSourceItem(ID itemId)
public virtual Item GetTargetItem(ID itemId)
public virtual Item GetVersionToPublish(Item sourceItem)
public PublishItemResult PublishSharedFields(Item sourceItem)
public bool SourceItemExists(ID itemId)
public bool TargetItemExists(ID itemId)

Sitecore.Reflection.NexusHelper
public static FastInvoker GetInvoker(string typeName, string methodName, bool isStatic)

Sitecore.Security.AccessControl.FieldAuthorizationHelper
public override AccessRuleCollection GetAccessRules(Field field)
public override void SetAccessRules(Field field, AccessRuleCollection rules)

Sitecore.Security.AccessControl.ItemAuthorizationHelper
public override AccessRuleCollection GetAccessRules(Item item)
public override void SetAccessRules(Item item, AccessRuleCollection rules)

Sitecore.Security.Authentication.AuthenticationHelper
public virtual bool CheckLegacyPassword(User user, string password)
public virtual User GetActiveUser()
public virtual void SetActiveUser(User user)
public virtual bool ValidateUser(string userName, string password)

Sitecore.Security.SecurityHelper
public static bool CanRunApplication(string applicationName)

Sitecore.SecurityModel.License.RSAHelper
public string Decrypt(string message)
public string Encrypt(string message)

Sitecore.Text.NVelocity.VelocityHelper
public static string Evaluate(VelocityContext context, string template, string logName)
public static void PopulateFromSitecoreContext(VelocityContext context)

Sitecore.Web.IPAddresses.IPHelper
public static IPList GetIPList(System.Xml.XmlNode node)

Sitecore.Xml.Xsl.SqlHelper
public string connect(string connection)
public string fullsearch(string search, string table, string displayFields, string searchFields)
public string select(string sql)

Sitecore.Xml.Xsl.XslHelper
public virtual string Application(string name)
public virtual string day()
public virtual string displayname(XPathNodeIterator iterator)
public virtual string EnterSecurityState(bool enabled)
public virtual string ExitSecurityState()
public virtual string formatdate(string isoDate, string format)
public virtual string fullUrl(string fieldName, XPathNodeIterator ni)
public virtual string GetDevice()
public virtual Item GetItem(XPathNodeIterator iterator)

Designing a Page Editor Experience: 1 - Setting up the Code

$
0
0

Shout Out

As a shout out, this series started because I was recently speaking with Mark Ursino and he gave me a demonstration of a recent build where he had taken advantage of a few really slick features in Page Editor. I decided to do some research to see what Page Editor could really do. In doing my research I came across a lot of great posts that individually detail pieces of functionality, some of which I'd seen demonstrated at the last Symposium. Others go in depth to explain why certain choices are lend to a better framework. For this post, I wanted to weave together as much of a cohesive picture along with some commentary and lots of referential links for developers to see a fuller Page Editor experience.

Preface

Before I begin, I want to bring to light some things to consider. The Page Editor has evolved to allow you to you store related content in DataSource fields on sublayouts as opposed to storing this information in fields, which is traditionally how many Sitecore sites were built. It's important to understand how the relationships between DataSources are stored in Sitecore and how they're changing between Sitecore 6.6 and 7. In 6.6 the DataSource for a sublayout is stored in the presentation details using the item's path (ie: /sitecore/content/home/pagename). As of 7 this was changed so that the DataSource is stored in the link database as an item ID. This is important for a few different reasons:

  • if you move, rename or remove a data source item, any sublayouts storing the item's path will not be updated, the values will not be easy to cleanup and if you don't code your pages correctly, you could have exceptions being thrown when your page tries to access fields on null objects.
  • as sublayouts are added to the site and items are created as data sources, if a user removes the sublayouts from a page, the items will likely not be removed with them and you'll have to figure out a process for garbage collection on this content.
  • if and when you upgrade from 6.6 to 7, consider that you will want to convert your system from storing the datasources in the presentation details to the link database. I'm not sure if there is any formal way to do this or not yet so do your research.

If you do choose to go ahead with 6.6 and store the DataSource information in the Presentation Details of a page, one thing you can do to help yourself is modify your system so that Sitecore stores the item ID's instead of their paths. This should at least make it somewhat easier to convert and more importantly much less prone to breakage.

Also, if you rely on Lucene search, consider that when you're storing the relationships between a page and it's content in the Presentation Details or link database, you're going to face new challenges to associate this content to a page. With 6.6 you'll need to figure out how to extract the DataSource ID's from the xml in the Layout field and with 7 you'll have to figure out how to gather the information from the link database.

Getting Started

Ok, with that said, whether or not you choose to store content relationships in the presentation details, Sitecore's Page Editor, has a lot to offer. When you get started thinking about designing an experience for Page Editor your journey will start with the code itself. There's a lot of great posts coming out focusing on component based approach including this really thorough series. This push has been for reuse and modularity but also more importantly to support the tools provided by Sitecore for tracking and personalization. What this means is that when you're designing your templates you should do so with consideration towards breaking up the page into reusable components whose content is interchangeable. Components are a general reference to user control sublayouts, xsl renderings and library server controls. I prefer working with sublayouts so going forward my examples will use them but there are other options.

On each of the sublayouts (renderings or server controls), to make the content fields accessible you should be using FieldRenderers. They live in the Sitecore.Web.UI.WebControls namespace and there are several controls built specifically for different field types: Date, Link etc. To learn more about these controls you can bone up a bit by perusing the Presentation Component Reference(System Overview), the Presentation Component API Cookbook(In-Depth Developer Reference) and the Presentation Component Cookbook(Working inside Sitecore)
Otherwise if you need to customize the output you may implement your own by extending the FieldControl class or higher(IE: FieldRenderer).

Once you've wired up your UI, following the path of the sublayout design patterns emerging, you'll want to setup a base class for your sublayouts that provide access to the sublayout parameters and datasource(as opposed to using the Sitecore.Context.Item). Below I've provided a sample base class that provides access to a DataSourceItem, ContextItem and a DataSourceOrContext. DataSourceOrContext will fall back to the ContextItem if the DataSourceItem is null. The reason for this is because by default, FieldRenderers select the Sitecore.Context.Item as their datasource but that might not be preferred. By using this type of base class provides a way for sublayout to specifically decide which source it is targeting by overriding the PreferredDataSource which if untouched, defaults to DataSourceOrContext. This class then recursively sets the datasource for all FieldRenderers on the sublayout. I sourced the logic for this from this great post. As an example, if you change the PreferredDataSource item to use only the DataSourceItem, the recursive function will hide the controls if the DataSourceItem is null so that the FieldRenderer's won't attempt to display data from the Sitecore.Context.Item.

public class BaseSublayout : System.Web.UI.UserControl {
	private Item _DataSourceItem;
	public Item DataSourceItem {
		get {
			if (_DataSourceItem == null)
				_DataSourceItem = Sitecore.Context.Database.GetItem(((Sublayout)Parent).DataSource);
			return _DataSourceItem;
		}
		set { _DataSourceItem = value;  }
	}

	public Item ContextItem {
		get {
			return Sitecore.Context.Item;
		}
	}

	private Item _DataSourceOrContext;
	public Item DataSourceOrContext {
		get {
			if (_DataSourceOrContext == null)
				_DataSourceOrContext = (DataSourceItem != null) ? DataSourceItem : ContextItem;
			return _DataSourceOrContext;
		}
	}

	private NameValueCollection _Parameters;
	public NameValueCollection Parameters {
		get {
			if (_Parameters == null)
				Parameters = Sitecore.Web.WebUtil.ParseUrlParameters(((Sublayout)Parent).Parameters);
			return _Parameters;
		}
		set {   _Parameters = value;  }
	}

	public virtual Item PreferredDataSource { get { return DataSourceOrContext; } }

	protected void ApplyDataSource() {
		foreach (Control c in Controls)
			RecurseControls(c, PreferredDataSource);
	}

	private void RecurseControls(Control parent, Item dsItem) {
		string typeName = parent.GetType().FullName;
		if (dsItem == null) {
			parent.Visible = false;
		} else if (typeName.StartsWith("Sitecore.Web.UI.WebControls")) {
			if(typeName.EndsWith("FieldRenderer"){
				Sitecore.Web.UI.WebControls.FieldRenderer f = (FieldRenderer)parent;
				f.Item = dsItem;
			} else {
				Sitecore.Web.UI.WebControls.FieldControl f = (FieldControl)parent;
				f.Item = dsItem;
			}
		}

		foreach (Control c in parent.Controls)
			RecurseControls(c, dsItem);
	}

	protected virtual void Page_Load(object sender, EventArgs e) {
		ApplyDataSource();
	}
}

I'm also bootstrapping the recursive function in the Page_Load method so if you want to work within that method on the local instance you'll want to override the method but make sure you call the Page_Load method from the base class:

protected override void Page_Load(object sender, EventArgs e) {
	... your code ...
	base.Page_Load(sender, e);
} 

Designing a Page Editor Experience: 2 - Sublayout fields

$
0
0

After you've decided what components your pages will be broken up into and created the sublayouts (renderings or server controls) each extending your base class, you can begin working on the setup inside Sitecore by configuring your component item's fields and setting up component insert options. These settings are stored on the sublayout items themselves and I'll detail them here.

The "Description" field is largely for you to provide a description of what the control does. I'm not aware of any place it is displayed but it's possible that it has other uses.

The "Parameter Templates" is one of the most interesting concepts. The intention is to allow developers the ability to customize the user interface for entering parameter values. If the default key/value UI control doesn't meet your needs you can attach a template to provide you with additional fields that you do need. First create a new template.

parameter template create

Add some fields.

parameter template fields

And set the new template to inherit from the Sitecore template: /System/Layout/Rendering Parameters/Standard Rendering Parameters.

parameter template base template 

Then you'll want to set the parameter template field on the sublayout item

parameter template set template 

Please note this is not a comprehensive guide to creating a template. I'm going to skip adding icons, standard values tokens and help text for convenience here but you should not. Now when you're working with a sublayout and you click "Edit" on the sublayout in the Presentation Details or in the Page Editor More->Edit the Component Properties, you'll see the new field section.

parameter template edit fields 

These values will then be accessible through the Parameters property defined on the BaseSublayout class provided in the previous post

The "Open Properties After Add" field allows you to choose if the component properties (datasource, caching, params etc.) windows will be opened automatically when a content editor adds a sublayout to a page. It has values of "Default", "Yes" and "No".

open properties on add 

These value affect the behavior of the checkbox "Open the properties dialog box immediately" when you insert a component/sublayout

insert sublayout 

A "Default" value will allow the content editor to choose. A "Yes" value disables the checkbox and automatically checks the box while a "No" value also disables the checkbox and keeps it unchecked.

The "Customize Page" field is apparently a legacy field. Quoting from the Presentation Component Reference: "The Customize Page property in the Editor Options section of rendering definition items is a legacy replaced by rendering settings data templates." Basically if you haven't used it yet, don't start.

The "Editable" field will prevent a content editor from being able to insert the sublayout through the Page Editor although an admin still has access and can add the sublayout through Presentation Details.

The "Datasource Location" field is used similar to the source on a template field. It's the location in the content tree where Sitecore will start a TreeList for you to select a data source item from when you click a to set the DataSource field on a sublayout. By default if you insert a sublayout you will not be asked to select a datasource. You can optionally edit the component properties to do so but if you select an item for this field you'll automatically see a popup asking you to choose a data source for the sublayout you're inserting.  Here I'll set the datasource to an item just under the home item.

 datasource location set

You'll see that the result tree now starts at that item.

datasource location select
  
The "Datasource Template" field is used to limit what types of content can be selected as a datasource. When you're selecting the datasource, if you select an unsupported type you'll receive a nice friendly message.

datasource template error 

Unfortunately the value for this field is stored as an internal link field and you can only select one template type. That being said, you may only need to select one template but pray you never rename your templates because this link will not update automatically.

The "Compatible Renderings" field is intended for situations where you want to swap out an existing sublayout but keep the datasource. You'll want to take note that this works best by selecing renderings that have use the same Datasource Templates so that if you swap it out the sublayout doesn't throw errors because the sublayout is expecting a different data source item type. To set this up, you first select a sublayout from the compatible renderings field.

compatible rendering field 

Then in the Page Editor after you've inserted this sublayout and click on it, you'll see a red and green arrow button with tooltip text: "Replace with another component".

compatible rendering change 

This will popup a dialog window for you to select fromt he sublayouts you've chosen

compatible rendering sublayout

The "Page Editor Buttons" field will allow you to add buttons to the detail window that displays when you select a sublayout in the Page Editor. There are two types of buttons: WebEdit buttons makes calls to the commands from the commands config file and Field Editor Buttons open a window that let you edit a specific set of fields. By default the bar is pretty light and you're only provided WebEdit Buttons to choose from.

web edit button types 

Each has it's own unique function:

  • the insert button will give you a popup that allows you to insert items for the context item.
    web edit button insert 
  • the delete button allows you to delete the context item (as long as it isn't the site start item)
    web edit button delete 
  • the sort button allows you to sort the child items through buttons or a drag and drop interface (if the browser supports it)
    web edit button sort 
  • the move up/move down moves the context item up and down in the Sitecore tree

If and when you decide you need some custom functionality available or you want to open up access to fields that aren't for display such as metadata fields, you'll want to jump to the Core database. The templates for the two button types are the "WebEdit Button" or the "Field Editor Button" and are both stored under: /core/sitecore/templates/System/WebEdit. 

web edit button templates 

You'll want to be creating them under: /core/sitecore/content/Applications/WebEdit/Custom Experience Buttons. The fields for a WebEdit Button are Header, Icon, Click(name from command file), Tooltip and Parameters.

web edit fields 

If you want to create a Field Editor Button there's a bit of setup involved so I'll walk through the steps. I'll note that the fields accessed will be on the Datasource item if it's been set so in this case metadata fields are only going to be useful if the datasource or context item is a page.

  • First you'll need to define meta data fields on a template
    template fields
  • Then jump into the core database and drill down to: /core/sitecore/content/Applications/WebEdit/Custom Experience Buttons
  • Insert an item of template type: /core/sitecore/templates/System/WebEdit/Field Editor Button
    field editor template
  • Set the fields you want to edit on the item. If there are multiple, separate the field names using the pipe(|) delimiter
    field editor item
  • Jump back into the master database assign the button to a sublayout item
    edit sublayout buttons
  • Open up Page Editor and select a sublayout with the button on it
    editor button
  • Click the button and you'll see a popup with the fields available to edit.
    field-editor.jpg

For another article that specifically caters to this feature, you can read up here.

The "Enable Datasource Query" field is available in Sitecore 7 and I to be honest I don't know what it does yet. It's likely related to the new field type that Sitecore is going to replace the Datasource field with that John West explains here.

As for the "Data" and "Caching" sections of the sublayout, I'm going to gloss over them since they're not likely to be used as often. They store the same component property values that you select when you add a sublayout to a page but this will set them globally for all uses. It's definitely possible that you could find this useful but generally you'll want to set these values specifically where they're applied to a page or standard values item.

The "Login control" field is one exception to the previous sections that I will explain. I dug into the Sitecore.Kernel and found that this is a level of security that allows you to show another sublayout if !Context.User.Identity.IsAuthenticated. To enable access, you can authenticate users with a couple of quick lines of code:

string domainUser = Sitecore.Context.Domain.Name + @"username";bool loginResult = AuthenticationManager.Login(domainUser, "password");

This can be used for an ad-hoc extranet which you can manage permissions for different groups. I have a more extensive guide for Setting up a Sitecore Extranet which explains the process in more detail.

Designing a Page Editor Experience: 3 - Placeholder Settings

$
0
0

Placeholder settings are used to create predefined insert options for components (sublayouts, renderings and web controls). You have two basic approaches to use: setting the placeholder key on the item's Placeholder Key field: 

placeholder settings global key 

or setting the key when you apply it to an item through it's presentation details.

placeholder settings local set

Setting the placeholder key on the placeholder settings item will set the component insert options of any placeholders with a matching key. This could be useful for global elements or in single site deployments where you're not concerned about managing individual settings for each site.

Setting the placeholder key on the standard values of a template will allow you to set the same group of component insert options on different templates or placeholders. This, I suspect, may be most useful especially in multi-site instances since it would allow you to specify the sublayouts used for your specific site's placeholder keys.

There are a two out-of-the-box placeholder setting items in Sitecore: content and webedit. You can incorporate these into your strategy or create your own. I'll demonstrate the latter. Browse to /master/sitecore/content/Layout/Placeholder Settings. Right-click on the placeholder settings item and insert a new Placeholder item.

placeholder settings insert

If you're going to set the placeholder key on the placeholder setting item you may want to name the item based on the content placeholder key it will be used for. On the other hand if you're going to be specifying the placeholder key in standard values, you'll want to name it based on its intended use. IE: Sidebar Controls, Header Content etc. The new item has four fields: Placeholder Key, Allowed Controls, Description and Editable.

As mentioned earlier, the Placeholder Key field is optional. If it is set Sitecore will apply the component insert options to any placeholders with a key that matches.

The Allowed Controls field allows you to select the components that you want to be available to an editor when they click on the "Add to here" buttons in the Page Editor. For this example I'm going to add my New Sublayout under allowed controls. So on the field I'll click the "edit" button and will receive this dialog window.

placeholder settings allowed

Then click "Ok" and you'll see that the field has selected the item.

placeholder setting fields

The Description field appears to be informational. I didn't find any place where it was visible other than on the control itself.

The Editable checkbox determines if the placeholder settings will be editable from the presentation details windows. Here I've unchecked the checkbox and you can see the edit option is greyed out. 

placeholder settings editable

I'll want to also add the placeholder setting to the standard values of an item. I'm going to open the presentation details and then I'll click on the edit button for the default device.

placeholder settings presentation details 

Then I'll select the Placeholder Settings section and click add.

place-set-add.jpg

From Here I can select the New Sublayout item and enter in "content" for the Placeholder Key.

placeholder settings local set 

Now if I go to the Page editor and click on the blue component button to insert a component. I'll see the "Add to here" buttons.

placeholder settings add component 

When I click on the "Add to here" button, I'll now only be given the New Sublayout as an option.

placeholder settings option

Viewing all 178 articles
Browse latest View live