Tying the Google Calendar and task list together

We now have services that can be used to create entries into a user's Google Calendar or task list; however, having that code all over our application adds unnecessary code and bloat to the components that need to interact with the services. It would be better to use a facade pattern to encapsulate the business logic around managing our brewing calendar and task list. This way, the components that want to interact with the user's calendar or task list can do it via this new service in a more refined way.

The brewCalendar service wraps the business logic needed to manage both the brewing calendar and brewing task list. It also handles the logic to create all of the calendar events and tasks for a brew day:

angular.module('brew-everywhere').factory('brewCalendar', function (messaging, events) {

  var brewingCalendar = {};
  var brewingCalendarId = '';
  var brewingCalendarName = 'My Brewing Calendar';
  var brewingTaskList = {};
  var brewingTaskListId = '';
  var brewingTaksListName = 'My Brewing Tasks';
  var reduceBrewingCalendar = function(calendar){
  var formattedDate = moment().format('YYYY-MM-DD'),
  // filter calendar item for those still in the future
  var result = _.filter(calendar.items, function(event){
    if(event.end.date > formattedDate) {
      return event;
    }
  });
  // sort by start date
  result = _.sortBy(result, function(event){ 
    return event.start.date;});
  return result;
};

var getBrewingCalendar = function(){
  messaging.publish(events.message._GET_CALENDAR_, 
    [brewingCalendarName, true]);
};

messaging.subscribe(events.message._GET_BREWING_CALENDAR_, 
  getBrewingCalender);

var onHandleGetCalendarComplete = function(calendar){
  brewingCalendar = calendar;
  brewingCalendarId = calendar.id;

  if(calendar.items.length > 0){
    var result = reduceBrewingCalendar(brewingCalendar);
    messaging.publish(events.message._GET_BREWING_CALENDAR_COMPLETE_, [brewingCalendar]);
    getBrewingTaskList();
  }
  else {
    createDefaultCalendarEntries(brewingCalendarId);
  }
};

messaging.subscribe(events.message._GET_CALENDAR_COMPLETE_, 
   onHandleGetCalendarComplete);

var getBrewingTaskList = function(){
  messaging.publish(events.message._GET_TASK_LIST_, 
    [brewingTaksListName, true])
};

messaging.subscribe(events.message._GET_BREWING_TASKS_, 
  getBrewingCalender);

var onHandleGetTaskListComplete = function(taskList){
  brewingTaskList = taskList;
  brewingTaskListId = taskList.id;

messaging.publish(events.message._GET_BREWING_TASKS_COMPLETE_, 
  [brewingTaskList]);
};

messaging.subscribe(events.message._GET_TASK_LIST_COMPLETE_, 
  onHandleGetTaskListComplete);

var createDefaultCalendarEntries = function(id){
  var event = defaultEvents.shift();
  if(event){
    messaging.publish(events.message._CREATE_EVENT_, 
      [id, event]);
  }
  else {
    getBrewingCalender();
  }
};

var handleCreateDefaultEventRequest = function(){
  createDefaultCalendarEntries(brewingCalendarId);
};

messaging.subscribe(events.message._CREATE_EVENT_COMPLETE_, 
  handleCreateDefaultEventRequest);

Tip

You can view the full source code in the sample files for the book.

Whenever a consumer publishes the _GET_BREWING_CALENDAR_ event, the service makes a call to the googleCalendar service to get the calendar with the name "My Brewing Calendar" and true for the create parameter. When the googleCalendar service returns the calendar, the onHandleGetCalendarComplete callback handler is called and it then checks to see if the calendar has any events; if so, it uses the reduceBrewingCalendar method to reduce the returned events to only those that have an end date in the future and then sorts them by start date. Once the reduceBrewingCalendar method returns the events, the callback handler stores off the calendar, publishes the _GET_BREWING_CALENDAR_COMPLETE_ event, and then calls the getBrewingTaskList method.

Should the brewing calendar have no events, we know that the calendar is newly created so the service calls the createDefaultCalendarEvents method, which goes through the array of defaultEvents one by one and adds them to the brewing calendar. This populates the brewing calendar with a series of events showing what beer styles to brew and when, helping to provide some guidance as to when to brew the different beer styles.

The getBrewingTaskList method makes a call to the googleTasks service to get the task list with the name "My Brewing Tasks" that has the value true for the create parameter. When the googleTasks service returns the task list, the onHandleGetTaskListComplete callback handler is called; it then stores off the task list and then publishes the _GET_BREWING_TASKS_COMPLETE_ event. Since there are no default brewing tasks, the service does not try to create any default brewing tasks.

The other large piece of business logic the brewCalendar service contains is the scheduling of brew days and their related tasks. Whenever a home brewer prepares for a brew day, there are certain tasks and dates that are relevant to the batch of beer brewed. The brewer has to make sure they have all of the ingredients to brew the beer and that they've prepared the yeast prior to the brew day. They also need to know how long the beer needs to ferment, when to bottle it, and how long it should condition prior to enjoying it.

Creating these events and tasks can be quite extensive; the start and end dates need to be calculated for each event and the ingredient list needs to be compiled. The service breaks each of these pieces of business logic into one of several methods and a single method calls each of these sub-methods to make sure all of the events and tasks are created. The createBrewDayEvents method handles all of this. The following is the code for createBrewDayEvents along with a method that creates a task and one that creates a calendar event:

var createBrewDayEvents = function(recipe, scheduleDate, 
  addBrewDayTasks, addFermentationTasks, 
  addConditioningTasks){
  if(addBrewDayTasks){
    addGetIngredientsTask(recipe, scheduleDate);
    addPrepareStarterTask(recipe, scheduleDate);
  }

  addBrewDayToCalendar(recipe, scheduleDate);

  if(addFermentationTasks){
    addPrimaryToCalendar(recipe, scheduleDate);
    addSecondaryToCalendar(recipe, scheduleDate);
    addTertiaryToCalendar(recipe, scheduleDate);
  }

  if(addConditioningTasks){
    addBottlingToCalendar(recipe, scheduleDate);
    addConditioningToCalendar(recipe, scheduleDate);
  }
};

messaging.subscribe(events.message._CREATE_BREW_DAY_EVENTS_, 
  createBrewDayEvents);

The createBrewDayEvents methods takes a recipe, the date of the brew day, and several flags indicating what calendar events and tasks to add to the user's calendar and task list. It then calls the various methods based on the flags. The createBrewDayEvents method will always add the brew day to the user's calendar.

Another method that is rather involved is the addIngredientsTask method. It iterates through the various ingredients in the recipe and builds a list that will be added to the task so that the user has a shopping list for everything they need to brew the given recipe. The code for the addIngredientsTask method is as follows:

var addGetIngredientsTask = function(currentRecipe, 
  scheduledDate){
  var dueDate = moment(scheduledDate).subtract('days', 2);
  var ingredientList = 'Gather the following ingredients:

';

  angular.forEach(currentRecipe.Fermentables, function(item){
    ingredientList += item.Amount + ' - ' + item.Name + ' ' + 
    item.Type + '
';
  });

  angular.forEach(currentRecipe.Adjuncts, function(item){
    ingredientList += item.Amount + ' - ' + item.Name + ' ' + 
      item.Type + '
';
  });

  angular.forEach(currentRecipe.Hops, function(item){
    ingredientList += item.Amount + ' - ' + item.Name + ' ' + 
      item.Form + '
';
  });

  angular.forEach(currentRecipe.Yeast, function(item){
    ingredientList += item.Amount + ' - ' + item.Name + ' ' + 
      item.Form + '
';
  });

  var task = {'title': 'Gather Ingredients: ' + 
    currentRecipe.Name,
    'status': 'needsAction',
    'due': dueDate.format('YYYY-MM-DD'),
    'notes': ingredientList
  };

  messaging.publish(events.message._CREATE_TASK_, 
    [brewingTaskListId, task]);
};

Once the addGetIngredientsTask method finishes compiling the shopping list, it creates a new task and assigns the shopping list to the notes section of the task. The method also calculates a due date by subtracting two days from the scheduled brew date to ensure that the brewer is alerted early enough to have time to gather all the required ingredients. Finally, the method publishes a _CREATE_TASK_ event, passing in the id of the user's brewing task list and the newly created task.

The addPrimaryToCalendar method, given in the following code, shows some of the date calculations that go into adding a calendar event that spans a given period of time. First, the method checks the recipe to make sure that the brewer has added a primary fermentation step. Then, it starts calculating the end date by adding the number of days of primary fermentation to scheduleDate stored in startDate. The method then needs to format the start and end dates into the yyyy-mm-dd format so that the new calendar event displays properly:

var addPrimaryToCalendar = function (currentRecipe, 
  scheduledDate) {
  if (currentRecipe.PrimaryAge > 0 && 
      currentRecipe.PrimaryTemp > 0) {
    var startDate = moment(scheduledDate);
    var endDate = startDate.add('days', 
      parseInt(currentRecipe.PrimaryAge));
    var formattedStartDate = startDate.format('YYYY-MM-DD'),
    var formattedEndDate = endDate.format('YYYY-MM-DD'),

    var event = {'summary': 'Primary Fermentation: ' + 
      currentRecipe.Name,
      'start': {'date': formattedStartDate},
      "end": {"date": formattedEndDate},
      'description': 'Primary Fermentation at ' + 
      currentRecipe.PrimaryTemp + ' degrees.',
      'reminders': {'useDefault': true}};

      messaging.publish(events.message._CREATE_EVENT_, 
      [brewingCalendarId, event]);
  }
};

Once the dates have been formatted, a new calendar event is created and sent to the googleCalendar service using the _CREATE_EVENT_ event. This time it passes in the unique id of the user's brewing calendar and the newly created event.

One of the important things about the brewCalendar service is that it allows us to hide away the implementation details of interacting with Google's Calendar and Tasks API. You can see how the other methods are implemented by checking out the brewCalendar.js file in the service directory of the source code for this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.139.224