Summary
Contents
This project had two major goals. First, I wanted to filter Trello cards based on tags.
If you are not familiar with Trello, it is described as
It is an easy way to organize information into lists, which is how my mind works. This video gives a nice introduction.
One current drawback with Trello is the lack of support for Tags. They do provide 6 different color coded labels. This is nice, but just not enough. Although I am not a perfect follower of the David Allen method of Getting Things Done, I do try to organize my tasks using his methods.
In addition to adding a “little GTD” to Trello, I also wanted to output a page that was formatted for a printer. The idea is that I would print the page each morning and carry it with me throughout the day. I would mark it up, add notes and new tasks. I would draw a line through completed tasks and even have a place to Doodle during meetings. Then, at the end of the day or the next morning before I print a new page, I would update Trello with the notes from the paper.
Description
One issue I am sure the Trello dev team has encountered with tags is the fact that cards are usually viewed by more than one user. Each user wants “their own” tags that makes sense to them. However, as a team, users may want a common set of tags.
Here is my original list of tags:
- +call = Calls that need to be placed
- +dead = Brain dead tasks for when we can’t focus.
- +dash = Things that can be done in under 15 minutes
- +email = Emails that need to be sent
- +errand = Things we need to do/get while out
- +focus = Requires dedicated time and concentration
- +paper = Tasks that only require a pen & paper (Brainstorming)
The idea is that I would just enter “+call” anywhere in the description and then I could filter all the cards that contained that tag. This works for a single user, but one of the huge benefits of Trello is that other people are viewing and editing those same cards.
The Kludge I came up with to solve this issue was to “prefix” the tag with the user initials. Now, my tag list became:
- +jsb-call = Calls that need to be placed
- +jsb-email = Emails that need to be sent
- +jsb-errand = Things we need to do/get while out
- +jsb-focus = Requires dedicated time and concentration
(I dropped a few out that I was no longer using)
I add these tags in the card description. Now, I just needed an easy way to filter the cards. This is where the Trello REST API steps up.
Proof of Concept
For my POC, I created a Winforms applicaiton. Here is a screen capture:
Yes, I went a little crazy with the colors, but I never claimed to be a designer (I can’t even match a pair of socks). If it helps any, remember that the goal is for the page to be printed. That is the reason for the underscores before each task. The calendars don’t really do anything on the web page, but are just there as a reference on the printed page. I also included the tag list as a reminder on the page.
top
Page Template
I created an html template for the page. The body of the template is shown below. The CSS is also included in the page, but I left it out for this example.
<body> <div> <div style="float: right;"> <div style="text-align: right;"> <table> <tr valign="top"> <td> <span id="datePrior" style="font-size: 7px;"></span> </td> <td> <span id="dateNext" style="font-size: 7px;"></span> </td> </tr> </table> <table> <tr valign="top"> <td> <span id="dateCurrent" style="font-size: 10px;"></span> </td> <td> <div style="text-align: left;"> <span class="TagCaption">Tags</span><br /> <span class="TagList">+{{TagPrefix}}-call</span><br /> <span class="TagList">+{{TagPrefix}}-email</span><br /> <span class="TagList">+{{TagPrefix}}-errand</span><br /> <span class="TagList">+{{TagPrefix}}-focus</span><br /> <span class="TagList">+{{TagPrefix}}-next</span><br /> <span class="TagList">+{{TagPrefix}}-waiting</span><br /> </div> </td> </tr> </table> </div> <h2 class="CaptionCall">Appointments / Calls</h2> {{call}} <h2 class="CaptionAssigned">Assigned To Me</h2> {{@me}} </div> <div style="float: left;"> <h1>{{Date}}<span class="Time">{{Time}}</span></h1> <h2 class="CaptionFocus">Focus</h2> {{focus}} <h2 class="CaptionNext">Next Actions</h2> {{next}} <h2 class="CaptionNote">Notes</h2> </div> </div> </body>View Objects
I created my own objects that contain only the few properties I care about for my view.
public class TrelloBoard { public string Id { get; set; } public string Text { get; set; } public string OrgText { get; set; } } public class TrelloList { public string Id { get; set; } public string Text { get; set; } } public class TrelloCard { public string OrgText { get; set; } public string BoardText { get; set; } public string ListText { get; set; } public string Text { get; set; } public string Url { get; set; } }CreatePage Method
Here is the method called to replace the tokens in the template. It looks for some specific tokens. If it finds a token that is not one of the pre-defined values, it calls the method to query Trello for that value.
private void CreatePage(ITrelloData data) { try { string templateFile = Path.Combine(Application.StartupPath, "DailyActions_Template.html"); string htmlFile = Path.Combine(Application.StartupPath, "DailyActions.html"); string html = File.ReadAllText(templateFile); string regEx = "{{.*?}}"; MatchCollection tokenList = Regex.Matches(html, regEx, RegexOptions.IgnoreCase); foreach (Match match in tokenList) { switch (match.Value.ToLower()) { case "{{date}}": { string date = dateTimePicker1.Value.ToLongDateString(); html = html.Replace(match.Value, date); break; } case "{{time}}": { string time = dateTimePicker1.Value.ToLongTimeString(); html = html.Replace(match.Value, time); break; } case "{{tagprefix}}": { html = html.Replace(match.Value, txtTagPrefix.Text); break; } default: { List<TrelloCard> cards = data.GetCardsForQuery(match.Value); html = html.Replace(match.Value, GetActionsHtml(cards)); break; } } } File.WriteAllText(htmlFile, html); webBrowser1.Navigate(htmlFile); } catch (Exception ex) { Console.WriteLine("Exception: " + ex.ToString()); } }GetCardsForQuery Method
public List<TrelloCard> GetCardsForQuery(string token) { // Returns an ordered list of Trello cards that have only the // properties from the the Board and List that are required // for the view. // Remove the token delimiters. string query = token.Replace("{", "").Replace("}", ""); // Trello uses the "@" for specific queries. If this is not one of // those queries, we need to add the tag prefix (user initials) and // surround the query with quotes. if (!query.Contains("@")) { query = """ + "+" + _tagPrefix + "-" + query + """; } List<TrelloCard> retVal = new List<TrelloCard>(); List<TrelloBoard> boards = new List<TrelloBoard>(); List<TrelloList> lists = new List<TrelloList>(); IEnumerable<Card> cards = _trello.Cards.Search(query, 1000); foreach (var card in cards) { TrelloCard trelloCard = new TrelloCard { Url = card.Url, Text = card.Name }; TrelloBoard trelloBoard = boards.Find(b => b.Id == card.IdBoard); if (trelloBoard == null) { trelloBoard = new TrelloBoard { Id = card.IdBoard }; Board board = _trello.Boards.WithId(card.IdBoard); trelloBoard.Text = board.Name; trelloBoard.OrgText = _trello.Organizations.WithId(board.IdOrganization).DisplayName; boards.Add(trelloBoard); } trelloCard.BoardText = trelloBoard.Text; trelloCard.OrgText = trelloBoard.OrgText; TrelloList list = lists.Find(b => b.Id == card.IdList); if (list == null) { list = new TrelloList { Id = card.IdList }; list.Text = _trello.Lists.WithId(card.IdList).Name; lists.Add(list); } trelloCard.ListText = list.Text; retVal.Add(trelloCard); } retVal = retVal.OrderBy(o => o.OrgText + "-" + o.BoardText + "-" + o.Text).ToList(); return retVal; }GetActionsHtml Method
Returns an Html unordered list grouped by Organization and Board.
private string GetActionsHtml(List<TrelloCard> actionList) { StringBuilder sb = new StringBuilder(); string org = ""; string board = ""; foreach (TrelloCard nextAction in actionList) { if (nextAction.OrgText != org) { if (!string.IsNullOrEmpty(org)) { sb.Append("</ul>"); board = ""; // Since the Org is changing, clear the board. } sb.AppendFormat("<div class='Organization'>{0}</div>", nextAction.OrgText); sb.AppendLine(); if (!string.IsNullOrEmpty(board)) { sb.Append("</ul>"); } sb.AppendFormat("<div class='Board'>{0}</div>", nextAction.BoardText); sb.AppendLine(); sb.Append("<ul>"); org = nextAction.OrgText; board = nextAction.BoardText; } sb.AppendFormat("<li>_ <a href='{0}' target='trello'>{1}</a><span class='List'>{2}</span></li>", nextAction.Url, nextAction.Text, nextAction.ListText); sb.AppendLine(); } sb.Append("</ul>"); sb.AppendLine(); return sb.ToString(); }Future Plans
- Convert to Asp.Net MVC site, allow others to use it.
- Schedule page to be ran daily and emailed
Technology
- .Net Winform, C#
- Trello.NET API