<!doctype html>
<html ng-app="commandDemo">

<head>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.js"></script>
  <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.2.js"></script>
  <script src="example.js"></script>
  <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>

<body class="container">
  <h1 class="page-header">Command Pattern Demo</h1>
  <div ng-controller="ListCtrl">
    <h2>List</h2>
    <ol>
      <li ng-repeat="item in list">{{item.name}}</li>
    </ol>
    <button ng-click="import()" class="btn btn-default">Import range from list</button>
    <button ng-click="export()" class="btn btn-default">Export range from list</button>
  </div>
</body>
</html>
angular.module('commandDemo', ['ui.bootstrap']);

angular.module('commandDemo').controller('ListCtrl', function($scope, $modal) {
  $scope.list = [
    {name: 'Asking Alexandria'},
    {name: 'Atreyu'},
    {name: 'Audioslave'},
    {name: 'Automatic Pilot'},
    {name: 'Avenged Sevenfold'},
    {name: 'A Wilhelm Scream'},
    {name: 'The B-52\'s'},
    {name: 'Babymetal'},
    {name: 'Bachman–Turner Overdrive'},
    {name: 'Bad Religion'},
    {name: 'Badfinger'},
    {name: 'The Band'},
    {name: 'Bauhaus'},
    {name: 'The Beatles'},
    {name: 'The Beautiful South'},
    {name: 'Belle & Sebastian'},
    {name: 'Between the Buried and Me'},
    {name: 'Biffy Clyro'},
    {name: 'Big Drill Car'}
  ];

  $scope.export = function() {
    $modal.open({
      templateUrl: 'exportModal.html',
      controller: 'ModalExportCtrl',
      resolve: {
        list: function() {
          return $scope.list;
        }
      }
    });
  }

  $scope.import = function() {
    $modal.open({
      templateUrl: 'importModal.html',
      controller: 'ModalImportCtrl',
      resolve: {
        list: function() {
          return $scope.list;
        }
      }
    });
  }
});

angular.module('commandDemo').factory('rangeManager', function() {

  /**
   * Return a request ready object
   * @param  obj range
   * @return obj rangeParams, an object to be parameterized in a request
   */
  function getParams(range) {
    return {
      limit: range.limit,
      offset: range.offset
    };
  }

  /**
   * Return a default range object from list
   * @param  array list
   * @return obj
   */
  function getDefaults(list) {
    return {
      limit: list.length,
      offset: 0,
      startingIndex: 1,
      endingIndex: list.length,
      count: list.length,
      limit: null,
      isValid: null
    }
  };

  /**
   * Validate range selection fields and set errors
   * @param  obj range
   * @return obj validatedRange, an range object object decorated with isValid, errorMessage, offset, limit
   * properties derived from starting and ending indices
   */
  function decorate(range) {
    var startingIndex = range.startingIndex;
    var endingIndex = range.endingIndex;
    var count = range.count;
    var validatedRange = {};

    validatedRange.isValid = false;
    validatedRange.limit = endingIndex - startingIndex + 1;
    validatedRange.offset = startingIndex - 1;
    validatedRange.errorMessage = '';
    if (angular.isUndefined(startingIndex) || angular.isUndefined(endingIndex)) {
      validatedRange.errorMessage = 'Please provide missing fields';
    } else if (startingIndex > endingIndex) {
      validatedRange.errorMessage = 'Invalid range';
    } else if (startingIndex <= 0 || endingIndex <= 0) {
      validatedRange.errorMessage = 'Index must be positive';
    } else if (endingIndex > count) {
      validatedRange.errorMessage = 'Out of range of current list';
    } else {
      validatedRange.isValid = true;
    }
    return validatedRange;
  }

  function processRange(range) {
    return angular.extend(range, decorate(range));
  }

  return {
    processRange: processRange,
    getParams: getParams,
    getDefaults: getDefaults,
  }
});

angular.module('commandDemo').controller('ModalExportCtrl', function($scope, $modalInstance, rangeManager, list) {

  // set defaults
  $scope.range = rangeManager.getDefaults(list);

  // decorate range object with validation properties when range values change
  $scope.$watch('range.startingIndex', function(newVal) {
    var rangeToProcess = angular.extend($scope.range, {
      startingIndex: newVal
    });
    $scope.range = rangeManager.processRange(rangeToProcess);
  });

  $scope.$watch('range.endingIndex', function(newVal) {
    var rangeToProcess = angular.extend($scope.range, {
      endingIndex: newVal
    });
    $scope.range = rangeManager.processRange(rangeToProcess);
  });

  // grab limit and offset and perform an import operation
  $scope.export = function() {
    var params = rangeManager.getParams($scope.range);
    // would ordinarily make a backend call
    alert('Exporting with limit: ' + params.limit + ', offset: ' + params.offset);
    // on success, close modal
    $modalInstance.close();
  };

  $scope.cancel = function() {
    $modalInstance.dismiss('cancel');
  };
});

angular.module('commandDemo').controller('ModalImportCtrl', function($scope, $modalInstance, rangeManager, list) {

  // set defaults
  $scope.range = rangeManager.getDefaults(list);

  // decorate range object with validation properties when range values change
  $scope.$watch('range.startingIndex', function(newVal) {
    var rangeToProcess = angular.extend($scope.range, {
      startingIndex: newVal
    });
    $scope.range = rangeManager.processRange(rangeToProcess);
  });

  $scope.$watch('range.endingIndex', function(newVal) {
    var rangeToProcess = angular.extend($scope.range, {
      endingIndex: newVal
    });
    $scope.range = rangeManager.processRange(rangeToProcess);
  });

  // grab limit and offset and perform an import operation
  $scope.import = function() {
    var params = rangeManager.getParams($scope.range);
    // would ordinarily make a backend call
    alert('Importing with limit: ' + params.limit + ', offset: ' + params.offset);
    // on success, close modal
    $modalInstance.close();
  };

  $scope.cancel = function() {
    $modalInstance.dismiss('cancel');
  };
});
<div ng-class="{'has-error': !range.isValid}" class="text-center">
  <div class="row">
    <div class="col-md-5">
      <input
        required
        type="number"
        integer
        inputmode="numeric"
        class="form-control"
        ng-model="range.startingIndex"
        id="startingIndex"
        name="startingIndex"
      >
    </div>
    <div class="col-md-2">to</div>
    <div class="col-md-5">
      <input
        required
        type="number"
        integer
        inputmode="numeric"
        class="form-control"
        ng-model="range.endingIndex"
        name="endingIndex"
      />
    </div>
  </div>
</div>
<div class="col-md-12">
  <span ng-show="!range.isValid" class="help-block" style="color: #a94442">{{range.errorMessage}}</span>
</div>
<div class="modal-header">
  <h5 class="pull-right" ng-hide="!range.isValid || addMethod === 'addSelected'">
    Import
    <span class="help-message">{{range.limit}}</span> records
    </h5>
  <h3 class="modal-title">Import from list</h3>
</div>
<div class="modal-body" ng-include="'rangeSelector.html'"></div>
<div class="modal-footer">
  <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
  <button data-ng-disabled="!range.isValid" ng-click="import()" class="btn btn-primary">Import</button>
</div>
<div class="modal-header">
    <h5 class="pull-right" ng-hide="!range.isValid || addMethod === 'addSelected'">
      Export
      <span class="help-message">{{range.limit}}</span> records
    </h5>
    <h3 class="modal-title">Export from list</h3>
</div>
<div class="modal-body" ng-include="'rangeSelector.html'"></div>
<div class="modal-footer">
  <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
  <button data-ng-disabled="!range.isValid" ng-click="export()" class="btn btn-primary">Export</button>
</div>