<!DOCTYPE html>
<html ng-app="app">

  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <title>Sign-in Register Service</title>
    <link href="//code.ionicframework.com/1.1.0/css/ionic.css" rel="stylesheet" />
    <link rel="stylesheet" href="style.css" />
    
    <script src="//code.ionicframework.com/1.1.0/js/ionic.bundle.js"></script>
    <script data-require="lodash.js@3.10.0" data-semver="3.10.0" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.js"></script>
    
    <script src="script.js"></script>
    <script src="modals.service.js"></script>
    <script src="sign-in-register.service.js"></script>
    
  </head>

  <body ng-controller="AppCtrl as vm">
    <ion-header-bar class="bar-positive">
      <h1 class="title">Sign-in/Register Service</h1>
    </ion-header-bar>
    <ion-content class="has-header">
      <div class="row responsive-sm">
        <div class="col col-offset-25 col-50">
      <div class="list card">
        <div class="item item-divider item-positive">Sign-in or Register</div>
        <div class="item">
          <div class="button-bar">
            <button ng-click="vm.showSignInRegister( 'signin' )" class="button button-balanced">
              Sign in
            </button>
            <button ng-click="vm.showSignInRegister( 'signup' )" class="button button-balanced">
              Register
            </button>
          </div>
        </div>
        <div class="item">
          <label class="item item-input">
            <span class="input-label">Username</span>
            <input type="text" ng-model="vm.user.username" placeholder="username" readonly="readonly" />
          </label>
        </div>
        <div class="item">  
          <label class="item item-input">
            <span class="input-label">Email Address</span>
            <input type="text" ng-model="vm.user.email" readonly="readonly" />
          </label>
        </div>
      </div>
      <div class="card">
        <div class="item item-divider item-assertive">console</div>
        <div class="item">
          <pre ng-repeat="msg in vm.console">{{msg}}</pre>
        </div>
      </div>
      </div></div>
    </ion-content>
  </body>

</html>
// condensed John Papa style
(function() {

  'use strict';

  angular.module('app', ['ionic', 'app.core']);
  angular.module('app.core', ['app.components']);
  angular.module('app.components', []);


  var appRun, ionicConfig, toastrConfig;

  appRun = function($rootScope, $ionicPlatform, $ionicHistory, $location, $state) {
    $ionicPlatform.ready(function() {
      if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
        cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      }
      if (window.StatusBar) {
        return StatusBar.styleLightContent();
      }
    });
  };


  ionicConfig = function($ionicConfigProvider) {
    $ionicConfigProvider.backButton.text('').icon('ion-ios-arrow-back').previousTitleText(false);
  };


  appRun.$inject = ['$rootScope', '$ionicPlatform', '$ionicHistory', '$location', '$state'];

  ionicConfig.$inject = ['$ionicConfigProvider'];

  angular.module('app.core')
    .config(ionicConfig)
    .run(appRun);
    
  
  /*
   * for testing REST API calls
   */
  var UsersResource = function($q){
    this.data = [
      {
        username: 'bob',
        email: 'bob@mail.com'
      },
      {
        username: 'art',
        email: 'art@mail.com'
      },
      {
        username: 'jane',
        email: 'jane@mail.com'
      }
    ];
    this.query = function(filter){
      var found = _.filter( this.data, filter);
      return $q.when(found);
    };
    this.post = function(data){
      var exists = _.filter( this.data, {username:data.username})
      if (exists.length)
        return $q.reject( "DUPLICATE KEY");
      this.data.push(data);
      return $q.when(data)
    }
  }
UsersResource.$inject = ['$q']  


  var AppCtrl;

  AppCtrl = function($log, $rootScope, $q, signInRegisterSvc, UsersResource) {
    var vm;
    vm = this;

    vm.isBrowser = ionic.Platform.isWebView() === false
    vm.user = {};

    vm.signIn = function(data) {
      vm.user = {};
      return $q.when( data
      ).then(function(data) {
        return UsersResource.query({
          username: data.username
        });
      }).then(function(user) {
          $rootScope.$emit('user:sign-in', $rootScope['user']);
          return user;
      })["catch"](function(err) {
        $rootScope.$emit('user:sign-out');
      });
    }
    
    vm.register = function(data) {
      vm.user = {};
      return $q.when( data
      ).then(function(data) {
        return UsersResource.post(data);
      })["catch"](function(err) {
        $rootScope.$emit('user:sign-out');
        if (_.isString(err)) {
          err = {
            message: err
          };
        }
        err['isError'] = true;
        return err;
      });
    }
    
    vm.showSignInRegister = function(initialSlide) {
      var vm;
      vm = this;
      return signInRegisterSvc.showSignInRegister(initialSlide, {
        signIn: vm.signIn,
        checkIfUserExists: function(data) {
          return UsersResource.query({
            username: data.username
          });
        },
        register: vm.register,
        notImplemented: null
      }).then(function(results) {
        console.log(['SignInRegisterSvc', results]);
        vm.user = results;
        return results;
      });

    };


    window.console.log = function(msg) {
      vm.console.unshift(vm.console.length + ":" + JSON.stringify(msg, null, 2));
    }

    vm.loading = false;
    vm.console = [];
    console.log("ready")
    return vm

  };

  AppCtrl.$inject = ['$log', '$rootScope', '$q', 'signInRegisterSvc', 'UsersResource'];

  angular.module('app')
    .service('UsersResource', UsersResource)
    .controller('AppCtrl', AppCtrl);

}).call(this);
/* Styles go here */

.pull-right {
    float: right;
}
#sign-in-register-modal-view .item, #sign-in-register-modal-view .item-input {
  border-color:transparent;
}
#sign-in-register-modal-view input {
  width: 100%;
}
#sign-in-register-modal-view .item a {
  cursor: pointer;
}

# Sign-in/Register Service for ionic Framework
This service uses `$ionicModal` to open a modal content pane containing 
an `ion-slide` with both a sign-up and register form.

add `signInRegisterSvc` as a dependency to your controller, 
then launch the service with the following call: 

```javascript
 signInRegisterSvc.showSignInRegister( 'signup', {
    signIn: function(data){
      // your server sign-in code
    },
    checkIfUserExists: function(data) {
      // check if username already exists
    },
    register: function(data){
      // your server new user registration code
    }
  }).then(function(results) {
    console.log(['SignInRegisterSvc', results]);
    $rootScope.user = results;
    return results;
  });

```

`bower.json` dependencies
```json
{
  "devDependencies": {
    "ionic": "driftyco/ionic-bower#1.1.0",
    "lodash": "~3.10.1",
  }
}
```
(function() {
  'use strict';

  /*
   * @description: reusable $ionicModal service
   * see: http://forum.ionicframework.com/t/ionic-modal-service-with-extras/15357
   * also: http://codepen.io/anon/pen/KdzawK?editors=101
   * usage: appModalSvc.show( <templateUrl>, "controller as vm", params )
   */
  var ReusableModal;

  ReusableModal = function($ionicModal, $rootScope, $q, $injector, $controller) {
    var _cleanup, _evalController, show;
    show = function(templateUrl, controller, parameters, options) {
      var ctrlInstance, defaultOptions, dfd, modalScope, thisScopeId;
      dfd = $q.defer();
      modalScope = $rootScope.$new();
      thisScopeId = modalScope.$id;
      ctrlInstance = null;
      defaultOptions = {
        animation: 'slide-in-up',
        focusFirstInput: false,
        backdropClickToClose: true,
        hardwareBackButtonClose: true,
        modalCallback: null
      };
      options = angular.extend(defaultOptions, options, {
        scope: modalScope
      });
      $ionicModal.fromTemplateUrl(templateUrl, options).then(function(modal) {
        var ctrlEval, locals, same;
        modalScope.modal = modal;
        modalScope.openModal = function() {
          return modalScope.modal.show();
        };
        modalScope.closeModal = function(result) {
          dfd.resolve(result);
          return modalScope.modal.hide();
        };
        modalScope.$on('modal.hidden', function(thisModal) {
          var modalScopeId;
          if (thisModal.currentScope) {
            modalScopeId = thisModal.currentScope.$id;
            if (thisScopeId === thisModal.currentScope.$id) {
              dfd.resolve(null);
              return _cleanup(thisModal.currentScope);
            }
          }
        });
        if (angular.isObject(controller)) {
          modalScope.vm = ctrlInstance = controller;
          same = modalScope.vm === modalScope.modal.scope.vm;
          ctrlInstance['openModal'] = modalScope.openModal;
          ctrlInstance['closeModal'] = modalScope.closeModal;
        } else {
          locals = {
            '$scope': modalScope,
            'parameters': parameters
          };
          ctrlEval = _evalController(controller);
          if (ctrlEval.controllerName) {
            ctrlInstance = $controller(controller, locals);
          }
          if (ctrlEval.isControllerAs && ctrlInstance) {
            ctrlInstance['openModal'] = modalScope.openModal;
            ctrlInstance['closeModal'] = modalScope.closeModal;
          }
        }
        if (parameters != null) {
          angular.extend(modalScope, parameters);
        }
        return modalScope.modal.show().then(function() {
          modalScope.$broadcast('modal.afterShow', modalScope.modal);
          return typeof options.modalCallback === "function" ? options.modalCallback(modal) : void 0;
        });
      }, function(err) {
        return dfd.reject(err);
      });
      return dfd.promise;
    };
    _cleanup = function(scope) {
      scope.$destroy();
      if (scope.modal) {
        scope.modal.remove();
      }
    };
    _evalController = function(ctrlName) {
      var fragments, result;
      if (ctrlName == null) {
        ctrlName = '';
      }
      result = {
        isControllerAs: false,
        controllerName: '',
        propName: ''
      };
      fragments = ctrlName.trim().split(/\s+/);
      result.isControllerAs = fragments.length === 3 && (fragments[1] || '').toLowerCase() === 'as';
      if (result.isControllerAs) {
        result.controllerName = fragments[0];
        result.propName = ctrlName;
      } else {
        result.controllerName = ctrlName;
      }
      return result;
    };
    return {
      show: show
    };
  };

  ReusableModal.$inject = ['$ionicModal', '$rootScope', '$q', '$injector', '$controller'];

  angular.module('app.components')
    .factory('appModalSvc', ReusableModal);

}).call(this);
<style id="sign-in-register-style">
  @media (min-width: 680px) {
    #sign-in-register-modal-view.modal { top: 20%; height:424px; bottom: 20%;}
  }
</style>
<ion-modal-view id="sign-in-register-modal-view">{{vm.slideCtrl.setSlide();''}}
  <ion-slide-box delegate-handle="sign-in-register" active-slide="vm.slideCtrl.index" auto-play="false" show-pager="false" slide-interval="" pager-click="" on-slide-changed="">
    <ion-slide class="sign-up">
      <ion-scroll>
        <div class="list">
          <div class="item">
            <button ng-click="vm.on.notImplemented('Facebook Connect')" class="button button-block button-positive light icon ion-social-facebook">&nbsp; Sign-up with Facebook</button>
          </div>
          <div class="item">
            <div class="text-center larger">&mdash; or &mdash;</div>
          </div>
          <div class="padding-horizontal">
            <form name="registerForm" novalidate="" ng-submit="vm.on.register(person)">
              <label class="item item-input"><i class="icon ion-person padding-horizontal larger"></i>
                <input type="text" name="Username" placeholder="Username" ng-model="person.username" required="required"/>
              </label>
              <div ng-show="vm['error']['username']" class="error"><span class="assertive">{{vm['error']['username']}}</span></div>
              <label class="item item-input"><i class="icon ion-email padding-horizontal larger"></i>
                <input type="text" name="Email" placeholder="Email" ng-model="person.email" required="required"/>
              </label>
              <label class="item item-input"><i class="icon ion-locked padding-horizontal larger"></i>
                <input type="password" name="Password" placeholder="Password" ng-model="person.password" required="required"/>
              </label>
              <label class="item item-input hide"><i class="icon ion-locked padding-horizontal larger"></i>
                <input type="password" name="PasswordRepeat" placeholder="Again" ng-model="person.passwordRepeat"/>
              </label>
              <div ng-show="vm['error']['passwordRepeat']" class="error"><span class="assertive">{{vm['error']['passwordRepeat']}}  </span></div>
              <div class="item">
                <div class="button-bar">
                  <button ng-click="closeModal('CANCELED')" class="button button-balanced button-outline">Cancel</button>
                  <button type="submit" class="button button-balanced">Sign up</button>
                </div>
              </div>
              <div class="item positive">
                <div class="text-right"> <a ng-click="vm.slideCtrl.setSlide('signin')">Have an account? Sign in <i class="icon ion-android-arrow-forward padding-right"></i></a></div>
              </div>
            </form>
          </div>
        </div>
      </ion-scroll>
    </ion-slide>
    <ion-slide class="sign-in">
      <ion-scroll>
        <div class="list">
          <div class="item">
            <button ng-click="vm.on.notImplemented('Facebook Connect')" class="button button-block button-positive light icon ion-social-facebook">&nbsp; Sign-in with Facebook</button>
          </div>
          <div class="item">
            <div class="text-center larger">&mdash; or &mdash;</div>
          </div>
          <div class="padding-horizontal">
            <form name="signInForm" novalidate="" ng-submit="vm.on.signIn(person);">
              <label class="item item-input"><i class="icon ion-person padding-horizontal larger"></i>
                <input type="text" name="Username" placeholder="Username" ng-model="person.username" required="required"/>
              </label>
              <div ng-show="vm['error']['username']" class="error"><span class="assertive">{{vm['error']['username']}}</span></div>
              <label class="item item-input"><i class="icon ion-locked padding-horizontal larger"></i>
                <input type="password" name="Password" placeholder="Password" ng-model="person.password" required="required"/>
              </label>
              <div class="item">
                <div class="button-bar">
                  <button ng-click="closeModal('CANCELED')" class="button button-balanced button-outline">Cancel</button>
                  <button type="submit" class="button button-balanced">Sign in</button>
                </div>
              </div>
              <div class="item positive text-left">
                <div class="pull-right"><a ng-click="vm.on.notImplemented('password recovery')">Forgot Password?</a></div><a ng-click="vm.slideCtrl.setSlide('signup')"><i class="icon ion-android-arrow-back padding-left"></i> Sign up</a>
              </div>
            </form>
          </div>
        </div>
      </ion-scroll>
    </ion-slide>
  </ion-slide-box>
</ion-modal-view>
(function() {
  'use strict';
  var CALLBACKS, MODAL_VIEW, SignInRegister, SignInRegisterCtrl, signInRegisterSvcConfig;

  CALLBACKS = {
    signIn: function(data) {
      return [data];
    },
    checkIfUserExists: function(username) {
      return [];
    },
    register: function(data) {
      return data;
    },
    notImplemented: function(label) {
      return console.log(["Sorry,", label, "is not available yet."].join(' '));
    }
  };

  MODAL_VIEW = {
    TEMPLATE: 'sign-in-register.template.html',
    CONTENT_HEIGHT: 424,
    GRID_RESPONSIVE_SM_BREAK: 680,
    MESSAGE: {},
    ERROR: {
      REQURIED: 'Please enter a value.',
      USERNAME_EXISTS: 'Sorry, that username is already taken.',
      PASSWORD_NO_MATCH: 'Sorry, your passwords do not match.',
      USER_NOT_FOUND: 'Sorry, the username and password combination was not found.'
    },
    DEFAULT_SLIDE: 'signin'
  };


  /*
   * @description Sign-in or Register modal service
   * shows $ionicModal for allowing user sign-in or register
   */

  SignInRegister = function($q, appModalSvc) {
    var init, self, setModalHeight;
    init = function() {
      var stop;
      stop = $scope.$on('modal.afterShow', function(ev) {
        var h;
        h = setModalHeight();
        if (typeof stop === "function") {
          stop();
        }
      });
    };
    setModalHeight = function() {
      var contentH, marginH, modalH, styleH;
      contentH = $window.innerWidth <= MODAL_VIEW.GRID_RESPONSIVE_SM_BREAK ? $window.innerHeight : Math.max(MODAL_VIEW.CONTENT_HEIGHT, $window.innerHeight);
      marginH = ($window.innerHeight - contentH) / 2;
      modalH = Math.max(MODAL_VIEW.MAP_MIN_HEIGHT, modalH);
      styleH = "#sign-in-register-modal-view.modal {top:%marginH%px; bottom:%marginH%px; height:%modalH%px}";
      styleH = styleH.replace(/%marginH%/g, marginH).replace(/%modalH%/g, modalH);
      angular.element(document.getElementById('address-lookup-style')).append(styleH);
      return modalH;
    };
    self = {
      showSignInRegister: function(initialSlide, callbacks) {
        if (_.isFunction(callbacks.signIn)) {
          CALLBACKS.signIn = callbacks.signIn;
        }
        if (_.isFunction(callbacks.checkIfUserExists)) {
          CALLBACKS.checkIfUserExists = callbacks.checkIfUserExists;
        }
        if (_.isFunction(callbacks.register)) {
          CALLBACKS.register = callbacks.register;
        }
        if (_.isFunction(callbacks.notImplemented)) {
          CALLBACKS.notImplemented = callbacks.notImplemented;
        }
        return appModalSvc.show( MODAL_VIEW.TEMPLATE, 'SignInRegisterCtrl as vm', {
          initialSlide: initialSlide,
          person: {}
        }).then(function(result) {
          console.log(['showSignInRegister', result]);
          if (result == null) {
            result = 'CANCELED';
          }
          if (result === 'CANCELED') {
            return $q.reject('CANCELED');
          }
          if (result != null ? result['isError'] : void 0) {
            return $q.reject(result);
          }
          return result;
        });
      }
    };
    return self;
  };

  SignInRegister.$inject = ['$q', 'appModalSvc'];


  /*
   * @description controller for appModalSvc $ionicModal
   *
   */

  SignInRegisterCtrl = function($scope, parameters, $q, $timeout, $window) {
    var vm;
    vm = this;
    vm.isBrowser = !ionic.Platform.isWebView();
    vm.MESSAGE = MODAL_VIEW.MESSAGE;
    vm['slideCtrl'] = {
      index: null,
      slideLabels: ['signup', 'signin'],
      initialSlide: parameters.initialSlide || MODAL_VIEW.DEFAULT_SLIDE,
      setSlide: function(label) {
        var i, next;
        if (vm['slideCtrl'].index === null) {
          $timeout(function() {
            if (!label) {
              label = vm['slideCtrl'].initialSlide;
            }
            return vm['slideCtrl'].setSlide(label);
          });
          return vm['slideCtrl'].index = 0;
        }
        if (label==null) {
          return vm['slideCtrl'].index;
        }
        if (label === 'initial') {
          label = vm['slideCtrl'].initialSlide;
        }
        i = vm['slideCtrl'].slideLabels.indexOf(label);
        next = i >= 0 ? i : vm['slideCtrl'].index;
        vm['error'] = {};
        return vm['slideCtrl'].index = next;
      }
    };
    vm['error'] = {};
    vm['on'] = {
      signIn: function(data, fnComplete) {
        if (data == null) {
          data = {};
        }
        vm['error'] = {};
        return $q.when().then(function() {
          if (!data.username) {
            return $q.reject('NOT FOUND');
          }
          data.username = data.username.toLowerCase().trim();
          return CALLBACKS.signIn(data);
        }).then(function(results) {
          var person;
          person = _.isArray(results) ? results.shift() : results;
          if (_.isEmpty(person)) {
            return $q.reject('NOT FOUND');
          }
          return person;
        }).then(function(result) {
          vm.closeModal(result);
          return result;
        })["catch"](function(err) {
          if (err === 'NOT FOUND') {
            vm['error']['username'] = MODAL_VIEW.ERROR.USER_NOT_FOUND;
          }
          return $q.reject(err);
        });
      },
      register: function(data, fnComplete) {
        if (data == null) {
          data = {};
        }
        vm['error'] = {};
        return $q.when().then(function() {
          if (!data.username) {
            return $q.reject('REQUIRED VALUE');
          }
          data.username = data.username.toLowerCase().trim();
          return CALLBACKS.checkIfUserExists(data);
        }).then(function(results) {
          var person;
          person = _.isArray(results) ? results.shift() : results;
          if (!_.isEmpty(person)) {
            return $q.reject('DUPLICATE USERNAME');
          }
          return CALLBACKS.register(data);
        }).then(function(result) {
          vm.closeModal(result);
          return result;
        })["catch"](function(err) {
          if (err === 'REQUIRED VALUE') {
            vm['error']['username'] = MODAL_VIEW.ERROR.REQUIRED;
          }
          if (err === 'DUPLICATE USERNAME') {
            vm['error']['username'] = MODAL_VIEW.ERROR.USERNAME_EXISTS;
          }
          return $q.reject(err);
        });
      },
      notImplemented: function(value) {
        return CALLBACKS.notImplemented(value);
      }
    };
    return vm;
  };

  SignInRegisterCtrl.$inject = ['$scope', 'parameters', '$q', '$timeout', '$window'];

  angular.module('app.components')
    .factory('signInRegisterSvc', SignInRegister)
    .controller('SignInRegisterCtrl', SignInRegisterCtrl);

}).call(this);