Mar
9
2008

How to enable target audiences for custom content types. In custom list definition.

We often take following steps to create custom list and add it to the site

a. Create custom content types with custom site columns.

b. Create custom list definition and references custom content types.

c. Create list instance as feature and activate it on the site we want to have this custom list.

To enable audience targeting for custom content type during list instance creation. We have to add one more field reference to content type.

step 1.


<ContentType ID="ENTER_GUID_HERE"

             BaseType="ENTER_BASE_TYPE_GUID_HERE"

              Group="CONTENT_TYPE_GROUP"

             Name="Custom Content Type"

             Description="My custom content type">

            <FieldRefs>

                <!-- other custom site columns omitted -->

                <FieldRef ID="{61cbb965-1e04-4273-b658-eedaa662f48d}" Name="Target_x0020_Audiences"/>

            </FieldRefs>

</ContentType>


Above adds target audiences setting field to content type.

James Tsai .NET C# SharePoint VSTO Target Audiences Content Type

Step 2. we also have to add the following field to list definition.


<!-- List schema.xml -->

<Fields>

<!--other field refs omitted -->

<Field ID="{61cbb965-1e04-4273-b658-eedaa662f48d}"

         Type="TargetTo"

          Name="Target_x0020_Audiences"

          DisplayName="Target Audiences"

          Required="FALSE"

          StaticName="Target_x0020_Audiences"

          SourceID="http://schemas.microsoft.com/sharepoint/v3"

      />

</Fields>

After you added custom list on the site, you can see that target audiences has been enabled for content type.

James Tsai .NET C# SharePoint VSTO Target Audiences Content Type

Note: If you enable target audiences from the UI and you have more than one content types for a list. Target audiences field will be enabled only for the first content type.

You have to add target audiences field reference (step 1) to all your content types in list. For them to support target audiences.

Feb
29
2008

How to make DotNetBlogEngine search box to work with AJAX AutoCompleteExtender control

Earlier today I was thinking: um.. It would be good to have AJAX auto complete on search box of my blog.

It only takes you about 10mins to do this (Thanks to ASP.NET AJAX Control Toolkit).

There are many articles/posts about how to use AutoCompleteExtender control from ASP.NET AJAX Control Toolkit already. So I will focus on how to make it work with DotNetBlogEngine default searchbox

Step 1.

Because AutoCompleteExtender will not work with current HTML textbox comes with default searchbox. We have to change it to ASP.NET Textbox control.

//SearchBox.cs
private void BuildHtml()
    {
      StringBuilder sb = new StringBuilder();
      /*
      sb.AppendLine("<div id=\"searchbox\">");
      sb.Append("<label for=\"searchfield\" style=\"display:none\">Search</label>");
      sb.AppendFormat("<input type=\"text\" value=\"{0}\" id=\"searchfield\" onkeypress=\"if(event.keyCode==13) return Search('{1}')\" onfocus=\"SearchClear('{2}')\"    onblur=\"SearchClear('{2}')\" />", BlogSettings.Instance.SearchDefaultText, Utils.RelativeWebRoot, BlogSettings.Instance.SearchDefaultText);
      */ 
  
      /* ------------------------------------ code omitted ------------------------------------  */
      sb.AppendLine("</div>");
      _Html = sb.ToString();

    }

we remove above hard coded HTML textbox control from the method and then override CreateChildControls() to create new ASP.NET textbox control - we also preserved all the settings from default html textbox control.

protected override void CreateChildControls()
{
    base.CreateChildControls();
    TextBox newSearchBox = new TextBox();
    newSearchBox.ID = "searchfield";
    newSearchBox.Attributes["value"] = string.Format("{0}", BlogSettings.Instance.SearchDefaultText);
    newSearchBox.Attributes["onkeypress"] = string.Format("if(event.keyCode==13) return Search('{0}')", Utils.RelativeWebRoot);
    newSearchBox.Attributes["onfocus"] = string.Format("SearchClear('{0}')", BlogSettings.Instance.SearchDefaultText);
    newSearchBox.Attributes["onblur"] = string.Format("SearchClear('{0}')", BlogSettings.Instance.SearchDefaultText);
    this.Controls.Add(newSearchBox);
}

In RenderControl(), we make sure new textbox control will get rendered within default <div>. So we are not breaking the look and feels.

public override void RenderControl(HtmlTextWriter writer)
{
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("<div id=\"searchbox\">"); //default div tag moved to here
        sb.Append("<label for=\"searchfield\" style=\"display:none\">Search</label>"); //default label tag moved to here
        writer.Write(sb.ToString());
        this.Controls[0].RenderControl(writer); //render new textbox control
        writer.Write(Html);
}  

Now you have ASP.NET textbox control for your search box. But still few minor tweaks need to be done.

Step 2.

Fix up JavaScript and Styles.

Because in blog.js there are some places hard coded "searchfield" for the lookup ID of the control. It will now throw an errors with new textbox control for its "onblur","onfocus" and other events, because the actual ID of the control is now generated by server.

/* blog.js */

function SearchClear(defaultText)
{
  var input = $("searchfield"); //this will throw null exception
  if (input.value == defaultText)
    input.value = "";
  else if (input.value == "")
    input.value = defaultText;
}

Change above method to following

function SearchClear(defaultText, clientId)
{
  var input = $(clientId);
  if (input.value == defaultText)
    input.value = "";
  else if (input.value == "")
    input.value = defaultText;
}  

And then change the properties of ASP.NET textbox control to pass extra control ClientID as parameter.

newSearchBox.Attributes["onfocus"] = string.Format("SearchClear('{0}','{1}')", BlogSettings.Instance.SearchDefaultText, this.Controls[0].ClientID);

In the style.css file it also has hardcoded "searchfield" style, you might want to change this too.


#searchbox #searchfield {
    width: 200px;
}    

Step 3.

Drag AutoCompleteExtender from toolbox to SidePanel.ascx and point TargetControlID to "searchfield" then you are Done!

 autocomplete

Extra: If you are looking for using AJAX AutoCompleteExtender without web service! check out

ASP.NET Ajax Beta: AutoCompleteBehavior without a Web Service

Note: I haven't put this AJAX AutoComplete control up to this site yet. Because there aren't much to search for anyway. But I will definitely use it in later.

Feb
24
2008

How to add a new event to controls in ListFormWebPart on the EditFrom.aspx / NewFrom.aspx pages

Background - The controls in EditForm.aspx and NewForm.aspx pages you see when you add/edit a item in SharePoint list library are generated by ListFormWebPart.

The problem - We want to attach a new event to the button (or any controls in this page). For example, we want to popup a javascript alert message when user click on "OK" button without breaking current onclick event.

Solution - This can be easily done by using javascript. And re-register onclick event for the OK button.

First, we need to find out the ID used by OK button control. When you view the source html of EditForm.aspx/NewForm.aspx, you can see the ID of OK button is ending with "SaveItem".

button_id

We also need to find out the default onclick event script of this OK button. So that we can insert our custom script at beginning of the default script or append it after.

button_onclick

From above screen shot we can see that default onclick event begins with string "If (PreSaveItem()).....". So we are going to replace this with our custom script. "alert('you have clicked OK');if (!PreSaveItem())"

The actual JavaScript like following: (The script can be warpped within <script> tag and put insided <asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server"> in NewForm.aspx and EditForm.aspx )

 

_spBodyOnLoadFunctionNames.push("StartUpCustomScript");
function StartUpCustomScript() {    
    ChangeOkButtonOnclickEvent("Input","SaveItem");

function ChangeOkButtonOnclickEvent(tagName, identifier) {
    var len = identifier.length;
    var tags = document.getElementsByTagName(tagName);  /*find all Input type controls on page (ie. control with tag <Input/>)*/
    for (var i=0; i < tags.length; i++) {
        var tempString = tags[i].name;       
        if(tempString.indexOf(identifier) == tempString.length-len ) /*find any Input type controls on page has its name ending with 'SaveItem'*/
        {
           /*if found, replace it default onclick with our custom script*/
           var func = tags[i].attributes["onclick"].value.replace("if (!PreSaveItem())","alert('you have clicked OK');if (!PreSaveItem())");
           /*remove its default onclick event*/
           tags[i].onclick = null;
           /*re-register its onlick event with new script*/
           tags[i].attachEvent("onclick",new Function(func));
       }
    }
    return null;
}  

And now we click on OK button, we can see our Alert message :)

button_clicked

Feb
14
2008

How to add/customize Edit Page option in SiteAction menu

If you ever want to customize OOTB "Edit Page" in SiteAction menu (give it different permission setting, description, image..etc). But still want it to work correctly when user click on it. You need to make sure the Url in <UrlAction/> is calling the right function.


<!-- Feature xml manifest.xml -->


<CustomAction Id="NewEditPageItem"

    GroupId="SiteActions"

    Location="Microsoft.SharePoint.StandardMenu"

    Sequence="1001"

    Title="Edit Page"

    Description="This is new Edit Page menu with customized settings." 

    ImageUrl="/_layouts/images/AnyImage.gif"

    Rights="AddListItems,EditListItems">

    <UrlAction Url="javascript:if(document.forms['aspnetForm']['MSOLayout_InDesignMode'] != null)document.forms['aspnetForm']['MSOLayout_InDesignMode'].value = 1;if(document.forms['aspnetForm']['MSOAuthoringConsole_FormContext'] != null)document.forms['aspnetForm']['MSOAuthoringConsole_FormContext'].value =1;if (document.forms['aspnetForm']['MSOSPWebPartManager_DisplayModeName']!= null)document.forms['aspnetForm']['MSOSPWebPartManager_DisplayModeName'].value ='Design';__doPostBack('ctl00$PlaceHolderTopNavBar$ SiteActionsMenuMain$ctl00$wsaEditPage_CmsActionControl','switchToAuthoring')"/>

  </CustomAction>

I have read other posts and they are saying it should look like


<UrlAction Url="javascript:MSOLayout_ChangeLayoutMode(false);"/>

That is incorrect. Because what it does is to change display mode and page view of the web part page

Feb
11
2008

How to use event handler to add web part to web part page

When you are designing a new page layout and want to have default web parts to be included when user creates a new page off this page layout. You can accomplish this by several ways.

1.       Easiest way probably is to put web parts inside the page layout. – But you cannot change the web part settings without modifying page layout.

2.       Use <AllUsersWebPart/> within element manifest file where you provisioning the page layout. – It is good way to do it only if it can work properly. I am having issues with this method. Having Default Web Parts in new Pages Based Off Page Layouts in MOSS 2007 Publishing Sites have great post about how to do this.

Or you can use event handler to add default web parts to the page when page gets created and added to page library.

First step, create a page layout with empty WebPartZone.

 


<!-- page layout .aspx -->


<WebPartPages:WebPartZone id="DefaultWebPartZone" runat="server" title="Feature Zone">


</WebPartPages:WebPartZone>

Second step, create a list event handler and register it with “Page document library”


<!-- Feature element manifest .xml-->


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Receivers ListTemplateId="850">

    <Receiver>

      <Name>AddedEventHandler</Name>

      <Type>ItemAdded</Type>

      <SequenceNumber>10000</SequenceNumber>

      <Assembly>YourAssemblyNameHere, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789ABCDEF</Assembly>

      <Class>YourNamespaceHere.YourClassNameHere</Class>

      <Data></Data>

      <Filter></Filter>

    </Receiver>

  </Receivers>

</Elements>
 

Thrid step, write your code to insert the web part to page.


 /*EventHandler.cs*/
public class WebPartPageBuilder : SPItemEventReceiver
{
        public override void ItemAdded(SPItemEventProperties properties)
        {    
            //we check if event fired from page library and page was created base on correct page layout (with web part zone).
            if (properties.ListTitle.Equals("Pages") && properties.ListItem.ContentType.Name.Equals("Page Layout Name")) 
            {
                SPFile thisFile = properties.ListItem.File;
                SPLimitedWebPartManager webPartManager = thisFile.GetLimitedWebPartManager(PersonalizationScope.Shared);
                ContentByQueryWebPart defaultCQWPWebPart = new ContentByQueryWebPart();
                /**web part settings ommited here**/ 
                webPartManager.AddWebPart(defaultCQWPWebPart, "DefaultWebPartZone", 1);
            }
            base.ItemAdded(properties);
        }
}

 Now, you have created a event handler that inserts web part to the page.

James Tsai

Blog Disclaimer