angular-ui-bootstrap-rails 0.5.0.0 → 0.6.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  });