angular-ui-bootstrap-rails 0.5.0.0 → 0.6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
1
+ angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
2
2
  angular.module('ui.bootstrap.transition', [])
3
3
 
4
4
  /**
@@ -336,6 +336,16 @@ angular.module("ui.bootstrap.alert", []).directive('alert', function () {
336
336
  };
337
337
  });
338
338
 
339
+ angular.module('ui.bootstrap.bindHtml', [])
340
+
341
+ .directive('bindHtmlUnsafe', function () {
342
+ return function (scope, element, attr) {
343
+ element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
344
+ scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
345
+ element.html(value || '');
346
+ });
347
+ };
348
+ });
339
349
  angular.module('ui.bootstrap.buttons', [])
340
350
 
341
351
  .constant('buttonConfig', {
@@ -407,7 +417,7 @@ angular.module('ui.bootstrap.buttons', [])
407
417
  /**
408
418
  * @ngdoc overview
409
419
  * @name ui.bootstrap.carousel
410
- *
420
+ *
411
421
  * @description
412
422
  * AngularJS version of an image carousel.
413
423
  *
@@ -438,10 +448,10 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
438
448
  }
439
449
  function goNext() {
440
450
  //If we have a slide to transition from and we have a transition type and we're allowed, go
441
- if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
451
+ if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
442
452
  //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
443
453
  nextSlide.$element.addClass(direction);
444
- nextSlide.$element[0].offsetWidth = nextSlide.$element[0].offsetWidth; //force reflow
454
+ var reflow = nextSlide.$element[0].offsetWidth; //force reflow
445
455
 
446
456
  //Set all other slides to stop doing their stuff for the new transition
447
457
  angular.forEach(slides, function(slide) {
@@ -480,7 +490,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
480
490
 
481
491
  $scope.next = function() {
482
492
  var newIndex = (currentIndex + 1) % slides.length;
483
-
493
+
484
494
  //Prevent this user-triggered transition from occurring if there is already one in progress
485
495
  if (!$scope.$currentTransition) {
486
496
  return self.select(slides[newIndex], 'next');
@@ -489,7 +499,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
489
499
 
490
500
  $scope.prev = function() {
491
501
  var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
492
-
502
+
493
503
  //Prevent this user-triggered transition from occurring if there is already one in progress
494
504
  if (!$scope.$currentTransition) {
495
505
  return self.select(slides[newIndex], 'prev');
@@ -706,7 +716,7 @@ function CarouselDemoCtrl($scope) {
706
716
  var lastValue = scope.active = getActive(scope.$parent);
707
717
  scope.$watch(function parentActiveWatch() {
708
718
  var parentActive = getActive(scope.$parent);
709
-
719
+
710
720
  if (parentActive !== scope.active) {
711
721
  // we are out of sync and need to copy
712
722
  if (parentActive !== lastValue) {
@@ -746,13 +756,6 @@ angular.module('ui.bootstrap.position', [])
746
756
  */
747
757
  .factory('$position', ['$document', '$window', function ($document, $window) {
748
758
 
749
- var mouseX, mouseY;
750
-
751
- $document.bind('mousemove', function mouseMoved(event) {
752
- mouseX = event.pageX;
753
- mouseY = event.pageY;
754
- });
755
-
756
759
  function getStyle(el, cssprop) {
757
760
  if (el.currentStyle) { //IE
758
761
  return el.currentStyle[cssprop];
@@ -816,16 +819,9 @@ angular.module('ui.bootstrap.position', [])
816
819
  return {
817
820
  width: element.prop('offsetWidth'),
818
821
  height: element.prop('offsetHeight'),
819
- top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
820
- left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
822
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
823
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
821
824
  };
822
- },
823
-
824
- /**
825
- * Provides the coordinates of the mouse
826
- */
827
- mouse: function () {
828
- return {x: mouseX, y: mouseY};
829
825
  }
830
826
  };
831
827
  }]);
@@ -1104,26 +1100,9 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1104
1100
  scope.$destroy();
1105
1101
  });
1106
1102
 
1107
- function formatDate(value) {
1108
- return (value) ? dateFilter(value, dateFormat) : null;
1109
- }
1110
- ngModel.$formatters.push(formatDate);
1111
-
1112
- // TODO: reverse from dateFilter string to Date object
1113
- function parseDate(value) {
1114
- if ( value ) {
1115
- var date = new Date(value);
1116
- if (!isNaN(date)) {
1117
- return date;
1118
- }
1119
- }
1120
- return value;
1121
- }
1122
- ngModel.$parsers.push(parseDate);
1123
-
1124
1103
  var getIsOpen, setIsOpen;
1125
- if ( attrs.open ) {
1126
- getIsOpen = $parse(attrs.open);
1104
+ if ( attrs.isOpen ) {
1105
+ getIsOpen = $parse(attrs.isOpen);
1127
1106
  setIsOpen = getIsOpen.assign;
1128
1107
 
1129
1108
  originalScope.$watch(getIsOpen, function updateOpen(value) {
@@ -1165,33 +1144,58 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1165
1144
  datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
1166
1145
  }
1167
1146
 
1168
- var $setModelValue = $parse(attrs.ngModel).assign;
1147
+ // TODO: reverse from dateFilter string to Date object
1148
+ function parseDate(viewValue) {
1149
+ if (!viewValue) {
1150
+ ngModel.$setValidity('date', true);
1151
+ return null;
1152
+ } else if (angular.isDate(viewValue)) {
1153
+ ngModel.$setValidity('date', true);
1154
+ return viewValue;
1155
+ } else if (angular.isString(viewValue)) {
1156
+ var date = new Date(viewValue);
1157
+ if (isNaN(date)) {
1158
+ ngModel.$setValidity('date', false);
1159
+ return undefined;
1160
+ } else {
1161
+ ngModel.$setValidity('date', true);
1162
+ return date;
1163
+ }
1164
+ } else {
1165
+ ngModel.$setValidity('date', false);
1166
+ return undefined;
1167
+ }
1168
+ }
1169
+ ngModel.$parsers.unshift(parseDate);
1169
1170
 
1170
1171
  // Inner change
1171
1172
  scope.dateSelection = function() {
1172
- $setModelValue(originalScope, scope.date);
1173
+ ngModel.$setViewValue(scope.date);
1174
+ ngModel.$render();
1175
+
1173
1176
  if (closeOnDateSelection) {
1174
1177
  setOpen( false );
1175
1178
  }
1176
1179
  };
1177
1180
 
1181
+ element.bind('input change keyup', function() {
1182
+ scope.$apply(function() {
1183
+ updateCalendar();
1184
+ });
1185
+ });
1186
+
1178
1187
  // Outter change
1179
- scope.$watch(function() {
1180
- return ngModel.$modelValue;
1181
- }, function(value) {
1182
- if (angular.isString(value)) {
1183
- var date = parseDate(value);
1184
-
1185
- if (value && !date) {
1186
- $setModelValue(originalScope, null);
1187
- throw new Error(value + ' cannot be parsed to a date object.');
1188
- } else {
1189
- value = date;
1190
- }
1191
- }
1192
- scope.date = value;
1188
+ ngModel.$render = function() {
1189
+ var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
1190
+ element.val(date);
1191
+
1192
+ updateCalendar();
1193
+ };
1194
+
1195
+ function updateCalendar() {
1196
+ scope.date = ngModel.$modelValue;
1193
1197
  updatePosition();
1194
- });
1198
+ }
1195
1199
 
1196
1200
  function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
1197
1201
  if (attribute) {
@@ -1218,15 +1222,22 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1218
1222
  scope.position.top = scope.position.top + element.prop('offsetHeight');
1219
1223
  }
1220
1224
 
1225
+ var documentBindingInitialized = false, elementFocusInitialized = false;
1221
1226
  scope.$watch('isOpen', function(value) {
1222
1227
  if (value) {
1223
1228
  updatePosition();
1224
1229
  $document.bind('click', documentClickBind);
1225
- element.unbind('focus', elementFocusBind);
1226
- element.focus();
1230
+ if(elementFocusInitialized) {
1231
+ element.unbind('focus', elementFocusBind);
1232
+ }
1233
+ element[0].focus();
1234
+ documentBindingInitialized = true;
1227
1235
  } else {
1228
- $document.unbind('click', documentClickBind);
1236
+ if(documentBindingInitialized) {
1237
+ $document.unbind('click', documentClickBind);
1238
+ }
1229
1239
  element.bind('focus', elementFocusBind);
1240
+ elementFocusInitialized = true;
1230
1241
  }
1231
1242
 
1232
1243
  if ( setIsOpen ) {
@@ -1234,6 +1245,8 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1234
1245
  }
1235
1246
  });
1236
1247
 
1248
+ var $setModelValue = $parse(attrs.ngModel).assign;
1249
+
1237
1250
  scope.today = function() {
1238
1251
  $setModelValue(originalScope, new Date());
1239
1252
  };
@@ -1260,286 +1273,6 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1260
1273
  }
1261
1274
  };
1262
1275
  }]);
1263
- // The `$dialogProvider` can be used to configure global defaults for your
1264
- // `$dialog` service.
1265
- var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']);
1266
-
1267
- dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){
1268
- $scope.title = model.title;
1269
- $scope.message = model.message;
1270
- $scope.buttons = model.buttons;
1271
- $scope.close = function(res){
1272
- dialog.close(res);
1273
- };
1274
- }]);
1275
-
1276
- dialogModule.provider("$dialog", function(){
1277
-
1278
- // The default options for all dialogs.
1279
- var defaults = {
1280
- backdrop: true,
1281
- dialogClass: 'modal',
1282
- backdropClass: 'modal-backdrop',
1283
- transitionClass: 'fade',
1284
- triggerClass: 'in',
1285
- resolve:{},
1286
- backdropFade: false,
1287
- dialogFade:false,
1288
- keyboard: true, // close with esc key
1289
- backdropClick: true // only in conjunction with backdrop=true
1290
- /* other options: template, templateUrl, controller */
1291
- };
1292
-
1293
- var globalOptions = {};
1294
-
1295
- var activeBackdrops = {value : 0};
1296
-
1297
- // The `options({})` allows global configuration of all dialogs in the application.
1298
- //
1299
- // var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
1300
- // // don't close dialog when backdrop is clicked by default
1301
- // $dialogProvider.options({backdropClick: false});
1302
- // });
1303
- this.options = function(value){
1304
- globalOptions = value;
1305
- };
1306
-
1307
- // Returns the actual `$dialog` service that is injected in controllers
1308
- this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
1309
- function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
1310
-
1311
- var body = $document.find('body');
1312
-
1313
- function createElement(clazz) {
1314
- var el = angular.element("<div>");
1315
- el.addClass(clazz);
1316
- return el;
1317
- }
1318
-
1319
- // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
1320
- // containing at lest template or templateUrl and controller:
1321
- //
1322
- // var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
1323
- //
1324
- // Dialogs can also be created using templateUrl and controller as distinct arguments:
1325
- //
1326
- // var d = new Dialog('path/to/dialog.html', MyDialogController);
1327
- function Dialog(opts) {
1328
-
1329
- var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
1330
- this._open = false;
1331
-
1332
- this.backdropEl = createElement(options.backdropClass);
1333
- if(options.backdropFade){
1334
- this.backdropEl.addClass(options.transitionClass);
1335
- this.backdropEl.removeClass(options.triggerClass);
1336
- }
1337
-
1338
- this.modalEl = createElement(options.dialogClass);
1339
- if(options.dialogFade){
1340
- this.modalEl.addClass(options.transitionClass);
1341
- this.modalEl.removeClass(options.triggerClass);
1342
- }
1343
-
1344
- this.handledEscapeKey = function(e) {
1345
- if (e.which === 27) {
1346
- self.close();
1347
- e.preventDefault();
1348
- self.$scope.$apply();
1349
- }
1350
- };
1351
-
1352
- this.handleBackDropClick = function(e) {
1353
- self.close();
1354
- e.preventDefault();
1355
- self.$scope.$apply();
1356
- };
1357
- }
1358
-
1359
- // The `isOpen()` method returns wether the dialog is currently visible.
1360
- Dialog.prototype.isOpen = function(){
1361
- return this._open;
1362
- };
1363
-
1364
- // The `open(templateUrl, controller)` method opens the dialog.
1365
- // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired.
1366
- Dialog.prototype.open = function(templateUrl, controller){
1367
- var self = this, options = this.options;
1368
-
1369
- if(templateUrl){
1370
- options.templateUrl = templateUrl;
1371
- }
1372
- if(controller){
1373
- options.controller = controller;
1374
- }
1375
-
1376
- if(!(options.template || options.templateUrl)) {
1377
- throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
1378
- }
1379
-
1380
- this._loadResolves().then(function(locals) {
1381
- var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();
1382
-
1383
- self.modalEl.html(locals.$template);
1384
-
1385
- if (self.options.controller) {
1386
- var ctrl = $controller(self.options.controller, locals);
1387
- self.modalEl.children().data('ngControllerController', ctrl);
1388
- }
1389
-
1390
- $compile(self.modalEl)($scope);
1391
- self._addElementsToDom();
1392
-
1393
- // trigger tranisitions
1394
- setTimeout(function(){
1395
- if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
1396
- if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
1397
- });
1398
-
1399
- self._bindEvents();
1400
- });
1401
-
1402
- this.deferred = $q.defer();
1403
- return this.deferred.promise;
1404
- };
1405
-
1406
- // closes the dialog and resolves the promise returned by the `open` method with the specified result.
1407
- Dialog.prototype.close = function(result){
1408
- var self = this;
1409
- var fadingElements = this._getFadingElements();
1410
-
1411
- if(fadingElements.length > 0){
1412
- for (var i = fadingElements.length - 1; i >= 0; i--) {
1413
- $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
1414
- }
1415
- return;
1416
- }
1417
-
1418
- this._onCloseComplete(result);
1419
-
1420
- function removeTriggerClass(el){
1421
- el.removeClass(self.options.triggerClass);
1422
- }
1423
-
1424
- function onCloseComplete(){
1425
- if(self._open){
1426
- self._onCloseComplete(result);
1427
- }
1428
- }
1429
- };
1430
-
1431
- Dialog.prototype._getFadingElements = function(){
1432
- var elements = [];
1433
- if(this.options.dialogFade){
1434
- elements.push(this.modalEl);
1435
- }
1436
- if(this.options.backdropFade){
1437
- elements.push(this.backdropEl);
1438
- }
1439
-
1440
- return elements;
1441
- };
1442
-
1443
- Dialog.prototype._bindEvents = function() {
1444
- if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); }
1445
- if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); }
1446
- };
1447
-
1448
- Dialog.prototype._unbindEvents = function() {
1449
- if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); }
1450
- if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); }
1451
- };
1452
-
1453
- Dialog.prototype._onCloseComplete = function(result) {
1454
- this._removeElementsFromDom();
1455
- this._unbindEvents();
1456
-
1457
- this.deferred.resolve(result);
1458
- };
1459
-
1460
- Dialog.prototype._addElementsToDom = function(){
1461
- body.append(this.modalEl);
1462
-
1463
- if(this.options.backdrop) {
1464
- if (activeBackdrops.value === 0) {
1465
- body.append(this.backdropEl);
1466
- }
1467
- activeBackdrops.value++;
1468
- }
1469
-
1470
- this._open = true;
1471
- };
1472
-
1473
- Dialog.prototype._removeElementsFromDom = function(){
1474
- this.modalEl.remove();
1475
-
1476
- if(this.options.backdrop) {
1477
- activeBackdrops.value--;
1478
- if (activeBackdrops.value === 0) {
1479
- this.backdropEl.remove();
1480
- }
1481
- }
1482
- this._open = false;
1483
- };
1484
-
1485
- // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog.
1486
- Dialog.prototype._loadResolves = function(){
1487
- var values = [], keys = [], templatePromise, self = this;
1488
-
1489
- if (this.options.template) {
1490
- templatePromise = $q.when(this.options.template);
1491
- } else if (this.options.templateUrl) {
1492
- templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache})
1493
- .then(function(response) { return response.data; });
1494
- }
1495
-
1496
- angular.forEach(this.options.resolve || [], function(value, key) {
1497
- keys.push(key);
1498
- values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
1499
- });
1500
-
1501
- keys.push('$template');
1502
- values.push(templatePromise);
1503
-
1504
- return $q.all(values).then(function(values) {
1505
- var locals = {};
1506
- angular.forEach(values, function(value, index) {
1507
- locals[keys[index]] = value;
1508
- });
1509
- locals.dialog = self;
1510
- return locals;
1511
- });
1512
- };
1513
-
1514
- // The actual `$dialog` service that is injected in controllers.
1515
- return {
1516
- // Creates a new `Dialog` with the specified options.
1517
- dialog: function(opts){
1518
- return new Dialog(opts);
1519
- },
1520
- // creates a new `Dialog` tied to the default message box template and controller.
1521
- //
1522
- // Arguments `title` and `message` are rendered in the modal header and body sections respectively.
1523
- // The `buttons` array holds an object with the following members for each button to include in the
1524
- // modal footer section:
1525
- //
1526
- // * `result`: the result to pass to the `close` method of the dialog when the button is clicked
1527
- // * `label`: the label of the button
1528
- // * `cssClass`: additional css class(es) to apply to the button for styling
1529
- messageBox: function(title, message, buttons){
1530
- return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
1531
- {model: function() {
1532
- return {
1533
- title: title,
1534
- message: message,
1535
- buttons: buttons
1536
- };
1537
- }
1538
- }});
1539
- }
1540
- };
1541
- }];
1542
- });
1543
1276
 
1544
1277
  /*
1545
1278
  * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
@@ -1593,93 +1326,391 @@ angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['
1593
1326
  }
1594
1327
  };
1595
1328
  }]);
1596
- angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
1597
- .directive('modal', ['$parse', '$dialog', function($parse, $dialog) {
1598
- return {
1599
- restrict: 'EA',
1600
- terminal: true,
1601
- link: function(scope, elm, attrs) {
1602
- var opts = angular.extend({}, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options));
1603
- var shownExpr = attrs.modal || attrs.show;
1604
- var setClosed;
1605
-
1606
- // Create a dialog with the template as the contents of the directive
1607
- // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope
1608
- opts = angular.extend(opts, {
1609
- template: elm.html(),
1610
- resolve: { $scope: function() { return scope; } }
1611
- });
1612
- var dialog = $dialog.dialog(opts);
1329
+ angular.module('ui.bootstrap.modal', [])
1613
1330
 
1614
- elm.remove();
1331
+ /**
1332
+ * A helper, internal data structure that acts as a map but also allows getting / removing
1333
+ * elements in the LIFO order
1334
+ */
1335
+ .factory('$$stackedMap', function () {
1336
+ return {
1337
+ createNew: function () {
1338
+ var stack = [];
1615
1339
 
1616
- if (attrs.close) {
1617
- setClosed = function() {
1618
- $parse(attrs.close)(scope);
1340
+ return {
1341
+ add: function (key, value) {
1342
+ stack.push({
1343
+ key: key,
1344
+ value: value
1345
+ });
1346
+ },
1347
+ get: function (key) {
1348
+ for (var i = 0; i < stack.length; i++) {
1349
+ if (key == stack[i].key) {
1350
+ return stack[i];
1351
+ }
1352
+ }
1353
+ },
1354
+ keys: function() {
1355
+ var keys = [];
1356
+ for (var i = 0; i < stack.length; i++) {
1357
+ keys.push(stack[i].key);
1358
+ }
1359
+ return keys;
1360
+ },
1361
+ top: function () {
1362
+ return stack[stack.length - 1];
1363
+ },
1364
+ remove: function (key) {
1365
+ var idx = -1;
1366
+ for (var i = 0; i < stack.length; i++) {
1367
+ if (key == stack[i].key) {
1368
+ idx = i;
1369
+ break;
1370
+ }
1371
+ }
1372
+ return stack.splice(idx, 1)[0];
1373
+ },
1374
+ removeTop: function () {
1375
+ return stack.splice(stack.length - 1, 1)[0];
1376
+ },
1377
+ length: function () {
1378
+ return stack.length;
1379
+ }
1619
1380
  };
1620
- } else {
1621
- setClosed = function() {
1622
- if (angular.isFunction($parse(shownExpr).assign)) {
1623
- $parse(shownExpr).assign(scope, false);
1381
+ }
1382
+ };
1383
+ })
1384
+
1385
+ /**
1386
+ * A helper directive for the $modal service. It creates a backdrop element.
1387
+ */
1388
+ .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
1389
+ return {
1390
+ restrict: 'EA',
1391
+ replace: true,
1392
+ templateUrl: 'template/modal/backdrop.html',
1393
+ link: function (scope, element, attrs) {
1394
+
1395
+ //trigger CSS transitions
1396
+ $timeout(function () {
1397
+ scope.animate = true;
1398
+ });
1399
+
1400
+ scope.close = function (evt) {
1401
+ var modal = $modalStack.getTop();
1402
+ if (modal && modal.value.backdrop && modal.value.backdrop != 'static') {
1403
+ evt.preventDefault();
1404
+ evt.stopPropagation();
1405
+ $modalStack.dismiss(modal.key, 'backdrop click');
1624
1406
  }
1625
1407
  };
1626
1408
  }
1409
+ };
1410
+ }])
1627
1411
 
1628
- scope.$watch(shownExpr, function(isShown, oldShown) {
1629
- if (isShown) {
1630
- dialog.open().then(function(){
1631
- setClosed();
1632
- });
1633
- } else {
1634
- //Make sure it is not opened
1635
- if (dialog.isOpen()){
1636
- dialog.close();
1412
+ .directive('modalWindow', ['$timeout', function ($timeout) {
1413
+ return {
1414
+ restrict: 'EA',
1415
+ scope: {
1416
+ index: '@'
1417
+ },
1418
+ replace: true,
1419
+ transclude: true,
1420
+ templateUrl: 'template/modal/window.html',
1421
+ link: function (scope, element, attrs) {
1422
+ scope.windowClass = attrs.windowClass || '';
1423
+
1424
+ //trigger CSS transitions
1425
+ $timeout(function () {
1426
+ scope.animate = true;
1427
+ });
1428
+ }
1429
+ };
1430
+ }])
1431
+
1432
+ .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap',
1433
+ function ($document, $compile, $rootScope, $$stackedMap) {
1434
+
1435
+ var backdropjqLiteEl, backdropDomEl;
1436
+ var backdropScope = $rootScope.$new(true);
1437
+ var body = $document.find('body').eq(0);
1438
+ var openedWindows = $$stackedMap.createNew();
1439
+ var $modalStack = {};
1440
+
1441
+ function backdropIndex() {
1442
+ var topBackdropIndex = -1;
1443
+ var opened = openedWindows.keys();
1444
+ for (var i = 0; i < opened.length; i++) {
1445
+ if (openedWindows.get(opened[i]).value.backdrop) {
1446
+ topBackdropIndex = i;
1637
1447
  }
1638
1448
  }
1449
+ return topBackdropIndex;
1450
+ }
1451
+
1452
+ $rootScope.$watch(backdropIndex, function(newBackdropIndex){
1453
+ backdropScope.index = newBackdropIndex;
1639
1454
  });
1640
- }
1641
- };
1642
- }]);
1455
+
1456
+ function removeModalWindow(modalInstance) {
1457
+
1458
+ var modalWindow = openedWindows.get(modalInstance).value;
1459
+
1460
+ //clean up the stack
1461
+ openedWindows.remove(modalInstance);
1462
+
1463
+ //remove window DOM element
1464
+ modalWindow.modalDomEl.remove();
1465
+
1466
+ //remove backdrop if no longer needed
1467
+ if (backdropIndex() == -1) {
1468
+ backdropDomEl.remove();
1469
+ backdropDomEl = undefined;
1470
+ }
1471
+
1472
+ //destroy scope
1473
+ modalWindow.modalScope.$destroy();
1474
+ }
1475
+
1476
+ $document.bind('keydown', function (evt) {
1477
+ var modal;
1478
+
1479
+ if (evt.which === 27) {
1480
+ modal = openedWindows.top();
1481
+ if (modal && modal.value.keyboard) {
1482
+ $rootScope.$apply(function () {
1483
+ $modalStack.dismiss(modal.key);
1484
+ });
1485
+ }
1486
+ }
1487
+ });
1488
+
1489
+ $modalStack.open = function (modalInstance, modal) {
1490
+
1491
+ openedWindows.add(modalInstance, {
1492
+ deferred: modal.deferred,
1493
+ modalScope: modal.scope,
1494
+ backdrop: modal.backdrop,
1495
+ keyboard: modal.keyboard
1496
+ });
1497
+
1498
+ var angularDomEl = angular.element('<div modal-window></div>');
1499
+ angularDomEl.attr('window-class', modal.windowClass);
1500
+ angularDomEl.attr('index', openedWindows.length() - 1);
1501
+ angularDomEl.html(modal.content);
1502
+
1503
+ var modalDomEl = $compile(angularDomEl)(modal.scope);
1504
+ openedWindows.top().value.modalDomEl = modalDomEl;
1505
+ body.append(modalDomEl);
1506
+
1507
+ if (backdropIndex() >= 0 && !backdropDomEl) {
1508
+ backdropjqLiteEl = angular.element('<div modal-backdrop></div>');
1509
+ backdropDomEl = $compile(backdropjqLiteEl)(backdropScope);
1510
+ body.append(backdropDomEl);
1511
+ }
1512
+ };
1513
+
1514
+ $modalStack.close = function (modalInstance, result) {
1515
+ var modal = openedWindows.get(modalInstance);
1516
+ if (modal) {
1517
+ modal.value.deferred.resolve(result);
1518
+ removeModalWindow(modalInstance);
1519
+ }
1520
+ };
1521
+
1522
+ $modalStack.dismiss = function (modalInstance, reason) {
1523
+ var modalWindow = openedWindows.get(modalInstance).value;
1524
+ if (modalWindow) {
1525
+ modalWindow.deferred.reject(reason);
1526
+ removeModalWindow(modalInstance);
1527
+ }
1528
+ };
1529
+
1530
+ $modalStack.getTop = function () {
1531
+ return openedWindows.top();
1532
+ };
1533
+
1534
+ return $modalStack;
1535
+ }])
1536
+
1537
+ .provider('$modal', function () {
1538
+
1539
+ var $modalProvider = {
1540
+ options: {
1541
+ backdrop: true, //can be also false or 'static'
1542
+ keyboard: true
1543
+ },
1544
+ $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
1545
+ function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
1546
+
1547
+ var $modal = {};
1548
+
1549
+ function getTemplatePromise(options) {
1550
+ return options.template ? $q.when(options.template) :
1551
+ $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
1552
+ return result.data;
1553
+ });
1554
+ }
1555
+
1556
+ function getResolvePromises(resolves) {
1557
+ var promisesArr = [];
1558
+ angular.forEach(resolves, function (value, key) {
1559
+ if (angular.isFunction(value) || angular.isArray(value)) {
1560
+ promisesArr.push($q.when($injector.invoke(value)));
1561
+ }
1562
+ });
1563
+ return promisesArr;
1564
+ }
1565
+
1566
+ $modal.open = function (modalOptions) {
1567
+
1568
+ var modalResultDeferred = $q.defer();
1569
+ var modalOpenedDeferred = $q.defer();
1570
+
1571
+ //prepare an instance of a modal to be injected into controllers and returned to a caller
1572
+ var modalInstance = {
1573
+ result: modalResultDeferred.promise,
1574
+ opened: modalOpenedDeferred.promise,
1575
+ close: function (result) {
1576
+ $modalStack.close(modalInstance, result);
1577
+ },
1578
+ dismiss: function (reason) {
1579
+ $modalStack.dismiss(modalInstance, reason);
1580
+ }
1581
+ };
1582
+
1583
+ //merge and clean up options
1584
+ modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
1585
+ modalOptions.resolve = modalOptions.resolve || {};
1586
+
1587
+ //verify options
1588
+ if (!modalOptions.template && !modalOptions.templateUrl) {
1589
+ throw new Error('One of template or templateUrl options is required.');
1590
+ }
1591
+
1592
+ var templateAndResolvePromise =
1593
+ $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
1594
+
1595
+
1596
+ templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
1597
+
1598
+ var modalScope = (modalOptions.scope || $rootScope).$new();
1599
+ modalScope.$close = modalInstance.close;
1600
+ modalScope.$dismiss = modalInstance.dismiss;
1601
+
1602
+ var ctrlInstance, ctrlLocals = {};
1603
+ var resolveIter = 1;
1604
+
1605
+ //controllers
1606
+ if (modalOptions.controller) {
1607
+ ctrlLocals.$scope = modalScope;
1608
+ ctrlLocals.$modalInstance = modalInstance;
1609
+ angular.forEach(modalOptions.resolve, function (value, key) {
1610
+ ctrlLocals[key] = tplAndVars[resolveIter++];
1611
+ });
1612
+
1613
+ ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
1614
+ }
1615
+
1616
+ $modalStack.open(modalInstance, {
1617
+ scope: modalScope,
1618
+ deferred: modalResultDeferred,
1619
+ content: tplAndVars[0],
1620
+ backdrop: modalOptions.backdrop,
1621
+ keyboard: modalOptions.keyboard,
1622
+ windowClass: modalOptions.windowClass
1623
+ });
1624
+
1625
+ }, function resolveError(reason) {
1626
+ modalResultDeferred.reject(reason);
1627
+ });
1628
+
1629
+ templateAndResolvePromise.then(function () {
1630
+ modalOpenedDeferred.resolve(true);
1631
+ }, function () {
1632
+ modalOpenedDeferred.reject(false);
1633
+ });
1634
+
1635
+ return modalInstance;
1636
+ };
1637
+
1638
+ return $modal;
1639
+ }]
1640
+ };
1641
+
1642
+ return $modalProvider;
1643
+ });
1643
1644
  angular.module('ui.bootstrap.pagination', [])
1644
1645
 
1645
- .controller('PaginationController', ['$scope', '$interpolate', function ($scope, $interpolate) {
1646
+ .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
1647
+ var self = this;
1646
1648
 
1647
- this.currentPage = 1;
1649
+ this.init = function(defaultItemsPerPage) {
1650
+ if ($attrs.itemsPerPage) {
1651
+ $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
1652
+ self.itemsPerPage = parseInt(value, 10);
1653
+ $scope.totalPages = self.calculateTotalPages();
1654
+ });
1655
+ } else {
1656
+ this.itemsPerPage = defaultItemsPerPage;
1657
+ }
1658
+ };
1648
1659
 
1649
1660
  this.noPrevious = function() {
1650
- return this.currentPage === 1;
1661
+ return this.page === 1;
1651
1662
  };
1652
1663
  this.noNext = function() {
1653
- return this.currentPage === $scope.numPages;
1664
+ return this.page === $scope.totalPages;
1654
1665
  };
1655
1666
 
1656
1667
  this.isActive = function(page) {
1657
- return this.currentPage === page;
1668
+ return this.page === page;
1658
1669
  };
1659
1670
 
1660
- this.reset = function() {
1661
- $scope.pages = [];
1662
- this.currentPage = parseInt($scope.currentPage, 10);
1671
+ this.calculateTotalPages = function() {
1672
+ return this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
1673
+ };
1663
1674
 
1664
- if ( this.currentPage > $scope.numPages ) {
1665
- $scope.selectPage($scope.numPages);
1666
- }
1675
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1676
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1677
+ };
1678
+
1679
+ this.render = function() {
1680
+ this.page = parseInt($scope.page, 10) || 1;
1681
+ $scope.pages = this.getPages(this.page, $scope.totalPages);
1667
1682
  };
1668
1683
 
1669
- var self = this;
1670
1684
  $scope.selectPage = function(page) {
1671
- if ( ! self.isActive(page) && page > 0 && page <= $scope.numPages) {
1672
- $scope.currentPage = page;
1685
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
1686
+ $scope.page = page;
1673
1687
  $scope.onSelectPage({ page: page });
1674
1688
  }
1675
1689
  };
1676
1690
 
1677
- this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1678
- return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1679
- };
1691
+ $scope.$watch('totalItems', function() {
1692
+ $scope.totalPages = self.calculateTotalPages();
1693
+ });
1694
+
1695
+ $scope.$watch('totalPages', function(value) {
1696
+ if ( $attrs.numPages ) {
1697
+ $scope.numPages = value; // Readonly variable
1698
+ }
1699
+
1700
+ if ( self.page > value ) {
1701
+ $scope.selectPage(value);
1702
+ } else {
1703
+ self.render();
1704
+ }
1705
+ });
1706
+
1707
+ $scope.$watch('page', function() {
1708
+ self.render();
1709
+ });
1680
1710
  }])
1681
1711
 
1682
1712
  .constant('paginationConfig', {
1713
+ itemsPerPage: 10,
1683
1714
  boundaryLinks: false,
1684
1715
  directionLinks: true,
1685
1716
  firstText: 'First',
@@ -1689,14 +1720,14 @@ angular.module('ui.bootstrap.pagination', [])
1689
1720
  rotate: true
1690
1721
  })
1691
1722
 
1692
- .directive('pagination', ['paginationConfig', function(config) {
1723
+ .directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
1693
1724
  return {
1694
1725
  restrict: 'EA',
1695
1726
  scope: {
1696
- numPages: '=',
1697
- currentPage: '=',
1698
- maxSize: '=',
1699
- onSelectPage: '&'
1727
+ page: '=',
1728
+ totalItems: '=',
1729
+ onSelectPage:' &',
1730
+ numPages: '='
1700
1731
  },
1701
1732
  controller: 'PaginationController',
1702
1733
  templateUrl: 'template/pagination/pagination.html',
@@ -1704,13 +1735,23 @@ angular.module('ui.bootstrap.pagination', [])
1704
1735
  link: function(scope, element, attrs, paginationCtrl) {
1705
1736
 
1706
1737
  // Setup configuration parameters
1707
- var boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1708
- directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1709
- firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1710
- previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1711
- nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1712
- lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1713
- rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
1738
+ var maxSize,
1739
+ boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1740
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1741
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1742
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1743
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1744
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1745
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
1746
+
1747
+ paginationCtrl.init(config.itemsPerPage);
1748
+
1749
+ if (attrs.maxSize) {
1750
+ scope.$parent.$watch($parse(attrs.maxSize), function(value) {
1751
+ maxSize = parseInt(value, 10);
1752
+ paginationCtrl.render();
1753
+ });
1754
+ }
1714
1755
 
1715
1756
  // Create page object used in template
1716
1757
  function makePage(number, text, isActive, isDisabled) {
@@ -1722,76 +1763,79 @@ angular.module('ui.bootstrap.pagination', [])
1722
1763
  };
1723
1764
  }
1724
1765
 
1725
- scope.$watch('numPages + currentPage + maxSize', function() {
1726
- paginationCtrl.reset();
1766
+ paginationCtrl.getPages = function(currentPage, totalPages) {
1767
+ var pages = [];
1727
1768
 
1728
1769
  // Default page limits
1729
- var startPage = 1, endPage = scope.numPages;
1730
- var isMaxSized = ( angular.isDefined(scope.maxSize) && scope.maxSize < scope.numPages );
1770
+ var startPage = 1, endPage = totalPages;
1771
+ var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
1731
1772
 
1732
1773
  // recompute if maxSize
1733
1774
  if ( isMaxSized ) {
1734
1775
  if ( rotate ) {
1735
1776
  // Current page is displayed in the middle of the visible ones
1736
- startPage = Math.max(paginationCtrl.currentPage - Math.floor(scope.maxSize/2), 1);
1737
- endPage = startPage + scope.maxSize - 1;
1777
+ startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
1778
+ endPage = startPage + maxSize - 1;
1738
1779
 
1739
1780
  // Adjust if limit is exceeded
1740
- if (endPage > scope.numPages) {
1741
- endPage = scope.numPages;
1742
- startPage = endPage - scope.maxSize + 1;
1781
+ if (endPage > totalPages) {
1782
+ endPage = totalPages;
1783
+ startPage = endPage - maxSize + 1;
1743
1784
  }
1744
1785
  } else {
1745
1786
  // Visible pages are paginated with maxSize
1746
- startPage = ((Math.ceil(paginationCtrl.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
1787
+ startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
1747
1788
 
1748
1789
  // Adjust last page if limit is exceeded
1749
- endPage = Math.min(startPage + scope.maxSize - 1, scope.numPages);
1790
+ endPage = Math.min(startPage + maxSize - 1, totalPages);
1750
1791
  }
1751
1792
  }
1752
1793
 
1753
1794
  // Add page number links
1754
1795
  for (var number = startPage; number <= endPage; number++) {
1755
1796
  var page = makePage(number, number, paginationCtrl.isActive(number), false);
1756
- scope.pages.push(page);
1797
+ pages.push(page);
1757
1798
  }
1758
1799
 
1759
1800
  // Add links to move between page sets
1760
1801
  if ( isMaxSized && ! rotate ) {
1761
1802
  if ( startPage > 1 ) {
1762
1803
  var previousPageSet = makePage(startPage - 1, '...', false, false);
1763
- scope.pages.unshift(previousPageSet);
1804
+ pages.unshift(previousPageSet);
1764
1805
  }
1765
1806
 
1766
- if ( endPage < scope.numPages ) {
1807
+ if ( endPage < totalPages ) {
1767
1808
  var nextPageSet = makePage(endPage + 1, '...', false, false);
1768
- scope.pages.push(nextPageSet);
1809
+ pages.push(nextPageSet);
1769
1810
  }
1770
1811
  }
1771
1812
 
1772
1813
  // Add previous & next links
1773
1814
  if (directionLinks) {
1774
- var previousPage = makePage(paginationCtrl.currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1775
- scope.pages.unshift(previousPage);
1815
+ var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1816
+ pages.unshift(previousPage);
1776
1817
 
1777
- var nextPage = makePage(paginationCtrl.currentPage + 1, nextText, false, paginationCtrl.noNext());
1778
- scope.pages.push(nextPage);
1818
+ var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
1819
+ pages.push(nextPage);
1779
1820
  }
1780
1821
 
1781
1822
  // Add first & last links
1782
1823
  if (boundaryLinks) {
1783
1824
  var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
1784
- scope.pages.unshift(firstPage);
1825
+ pages.unshift(firstPage);
1785
1826
 
1786
- var lastPage = makePage(scope.numPages, lastText, false, paginationCtrl.noNext());
1787
- scope.pages.push(lastPage);
1827
+ var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
1828
+ pages.push(lastPage);
1788
1829
  }
1789
- });
1830
+
1831
+ return pages;
1832
+ };
1790
1833
  }
1791
1834
  };
1792
1835
  }])
1793
1836
 
1794
1837
  .constant('pagerConfig', {
1838
+ itemsPerPage: 10,
1795
1839
  previousText: '« Previous',
1796
1840
  nextText: 'Next »',
1797
1841
  align: true
@@ -1801,9 +1845,10 @@ angular.module('ui.bootstrap.pagination', [])
1801
1845
  return {
1802
1846
  restrict: 'EA',
1803
1847
  scope: {
1804
- numPages: '=',
1805
- currentPage: '=',
1806
- onSelectPage: '&'
1848
+ page: '=',
1849
+ totalItems: '=',
1850
+ onSelectPage:' &',
1851
+ numPages: '='
1807
1852
  },
1808
1853
  controller: 'PaginationController',
1809
1854
  templateUrl: 'template/pagination/pager.html',
@@ -1815,6 +1860,8 @@ angular.module('ui.bootstrap.pagination', [])
1815
1860
  nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1816
1861
  align = paginationCtrl.getAttributeValue(attrs.align, config.align);
1817
1862
 
1863
+ paginationCtrl.init(config.itemsPerPage);
1864
+
1818
1865
  // Create page object used in template
1819
1866
  function makePage(number, text, isDisabled, isPrevious, isNext) {
1820
1867
  return {
@@ -1826,16 +1873,12 @@ angular.module('ui.bootstrap.pagination', [])
1826
1873
  };
1827
1874
  }
1828
1875
 
1829
- scope.$watch('numPages + currentPage', function() {
1830
- paginationCtrl.reset();
1831
-
1832
- // Add previous & next links
1833
- var previousPage = makePage(paginationCtrl.currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false);
1834
- scope.pages.unshift(previousPage);
1835
-
1836
- var nextPage = makePage(paginationCtrl.currentPage + 1, nextText, paginationCtrl.noNext(), false, true);
1837
- scope.pages.push(nextPage);
1838
- });
1876
+ paginationCtrl.getPages = function(currentPage) {
1877
+ return [
1878
+ makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
1879
+ makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
1880
+ ];
1881
+ };
1839
1882
  }
1840
1883
  };
1841
1884
  }]);
@@ -1845,7 +1888,7 @@ angular.module('ui.bootstrap.pagination', [])
1845
1888
  * function, placement as a function, inside, support for more triggers than
1846
1889
  * just mouse enter/leave, html tooltips, and selector delegation.
1847
1890
  */
1848
- angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1891
+ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
1849
1892
 
1850
1893
  /**
1851
1894
  * The $tooltip service creates tooltip- and popover-like directives as well as
@@ -1878,9 +1921,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1878
1921
  * $tooltipProvider.options( { placement: 'left' } );
1879
1922
  * });
1880
1923
  */
1881
- this.options = function( value ) {
1882
- angular.extend( globalOptions, value );
1883
- };
1924
+ this.options = function( value ) {
1925
+ angular.extend( globalOptions, value );
1926
+ };
1884
1927
 
1885
1928
  /**
1886
1929
  * This allows you to extend the set of trigger mappings available. E.g.:
@@ -2026,13 +2069,6 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
2026
2069
  // Calculate the tooltip's top and left coordinates to center it with
2027
2070
  // this directive.
2028
2071
  switch ( scope.tt_placement ) {
2029
- case 'mouse':
2030
- var mousePos = $position.mouse();
2031
- ttPosition = {
2032
- top: mousePos.y,
2033
- left: mousePos.x
2034
- };
2035
- break;
2036
2072
  case 'right':
2037
2073
  ttPosition = {
2038
2074
  top: position.top + position.height / 2 - ttHeight / 2,
@@ -2313,60 +2349,86 @@ angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
2313
2349
  angular.module('ui.bootstrap.rating', [])
2314
2350
 
2315
2351
  .constant('ratingConfig', {
2316
- max: 5
2352
+ max: 5,
2353
+ stateOn: null,
2354
+ stateOff: null
2317
2355
  })
2318
2356
 
2319
- .directive('rating', ['ratingConfig', '$parse', function(ratingConfig, $parse) {
2320
- return {
2321
- restrict: 'EA',
2322
- scope: {
2323
- value: '=',
2324
- onHover: '&',
2325
- onLeave: '&'
2326
- },
2327
- templateUrl: 'template/rating/rating.html',
2328
- replace: true,
2329
- link: function(scope, element, attrs) {
2357
+ .controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
2330
2358
 
2331
- var maxRange = angular.isDefined(attrs.max) ? scope.$parent.$eval(attrs.max) : ratingConfig.max;
2359
+ this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
2360
+ this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
2361
+ this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
2332
2362
 
2333
- scope.range = [];
2334
- for (var i = 1; i <= maxRange; i++) {
2335
- scope.range.push(i);
2336
- }
2363
+ this.createDefaultRange = function(len) {
2364
+ var defaultStateObject = {
2365
+ stateOn: this.stateOn,
2366
+ stateOff: this.stateOff
2367
+ };
2337
2368
 
2338
- scope.rate = function(value) {
2339
- if ( ! scope.readonly ) {
2340
- scope.value = value;
2341
- }
2342
- };
2369
+ var states = new Array(len);
2370
+ for (var i = 0; i < len; i++) {
2371
+ states[i] = defaultStateObject;
2372
+ }
2373
+ return states;
2374
+ };
2343
2375
 
2344
- scope.enter = function(value) {
2345
- if ( ! scope.readonly ) {
2346
- scope.val = value;
2347
- }
2348
- scope.onHover({value: value});
2349
- };
2376
+ this.normalizeRange = function(states) {
2377
+ for (var i = 0, n = states.length; i < n; i++) {
2378
+ states[i].stateOn = states[i].stateOn || this.stateOn;
2379
+ states[i].stateOff = states[i].stateOff || this.stateOff;
2380
+ }
2381
+ return states;
2382
+ };
2350
2383
 
2351
- scope.reset = function() {
2352
- scope.val = angular.copy(scope.value);
2353
- scope.onLeave();
2354
- };
2355
- scope.reset();
2384
+ // Get objects used in template
2385
+ $scope.range = angular.isDefined($attrs.ratingStates) ? this.normalizeRange(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createDefaultRange(this.maxRange);
2356
2386
 
2357
- scope.$watch('value', function(value) {
2358
- scope.val = value;
2359
- });
2387
+ $scope.rate = function(value) {
2388
+ if ( $scope.readonly || $scope.value === value) {
2389
+ return;
2390
+ }
2360
2391
 
2361
- scope.readonly = false;
2362
- if (attrs.readonly) {
2363
- scope.$parent.$watch($parse(attrs.readonly), function(value) {
2364
- scope.readonly = !!value;
2365
- });
2366
- }
2392
+ $scope.value = value;
2393
+ };
2394
+
2395
+ $scope.enter = function(value) {
2396
+ if ( ! $scope.readonly ) {
2397
+ $scope.val = value;
2367
2398
  }
2399
+ $scope.onHover({value: value});
2400
+ };
2401
+
2402
+ $scope.reset = function() {
2403
+ $scope.val = angular.copy($scope.value);
2404
+ $scope.onLeave();
2368
2405
  };
2369
- }]);
2406
+
2407
+ $scope.$watch('value', function(value) {
2408
+ $scope.val = value;
2409
+ });
2410
+
2411
+ $scope.readonly = false;
2412
+ if ($attrs.readonly) {
2413
+ $scope.$parent.$watch($parse($attrs.readonly), function(value) {
2414
+ $scope.readonly = !!value;
2415
+ });
2416
+ }
2417
+ }])
2418
+
2419
+ .directive('rating', function() {
2420
+ return {
2421
+ restrict: 'EA',
2422
+ scope: {
2423
+ value: '=',
2424
+ onHover: '&',
2425
+ onLeave: '&'
2426
+ },
2427
+ controller: 'RatingController',
2428
+ templateUrl: 'template/rating/rating.html',
2429
+ replace: true
2430
+ };
2431
+ });
2370
2432
 
2371
2433
  /**
2372
2434
  * @ngdoc overview
@@ -2454,7 +2516,7 @@ function TabsetCtrl($scope, $element) {
2454
2516
  templateUrl: 'template/tabs/tabset.html',
2455
2517
  compile: function(elm, attrs, transclude) {
2456
2518
  return function(scope, element, attrs, tabsetCtrl) {
2457
- scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
2519
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
2458
2520
  scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2459
2521
  scope.direction = angular.isDefined(attrs.direction) ? scope.$parent.$eval(attrs.direction) : 'top';
2460
2522
  scope.tabsAbove = (scope.direction != 'below');
@@ -2662,7 +2724,7 @@ function($parse, $http, $templateCache, $compile) {
2662
2724
  }
2663
2725
  }])
2664
2726
 
2665
- .directive('tabsetTitles', function($http) {
2727
+ .directive('tabsetTitles', ['$http', function($http) {
2666
2728
  return {
2667
2729
  restrict: 'A',
2668
2730
  require: '^tabset',
@@ -2679,22 +2741,13 @@ function($parse, $http, $templateCache, $compile) {
2679
2741
  }
2680
2742
  }
2681
2743
  };
2682
- })
2744
+ }])
2683
2745
 
2684
2746
  ;
2685
2747
 
2686
2748
 
2687
2749
  angular.module('ui.bootstrap.timepicker', [])
2688
2750
 
2689
- .filter('pad', function() {
2690
- return function(input) {
2691
- if ( angular.isDefined(input) && input.toString().length < 2 ) {
2692
- input = '0' + input;
2693
- }
2694
- return input;
2695
- };
2696
- })
2697
-
2698
2751
  .constant('timepickerConfig', {
2699
2752
  hourStep: 1,
2700
2753
  minuteStep: 1,
@@ -2704,16 +2757,18 @@ angular.module('ui.bootstrap.timepicker', [])
2704
2757
  mousewheel: true
2705
2758
  })
2706
2759
 
2707
- .directive('timepicker', ['padFilter', '$parse', 'timepickerConfig', function (padFilter, $parse, timepickerConfig) {
2760
+ .directive('timepicker', ['$parse', '$log', 'timepickerConfig', function ($parse, $log, timepickerConfig) {
2708
2761
  return {
2709
2762
  restrict: 'EA',
2710
- require:'ngModel',
2763
+ require:'?^ngModel',
2711
2764
  replace: true,
2765
+ scope: {},
2712
2766
  templateUrl: 'template/timepicker/timepicker.html',
2713
- scope: {
2714
- model: '=ngModel'
2715
- },
2716
- link: function(scope, element, attrs, ngModelCtrl) {
2767
+ link: function(scope, element, attrs, ngModel) {
2768
+ if ( !ngModel ) {
2769
+ return; // do nothing if no ng-model
2770
+ }
2771
+
2717
2772
  var selected = new Date(), meridians = timepickerConfig.meridians;
2718
2773
 
2719
2774
  var hourStep = timepickerConfig.hourStep;
@@ -2734,28 +2789,27 @@ angular.module('ui.bootstrap.timepicker', [])
2734
2789
  scope.showMeridian = timepickerConfig.showMeridian;
2735
2790
  if (attrs.showMeridian) {
2736
2791
  scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
2737
- scope.showMeridian = !! value;
2738
-
2739
- if ( ! scope.model ) {
2740
- // Reset
2741
- var dt = new Date( selected );
2742
- var hours = getScopeHours();
2743
- if (angular.isDefined( hours )) {
2744
- dt.setHours( hours );
2792
+ scope.showMeridian = !!value;
2793
+
2794
+ if ( ngModel.$error.time ) {
2795
+ // Evaluate from template
2796
+ var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
2797
+ if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
2798
+ selected.setHours( hours );
2799
+ refresh();
2745
2800
  }
2746
- scope.model = new Date( dt );
2747
2801
  } else {
2748
- refreshTemplate();
2802
+ updateTemplate();
2749
2803
  }
2750
2804
  });
2751
2805
  }
2752
2806
 
2753
2807
  // Get scope.hours in 24H mode if valid
2754
- function getScopeHours ( ) {
2808
+ function getHoursFromTemplate ( ) {
2755
2809
  var hours = parseInt( scope.hours, 10 );
2756
2810
  var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
2757
2811
  if ( !valid ) {
2758
- return;
2812
+ return undefined;
2759
2813
  }
2760
2814
 
2761
2815
  if ( scope.showMeridian ) {
@@ -2769,14 +2823,22 @@ angular.module('ui.bootstrap.timepicker', [])
2769
2823
  return hours;
2770
2824
  }
2771
2825
 
2826
+ function getMinutesFromTemplate() {
2827
+ var minutes = parseInt(scope.minutes, 10);
2828
+ return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
2829
+ }
2830
+
2831
+ function pad( value ) {
2832
+ return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
2833
+ }
2834
+
2772
2835
  // Input elements
2773
- var inputs = element.find('input');
2774
- var hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
2836
+ var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
2775
2837
 
2776
2838
  // Respond on mousewheel spin
2777
2839
  var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
2778
2840
  if ( mousewheel ) {
2779
-
2841
+
2780
2842
  var isScrollingUp = function(e) {
2781
2843
  if (e.originalEvent) {
2782
2844
  e = e.originalEvent;
@@ -2785,7 +2847,7 @@ angular.module('ui.bootstrap.timepicker', [])
2785
2847
  var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
2786
2848
  return (e.detail || delta > 0);
2787
2849
  };
2788
-
2850
+
2789
2851
  hoursInputEl.bind('mousewheel wheel', function(e) {
2790
2852
  scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
2791
2853
  e.preventDefault();
@@ -2797,50 +2859,54 @@ angular.module('ui.bootstrap.timepicker', [])
2797
2859
  });
2798
2860
  }
2799
2861
 
2800
- var keyboardChange = false;
2801
2862
  scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
2802
2863
  if ( ! scope.readonlyInput ) {
2864
+
2865
+ var invalidate = function(invalidHours, invalidMinutes) {
2866
+ ngModel.$setViewValue( null );
2867
+ ngModel.$setValidity('time', false);
2868
+ if (angular.isDefined(invalidHours)) {
2869
+ scope.invalidHours = invalidHours;
2870
+ }
2871
+ if (angular.isDefined(invalidMinutes)) {
2872
+ scope.invalidMinutes = invalidMinutes;
2873
+ }
2874
+ };
2875
+
2803
2876
  scope.updateHours = function() {
2804
- var hours = getScopeHours();
2877
+ var hours = getHoursFromTemplate();
2805
2878
 
2806
2879
  if ( angular.isDefined(hours) ) {
2807
- keyboardChange = 'h';
2808
- if ( scope.model === null ) {
2809
- scope.model = new Date( selected );
2810
- }
2811
- scope.model.setHours( hours );
2880
+ selected.setHours( hours );
2881
+ refresh( 'h' );
2812
2882
  } else {
2813
- scope.model = null;
2814
- scope.validHours = false;
2883
+ invalidate(true);
2815
2884
  }
2816
2885
  };
2817
2886
 
2818
2887
  hoursInputEl.bind('blur', function(e) {
2819
- if ( scope.validHours && scope.hours < 10) {
2888
+ if ( !scope.validHours && scope.hours < 10) {
2820
2889
  scope.$apply( function() {
2821
- scope.hours = padFilter( scope.hours );
2890
+ scope.hours = pad( scope.hours );
2822
2891
  });
2823
2892
  }
2824
2893
  });
2825
2894
 
2826
2895
  scope.updateMinutes = function() {
2827
- var minutes = parseInt(scope.minutes, 10);
2828
- if ( minutes >= 0 && minutes < 60 ) {
2829
- keyboardChange = 'm';
2830
- if ( scope.model === null ) {
2831
- scope.model = new Date( selected );
2832
- }
2833
- scope.model.setMinutes( minutes );
2896
+ var minutes = getMinutesFromTemplate();
2897
+
2898
+ if ( angular.isDefined(minutes) ) {
2899
+ selected.setMinutes( minutes );
2900
+ refresh( 'm' );
2834
2901
  } else {
2835
- scope.model = null;
2836
- scope.validMinutes = false;
2902
+ invalidate(undefined, true);
2837
2903
  }
2838
2904
  };
2839
2905
 
2840
2906
  minutesInputEl.bind('blur', function(e) {
2841
- if ( scope.validMinutes && scope.minutes < 10 ) {
2907
+ if ( !scope.invalidMinutes && scope.minutes < 10 ) {
2842
2908
  scope.$apply( function() {
2843
- scope.minutes = padFilter( scope.minutes );
2909
+ scope.minutes = pad( scope.minutes );
2844
2910
  });
2845
2911
  }
2846
2912
  });
@@ -2849,38 +2915,49 @@ angular.module('ui.bootstrap.timepicker', [])
2849
2915
  scope.updateMinutes = angular.noop;
2850
2916
  }
2851
2917
 
2852
- scope.$watch( function getModelTimestamp() {
2853
- return +scope.model;
2854
- }, function( timestamp ) {
2855
- if ( !isNaN( timestamp ) && timestamp > 0 ) {
2856
- selected = new Date( timestamp );
2857
- refreshTemplate();
2858
- }
2859
- });
2918
+ ngModel.$render = function() {
2919
+ var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
2860
2920
 
2861
- function refreshTemplate() {
2862
- var hours = selected.getHours();
2863
- if ( scope.showMeridian ) {
2864
- // Convert 24 to 12 hour system
2865
- hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12;
2921
+ if ( isNaN(date) ) {
2922
+ ngModel.$setValidity('time', false);
2923
+ $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
2924
+ } else {
2925
+ if ( date ) {
2926
+ selected = date;
2927
+ }
2928
+ makeValid();
2929
+ updateTemplate();
2866
2930
  }
2867
- scope.hours = ( keyboardChange === 'h' ) ? hours : padFilter(hours);
2868
- scope.validHours = true;
2931
+ };
2932
+
2933
+ // Call internally when we know that model is valid.
2934
+ function refresh( keyboardChange ) {
2935
+ makeValid();
2936
+ ngModel.$setViewValue( new Date(selected) );
2937
+ updateTemplate( keyboardChange );
2938
+ }
2869
2939
 
2870
- var minutes = selected.getMinutes();
2871
- scope.minutes = ( keyboardChange === 'm' ) ? minutes : padFilter(minutes);
2872
- scope.validMinutes = true;
2940
+ function makeValid() {
2941
+ ngModel.$setValidity('time', true);
2942
+ scope.invalidHours = false;
2943
+ scope.invalidMinutes = false;
2944
+ }
2873
2945
 
2874
- scope.meridian = ( scope.showMeridian ) ? (( selected.getHours() < 12 ) ? meridians[0] : meridians[1]) : '';
2946
+ function updateTemplate( keyboardChange ) {
2947
+ var hours = selected.getHours(), minutes = selected.getMinutes();
2875
2948
 
2876
- keyboardChange = false;
2949
+ if ( scope.showMeridian ) {
2950
+ hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
2951
+ }
2952
+ scope.hours = keyboardChange === 'h' ? hours : pad(hours);
2953
+ scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
2954
+ scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
2877
2955
  }
2878
2956
 
2879
2957
  function addMinutes( minutes ) {
2880
2958
  var dt = new Date( selected.getTime() + minutes * 60000 );
2881
- selected.setHours( dt.getHours() );
2882
- selected.setMinutes( dt.getMinutes() );
2883
- scope.model = new Date( selected );
2959
+ selected.setHours( dt.getHours(), dt.getMinutes() );
2960
+ refresh();
2884
2961
  }
2885
2962
 
2886
2963
  scope.incrementHours = function() {
@@ -2902,7 +2979,7 @@ angular.module('ui.bootstrap.timepicker', [])
2902
2979
  };
2903
2980
  }]);
2904
2981
 
2905
- angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2982
+ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
2906
2983
 
2907
2984
  /**
2908
2985
  * A helper service that can parse typeahead's syntax (string provided by users)
@@ -2933,7 +3010,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2933
3010
  };
2934
3011
  }])
2935
3012
 
2936
- .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
3013
+ .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
3014
+ function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
2937
3015
 
2938
3016
  var HOT_KEYS = [9, 13, 27, 38, 40];
2939
3017
 
@@ -3046,7 +3124,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3046
3124
 
3047
3125
  //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
3048
3126
  //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
3049
- modelCtrl.$parsers.push(function (inputValue) {
3127
+ modelCtrl.$parsers.unshift(function (inputValue) {
3050
3128
 
3051
3129
  resetMatches();
3052
3130
  if (inputValue && inputValue.length >= minSearch) {
@@ -3062,7 +3140,12 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3062
3140
  }
3063
3141
  }
3064
3142
 
3065
- return isEditable ? inputValue : undefined;
3143
+ if (isEditable) {
3144
+ return inputValue;
3145
+ } else {
3146
+ modelCtrl.$setValidity('editable', false);
3147
+ return undefined;
3148
+ }
3066
3149
  });
3067
3150
 
3068
3151
  modelCtrl.$formatters.push(function (modelValue) {
@@ -3076,12 +3159,13 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3076
3159
  return inputFormatter(originalScope, locals);
3077
3160
 
3078
3161
  } else {
3079
- locals[parserResult.itemName] = modelValue;
3080
3162
 
3081
3163
  //it might happen that we don't have enough info to properly render input value
3082
3164
  //we need to check for this situation and simply return model value if we can't apply custom formatting
3165
+ locals[parserResult.itemName] = modelValue;
3083
3166
  candidateViewValue = parserResult.viewMapper(originalScope, locals);
3084
- emptyViewValue = parserResult.viewMapper(originalScope, {});
3167
+ locals[parserResult.itemName] = undefined;
3168
+ emptyViewValue = parserResult.viewMapper(originalScope, locals);
3085
3169
 
3086
3170
  return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
3087
3171
  }
@@ -3095,6 +3179,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3095
3179
  locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
3096
3180
  model = parserResult.modelMapper(originalScope, locals);
3097
3181
  $setModelValue(originalScope, model);
3182
+ modelCtrl.$setValidity('editable', true);
3098
3183
 
3099
3184
  onSelectCallback(originalScope, {
3100
3185
  $item: item,
@@ -3102,8 +3187,9 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3102
3187
  $label: parserResult.viewMapper(originalScope, locals)
3103
3188
  });
3104
3189
 
3105
- //return focus to the input element if a mach was selected via a mouse click event
3106
3190
  resetMatches();
3191
+
3192
+ //return focus to the input element if a mach was selected via a mouse click event
3107
3193
  element[0].focus();
3108
3194
  };
3109
3195
 
@@ -3138,9 +3224,18 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3138
3224
  }
3139
3225
  });
3140
3226
 
3141
- $document.bind('click', function(){
3142
- resetMatches();
3143
- scope.$digest();
3227
+ // Keep reference to click handler to unbind it.
3228
+ var dismissClickHandler = function (evt) {
3229
+ if (element[0] !== evt.target) {
3230
+ resetMatches();
3231
+ scope.$digest();
3232
+ }
3233
+ };
3234
+
3235
+ $document.bind('click', dismissClickHandler);
3236
+
3237
+ originalScope.$on('$destroy', function(){
3238
+ $document.unbind('click', dismissClickHandler);
3144
3239
  });
3145
3240
 
3146
3241
  element.after($compile(popUpEl)(scope));
@@ -3208,6 +3303,6 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
3208
3303
  }
3209
3304
 
3210
3305
  return function(matchItem, query) {
3211
- return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : query;
3306
+ return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3212
3307
  };
3213
3308
  });