torsdag, august 31, 2006

A Sitecore Core Developer goes solo

I made a decision a couple of months ago of going back to my old discipline of software consulting. This means that I have resigned my position at Sitecore as a Core Developer and QA manager, and have created a Software Consultancy firm called “Delegate ApS” (I know, quite C#’ish)

My decision has been a hard one and has nothing to do with me not being happy with working on this amazing product. I simply missed the kick of being around real customers and following projects through.

During the time of employment at Sitecore I have learned allot of hardcore stuff. Specially working with Ole and Jacob (Core Developers and innovators of Sitecore) has been extremely giving. Before working at Sitecore, I was a consultant and have therefore worked with allot of developers through the years. Without comparison, the guys at Sitecore are the most competent. I will truly miss the high level of abstraction there guys are able to discuss architecture on.

However, this will not be the end of my work with Sitecore. Sitecore will make sure that I become a Sitecore Certified Trainer as soon as possible and I have decided that my new firm (Delegate ApS ) is to become a certified Sitecore Partner within a month or so. This means that I will finally sit on the other end of the table and experience actually working with the product, instead of creating the product.

All the luck in the world to Sitecore here at the end of my voyage as an Sitecore employee.

tirsdag, maj 02, 2006

InPlaceEditExtender for April CTP release of ATLAS

Nikhil Kothari created a fine ATLAS control named InPlaceEditExtender. The control does not work on the April CTP of ATLAS, so I modified the javascript to comply with the April CTP release, as well as added some support for dropdowns.
Download the control from Nikhil's blog, and replace the javascript with the following javascript and you will have the InPlaceEditExtender control for April CTP:

Type.registerNamespace('nStuff.Samples.InPlaceEdit');

 

nStuff.Samples.InPlaceEdit.InPlaceEditBehavior = function() {

    nStuff.Samples.InPlaceEdit.InPlaceEditBehavior.initializeBase(this);

 

    var _labelCssClass;

    var _labelHoverCssClass;

 

    var _labelElement;

    var _isEditing = false;

    var _isInputControl = false;

 

    var _textBoxBlurHandler;

    var _labelFocusHandler;

    var _labelMouseOverHandler;

    var _labelMouseOutHandler;

    var _validatedHandler;

 

    this.get_isEditing = function() {

        return _isEditing;

    }

 

    this.get_labelCssClass = function() {

        return _labelCssClass;

    }

    this.set_labelCssClass = function(value) {

        _labelCssClass = value;

    }

 

    this.get_labelHoverCssClass = function() {

        return _labelHoverCssClass;

    }

    this.set_labelHoverCssClass = function(value) {

        _labelHoverCssClass = value;

    }

 

    this.beginEdit = function() {

        if (_isEditing) {

            return;

        }

 

        var textBoxElement = this.control.element;

        textBoxElement.style.display = '';

        _labelElement.style.display = 'none';

        if(!textBoxElement.disabled)

            textBoxElement.focus();

 

        _isEditing = true;

        this.raisePropertyChanged('isEditing');

    }

 

    this.dispose = function() {

        if (_labelElement) {

            _labelElement.detachEvent('onfocus', _labelFocusHandler);

            _labelElement.detachEvent('onmouseover', _labelMouseOverHandler);

            _labelElement.detachEvent('onmouseout', _labelMouseOutHandler);

 

            _labelElement = null;

            _labelFocusHandler = null;

            _labelMouseOverHandler = null;

            _labelMouseOutHandler = null;

        }

 

        if (_textBoxBlurHandler) {

            var textBoxElement = this.control.element;

            textBoxElement.detachEvent('onblur', _textBoxBlurHandler);

            _textBoxBlurHandler = null;

        }

 

        if (_validatedHandler) {

            this.control.validated.remove(_validatedHandler);

            _validatedHandler = null;

        }

 

        nStuff.Samples.InPlaceEdit.InPlaceEditBehavior.callBaseMethod(this, 'dispose');

    }

 

    this.endEdit = function() {

        if (!_isEditing) {

            return;

        }

        if (_isInputControl && this.control.get_isInvalid()) {

            return;

        }

 

        var textBoxElement = this.control.element;

        _labelElement.innerHTML = textBoxElement.value;

        _labelElement.style.display = 'block';

        textBoxElement.style.display = 'none';

 

        _isEditing = false;

        this.raisePropertyChanged('isEditing');

    }

 

    this.getDescriptor = function() {

        var td = nStuff.Samples.InPlaceEdit.InPlaceEditBehavior.callBaseMethod(this, 'getDescriptor');

 

        td.addProperty('isEditing', Boolean, /* readOnly */ true);

        td.addProperty('labelCssClass', String);

        td.addProperty('labelHoverCssClass', String);

        td.addMethod('beginEdit');

        td.addMethod('endEdit');

        return td;

    }

 

    this.initialize = function() {

        nStuff.Samples.InPlaceEdit.InPlaceEditBehavior.callBaseMethod(this, 'initialize');

 

        _labelElement = document.createElement('LABEL');

 

        var textBoxElement = this.control.element;

        var textBoxBounds = Sys.UI.Control.getBounds(textBoxElement);

        var containerElement = document.createElement('SPAN');

 

        textBoxElement.parentNode.insertBefore(containerElement, textBoxElement);

        containerElement.appendChild(textBoxElement);

        containerElement.appendChild(_labelElement);

 

        textBoxElement.style.display = 'none';

        if(textBoxElement.tagName == 'SELECT')

            _labelElement.innerHTML= textBoxElement.options[textBoxElement.selectedIndex].text;

        else

            _labelElement.innerHTML = textBoxElement.value;

        _labelElement.tabIndex = textBoxElement.tabIndex;

        _labelElement.className = _labelCssClass;

        _labelElement.style.display = 'block';

        _labelElement.style.width = textBoxBounds.width + 'px';

        _labelElement.style.height = textBoxBounds.height + 'px';

 

        _textBoxBlurHandler = Function.createDelegate(this, this._onTextBoxBlur);

        _labelFocusHandler = Function.createDelegate(this, this._onLabelFocus);

        _labelMouseOverHandler = Function.createDelegate(this, this._onLabelMouseOver);

        _labelMouseOutHandler = Function.createDelegate(this, this._onLabelMouseOut);

 

        textBoxElement.attachEvent('onblur', _textBoxBlurHandler);

        if (Sys.Runtime.get_hostType() == Sys.HostType.InternetExploreJ) {

            _labelElement.attachEvent('onfocus', _labelFocusHandler);

        }

        else {

            _labelElement.attachEvent('onclick', _labelFocusHandler);

        }

        _labelElement.attachEvent('onmouseover', _labelMouseOverHandler);

        _labelElement.attachEvent('onmouseout', _labelMouseOutHandler);

 

        if (Sys.UI.InputControl.isInstanceOfType(this.control)) {

            _isInputControl = true;

            _validatedHandler = Function.createDelegate(this, this._onValidated);

            this.control.validated.add(_validatedHandler);

        }

    }

 

    this._onLabelFocus = function() {

        if (_labelHoverCssClass && _labelHoverCssClass.length) {

            Sys.UI.Control.removeCssClass(_labelElement, _labelHoverCssClass);

        }

 

        this.beginEdit();       

    }

 

    this._onLabelMouseOut = function() {

        if (_labelHoverCssClass && _labelHoverCssClass.length) {

            Sys.UI.Control.removeCssClass(_labelElement, _labelHoverCssClass);

        }

    }

 

    this._onLabelMouseOver = function() {

        if (_labelHoverCssClass && _labelHoverCssClass.length) {

            Sys.UI.Control.addCssClass(_labelElement, _labelHoverCssClass);

        }

    }

 

    this._onTextBoxBlur = function() {

        this.endEdit();

    }

 

    this._onValidated = function(sender, eventArgs) {

        if (this.control.get_isInvalid()) {

            this.beginEdit();

        }

    }

}

//Type.registerSealedClass('nStuff.Samples.InPlaceEdit.InPlaceEditBehavior', Sys.UI.Behavior);

nStuff.Samples.InPlaceEdit.InPlaceEditBehavior.registerSealedClass('nStuff.Samples.InPlaceEdit.InPlaceEditBehavior', Sys.UI.Behavior);

Sys.TypeDescriptor.addType('nk', 'inPlaceEdit', nStuff.Samples.InPlaceEdit.InPlaceEditBehavior);

tirsdag, april 11, 2006

Sitecore performance

ScottGu posted a blog entry where he explains the danger of setting debug=true in production enviroments.

The consequenses include:

1) Compilation of pages takes longer
2) Code executes slower
3) More memory is used at runtime
4) Scripts and images from WebResources.axd are not cached.

This is specially Sitecore relevant.


debug="false" is our friend

For details, see Scott's blog post.

http://weblogs.asp.net/scottgu/archive/2006/04/11/442448.aspx

fredag, april 07, 2006

.NET Remoting in Sitecore

The upcoming release of Sitecore (I guess it will be named Sitecore V5.3) will include a lot of new cool features. One of my favorite new features Im working on is remote invocation of Sitecore objects through .NET Remoting - giving you remote access to almost the entire Sitecore API (SitecoreKernel.dll) from any .NET client.

Hopefully the Remoting features will be stable enough to make the final release.

To enable your non-sitecore project (for example a Forms application) to use Sitecore Remoting, simply add a reference to Sitecore.Kernel.dll and you are good to go.

Access to Sitecore API is achieved through the new Sitecore.Remoting namespace, where you will have to know about two new types: The RemoteFactory and RemotingClientConfigurator. Only one line of code is necessary in order to get a remote reference to Sitecore:

Sitecore.Remoting.RemotingClientConfigurator.Configure(_sitecoreurl, "admin", "");



This snippet sets up the connection to Sitecore at a given url, with the username 'admin' and the empty password.

To access Sitecore objects, simply use the new RemoteFactory object (a remoting enabled version of the Factory class):

RemoteFactory factory = new RemoteFactory();



You now have access to all Sitecore objects. For example:

factory.GetDatabase("master").GetRootItem()



This will get you the root item of the master database.

In a later post, I will show how Sitecore can be managed and scripted through Microsofts new shell: MONAD using the new Remoting features.

Hopefully other more or less useful programs will be developed as a result of the introduction of .NET Remoting in Sitecore. I talked to Alexy Rusakov (Sitecore developer from our Ukraine division) about creating an addin to the Google toolbar showing a list of items who's workflow step is assigned to a specific user.

Only your imagination is the limit :-)


Regards

tirsdag, marts 28, 2006

Atlas StopableTimer

I tried out the March CTP release of Microsofts new Ajax implementation Atlas. The library now has a "go live" license making it even more interesting for us real life programmers.

The TimerControl included in Atlas March CTP has a problem. It cannot be stopped ones started. I guess that this is intentional (that’s what the documentation says) but the fact remains. You often want to be able to update a page, only while some task is run on the server.

A user on the Atlas forum named Rama Krishna (that’s his username) came up with a clean and great solution to create a stopable timer control (see the post here). However, the posted control only worked on the January CTP release.

I took his control and modified and updated it for the March CTP release (All credit to Rama).

The control emits a client-side timer control (xml) and assigns an id to it. (The base TimerControl does not). Then a small piece of java script is registered to handle enabling and disabling the timer at startup.

First put the following in the App_Code folder:

using System;

using Microsoft.Web.UI.Controls;

 

namespace CustomControls {

 

    public class StopableTimer : TimerControl {

        public StopableTimer() {

        }

 

        protected override void OnPreRender(EventArgs e) {

            base.OnPreRender(e);

            if (Page.IsPostBack) {

              Page.ClientScript.RegisterStartupScript(

          Page.GetType(), "TimerStop",

                    "Sys.Application.findObject('" +

            UniqueID +

            "').set_enabled(" +

            (Enabled ? "true" : "false") +

            ");"

                    , true);

          }

        }

 

        protected override void RenderScript(Microsoft.Web.Script.ScriptTextWriter writer) {

            writer.WriteStartElement("timer");

            writer.WriteAttributeString("id", UniqueID);

            writer.WriteAttributeString("interval",

          Interval.ToString(System.Globalization.CultureInfo.InvariantCulture));

            writer.WriteAttributeString("enabled", Enabled.ToString());

            writer.WriteStartElement("tick");

            writer.WriteStartElement("postBack");

            writer.WriteAttributeString("target", UniqueID);

            writer.WriteAttributeString("eventArgument", string.Empty);

            writer.WriteEndElement();

            writer.WriteEndElement();

            writer.WriteEndElement();

        }

    }

}



When you have created the control, you only need to register it on you aspx page - and use it as you would use a TimerControl

<%@ Register Assembly="App_Code" Namespace="CustomControls" TagPrefix="AppCode" %>

fredag, december 30, 2005

Sitecore ASP.NET 2.0 final version released

We will release Sitecore version 5.2.0.2 today. This is the ASP.NET 2.0 version of Sitecore version 5.1.1.8

torsdag, december 22, 2005

Sitecore Portal

This post is a draft of how to use the build in portal functionality in Sitecore V5.1.1.x. Please provide input on its content so that this post can become an article in time.

Using the ”Sitecore Today” portal in you own applications.
Sitecore has build in a simple portal framework used in some applications in the Sitecore GUI client. It is possible to use this framework in your own applications by the means of creating some XML (XAML like) controls and some code. This is an instruction on how to get started.

1) Create the Portal Layout
The first thing you need to do is to create the layout that defines the portal. To create such layout, start the layout studio and click File -> new -> XML Layout. The “Create XML Layout wizard” appears.

Press next and name the layout what you like. For this example the layout is called “Portal Demo”. Press next two times to place the layout in the layouts folder, and finish the wizard.

The layout studio opens the newly created xml layout and displays its content in a window. Delete the content of the editor, and replace it with the following:

<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
<PortalDemo>
<FormPage Scroll="yes">
<CodeBeside Type="Example.PortalDemo,MyDll"/>
<Stylesheet Src="/sitecore/portal/tech/tech.css"/>
<Portal ID="PortalDemo" DataSource="/sitecore/content/Home/PortalDemo/Portal" RefreshPageOnRedraw="true">
<Border Class="Portal">
<GridPanel Columns="3" Width="100%" Height="100%" CellSpacing="4" CellPadding="4">
<PortalZone ID="Left" GridPanel.Width="33%" GridPanel.Class="PortalColumn" GridPanel.VAlign="top" GridPanel.Height="100%"/>
<PortalZone ID="Mid" GridPanel.Width="33%" GridPanel.Class="PortalColumn" GridPanel.VAlign="top"/>
<PortalZone ID="Right" GridPanel.Width="33%" GridPanel.Class="PortalColumn" GridPanel.VAlign="top"/>
</GridPanel>
</Border>
</Portal>
<Frame ID="Sidebar" Width="6" Height="100%" Style="position:absolute; left:expression(parentNode.clientWidth-6); top:0"/>
</FormPage>
</PortalDemo>
</control>

The fist element defines that this is a control and the second element <PortalDemo> defines the name of the layout. The portal uses the FormPage <FormPage Scroll="yes"> with scrolling and has a codeBeside class type of PortalDemo located in the dynamic linked library MyDll.
The creation of the codeBeside type will be explained in the next step. The <Stylesheet/> element points to the same style sheet that is used for the “Sitecore Today” application found in the Sitecore client, but can just as well be replaced by our own custom style sheet to achieve you own look and feel.

The next element <Portal> defines the actual portal and it defines where in the content tree its portlets (Sitecore items) are to be found. We will create these items shortly.

The portal element defines the portalZones where portlets can be placed by Id. In this example we have defined tree zones named Left, Mid and Right.

The last element <Frame> defines a sidebar users can invoke to add and remove portlets to their portal.

2) Create the layout code beside class
As seen, the portal defines a code beside class, with the sole purpose of adding portlets to the sidebar.

Create a new class Named PortalDemo in the namespace Example. The source should look like this:

using System;
using Sitecore;
using Sitecore.Text;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
namespace MyPortal {
public class RuniPortal : BaseForm {
protected Frame Sidebar;

protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (!Sitecore.Context.ClientPage.IsEvent) {
UrlString url = new UrlString(UIUtil.GetUri("control:PortalSidebar"));
url.Add("pid", "Sidebar");
url.Add("pds", "/sitecore/content/Home/Portal sidebar");
url.Add("pt", "/sitecore/content/Home/PortalDemo/Portal");
url.Add("pn", "PortalDemo");

Sidebar.SourceUri = url.ToString();
}
}

#endregion
}
}

The PortalSidebar control is used to contain portlets and requires some url parameters as setup. The first parameter defines the control to use, the second the id of the control, the third the location of the Sitecore items defining the sidebar and the fourth the path of the portal and the last the name of the portal.

3) Creating a portlet
Portlets can be created with any control, as long as the items that are defining it are based on the portlet template. However, to support moving and adding new portlets from the portlet itself, a special control must be created.

To create a portlet which basically wraps the document control, create a new xmlcontrol from the layout studio and paste the following xml into it (same procedure as in step 1):

<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
<DocumentPortlet ID="DocumentPortlet" def:inherits="DocumentPortletXmlControl,Sitecore.Client" > <!--The id is used for moving between columns-->
<DefaultPortletWindow def:ID="Window" Header="Document" Icon="Applications/16x16/star_yellow.png"> <!-- The id is used for the context menu popup -->
<Document def:ID="document"/>
</DefaultPortletWindow>
</DocumentPortlet>
</control>

The newly defined controls is called “DocumentPortlet” and uses the DefaultPortletWindow control to render the portlets surrounding frame, giving it support for moving it from portalzone to portalzone and showing and hiding it. This DocumentPortlet must also have a code beside file which we will create afterwards.

Inside the DocumentPortlet, the Document control is used to render the item’s content. Now create the codebeside class:

using System;
using Sitecore.Web.UI.XmlControls;

public class DocumentPortletXmlControl : XmlControl {
protected XmlControl Window;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);

if (!Sitecore.Context.ClientPage.IsEvent) {
Window.ID = ID + "_window";
}
}
}

This is a required step in order to make sure that the id of the control is valid.

When this step is completed, it is time to create the Sitecore items to support the Portal, and the Portal sidebar.

4) Creating the Portal Items
Create a new Item in the content editor based on the Document template. The name of this item should be the same as defined in the PortalDemo layout created in previous step.

Choose the PortalDemo as the default layout in the layout window in the content editor, and save it.

As a child of the newly created PortalDemo item, create a new item based on the Portal template.

Under the new item, create another item named “Test” based on the template portlet, and fill in the description, and ID. In the “Control name” field add the name of the portlet control we just created: “DocumentPortlet”. In the zone filed, write for example: “Right” to place the portlet in the portal zone named Right.

5) Creating the sidebar Items
Create a new Item under the Home item named Portal sidebar. The Item must be based on the Portal template.

Create a new Item under the “Portal sidebar” based on the Portlet template named “Configurator”. Give it the Item ID: “PortalConfiguratorPortlet” and the Name “PortalConfiguratorPortlet” In the Zone field write “DefaultZone” and in the Order field write “10”. Check the “Default” checkbox.

6) Displaying the Portal
The portal should now be configured with one portlet, and can be seen in the client. Navigate to http://yourserver/PortalDemo.aspx to display the newly created portal. You can invoke the sidebar by moving the mouse towards the right of the browser.

Good luck.