0121 31 45 374
Qoute Icon

Group Newsletter Studio newsletters by month in the Umbraco backend tree

Tim

One of our customer's websites uses the Newsletter Studio Umbraco package to manage their mailing lists but had an issue because they send a lot of emails (40+ a month) so the draft/sent/archive folders were getting pretty large. We needed a way of grouping them more logically. Markus got back to me pretty quickly on the forums suggesting that we implement a custom tree controller and shared the original source controller code here.

The way we've implemented it is fairly simple -and not overly efficient due to the way the data access works but I thought it worth sharing as a couple of others have asked for the code, first though here's a little screenshot:

 

Before:
image

After:
image

 

Drafts are listed based on the created date, sent/archived generate folders based on the sent date as that felt most logical but it wouldn't be hard to change if you didn't like it. This was just a quick "get it out there ASAP" change so I'm sure the code can be cleared up. Welcome any comments.

 

Installation is simple, modify the `/config/trees.config` file:

 <add title="Newsletters" type="YourProject.NewsletterStudio.CustomNewsletterTreeController, YourProject" iconopen="icon-message" iconclosed="icon-message" application="NewsletterStudio" alias="Newsletter" sortorder="5" initialize="true"></add>

Then you just need to add this class to your solution:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http.Formatting;
using NewsletterStudio.Core.Extensions;
using NewsletterStudio.Core.Interfaces.Data;
using NewsletterStudio.Core.Model;
using NewsletterStudio.Infrastucture;
using NewsletterStudio.Infrastucture.Services;
using NewsletterStudio.Umbraco;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Mvc;
using Umbraco.Web.Trees;

namespace YourProject.NewsletterStudio
{
	[PluginController(NewsletterStudioApplication.Name)]
	[Tree("NewsletterStudio", "Newsletter", "Newsletters", "icon-message", "icon-message", sortOrder: 5)]
	public class CustomNewsletterTreeController : TreeController
	{
		private INewsletterRepository _newsletterRepository;
		private LocalizationService _localization;

		public CustomNewsletterTreeController()
		{
			_newsletterRepository = GlobalFactory.Current.NewsletterRepository;
			_localization = new LocalizationService();
		}

		protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
		{
			var node = base.CreateRootNode(queryStrings);
			node.Name = _localization.Get("ns_newsletters");
			return node;
		}

		protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
		{
			var nodes = new TreeNodeCollection();

			if (id == "-1")
			{
				var nodeDraft = this.CreateTreeNode("draft", id, queryStrings, _localization.Get("ns_draft"), "icon-outbox", _newsletterRepository.CountDraft() > 0);
				nodeDraft.RoutePath = "NewsletterStudio";
				nodes.Add(nodeDraft);

				var nodeSent = this.CreateTreeNode("sent", id, queryStrings, _localization.Get("ns_sent"), "icon-mailbox", _newsletterRepository.CountSent() > 0);
				nodeSent.RoutePath = "NewsletterStudio";
				nodes.Add(nodeSent);

				var nodeArchive = this.CreateTreeNode("archive", id, queryStrings, _localization.Get("ns_archive"), "icon-file-cabinet", _newsletterRepository.CountArchive() > 0);
				nodeArchive.RoutePath = "NewsletterStudio";
				nodes.Add(nodeArchive);
			}
			else
			{
				var statusParts = new[] { id };
				var emailStatus = id;
				if (emailStatus.Contains("-"))
				{
					statusParts = emailStatus.Split('-');
					emailStatus = statusParts.FirstOrDefault();
				}

				// NOTE: This is messy as we're getting all items just to get the years/months but we don't have much of a choice atm
				IList letterList;
				switch (emailStatus)
				{
					case "draft":
						letterList = _newsletterRepository.GetDraft();
						break;
					case "sent":
						letterList = _newsletterRepository.GetSent();
						break;
					case "archive":
						letterList = _newsletterRepository.GetArchived();
						break;
					default:
						letterList = new List();
						break;
				}

				// This is a year or month tree node
				if (statusParts.Length > 1)
				{
					var subPart = statusParts[1];
					var year = Convert.ToInt32(statusParts[2]);

					var letterYears = letterList.Where(l => GetDateToGroupBy(emailStatus, l).Year == year);
					switch (subPart)
					{
						case "year":
							var months = letterYears.GroupBy(l => GetDateToGroupBy(emailStatus, l).Month);
							foreach (var month in months.OrderBy(m => m.Key))
							{
								var item = CreateTreeNode($"{emailStatus}-month-{year}-{month.Key}", id, queryStrings, CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month.Key), "icon-folder", true);
								nodes.Add(item);
							}

							break;
						case "month":
							var monthNumber = Convert.ToInt32(statusParts[3]);
							foreach (var newsletter in letterYears.Where(l => GetDateToGroupBy(emailStatus, l).Month == monthNumber).OrderBy(l => GetDateToGroupBy(emailStatus, l)))
							{
								var item = CreateTreeNode(newsletter.Id.ToString(), id, queryStrings, newsletter.Name, "icon-message", false);

								if (newsletter.Status == NewsletterStatus.Completed)
								{
									item.Icon = "icon-pie-chart";
									item.RoutePath = "NewsletterStudio/Newsletter/analytics/" + newsletter.Id;
								}
								else
								{
									if (newsletter.ScheduledSendDate.HasValue)
										item.CssClasses.Add("ns-is-scheduled");
								}

								if (newsletter.Status == NewsletterStatus.Error)
									item.CssClasses.Add("ns-has-error");

								nodes.Add(item);
							}

							break;
					}
				}
				else
				{
					var years = letterList.GroupBy(l => GetDateToGroupBy(emailStatus, l).Year);
					foreach (var year in years.OrderBy(m => m.Key))
					{
						var item = this.CreateTreeNode($"{emailStatus}-year-{year.Key}", id, queryStrings, year.Key.ToString(), "icon-folder", true);
						nodes.Add(item);
					}
				}
			}

			return nodes;
		}

		private DateTime GetDateToGroupBy(string emailStatus, Newsletter newsletter)
		{
			if (emailStatus == "draft") return newsletter.CreatedDate;

			return newsletter.SentDate ?? newsletter.CreatedDate;
		}

		protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
		{
			var menu = new MenuItemCollection();

			if (id == "-1" || id == "draft")
			{
				menu.DefaultMenuAlias = ActionNew.Instance.Alias;
				menu.Items.Add(_localization.Get("general_create"));
			}
			else if (id.IsNumeric())
			{
				menu.Items.Add(_localization.Get("ns_copyToNew"));
				menu.Items.Add(_localization.Get("general_delete"));
			}

			menu.Items.Add(_localization.Get("actions_refreshNode"));

			return menu;
		}
	}
}

Liked this post? Got a suggestion? Leave a comment