Fork me on GitHub
Show:

File: ../src/commands/abstractcommand.js

define([
  'aeris/util',
  'aeris/promise',
  'aeris/errors/unimplementedmethoderror',
  'aeris/errors/invalidargumenterror',
  'aeris/errors/commandhistoryerror'
], function(_, Promise, UnimplementedMethodError, InvalidArgumentError, CommandHistoryError) {
  /**
   * AbstractCommand
   *
   * Base class for all commands.
   * Handles invalid command requests.
   *  eg: attempting to undo a command that hasn't been
   *      executed will throw a CommandHistoryError.
   *
   * Proper use of {aeris.commands.CommandManager}
   * will help you avoid these types of errors.
   *
   *
   * @abstract
   * @class aeris.commands.AbstractCommand
   * @constructor
   */
  var AbstractCommand = function() {
    /**
     * Is an action currently in progress / unresolved?
     *
     * @type {boolean}
     * @private
     * @property requestPending_
     */
    this.requestPending_ = false;


    /**
     * 1 = Execution requested (may not be complete)
     * 0 = Undo request
     * -1 = No actions requested
     * @type {number}
     * @property requestState_
     */
    this.requestState_ = -1;
  };


  /**
   * Execute the command.
   * wrapper around {aeris.commands.AbstractCommand}#execute_
   *
   * Note that classes extending from {aeris.commands.AbstractCommand}
   * should override the protected {aeris.commands.AbstractCommand}#execute_
   * method, which is called by the public {aeris.commands.AbstractCommand}#execute
   * method.
   *
   * @method execute
   * @final
   * @return {aeris.Promise} A promise to complete execution of the command.
   */
  AbstractCommand.prototype.execute = function() {
    var promise = new Promise();

    // Because I keep passing in a callback here, instead of to .done()
    if (arguments.length) {
      throw new InvalidArgumentError('Execute does not expect any arguments.');
    }

    if (this.requestState_ === 1) {
      throw new CommandHistoryError('Unable to execute command: command has already been executed');
    }
    else if (this.requestPending_) {
      throw new CommandHistoryError('Unable to execute command: undo is still in progress');
    }

    this.requestState_ = 1;
    this.requestPending_ = true;

    // Execute command
    this.execute_().
        always(function() {
          this.requestPending_ = false;
        }, this).
        done(promise.resolve, promise).
        fail(promise.reject, promise);


    return promise;
  };


  /**
   * Undo a command
   * wrapper around {aeris.commands.AbstractCommand}#undo_
   *
   * Note that classes extending from {aeris.commands.AbstractCommand}
   * should override the protected {aeris.commands.AbstractCommand}#undo_
   * method, which is called by the public {aeris.commands.AbstractCommand}#undo
   * method.
   *
   * @method undo
   * @final
   * @abstract
   * @return {aeris.Promise} A promise to complete the undo.
   */
  AbstractCommand.prototype.undo = function() {
    var promise = new Promise();

    // Because I keep passing in a callback here, instead of to .done()
    if (arguments.length) {
      throw new InvalidArgumentError('Undo does not expect any arguments.');
    }

    if (this.requestState_ !== 1) {
      throw new CommandHistoryError('Unable to undo command: command has not yet been executed');
    }
    else if (this.requestPending_) {
      throw new CommandHistoryError('Unable to undo command: command execution is still in progress.');
    }

    this.requestState_ = 0;
    this.requestPending_ = true;

    // Undo command
    this.undo_().
        always(function() {
          this.requestPending_ = false;
        }, this).
        done(promise.resolve, promise).
        fail(promise.reject, promise);

    return promise;
  };


  /**
   * Handle the business logic of executing a command.
   * Must return a {aeris.Promise}
   *
   * @private
   * @method execute_
   * @abstract
   * @return {aeris.Promise} A promise to execute the command.
   */
  AbstractCommand.prototype.execute_ = function() {
    throw new UnimplementedMethodError('Command has not implemented an execute_ method.');
  };


  /**
   * Handle the business logic of undoing a command
   * Must return a {aeris.Promise}
   *
   * @protected
   * @private undo_
   * @abstract
   * @return {aeris.Promise} A promise to undo the command.
   */
  AbstractCommand.prototype.undo_ = function() {
    throw new UnimplementedMethodError('Command has not implemented a undo_ method');
  };


  return AbstractCommand;
});