Skapa en custom section i Umbraco (Steg 1)

den 26 februari 2010 av Fredrik Sewen

Bakgrund:

Vi har en sajt för föreningar. Föreningarna ser bara sin egen förenings innehåll i Content-sektionen. Nu har det kommit en förfrågan från flera föreningar att de vill kunna lägga till sina medlemmar och ha en inloggad del. Vi vill inte ge administratörerna för de olika föreningarna direkt tillgång till Umbracos member-sektion eftersom de då skulle se alla föreningars medlemmar. Alltså faller valet på att skapa en ny custom section där de kan administrera den egna föreningens medlemmar. Vi tyckte det här var ett ypperligt tillfälle att dela med oss hur vi skapar en custom section.

Problemformulering:

  1. I Users-sektionen lägger vi till en User -> MemberGroup - mappning.
  2. Skapa en ny CustomMember-sektion som liknar den Member-sektion som redan finns i Umbraco (CRUD).

Om Umbraco databasen:

Sektioner i Umbraco hanteras av två databastabeller.

umbracoApp

Består av Krasst ansvarar umbracoApp för visningen av menyn för sektionerna i Umbraco admin. Tabellen består av fem kolumner som beskrivs nedan. De flesta har med just visningen att göra, men appAlias-kolumnen är värd att lägga på minnet för den kommer spela roll i vår nästa tabell och i config.

umbracoAppTree

När en sektion laddas så är det den här tabellen som ansvarar för vad som kommer dyka upp i Content-menyn.

För att läsa mer om tabellerna gå till http://www.geckonewmedia.com/blog/2009/8/3/how-to-create-a-custom-section-in-umbraco-4

Eftersom vår första uppgift var att lägga till en User -> MemberGroup - mappning i Users-sektionen som redan finns så är det umbracoAppTree som vi ska lägga till en ny rad i.

INSERT INTO umbracoAppTree

(treeSilent, treeInitialize, treeSortOrder, appAlias, treeAlias, treeTitle, treeIconClosed, treeIconOpen, treeHandlerAssembly, treeHandlerType)

VALUES ('false', 'true', 3, 'users', 'userGroupMapping', 'User Group mapping', 'folder.gif', 'folder_o.gif', 'umbBoRatt.Umbraco.Sections.Users', 'LoadUserGroupMappings')

Vi väljer att inte krångla till det med design här, eftersom det bara kommer vara sajtens administratörer som ser det. Vi ser till att våra andra ikoner ser snyggare ut framöver.

TIPS! Umbraco förutsätter att tree-ikonerna ligger i mappen "~/umbraco/images/umbraco/"

Det vi nu har sagt till Umbraco är att när den laddar Users-sektionen ska den lägga till en ny folder med namnet "User Group mapping". För att veta folderns innehåll ska den ladda classen LoadUserGroupMapping med namespace umbBoratt.Umbraco.Sections.Users. Alltså får vi se till att skapa den klassen.

OBS! Om Umbraco inte hittar ditt namespace eller klassen kommer det inte synas något i admin. Kontrollera referenser, namespace och klassnamn en gång till. När du gjort ändringar i databasen, se till att röra web.config för att starta om applikationen.

BaseTree

Nu kommer det härligaste med Umbraco. Eftersom Umbraco är Open Source behöver man aldrig gissa sig fram till hur man ska göra. Det finns alltid ett exempel att ta av. I det här fallet ska vårt nya träd lista användare. Vi har redan ett likadant träd som listar användare. Vi ser i databasen att klassen ska heta loadUsers. Vi gör en sökning i Umbracos källkod och finner klassen i mappen "/umbraco/presentations/umbraco/trees/". Vi använder källkoden från den filen med några smärre förändringar.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using umbraco.cms.presentation.Trees;
using umbraco.BusinessLogic.Actions;
using umbraco.interfaces;
using umbraco.BusinessLogic;
using umbraco.BasePages;

namespace umbBoRatt.Umbraco.Sections.Users
{
    public class LoadUserGroupMappings : BaseTree
    {
        public LoadUserGroupMappings(string application) : base(application) { }

        protected override void CreateRootNode(ref XmlTreeNode rootNode)
        {
            rootNode.Icon = FolderIcon;
            rootNode.OpenIcon = FolderIconOpen;
            rootNode.NodeType = "init" + TreeAlias;
            rootNode.NodeID = "init";
            rootNode.Action = "";
        }

        protected override void CreateRootNodeActions(ref List actions)
        {
            actions.Clear();
            actions.Add(ActionRefresh.Instance);
        }

        protected override void CreateAllowedActions(ref List actions)
        {
            actions.Clear();
        }

        public override void Render(ref XmlTree tree)
        {
            User[] users = User.getAll();

            User currUser = UmbracoEnsuredPage.CurrentUser;

            bool currUserIsAdmin = currUser.IsAdmin();
            foreach (User u in users)
            {
                XmlTreeNode xNode = XmlTreeNode.Create(this);

                // special check for ROOT user
                if (u.Id == 0)
                {
                    //if its the administrator, don't create a menu
                    xNode.Menu = null;
                    //if the current user is not the administrator, then don't add this node.
                    if (currUser.Id != 0)
                        continue;
                }
                // Special check for admins in general (only show admins to admins)
                else if (!currUserIsAdmin && u.IsAdmin())
                {
                    continue;
                }

                if (u.Disabled)
                    xNode.IconClass = "umbraco-tree-icon-grey";

                xNode.NodeID = u.Id.ToString();
                xNode.Text = u.Name;
                xNode.Action = "javascript:openUserMemberGroupMappings(" + u.Id + ");";
                xNode.Icon = "user.gif";
                xNode.OpenIcon = "user.gif";

                tree.Add(xNode);
            }
        }

        public override void RenderJS(ref StringBuilder Javascript)
        {
            Javascript.Append(
               @"
                    function openUserMemberGroupMappings(id) {
                        parent.right.document.location.href = 'plugins/CustomMember/Users/editUserMemberGroupMappings.aspx?id=' + id;
                    }
                ");
        }
    }
}

Förklaringar till BaseTree

Vår klass ärver från BaseTree. Vi har valt att override ett antal metoder. Jag kommer förklara deras funktion en och en.

CreateRootNode: I vårt träd har vi en rootNode. Vi kan göra ändringar i den noden i CreateRootNode metoden. Vi väljer ikon, sätter ett id och en eventuell action (kommer kalla på ett eget javascript om det är satt). rootNode.NodeType är viktig om man ska koppla Create-event till sin nod. Vi kommer gå igenom det närmre när vi skapar vår CustomMember-sektion.

CreateRootNodeActions: Definierar vad som ska hända när man högerklickar på en nod i trädet. I vårt fall tillåter vi endast att man kan uppdatera trädet.

CreateAllowedActions: Definerar vilka actions som kommer vara tillåtna för vår sub-noder. I vårt fall vill vi bara att man ska kunna klicka på varje användare och därifrån definiera User -> MemberGroup - mappningen, därför tömmer vi sub-noderna på actions.

Render: När man väljer att expandera trädet kommer Render metoden att kallas på. Det är här man skapar sub-noder till trädstrukturen. I vårt fall hämtar vi alla Users. Efter några kontroller om det är admin-användare eller inte som är inloggad tar vi var och en av våra Users och skapar noder av dessa.

foreach (User u in users)

{

XmlTreeNode xNode = XmlTreeNode.Create(this);

För varje nod väljer vi dessutom ikoner, text och en action (ett javascript som kallas på vid klick på noden). Slutligen lägger vi till noden till vår rootNode .

tree.Add(xNode);

RenderJS: Kommer lägga till ett javascript till sidan, som vi sedan kan kalla på via våra actions från noderna.

Den stora förändringen vi gjort ligger i vad som ska hända när man klickar på en användare.

xNode.Action = "javascript:openUserMemberGroupMappings(" + u.Id + ");";

Ovan har vi har definerat att när man klickar på en medlem så kommer ett javascript openMemberGroupMappings att anropas. I vår RenderJS metod definerar vi det javascriptet. Det är så enkelt att det öppnar en aspx-fil i Umbraco admins stora iFrame. Nu är det dags att skapa den aspx-filen.

UI

Eftersom vi vill att vårt gränssnitt ska påminna så mycket som möjligt om Umbracos eget gränssnitt börjar vi med att ta lite hjälp. I loadUsers-klassen ser vi att den kallar på en fil som heter editUser.aspx. Vi öppnar den och får vår grunddesign. Vi importerar umbraco.ui och får tillgång till kontroller som har ett native Umbraco utseende.

<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>

Se hela front-end koden här:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="editUserMemberGroupMappings.aspx.cs" MasterPageFile="/umbraco/masterpages/umbracoPage.Master" Inherits="umbBoRatt.Web.umbraco.plugins.CustomMember.Users.editUserMemberGroupMappings" %>

<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
<asp:Content ContentPlaceHolderID="body" runat="server">
 <cc1:UmbracoPanel runat="server" ID="UserMemberGroupMappings" Text="User Member group mappings" hasMenu="true">
 <cc1:Pane ID="Pane1" runat="server">
 <cc1:PropertyPanel runat="server" ID="PropertyPanel2" Text="Member">
 <asp:Literal ID="lblMemberName" runat="server"></asp:Literal>
 </cc1:PropertyPanel>
 <cc1:PropertyPanel runat="server" ID="PropertyPanel1" Text="Member groups">
 <asp:CheckBoxList ID="memberGroups" runat="server" OnDataBound="memberGroups_DataBind">
 </asp:CheckBoxList>
 </cc1:PropertyPanel>
 </cc1:Pane>
 
</cc1:UmbracoPanel>
</asp:Content>

I page_load i vår back-end lägger vi till en spara-knapp till menyraden i vår UmbracoPanel. Sedan binder vi vår CheckBoxList med alla MemberGroups.

I spara-metoden itererar vi listan och sparar alla ikryssade MemberGroups. Vi ser även till att aktivera Umbraco admins inbyggda pratbubbla (speechBubble). Vi får tillgång till den genom att vi ärver från UmbracoEnsuredPage istället för den vanliga Page.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using umbraco.BasePages;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.web;
using umbraco.presentation.channels.businesslogic;
using umbraco.uicontrols;
using umbraco.providers;
using umbraco;
using umbraco.cms.businesslogic.member;

namespace umbBoRatt.Web.umbraco.plugins.CustomMember.Users
{
    public partial class editUserMemberGroupMappings : UmbracoEnsuredPage
    {
        public User pageUser { get { return new User(int.Parse(Request["id"])); } }
        private umbBoRatt.Core.Controllers.UserMemberGroupController Controller;
         

        protected void Page_Load(object sender, EventArgs e)
        {
            Controller = new umbBoRatt.Core.Controllers.UserMemberGroupController(new umbBoRatt.Core.Models.UserMemberGroupRepository(GlobalSettings.DbDSN));

            //Add save to menu
            MenuImageButton save = UserMemberGroupMappings.Menu.NewImageButton();
            save.ImageUrl = GlobalSettings.Path + "/images/editor/save.gif";
            save.Click += new ImageClickEventHandler(saveUser_Click);

            if (!Page.IsPostBack)
            {
                lblMemberName.Text = pageUser.Name;

                //Bind all member groups
                memberGroups.DataSource = MemberGroup.GetAll;
                memberGroups.DataTextField = "Text";
                memberGroups.DataValueField = "Id";
                memberGroups.DataBind();
            }
        }

        /// 
        /// Handles the databind event of the list control.
        /// 
        /// The source of the event.
        /// The  instance containing the event data.
        protected void memberGroups_DataBind(object sender, EventArgs e)
        {
            int userId = pageUser.Id;
            foreach (ListItem li in memberGroups.Items)
            {
                li.Selected = Controller.HasGroup(userId, int.Parse(li.Value));
            }
        }

        /// 
        /// Handles the Click event of the saveUser control.
        /// 
        /// The source of the event.
        /// The  instance containing the event data.
        private void saveUser_Click(object sender, ImageClickEventArgs e)
        {
            //1) Remove all groups for user
            //2) Foreach li.selected add group to user
            //3) Show speechbubble

            int userId = pageUser.Id;
            Controller.RemoveAllGroups(userId);

            foreach (ListItem li in memberGroups.Items)
            {
                if (li.Selected)
                {
                    Controller.AddMemberGroup(userId, int.Parse(li.Value));
                }
            }

            speechBubble(speechBubbleIcon.save, ui.Text("speechBubbles", "editUserSaved", base.getUser()), "");     
        }
    }
}

Resultatet blir:

Sammanfattning:

Vi har gått igenom de tabeller som Umbraco använder för sektioner och trädstrukturen för sektionerna. Vi har även skaffat oss en övergripande förståelse för hur Umbraco admin använder klassen BaseTree för trädstrukturen i sektionerna. Med hjälp av blogginlägget http://www.geckonewmedia.com/blog/2009/8/3/how-to-create-a-custom-section-in-umbraco-4 kommer det inte vara något problem för dig som läsare att redan nu ge dig på att skapa en Custom Sections i Umbraco. Mer om det kommer i Steg 2 av serien att skapa en Custom Section i Umbraco.

blog comments powered by Disqus

Kategori

Senaste kommentarer

Powered by Disqus

Prenumerera

Rss