Quite new to Sitecore and would like to understand the best way to access sitecore items. I've seen couple of ways:
Create Page ID field and get all items for given template and folder. Then do linq query on page ID.
Store all Page ID (Sitecore Item ID) on Constants file. Use this to query Sitecore using GetItem(itemID) API.
Could someone please suggest what's the best practice. Either way, I can see that there will be huge Constants file containing either custom Page ID or Sitecore Item ID.
My worry is do we really need to manage this Constants file or is there an elegant way to query CMS contents for given Page.
Thanks.
Approach 1 seems a bit odd. I don't really see why you would get a collection of items first when you already have the ID, but maybe I've misunderstood what you're saying.
I think a combination of 2 things you mention is best.
Constants classes are good for "landmark" items that will always definitely be there, so its fine for the GUID to be in code.
Some of these landmark items can be "configuraton items" that have fields containing the IDs of other items (These fields might have names like "Templates allowed in search"). This approach allows some flexibility for change by Sitecore users as the site evolves.
If you're concerned about the management of a constants file, I have to wonder how many items you need to access by ID. Sitecore items have properties like Parent and Children. You can also find items by template type etc.
This approach works well for me. I certainly don't think there is a more elegant way of getting strongly typed references to Sitecore items.
Personally, I prefer not to use constants files. What happens if your client or one of your developers deletes one of those items and creates a new one? One of the fundamental principles of Sitecore is that items can be added and removed by users who are not necessarily "technical" personnel.
Sitecore provides "Insert Options" so that you can specify what types of items can be added to each folder, and also grants the ability to protect certain items from deletion, via Roles and User Permissions. What does this mean conceptually? It means that Sitecore is set up such that the system architects/developers can create a structure that is not to be violated, while the content editors can add or remove content within that structure. In other words, Sitecore is designed to provide a framework in which the items can change but the location of each type of item is pre-determined.
As such, I suggest that you use the Custom Item Generator module available in the Sitecore Marketplace (free). CIG generates C# class representations (models) of your templates, and makes all fields into properties (I don't want to get too off-topic, but this is an awesome feature of CIG, especially when working with newer developers). You can add your own methods to your CIG classes for getting children of a particular type. For example, on a site in which the Profile Page is a direct child of the Homepage the following method could be added to the CIG HomepageItem.instance.cs file's HomepageItem partial class:
...
public partial class HomepageItem
{
public ProfilePageItem GetProfilePage()
{
//note that .IsOfType(...) is pseudo-code and not a real method, but I do
// suggest that you define an extension for it
return InnerItem.Children.FirstOrDefault(i => i.IsOfType(ProfilePageItem.TemplateId));
}
}
Be sure that you are assigning Insert Options to restrict the types of items that can be added as children to each item you make (add them on the Standard Values and not on the individual content items). I also suggest that you make a separate Template that inherits from Common/Folder for every folder that you use in the content tree. This way you can use CIG for your entire structure, via:
...in your Globals Item's CIG class...
public partial class GlobalsItem
{
public SlidesFolderItem GetSlidesFolder()
{
return InnerItem.Children.FirstOrDefault(i => i.IsOfType(SlidesFolderItem.TemplateId));
}
}
...in your Slides Folder Item's CIG class...
public partial class SlidesFolderItem
{
public IEnumerable<SlideItem> GetSlides()
{
return InnerItem.Children.Where(i => i.IsOfType(SlideItem.TemplateId));
}
}
and then you can get your Slide items via:
...
var slidesFolder = globals.GetSlidesFolder();
var slides = slidesFolder != null ? slidesFolder.GetSlides() : null;
Remember that CIG, Insert Options, and templates for each type of folder will enable you to create an iron-clad structure for your site that will not break if content editors make unexpected changes like replacing items.
Let me know if you have any questions on this. Good luck, and Happy Coding! :)
Related
I am creating a Sitecore MVC site for a client and I need to create page that will list news articles for the company.
So far, I have created items that use a shared data template called “Article,” and I also have a sublayout (a view rendering) called “Article” that will display these items.
For the list itself, my plan was to create another component (a sublayout) call “News_List”, and to put a placeholder in it called “List”.
My question is this: can I allow the author to insert articles (e.g., N items of type “Article”) into this placeholder via the page editor?
Will SC allow you to insert multiple instances of the same component into a placeholder? Will this break anything?
I believe this is a pretty common question but I have not found a definitive answer. Thanks in advance…!
You can insert as many components (of the same type) in your placeholder as you want.. Just make sure to put the placeholder settings correctly and give it a decent name (not just "list" ;))
But are you sure you want to do this? Your editors will manually need to create a list of components for each article they want to add on the page. Doesn't sound to be very user (editor) friendly.. Maybe you should consider creating a list component that can get a list of articles as a datasource and show those. Or even select them automatically (but that might be not according to your business case)..
Yes, authors can add multiple instances of the same component into a single placeholder.
Assuming that the code of the component doesn't do any stupid things it's absolutely ok to do this.
Let's say there exists a presentational component in a project that renders an unordered list (called ListRenderer, perhaps.) We have a couple options of supplying data to any given ListRenderer on a page:
Have a TreeList (or TreeListEx) field on the content item, and have ListRenderer read from it.
Supply a DataSource (or other Parameter) to the ListRenderer via the presentation details.
I usually avoid #1 in my projects because it binds Sublayouts to templates, which gets quite messy. If you go down that path, eventually you'll have fields to support every potential sublayout in your project.
So my solutions tend toward option #2, which gets rid of that problem. It does, however, come with its own bag of questions. Where do I put these various "Lists" for a given ListRenderer to use? To maximize reuse and sharing, I usually create a components directory near the site root that contains all these types of things, if I predict the Lists will be shared. This seems less findable and harder to use for the content author, who suddenly have no idea where the source for their ListRenderer is unless they know how to crack open the presentation details (which is slightly advanced for my average user).
If I feel like Lists won't be shared, and are very specific to the page, I'll put them directly underneath the item in question. This has a tendency to muddle up the content tree, though, and any dynamically generated navigation sublayout then has to check for whether or not an item is an actual page before it generates the link to it. The more I work in Sitecore, the less I use this approach, but it seems easier for the content author. There is much easier access to information when you use this approach.
Is there any industry-accepted way of approaching this problem? It happens in projects all the time, and in my head I struggle to balance technical and content authorship concerns in situations like these.
Great question. I've used all the techniques you mentioned, depending on the audience and specifics of the project. The problem is that, as with all things Sitecore, they are all valid ways of achieving the same goal and you will struggle to find one answer that will work in every situation.
I almost always use #2 as well, but some content author retraining maybe necessary and make sure you add in restrictions to what the content author is able to select as a target. I have (within the same project) structured the items near the root (in a shared content folder) and under the item in question, depending on what I felt would provide the best context.
Also, if other child pages would exist below the item as well as the list items, then I would put the list items in a separate folder (with a common "list items" icon") and re-order it to be the first item for separation and clarity.
If you want to use any kind of personalization and DMS then you will need the ability to switch out the datasource anyway so you shouldn't hard code locations.
You might also (if you have not already) want to consider using:
Convert Data Source Paths to IDs Using the Sitecore ASP.NET CMS
- Useful if you need to restructure your content at a later date
Queryable Datasource Locations
- Useful for multi-site situations when you need to make clones, or setting as the default datasource value in Standard Values when the lists are directly below the item but gives you the flexibility to change it.
I prefer using querable datasources personally, I find the xpath syntax more logical.
As Mark has commented, there is no real industry standard.
I feel like this is something that needs improvement.
Especially when you are using the DataSource option, things become less transparent to the editors and as the size of the site grows, so does the complexity.
All I can tell you is how I would do it, which is most likely much like how you are doing it.
1) For overview pages like news, events and faq items, I will put the items underneath the overview item and use the NewsMover shared source module to auto-create a hierarchy.
2) I will create a Global site that contains items that are shared across sites or pages. DataSource items for components will be put in here.
3) For components that are present on the standard values, I will add a list field to the template (for example, when you display related items on a content page)
Most often it's a logical choice and sometimes it's just a matter of taste.
I'd like to add that I've written a blog post on how to have datasource items created automatically for components that are set on standard values. That might help you if you are using those.
Edit:
"I usually avoid #1 in my projects because it binds Sublayouts to templates, which gets quite messy. If you go down that path, eventually you'll have fields to support every potential sublayout in your project."
Today I've blogged about a method of hiding fields and sections in the content editor if there is no sublayout set on the item that requires those fields, which helps to prevent the mess of having a lot of unused fields on your items.
Before digging into my explanation i will summarize my question:
How do I provide the user (editor) with a user-friendly possibility to select a datasource item for sublayouts that are preset on the standard values?
My situation is as follows:
I have a page template, with pre-defined layout on the standard values.
Let's say the layout consists of:
one placeholder "wrapper"
one sublayout "content"
This sublayout is pre-defined on my page template, but can also be placed in the placeholder using the Page Editor.
It needs to have a datasource item that defines a Title and Body value.
Now, if a user adds this sublayout to the placeholder using the Page Editor, he will get a nice interface to select or create the datasource item (see screenshot).
However, if the sublayout was pre-defined on the standard values, it will be added without datasource (I can't pre-set the data source in the standard values because it's still unknown by then).
At that point there seems to be no way to get to that nice interface for selecting or creating a datasource item.
Ideally I want to be able to add a field to my template that can hold a datasource item which the user can select/create using the nice interface. I looked at the datasource field type, which could be an alternative, but it's still not exactly what I want.
Bare in mind that the content sublayout is just an example.
I understand that in that specific case I could solve it by always adding a title/body field to the template which hold the values if there is no datasource, but for my real world problem that won't suffice.
I don't have a whole lot of experience with the Page Editor (with the new way of working with it) so I would like to get some advice on this subject.
According to what you said here:
Ideally I want to be able to add a field to my template that can hold a datasource item which the user can select/create using the nice interface. I looked at the datasource field type, which could be an alternative, but it's still not exactly what I want.
It seems you want an intuitive data source selector interface in the CMS shell similar to the Page Editor-based UI.
Quick answer: Simply put, there's nothing that does this for you in Sitecore.
Longer answer: There are still some options for you, e.g.
Define a global "dummy" data source and set that to be the data source set in in Presentation > Layout Details of the template's standard values. So every time you create a new page, it will always point to that dummy value to show something.
From here you can do a few things:
If the user must use the shell UI and not Page Editor, they can simply create another data source item for the specific page and update that page to point to it using the existing native interface in Layout Details.
Another option is to write an event handler, say for item:created or similar that when you create an item, auto-create a corresponding data source item for this specific page (whether this auto-created item be a sub-items or global item...) then programmatically set this to be the data source. A similar concept is shown in this video by Nick Wesselman: http://www.techphoria414.com/Blog/2012/May/Sitecore_Page_Editor_Unleashed
I'm building a data repository site that I will then clone in its entirety to provide multiple clone sites, enabling localistaion of global content.
What I need to do is to ensure that all references between items in the repository site (links in rich text fields, item references to pull in "related items" spots etc) are overridden to refer to the relevant clones instead of the original items in the repository.
This will likely involve e.g. customising the LinkManager and maybe GetItem(itemID) with some additional logic to find the correct clone.
What I need to know is which bits of the API do I need to worry about? Is there a single modification I can make which will inherit to link rendering in a rich text field in .Net components, item references fed to a sublayout from drop list, renderings through XSLT etc? I need an item ID to work as an alias for its clone when in the context of the clone site. Context.Database.GetItem(ID) needs to return a clone when in the clone site context.
I'm basically looking for a mechanism that will translate "Data/Home/Products/Product A" to "clone/Home/Products/ProductA" whenever and however I use it in the context of a clone site.
Where do I need to implement this logic, how many places?
Cross posted to SDN http://sdn.sitecore.net/SDN5/Forum/ShowPost.aspx?PostID=35598
This is related to an earlier question Handling internal links in Sitecore 6.4 cloned sites , but contains more detail and is more specific.
EDIT: though the ideal solution would place this functionality deep within Sitecore it is important that this only applies to content as viewed on the actual website, i.e. it must not interfere with Sitecore pipelines for e.g. creating, cloning and deleting items.
I recommend you take a different approach. Rather than changing the links themselves, you can add code to the HttpRequestPipeline to resolve "Data/Home/Products/Product A" as "clone/Home/Products/ProductA". A similar approach is described in Reusing and Sharing Data:
A CMS user could use the Rich Text Editor, rendering properties, or other features to link to an item
based on an item selection data template. Account for these conditions in your code. You can
configure an httpRequestBegin pipeline processor to handle HTTP requests for items [...]
To apply this approach to your scenario, add the custom implementation of HttpRequestProcessor after <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel"/> in the HttpRequestBegin pipeline in web.config.
Here's the logic to implement:
Use HttpContext.Current.Request.UrlReferrer to determine the referring site.
Verify that the referring site is in your list of cloned sites.
Check if Sitecore.Context.Item is in the source site.
Create a string with the new path, and verify that this item exists using Database.GetItem().
Modify Sitecore.Context.Item to the new item.
The advantage of this approach is that you do not need to intercept the many ways that a link can be created, and you keep your path rewriting logic in one place. In effect, you will be creating an alias from "Data/Products/ProductA" to "clone/Home/ProductA" that will only take effect if your site is in your list of clones.
Update: I did a test of this approach in Office Core. I created a second site, AlternalteSite, with a child node Our-Process. The AlternateSite home page has a link to /home/Our-Process. When the code below is added to the HttpRequestBegin pipeline, the link directs to the /AlternateSite/Our-Process item.
public class SiteChanger : HttpRequestProcessor
{
// Some definitions removed...
public override void Process(HttpRequestArgs args)
{
if (ReferringSiteIsTarget())
{
Item targetItem = GetTargetItem();
if (targetItem != null)
{
Sitecore.Context.Item = targetItem;
}
}
}
private Item GetTargetItem()
{
string currentItemPath = Sitecore.Context.Item.Paths.FullPath;
string newPath;
if (currentItemPath.Contains(SourcePath))
{
newPath = currentItemPath.Replace(SourcePath, TargetPath);
return Sitecore.Context.Database.GetItem(newPath);
}
return null;
}
}
Update2: As James Walford points out in the comments, this approach only works if the clones are not renamed. Sitecore does not, to my knowledge, provide a way of traversing from a source item to its clones in the web database. In master, you can use the Link Database to get from an item to its clones (see this forum post by John West), but after publication, clones become normal items, so presumably will not be included in the Link Database.
One approach would be to add a multilist of links to clones to the standard template, and add logic to populate this as part of the uiCloneItems pipeline, and to use this data in the HttpRequestProcessor code. This will preserve the Link Database relationship, but will put overhead on both the cloning process and the Http Request resolution process, as your code would need to iterate through all the clones to determine which one lived in the requesting website.
I'm working on a site that will support several languages. I create a template and a content item. Then I fill in the fields for language-specific versions of a content item. Thats OK.
I want to translate labels that go through the whole application.
Let it be "More" label. I thought it is something about Dictionary and usage of Sitecore.Globalization.Translate.Text("More") but it doesn't work for pages with non-default language(provided I created a language-specific version of a dictionary item).
What is the correct solution?
but it doesn't work for pages with non-default language(provided I created a language-specific version of a dictionary item)
Vitaliy,
Leveraging dictionary for this purpose is a proper way of doing it.
The Text method of Sitecore.Globalization.Translate class is supposed to respect the context language, so that should work.
public static string Text(string key)
{
Assert.ArgumentNotNull(key, "key");
return TextByLanguage(key, Context.Language);
}
Please file a ticket with tech support if that does not work for you.