Fork me on GitHub
Show:

File: ../src/maps/routes/routebuilder.js

define([
  'aeris/events',
  'aeris/util',
  'aeris/emptypromise',
  'aeris/errors/invalidargumenterror',
  'aeris/maps/routes/waypoint',
  'aeris/maps/routes/route',
  'aeris/maps/routes/routerenderer',
  'aeris/commands/commandmanager',
  'aeris/maps/routes/commands/addwaypointcommand',
  'aeris/maps/routes/commands/movewaypointcommand',
  'aeris/maps/routes/commands/removewaypointcommand',
  'aeris/maps/routes/commands/resetroutecommand',
  'aeris/maps/routes/commands/appendreverseroutecommand'
], function(
  Events,
  _,
  EmptyPromise,
  InvalidArgumentError,
  Waypoint,
  Route,
  RouteRenderer,
  CommandManager,
  AddWaypointCommand,
  MoveWaypointCommand,
  RemoveWaypointCommand,
  ResetRouteCommand,
  AppendReverseRouteCommand
) {


  /**
   * Central Route component interface
   * for an application layer.
   *
   * Provides interface to route commands
   * and binds route events to {aeris.maps.gmaps.route.RouteRenderer}
   *
   * @param {Object=} opt_options
   * @param {aeris.maps.gmaps.route.RouteRenderer=}   opt_options.routeRenderer
   * @param {aeris.commands.CommandManager=}          opt_options.commandManger
   * @param {aeris.maps.gmaps.route.Route}            opt_options.route
   * @param {Object=}                                 opt_options.styles See RouteRenderer for style options.
   *
   * @param {Function=} opt_options.AddWaypointCommand
   * @param {Function=} opt_options.MoveWaypointCommand
   * @param {Function=} opt_options.RemoveWaypointCommand
   * @param {Function=} opt_options.ResetRouteCommand
   * @param {Function=} opt_options.AppendReverseRouteCommand
   *
   * @class aeris.maps.gmaps.route.RouteBuilder
   * @constructor
   */
  var RouteBuilder = function(opt_options) {
    var options = _.defaults(opt_options || {}, {
      followDirections: true,
      travelMode: 'DRIVING',
      commandManager: new CommandManager(),
      AddWaypointCommand: AddWaypointCommand,
      MoveWaypointCommand: MoveWaypointCommand,
      RemoveWaypointCommand: RemoveWaypointCommand,
      ResetRouteCommand: ResetRouteCommand,
      AppendReverseRouteCommand: AppendReverseRouteCommand,
      styles: {}
    });

    Events.call(this);


    /**
     * The Route being built.
     *
     * @type {aeris.maps.gmaps.route.Route}
     * @private
     * @property route_
     */
    this.route_;


    /**
     * The {aeris.maps.gmaps.route.RouteRenderer} used by the
     * builder to render route view elements.
     *
     * @type {aeris.maps.gmaps.route.RouteRenderer}
     * @private
     * @property routeRenderer_
     */
    this.routeRenderer_ = options.routeRenderer || new RouteRenderer();

    /**
     * Travel mode to set on new waypoints.
     * @type {aeris.maps.gmaps.route.Waypoint.travelMode}
     * @property travelMode
     */
    this.travelMode = options.travelMode;


    /**
     * Whether added waypoints should follow directions.
     * @type {Boolean}
     * @property followDirections
     */
    this.followDirections = options.followDirections;



    /**
     *
     * @type {boolean}
     * @property commandManager_
     */
    this.commandManager_ = options.commandManager;


    /**
     * @type {Function} Constructor.
     * @private
     * @property AddWaypointCommand_
     */
    this.AddWaypointCommand_ = options.AddWaypointCommand;


    /**
     * @type {Function} Constructor.
     * @private
     * @property MoveWaypointCommand_
     */
    this.MoveWaypointCommand_ = options.MoveWaypointCommand;


    /**
     * @type {Function} Constructor.
     * @private
     * @property RemoveWaypointCommand_
     */
    this.RemoveWaypointCommand_ = options.RemoveWaypointCommand;


    /**
     * @type {Function} Constructor.
     * @private
     * @property ResetRouteCommand_
     */
    this.ResetRouteCommand_ = options.ResetRouteCommand;


    /**
     * @type {Function} Constructor.
     * @private
     * @property AppendReverseRouteCommand_
     */
    this.AppendReverseRouteCommand_ = options.AppendReverseRouteCommand;


    /**
     * Hash of event listeners bound to the {aeris.maps.gmaps.route.Route}
     *
     * @type {Object}
     * @private
     * @property routeEvents_
     */
    this.routeEvents_ = {
      add: [this.renderWaypoint_, this.updateWaypointInstructions_],
      remove: this.eraseWaypoint_,
      reset: function(route, options) {
        _.each(options.previousModels, this.eraseWaypoint_, this);
        this.redrawRoute_(route);
      },

      'change:position change:position change:path change:selected': function(waypoint) {
        this.renderWaypoint_(waypoint);
      },

      'click': function(latLon, waypoint) {
        this.trigger('waypoint:click', latLon, waypoint);
      },
      'dragend': function(latLon, waypoint) {
        this.trigger('waypoint:dragend', latLon, waypoint);
      },
      'path:click': function(latLon, waypoint) {
        this.trigger('path:click', latLon, waypoint);
      }
    };

    this.setRoute(options.route || new Route());

    this.styleRoute(options.styles);

    this.proxyCommandManagerEvents_();

    /**
     * @event waypoint:click
     * @param {aeris.maps.LatLon} latLon
     * @param {aeris.maps.gmaps.route.Waypoint} marker
     */
    /**
     * @event waypoint:dragend
     * @param {aeris.maps.LatLon} latLon
     * @param {aeris.maps.gmaps.route.Waypoint} marker
     */
    /**
     * @event path:click
     * @param {aeris.maps.LatLon} latLon
     * @param {aeris.maps.gmaps.route.Waypoint} marker
     *        Waypoint whose path was clicked.
     */

    /**
     * A command has been executed, undone, or redone.
     * @event command
     */
    /**
     * @event command:execute
     */
    /**
     * @event command:undo
     */
    /**
     * @event command:redo
     */
  };

  _.extend(RouteBuilder.prototype, Events.prototype);


  RouteBuilder.prototype.proxyCommandManagerEvents_ = function() {
    this.proxyEvents(this.commandManager_, function(topic, arguments) {
      this.trigger('command');

      return {
        topic: 'command:' + topic,
        arguments: []               // No need to expose command parameter
      };
    }, this);
  };


  /**
   * @return {aeris.maps.gmaps.route.Route}
   * @method getRoute
   */
  RouteBuilder.prototype.getRoute = function() {
    return this.route_;
  };


  /**
   * @param {aeris.maps.Map} map
   * @method setMap
   */
  RouteBuilder.prototype.setMap = function(map) {
    this.routeRenderer_.setMap(map);
  };


  /**
   * @param {Object} styles
   * @method styleRoute
   */
  RouteBuilder.prototype.styleRoute = function(styles) {
    this.routeRenderer_.setStyles(styles);
  };


  /**
   * Delegates events to `this.route_`.
   *
   * @param {Object=} opt_events Defaults to this.routeEvents_.
   *        eg { 'click': this.handleClick_ }.
   * @param {Object} opt_ctx Context in which to invoke event handlers.
   * @method delegateRouteEvents
   */
  RouteBuilder.prototype.delegateRouteEvents = function(opt_events, opt_ctx) {
    opt_events || (opt_events = this.routeEvents_);
    opt_ctx || (opt_ctx = this);

    this.listenTo(this.getRoute(), opt_events, opt_ctx);
  };


  /**
   * Undelegate events bound to this.route_
   * @method undelegateRouteEvents
   */
  RouteBuilder.prototype.undelegateRouteEvents = function() {
    this.stopListening(this.getRoute());
  };


  /**
   * Undelegate all events bound by this
   * RouteBuilder.
   * @method undelegateEvents
   */
  RouteBuilder.prototype.undelegateEvents = function() {
    this.undelegateRouteEvents();
    this.commandManager_.removeProxy();
  };


  /**
   * @return {Object}
   * @method routeToJSON
   */
  RouteBuilder.prototype.routeToJSON = function() {
    return this.route_.toJSON();
  };


  /**
   * Undo the last executed command
   *
   * @return {aeris.Promise} Promise to undo command.
   * @method undo
   */
  RouteBuilder.prototype.undo = function() {
    return this.commandManager_.undo();
  };


  /**
   * @return. {Boolean} Whether undo is an available action.
   * @method canUndo
   */
  RouteBuilder.prototype.canUndo = function() {
    return this.commandManager_.canUndo();
  };

  /**
   * Redo the last executed command.
   *
   * @return {aeris.Promise} Promise to redo command.
   * @method redo
   */
  RouteBuilder.prototype.redo = function() {
    return this.commandManager_.redo();
  };


  /**
   * @return. {Boolean} Whether redo is an available action.
   * @method canRedo
   */
  RouteBuilder.prototype.canRedo = function() {
    return this.commandManager_.canRedo();
  };


  /**
   * Add a waypoint to the route.
   *
   * @param {aeris.maps.gmaps.route.Waypoint|Array.<number>} waypoint
   *
   * @param {Object=} opt_options
   * @param {number} opt_options.at Index at which to add the waypoint.
   *
   * @return {aeris.Promise} A promise to add the waypoint.
   * @method addWaypoint
   */
  RouteBuilder.prototype.addWaypoint = function(waypoint, opt_options) {
    var options = opt_options || {};
    var command;

    this.updateWaypointInstructions_(waypoint);

    if (options.at) {
      return this.addWaypointAt(waypoint, options.at);
    }

    command = new this.AddWaypointCommand_(this.getRoute(), waypoint);
    return this.executeCommand_(command);
  };


  /**
   * Insert a waypoint into to the route at a specified index.
   *
   * @param {aeris.maps.gmaps.route.Waypoint|Array.<number>} waypoint
   * @param {Number=} atIndex
   *
   * @return {aeris.Promise} A promise to add the waypoint.
   * @method addWaypointAt
   */
  RouteBuilder.prototype.addWaypointAt = function(waypoint, atIndex) {
    var command;
    var commandOptions = {
      at: atIndex
    };

    this.updateWaypointInstructions_(waypoint);

    command = new this.AddWaypointCommand_(this.getRoute(), waypoint, commandOptions);

    this.executeCommand_(command);
  };


  /**
   * Change the position of a waypoint.
   *
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @param {aeris.maps.LatLon} latLon
   *
   * @return {aeris.Promise}
   * @method moveWaypoint
   */
  RouteBuilder.prototype.moveWaypoint = function(waypoint, latLon) {
    var command = new this.MoveWaypointCommand_(this.getRoute(), waypoint, latLon);
    return this.executeCommand_(command);
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @return {aeris.Promise} A promise to remove the waypoint.
   * @method removeWaypoint
   */
  RouteBuilder.prototype.removeWaypoint = function(waypoint) {
    var command = new this.RemoveWaypointCommand_(this.getRoute(), waypoint);
    return this.executeCommand_(command);
  };


  /**
   * @return {aeris.Promise} Promise to execute command.
   * @method appendReverseRoute
   */
  RouteBuilder.prototype.appendReverseRoute = function() {
    var command;

    var doesRouteHaveWaypointsToReverse = this.route_.length > 1;
    if (!doesRouteHaveWaypointsToReverse) { return new EmptyPromise(); }

    command = new this.AppendReverseRouteCommand_(this.getRoute());

    return this.executeCommand_(command);
  };


  /**
   * Reset all waypoints in a route.
   *
   * @param {Array.<aeris.maps.gmaps.route.Waypoint>=} opt_waypoints
   *        If not set, will clear all waypoints in the route.
   * @return {aeris.Promise} A promise to reset the route.
   * @method resetRoute
   */
  RouteBuilder.prototype.resetRoute = function(opt_waypoints) {
    var command;

    command = new this.ResetRouteCommand_(this.getRoute(), opt_waypoints);
    return this.executeCommand_(command);
  };


  /**
   * @private
   *
   * @param {aeris.commands.AbstractCommand} command
   * @return {aeris.Promise}
   * @method executeCommand_
   */
  RouteBuilder.prototype.executeCommand_ = function(command) {
    var promiseToExecute = this.commandManager_.executeCommand(command);
    return promiseToExecute;
  };


  /**
   * @param {aeris.maps.gmaps.route.Route} route
   * @method setRoute
   */
  RouteBuilder.prototype.setRoute = function(route) {
    if (this.route_) {
      this.undelegateRouteEvents();
    }

    this.route_ = route;
    this.delegateRouteEvents();
  };


  /**
   * Render a waypoint.
   *
   * @private
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @method renderWaypoint_
   */
  RouteBuilder.prototype.renderWaypoint_ = function(waypoint) {
    this.routeRenderer_.renderWaypoint(waypoint);
  };


  RouteBuilder.prototype.updateWaypointInstructions_ = function(waypoint) {
    waypoint.set({
      followDirections: this.followDirections,
      travelMode: this.travelMode
    }, { validate: true });
  };


  /**
   * Remove a waypoint view from the map.
   *
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @param {number} indexOfWaypointInRoute
   * @private
   * @method eraseWaypoint_
   */
  RouteBuilder.prototype.eraseWaypoint_ = function(waypoint, indexOfWaypointInRoute) {
    var nextWaypoint = this.getRoute().at(indexOfWaypointInRoute);

    this.routeRenderer_.eraseWaypoint(waypoint);

    // Redraw next waypoint
    if (nextWaypoint) {
      this.routeRenderer_.renderWaypoint(nextWaypoint);
    }
  };


  /**
   * @param {Array.<aeris.maps.gmaps.route.Waypoint>} waypoints
   * @private
   * @method redrawRoute_
   */
  RouteBuilder.prototype.redrawRoute_ = function(waypoints) {
    this.routeRenderer_.eraseRoute(this.getRoute());

    if (waypoints.length) {
      this.routeRenderer_.renderRoute(this.getRoute());
    }
  };


  return _.expose(RouteBuilder, 'aeris.maps.gmaps.route.RouteBuilder');
});