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:
- I Users-sektionen lägger vi till en User -> MemberGroup -
mappning.
- 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.