Brackets is a new app from Force.com Labs that helps you run tournament prediction games in your org. (See announcements here and over on ReadWriteWeb.com.) It has some unique features. This article reviews the data model, the publish-subscribe mechanism and the user interface. I've added notes at the bottom of each section with some ideas about where the app could go from here. I conclude with some notes about possible next steps.
You can install Brackets into your Force.com Free Edition, Enterprise Edition or Unlimited Edition. Our good friends at Model Metrics built a nice tournament which you can import with a single click. Looking for near real time app updates? Follow @ForceDotComLabs and @ReidCarlberg on Twitter.
Once you have a look through this article, why not take a look at the code? It's over on Github. Feel free to post feedback, ideas or bug reports on the developer boards.
Data Model
The Data Model is simple. The main goal of the app is to track tournaments, groups who want to predict those tournaments and individual user predictions. At the highest level, it looks like this:
Tournaments have Rounds, Rounds have Matches and Matches connect two Teams. Matches also have a lookup to their following Match. On the right side, Groups lookup to Tournaments and have UserTournaments. UserTournaments lookup to Tournament and Users and have UserMatchPred objects. UserMatchPred joins a Match with a Team as a predicted winner. Check out the Objects folder on GitHub to see all the gory details.
Tournaments assume a Team count that is a power of two — 2, 4, 8, 16, etc. In the real world, tournaments don't always match this. The app includes the ability to build a 12 team tournament that would be useful for NFL Playoffs, but otherwise enforces this rule. If you found yourself in a circumstance where you needed, for example, 30 teams, you could build a 32 team tournament, add two dummy teams, and give two leaders a by in the first round.
All Objects, Apex Classes and Visualforce Pages share a simple naming convention, a "Brackets_" prefix. This is similar to our approach with Milestones PM and significantly reduces the likelihood of namespace collision without the added requirement of creating a managed package.
Notes:
The "UserTournament" naming convention and structure is a leftover from an earlier iteration. It needs to be renamed to something more appropriate and we can now refactor out the direct relationship to Tournament.
It would be interesting to enhance the tournament building function to explicitly handle teams that get a by during an early round. As it stands today, users would still be asked to make a prediction for that team. This could be handled using something as simple as a checkbox on the Match and a bit of enhancement to the prediction logic.
Publish-Subscribe
In general, users will interact with the right side of the picture above. The left side, the Tournament structure, is designed to be either populated by an admin or imported from another org. Importing from another org is definitely the fastest way to go.
The publish-subscribe mechanism is simple. In the publishing org, an admin configures a tournament to their exact needs. This includes Team descriptions, Round scoring rules, prediction deadlines, etc. Once they are ready, there is a "Save" button that helps them configure the subscribing URL and a publish button for when they go live. Subscribing orgs, and there can be an unlimited number of these, simply import the URL.
Once you have the URL, the data that comes through is simple XML. Why XML instead of REST? Brackets takes advantage of the same XML based Export-Import routines as found in Milestones PM and Action Plans, so we had a head start using XML, but there's no reason REST wouldn't be a good choice.
A key portion of the app is tournament updates. If, for example, you have a tournament such as NCAA's"March Madness" Men's College Basketball Tournament, it can be time consuming to update game winners and scores. The update portion allows the publishing org to do that for your subscribers. Subscribers simply click on an update button and their data is quickly in sync with the publishing org.
The import routines are built around the upsert method (which inserts or updates a record, depending on whether or not it already exists). The app takes advantage of the Force.com GUID's provided by the publishing org as External ID's in the subscribing org. The encapsulates everything we need to know about a tournament in one simple feed.
Publishing is handled by the BracketsUpdateTournament page and BracketsSitesTournamentPublisher class. This system is tested using the BracketsExportImportTest class.
Notes:
In the version we published in March, 2011, there are a couple of tournament URL's hardcoded into the getting stared page (BracketsAbout.page). This should be refactored to a more generic system later, but any user can still import any published URL using the manual "Import Tournament" functionality.
Updates currently require a human to click a button. This could obviously be scheduled to run automatically with a little bit of code.
User Interface
We knew early on that developing a reliable and easy to understand user interface would be key to this app. After some initial experiments with Adobe Flex, we settled on JQuery as the rendering engine.
When a user clicks on the Play Brackets tab, they can then select the Group that they want to play. The system then builds the data for that group, including the original tournament, current standings, and existing predicitons. This data is then returned to the rendering page as a REST structure which JQuery iterates through. The system actually includes two rendering routines. The first is for standard tournaments (shown above) and the second was designed to build a tree appropriate for the NFL Playoffs.
Users make their predictions by double clicking on a team. During development, we experimented with drag and drop and single click gestures, but settled on the double click. Next, we added hover boxes showing team information and single click access to the Team page.
As a user makes their predictions, the UI builds a new REST structure in a hidden input box. When the user clicks the "Save Your Predictions" button, this REST structure is sent to the controller and written to the UserMatchPred object.
Finally, we added some popups to help users have a better experience. One popup is a simple help screen that tells users what they need to do next. It pops up automatically when you load the page, but is easy to hide by checking a box and then saving your predictions. The other two popups are for Group Chatter and a Group Leaderboard.
You can the code that handles this on the BracketsPredictions page. The rendering page is heavily componentized. Start by looking at BracketsPredictionsStandard for more details.
Notes:
There are several ways we could modify the user interface. Match descriptions are not yet integrated. A Chatter popup for the teams would be an improvement over jumping to the Team page. See my comments above about handling teams who get a by in an early round.
It would also be interesting to have a two sided prediction tree. For convenience and readability, we haven't created one here (although the NFL Playoffs layout is two sides), but those should be easy problems to overcome.
Another area that we toyed with but never finalized was aggregate user predictions by tournament. If you have Team A vs. Team B, for example, it would be extremely interesting to see how many people are picking one versus the other. One level up would be to submit these predictions back to the central publishing org.
Next Steps
Your first next step on this app is to install it and see how it works. Even if you aren't a sports person, installing and using this app will give you ideas about how you can build better apps on the platform. Could you, for example, create a JQuery based product configurator? What about building a dynamic org chart? What about using it to build a rich Account hierarchy? All of these are possible. I am extremely interested in seeing what other developers do around the notion of initial data population. Force.com has excellent metadata packaging support, but initial data population methods are left up to the developer. I've founnd the pattern used here to be reliable and repeatable.
Do you have code you'd like to submit to Brackets? Contact labs@salesforce.com for more info.