<!doctype html>
<html lang="en" ng-app="myApp">
<head>
    <meta charset="utf-8">
    <title>My AngularJS App</title>
    <link rel="stylesheet" href="app.css"/>
    <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body ng-controller="mainCtrl">

<smart-table class="table table-striped" table-title="Smart Table example" columns="columnCollection"
             rows="rowCollection" config="globalConfig"></smart-table>

<script src="http://code.angularjs.org/1.2.0rc1/angular.min.js"></script>
<script src="http://code.angularjs.org/1.2.0rc1/angular-mocks.js"></script>
<script src="Smart-Table.debug.js"></script>
<script src="ui-bootstrap-custom-0.5.0.js"></script>
<script src="ui-bootstrap-custom-tpls-0.5.0.js"></script>
<script src="server.js"></script>
<script src="app.js"></script>
</body>
</html>
/* app css stylesheet */

.menu {
    list-style: none;
    border-bottom: 0.1em solid black;
    margin-bottom: 2em;
    padding: 0 0 0.5em;
}

.menu:before {
    content: "[";
}

.menu:after {
    content: "]";
}

.menu > li {
    display: inline;
}

.menu > li:before {
    content: "|";
    padding-right: 0.3em;
}

.menu > li:nth-child(1):before {
    content: "";
    padding: 0;
}

.smart-table-data-row.selected {
    background: darkgray;
}

.header-content:before {
    content: ' ';
    position: relative;
    left: -5px;
}

.sort-ascent:before {
    content: "\25B4";
}

.sort-descent:before {
    content: "\25BE";
}

.pagination {
    text-align: center;
    cursor: pointer;
}

.smart-table th {
    width: 120px;
    padding: 0 20px;
}


angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.dialog"]);
angular.module('ui.bootstrap.transition', [])

/**
 * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
 * @param  {DOMElement} element  The DOMElement that will be animated.
 * @param  {string|object|function} trigger  The thing that will cause the transition to start:
 *   - As a string, it represents the css class to be added to the element.
 *   - As an object, it represents a hash of style attributes to be applied to the element.
 *   - As a function, it represents a function to be called that will cause the transition to occur.
 * @return {Promise}  A promise that is resolved when the transition finishes.
 */
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {

  var $transition = function(element, trigger, options) {
    options = options || {};
    var deferred = $q.defer();
    var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];

    var transitionEndHandler = function(event) {
      $rootScope.$apply(function() {
        element.unbind(endEventName, transitionEndHandler);
        deferred.resolve(element);
      });
    };

    if (endEventName) {
      element.bind(endEventName, transitionEndHandler);
    }

    // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
    $timeout(function() {
      if ( angular.isString(trigger) ) {
        element.addClass(trigger);
      } else if ( angular.isFunction(trigger) ) {
        trigger(element);
      } else if ( angular.isObject(trigger) ) {
        element.css(trigger);
      }
      //If browser does not support transitions, instantly resolve
      if ( !endEventName ) {
        deferred.resolve(element);
      }
    });

    // Add our custom cancel function to the promise that is returned
    // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
    // i.e. it will therefore never raise a transitionEnd event for that transition
    deferred.promise.cancel = function() {
      if ( endEventName ) {
        element.unbind(endEventName, transitionEndHandler);
      }
      deferred.reject('Transition cancelled');
    };

    return deferred.promise;
  };

  // Work out the name of the transitionEnd event
  var transElement = document.createElement('trans');
  var transitionEndEventNames = {
    'WebkitTransition': 'webkitTransitionEnd',
    'MozTransition': 'transitionend',
    'OTransition': 'oTransitionEnd',
    'transition': 'transitionend'
  };
  var animationEndEventNames = {
    'WebkitTransition': 'webkitAnimationEnd',
    'MozTransition': 'animationend',
    'OTransition': 'oAnimationEnd',
    'transition': 'animationend'
  };
  function findEndEventName(endEventNames) {
    for (var name in endEventNames){
      if (transElement.style[name] !== undefined) {
        return endEventNames[name];
      }
    }
  }
  $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
  $transition.animationEndEventName = findEndEventName(animationEndEventNames);
  return $transition;
}]);

// The `$dialogProvider` can be used to configure global defaults for your
// `$dialog` service.
var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']);

dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){
  $scope.title = model.title;
  $scope.message = model.message;
  $scope.buttons = model.buttons;
  $scope.close = function(res){
    dialog.close(res);
  };
}]);

dialogModule.provider("$dialog", function(){

  // The default options for all dialogs.
  var defaults = {
    backdrop: true,
    dialogClass: 'modal',
    backdropClass: 'modal-backdrop',
    transitionClass: 'fade',
    triggerClass: 'in',
    resolve:{},
    backdropFade: false,
    dialogFade:false,
    keyboard: true, // close with esc key
    backdropClick: true // only in conjunction with backdrop=true
    /* other options: template, templateUrl, controller */
	};

	var globalOptions = {};

  var activeBackdrops = {value : 0};

  // The `options({})` allows global configuration of all dialogs in the application.
  //
  //      var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
  //        // don't close dialog when backdrop is clicked by default
  //        $dialogProvider.options({backdropClick: false});
  //      });
	this.options = function(value){
		globalOptions = value;
	};

  // Returns the actual `$dialog` service that is injected in controllers
	this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
  function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {

		var body = $document.find('body');

		function createElement(clazz) {
			var el = angular.element("<div>");
			el.addClass(clazz);
			return el;
		}

    // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
    // containing at lest template or templateUrl and controller:
    //
    //     var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
    //
    // Dialogs can also be created using templateUrl and controller as distinct arguments:
    //
    //     var d = new Dialog('path/to/dialog.html', MyDialogController);
		function Dialog(opts) {

      var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
      this._open = false;

      this.backdropEl = createElement(options.backdropClass);
      if(options.backdropFade){
        this.backdropEl.addClass(options.transitionClass);
        this.backdropEl.removeClass(options.triggerClass);
      }

      this.modalEl = createElement(options.dialogClass);
      if(options.dialogFade){
        this.modalEl.addClass(options.transitionClass);
        this.modalEl.removeClass(options.triggerClass);
      }

      this.handledEscapeKey = function(e) {
        if (e.which === 27) {
          self.close();
          e.preventDefault();
          self.$scope.$apply();
        }
      };

      this.handleBackDropClick = function(e) {
        self.close();
        e.preventDefault();
        self.$scope.$apply();
      };
    }

    // The `isOpen()` method returns wether the dialog is currently visible.
    Dialog.prototype.isOpen = function(){
      return this._open;
    };

    // The `open(templateUrl, controller)` method opens the dialog.
    // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired.
    Dialog.prototype.open = function(templateUrl, controller){
      var self = this, options = this.options;

      if(templateUrl){
        options.templateUrl = templateUrl;
      }
      if(controller){
        options.controller = controller;
      }

      if(!(options.template || options.templateUrl)) {
        throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
      }

      this._loadResolves().then(function(locals) {
        var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();

        self.modalEl.html(locals.$template);

        if (self.options.controller) {
          var ctrl = $controller(self.options.controller, locals);
          self.modalEl.children().data('ngControllerController', ctrl);
        }

        $compile(self.modalEl)($scope);
        self._addElementsToDom();

        // trigger tranisitions
        setTimeout(function(){
          if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
          if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
        });

        self._bindEvents();
      });

      this.deferred = $q.defer();
      return this.deferred.promise;
    };

    // closes the dialog and resolves the promise returned by the `open` method with the specified result.
    Dialog.prototype.close = function(result){
      var self = this;
      var fadingElements = this._getFadingElements();

      if(fadingElements.length > 0){
        for (var i = fadingElements.length - 1; i >= 0; i--) {
          $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
        }
        return;
      }

      this._onCloseComplete(result);

      function removeTriggerClass(el){
        el.removeClass(self.options.triggerClass);
      }

      function onCloseComplete(){
        if(self._open){
          self._onCloseComplete(result);
        }
      }
    };

    Dialog.prototype._getFadingElements = function(){
      var elements = [];
      if(this.options.dialogFade){
        elements.push(this.modalEl);
      }
      if(this.options.backdropFade){
        elements.push(this.backdropEl);
      }

      return elements;
    };

    Dialog.prototype._bindEvents = function() {
      if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); }
      if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); }
    };

    Dialog.prototype._unbindEvents = function() {
      if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); }
      if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); }
    };

    Dialog.prototype._onCloseComplete = function(result) {
      this._removeElementsFromDom();
      this._unbindEvents();

      this.deferred.resolve(result);
    };

    Dialog.prototype._addElementsToDom = function(){
      body.append(this.modalEl);

      if(this.options.backdrop) { 
        if (activeBackdrops.value === 0) {
          body.append(this.backdropEl); 
        }
        activeBackdrops.value++;
      }

      this._open = true;
    };

    Dialog.prototype._removeElementsFromDom = function(){
      this.modalEl.remove();

      if(this.options.backdrop) { 
        activeBackdrops.value--;
        if (activeBackdrops.value === 0) {
          this.backdropEl.remove(); 
        }
      }
      this._open = false;
    };

    // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog.
    Dialog.prototype._loadResolves = function(){
      var values = [], keys = [], templatePromise, self = this;

      if (this.options.template) {
        templatePromise = $q.when(this.options.template);
      } else if (this.options.templateUrl) {
        templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache})
        .then(function(response) { return response.data; });
      }

      angular.forEach(this.options.resolve || [], function(value, key) {
        keys.push(key);
        values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
      });

      keys.push('$template');
      values.push(templatePromise);

      return $q.all(values).then(function(values) {
        var locals = {};
        angular.forEach(values, function(value, index) {
          locals[keys[index]] = value;
        });
        locals.dialog = self;
        return locals;
      });
    };

    // The actual `$dialog` service that is injected in controllers.
    return {
      // Creates a new `Dialog` with the specified options.
      dialog: function(opts){
        return new Dialog(opts);
      },
      // creates a new `Dialog` tied to the default message box template and controller.
      //
      // Arguments `title` and `message` are rendered in the modal header and body sections respectively.
      // The `buttons` array holds an object with the following members for each button to include in the
      // modal footer section:
      //
      // * `result`: the result to pass to the `close` method of the dialog when the button is clicked
      // * `label`: the label of the button
      // * `cssClass`: additional css class(es) to apply to the button for styling
      messageBox: function(title, message, buttons){
        return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
          {model: function() {
            return {
              title: title,
              message: message,
              buttons: buttons
            };
          }
        }});
      }
    };
  }];
});
angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.dialog"]);
angular.module("ui.bootstrap.tpls", ["template/dialog/message.html"]);
angular.module('ui.bootstrap.transition', [])

/**
 * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
 * @param  {DOMElement} element  The DOMElement that will be animated.
 * @param  {string|object|function} trigger  The thing that will cause the transition to start:
 *   - As a string, it represents the css class to be added to the element.
 *   - As an object, it represents a hash of style attributes to be applied to the element.
 *   - As a function, it represents a function to be called that will cause the transition to occur.
 * @return {Promise}  A promise that is resolved when the transition finishes.
 */
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {

  var $transition = function(element, trigger, options) {
    options = options || {};
    var deferred = $q.defer();
    var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];

    var transitionEndHandler = function(event) {
      $rootScope.$apply(function() {
        element.unbind(endEventName, transitionEndHandler);
        deferred.resolve(element);
      });
    };

    if (endEventName) {
      element.bind(endEventName, transitionEndHandler);
    }

    // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
    $timeout(function() {
      if ( angular.isString(trigger) ) {
        element.addClass(trigger);
      } else if ( angular.isFunction(trigger) ) {
        trigger(element);
      } else if ( angular.isObject(trigger) ) {
        element.css(trigger);
      }
      //If browser does not support transitions, instantly resolve
      if ( !endEventName ) {
        deferred.resolve(element);
      }
    });

    // Add our custom cancel function to the promise that is returned
    // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
    // i.e. it will therefore never raise a transitionEnd event for that transition
    deferred.promise.cancel = function() {
      if ( endEventName ) {
        element.unbind(endEventName, transitionEndHandler);
      }
      deferred.reject('Transition cancelled');
    };

    return deferred.promise;
  };

  // Work out the name of the transitionEnd event
  var transElement = document.createElement('trans');
  var transitionEndEventNames = {
    'WebkitTransition': 'webkitTransitionEnd',
    'MozTransition': 'transitionend',
    'OTransition': 'oTransitionEnd',
    'transition': 'transitionend'
  };
  var animationEndEventNames = {
    'WebkitTransition': 'webkitAnimationEnd',
    'MozTransition': 'animationend',
    'OTransition': 'oAnimationEnd',
    'transition': 'animationend'
  };
  function findEndEventName(endEventNames) {
    for (var name in endEventNames){
      if (transElement.style[name] !== undefined) {
        return endEventNames[name];
      }
    }
  }
  $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
  $transition.animationEndEventName = findEndEventName(animationEndEventNames);
  return $transition;
}]);

// The `$dialogProvider` can be used to configure global defaults for your
// `$dialog` service.
var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']);

dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){
  $scope.title = model.title;
  $scope.message = model.message;
  $scope.buttons = model.buttons;
  $scope.close = function(res){
    dialog.close(res);
  };
}]);

dialogModule.provider("$dialog", function(){

  // The default options for all dialogs.
  var defaults = {
    backdrop: true,
    dialogClass: 'modal',
    backdropClass: 'modal-backdrop',
    transitionClass: 'fade',
    triggerClass: 'in',
    resolve:{},
    backdropFade: false,
    dialogFade:false,
    keyboard: true, // close with esc key
    backdropClick: true // only in conjunction with backdrop=true
    /* other options: template, templateUrl, controller */
	};

	var globalOptions = {};

  var activeBackdrops = {value : 0};

  // The `options({})` allows global configuration of all dialogs in the application.
  //
  //      var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
  //        // don't close dialog when backdrop is clicked by default
  //        $dialogProvider.options({backdropClick: false});
  //      });
	this.options = function(value){
		globalOptions = value;
	};

  // Returns the actual `$dialog` service that is injected in controllers
	this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
  function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {

		var body = $document.find('body');

		function createElement(clazz) {
			var el = angular.element("<div>");
			el.addClass(clazz);
			return el;
		}

    // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
    // containing at lest template or templateUrl and controller:
    //
    //     var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
    //
    // Dialogs can also be created using templateUrl and controller as distinct arguments:
    //
    //     var d = new Dialog('path/to/dialog.html', MyDialogController);
		function Dialog(opts) {

      var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
      this._open = false;

      this.backdropEl = createElement(options.backdropClass);
      if(options.backdropFade){
        this.backdropEl.addClass(options.transitionClass);
        this.backdropEl.removeClass(options.triggerClass);
      }

      this.modalEl = createElement(options.dialogClass);
      if(options.dialogFade){
        this.modalEl.addClass(options.transitionClass);
        this.modalEl.removeClass(options.triggerClass);
      }

      this.handledEscapeKey = function(e) {
        if (e.which === 27) {
          self.close();
          e.preventDefault();
          self.$scope.$apply();
        }
      };

      this.handleBackDropClick = function(e) {
        self.close();
        e.preventDefault();
        self.$scope.$apply();
      };
    }

    // The `isOpen()` method returns wether the dialog is currently visible.
    Dialog.prototype.isOpen = function(){
      return this._open;
    };

    // The `open(templateUrl, controller)` method opens the dialog.
    // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired.
    Dialog.prototype.open = function(templateUrl, controller){
      var self = this, options = this.options;

      if(templateUrl){
        options.templateUrl = templateUrl;
      }
      if(controller){
        options.controller = controller;
      }

      if(!(options.template || options.templateUrl)) {
        throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
      }

      this._loadResolves().then(function(locals) {
        var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();

        self.modalEl.html(locals.$template);

        if (self.options.controller) {
          var ctrl = $controller(self.options.controller, locals);
          self.modalEl.children().data('ngControllerController', ctrl);
        }

        $compile(self.modalEl)($scope);
        self._addElementsToDom();

        // trigger tranisitions
        setTimeout(function(){
          if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
          if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
        });

        self._bindEvents();
      });

      this.deferred = $q.defer();
      return this.deferred.promise;
    };

    // closes the dialog and resolves the promise returned by the `open` method with the specified result.
    Dialog.prototype.close = function(result){
      var self = this;
      var fadingElements = this._getFadingElements();

      if(fadingElements.length > 0){
        for (var i = fadingElements.length - 1; i >= 0; i--) {
          $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
        }
        return;
      }

      this._onCloseComplete(result);

      function removeTriggerClass(el){
        el.removeClass(self.options.triggerClass);
      }

      function onCloseComplete(){
        if(self._open){
          self._onCloseComplete(result);
        }
      }
    };

    Dialog.prototype._getFadingElements = function(){
      var elements = [];
      if(this.options.dialogFade){
        elements.push(this.modalEl);
      }
      if(this.options.backdropFade){
        elements.push(this.backdropEl);
      }

      return elements;
    };

    Dialog.prototype._bindEvents = function() {
      if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); }
      if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); }
    };

    Dialog.prototype._unbindEvents = function() {
      if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); }
      if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); }
    };

    Dialog.prototype._onCloseComplete = function(result) {
      this._removeElementsFromDom();
      this._unbindEvents();

      this.deferred.resolve(result);
    };

    Dialog.prototype._addElementsToDom = function(){
      body.append(this.modalEl);

      if(this.options.backdrop) { 
        if (activeBackdrops.value === 0) {
          body.append(this.backdropEl); 
        }
        activeBackdrops.value++;
      }

      this._open = true;
    };

    Dialog.prototype._removeElementsFromDom = function(){
      this.modalEl.remove();

      if(this.options.backdrop) { 
        activeBackdrops.value--;
        if (activeBackdrops.value === 0) {
          this.backdropEl.remove(); 
        }
      }
      this._open = false;
    };

    // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog.
    Dialog.prototype._loadResolves = function(){
      var values = [], keys = [], templatePromise, self = this;

      if (this.options.template) {
        templatePromise = $q.when(this.options.template);
      } else if (this.options.templateUrl) {
        templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache})
        .then(function(response) { return response.data; });
      }

      angular.forEach(this.options.resolve || [], function(value, key) {
        keys.push(key);
        values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
      });

      keys.push('$template');
      values.push(templatePromise);

      return $q.all(values).then(function(values) {
        var locals = {};
        angular.forEach(values, function(value, index) {
          locals[keys[index]] = value;
        });
        locals.dialog = self;
        return locals;
      });
    };

    // The actual `$dialog` service that is injected in controllers.
    return {
      // Creates a new `Dialog` with the specified options.
      dialog: function(opts){
        return new Dialog(opts);
      },
      // creates a new `Dialog` tied to the default message box template and controller.
      //
      // Arguments `title` and `message` are rendered in the modal header and body sections respectively.
      // The `buttons` array holds an object with the following members for each button to include in the
      // modal footer section:
      //
      // * `result`: the result to pass to the `close` method of the dialog when the button is clicked
      // * `label`: the label of the button
      // * `cssClass`: additional css class(es) to apply to the button for styling
      messageBox: function(title, message, buttons){
        return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
          {model: function() {
            return {
              title: title,
              message: message,
              buttons: buttons
            };
          }
        }});
      }
    };
  }];
});

angular.module("template/dialog/message.html", []).run(["$templateCache", function($templateCache) {
  $templateCache.put("template/dialog/message.html",
    "<div class=\"modal-header\">\n" +
    "	<h3>{{ title }}</h3>\n" +
    "</div>\n" +
    "<div class=\"modal-body\">\n" +
    "	<p>{{ message }}</p>\n" +
    "</div>\n" +
    "<div class=\"modal-footer\">\n" +
    "	<button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"btn\" ng-class=\"btn.cssClass\">{{ btn.label }}</button>\n" +
    "</div>\n" +
    "");
}]);

/* Server modul */
(function (angular) {
    "use strict";
    angular.module('smartTable.server', [])
    .factory('smartTableServerSource', ['$q', '$http', function (q, http) {

        function sendData(url, obj) {
            var deferred = q.defer();
            http.post(url, obj ? angular.toJson(obj) : null)
                    .success(function (data) { return deferred.resolve(data); })
                    .error(function (data) { return deferred.reject(data); });
            return deferred.promise;
        }

        return {
            getList: function (url) {
                return sendData(url);
            },
            addObj: function (url, obj) {
                return sendData(url, obj);
            },
            updateObj: function (url, obj) {
                return sendData(url, obj);
            },
            deleteObj: function (url, obj) {
                return sendData(url, obj);
            },
            getOptions: function (url) {
                return sendData(url);
            }
        };
    } ]);
})(angular);

/* Column module */
(function (global, angular) {
    "use strict";
    var smartTableColumnModule = angular.module('smartTable.column', ['smartTable.templateUrlList', 'smartTable.server'])
    .constant('DefaultColumnConfiguration', {
        isSortable: true,
        isEditable: false,
        type: 'text',


        //it is useless to have that empty strings, but it reminds what is available
        headerTemplateUrl: '',
        headerTemplate: '',
        map: '',
        label: '',
        sortPredicate: '',
        formatFunction: '',
        formatParameter: '',
        filterPredicate: '',
        cellTemplateUrl: '',
        cellTemplate: '',
        headerClass: '',
        cellClass: '',
        optionsUrl: '',
        options: [],
        editType: 'text', // text, select, password, email, number, url, checkbox, radio, textarea, custom (default is textbox input)
        validationAttrs: '',
        validationMsgs: '',
        defaultTemplate: '', // NOTE: cooperate with some editTypes (select or custom)
        noList: false, // TODO: decrease colspan, if noList = true
        noEdit: false,
        noAdd: false
    });

    function ColumnProvider(DefaultColumnConfiguration, templateUrlList) {

        function Column(config) {
            if (!(this instanceof Column)) {
                return new Column(config);
            }
            var self = this;
            if (config.optionsUrl)
                this.getOptions(config.optionsUrl).then(function (response) {
                    if (!config.options)
                        config.options = [];
                    config.options.length = 0;
                    config.options.push.apply(config.options, response.data);
                    setColumn(self, config);
                });
            setColumn(self, config);
        }

        function setColumn(column, config) {
            angular.extend(column, config); // load from server
        }

        this.setDefaultOption = function (option) {
            angular.extend(Column.prototype, option);
        };

        DefaultColumnConfiguration.headerTemplateUrl = templateUrlList.defaultHeader;
        this.setDefaultOption(DefaultColumnConfiguration);

        this.$get = ['smartTableServerSource', function (server) {
            this.setDefaultOption(server);
            return Column;
        } ];
    }

    ColumnProvider.$inject = ['DefaultColumnConfiguration', 'templateUrlList'];
    smartTableColumnModule.provider('Column', ColumnProvider);

    //make it global so it can be tested
    global.ColumnProvider = ColumnProvider;
})(window, angular);

/* Directives */
(function (angular) {
    "use strict";
    angular.module('smartTable.directives', ['smartTable.templateUrlList', 'smartTable.templates', 'smartTable.server', 'ui.bootstrap'])
        .directive('smartTable', ['templateUrlList', 'DefaultTableConfiguration', 'smartTableServerSource', function (templateList, defaultConfig, serverSource) {
            return {
                restrict: 'EA',
                scope: {
                    columnCollection: '=columns',
                    dataCollection: '=rows',
                    config: '='
                },
                replace: 'true',
                templateUrl: templateList.smartTable,
                controller: 'TableCtrl',
                link: function (scope, element, attr, ctrl) {

                    var templateObject;

                    scope.$watch('config', function (config) {
                        var newConfig = angular.extend({}, defaultConfig, config),
                            length = scope.columns !== undefined ? scope.columns.length : 0;

                        ctrl.setGlobalConfig(newConfig);

                        // resolve actions
                        if (newConfig.actions.list.url) {
                            serverSource.getList(newConfig.actions.list.url)
                                .then(function (response) {
                                    scope.dataCollection.push.apply(scope.dataCollection, response.data);
                                });
                        }
                        if (newConfig.actions.edit.url) {
                            scope.columnCollection.push({ label: '', cellTemplate: '<edit-dialog type="edit"></edit-dialog>' });
                        }
                        if (newConfig.actions.remove.url) {
                            scope.columnCollection.push({ label: '', cellTemplate: '<confirm-dialog></confirm-dialog>' });
                        }

                        //remove the checkbox column if needed
                        if (newConfig.selectionMode !== 'multiple' || newConfig.displaySelectionCheckbox !== true) {
                            for (var i = length - 1; i >= 0; i--) {
                                if (scope.columns[i].isSelectionColumn === true) {
                                    ctrl.removeColumn(i);
                                }
                            }
                        } else {
                            //add selection box column if required                            
                            ctrl.insertColumn({ cellTemplateUrl: templateList.selectionCheckbox, headerTemplateUrl: templateList.selectAllCheckbox, isSelectionColumn: true }, 0);
                        }
                    }, true);

                    //insert columns from column config
                    //TODO add a way to clean all columns
                    scope.$watch('columnCollection', function (oldValue, newValue) {
                        if (scope.columnCollection) {
                            if (newValue.length !== scope.columns.length) {
                                for (var i = 0, l = scope.columnCollection.length; i < l; i++) {
                                    ctrl.insertColumn(scope.columnCollection[i]);
                                }
                            }
                        } else {
                            //or guess data Structure
                            if (scope.dataCollection && scope.dataCollection.length > 0) {
                                templateObject = scope.dataCollection[0];
                                angular.forEach(templateObject, function (value, key) {
                                    if (key[0] != '$') {
                                        ctrl.insertColumn({ label: key, map: key });
                                    }
                                });
                            }
                        }
                    }, true);

                    //if item are added or removed into the data model from outside the grid
                    scope.$watch('dataCollection.length', function (oldValue, newValue) {
                        if (oldValue !== newValue) {
                            ctrl.sortBy(); //it will trigger the refresh... some hack ?
                        }
                    });

                }
            };
        } ])
        .directive('confirmDialog', ['$dialog', 'smartTableServerSource', function ($dialog, serverSource) {
            return {
                restrict: 'E',
                //include smart table controller to use its API if needed
                require: '^smartTable',
                template: '<button class="btn btn-link {{buttonClass}}" ng-click="delUser()"><i class="icon" ng-class="iconClass" ></i>{{desc}}</button>',
                replace: true,
                link: function (scope, element, attrs, ctrl) {
                    //can use scope.dataRow, scope.column, scope.formatedValue, and ctrl API

                    scope.iconClass = scope.actions.remove.iconClass;
                    scope.buttonClass = scope.actions.remove.buttonClass;
                    scope.desc = scope.actions.remove.desc;

                    var handleResult = function (result) {
                        if (result === 'ok') {
                            serverSource.deleteObj(scope.actions.remove.url, scope.dataRow)
                                .then(function (response) {
                                    ctrl.deleteWholeDataRow(scope.dataRow);
                                    ctrl.sortBy();
                                });
                        }
                    };

                    scope.delUser = function () {
                        var title = scope.actions.remove.title;
                        var msg = scope.actions.remove.msg;
                        var btns = [{ result: 'cancel', label: 'Cancel' }, { result: 'ok', label: 'OK', cssClass: 'btn-primary'}];

                        $dialog.messageBox(title, msg, btns)
                            .open()
                            .then(handleResult);
                    };
                }
            };
        } ])
        .directive('editDialog', ['$dialog', 'templateUrlList', 'smartTableServerSource', function ($dialog, templateList, serverSource) {
            return {
                restrict: 'E',
                //include smart table controller to use its API if needed
                require: '^smartTable',
                template: '<button class="btn btn-link {{buttonClass}}" ng-click="pop()"><i class="icon" ng-class="iconClass" ></i>{{desc}}</button>',
                replace: true,
                link: function (scope, element, attrs, ctrl) {
                    //can use scope.dataRow, scope.column, scope.formatedValue, and ctrl API
                    var type = attrs.type,
                        title = '',
                        url = '',
                        obj = {},
                        columns = scope.columns;

                    switch (type) {
                        case 'add':
                            scope.iconClass = scope.config.actions.add.iconClass;
                            scope.buttonClass = scope.config.actions.add.buttonClass;
                            scope.desc = scope.config.actions.add.desc;
                            title = scope.config.actions.add.title;
                            url = scope.config.actions.add.url;
                            break;
                        case 'edit':
                            scope.iconClass = scope.actions.edit.iconClass;
                            scope.buttonClass = scope.actions.edit.buttonClass;
                            scope.desc = scope.actions.edit.desc;
                            title = scope.actions.edit.title;
                            url = scope.actions.edit.url;
                            obj = angular.copy(scope.dataRow);
                            break;
                    }

                    var handleAction = function (obj) {
                        if (obj) {
                            switch (type) {
                                case 'add':
                                    ctrl.addWholeDataRow(obj);
                                    break;
                                case 'edit':
                                    angular.extend(scope.dataRow, obj);
                                    ctrl.updateWholeDataRow(scope.dataRow);
                                    break;
                            }
                            ctrl.sortBy();
                        }
                    };

                    scope.pop = function () {
                        var d = $dialog.dialog(
                        {
                            dialogFade: false,
                            resolve: {
                                dataRow: function () { return obj; },
                                columns: function () { return columns; },
                                type: function () { return type; },
                                title: function () { return title; },
                                url: function () { return url; }
                            }
                        });
                        d.open(templateList.dialogEdit, editDialogCtrl).then(handleAction);
                        return false;
                    };

                    editDialogCtrl.$inject = ['$scope', 'dialog', 'dataRow', 'columns', 'type', 'title', 'url'];
                    function editDialogCtrl($scope, dialog, dataRow, columns, type, title, url) {
                        $scope.dataRow = dataRow;
                        $scope.columns = columns;
                        $scope.title = title;
                        $scope.type = type;
                        $scope.close = function () {
                            dialog.close();
                        };
                        $scope.save = function () {
                            switch (type) {
                                case 'add':
                                    serverSource.addObj(url, dataRow)
                                        .then(function (response) {
                                            dialog.close(response.data);
                                        });
                                    break;
                                case 'edit':
                                    serverSource.updateObj(url, dataRow)
                                        .then(function (response) {
                                            angular.extend(dataRow, response.data);
                                            dialog.close(dataRow);
                                        });
                                    break;
                            }
                        };
                    };
                }
            };
        } ])
        .directive('smartTableEditDataCell', ['$compile', function (compile) {
            return {
                restrict: 'C',
                link: function (scope, element) {

                    function setUpType() {
                        var t = '<td>{{column.label}}</td>\n';
                        switch (scope.column.editType) {
                            case 'select':
                                t += '<td><select name="' + scope.column.map + '" ' + scope.column.validationAttrs + ' ng-model="dataRow[column.map]" ng-options="c.Value as c.Text for c in column.options">' 
                                t += scope.column.defaultTemplate + '</select></td>\n' ;
                                break;
                            case 'text':
                            case 'password':
                            case 'url':
                            case 'number':
                            case 'email':
                                t += '<td><input type="' + scope.column.editType + '" name="' + scope.column.map + '" ' + scope.column.validationAttrs + ' ng-model="dataRow[column.map]"></td>\n';
                                break;
                            case 'checkbox':
                                t += '<td><label class="checkbox"><input type="checkbox" name="' + scope.column.map + '" ' + scope.column.validationAttrs + ' ng-model="dataRow[column.map]"></label></td>\n';
                                break;
                            case 'radio':
                                t += '<td><label class="radio" ng-repeat="option in column.options">\n' +
                                     '<input type="radio" ng-model="dataRow[column.map]" name="optionsRadios" value="{{option.Value}}" />{{option.Text}}' +                               
                                     '</label></td>\n' +
                                     '<td></td>\n';
                                break;
                            case 'textarea':
                                t += '<td><textarea type="text" name="' + scope.column.map + '" ' + scope.column.validationAttrs + ' ng-model="dataRow[column.map]"></td>\n';
                                break;
                            case 'custom':
                                t = scope.column.defaultTemplate;
                                break;
                            default:
                                t += '<td><input name="' + scope.column.map + '" ' + scope.column.validationAttrs + ' type="text" ng-model="dataRow[column.map]"></td>\n';                                    
                        }
                        if (scope.column.editType !== 'radio')
                            t +='<td>' + scope.column.validationMsgs; 
                            switch (scope.column.editType) {
                                case 'url':
                                    t += '<span class="error" ng-show="smartTableValidForm.' + scope.column.map + '.$error.url">Not valid url!</span>';
                                    break;
                                case 'number':
                                    t += '<span class="error" ng-show="smartTableValidForm.' + scope.column.map + '.$error.number">Not valid number!</span>';
                                    break;
                                case 'email':
                                    t += '<span class="error" ng-show="smartTableValidForm.' + scope.column.map + '.$error.email">Not valid email!</span>';
                                    break;
                                default:
                            }
                            t += '</td>\n';

                        element.html(t);
                        compile(element.contents())(scope);
                    }

                    if (scope.type === 'edit' && scope.column.noEdit)
                        return;

                    if (scope.type === 'add' && scope.column.noAdd)
                        return;

                    if (scope.column.map)
                        setUpType();
                }
            };
        } ])
    //just to be able to select the row
        .directive('smartTableDataRow', function () {

            return {
                require: '^smartTable',
                restrict: 'C',
                link: function (scope, element, attr, ctrl) {

                    element.bind('click', function () {
                        scope.$apply(function () {
                            ctrl.toggleSelection(scope.dataRow);
                        })
                    });
                }
            };
        })
    //header cell with sorting functionality or put a checkbox if this column is a selection column
        .directive('smartTableHeaderCell', ['$http', '$templateCache', '$compile', function (http, templateCache, compile) {
            return {
                restrict: 'C',
                require: '^smartTable',
                link: function (scope, element, attr, ctrl) {

                    if (scope.column.noList)
                        return;

                    element.bind('click', function () {
                        scope.$apply(function () {
                            ctrl.sortBy(scope.column);
                        });
                    })

                    var column = scope.column,
                        childScope;

                    function defaultContent() {
                        // TODO
                    }

                    scope.$watch('column.headerTemplateUrl', function (value) {
                        if (value) {
                            //we have to load the template (and cache it) : a kind of ngInclude
                            http.get(value, { cache: templateCache }).success(function (response) {
                                //create a scope
                                childScope = scope.$new();
                                //compile the element with its new content and new scope
                                element.html(response);
                                compile(element.contents())(childScope);
                            }).error(defaultContent);

                        } else {
                            defaultContent();
                        }
                    });

                    scope.$watch('column.headerTemplate', function (value) {
                        if (value) {
                            //create a scope
                            childScope = scope.$new();
                            //compile the element with its new content and new scope
                            element.html(value);
                            compile(element.contents())(childScope);
                        }
                    });
                }
            };
        } ])
        .directive('smartTableSelectAll', function () {
            return {
                restrict: 'C',
                require: '^smartTable',
                link: function (scope, element, attr, ctrl) {
                    element.bind('click', function (event) {
                        ctrl.toggleSelectionAll(element[0].checked === true);
                    })
                }
            };
        })
    //credit to Valentyn shybanov : http://stackoverflow.com/questions/14544741/angularjs-directive-to-stoppropagation
        .directive('stopEvent', function () {
            return {
                restrict: 'A',
                link: function (scope, element, attr) {
                    element.bind(attr.stopEvent, function (e) {
                        e.stopPropagation();
                    });
                }
            }
        })
    //the global filter
        .directive('smartTableGlobalSearch', ['templateUrlList', function (templateList) {
            return {
                restrict: 'C',
                require: '^smartTable',
                scope: {
                    columnSpan: '@',
                    globalSearchClass: '@'
                },
                templateUrl: templateList.smartTableGlobalSearch,
                replace: false,
                link: function (scope, element, attr, ctrl) {

                    scope.searchValue = '';

                    scope.$watch('searchValue', function (value) {
                        //todo perf improvement only filter on blur ?
                        ctrl.search(value);
                    });
                }
            }
        } ])
    //a customisable cell (see templateUrl) and editable
    //TODO check with the ng-include strategy
        .directive('smartTableDataCell', ['$filter', '$http', '$templateCache', '$compile', '$parse', function (filter, http, templateCache, compile, parse) {
            return {
                restrict: 'C',
                link: function (scope, element) {

                    if (scope.column.noList)
                        return;

                    var 
                        column = scope.column,
                        row = scope.dataRow,
                        format = filter('format'),
                        getter = parse(column.map),
                        childScope;

                    //can be useful for child directives
                    if (column.optionsUrl === "")
                        scope.formatedValue = format(getter(row), column.formatFunction, column.formatParameter);
                    else {
                        scope.$watch('column.options', function (value) {
                            if (value.length)
                                for (var i = 0; i < value.length; i++)
                                    if (value[i].Value === getter(row)) {
                                        element.html(value[i].Text);
                                        break;
                                    }
                        });
                    }

                    function defaultContent() {
                        //clear content
                        if (column.isEditable) {
                            element.html('<div editable-cell="" row="dataRow" column="column" type="column.type"></div>');
                            compile(element.contents())(scope);
                        } else {
                            element.text(scope.formatedValue);
                        }
                    }

                    scope.$watch('column.cellTemplateUrl', function (value) {

                        if (value) {
                            //we have to load the template (and cache it) : a kind of ngInclude
                            http.get(value, { cache: templateCache }).success(function (response) {

                                //create a scope
                                childScope = scope.$new();
                                //compile the element with its new content and new scope
                                element.html(response);
                                compile(element.contents())(childScope);
                            }).error(defaultContent);

                        } else {
                            defaultContent();
                        }
                    });

                    scope.$watch('column.cellTemplate', function (value) {
                        if (value) {
                            //create a scope
                            childScope = scope.$new();
                            //compile the element with its new content and new scope
                            element.html(value);
                            compile(element.contents())(childScope);
                        }
                    });
                }
            };
        } ])
    //directive that allows type to be bound in input
        .directive('inputType', function () {
            return {
                restrict: 'A',
                priority: 1,
                link: function (scope, ielement, iattr) {
                    //force the type to be set before inputDirective is called
                    var type = scope.$eval(iattr.type);
                    iattr.$set('type', type);
                }
            };
        })
    //an editable content in the context of a cell (see row, column)
        .directive('editableCell', ['templateUrlList', '$parse', function (templateList, parse) {
            return {
                restrict: 'EA',
                require: '^smartTable',
                templateUrl: templateList.editableCell,
                scope: {
                    row: '=',
                    column: '=',
                    type: '='
                },
                replace: true,
                link: function (scope, element, attrs, ctrl) {
                    var form = angular.element(element.children()[1]),
                        input = angular.element(form.children()[0]),
                        getter = parse(scope.column.map);

                    //init values
                    scope.isEditMode = false;
                    scope.value = getter(scope.row);


                    scope.submit = function () {
                        //update model if valid
                        if (scope.myForm.$valid === true) {
                            ctrl.updateDataRow(scope.row, scope.column.map, scope.value);
                            ctrl.sortBy(); //it will trigger the refresh...  (ie it will sort, filter, etc with the new value)
                        }
                        scope.toggleEditMode();
                    };

                    scope.toggleEditMode = function () {
                        scope.value = getter(scope.row);
                        scope.isEditMode = scope.isEditMode !== true;
                    };

                    scope.$watch('isEditMode', function (newValue, oldValue) {
                        if (newValue) {
                            input[0].select();
                            input[0].focus();
                        }
                    });

                    input.bind('blur', function () {
                        scope.$apply(function () {
                            scope.submit();
                        });
                    });
                }
            };
        } ]);
})(angular);

/* Filters */
(function (angular) {
    "use strict";
    angular.module('smartTable.filters', []).
        constant('DefaultFilters', ['currency', 'date', 'json', 'lowercase', 'number', 'uppercase']).
        filter('format', ['$filter', 'DefaultFilters', function (filter, defaultfilters) {
            return function (value, formatFunction, filterParameter) {

                var returnFunction;

                if (formatFunction && angular.isFunction(formatFunction)) {
                    returnFunction = formatFunction;
                } else {
                    returnFunction = defaultfilters.indexOf(formatFunction) !== -1 ? filter(formatFunction) : function (value) {
                        return value;
                    };
                }
                return returnFunction(value, filterParameter);
            };
        } ]);
})(angular);


/*table module */

(function (angular) {
    "use strict";
    angular.module('smartTable.table', ['smartTable.column', 'smartTable.utilities', 'smartTable.directives', 'smartTable.filters', 'ui.bootstrap.pagination.smartTable'])
        .constant('DefaultTableConfiguration', {
            selectionMode: 'none',
            isGlobalSearchActivated: false,
            globalSearchClass: '',
            displaySelectionCheckbox: false,
            isPaginationEnabled: true,
            itemsByPage: 10,
            maxSize: 5,
            actions: {
                list: { url: undefined },
                edit: { url: undefined, title: 'Edit Dialog', buttonClass: '', iconClass: 'icon-edit', desc: '' },
                add: { url: undefined, title: 'Add Dialog', buttonClass: '', iconClass: 'icon-plus', desc: '' },
                remove: { url: undefined, title: 'Confirmation Dialog', buttonClass: '', iconClass: 'icon-remove', desc: '', msg: 'Do you really want to delete it?' }
            },
            //just to remind available option
            sortAlgorithm: '',
            filterAlgorithm: ''
        })
        .controller('TableCtrl', ['$scope', 'Column', '$filter', '$parse', 'ArrayUtility', 'DefaultTableConfiguration', function (scope, Column, filter, parse, arrayUtility, defaultConfig) {

            scope.columns = [];
            scope.dataCollection = scope.dataCollection || [];
            scope.displayedCollection = []; //init empty array so that if pagination is enabled, it does not spoil performances
            scope.numberOfPages = calculateNumberOfPages(scope.dataCollection);
            scope.currentPage = 1;
            scope.holder = { isAllSelected: false };

            var predicate = {},
                lastColumnSort;

            function isAllSelected() {
                var i,
                    l = scope.displayedCollection.length;
                for (i = 0; i < l; i++) {
                    if (scope.displayedCollection[i].isSelected !== true) {
                        return false;
                    }
                }
                return true;
            }

            function calculateNumberOfPages(array) {

                if (!angular.isArray(array)) {
                    return 1;
                }
                if (array.length === 0 || scope.itemsByPage < 1) {
                    return 1;
                }
                return Math.ceil(array.length / scope.itemsByPage);
            }

            function sortDataRow(array, column) {
                var sortAlgo = (scope.sortAlgorithm && angular.isFunction(scope.sortAlgorithm)) === true ? scope.sortAlgorithm : filter('orderBy');
                if (column) {
                    return arrayUtility.sort(array, sortAlgo, column.sortPredicate, column.reverse);
                } else {
                    return array;
                }
            }

            function selectDataRow(array, selectionMode, index, select) {

                var dataRow, oldValue;

                if ((!angular.isArray(array)) || (selectionMode !== 'multiple' && selectionMode !== 'single')) {
                    return;
                }

                if (index >= 0 && index < array.length) {
                    dataRow = array[index];
                    if (selectionMode === 'single') {
                        //unselect all the others
                        for (var i = 0, l = array.length; i < l; i++) {
                            oldValue = array[i].isSelected;
                            array[i].isSelected = false;
                            if (oldValue === true) {
                                scope.$emit('selectionChange', { item: array[i] });
                            }
                        }
                    }
                    dataRow.isSelected = select;
                    scope.holder.isAllSelected = isAllSelected();
                    scope.$emit('selectionChange', { item: dataRow });
                }
            }

            /**
            * set the config (config parameters will be available through scope
            * @param dest - destination object
            * @param src - source(s) object. You can specify multiple src objects.
            */
            function deepExtend(dst) {
                // remove dst object from arguments            
                var args = [].splice.call(arguments, 1);
                angular.forEach(args, function (obj) {
                    var deep = false;
                    var keys = [];
                    // go though all sources
                    angular.forEach(obj, function (value, key) {
                        if (angular.isObject(value)) {
                            dst[key] = angular.copy(value);
                            deep = true;
                            keys.push(key);
                        } else
                            dst[key] = value;
                    });
                    // deep copy of objects needed 
                    // TODO: deep copy of other complex objects
                    if (deep)
                        for (var j = 0; j < keys.length; j++) {
                            var sources = []; // create a new source list of objects that should extend source
                            var key = keys[j];
                            sources.push(dst[key]);
                            for (var i = 0; i < args.length; i++)
                                if (angular.isObject(args[i][key]))
                                    sources.push(args[i][key]);
                            if (sources.length > 1)
                                deepExtend.apply(undefined, sources);
                        }
                });
            }

            /**
            * set the config (config parameters will be available through scope
            * @param config
            */
            this.setGlobalConfig = function (config) {
                deepExtend(scope, defaultConfig, config);
                //angular.extend(scope, defaultConfig, config);                                               
            };

            /**
            * change the current page displayed
            * @param page
            */
            this.changePage = function (page) {
                var oldPage = scope.currentPage;
                if (angular.isNumber(page.page)) {
                    scope.currentPage = page.page;
                    scope.displayedCollection = this.pipe(scope.dataCollection);
                    scope.holder.isAllSelected = isAllSelected();
                    scope.$emit('changePage', { oldValue: oldPage, newValue: scope.currentPage });
                }
            };

            /**
            * set column as the column used to sort the data (if it is already the case, it will change the reverse value)
            * @method sortBy
            * @param column
            */
            this.sortBy = function (column) {
                var index = scope.columns.indexOf(column);
                if (index !== -1) {
                    if (column.isSortable === true) {
                        // reset the last column used
                        if (lastColumnSort && lastColumnSort !== column) {
                            lastColumnSort.reverse = 'none';
                        }

                        column.sortPredicate = column.sortPredicate || column.map;
                        column.reverse = column.reverse !== true;
                        lastColumnSort = column;
                    }
                }

                scope.displayedCollection = this.pipe(scope.dataCollection);
            };

            /**
            * set the filter predicate used for searching
            * @param input
            * @param column
            */
            this.search = function (input, column) {

                //update column and global predicate
                if (column && scope.columns.indexOf(column) !== -1) {
                    predicate.$ = '';
                    column.filterPredicate = input;
                } else {
                    for (var j = 0, l = scope.columns.length; j < l; j++) {
                        scope.columns[j].filterPredicate = '';
                    }
                    predicate.$ = input;
                }
                // NOTE: global search also in options ids -> TODO
                for (var j = 0, l = scope.columns.length; j < l; j++) {
                    if(scope.columns[j].optionsUrl !== "") {
                        var column = scope.columns[j];
                        predicate[column.map + '-smartSearch'] = scope.columns[j].filterPredicate;
                        for (var i = 0, l = scope.dataCollection.length; i < l; i++) {
                            var dataRow = scope.dataCollection[i]; 
                            dataRow[column.map + '-smartSearch'] = '';
                            for (var k = 0; k < column.options.length; k++)
                                if (column.options[k].Value == dataRow[column.map]){
                                    dataRow[column.map + '-smartSearch'] = column.options[k].Text;
                                    break;
                                }                                                        
                        }
                    } else
                        predicate[scope.columns[j].map] = scope.columns[j].filterPredicate;
                }
                scope.displayedCollection = this.pipe(scope.dataCollection);

            };

            /**
            * combine sort, search and limitTo operations on an array,
            * @param array
            * @returns Array, an array result of the operations on input array
            */
            this.pipe = function (array) {
                var filterAlgo = (scope.filterAlgorithm && angular.isFunction(scope.filterAlgorithm)) === true ? scope.filterAlgorithm : filter('filter'),
                    output;
                //filter and sort are commutative
                var filteredArray = arrayUtility.filter(array, filterAlgo, predicate);
                output = sortDataRow(filteredArray, lastColumnSort);
                scope.numberOfPages = calculateNumberOfPages(output);
                return scope.isPaginationEnabled ? arrayUtility.fromTo(output, (scope.currentPage - 1) * scope.itemsByPage, scope.itemsByPage) : output;
            };

            /*////////////
            Column API
            ///////////*/


            /**
            * insert a new column in scope.collection at index or push at the end if no index
            * @param columnConfig column configuration used to instantiate the new Column
            * @param index where to insert the column (at the end if not specified)
            */
            this.insertColumn = function (columnConfig, index) {
                var column = new Column(columnConfig);
                arrayUtility.insertAt(scope.columns, index, column);
            };

            /**
            * remove the column at columnIndex from scope.columns
            * @param columnIndex index of the column to be removed
            */
            this.removeColumn = function (columnIndex) {
                arrayUtility.removeAt(scope.columns, columnIndex);
            };

            /**
            * move column located at oldIndex to the newIndex in scope.columns
            * @param oldIndex index of the column before it is moved
            * @param newIndex index of the column after the column is moved
            */
            this.moveColumn = function (oldIndex, newIndex) {
                arrayUtility.moveAt(scope.columns, oldIndex, newIndex);
            };


            /*///////////
            ROW API
            */

            /**
            * select or unselect the item of the displayedCollection with the selection mode set in the scope
            * @param dataRow
            */
            this.toggleSelection = function (dataRow) {
                var index = scope.dataCollection.indexOf(dataRow);
                if (index !== -1) {
                    selectDataRow(scope.dataCollection, scope.selectionMode, index, dataRow.isSelected !== true);
                }
            };

            /**
            * select/unselect all the currently displayed rows
            * @param value if true select, else unselect
            */
            this.toggleSelectionAll = function (value) {
                var i = 0,
                    l = scope.displayedCollection.length;

                if (scope.selectionMode !== 'multiple') {
                    return;
                }
                for (; i < l; i++) {
                    selectDataRow(scope.displayedCollection, scope.selectionMode, i, value === true);
                }
            };

            /**
            * remove the item at index rowIndex from the displayed collection
            * @param rowIndex
            * @returns {*} item just removed or undefined
            */
            this.removeDataRow = function (rowIndex) {
                var toRemove = arrayUtility.removeAt(scope.displayedCollection, rowIndex);
                arrayUtility.removeAt(scope.dataCollection, scope.dataCollection.indexOf(toRemove));
            };

            /**
            * move an item from oldIndex to newIndex in displayedCollection
            * @param oldIndex
            * @param newIndex
            */
            this.moveDataRow = function (oldIndex, newIndex) {
                arrayUtility.moveAt(scope.displayedCollection, oldIndex, newIndex);
            };

            /**
            * update the model, it can be a non existing yet property
            * @param dataRow the dataRow to update
            * @param propertyName the property on the dataRow ojbect to update
            * @param newValue the value to set
            */
            this.updateDataRow = function (dataRow, propertyName, newValue) {
                var index = scope.displayedCollection.indexOf(dataRow),
                    getter = parse(propertyName),
                    setter = getter.assign,
                    oldValue;
                if (index !== -1) {
                    oldValue = getter(scope.displayedCollection[index]);
                    if (oldValue !== newValue) {
                        setter(scope.displayedCollection[index], newValue);
                        scope.$emit('updateDataRow', { item: scope.displayedCollection[index] });
                    }
                }
            };

            /**
            * update the item at displayed collection
            * @param dataRow
            */
            this.updateWholeDataRow = function (dataRow) {
                var index = scope.displayedCollection.indexOf(dataRow);
                delete dataRow.$$hashKey;
                var clone = angular.copy(dataRow);
                this.removeDataRow(index);
                arrayUtility.insertAt(scope.dataCollection, index, clone)
                arrayUtility.insertAt(scope.displayedCollection, index, clone);
            };

            /**
            * update the item at displayed collection
            * @param dataRow
            */
            this.addWholeDataRow = function (dataRow) {
                arrayUtility.insertAt(scope.dataCollection, 0, dataRow)
                arrayUtility.insertAt(scope.displayedCollection, 0, dataRow);
            };

            /**
            * delete the item at displayed collection
            * @param dataRow
            */
            this.deleteWholeDataRow = function (dataRow) {
                var index = scope.dataCollection.indexOf(dataRow);
                this.removeDataRow(index);
            };

        } ]);

})(angular);



angular.module('smartTable.templates', ['partials/dialogEdit.html', 'partials/defaultCell.html', 'partials/defaultHeader.html', 'partials/editableCell.html', 'partials/globalSearchCell.html', 'partials/pagination.html', 'partials/selectAllCheckbox.html', 'partials/selectionCheckbox.html', 'partials/smartTable.html']);

angular.module("partials/defaultCell.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/defaultCell.html",
    "{{formatedValue}}");
} ]);

angular.module("partials/defaultHeader.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/defaultHeader.html",
    "<span class=\"header-content\" ng-class=\"{'sort-ascent':column.reverse==true,'sort-descent':column.reverse==false}\">{{column.label}}</span>");
} ]);

angular.module("partials/editableCell.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/editableCell.html",
    "<div ng-dblclick=\"toggleEditMode($event)\">\n" +
    "    <span ng-hide=\"isEditMode\">{{value | format:column.formatFunction:column.formatParameter}}</span>\n" +
    "\n" +
    "    <form ng-submit=\"submit()\" ng-show=\"isEditMode\" name=\"myForm\">\n" +
    "        <input name=\"myInput\" ng-model=\"value\" type=\"type\" input-type/>\n" +
    "    </form>\n" +
    "</div>");
} ]);

angular.module("partials/globalSearchCell.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/globalSearchCell.html",
    //"<label>Search :</label>\n" +
    "<input type=\"text\" class=\"pull-right\" placeholder=\"Search\" ng-model=\"searchValue\"/>");
} ]);

angular.module("partials/pagination.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/pagination.html",
    "<div class=\"pagination\">\n" +
    "    <ul>\n" +
    "        <li ng-repeat=\"page in pages\" ng-class=\"{active: page.active, disabled: page.disabled}\"><a\n" +
    "                ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
    "    </ul>\n" +
    "</div> ");
} ]);

angular.module("partials/selectAllCheckbox.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/selectAllCheckbox.html",
    "<input class=\"smart-table-select-all\"  type=\"checkbox\" ng-model=\"holder.isAllSelected\"/>");
} ]);

angular.module("partials/selectionCheckbox.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/selectionCheckbox.html",
    "<input type=\"checkbox\" ng-model=\"dataRow.isSelected\" stop-event=\"click\"/>");
} ]);

angular.module("partials/smartTable.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("partials/smartTable.html",
    "<table class=\"smart-table\">\n" +
    "    <thead>\n" +
    "    <tr class=\"smart-table-global-search-row\" ng-show=\"isGlobalSearchActivated\">\n" +
    "        <td class=\"smart-table-global-search {{globalSearchClass}}\" global-search-class=\"{{config.globalSearchClass}}\" column-span=\"{{columns.length}}\" colspan=\"{{columnSpan}}\">\n" +
    "        </td>\n" +
    "    </tr>\n" +
    "    <tr class=\"smart-table-global-search-row\" ng-show=\"isGlobalSearchActivated\">\n" +
    "        <td ng-show=\"actions.add.url\" colspan=\"{{columns.length}}\">\n" +
    "           <edit-dialog type=\"add\"></edit-dialog>\n" +
    "        </td>\n" +
    "    </tr>\n" +
    "    <tr class=\"smart-table-header-row\">\n" +
    "        <th ng-repeat=\"column in columns\" class=\"smart-table-header-cell {{column.headerClass}}\" ng-hide=\"column.noList\">\n" +
    "        </th>\n" +
    "    </tr>\n" +
    "    </thead>\n" +
    "    <tbody>\n" +
    "    <tr ng-repeat=\"dataRow in displayedCollection\" ng-class=\"{selected:dataRow.isSelected}\"\n" +
    "        class=\"smart-table-data-row\">\n" +
    "        <td ng-repeat=\"column in columns\" ng-hide=\"column.noList\" class=\"smart-table-data-cell {{column.cellClass}}\"></td>\n" +
    "    </tr>\n" +
    "    </tbody>\n" +
    "    <tfoot ng-show=\"isPaginationEnabled\">\n" +
    "    <tr class=\"smart-table-footer-row\">\n" +
    "        <td colspan=\"{{columns.length}}\">\n" +
    "            <div pagination-smart-table=\"\" num-pages=\"numberOfPages\" max-size=\"maxSize\" current-page=\"currentPage\"></div>\n" +
    "        </td>\n" +
    "    </tr>\n" +
    "    </tfoot>\n" +
    "</table>\n" +
    "\n" +
    "\n" +
    "");
} ]);

angular.module('partials/dialogEdit.html', []).run(["$templateCache", function ($templateCache) {
    $templateCache.put('partials/dialogEdit.html',
    "<div class=\"modal-header\">\n" +
    "    <h3>{{title}}</h3>\n" +
    "</div>\n" +
    "<div class=\"modal-body\">\n" +
    "<form novalidate name=\"smartTableValidForm\">" +
    "<table>\n" +
    "    <tbody>\n" +
    "       <tr ng-repeat=\"column in columns\" class=\"smart-table-edit-data-cell\">\n" +
    "       </tr>\n" +
    "    </tbody>\n" +
    "</table>\n" +
    "</form>" +
    "</div>\n" +
    "<div class=\"modal-footer\">\n" +
    "    <button ng-click=\"close()\" class=\"btn btn-danger\">Close</button>\n" +
    "    <button ng-click=\"save(user)\" class=\"btn btn-success\" ng-disabled=\"smartTableValidForm.$invalid\">Save</button>\n" +
    "</div>\n" +
    "\n" +
    "\n" +
    "");
} ]);

(function (angular) {
    "use strict";
    angular.module('smartTable.templateUrlList', [])
        .constant('templateUrlList', {
            smartTable: 'partials/smartTable.html',
            smartTableGlobalSearch: 'partials/globalSearchCell.html',
            editableCell: 'partials/editableCell.html',
            selectionCheckbox: 'partials/selectionCheckbox.html',
            selectAllCheckbox: 'partials/selectAllCheckbox.html',
            defaultHeader: 'partials/defaultHeader.html',
            pagination: 'partials/pagination.html',
            dialogEdit: 'partials/dialogEdit.html'
        });
})(angular);


(function (angular) {
    "use strict";
    angular.module('smartTable.utilities', [])

        .factory('ArrayUtility', function () {

            /**
            * remove the item at index from arrayRef and return the removed item
            * @param arrayRef
            * @param index
            * @returns {*}
            */
            var removeAt = function (arrayRef, index) {
                if (index >= 0 && index < arrayRef.length) {
                    return arrayRef.splice(index, 1)[0];
                }
            },

            /**
            * insert item in arrayRef at index or a the end if index is wrong
            * @param arrayRef
            * @param index
            * @param item
            */
                insertAt = function (arrayRef, index, item) {
                    if (index >= 0 && index < arrayRef.length) {
                        arrayRef.splice(index, 0, item);
                    } else {
                        arrayRef.push(item);
                    }
                },

            /**
            * move the item at oldIndex to newIndex in arrayRef
            * @param arrayRef
            * @param oldIndex
            * @param newIndex
            */
                moveAt = function (arrayRef, oldIndex, newIndex) {
                    var elementToMove;
                    if (oldIndex >= 0 && oldIndex < arrayRef.length && newIndex >= 0 && newIndex < arrayRef.length) {
                        elementToMove = arrayRef.splice(oldIndex, 1)[0];
                        arrayRef.splice(newIndex, 0, elementToMove);
                    }
                },

            /**
            * sort arrayRef according to sortAlgorithm following predicate and reverse
            * @param arrayRef
            * @param sortAlgorithm
            * @param predicate
            * @param reverse
            * @returns {*}
            */
                sort = function (arrayRef, sortAlgorithm, predicate, reverse) {

                    if (!sortAlgorithm || !angular.isFunction(sortAlgorithm)) {
                        return arrayRef;
                    } else {
                        return sortAlgorithm(arrayRef, predicate, reverse === true); //excpet if reverse is true it will take it as false
                    }
                },

            /**
            * filter arrayRef according with filterAlgorithm and predicate
            * @param arrayRef
            * @param filterAlgorithm
            * @param predicate
            * @returns {*}
            */
                filter = function (arrayRef, filterAlgorithm, predicate) {
                    if (!filterAlgorithm || !angular.isFunction(filterAlgorithm)) {
                        return arrayRef;
                    } else {
                        return filterAlgorithm(arrayRef, predicate);
                    }
                },

            /**
            * return an array, part of array ref starting at min and the size of length
            * @param arrayRef
            * @param min
            * @param length
            * @returns {*}
            */
                fromTo = function (arrayRef, min, length) {

                    var out = [],
                        limit,
                        start;

                    if (!angular.isArray(arrayRef)) {
                        return arrayRef;
                    }

                    start = Math.max(min, 0);
                    start = Math.min(start, (arrayRef.length - 1) > 0 ? arrayRef.length - 1 : 0);

                    length = Math.max(0, length);
                    limit = Math.min(start + length, arrayRef.length);

                    for (var i = start; i < limit; i++) {
                        out.push(arrayRef[i]);
                    }
                    return out;
                };


            return {
                removeAt: removeAt,
                insertAt: insertAt,
                moveAt: moveAt,
                sort: sort,
                filter: filter,
                fromTo: fromTo
            };
        });
})(angular);



(function (angular) {
    angular.module('ui.bootstrap.pagination.smartTable', ['smartTable.templateUrlList'])

        .constant('paginationConfig', {
            boundaryLinks: false,
            directionLinks: true,
            firstText: 'First',
            previousText: '<',
            nextText: '>',
            lastText: 'Last'
        })

        .directive('paginationSmartTable', ['paginationConfig', 'templateUrlList', function (paginationConfig, templateUrlList) {
            return {
                restrict: 'EA',
                require: '^smartTable',
                scope: {
                    numPages: '=',
                    currentPage: '=',
                    maxSize: '='
                },
                templateUrl: templateUrlList.pagination,
                replace: true,
                link: function (scope, element, attrs, ctrl) {

                    // Setup configuration parameters
                    var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
                    var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
                    var firstText = angular.isDefined(attrs.firstText) ? attrs.firstText : paginationConfig.firstText;
                    var previousText = angular.isDefined(attrs.previousText) ? attrs.previousText : paginationConfig.previousText;
                    var nextText = angular.isDefined(attrs.nextText) ? attrs.nextText : paginationConfig.nextText;
                    var lastText = angular.isDefined(attrs.lastText) ? attrs.lastText : paginationConfig.lastText;

                    // Create page object used in template
                    function makePage(number, text, isActive, isDisabled) {
                        return {
                            number: number,
                            text: text,
                            active: isActive,
                            disabled: isDisabled
                        };
                    }

                    scope.$watch('numPages + currentPage + maxSize', function () {
                        scope.pages = [];

                        // Default page limits
                        var startPage = 1, endPage = scope.numPages;

                        // recompute if maxSize
                        if (scope.maxSize && scope.maxSize < scope.numPages) {
                            startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize / 2), 1);
                            endPage = startPage + scope.maxSize - 1;

                            // Adjust if limit is exceeded
                            if (endPage > scope.numPages) {
                                endPage = scope.numPages;
                                startPage = endPage - scope.maxSize + 1;
                            }
                        }

                        // Add page number links
                        for (var number = startPage; number <= endPage; number++) {
                            var page = makePage(number, number, scope.isActive(number), false);
                            scope.pages.push(page);
                        }

                        // Add previous & next links
                        if (directionLinks) {
                            var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious());
                            scope.pages.unshift(previousPage);

                            var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext());
                            scope.pages.push(nextPage);
                        }

                        // Add first & last links
                        if (boundaryLinks) {
                            var firstPage = makePage(1, firstText, false, scope.noPrevious());
                            scope.pages.unshift(firstPage);

                            var lastPage = makePage(scope.numPages, lastText, false, scope.noNext());
                            scope.pages.push(lastPage);
                        }


                        if (scope.currentPage > scope.numPages) {
                            scope.selectPage(scope.numPages);
                        }
                    });
                    scope.noPrevious = function () {
                        return scope.currentPage === 1;
                    };
                    scope.noNext = function () {
                        return scope.currentPage === scope.numPages;
                    };
                    scope.isActive = function (page) {
                        return scope.currentPage === page;
                    };

                    scope.selectPage = function (page) {
                        if (!scope.isActive(page) && page > 0 && page <= scope.numPages) {
                            scope.currentPage = page;
                            ctrl.changePage({ page: page });
                        }
                    };
                }
            };
        } ]);
})(angular);

'use strict';
// Declare app level module which depends on filters, and services
angular.module('myApp', ['smartTable.table', 'serverSide']).
        controller('mainCtrl', ['$scope', function (scope) {

            scope.rowCollection = [];

            scope.columnCollection = [
                { label: 'First Name', map: 'FirstName', validationAttrs: 'required', validationMsgs: '<span class="error" ng-show="smartTableValidForm.FirstName.$error.required">Required!</span>' },
                { label: 'Last Name', map: 'LastName' },
                { label: 'User Name', map: 'UserName', validationAttrs: 'required' },
                { label: 'Password', map: 'Password', noList: true, editType: 'password' },
                { label: 'Customer', map: 'CustId', optionsUrl: '/GetCusts', editType: 'radio' },
                { label: 'Role', map: 'RoleId', optionsUrl: '/GetRoles', editType: 'select', defaultTemplate: '<option value="" ng-hide="dataRow[column.map]">---</option>', validationAttrs: 'required', validationMsgs: '<span class="error" ng-show="smartTableValidForm.RoleId.$error.required">Required!</span>' }, // NOTE: small hack which enables defaultTemplate option :)
                { label: 'E-mail', editType: 'email', map: 'Email' },
                { label: 'Cell Phone', map: 'Mobilephone', noEdit: true, validationAttrs: 'required' },
                { label: 'Locked', map: 'IsLocked', cellTemplate: '<input disabled type="checkbox" name="{{column.map}}" ng-model="dataRow[column.map]">', editType: 'checkbox', noAdd: true }
            ];

            scope.globalConfig = {
                isPaginationEnabled: true,
                isGlobalSearchActivated: true,
                itemsByPage: 10,
                selectionMode: 'single',
                actions: {
                    list: { url: '/GetUsers' },
                    edit: { url: '/EditUser', title: 'Edit User', desc: 'Edit', iconClass: '' }, 
                    add: { url: '/AddUser', title: 'Add User', buttonClass: 'pull-right', iconClass: 'icon-plus', desc: ' Add User' }, // TODO: zkontrolovat default description
                    remove: { url: '/DelUser', title: 'Confirmation Dialog', msg: 'Do you really want to delete the user?' }
                }
            };

        } ]);
'use strict';

// Imitation of server - for testing purpose
angular.module('serverSide', ['ngMockE2E'])
        .run(['$httpBackend', function ($httpBackend) {
            var users = [
            { "RoleId": 464, "UserId": 100849, "UserName": "matt", "FirstName": "Tom", "LastName": "Blue", "Email": "matt@comp.com", "Telephone": null, "Mobilephone": "111222333", "Password": "tom", "CustCd": null, "CustId": 16, "Loccd": null, "IsLocked": true },
            { "RoleId": 463, "UserId": 100851, "UserName": "sale", "FirstName": "sale", "LastName": "sale", "Email": "sales@comp.com", "Telephone": null, "Mobilephone": "777111223", "Password": "sale", "CustCd": null, "CustId": null, "Loccd": null, "IsLocked": false },
            { "RoleId": 464, "UserId": 100853, "UserName": "novak", "FirstName": "Mark", "LastName": "Novak", "Email": "asa@asd.cz", "Telephone": null, "Mobilephone": "777888444", "Password": "new", "CustCd": null, "CustId": 15, "Loccd": null, "IsLocked": false },
            { "RoleId": 465, "UserId": 100860, "UserName": "testadmin", "FirstName": "testadmin", "LastName": "testadmin", "Email": "test@test.cz", "Telephone": null, "Mobilephone": "777888999", "Password": "testadmin", "CustCd": null, "CustId": null, "Loccd": null, "IsLocked": false },
            { "RoleId": 465, "UserId": 100861, "UserName": "admin", "FirstName": "admin", "LastName": "admin", "Email": "admin@comp.com", "Telephone": "777888999", "Mobilephone": "777888999", "Password": "admin", "CustCd": null, "CustId": null, "Loccd": null, "IsLocked": false },
            { "RoleId": 464, "UserId": 100873, "UserName": "test", "FirstName": "test", "LastName": "test", "Email": "test@test.cz", "Telephone": null, "Mobilephone": "789456123", "Password": "test", "CustCd": null, "CustId": 16, "Loccd": null, "IsLocked": false },
            { "RoleId": 463, "UserId": 100874, "UserName": "testsale", "FirstName": "testsale", "LastName": "testsale", "Email": "testsale@test.cz", "Telephone": null, "Mobilephone": "789456123", "Password": "testsale", "CustCd": null, "CustId": null, "Loccd": null, "IsLocked": false}];

            var roles = [{ "Text": "Sales Team", "Value": 463 }, { "Text": "Customer", "Value": 464 }, { "Text": "Admin", "Value": 465}];
            var custs = [{ "Text": "Company AAA", "Value": 15 }, { "Text": "Company BBB", "Value": 16}];

            $httpBackend.whenPOST('/GetRoles').respond(function (method, url, data, headers) {
                var result = [200];
                result.push({ data: roles })
                return result;
            });
            $httpBackend.whenPOST('/GetCusts').respond(function (method, url, data, headers) {
                var result = [200];
                result.push({ data: custs })
                return result;
            });
            $httpBackend.whenPOST('/GetUsers').respond(function (method, url, data, headers) {
                var result = [200];
                result.push({ data: users })
                return result;
            });
            $httpBackend.whenPOST('/EditUser').respond(function (method, url, data, headers) {
                var updatedUser = angular.fromJson(data);
                for (var i = 0; i < users.length; i++)
                    if (updatedUser.UserId === users[i].UserId) {
                        angular.extend(users[i], updatedUser);
                        var result = [200];
                        result.push({ data: users[i] })
                        return result;
                    }
            });
            $httpBackend.whenPOST('/AddUser').respond(function (method, url, data, headers) {
                var newUser = angular.fromJson(data);
                users.push(newUser);

                var result = [200];
                result.push({ data: newUser })
                return result;
            });
            $httpBackend.whenPOST('/DelUser').respond(function (method, url, data, headers) {
                var updatedUser = angular.fromJson(data);
                for (var i = 0; i < users.length; i++)
                    if (updatedUser.UserId === users[i].UserId) {
                        users.splice(i, 1);
                        var result = [200];
                        result.push({ data: users[i] })
                        return result;
                    }
            });
        } ]);