Sitecore Cookbook for Developers
上QQ阅读APP看书,第一时间看更新

Creating a Sheer UI application using XAML control to list products

Extensible Application Markup Language (XAML) is a declarative markup language used to create user interfaces. There are two types of XAML control frameworks available in Sitecore, XML control and xamlControls. In this recipe, we will create the Sheer UI application using XML control and, based on this, you will be able to create an application using xamlControls as well. You can get some basic information about both from https://goo.gl/4WWeVw.

Sitecore has built a new framework, SPEAK, which is a replacement for the XAML/Sheer UI, so we will not focus much on it. However, it's good to know how it works so that it can be helpful in customizing existing applications. Here, we will create an application to list all products as well as delete selected products. You will learn how to use different controls, invoke methods, handle messages, and create events in XML control.

How to do it…

We will first create an XML layout for the Sheer UI application:

  1. Create an XML file named ProductList.xml in the \sitecore\shell\Applications\Cookbook\ folder, and place the following contents in it:
    <?xml version="1.0" encoding="utf-8" ?>
    <control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
      <ProductListing>
        <FormDialog Icon="People/16x16/clock_run.png" Header="Product List" Text="View all the products">
          <CodeBeside Type="SitecoreCookbook.XAML.ProductListPage,SitecoreCookbook"/>
          <Stylesheet Src="/sitecore/shell/Applications/Content Manager/Dialogs/Properties/Properties.css" />
    
          <GridPanel ID="Viewer" Height="315" class="scBackground" vAlign="top" Width="100%" Cellpadding="5">
          </GridPanel>
        </FormDialog>
      </ProductListing>
    </control>
  2. Now, find the <GridPanel> node in the preceding code, and place ListView to list products as follows:
    <Border align="center">
      <Scrollbox ID="Summary" Width="100%" Height="300" Style="padding:14px 4px 0px 4px; border-width:1px; border-style:solid">
        <Listview ID="ProductList" View="Details" Width="100%" Background="white" MultiSelect="true">
          <ListviewHeader>
            <ListviewHeaderItem Name="Title" Header="Title" />
            <ListviewHeaderItem Name="Price" Header="Price" />
            <ListviewHeaderItem Name="Id" Header="Item Id" />
          </ListviewHeader>
        </Listview>
      </Scrollbox>
    </Border>
  3. Now, we will place two button controls (to delete the selected product) in the <GridPanel> node. One button will get invoked by a method named DeleteProducts and the other by a message named product:delete:
    <Border align="left">
      <Button Header="Delete with method" Click="DeleteProducts" />
      <Button Header="Delete with message" Click="product:delete" />
    </Border>
  4. Now we will do the coding part to list products. In the SitecoreCookbook project, create a ProductListPage class in the XAML folder, and inherit it from Sitecore.Web.UI.Pages.DialogForm:
    public class ProductListPage : DialogForm
    {
      protected GridPanel Viewer;
      protected Button btnDelete;
      protected Listview ProductList;
    }
  5. Create a LoadProducts() method in the class to list all products and add them to the current page as an object of ListViewItem:
    private void LoadProducts()
    {
      string ProductsPath = "/sitecore/Content/Home/Products/Phones";
      Item products = Factory.GetDatabase("master").GetItem(ProductsPath);
      foreach (Item product in products.Children)
      {
        ListviewItem productItem = new ListviewItem();
        Context.ClientPage.AddControl(ProductList, productItem);
        productItem.ID = Control.GetUniqueID("I");
        productItem.Header = product["Title"];
        productItem.ColumnValues["Id"] = product.ID.ToString();
        productItem.ColumnValues["Title"] = product["Title"];
        productItem.ColumnValues["Price"] = product["Price"];
      }
    }
  6. Now, override the OnLoad() event of the DialogForm class to invoke the preceding LoadProducts() method. Here, ClientPage.IsEvent works the same as Webform's Page.IsPostBack. So, products will be loaded on the first request of the dialog:
    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);
      if (!Context.ClientPage.IsEvent) {
        LoadProducts();
        this.OK.Visible = false;
        this.Cancel.Value = "Close";
      }
    }
  7. Build the project and open our XAML application using the following URL:

    http://sitecorecookbook/sitecore/shell/default.aspx?xmlcontrol=ProductListing

    You can find a list of products, as shown in the following image:

    How to do it…

    XAML provides different types of events that we can achieve using invoking methods or passing messages to the event. Here, we will use both approaches to delete selected products.

  8. If you check, as per the code that we placed in the XML file in step 3, the first button is expecting the DeleteProducts() method, which is defined in the button's Click property. Here, we will define this method to delete selected products:
    protected void DeleteProducts()
    {
      if (ProductList.SelectedItems.Length > 0) {
        foreach (ListviewItem productItem in ProductList.SelectedItems) {
          Item product = Factory.GetDatabase("master").GetItem(new ID(productItem.ColumnValues["Id"].ToString()));
          product.Recycle();
        }
    
        ProductList.Controls.Clear();
        LoadProducts();
        ProductList.Refresh();
    
        SheerResponse.Alert("Selected products are deleted!");
      }
      else
        SheerResponse.Alert("No product selected!");
    }
  9. The second button is expecting the application to handle the product:delete message on its click, which is again defined in the Click property of the button. We can handle this message by overriding the HandleMessage() method as follows:
    public override void HandleMessage(Message message)
    {
      if (message.Name == "product:delete")
        DeleteProducts();
    }
  10. Run this application again and select one or more products. Click on any button to delete the product.

How it works…

We created a XAML dialog using an XML file. In the XML, we defined a <ProductListing> tag after the <control> tag, which describes the name of the control. The control name is the identity of the execution of this application and should be unique across the Sitecore solution. While building the solution, the XML file also gets compiled, and while accessing the XAML application, it finds the XML file where the requested control name is defined.

We can create multiple controls like this, but Sitecore recommends creating one control per XML file. Sitecore stores its controls in two locations: /sitecore/shell/Applications and /sitecore/shell/Controls. If you want to override any existing Sitecore control, you can place the overridden XML file in the /sitecore/shell/override folder to separate it from Sitecore's original copy. If you want to place your custom XML file outside of these directories, you have to register it in <controlSources> in the \App_Config\Sitecore.config file as follows:

<source mode="on" namespace="SitecoreCookbook.XAML" folder="/SitecoreCookbook" deep="true"/>

In the XML file, the Type attribute in <CodeBeside> expects assembly details, which means where the server-side code will get executed.

Steps 7 and 8 show you how to invoke a method and pass a message to delete selected products. Both the method and message can be used in any event of buttons or controls, like we used it in button's Click event:

<Button Header="Delete with method" Click="Delete Products" />
<Button Header="Pass with message" Click="product:delete" />

There's more…

You learned how to invoke a method and pass a message to respond to user interaction. We can use events such as Button.OnClick += new EventHandler(BesideButton_OnClick); in the same way.

Instead of the HandleMessage() method, we can also handle a message with a custom method by just adding the HandleMessageAttribute attribute to this method, passing a relevant message to its constructor such as the following:

[HandleMessage("product:delete")]
public void DeleteProductMethod(Message message){}

State management is also possible in XAML to persist some values. We can achieve this using the ServerSettings property in the ClientPage class. We can use Context.ClientPage.ServerProperties["PersistentValue"] to persist information.

xamlControls

The xamlControls needs the <xamlControls> node unlike the XML controls, which use the <controls> node. It also needs a file extension, such as .xaml.xml, unlike .xml for XML controls.

In XML controls, we create a control name such as <ProductListing>, while in xamlControls, we have to use <SitecoreCookbook.XAML.ProductListing> as a control name.

XML controls can be viewed at the following URL:

http://sitecorecookbook/sitecore/shell/default.aspx?xmlcontrol=<ControlName>

xamlControls can be viewed at the following URL:

http://sitecorecookbook/sitecore/shell/~/xaml/<ControlName>.aspx

The structure of xamlControls is slightly different from XML controls, as follows:

<xamlControls xmlns:x="http://www.sitecore.net/xaml">
  <SitecoreCookbook.XAML.ProductsListing x:inherits="SitecoreCookbook.XAML.ProductListPage, SitecoreCookbook">
  </SitecoreCookbook.XAML.SitecoreSitesList>
</xamlControls>

We can also create a wizard using XAML controls such as a template creation wizard. You can read more about this at https://goo.gl/pL1npo.