| John's profileJohn West Blogs about Si...PhotosBlogLists | Help |
|
11/20/2008 Reflecting on SitecoreI do not have access to the source code for Sitecore, so I often use Redgate's .NET Reflector to investigate the code. I think it's still fair to credit Lutz Roeder for this excellent tool. Redgate (which makes some other great products as well) can probably increase the value of Reflector more efficiently than an individual, such as by hosting online forums, managing newsletters, soliciting developers to build add-ins, and so forth. A recent Redgate .NET Reflector newsletter provided a link to an article by Jason Crease titled "First Steps with .NET Reflector", and another by Andrew Clarke titled "Using .NET Reflector Add-ins". I didn't even know about Reflector add-ins, so I hope to find (or possibly even develop) one that addresses some of my only complaints with this tool: that I can search by name for a class, struct, or enum, but I cannot search for a method by name, and I cannot search for a string within the disassembled code. I don't believe that using Reflector with Sitecore assemblies violates any Sitecore licensing terms, but if it does, I am sure that Sitecore will never enforce those clauses of the license. In order to prevent reverse-engineering the product, Sitecore obfuscates a small portion of the internal code that it doesn't expect most Sitecore developers to read. Most Sitecore developers primarily use APIs in the Sitecore.Kernel.dll assembly. Once in a while you might need to open Sitecore.Client.dll. Once you access a class, Reflector will prompt you to let it open additional assemblies as needed. The three Reflector commands I find most helpful are:
It's easy to determine how to investigate certain aspects of Sitecore. For instance, the properties of Sitecore.Context generally lead you to the most relevant classes, and you can investigate the classes referenced in web.config to see how Sitecore implements all sorts of features. When you're ready, you can inherit from or replace those classes with customizations. More advanced developers might be interested in how Sitecore implements certain features in the user interface. This usually comes down to one of two things: investigating a command in the Content Editor ribbon, or investigating an entire application such as the User Manager. To investigate ribbon commands, it helps to know a little more about how Sitecore works. Log in to the Sitecore Desktop as an administrator, click the database icon in the lower right corner, and then click core. You are now working with the Core database, which controls the Sitecore user interfaces, instead of the default Master database, which contains all versions of all content. Launch the Content Editor and navigate to the item you wish to investigate. Remember to switch back to the Master database when you're done investigating the Core database. For example, to investigate the Publish button in the Publish group on the Publish tab, in the Content Editor, navigate to /Sitecore/Content/Applications/Content Editor/Ribbons/Chunks/Publish/Publish (chunks being the original name for groups). In the Data section of this item, the Click field contains the token item:publishnow. This corresponds to an entry in the /App_Config/Commands.config file, which specifies the class that implements the logic that occurs when you click the Publish command, including the assembly that contains that class. The Menu field of this item references other items in the Core database that correspond to the commands on the menu that appears if you click the drop-down menu underneath the Publish command. Sitecore uses a really elegant (and hence, sometimes complex) approach for developing certain user interface components. Instead of hard-coding everything in .aspx files, Sitecore uses XML files under the /sitecore/shell/applications directory for a number of user interface components. The Sitecore SheerUI layer converts these XML files to ASP.NET controls, which render the user interface. Sitecore may not always name these files what you expect, so you might have to find a reference in the Core database or elsewhere to the item you need, or just look around and validate your guess before investigating too closely. There appear to be some XAML/Sheer tutorials under the /sitecore/shell/applications/xaml directory (my understanding is that Sitecore referred to SheerUI as XAML before Microsoft formalized XAML). These XML files reference classes that effectively provide code-beside for the UI components represented by the XML, and these classes tend to be in the Sitecore.Client.dll assembly. For example, the /sitecore/shell/applications/licenses/licensedetails/licensedetails.xml file implements the license details UI. The <CodeBeside> element in this file specifies the code-behind that implements the logic behind the license details UI component. One of the greatest things about Sitecore is that you can override almost any feature of the product with your own logic. You can change the classes referenced in web.config and /App_Config/Commands.config, you can add your own commands and other features to the ribbon and /App_Config/Commands.config, and you can override SheerUI components (copy the XML file from the source directory to the /sitecore/shell/override folder and change the <CodeBeside> attribute to reference your class and assembly). Note that Sitecore recently introduced a new technology called XAMLSharp, which has the effect described here. If you have read this entire post, you may also be interested in the MSDN Magazine .NET Tools article "Ten Must-Have Tools Every Developer Should Download Now", which I found in the Wikipedia page for Reflector. It's embarrassing to say I'm not as familiar with NUnit, NDoc, or NAnt as I could be. Update: The CodeSearch Reflector add-in seems to allow searching for strings in code. It's a little slow, which reminds me to write that I think it's a good idea to close assemblies that you're not using. My shortcut is to select one assembly, and then press CTRL-F4 until I have closed all of the assemblies. I couldn't find anything that would allow me to search for a method by name. Update Again: XSL developers can investigate signatures for XSL extension methods in the sc namespace by reviewing the Sitecore.Xml.Xsl.XslHelper class in the Sitecore.Kernel.dll assembly. Or review the Sitecore API documentation. Sitecore's XSL Presentation Component Reference provides an overview of common XSL extension methods, and lists the classes exposed by other namespaces in the section "Additional XSL Extension Method Classes". Update Once More: A coworker told me they use the FileDisassembler Reflector add-in to create source files from code. This coworker prefers to compile the code and use the debugger to see how it works instead of just reading it, but this could also be handy to override things that could otherwise be difficult to override (I think some cases that use this or contain private and protected methods). But be forewarned: using this approach to override components could prevent you from receiving bugfixes and could also present upgrade challenges. Yet Another Update (this time with Windows Live (Blog) Writer): Deblector sounds very useful for stepping through .NET framework assemblies, but for some reason I can’t seem to attach to w3wp.exe, and haven’t received a response to an email sent to the owner. 11/19/2008 Overriding Sitecore's Logic to Determine the Context LanguageThe Sitecore layout engine retrieves content from the Sitecore repository in the context language (Sitecore.Context.Language). The default logic to determine the context language is to use the first of these variables that specifies a value:
In the first two cases, Sitecore generates a session cookie so that subsequent requests do not have to include the URL query string or language prefix in the URL. If the user returns to the site without specifying the URL query string parameter or the language prefix in the URL path, their language selection would be lost. If the organization has not published a translation of the context item in the context language, Sitecore acts as if all fields in that item are empty. Some solutions need to augment the default logic for determining the context language. For instance, the developer may want to:
Additionally, ASP.NET uses the System.Threading.Thread.CurrentThread.CurrentUICulture and System.Threading.Thread.CurrentThread.CurrentCulture properties for localization, such as formatting dates. Sitecore does not set these properties based on the context language, so they default to the operating system configuration. It might be helpful if the language resolution logic set these properties. The following solution is relatively untested but could provide inspiration for a production-quality solution to meet these requirements. My excuses for not testing are that a developer should not test their own code and I am not even really a developer, but the issue is really that there are a lot of conditions to test, especially with different configurations of the dynamic link manager, and nobody is paying me to do this. Sitecore.Context.Language is a smart property, which means it follows the lazy load pattern: if code accesses this property when nothing has set it, the getter for the property contains logic to determine the context language. Sitecore uses the Sitecore.Pipelines.HttpRequest.LanguageResolver processor in the httpRequestBegin pipeline to determine the context language. Because Sitecore.Context.Language is a smart property, this processor probably isn't necessary, as some subsequent logic in the processing of the request is almost guaranteed to access Sitecore.Context.Language. In any case, we want to override this logic. Normally when adding logic to a pipeline, we replace an existing processor with our processor, or add our processor before or after a default processor. But in this case our processor depends on Sitecore.Context.Item, and therefore must appear after the Sitecore.Pipelines.HttpRequest.ItemResolver processor which sets the Sitecore.Context.Item property. Without investigating, we don't know whether processors between Sitecore.Pipelines.HttpRequest.LanguageResolver and Sitecore.Pipelines.HttpRequest.ItemResolver use Sitecore.Context.Language, so we'll just leave that default Sitecore.Pipelines.HttpRequest.LanguageResolver alone and add our processor after Sitecore.Pipelines.HttpRequest.ItemResolver. Compile the processor into your visual studio project. Add the custom processor after the default Sitecore.Pipelines.HttpRequest.ItemResolver processor in web.config: <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" /> <processor type="Namespace.Pipelines.HttpRequestBegin.LanguageResolver, assembly"> <persistLanguage>true</persistLanguage> <setCulture>true</setCulture> </processor> If you don't want to create a persistant cookie to persist the user's language preference between browser sessions, set the value of the <persistLanguage> element to false, or simply remove this property setter. To support fallback languages:
Note that asNeeded is the default value for the languageEmbedding attribute of the /configuration/sitecore/linkManager/providers/add element in web.config that controls the logic for generating friendly URLs. The logic applied for this value may not be exactly what you expect, which can result in multiple URLs for a single content item in a single language. I recommend setting this to always for solutions involving a single language, or never otherwise. My current code for this processor follows:
using System;
namespace Namespace.Pipelines.HttpRequestBegin
{
public class LanguageResolver
{
private int _fallbackDepthLimit = 5;
public int FallbackDepthLimit
{
set
{
_fallbackDepthLimit = value;
}
get
{
return _fallbackDepthLimit;
}
}
public bool SetCulture
{
get;
set;
}
public bool PersistLanguage
{
get;
set;
}
public void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
if (Sitecore.Context.Item == null)
{
string message = String.Format("{0} : context item null : {1}", this.GetType().ToString(),
Sitecore.Web.WebUtil.GetRawUrl());
Sitecore.Diagnostics.Log.Error( message, this);
}
else if ( Sitecore.Context.Site == null )
{
string message = String.Format("{0} : context site null : {1}", this.GetType().ToString(),
Sitecore.Web.WebUtil.GetRawUrl());
Sitecore.Diagnostics.Log.Error( message, this );
}
else if ( LanguageSetFromUrlPath()
|| LanguageSetFromQueryString()
|| LanguageSetFromCookie()
|| LanguageSetFromBrowserPreferences()
|| LanguageSetFromContextSite()
|| LanguageSetFromDefaultSetting()
|| LanguageSetToFirstExistingLanguage())
{
if (SetCulture)
{
System.Threading.Thread.CurrentThread.CurrentUICulture =
new System.Globalization.CultureInfo(Sitecore.Context.Language.Name);
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Globalization.CultureInfo.CreateSpecificCulture(Sitecore.Context.Language.Name);
}
}
else
{
string message = String.Format("{0} : Unable to determine valid context language : {1}",
this.GetType().ToString(), Sitecore.Web.WebUtil.GetRawUrl());
Sitecore.Diagnostics.Log.Error(message, this );
}
}
private bool LanguageSetFromUrlPath()
{
return Sitecore.Context.Data.FilePathLanguage != null && SetLanguage(Sitecore.Context.Data.FilePathLanguage.Name, false, true, 0);
}
private bool LanguageSetFromQueryString()
{
return SetLanguage(System.Web.HttpContext.Current.Request.QueryString["sc_lang"], true, true, 0);
}
private bool LanguageSetFromCookie()
{
return SetLanguage(Sitecore.Web.WebUtil.GetCookieValue(Sitecore.Context.Site.GetCookieKey("lang")), false, true, 0);
}
private bool LanguageSetFromBrowserPreferences()
{
string langs = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_ACCEPT_LANGUAGE"];
if (!String.IsNullOrEmpty(langs))
{
foreach (string lang in Sitecore.StringUtil.Split(langs, ',', true))
{
string langName = lang;
if (lang.IndexOf(';') > -1)
{
langName = lang.Substring(0, lang.IndexOf(';'));
}
if (SetLanguage(langName, false,false, 0))
{
return true;
}
}
}
return false;
}
private bool LanguageSetFromContextSite()
{
return SetLanguage(Sitecore.Context.Site.Language, false, true, 0);
}
private bool LanguageSetFromDefaultSetting()
{
return SetLanguage(Sitecore.Configuration.Settings.DefaultLanguage, false,true, 0);
}
private bool LanguageSetToFirstExistingLanguage()
{
foreach (Sitecore.Globalization.Language language in Sitecore.Context.Item.Database.Languages)
{
if (SetLanguage(language.Name, false, false, 0))
{
return true;
}
}
return false;
}
private bool SetLanguage(string languageName, bool spanRequests, bool fallback, int fallbackDepth )
{
if (!String.IsNullOrEmpty(languageName))
{
foreach (Sitecore.Globalization.Language compare in Sitecore.Context.Item.Database.Languages)
{
if (languageName.Equals(compare.Name, StringComparison.InvariantCultureIgnoreCase))
{
if (HasVersionInLanguage(Sitecore.Context.Item, compare))
{
SetContextLanguage(compare, spanRequests);
return true;
}
else if (fallback)
{
Sitecore.Data.Items.Item languageItem =
Sitecore.Context.Item.Database.GetItem("/sitecore/system/languages" + compare.Name);
if (languageItem != null)
{
string fallbackLanguageName = languageItem["fallback language"];
if (!String.IsNullOrEmpty(fallbackLanguageName))
{
if (fallbackLanguageName == languageName)
{
string message = String.Format("{0} : invalid fallback language {1} in {2}",
this.GetType().ToString(), fallbackLanguageName,
languageItem.Paths.FullPath);
Sitecore.Diagnostics.Log.Error( message, this );
}
else
{
if ( fallbackDepth < FallbackDepthLimit )
{
return( SetLanguage(fallbackLanguageName, spanRequests, fallback, fallbackDepth++));
}
else
{
string message = String.Format("{0} : Fallback depth limit {1} exceeded processing {2}",
this.GetType().ToString(), FallbackDepthLimit, fallbackLanguageName);
Sitecore.Diagnostics.Log.Warn(message, this);
}
}
}
}
}
}
}
}
return false;
}
private bool HasVersionInLanguage(Sitecore.Data.Items.Item item,Sitecore.Globalization.Language language)
{
Sitecore.Data.Items.Item langItem = Sitecore.Context.Item.Database.GetItem(Sitecore.Context.Item.ID, language);
return langItem.Versions.Count > 0;
}
private void SetContextLanguage(Sitecore.Globalization.Language language, bool spanRequests )
{
Sitecore.Context.SetLanguage(language, spanRequests);
if ( spanRequests && PersistLanguage )
{
string cookieName = Sitecore.Context.Site.GetCookieKey("lang");
Sitecore.Web.WebUtil.SetCookieValue( cookieName, language.Name,DateTime.MaxValue);
}
}
}
}
This code could definitely use some refactoring. The following properties, which default to false unless set in the processor signature in web.config, serve the purposes described:
The following methods serve the purposes described:
One thing that concerns me is how ASP.NET uses the language associated with the current thread. If we assume that the entire system processes the entire request using this thread, or passes this setting to any child threads, then I think this should be reliable. But this is obviously far beyond my knowledge of ASP.NET threading. For me, it seemed to work using the Sitecore.Web.UI.WebControls.FieldRenderer Web control, the <sc:date> XSL extension control, and a custom XSL extension method, but my test system doesn't have the kind of load that might raise threading issues. Updated: New code. 11/14/2008 MSDN Magazine Utility Spotlight Article: 12 Steps To Faster Web Pages With Visual Round Trip AnalyzerThe November 2008 issue of MSDN Magazine has a Utility Spotlight article by Jim Pierson titled "12 Steps To Faster Web Pages With Visual Round Trip Analyzer" (http://msdn.microsoft.com/en-us/magazine/dd188562.aspx). Much of this content is relatively technical, including the explanation of Visual Round Trip Analyzer. But there are also some tips that should be relatively easy to implement for any Sitecore solution. I won't try to summarize the article, but point out and expand on what I expect would be the easiest suggestions to try.
11/10/2008 Southern Methodist University Mailing List for Higher Education and Public Sector Sitecore AdministratorsSitecore USA customer Southern Methodist University has created a mailing list for administrators using or considering Sitecore for their Web solutions. To join the list, see http://sdn.sitecore.net/forum/ShowPost.aspx?PostID=12546#12546. The list owner specifically requests no posting of advertisements on the list. 10/31/2008 C#, .NET, and ASP.NET Coding Standards and Best PracticesI just came across this resource on C# coding standards, which covers some general .NET and ASP.NET best practices: http://weblogs.asp.net/lhunt/pages/CSharp-Coding-Standards-document.aspx Like anything on this subject, this contains some opinion, but it also contains enough useful tips to be worth sharing. 10/20/2008 Editing Controls Do Not Appear in the Sitecore Page EditorThere have been a few forum posts and support cases on this issue recently so I thought it might help to blog about it. If you have deleted the ASP.NET global <form> element from your layout, when you access a content item that uses that layout using the Page Editor, the editing controls do not appear. To correct this issue, re-introduce the <form> element into your layout. The default <form> element used by Sitecore is as follows: <form method="post" runat="server" id="mainform">Insert your controls here.</form> The opening <form> element generally occurs directly after the opening <body> element, and the closing </form> element generally occurs immediately before the closing </body> element, surrounding all of your controls. Visual Studio Debugger Hangs Debugging a Sitecore SolutionI am surprised there are not more forum posts and support cases on this issue, which appears to be an annoying bug in Visual Studio (possibly only 2008). If you have selected the Show All Files toggle in Solution Explorer, and you try to debug by attaching to the ASP.NET worker process, Visual Studio stops responding. To correct this issue, restart Visual Studio, clear the Show All Files toggle, and debug again. Importing and Updating Data with SitecoreWeb Data Source (http://www.webdatasource.com) has published a blog entry of mine, "Importing and Updating Data with Sitecore". You can read it here: http://www.webdatasource.com/2008/10/importing-and-updating-data-with-sitecore/ 10/18/2008 Sorting Sitecore Items by a Field ValueSitecore provides a variety of techniques to allow users and developers to control item sorting. By default, Sitecore sorts the children of every item in alphabetical order by name. Users can sort items in the content tree. For each item, you can override this default by clicking the dialog launcher in the Sort group on the Home tab, and then selecting a sort rule. Sitecore presents items in XML document order - from the top item in the CMS repository down. This order reflects the child sort rule for each item. In an XSL rendering, you can alter the default item order using the <xsl:sort> XSL element. For example, to sort the children of the context item by the date that they were last updated:
<ul>
<xsl:for-each select="$sc_currentitem/item">
<xsl:sort select="sc:fld('__updated',.)" />
<li>
<sc:link>
<xsl:value-of select="@name" />
</sc:link>
</li>
</xsl:for-each>
</ul>
In .NET, you can sort an array of Sitecore.Data.Items.Item objects. One important difference between these two approaches is that in XML, you sort a structure in the XML repository. In .NET, you sort a flat array containing any number of items. To demonstrate this difference, consider a TreelistEx field (or a Checklist field, or a Multilist field, or a Treelist field - any field that stores its value as a pipe-separated list of GUIDs). You could use a TreelistEx field to let the user select some number of related content items, and use a presentation component to link to each of those items. In most cases you want to process the selected items in the order that the user specified them in the field, but in some cases you want to sort them using logic. In .NET you could sort this list of items by almost any criteria, for example the value of a field in each item, by implementing a System.Collections.Generic.IComparer<Sitecore.Data.Items.Item>.
Because the value of a TreelistEx field is a string of GUIDs rather than an XML document, XSL does not provide native facilities to sort this data. This is why Sitecore provides sc:Split() XSL extension method for processing fields that contain a list of pipe-separated GUIDs. The sc:Split() method accepts an item and the name of the field, and returns the IDs as temporary XML structure for easier parsing from XSL. While it might be possible to sort the items referenced by the XML structure returned from sc:Split() using pure XSL, it probably wouldn't be very efficient to write or execute that code, and especially might not be easy for someone else to read. This is probably an appropriate place to use a .NET XSL extension. For more information about XSL extension methods, see the Presentation Component XSL Reference on the Sitecore Developer Network (http://sdn.sitecore.net). To sort the items specified by a TreelistEx field by a field value in each of those items, we need an XSL extension method that accepts three parameters: the item containing the TreelistEx field, a string containing the name of the TreelistEx field, and a string containing the name of the field by which to sort the items. We might want a fourth parameter to reverse the sort order. Because we will be passing the XML representation of an item from the XSL transformation engine to the XSL extension method, our class will inherit from Sitecore.Xml.Xsl.XslHelper so that we can use its GetItem() method, which retrieves the Sitecore.Data.Items.Item for use by .NET code that corresponds to the System.Xml.XPath.XPathNodeIterator() representation of the item that XSL uses. The rest of the .NET code should be pretty straightforward, though maybe it isn't well tested. The little mess at the end of the SortListField() method implementation could be avoided if the GetChildIterator() method of Sitecore.Xml.Xsl.XslHelper was not private. Because Sitecore stores dates in the ISO format corresponding to the .NET format string yyyyMMddTHHmmss, it's probably not necessary to convert these values to dates, but it shouldn't cost much.
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.XPath;
using Sitecore.Data.Items;
namespace Namespace.Xml.Xsl
{
public class XslHelper : Sitecore.Xml.Xsl.XslHelper
{
public virtual XPathNodeIterator SortListField(string listField,
string sortField, XPathNodeIterator ni)
{
return SortListField(listField, sortField, ni, false);
}
public virtual XPathNodeIterator SortListField(string listField,
string sortField, XPathNodeIterator ni, bool reverse)
{
Sitecore.Xml.Packet packet = new Sitecore.Xml.Packet("values");
XPathNodeIterator iterator = ni.Clone();
if(iterator.MoveNext())
{
Sitecore.Data.Items.Item item = GetItem(iterator);
if (item != null)
{
Sitecore.Data.Fields.MultilistField fList = item.Fields[listField];
if (fList!= null)
{
List<Item> itemList = new List<Item>();
foreach(Sitecore.Data.Items.Item pointer in fList.GetItems())
{
if( pointer != null )
{
itemList.Add(pointer);
}
}
Item[] items = itemList.ToArray();
Array.Sort(items, new ItemFieldComparer(sortField));
if ( reverse )
{
Array.Reverse(items);
}
foreach (Sitecore.Data.Items.Item reference in items)
{
packet.AddElement("value", reference.ID.ToString());
}
}
}
}
XPathNavigator navigator = packet.XmlDocument.CreateNavigator()
?? new XmlDocument().CreateNavigator();
navigator.MoveToRoot();
navigator.MoveToFirstChild();
return navigator.SelectChildren(XPathNodeType.Element);
}
}
public class ItemFieldComparer : IComparer<Item>
{
private const int Y_FIRST = -1;
private const int X_EQUALTO_Y = 0;
private const int X_FIRST = 1;
private readonly string _fieldName = null;
public ItemFieldComparer(string fieldName)
{
_fieldName = fieldName;
}
public int Compare(Item x, Item y)
{
if (x == null && y == null)
return X_EQUALTO_Y;
if (y == null)
return X_FIRST;
if (x == null)
return Y_FIRST;
Sitecore.Data.Fields.Field xField = x.Fields[_fieldName];
Sitecore.Data.Fields.Field yField = y.Fields[_fieldName];
if (yField == null && xField == null)
return X_EQUALTO_Y;
if (xField != null
&& (yField == null || String.IsNullOrEmpty(yField.Value))
&& !String.IsNullOrEmpty(xField.Value))
return X_FIRST;
if (yField != null &&
(xField == null || String.IsNullOrEmpty(xField.Value))
&& !String.IsNullOrEmpty(yField.Value))
return Y_FIRST;
if (yField.Value == xField.Value)
return X_EQUALTO_Y;
string fieldType = xField.Type.ToLower();
if (xField.ID == Sitecore.FieldIDs.Sortorder)
fieldType = "integer";
switch (fieldType)
{
case "date":
case "datetime":
return
((Sitecore.Data.Fields.DateField)xField).DateTime.CompareTo(
((Sitecore.Data.Fields.DateField)yField).DateTime);
case "integer":
return Int32.Parse(xField.Value).CompareTo(Int32.Parse(yField.Value));
default:
return xField.Value.CompareTo(yField.Value);
}
}
}
}
This code returns an XML structure something like: <values> <value>GUID</value> ... <value>GUID</value> </values> You can update the default sc namespace to include this method by changing the <extension> element in web.config with namespace http://www.sitecore.net/sc: <extension mode="on" type="Namespace.Xml.Xsl.XslHelper, Assembly" namespace="http://www.sitecore.net/sc" singleInstance="true" /> Then you can then invoke the XSL extension method from XSL renderings as follows, for example to sort the items selected in the field named TreelistExField in the context item by the the value of the field named SortField in each item:
<ul>
<xsl:for-each select="sc:SortListField('TreelistExField','SortField',$sc_currentitem)">
<xsl:for-each select="sc:item(text(),$sc_currentitem)">
<li>
<sc:link>
<xsl:value-of select="@name" />
</sc:link>
</li>
</xsl:for-each>
</xsl:for-each>
</ul>
You can reverse the sort order by passing True as the fourth parameter:
<xsl:for-each select="sc:SortListField('TreelistExField','SortField',$sc_currentitem,true())">
The outer for-each iterates over each of the <value> elements in the XML structure. The inner for-each iterates over the item that corresponds to the GUID within that <value> element. It might seem unusual to for-each over a single element, but actually makes the code more clear by setting the context item within the nested for-each to the referenced item rather than using a variable. Sitecore 6 DocumentationMaybe not everyone knows that Sitecore has released a great deal of documentation for Sitecore 6 on the Sitecore Developer Network (SDN - http://sdn.sitecore.net). In general, the references describe concepts, while the cookbooks describe implementation. For developers, a good place to start is the Sitecore 6 Resources page. I think the best new resources include:
You can download the entire Sitecore 6 API as a Windows Help file. And you can download the Documentation Package containing most of these files. Additionally, I am trying to remember to monitor the Release Notes. You can access information about the various optional Sitecore modules from the SDN Products Page. So far, the feedback on these documents has been quite positive. Use the email link in the footer on SDN to email the Sitecore documentation team with questions, corrections, and suggestions. I will also maintain a list of links to blog posts, forum threads, and other resources that address common issues:
Specific to XSL:
Not really documentation, but some Sitecore Shared Source projects I access frequently:
10/17/2008 Sitecore 6 Link ManagementThe Sitecore 6 Web Content Management System (WCM or CMS) provides new link management features to prevent broken links and images, which detract from the user experience and can affect search engine ranking. A typical HTML Web page may consist of a number of references to other resources, which may or may not exist, possibly on a different Web server. These elements including anchor (<a>) elements that represent links and <img> elements that reference the URLs of images. In addition to anchor and image elements in content maintained by CMS users, presentation components such as Web controls generate links, images, and references to other resources dynamically, such as to provide data-driven navigation. Sitecore can only manage links in the CMS; developers are responsible for the references generated by the presentation components they develop. Any of these elements can reference a resource that does not exist. Broken references in CMS content typically result from:
You can use validation to trap broken external links while the user edits content, or periodically generate a report of broken external links. The CMS can help to manage internal links when a user moves, renames, or deletes a resource in the CMS repository. When a user deletes a resource referenced by another resource, Sitecore prompts the user what to do with those references: break them, remove them, or update them to reference another resource. Sitecore automatically assigns each resource in the CMS repository both a friendly URL and a Global Unique Identifier (GUID). Resources reference other resources using GUIDs instead of paths whenever possible. When a user renames or moves a resource, no other data changes, as the GUID does not change. Sitecore does not need to prompt the user in this condition, as references by GUID do not break. Presentation components transform these GUID references into friendly URLs sent in the response to the Web client. For example, the raw value of a Rich Text Editor (RTE) field containing an image appears as follows: <img src="/~/media/3C6C861508D44B899DE08DC9B84AFF13.ashx?w=200&h=230&as=1" width="200" height="230" alt=""/ >The raw value of a RTE field containing a link to another content item appears as follows: <a href="~/link.aspx?_id=0B16893D260041838FAD668AAA551E21&_z=z">Click here</a>The raw value of an RTE field containing a link to a media item appears as follows: <a href="~/media/87A923D5B58641FEB1949F9ED79E79C3.ashx">Click here</a> XSL extension controls such as <sc:html>, <sc:link>, and <sc:image> automatically transform all GUID references to friendly paths. The sc:field() XSL extension method transforms GUID references to friendly paths, while the sc:fld() extension method returns the raw field value, which may include GUID references. Sitecore .NET developers use various methods to transform GUID references to friendly URLs. The static method Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Data.Items.Item) returns the friendly URL of a content item. For example, to retrieve the URL of the content item requested by the browser: Sitecore.Data.Items.Item item = Sitecore.Context.Item; string url = Sitecore.Links.LinkManager.GetItemUrl(item); The static method Sitecore.Resources.Media.MediaManager.GetMediaUrl(Sitecore.Data.Items.MediaItem) returns the friendly URL of a media item.
For example, to generate a series of <img> elements based on the images selected in the TreelistEx field named FieldName:
Sitecore.Data.Items.Item item = Sitecore.Context.Item;
Sitecore.Data.Fields.MultilistField images = item.Fields["FieldName"];
foreach (Sitecore.Data.Items.Item pointer in images.GetItems())
{
Sitecore.Data.Items.MediaItem media
= new Sitecore.Data.Items.MediaItem(item);
string url = Sitecore.StringUtil.EnsurePrefix('/',
Sitecore.Resources.Media.MediaManager.GetMediaUrl(media));
}
Note that Sitecore does not include the leading slash character ("/"),resulting in URLs that can exceed browser, Web server, or operating system limits for path lengths. Always ensure media URLs sent to the browser begin with the slash character. The static method Sitecore.Links.LinkManager.ExpandDynamicLinks(string) transforms GUID references in RTE field values to friendly URLs. For example, to transform all references in the field named text in the context item: Sitecore.Data.Items.Item item = Sitecore.Context.Item; string before = item.Fields[FieldName].Value; string after = Sitecore.Links.LinkManager.ExpandDynamicLinks(before); To configure how Sitecore generates friendly URLs, edit the attributes of the /configuration/sitecore/linkManager element in web.config. The comments above this element document the purpose of each attribute. For example, to cause content item URLs for a multilingual site to always include the language code (/lang/page.aspx), set languageEmbedding to always. You could also configure this element to use a class other than the default Sitecore.Links.LinkProvider to generate URLs. 10/9/2008 Sysinternals PlugOne of my coworkers sent my team a link to Sysinternals ZoomIt, and something else had indicated that Microsoft has updated some of these tools recently, so this is probably worth a blog post. Sysinternals covers several great utilities. I think it's best to get the suite: http://technet.microsoft.com/en-us/sysinternals/0e18b180-9b7a-4c49-8120-c47c5a693683.aspx Of these, the utilities I have seen most useful for working with ASP.NET and other applications on Microsoft Windows include:
9/26/2008 Passwords? We Don't Need No Stinkin' passwords!You can avoid the need to enter your password every time you log in to Sitecore. This could save you literally seconds each day, almost as much time as it takes to read this post or implement the change. Add code such as the following within the <asp:Login> control in /sitecore/login/default.aspx: <tr> <td colspan="2" align="center"> <asp:Checkbox ID="RememberMe" runat="server" Text="Rememeber Me"></asp:Checkbox> </td> </tr> Log in to Sitecore (I use the desktop, haven't tried the other interfaces) and bookmark the URL. Close the browser without logging out. Re-open the browser and access the bookmark. Updated 30.June.2009: Sitecore CMS 6.1 includes the Remember Me feature by default. 9/6/2008 Remove Sitecore Grid DesignerIf you don't intend to use Sitecore Grid Designer, you can remove it from both Content Editor and Developer Center.
To remove Grid Designer in Content Editor, update the data templates for layouts and sublayouts:
To remove Grid Designer from Developer Center, you have to replace the code-behind for /sitecore/shell/Applications/Layouts/IDE/Editors/HTML/IDE Html Editor.xml. This code should do:
namespace Namespace.Shell.Applications.Layouts.IDE.Editors.HTML
{
public class IDEHtmlEditorForm : Sitecore.Shell.Applications.Layouts.IDE.Editors.HTML.IDEHtmlEditorForm
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.DesignButton != null)
{
this.ShowDesign();
}
}
}
}
In /sitecore/shell/Applications/Layouts/IDE/Editors/HTML/IDE Html Editor.xml, comment this: <CodeBeside Type="Sitecore.Shell.Applications.Layouts.IDE.Editors.HTML.IDEHtmlEditorForm,Sitecore.Client"/>
and add this, replacing Namespace and Assembly with your namespace and assembly. <CodeBeside Type="Namespace.Shell.Applications.Layouts.IDE.Editors.HTML.IDEHtmlEditorForm,Assembly"/>
You might also want to move the Grid tab to the end of the tab strip. Move this: <Toolbutton ID="GridButton" Header="Grid" Icon="Business/16x16/table_edit.png" IconSize="id16x16" Down="true" Click="ShowGrid" ToolTip="Show Grid Editor Mode"/>
after this: <Toolbutton ID="LiveButton" Icon="Network/16x16/earth_view.png" IconSize="id16x16" Click="ShowLiveMode" ToolTip="Toggle Live Mode"/> 9/5/2008 Disable Word 2007 Full Screen Reading View for AttachmentsI find it really annoying that Word uses the Full Screen Reading View when I open an email attachment. The solution is very simple, but even so I will manage to forget it, so I am posting it here: -Open an attachment in Full Screen Reading View. -Click View Options in the top right corner. -Select "Don't Open Attachments in Full Screen". If you want to change it back: -Click the View tab. -In the Document Views group, click Full Screen Reading. -Click View Options in the top right corner. -Clear "Don't Open Attachments in Full Screen". 4/29/2008 Add Page Editor to the Publish tab in Sitecore 6While setting layout settings using Device Editor in Sitecore, I'm too lazy to select the Publish tab before opening Page Editor to see the changes. I want the Page Editor button on the Presentation tab too.
Update: Alternatively, you can copy /sitecore/content/Applications/Content Editor/Ribbons/Strips/Presentation/Layout to /sitecore/content/Applications/Content Editor/Ribbons/Strips/Publish. 4/2/2008 Sitecore UI Command to Copy Item's Template's Path to the Windows ClipboardUpdated: See the comment at the end of this entry before bothering to read it. While writing technical documentation for Sitecore 6, I must frequently refer to the data template associated with an item using the fully qualified path (/sitecore/templates/folder/…/template). I have added a command to the Show chunk in the developer tab to prompt with this value and ask if it should be copied to the clipboard. Implementation 1. Compile the following code into a Visual Studio project. using System; using System.Collections.Specialized; namespace Sitecore.Starterkit.Shell.Framework.Commands.Clipboard { public class CopyTemplatePathToClipboard : Sitecore.Shell.Framework.Commands.Command { public override void Execute(Sitecore.Shell.Framework.Commands.CommandContext context) { if (context.Items.Length == 1) { Sitecore.Data.Items.Item item = context.Items[0]; NameValueCollection parameters = new NameValueCollection(); parameters["id"] = item.ID.ToString(); parameters["database"] = item.Database.Name; Context.ClientPage.Start(this, "Run", parameters); } } public override Sitecore.Shell.Framework.Commands.CommandState QueryState(Sitecore.Shell.Framework.Commands.CommandContext context) { Sitecore.Diagnostics.Error.AssertObject(context, "context"); if (context.Items.Length != 1) { return Sitecore.Shell.Framework.Commands.CommandState.Hidden; } if (!UIUtil.IsIE()) { return Sitecore.Shell.Framework.Commands.CommandState.Hidden; } return base.QueryState(context); }
protected void Run(Sitecore.Web.UI.Sheer.ClientPipelineArgs args) { Sitecore.Data.Items.Item itm = Sitecore.Configuration.Factory.GetDatabase(args.Parameters["database"]).GetItem(args.Parameters["id"]).Template;
if (args.IsPostBack) { if (args.Result == "yes") { Sitecore.Web.UI.Sheer.SheerResponse.Eval("window.clipboardData.setData(\"Text\", \"" + itm.Paths.FullPath + "\")"); } } else { Sitecore.Web.UI.Sheer.SheerResponse.Confirm(Sitecore.Globalization.Translate.Text("The path to the template associated with the current item is\n\n{0}\n\nDo you want to copy it to the clipboard", new object[] { itm.Paths.FullPath })); args.WaitForPostBack(); } } } } 2. Add this line to /App_Config/commands.config: <command name="clipboard:copytemplatepathtoclipboard" type="Sitecore.Starterkit.Shell.Framework.Commands.Clipboard.CopyTemplatePathToClipboard,Sitecore.Starterkit" /> 3. Create an item named TemplatePath under /sitecore/content/Applications/Content Editor/Ribbons/Chunks/Show/TemplatePath based on the /sitecore/templates/System/Ribbon/Small Button template (I had to implement this feature due to constantly copying paths like this one). As a shortcut, just duplicate the existing Path item with a new name. 4. Set Header to Template Path. 5. Set Icon to applications/16x16/window_dialog.png. 6. Set Click to clipboard:copytemplatepathtoclipboard. 7. Set Tooltip to Copy the path of the template associated with the current item to the clipboard. It only takes a few facts and a tool to figure out how to customize the UI like this. Right-click the tab strip to enable or disable the developer tab. The Sitecore core database controls the user interface using commands defined in /App_Config/Config. Use Lutz Roeder’s Reflector for .NET (http://www.aisto.com/roeder/dotnet) to replicate existing functionality, in this case the Path command in the Show group of the Developer tab. To manage the core database, select core using the icon in the task bar in the desktop, then open Content Editor. Under Applications, find Content Editor. Expand Ribbons, Strips and Chunks (groups) to investigate how the system assembles the ribbon. The Developer tab uses the Show group defined under /sitecore/content/Applications/Content Editor/Ribbons/Chunks/Show. The /sitecore/content/Applications/Content Editor/Ribbons/Chunks/Show/Path item corresponds to the Path command. According to /App_Config/Commands.config, this command invokes Sitecore.Shell.Framework.Commands.Clipboard.CopyPathToClipboard. Use Reflector to copy the relevant code from this class to a custom class and update as needed. I expect this might work on 5.3, possibly with slight modifications. If I hadn't documented the process, this might have saved me time. 2/24/2008 Copy Sitecore Layout SettingsThis will likely soon appear as a FAQ or scrapbook entry, but you saw it here first. How do I copy layout settings or some other complex field value from one Sitecore item to another, or from an item to its template's standard values?
Sitecore XpressSitecore (http://www.sitecore.net) has released Sitecore Xpress (http://xpress.sitecore.net), an edition of Sitecore CMS with scalability limitations and a license restricted to one year. Sitecore Xpress includes the Sitecore Starter Kit instead of an empty solution. The Starter Kit provides a learning environment not intended for production solutions. Note that the Starter Kit and hence Sitecore Xpress runs in Sitecore live mode, without a web database or the need for publishing. While the Sitecore Developer Network (SDN - http://sdn.sitecore.net) documents much of Sitecore's comprehensive ASP.NET web development platform, Sitecore encourages professional developers to attend Sitecore Certified Developer training (http://www.sitecore.net/Training/Course%20Overview/Developer%20level%20one.aspx). Sitecore provides support to certified developers working for partners and customers with valid support agreements through the support portal (http://sdn5.sitecore.net/Support.aspx after completing the SCD1 exam). Access to resources on SDN developer including forums (http://sdn5.sitecore.net/Forum.aspx) does not require developer certification. Training materials and resources relevant to new developers are available on the Sitecore developer network (http://sdn5.sitecore.net/Developer/Training%20Materials.aspx), as is a developer cookbook (http://sdn5.sitecore.net/Developer/Developers%20Cookbook.aspx). To install Xpress, see the requirements for using Sitecore, though Sitecore will generally run with less hardware than specified (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/requirements.aspx). Xpress does not currently support a remote database server. Sitecore requires an IIS document root; do not attempt to install to a subdirectory or virtual directory. Developers who work with numerous web solutions on Windows XP may implement multiple IIS web sites (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/ZIP%20installation/Multiple%20XP%20Sites.aspx). This restriction is only relevant to Windows XP; Windows Vista, 2003 and 2008 support multiple active web sites. Note that Windows XP and possibly Vista also limits the number of concurrent HTTP requests to 5, which can cause issues if a browser has been tweaked to allow more than 2 concurrent HTTP connections, or if there are too many active browser windows accessing an IIS instance. Download and install Microsoft SQL Server 2005 Express Edition (http://www.microsoft.com/sql) and install according to SDN instructions (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/ZIP%20installation/SQL%20Server/SQL%202005%20Express.aspx). Choose advanced configuration options. Install Management Studio Express, used mixed mode authentication and remember the password. If an existing installation of SQL Server supports only Windows authentication, reconfigure SQL server for mixed mode authentication as described (under Visual Studio 2005 at http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/ZIP%20installation/SQL%20Server/SQL%202005%20Express.aspx). See Sitecore pre-installation instructions for Vista (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/Installing%20Sitecore%20on%20Vista/Windows%20Vista%20Pre-installation%20Steps.aspx). Get the installation executable and a license from the Xpress website (http://xpress.sitecore.net). Follow the instructions on SDN to install Sitecore (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/EXE%20Installation.aspx). Because Xpress does not support a remote database server, follow the instructions under Sitecore Client Part Setup but choose to install the client and databases when prompted.
If the installer fails for any reason, see troubleshooting instructions on SDN (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/Troubleshooting/Installer%20Issues.aspx). Report issues on the SDN forums if needed (http://sdn5.sitecore.net/Forum.aspx). Add the site to the browser’s trusted sites and configure the browser as required (http://sdn5.sitecore.net/SDN5/Articles/Administration/Configuring%20IE7.aspx). The most common installation issue is generally that default.aspx is not a default document in IIS. The result can be permission, directory listing and virtual directory listing denied error messages to in the browser. A future version of the installer will register default.aspx automatically, but for now register default.aspx (http://sdn5.sitecore.net/Products/Sitecore%20V5/Installation/Troubleshooting/Registering%20default,-d-,aspx.aspx). Configuration changes through IIS7 user interfaces erase text values in web.config elements. To register default.aspx on IIS7, ensure the default document feature of IIS is installed, backup web.config and update manually. <system.webServer> <defaultDocument> <files> <clear /> <add value="default.aspx" /> For more information or to request a hotfix, see Microsoft support (http://support.microsoft.com/kb/930451). The default password is empty for the default Sitecore and Sitecore Xpress user named admin. Assuming Xpress is much like Sitecore, be sure to select the Advanced tab on the login screen and log in to the Sitecore Desktop. See SDN for instructions to create a Visual Studio project (http://sdn5.sitecore.net/Articles/API/Creating%20VS2005%20Project.aspx), and remember to set the copy local property on any reference to an assembly in the /bin directory to false. 2/23/2008 Healthy OutlookMy workplace uses Microsoft Outlook 2007, Citrix GoToMeeting and ESET NOD32 Antivirus. Sales would like to add something like Microsoft CRM client, but this isn’t working because remote machines are not on the domain yet; I might have tips about that later. In general, applications that run when the computer boots or when a user logs in do not increase the stability of the system, and plug-ins that run when applications run do not increase the stability of the application. Several factors can improve the Outlook user experience, though some of this is specific to requirements for our custom applications. So this isn't all about Outlook. I should warn you that I prefer to make updates than comments, unless they change the meaning or otherwise conflict with existing content. ESET NOD32 seems to behave significantly differently depending on the installed version and options. I seem to remember a NOD32 option on the tools menu, but maybe that was specific to Office 2003 or a previous version of NOD32. Like any other software installation, always review all advanced installation options when installing NOD32. But unlike installing most other software, enable most ESET NOD32 options other than the following, accessed by pressing F5 in the NOD32 user interface.
In Outlook:
Right-click on the gotomeeting icon in the notification area of the Windows task bar and choose preferences. Select Integrations and clear all of the options. To improve outlook performance, reduce the size of your .ost file (Mailbox). You can determine the size of the existing .ost file by choosing Account Settings from the Tools menu in Outlook and choosing Open Folder on the Data Files tab. I reduced my .ost from almost 2GB to less than 600MB using the tips below. To reduce storage space and improve performance, avoid including your signature whenever you can. Get into the habit of using SHIFT+DEL to permanently delete messages instead of moving them to the deleted items folder. This is especially important for messages with large attachments and anything you never want to see again, such as spam. I heard it's a good idea to move spam to the spam folder before deleting it. Maybe it adds puts it in the spam category, adds it to the blocked sender list, sends some data to some antispam software vendor(s), or does something else based on that action. Periodically, remove obsolete messages, especially spam and messages containing large attachments, from various folders. Sort the deleted items folder by categories and SHIFT+DEL all items marked as spam. Sort by size and SHIFT+DEL large messages (do the same for the sent items folder). Sort by sender and SHIFT+DEL messages from distribution lists and automated processes. SHIFT+DEL anything else you are comfortable deleting. Navigate to any junk email and spam folders and SHIFT+DEL all messages. You can generally do the same in the folders under Sync Issues. Use SHIFT+DEL whenever possible to optimize the system. I found that deleting or even moving things, especially from the deleted items folder, can lead to some annoying prompting. As a workaround, sort the folder by icon and SHIFT+DEL any calendar entries first. Another way to reduce the size of the.ost file is to move data to a .pst file such as personal folders or archive. If you're like me, most of your mail is in deleted items and sent items (if most of your mail is in your inbox, we need to have a conversation about your productivity). You probably don't use this mail, but you want to keep it around for one reason or another. If you don't already see Archive Folders in the folder list, choose Archive from the File menu. Select any folder that does not contain many messages or child folders, choose a date years in the past (before you started using Outlook) and press the OK button. This should create the archive file but not archive any messages. Create a folder named something like "sent-2007" under Archive Folders. Sort the sent items folder by date, and move anything sent in 2007 or before to the corresponding archive folder. Do the same for deleted items. Periodically, such as after removing or archiving a large amount of data, compress the .ost file. In Outlook, choose account settings from the tools menu (what ribbon?). On the email tab, select the exchange, change, more settings, advanced, offline folder file settings, compact now. You probably won't be able to use Outlook for some time, and probalby shouldn't use webmail during that time. Remember to defragment your hard disks once in a while, such as after deleting a bunch of data in Outlook or elsewhere. On to browser configuration. In Internet Explorer, choose internet options from the tools menu to access the internet options tabs. On the general tab, select tab settings and choose always open pop-ups in a new tab.
Whenever possible, add patterns such as *.domain.tld to trusted sites rather than specific web sites. See the internal index page for ideas of patterns to add, as well as links to most internal applications. On the security tab, select trusted sites, then sites. Clear server verification if needed and add each site or pattern. If you’ve been annoyed by new windows opening when you enter navigate from one site to another, consider clearing the protected mode checkbox for each level of site except restricted sites while you’re here. Implement the following on the advanced tab.
Remember that configuring Windows to use a browser other than Internet Explorer can damage the VPN connection. Clear the browser cache (tools > delete browsing history > delete temporary internet files and potentially cookies), restart IIS (command prompt, net stop w3svc, net start w3svc), apply Microsoft updates (http://update.microsoft.com) and reboot the machine before seeking support for issues with browser-based applications. Remember that webmail generally works on the road when the ports used by Outlook are blocked. We’ve found two issues worth correcting in the default SCPrinters installation. Since it doesn’t work on local web sites anyway, disable google analytics whenever possible to improve performance. Specifically, edit the SCPrinters master layout. In Developer Center, switch to the HTML tab. Comment or delete the following code, then select the design tab and save. <!-- <script src="http://www.google-analytics.com/urchin.js" type="text/javascript"> </script> <script type="text/javascript"> _uacct = "UA-236354-2"; if (typeof(urchinTracker) != "undefined") { urchinTracker(); } </script> --> Additionally, set <trace enabled="false" in the site’s web.config (typically C:\inetpub\sitecore\scprinters). It might be worthwhile to update <setting name="LicenseFile" to something like C:\inetpub\sitecore\license.xml so the license can be replaced in one place for all sites. One of the sales people said that if they don't use Sitecore for about 20 minutes, it's slow when they access it again. Apparently updating the KeepAlive setting to the right URL, and/or updating the sessionState timeout from 20 minutes to 60, address this issue, but it could also be a good idea to configure the application pool not to restart. Adding the following as the first line in a license makes the browser display a report instead of the XML contents of the license. <?xml-stylesheet type="text/xsl" href="http://resources.thedotnetcms.com/license.xsl"?> Sysinternals, recently acquired by Microsoft, provides autoruns, an excellent tool for disabling startup prorams (http://technet.microsoft.com/en-us/sysinternals/bb963902.aspx). Don’t disable things randomly, but consider whether you really need each entry listed under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run, HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run and the startup folder of the programs menu for both all users and the specific user. The following features and many more generally function with their startup programs disabled.
I’d like to investigate an issue with QuickTime installed by iTunes causing Outlook to remain open after exiting, but for now I’ll just say don’t shut the machine down until task manager, or process explorer if you use sysinternals (http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx), reports outlook.exe is not running. I prefer Foxit PDF reader (http://www.foxitsoftware.com). Clear advertisement on the view menu. I’m not sure how Foxit thinks they can sell PDF generation software but if you have Office 2007 you can get the PDF add-in (http://www.microsoft.com/downloads/details.aspx?FamilyId=4D951911-3E7E-4AE6-B059-A2E79ED87041&displaylang=en). To generate a PDF file in Microsoft Word, save as PDF and select appropriate bookmark options. Speaking of Microsoft Office Word 2007...I use keyboard shortcuts to apply common styles. I like at least ALT-1 through ALT-6 for headings 1 through 6, ALT-T for a fixed-width font, and ALT-C for Normal. I have not found a way to set a shortcut for Clear All.
I write technical documentation for a software product. While Word’s grammar check has some annoying issues, and is obviously no substitute for human editing, it can provide some value when you have read some text too many times.
The following is a list of the the free software (except ESET) that I typically install on any machine:
I guess this really should have been a few different posts. |
|
|