angular-ui-bootstrap-rails 0.4.0.0 → 0.5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 228e8d4dcf89892df3b3f8b49e4ec1318dc9162f
4
- data.tar.gz: 4b9fe4ac8c6b2bf0a1d871c090ff407995920285
3
+ metadata.gz: 41a0eb3d7f6aa58214fe4456e6d59f881acd73e6
4
+ data.tar.gz: 5ce4e249235df1ccd64d828b214ee2acf9b123ac
5
5
  SHA512:
6
- metadata.gz: e9ef81d74df63d12b613cf78355298503c0655f0021fc40e7915f653fcad1b0d6912918122db9a58b31a70f32237cec4caab15f264d805c01db37c1db04a437b
7
- data.tar.gz: 85dc8e5ea4a12e094b26f4172d88da1b9f43b81920b12274ad04722630c4e7a5eedcea286f2d36f47285b047e2b5b21dae9fc2ba0bf58288a0b32240e396849d
6
+ metadata.gz: c1ecdcb8ea3ddb278edffd213e8befdbeafe6c114b8d1e3a33d8d0cd80f3bb9ee9a07ebab0f84072e887989bcf34bf562d6180ba6d508c2d08841edddb42eed5
7
+ data.tar.gz: 81b75e66f470254abae8c087b6a8fac53531cd2f9129a76977ec311941e86c386a459d63b03906dda4da0b3f9b5f61a6ebc575a3802b187f29dba9644189e2b7
@@ -1,7 +1,7 @@
1
1
  module AngularUI
2
2
  module Bootstrap
3
3
  module Rails
4
- VERSION = "0.4.0.0"
4
+ VERSION = "0.5.0.0"
5
5
  end
6
6
  end
7
7
  end
@@ -1,5 +1,5 @@
1
- angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.position","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
2
- angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/dialog/message.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead.html"]);
1
+ angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "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"]);
2
+ angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/dialog/message.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset-titles.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]);
3
3
  angular.module('ui.bootstrap.transition', [])
4
4
 
5
5
  /**
@@ -380,21 +380,25 @@ angular.module('ui.bootstrap.buttons', [])
380
380
  require:'ngModel',
381
381
  link:function (scope, element, attrs, ngModelCtrl) {
382
382
 
383
- var trueValue = scope.$eval(attrs.btnCheckboxTrue);
384
- var falseValue = scope.$eval(attrs.btnCheckboxFalse);
383
+ function getTrueValue() {
384
+ var trueValue = scope.$eval(attrs.btnCheckboxTrue);
385
+ return angular.isDefined(trueValue) ? trueValue : true;
386
+ }
385
387
 
386
- trueValue = angular.isDefined(trueValue) ? trueValue : true;
387
- falseValue = angular.isDefined(falseValue) ? falseValue : false;
388
+ function getFalseValue() {
389
+ var falseValue = scope.$eval(attrs.btnCheckboxFalse);
390
+ return angular.isDefined(falseValue) ? falseValue : false;
391
+ }
388
392
 
389
393
  //model -> UI
390
394
  ngModelCtrl.$render = function () {
391
- element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, trueValue));
395
+ element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
392
396
  };
393
397
 
394
398
  //ui->model
395
399
  element.bind(toggleEvent, function () {
396
400
  scope.$apply(function () {
397
- ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue);
401
+ ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? getFalseValue() : getTrueValue());
398
402
  ngModelCtrl.$render();
399
403
  });
400
404
  });
@@ -733,7 +737,101 @@ function CarouselDemoCtrl($scope) {
733
737
  };
734
738
  }]);
735
739
 
736
- angular.module('ui.bootstrap.datepicker', [])
740
+ angular.module('ui.bootstrap.position', [])
741
+
742
+ /**
743
+ * A set of utility methods that can be use to retrieve position of DOM elements.
744
+ * It is meant to be used where we need to absolute-position DOM elements in
745
+ * relation to other, existing elements (this is the case for tooltips, popovers,
746
+ * typeahead suggestions etc.).
747
+ */
748
+ .factory('$position', ['$document', '$window', function ($document, $window) {
749
+
750
+ var mouseX, mouseY;
751
+
752
+ $document.bind('mousemove', function mouseMoved(event) {
753
+ mouseX = event.pageX;
754
+ mouseY = event.pageY;
755
+ });
756
+
757
+ function getStyle(el, cssprop) {
758
+ if (el.currentStyle) { //IE
759
+ return el.currentStyle[cssprop];
760
+ } else if ($window.getComputedStyle) {
761
+ return $window.getComputedStyle(el)[cssprop];
762
+ }
763
+ // finally try and get inline style
764
+ return el.style[cssprop];
765
+ }
766
+
767
+ /**
768
+ * Checks if a given element is statically positioned
769
+ * @param element - raw DOM element
770
+ */
771
+ function isStaticPositioned(element) {
772
+ return (getStyle(element, "position") || 'static' ) === 'static';
773
+ }
774
+
775
+ /**
776
+ * returns the closest, non-statically positioned parentOffset of a given element
777
+ * @param element
778
+ */
779
+ var parentOffsetEl = function (element) {
780
+ var docDomEl = $document[0];
781
+ var offsetParent = element.offsetParent || docDomEl;
782
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
783
+ offsetParent = offsetParent.offsetParent;
784
+ }
785
+ return offsetParent || docDomEl;
786
+ };
787
+
788
+ return {
789
+ /**
790
+ * Provides read-only equivalent of jQuery's position function:
791
+ * http://api.jquery.com/position/
792
+ */
793
+ position: function (element) {
794
+ var elBCR = this.offset(element);
795
+ var offsetParentBCR = { top: 0, left: 0 };
796
+ var offsetParentEl = parentOffsetEl(element[0]);
797
+ if (offsetParentEl != $document[0]) {
798
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
799
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
800
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
801
+ }
802
+
803
+ return {
804
+ width: element.prop('offsetWidth'),
805
+ height: element.prop('offsetHeight'),
806
+ top: elBCR.top - offsetParentBCR.top,
807
+ left: elBCR.left - offsetParentBCR.left
808
+ };
809
+ },
810
+
811
+ /**
812
+ * Provides read-only equivalent of jQuery's offset function:
813
+ * http://api.jquery.com/offset/
814
+ */
815
+ offset: function (element) {
816
+ var boundingClientRect = element[0].getBoundingClientRect();
817
+ return {
818
+ width: element.prop('offsetWidth'),
819
+ height: element.prop('offsetHeight'),
820
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
821
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
822
+ };
823
+ },
824
+
825
+ /**
826
+ * Provides the coordinates of the mouse
827
+ */
828
+ mouse: function () {
829
+ return {x: mouseX, y: mouseY};
830
+ }
831
+ };
832
+ }]);
833
+
834
+ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
737
835
 
738
836
  .constant('datepickerConfig', {
739
837
  dayFormat: 'dd',
@@ -744,31 +842,139 @@ angular.module('ui.bootstrap.datepicker', [])
744
842
  monthTitleFormat: 'yyyy',
745
843
  showWeeks: true,
746
844
  startingDay: 0,
747
- yearRange: 20
845
+ yearRange: 20,
846
+ minDate: null,
847
+ maxDate: null
748
848
  })
749
849
 
750
- .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', function (dateFilter, $parse, datepickerConfig) {
850
+ .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
851
+ var format = {
852
+ day: getValue($attrs.dayFormat, dtConfig.dayFormat),
853
+ month: getValue($attrs.monthFormat, dtConfig.monthFormat),
854
+ year: getValue($attrs.yearFormat, dtConfig.yearFormat),
855
+ dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
856
+ dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
857
+ monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
858
+ },
859
+ startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
860
+ yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
861
+
862
+ this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
863
+ this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
864
+
865
+ function getValue(value, defaultValue) {
866
+ return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
867
+ }
868
+
869
+ function getDaysInMonth( year, month ) {
870
+ return new Date(year, month, 0).getDate();
871
+ }
872
+
873
+ function getDates(startDate, n) {
874
+ var dates = new Array(n);
875
+ var current = startDate, i = 0;
876
+ while (i < n) {
877
+ dates[i++] = new Date(current);
878
+ current.setDate( current.getDate() + 1 );
879
+ }
880
+ return dates;
881
+ }
882
+
883
+ function makeDate(date, format, isSelected, isSecondary) {
884
+ return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
885
+ }
886
+
887
+ this.modes = [
888
+ {
889
+ name: 'day',
890
+ getVisibleDates: function(date, selected) {
891
+ var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
892
+ var difference = startingDay - firstDayOfMonth.getDay(),
893
+ numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
894
+ firstDate = new Date(firstDayOfMonth), numDates = 0;
895
+
896
+ if ( numDisplayedFromPreviousMonth > 0 ) {
897
+ firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
898
+ numDates += numDisplayedFromPreviousMonth; // Previous
899
+ }
900
+ numDates += getDaysInMonth(year, month + 1); // Current
901
+ numDates += (7 - numDates % 7) % 7; // Next
902
+
903
+ var days = getDates(firstDate, numDates), labels = new Array(7);
904
+ for (var i = 0; i < numDates; i ++) {
905
+ var dt = new Date(days[i]);
906
+ days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
907
+ }
908
+ for (var j = 0; j < 7; j++) {
909
+ labels[j] = dateFilter(days[j].date, format.dayHeader);
910
+ }
911
+ return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
912
+ },
913
+ compare: function(date1, date2) {
914
+ return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
915
+ },
916
+ split: 7,
917
+ step: { months: 1 }
918
+ },
919
+ {
920
+ name: 'month',
921
+ getVisibleDates: function(date, selected) {
922
+ var months = new Array(12), year = date.getFullYear();
923
+ for ( var i = 0; i < 12; i++ ) {
924
+ var dt = new Date(year, i, 1);
925
+ months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
926
+ }
927
+ return { objects: months, title: dateFilter(date, format.monthTitle) };
928
+ },
929
+ compare: function(date1, date2) {
930
+ return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
931
+ },
932
+ split: 3,
933
+ step: { years: 1 }
934
+ },
935
+ {
936
+ name: 'year',
937
+ getVisibleDates: function(date, selected) {
938
+ var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
939
+ for ( var i = 0; i < yearRange; i++ ) {
940
+ var dt = new Date(startYear + i, 0, 1);
941
+ years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
942
+ }
943
+ return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
944
+ },
945
+ compare: function(date1, date2) {
946
+ return date1.getFullYear() - date2.getFullYear();
947
+ },
948
+ split: 5,
949
+ step: { years: yearRange }
950
+ }
951
+ ];
952
+
953
+ this.isDisabled = function(date, mode) {
954
+ var currentMode = this.modes[mode || 0];
955
+ return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
956
+ };
957
+ }])
958
+
959
+ .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
751
960
  return {
752
961
  restrict: 'EA',
753
962
  replace: true,
963
+ templateUrl: 'template/datepicker/datepicker.html',
754
964
  scope: {
755
- model: '=ngModel',
756
965
  dateDisabled: '&'
757
966
  },
758
- templateUrl: 'template/datepicker/datepicker.html',
759
- link: function(scope, element, attrs) {
760
- scope.mode = 'day'; // Initial mode
967
+ require: ['datepicker', '?^ngModel'],
968
+ controller: 'DatepickerController',
969
+ link: function(scope, element, attrs, ctrls) {
970
+ var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
971
+
972
+ if (!ngModel) {
973
+ return; // do nothing if no ng-model
974
+ }
761
975
 
762
976
  // Configuration parameters
763
- var selected = new Date(), showWeeks, minDate, maxDate, format = {};
764
- format.day = angular.isDefined(attrs.dayFormat) ? scope.$eval(attrs.dayFormat) : datepickerConfig.dayFormat;
765
- format.month = angular.isDefined(attrs.monthFormat) ? scope.$eval(attrs.monthFormat) : datepickerConfig.monthFormat;
766
- format.year = angular.isDefined(attrs.yearFormat) ? scope.$eval(attrs.yearFormat) : datepickerConfig.yearFormat;
767
- format.dayHeader = angular.isDefined(attrs.dayHeaderFormat) ? scope.$eval(attrs.dayHeaderFormat) : datepickerConfig.dayHeaderFormat;
768
- format.dayTitle = angular.isDefined(attrs.dayTitleFormat) ? scope.$eval(attrs.dayTitleFormat) : datepickerConfig.dayTitleFormat;
769
- format.monthTitle = angular.isDefined(attrs.monthTitleFormat) ? scope.$eval(attrs.monthTitleFormat) : datepickerConfig.monthTitleFormat;
770
- var startingDay = angular.isDefined(attrs.startingDay) ? scope.$eval(attrs.startingDay) : datepickerConfig.startingDay;
771
- var yearRange = angular.isDefined(attrs.yearRange) ? scope.$eval(attrs.yearRange) : datepickerConfig.yearRange;
977
+ var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
772
978
 
773
979
  if (attrs.showWeeks) {
774
980
  scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
@@ -776,174 +982,282 @@ angular.module('ui.bootstrap.datepicker', [])
776
982
  updateShowWeekNumbers();
777
983
  });
778
984
  } else {
779
- showWeeks = datepickerConfig.showWeeks;
780
985
  updateShowWeekNumbers();
781
986
  }
782
987
 
783
988
  if (attrs.min) {
784
989
  scope.$parent.$watch($parse(attrs.min), function(value) {
785
- minDate = new Date(value);
990
+ datepickerCtrl.minDate = value ? new Date(value) : null;
786
991
  refill();
787
992
  });
788
993
  }
789
994
  if (attrs.max) {
790
995
  scope.$parent.$watch($parse(attrs.max), function(value) {
791
- maxDate = new Date(value);
996
+ datepickerCtrl.maxDate = value ? new Date(value) : null;
792
997
  refill();
793
998
  });
794
999
  }
795
1000
 
796
- function updateCalendar (rows, labels, title) {
797
- scope.rows = rows;
798
- scope.labels = labels;
799
- scope.title = title;
1001
+ function updateShowWeekNumbers() {
1002
+ scope.showWeekNumbers = mode === 0 && showWeeks;
800
1003
  }
801
1004
 
802
- // Define whether the week number are visible
803
- function updateShowWeekNumbers() {
804
- scope.showWeekNumbers = ( scope.mode === 'day' && showWeeks );
1005
+ // Split array into smaller arrays
1006
+ function split(arr, size) {
1007
+ var arrays = [];
1008
+ while (arr.length > 0) {
1009
+ arrays.push(arr.splice(0, size));
1010
+ }
1011
+ return arrays;
805
1012
  }
806
1013
 
807
- function compare( date1, date2 ) {
808
- if ( scope.mode === 'year') {
809
- return date2.getFullYear() - date1.getFullYear();
810
- } else if ( scope.mode === 'month' ) {
811
- return new Date( date2.getFullYear(), date2.getMonth() ) - new Date( date1.getFullYear(), date1.getMonth() );
812
- } else if ( scope.mode === 'day' ) {
813
- return (new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) - new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) );
1014
+ function refill( updateSelected ) {
1015
+ var date = null, valid = true;
1016
+
1017
+ if ( ngModel.$modelValue ) {
1018
+ date = new Date( ngModel.$modelValue );
1019
+
1020
+ if ( isNaN(date) ) {
1021
+ valid = false;
1022
+ $log.error('Datepicker 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.');
1023
+ } else if ( updateSelected ) {
1024
+ selected = date;
1025
+ }
814
1026
  }
1027
+ ngModel.$setValidity('date', valid);
1028
+
1029
+ var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
1030
+ angular.forEach(data.objects, function(obj) {
1031
+ obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
1032
+ });
1033
+
1034
+ ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
1035
+
1036
+ scope.rows = split(data.objects, currentMode.split);
1037
+ scope.labels = data.labels || [];
1038
+ scope.title = data.title;
815
1039
  }
816
1040
 
817
- function isDisabled(date) {
818
- return ((minDate && compare(date, minDate) > 0) || (maxDate && compare(date, maxDate) < 0) || (scope.dateDisabled && scope.dateDisabled({ date: date, mode: scope.mode })));
1041
+ function setMode(value) {
1042
+ mode = value;
1043
+ updateShowWeekNumbers();
1044
+ refill();
819
1045
  }
820
1046
 
821
- // Split array into smaller arrays
822
- var split = function(a, size) {
823
- var arrays = [];
824
- while (a.length > 0) {
825
- arrays.push(a.splice(0, size));
1047
+ ngModel.$render = function() {
1048
+ refill( true );
1049
+ };
1050
+
1051
+ scope.select = function( date ) {
1052
+ if ( mode === 0 ) {
1053
+ var dt = new Date( ngModel.$modelValue );
1054
+ dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1055
+ ngModel.$setViewValue( dt );
1056
+ refill( true );
1057
+ } else {
1058
+ selected = date;
1059
+ setMode( mode - 1 );
826
1060
  }
827
- return arrays;
828
1061
  };
829
- var getDaysInMonth = function( year, month ) {
830
- return new Date(year, month + 1, 0).getDate();
1062
+ scope.move = function(direction) {
1063
+ var step = datepickerCtrl.modes[mode].step;
1064
+ selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
1065
+ selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
1066
+ refill();
1067
+ };
1068
+ scope.toggleMode = function() {
1069
+ setMode( (mode + 1) % datepickerCtrl.modes.length );
1070
+ };
1071
+ scope.getWeekNumber = function(row) {
1072
+ return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
831
1073
  };
832
1074
 
833
- var fill = {
834
- day: function() {
835
- var days = [], labels = [], lastDate = null;
1075
+ function getISO8601WeekNumber(date) {
1076
+ var checkDate = new Date(date);
1077
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1078
+ var time = checkDate.getTime();
1079
+ checkDate.setMonth(0); // Compare with Jan 1
1080
+ checkDate.setDate(1);
1081
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1082
+ }
1083
+ }
1084
+ };
1085
+ }])
836
1086
 
837
- function addDays( dt, n, isCurrentMonth ) {
838
- for (var i =0; i < n; i ++) {
839
- days.push( {date: new Date(dt), isCurrent: isCurrentMonth, isSelected: isSelected(dt), label: dateFilter(dt, format.day), disabled: isDisabled(dt) } );
840
- dt.setDate( dt.getDate() + 1 );
841
- }
842
- lastDate = dt;
843
- }
1087
+ .constant('datepickerPopupConfig', {
1088
+ dateFormat: 'yyyy-MM-dd',
1089
+ closeOnDateSelection: true
1090
+ })
844
1091
 
845
- var d = new Date(selected);
846
- d.setDate(1);
1092
+ .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig',
1093
+ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig) {
1094
+ return {
1095
+ restrict: 'EA',
1096
+ require: 'ngModel',
1097
+ link: function(originalScope, element, attrs, ngModel) {
847
1098
 
848
- var difference = startingDay - d.getDay();
849
- var numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference;
1099
+ var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
1100
+ var dateFormat = attrs.datepickerPopup || datepickerPopupConfig.dateFormat;
850
1101
 
851
- if ( numDisplayedFromPreviousMonth > 0 ) {
852
- d.setDate( - numDisplayedFromPreviousMonth + 1 );
853
- addDays(d, numDisplayedFromPreviousMonth, false);
854
- }
855
- addDays(lastDate || d, getDaysInMonth(selected.getFullYear(), selected.getMonth()), true);
856
- addDays(lastDate, (7 - days.length % 7) % 7, false);
1102
+ // create a child scope for the datepicker directive so we are not polluting original scope
1103
+ var scope = originalScope.$new();
1104
+ originalScope.$on('$destroy', function() {
1105
+ scope.$destroy();
1106
+ });
857
1107
 
858
- // Day labels
859
- for (i = 0; i < 7; i++) {
860
- labels.push( dateFilter(days[i].date, format.dayHeader) );
861
- }
862
- updateCalendar( split( days, 7 ), labels, dateFilter(selected, format.dayTitle) );
863
- },
864
- month: function() {
865
- var months = [], i = 0, year = selected.getFullYear();
866
- while ( i < 12 ) {
867
- var dt = new Date(year, i++, 1);
868
- months.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.month), disabled: isDisabled(dt)} );
869
- }
870
- updateCalendar( split( months, 3 ), [], dateFilter(selected, format.monthTitle) );
871
- },
872
- year: function() {
873
- var years = [], year = parseInt((selected.getFullYear() - 1) / yearRange, 10) * yearRange + 1;
874
- for ( var i = 0; i < yearRange; i++ ) {
875
- var dt = new Date(year + i, 0, 1);
876
- years.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.year), disabled: isDisabled(dt)} );
1108
+ function formatDate(value) {
1109
+ return (value) ? dateFilter(value, dateFormat) : null;
1110
+ }
1111
+ ngModel.$formatters.push(formatDate);
1112
+
1113
+ // TODO: reverse from dateFilter string to Date object
1114
+ function parseDate(value) {
1115
+ if ( value ) {
1116
+ var date = new Date(value);
1117
+ if (!isNaN(date)) {
1118
+ return date;
877
1119
  }
878
- var title = years[0].label + ' - ' + years[years.length - 1].label;
879
- updateCalendar( split( years, 5 ), [], title );
1120
+ }
1121
+ return value;
1122
+ }
1123
+ ngModel.$parsers.push(parseDate);
1124
+
1125
+ var getIsOpen, setIsOpen;
1126
+ if ( attrs.open ) {
1127
+ getIsOpen = $parse(attrs.open);
1128
+ setIsOpen = getIsOpen.assign;
1129
+
1130
+ originalScope.$watch(getIsOpen, function updateOpen(value) {
1131
+ scope.isOpen = !! value;
1132
+ });
1133
+ }
1134
+ scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
1135
+
1136
+ function setOpen( value ) {
1137
+ if (setIsOpen) {
1138
+ setIsOpen(originalScope, !!value);
1139
+ } else {
1140
+ scope.isOpen = !!value;
1141
+ }
1142
+ }
1143
+
1144
+ var documentClickBind = function(event) {
1145
+ if (scope.isOpen && event.target !== element[0]) {
1146
+ scope.$apply(function() {
1147
+ setOpen(false);
1148
+ });
880
1149
  }
881
1150
  };
882
- var refill = function() {
883
- fill[scope.mode]();
1151
+
1152
+ var elementFocusBind = function() {
1153
+ scope.$apply(function() {
1154
+ setOpen( true );
1155
+ });
884
1156
  };
885
- var isSelected = function( dt ) {
886
- if ( scope.model && scope.model.getFullYear() === dt.getFullYear() ) {
887
- if ( scope.mode === 'year' ) {
888
- return true;
889
- }
890
- if ( scope.model.getMonth() === dt.getMonth() ) {
891
- return ( scope.mode === 'month' || (scope.mode === 'day' && scope.model.getDate() === dt.getDate()) );
892
- }
1157
+
1158
+ // popup element used to display calendar
1159
+ var popupEl = angular.element('<datepicker-popup-wrap><datepicker></datepicker></datepicker-popup-wrap>');
1160
+ popupEl.attr({
1161
+ 'ng-model': 'date',
1162
+ 'ng-change': 'dateSelection()'
1163
+ });
1164
+ var datepickerEl = popupEl.find('datepicker');
1165
+ if (attrs.datepickerOptions) {
1166
+ datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
1167
+ }
1168
+
1169
+ var $setModelValue = $parse(attrs.ngModel).assign;
1170
+
1171
+ // Inner change
1172
+ scope.dateSelection = function() {
1173
+ $setModelValue(originalScope, scope.date);
1174
+ if (closeOnDateSelection) {
1175
+ setOpen( false );
893
1176
  }
894
- return false;
895
1177
  };
896
1178
 
897
- scope.$watch('model', function ( dt, olddt ) {
898
- if ( angular.isDate(dt) ) {
899
- selected = angular.copy(dt);
900
- }
1179
+ // Outter change
1180
+ scope.$watch(function() {
1181
+ return ngModel.$modelValue;
1182
+ }, function(value) {
1183
+ if (angular.isString(value)) {
1184
+ var date = parseDate(value);
901
1185
 
902
- if ( ! angular.equals(dt, olddt) ) {
903
- refill();
1186
+ if (value && !date) {
1187
+ $setModelValue(originalScope, null);
1188
+ throw new Error(value + ' cannot be parsed to a date object.');
1189
+ } else {
1190
+ value = date;
1191
+ }
904
1192
  }
905
- });
906
- scope.$watch('mode', function() {
907
- updateShowWeekNumbers();
908
- refill();
1193
+ scope.date = value;
1194
+ updatePosition();
909
1195
  });
910
1196
 
911
- scope.select = function( dt ) {
912
- selected = new Date(dt);
913
-
914
- if ( scope.mode === 'year' ) {
915
- scope.mode = 'month';
916
- selected.setFullYear( dt.getFullYear() );
917
- } else if ( scope.mode === 'month' ) {
918
- scope.mode = 'day';
919
- selected.setMonth( dt.getMonth() );
920
- } else if ( scope.mode === 'day' ) {
921
- scope.model = new Date(selected);
1197
+ function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
1198
+ if (attribute) {
1199
+ originalScope.$watch($parse(attribute), function(value){
1200
+ scope[scopeProperty] = value;
1201
+ });
1202
+ datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
922
1203
  }
923
- };
924
- scope.move = function(step) {
925
- if (scope.mode === 'day') {
926
- selected.setMonth( selected.getMonth() + step );
927
- } else if (scope.mode === 'month') {
928
- selected.setFullYear( selected.getFullYear() + step );
929
- } else if (scope.mode === 'year') {
930
- selected.setFullYear( selected.getFullYear() + step * yearRange );
1204
+ }
1205
+ addWatchableAttribute(attrs.min, 'min');
1206
+ addWatchableAttribute(attrs.max, 'max');
1207
+ if (attrs.showWeeks) {
1208
+ addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
1209
+ } else {
1210
+ scope.showWeeks = true;
1211
+ datepickerEl.attr('show-weeks', 'showWeeks');
1212
+ }
1213
+ if (attrs.dateDisabled) {
1214
+ datepickerEl.attr('date-disabled', attrs.dateDisabled);
1215
+ }
1216
+
1217
+ function updatePosition() {
1218
+ scope.position = $position.position(element);
1219
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
1220
+ }
1221
+
1222
+ scope.$watch('isOpen', function(value) {
1223
+ if (value) {
1224
+ updatePosition();
1225
+ $document.bind('click', documentClickBind);
1226
+ element.unbind('focus', elementFocusBind);
1227
+ element.focus();
1228
+ } else {
1229
+ $document.unbind('click', documentClickBind);
1230
+ element.bind('focus', elementFocusBind);
931
1231
  }
932
- refill();
933
- };
934
- scope.toggleMode = function() {
935
- scope.mode = ( scope.mode === 'day' ) ? 'month' : ( scope.mode === 'month' ) ? 'year' : 'day';
936
- };
937
- scope.getWeekNumber = function(row) {
938
- if ( scope.mode !== 'day' || ! scope.showWeekNumbers || row.length !== 7 ) {
939
- return;
1232
+
1233
+ if ( setIsOpen ) {
1234
+ setIsOpen(originalScope, value);
940
1235
  }
1236
+ });
941
1237
 
942
- var index = ( startingDay > 4 ) ? 11 - startingDay : 4 - startingDay; // Thursday
943
- var d = new Date( row[ index ].date );
944
- d.setHours(0, 0, 0);
945
- return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 86400000) + 1) / 7); // 86400000 = 1000*60*60*24;
1238
+ scope.today = function() {
1239
+ $setModelValue(originalScope, new Date());
946
1240
  };
1241
+ scope.clear = function() {
1242
+ $setModelValue(originalScope, null);
1243
+ };
1244
+
1245
+ element.after($compile(popupEl)(scope));
1246
+ }
1247
+ };
1248
+ }])
1249
+
1250
+ .directive('datepickerPopupWrap', [function() {
1251
+ return {
1252
+ restrict:'E',
1253
+ replace: true,
1254
+ transclude: true,
1255
+ templateUrl: 'template/datepicker/popup.html',
1256
+ link:function (scope, element, attrs) {
1257
+ element.bind('click', function(event) {
1258
+ event.preventDefault();
1259
+ event.stopPropagation();
1260
+ });
947
1261
  }
948
1262
  };
949
1263
  }]);
@@ -975,9 +1289,9 @@ dialogModule.provider("$dialog", function(){
975
1289
  keyboard: true, // close with esc key
976
1290
  backdropClick: true // only in conjunction with backdrop=true
977
1291
  /* other options: template, templateUrl, controller */
978
- };
1292
+ };
979
1293
 
980
- var globalOptions = {};
1294
+ var globalOptions = {};
981
1295
 
982
1296
  var activeBackdrops = {value : 0};
983
1297
 
@@ -987,21 +1301,21 @@ dialogModule.provider("$dialog", function(){
987
1301
  // // don't close dialog when backdrop is clicked by default
988
1302
  // $dialogProvider.options({backdropClick: false});
989
1303
  // });
990
- this.options = function(value){
991
- globalOptions = value;
992
- };
1304
+ this.options = function(value){
1305
+ globalOptions = value;
1306
+ };
993
1307
 
994
1308
  // Returns the actual `$dialog` service that is injected in controllers
995
- this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
1309
+ this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
996
1310
  function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
997
1311
 
998
- var body = $document.find('body');
1312
+ var body = $document.find('body');
999
1313
 
1000
- function createElement(clazz) {
1001
- var el = angular.element("<div>");
1002
- el.addClass(clazz);
1003
- return el;
1004
- }
1314
+ function createElement(clazz) {
1315
+ var el = angular.element("<div>");
1316
+ el.addClass(clazz);
1317
+ return el;
1318
+ }
1005
1319
 
1006
1320
  // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
1007
1321
  // containing at lest template or templateUrl and controller:
@@ -1011,7 +1325,7 @@ dialogModule.provider("$dialog", function(){
1011
1325
  // Dialogs can also be created using templateUrl and controller as distinct arguments:
1012
1326
  //
1013
1327
  // var d = new Dialog('path/to/dialog.html', MyDialogController);
1014
- function Dialog(opts) {
1328
+ function Dialog(opts) {
1015
1329
 
1016
1330
  var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
1017
1331
  this._open = false;
@@ -1041,10 +1355,6 @@ dialogModule.provider("$dialog", function(){
1041
1355
  e.preventDefault();
1042
1356
  self.$scope.$apply();
1043
1357
  };
1044
-
1045
- this.handleLocationChange = function() {
1046
- self.close();
1047
- };
1048
1358
  }
1049
1359
 
1050
1360
  // The `isOpen()` method returns wether the dialog is currently visible.
@@ -1333,25 +1643,41 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
1333
1643
  }]);
1334
1644
  angular.module('ui.bootstrap.pagination', [])
1335
1645
 
1336
- .controller('PaginationController', ['$scope', function (scope) {
1646
+ .controller('PaginationController', ['$scope', '$interpolate', function ($scope, $interpolate) {
1647
+
1648
+ this.currentPage = 1;
1337
1649
 
1338
- scope.noPrevious = function() {
1339
- return scope.currentPage === 1;
1650
+ this.noPrevious = function() {
1651
+ return this.currentPage === 1;
1340
1652
  };
1341
- scope.noNext = function() {
1342
- return scope.currentPage === scope.numPages;
1653
+ this.noNext = function() {
1654
+ return this.currentPage === $scope.numPages;
1343
1655
  };
1344
1656
 
1345
- scope.isActive = function(page) {
1346
- return scope.currentPage === page;
1657
+ this.isActive = function(page) {
1658
+ return this.currentPage === page;
1347
1659
  };
1348
1660
 
1349
- scope.selectPage = function(page) {
1350
- if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) {
1351
- scope.currentPage = page;
1352
- scope.onSelectPage({ page: page });
1661
+ this.reset = function() {
1662
+ $scope.pages = [];
1663
+ this.currentPage = parseInt($scope.currentPage, 10);
1664
+
1665
+ if ( this.currentPage > $scope.numPages ) {
1666
+ $scope.selectPage($scope.numPages);
1667
+ }
1668
+ };
1669
+
1670
+ var self = this;
1671
+ $scope.selectPage = function(page) {
1672
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.numPages) {
1673
+ $scope.currentPage = page;
1674
+ $scope.onSelectPage({ page: page });
1353
1675
  }
1354
1676
  };
1677
+
1678
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1679
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1680
+ };
1355
1681
  }])
1356
1682
 
1357
1683
  .constant('paginationConfig', {
@@ -1364,7 +1690,7 @@ angular.module('ui.bootstrap.pagination', [])
1364
1690
  rotate: true
1365
1691
  })
1366
1692
 
1367
- .directive('pagination', ['paginationConfig', function(paginationConfig) {
1693
+ .directive('pagination', ['paginationConfig', function(config) {
1368
1694
  return {
1369
1695
  restrict: 'EA',
1370
1696
  scope: {
@@ -1376,16 +1702,16 @@ angular.module('ui.bootstrap.pagination', [])
1376
1702
  controller: 'PaginationController',
1377
1703
  templateUrl: 'template/pagination/pagination.html',
1378
1704
  replace: true,
1379
- link: function(scope, element, attrs) {
1705
+ link: function(scope, element, attrs, paginationCtrl) {
1380
1706
 
1381
1707
  // Setup configuration parameters
1382
- var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
1383
- var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
1384
- var firstText = angular.isDefined(attrs.firstText) ? scope.$parent.$eval(attrs.firstText) : paginationConfig.firstText;
1385
- var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : paginationConfig.previousText;
1386
- var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : paginationConfig.nextText;
1387
- var lastText = angular.isDefined(attrs.lastText) ? scope.$parent.$eval(attrs.lastText) : paginationConfig.lastText;
1388
- var rotate = angular.isDefined(attrs.rotate) ? scope.$eval(attrs.rotate) : paginationConfig.rotate;
1708
+ var boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1709
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1710
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1711
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1712
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1713
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1714
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
1389
1715
 
1390
1716
  // Create page object used in template
1391
1717
  function makePage(number, text, isActive, isDisabled) {
@@ -1398,8 +1724,8 @@ angular.module('ui.bootstrap.pagination', [])
1398
1724
  }
1399
1725
 
1400
1726
  scope.$watch('numPages + currentPage + maxSize', function() {
1401
- scope.pages = [];
1402
-
1727
+ paginationCtrl.reset();
1728
+
1403
1729
  // Default page limits
1404
1730
  var startPage = 1, endPage = scope.numPages;
1405
1731
  var isMaxSized = ( angular.isDefined(scope.maxSize) && scope.maxSize < scope.numPages );
@@ -1408,7 +1734,7 @@ angular.module('ui.bootstrap.pagination', [])
1408
1734
  if ( isMaxSized ) {
1409
1735
  if ( rotate ) {
1410
1736
  // Current page is displayed in the middle of the visible ones
1411
- startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize/2), 1);
1737
+ startPage = Math.max(paginationCtrl.currentPage - Math.floor(scope.maxSize/2), 1);
1412
1738
  endPage = startPage + scope.maxSize - 1;
1413
1739
 
1414
1740
  // Adjust if limit is exceeded
@@ -1418,7 +1744,7 @@ angular.module('ui.bootstrap.pagination', [])
1418
1744
  }
1419
1745
  } else {
1420
1746
  // Visible pages are paginated with maxSize
1421
- startPage = ((Math.ceil(scope.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
1747
+ startPage = ((Math.ceil(paginationCtrl.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
1422
1748
 
1423
1749
  // Adjust last page if limit is exceeded
1424
1750
  endPage = Math.min(startPage + scope.maxSize - 1, scope.numPages);
@@ -1427,7 +1753,7 @@ angular.module('ui.bootstrap.pagination', [])
1427
1753
 
1428
1754
  // Add page number links
1429
1755
  for (var number = startPage; number <= endPage; number++) {
1430
- var page = makePage(number, number, scope.isActive(number), false);
1756
+ var page = makePage(number, number, paginationCtrl.isActive(number), false);
1431
1757
  scope.pages.push(page);
1432
1758
  }
1433
1759
 
@@ -1446,25 +1772,21 @@ angular.module('ui.bootstrap.pagination', [])
1446
1772
 
1447
1773
  // Add previous & next links
1448
1774
  if (directionLinks) {
1449
- var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious());
1775
+ var previousPage = makePage(paginationCtrl.currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1450
1776
  scope.pages.unshift(previousPage);
1451
1777
 
1452
- var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext());
1778
+ var nextPage = makePage(paginationCtrl.currentPage + 1, nextText, false, paginationCtrl.noNext());
1453
1779
  scope.pages.push(nextPage);
1454
1780
  }
1455
1781
 
1456
1782
  // Add first & last links
1457
1783
  if (boundaryLinks) {
1458
- var firstPage = makePage(1, firstText, false, scope.noPrevious());
1784
+ var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
1459
1785
  scope.pages.unshift(firstPage);
1460
1786
 
1461
- var lastPage = makePage(scope.numPages, lastText, false, scope.noNext());
1787
+ var lastPage = makePage(scope.numPages, lastText, false, paginationCtrl.noNext());
1462
1788
  scope.pages.push(lastPage);
1463
1789
  }
1464
-
1465
- if ( scope.currentPage > scope.numPages ) {
1466
- scope.selectPage(scope.numPages);
1467
- }
1468
1790
  });
1469
1791
  }
1470
1792
  };
@@ -1490,9 +1812,9 @@ angular.module('ui.bootstrap.pagination', [])
1490
1812
  link: function(scope, element, attrs, paginationCtrl) {
1491
1813
 
1492
1814
  // Setup configuration parameters
1493
- var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : config.previousText;
1494
- var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : config.nextText;
1495
- var align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : config.align;
1815
+ var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1816
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1817
+ align = paginationCtrl.getAttributeValue(attrs.align, config.align);
1496
1818
 
1497
1819
  // Create page object used in template
1498
1820
  function makePage(number, text, isDisabled, isPrevious, isNext) {
@@ -1506,117 +1828,19 @@ angular.module('ui.bootstrap.pagination', [])
1506
1828
  }
1507
1829
 
1508
1830
  scope.$watch('numPages + currentPage', function() {
1509
- scope.pages = [];
1831
+ paginationCtrl.reset();
1510
1832
 
1511
1833
  // Add previous & next links
1512
- var previousPage = makePage(scope.currentPage - 1, previousText, scope.noPrevious(), true, false);
1834
+ var previousPage = makePage(paginationCtrl.currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false);
1513
1835
  scope.pages.unshift(previousPage);
1514
1836
 
1515
- var nextPage = makePage(scope.currentPage + 1, nextText, scope.noNext(), false, true);
1837
+ var nextPage = makePage(paginationCtrl.currentPage + 1, nextText, paginationCtrl.noNext(), false, true);
1516
1838
  scope.pages.push(nextPage);
1517
-
1518
- if ( scope.currentPage > scope.numPages ) {
1519
- scope.selectPage(scope.numPages);
1520
- }
1521
1839
  });
1522
1840
  }
1523
1841
  };
1524
1842
  }]);
1525
1843
 
1526
- angular.module('ui.bootstrap.position', [])
1527
-
1528
- /**
1529
- * A set of utility methods that can be use to retrieve position of DOM elements.
1530
- * It is meant to be used where we need to absolute-position DOM elements in
1531
- * relation to other, existing elements (this is the case for tooltips, popovers,
1532
- * typeahead suggestions etc.).
1533
- */
1534
- .factory('$position', ['$document', '$window', function ($document, $window) {
1535
-
1536
- var mouseX, mouseY;
1537
-
1538
- $document.bind('mousemove', function mouseMoved(event) {
1539
- mouseX = event.pageX;
1540
- mouseY = event.pageY;
1541
- });
1542
-
1543
- function getStyle(el, cssprop) {
1544
- if (el.currentStyle) { //IE
1545
- return el.currentStyle[cssprop];
1546
- } else if ($window.getComputedStyle) {
1547
- return $window.getComputedStyle(el)[cssprop];
1548
- }
1549
- // finally try and get inline style
1550
- return el.style[cssprop];
1551
- }
1552
-
1553
- /**
1554
- * Checks if a given element is statically positioned
1555
- * @param element - raw DOM element
1556
- */
1557
- function isStaticPositioned(element) {
1558
- return (getStyle(element, "position") || 'static' ) === 'static';
1559
- }
1560
-
1561
- /**
1562
- * returns the closest, non-statically positioned parentOffset of a given element
1563
- * @param element
1564
- */
1565
- var parentOffsetEl = function (element) {
1566
- var docDomEl = $document[0];
1567
- var offsetParent = element.offsetParent || docDomEl;
1568
- while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
1569
- offsetParent = offsetParent.offsetParent;
1570
- }
1571
- return offsetParent || docDomEl;
1572
- };
1573
-
1574
- return {
1575
- /**
1576
- * Provides read-only equivalent of jQuery's position function:
1577
- * http://api.jquery.com/position/
1578
- */
1579
- position: function (element) {
1580
- var elBCR = this.offset(element);
1581
- var offsetParentBCR = { top: 0, left: 0 };
1582
- var offsetParentEl = parentOffsetEl(element[0]);
1583
- if (offsetParentEl != $document[0]) {
1584
- offsetParentBCR = this.offset(angular.element(offsetParentEl));
1585
- offsetParentBCR.top += offsetParentEl.clientTop;
1586
- offsetParentBCR.left += offsetParentEl.clientLeft;
1587
- }
1588
-
1589
- return {
1590
- width: element.prop('offsetWidth'),
1591
- height: element.prop('offsetHeight'),
1592
- top: elBCR.top - offsetParentBCR.top,
1593
- left: elBCR.left - offsetParentBCR.left
1594
- };
1595
- },
1596
-
1597
- /**
1598
- * Provides read-only equivalent of jQuery's offset function:
1599
- * http://api.jquery.com/offset/
1600
- */
1601
- offset: function (element) {
1602
- var boundingClientRect = element[0].getBoundingClientRect();
1603
- return {
1604
- width: element.prop('offsetWidth'),
1605
- height: element.prop('offsetHeight'),
1606
- top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
1607
- left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
1608
- };
1609
- },
1610
-
1611
- /**
1612
- * Provides the coordinates of the mouse
1613
- */
1614
- mouse: function () {
1615
- return {x: mouseX, y: mouseY};
1616
- }
1617
- };
1618
- }]);
1619
-
1620
1844
  /**
1621
1845
  * The following features are still outstanding: animation as a
1622
1846
  * function, placement as a function, inside, support for more triggers than
@@ -1655,9 +1879,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1655
1879
  * $tooltipProvider.options( { placement: 'left' } );
1656
1880
  * });
1657
1881
  */
1658
- this.options = function( value ) {
1659
- angular.extend( globalOptions, value );
1660
- };
1882
+ this.options = function( value ) {
1883
+ angular.extend( globalOptions, value );
1884
+ };
1661
1885
 
1662
1886
  /**
1663
1887
  * This allows you to extend the set of trigger mappings available. E.g.:
@@ -1701,16 +1925,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1701
1925
  * undefined; otherwise, it uses the `triggerMap` value of the show
1702
1926
  * trigger; else it will just use the show trigger.
1703
1927
  */
1704
- function setTriggers ( trigger ) {
1705
- var show, hide;
1706
-
1707
- show = trigger || options.trigger || defaultTriggerShow;
1708
- if ( angular.isDefined ( options.trigger ) ) {
1709
- hide = triggerMap[options.trigger] || show;
1710
- } else {
1711
- hide = triggerMap[show] || show;
1712
- }
1713
-
1928
+ function getTriggers ( trigger ) {
1929
+ var show = trigger || options.trigger || defaultTriggerShow;
1930
+ var hide = triggerMap[show] || show;
1714
1931
  return {
1715
1932
  show: show,
1716
1933
  hide: hide
@@ -1718,7 +1935,6 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1718
1935
  }
1719
1936
 
1720
1937
  var directiveName = snake_case( type );
1721
- var triggers = setTriggers( undefined );
1722
1938
 
1723
1939
  var startSym = $interpolate.startSymbol();
1724
1940
  var endSym = $interpolate.endSymbol();
@@ -1741,6 +1957,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1741
1957
  var popupTimeout;
1742
1958
  var $body;
1743
1959
  var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
1960
+ var triggers = getTriggers( undefined );
1961
+ var hasRegisteredTriggers = false;
1744
1962
 
1745
1963
  // By default, the tooltip is not open.
1746
1964
  // TODO add ability to start tooltip opened
@@ -1800,7 +2018,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1800
2018
  }
1801
2019
 
1802
2020
  // Get the position of the directive element.
1803
- position = options.appendToBody ? $position.offset( element ) : $position.position( element );
2021
+ position = appendToBody ? $position.offset( element ) : $position.position( element );
1804
2022
 
1805
2023
  // Get the height and width of the tooltip so we can center it.
1806
2024
  ttWidth = tooltip.prop( 'offsetWidth' );
@@ -1895,10 +2113,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1895
2113
  });
1896
2114
 
1897
2115
  attrs.$observe( prefix+'Trigger', function ( val ) {
1898
- element.unbind( triggers.show );
1899
- element.unbind( triggers.hide );
1900
2116
 
1901
- triggers = setTriggers( val );
2117
+ if (hasRegisteredTriggers) {
2118
+ element.unbind( triggers.show, showTooltipBind );
2119
+ element.unbind( triggers.hide, hideTooltipBind );
2120
+ }
2121
+
2122
+ triggers = getTriggers( val );
1902
2123
 
1903
2124
  if ( triggers.show === triggers.hide ) {
1904
2125
  element.bind( triggers.show, toggleTooltipBind );
@@ -1906,6 +2127,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1906
2127
  element.bind( triggers.show, showTooltipBind );
1907
2128
  element.bind( triggers.hide, hideTooltipBind );
1908
2129
  }
2130
+
2131
+ hasRegisteredTriggers = true;
1909
2132
  });
1910
2133
 
1911
2134
  attrs.$observe( prefix+'AppendToBody', function ( val ) {
@@ -2098,13 +2321,15 @@ angular.module('ui.bootstrap.rating', [])
2098
2321
  return {
2099
2322
  restrict: 'EA',
2100
2323
  scope: {
2101
- value: '='
2324
+ value: '=',
2325
+ onHover: '&',
2326
+ onLeave: '&'
2102
2327
  },
2103
2328
  templateUrl: 'template/rating/rating.html',
2104
2329
  replace: true,
2105
2330
  link: function(scope, element, attrs) {
2106
2331
 
2107
- var maxRange = angular.isDefined(attrs.max) ? scope.$eval(attrs.max) : ratingConfig.max;
2332
+ var maxRange = angular.isDefined(attrs.max) ? scope.$parent.$eval(attrs.max) : ratingConfig.max;
2108
2333
 
2109
2334
  scope.range = [];
2110
2335
  for (var i = 1; i <= maxRange; i++) {
@@ -2121,10 +2346,12 @@ angular.module('ui.bootstrap.rating', [])
2121
2346
  if ( ! scope.readonly ) {
2122
2347
  scope.val = value;
2123
2348
  }
2349
+ scope.onHover({value: value});
2124
2350
  };
2125
2351
 
2126
2352
  scope.reset = function() {
2127
2353
  scope.val = angular.copy(scope.value);
2354
+ scope.onLeave();
2128
2355
  };
2129
2356
  scope.reset();
2130
2357
 
@@ -2158,26 +2385,27 @@ angular.module('ui.bootstrap.tabs', [])
2158
2385
  };
2159
2386
  })
2160
2387
 
2161
- .controller('TabsetController', ['$scope', '$element',
2388
+ .controller('TabsetController', ['$scope', '$element',
2162
2389
  function TabsetCtrl($scope, $element) {
2390
+
2163
2391
  var ctrl = this,
2164
2392
  tabs = ctrl.tabs = $scope.tabs = [];
2165
2393
 
2166
2394
  ctrl.select = function(tab) {
2167
2395
  angular.forEach(tabs, function(tab) {
2168
2396
  tab.active = false;
2169
- });
2397
+ });
2170
2398
  tab.active = true;
2171
2399
  };
2172
2400
 
2173
2401
  ctrl.addTab = function addTab(tab) {
2174
2402
  tabs.push(tab);
2175
- if (tabs.length == 1) {
2403
+ if (tabs.length === 1 || tab.active) {
2176
2404
  ctrl.select(tab);
2177
2405
  }
2178
2406
  };
2179
2407
 
2180
- ctrl.removeTab = function removeTab(tab) {
2408
+ ctrl.removeTab = function removeTab(tab) {
2181
2409
  var index = tabs.indexOf(tab);
2182
2410
  //Select a new tab if the tab to be removed is selected
2183
2411
  if (tab.active && tabs.length > 1) {
@@ -2198,6 +2426,8 @@ function TabsetCtrl($scope, $element) {
2198
2426
  * Tabset is the outer container for the tabs directive
2199
2427
  *
2200
2428
  * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
2429
+ * @param {string=} direction What direction the tabs should be rendered. Available:
2430
+ * 'right', 'left', 'below'.
2201
2431
  *
2202
2432
  * @example
2203
2433
  <example module="ui.bootstrap">
@@ -2218,12 +2448,20 @@ function TabsetCtrl($scope, $element) {
2218
2448
  return {
2219
2449
  restrict: 'EA',
2220
2450
  transclude: true,
2451
+ replace: true,
2452
+ require: '^tabset',
2221
2453
  scope: {},
2222
2454
  controller: 'TabsetController',
2223
2455
  templateUrl: 'template/tabs/tabset.html',
2224
- link: function(scope, element, attrs) {
2225
- scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
2226
- scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2456
+ compile: function(elm, attrs, transclude) {
2457
+ return function(scope, element, attrs, tabsetCtrl) {
2458
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
2459
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2460
+ scope.direction = angular.isDefined(attrs.direction) ? scope.$parent.$eval(attrs.direction) : 'top';
2461
+ scope.tabsAbove = (scope.direction != 'below');
2462
+ tabsetCtrl.$scope = scope;
2463
+ tabsetCtrl.$transcludeFn = transclude;
2464
+ };
2227
2465
  }
2228
2466
  };
2229
2467
  })
@@ -2238,7 +2476,7 @@ function TabsetCtrl($scope, $element) {
2238
2476
  * @param {boolean=} active A binding, telling whether or not this tab is selected.
2239
2477
  * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
2240
2478
  *
2241
- * @description
2479
+ * @description
2242
2480
  * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
2243
2481
  *
2244
2482
  * @example
@@ -2318,8 +2556,9 @@ function($parse, $http, $templateCache, $compile) {
2318
2556
  transclude: true,
2319
2557
  scope: {
2320
2558
  heading: '@',
2321
- onSelect: '&select' //This callback is called in contentHeadingTransclude
2559
+ onSelect: '&select', //This callback is called in contentHeadingTransclude
2322
2560
  //once it inserts the tab's content into the dom
2561
+ onDeselect: '&deselect'
2323
2562
  },
2324
2563
  controller: function() {
2325
2564
  //Empty controller so other directives can require being 'under' a tab
@@ -2327,17 +2566,13 @@ function($parse, $http, $templateCache, $compile) {
2327
2566
  compile: function(elm, attrs, transclude) {
2328
2567
  return function postLink(scope, elm, attrs, tabsetCtrl) {
2329
2568
  var getActive, setActive;
2330
- scope.active = false; // default value
2331
2569
  if (attrs.active) {
2332
2570
  getActive = $parse(attrs.active);
2333
2571
  setActive = getActive.assign;
2334
2572
  scope.$parent.$watch(getActive, function updateActive(value) {
2335
- if ( !!value && scope.disabled ) {
2336
- setActive(scope.$parent, false); // Prevent active assignment
2337
- } else {
2338
- scope.active = !!value;
2339
- }
2573
+ scope.active = !!value;
2340
2574
  });
2575
+ scope.active = getActive(scope.$parent);
2341
2576
  } else {
2342
2577
  setActive = getActive = angular.noop;
2343
2578
  }
@@ -2347,6 +2582,8 @@ function($parse, $http, $templateCache, $compile) {
2347
2582
  if (active) {
2348
2583
  tabsetCtrl.select(scope);
2349
2584
  scope.onSelect();
2585
+ } else {
2586
+ scope.onDeselect();
2350
2587
  }
2351
2588
  });
2352
2589
 
@@ -2367,42 +2604,14 @@ function($parse, $http, $templateCache, $compile) {
2367
2604
  scope.$on('$destroy', function() {
2368
2605
  tabsetCtrl.removeTab(scope);
2369
2606
  });
2370
- //If the tabset sets this tab to active, set the parent scope's active
2371
- //binding too. We do this so the watch for the parent's initial active
2372
- //value won't overwrite what is initially set by the tabset
2373
2607
  if (scope.active) {
2374
2608
  setActive(scope.$parent, true);
2375
- }
2376
-
2377
- //Transclude the collection of sibling elements. Use forEach to find
2378
- //the heading if it exists. We don't use a directive for tab-heading
2379
- //because it is problematic. Discussion @ http://git.io/MSNPwQ
2380
- transclude(scope.$parent, function(clone) {
2381
- //Look at every element in the clone collection. If it's tab-heading,
2382
- //mark it as that. If it's not tab-heading, mark it as tab contents
2383
- var contents = [], heading;
2384
- angular.forEach(clone, function(el) {
2385
- //See if it's a tab-heading attr or element directive
2386
- //First make sure it's a normal element, one that has a tagName
2387
- if (el.tagName &&
2388
- (el.hasAttribute("tab-heading") ||
2389
- el.hasAttribute("data-tab-heading") ||
2390
- el.tagName.toLowerCase() == "tab-heading" ||
2391
- el.tagName.toLowerCase() == "data-tab-heading"
2392
- )) {
2393
- heading = el;
2394
- } else {
2395
- contents.push(el);
2396
- }
2397
- });
2398
- //Share what we found on the scope, so our tabHeadingTransclude and
2399
- //tabContentTransclude directives can find out what the heading and
2400
- //contents are.
2401
- if (heading) {
2402
- scope.headingElement = angular.element(heading);
2403
- }
2404
- scope.contentElement = angular.element(contents);
2405
- });
2609
+ }
2610
+
2611
+
2612
+ //We need to transclude later, once the content container is ready.
2613
+ //when this link happens, we're inside a tab heading.
2614
+ scope.$transcludeFn = transclude;
2406
2615
  };
2407
2616
  }
2408
2617
  };
@@ -2411,7 +2620,7 @@ function($parse, $http, $templateCache, $compile) {
2411
2620
  .directive('tabHeadingTransclude', [function() {
2412
2621
  return {
2413
2622
  restrict: 'A',
2414
- require: '^tab',
2623
+ require: '^tab',
2415
2624
  link: function(scope, elm, attrs, tabCtrl) {
2416
2625
  scope.$watch('headingElement', function updateHeadingElement(heading) {
2417
2626
  if (heading) {
@@ -2423,21 +2632,56 @@ function($parse, $http, $templateCache, $compile) {
2423
2632
  };
2424
2633
  }])
2425
2634
 
2426
- .directive('tabContentTransclude', ['$parse', function($parse) {
2635
+ .directive('tabContentTransclude', ['$compile', '$parse', function($compile, $parse) {
2427
2636
  return {
2428
2637
  restrict: 'A',
2429
2638
  require: '^tabset',
2430
- link: function(scope, elm, attrs, tabsetCtrl) {
2431
- scope.$watch($parse(attrs.tabContentTransclude), function(tab) {
2432
- elm.html('');
2433
- if (tab) {
2434
- elm.append(tab.contentElement);
2435
- }
2639
+ link: function(scope, elm, attrs) {
2640
+ var tab = scope.$eval(attrs.tabContentTransclude);
2641
+
2642
+ //Now our tab is ready to be transcluded: both the tab heading area
2643
+ //and the tab content area are loaded. Transclude 'em both.
2644
+ tab.$transcludeFn(tab.$parent, function(contents) {
2645
+ angular.forEach(contents, function(node) {
2646
+ if (isTabHeading(node)) {
2647
+ //Let tabHeadingTransclude know.
2648
+ tab.headingElement = node;
2649
+ } else {
2650
+ elm.append(node);
2651
+ }
2652
+ });
2436
2653
  });
2437
2654
  }
2438
2655
  };
2656
+ function isTabHeading(node) {
2657
+ return node.tagName && (
2658
+ node.hasAttribute('tab-heading') ||
2659
+ node.hasAttribute('data-tab-heading') ||
2660
+ node.tagName.toLowerCase() === 'tab-heading' ||
2661
+ node.tagName.toLowerCase() === 'data-tab-heading'
2662
+ );
2663
+ }
2439
2664
  }])
2440
2665
 
2666
+ .directive('tabsetTitles', function($http) {
2667
+ return {
2668
+ restrict: 'A',
2669
+ require: '^tabset',
2670
+ templateUrl: 'template/tabs/tabset-titles.html',
2671
+ replace: true,
2672
+ link: function(scope, elm, attrs, tabsetCtrl) {
2673
+ if (!scope.$eval(attrs.tabsetTitles)) {
2674
+ elm.remove();
2675
+ } else {
2676
+ //now that tabs location has been decided, transclude the tab titles in
2677
+ tabsetCtrl.$transcludeFn(tabsetCtrl.$scope.$parent, function(node) {
2678
+ elm.append(node);
2679
+ });
2680
+ }
2681
+ }
2682
+ };
2683
+ })
2684
+
2441
2685
  ;
2442
2686
 
2443
2687
 
@@ -2533,20 +2777,22 @@ angular.module('ui.bootstrap.timepicker', [])
2533
2777
  // Respond on mousewheel spin
2534
2778
  var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
2535
2779
  if ( mousewheel ) {
2536
-
2780
+
2537
2781
  var isScrollingUp = function(e) {
2538
2782
  if (e.originalEvent) {
2539
2783
  e = e.originalEvent;
2540
2784
  }
2541
- return (e.detail || e.wheelDelta > 0);
2785
+ //pick correct delta variable depending on event
2786
+ var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
2787
+ return (e.detail || delta > 0);
2542
2788
  };
2543
-
2544
- hoursInputEl.bind('mousewheel', function(e) {
2789
+
2790
+ hoursInputEl.bind('mousewheel wheel', function(e) {
2545
2791
  scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
2546
2792
  e.preventDefault();
2547
2793
  });
2548
2794
 
2549
- minutesInputEl.bind('mousewheel', function(e) {
2795
+ minutesInputEl.bind('mousewheel wheel', function(e) {
2550
2796
  scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
2551
2797
  e.preventDefault();
2552
2798
  });
@@ -2633,10 +2879,8 @@ angular.module('ui.bootstrap.timepicker', [])
2633
2879
 
2634
2880
  function addMinutes( minutes ) {
2635
2881
  var dt = new Date( selected.getTime() + minutes * 60000 );
2636
- if ( dt.getDate() !== selected.getDate()) {
2637
- dt.setDate( dt.getDate() - 1 );
2638
- }
2639
- selected.setTime( dt.getTime() );
2882
+ selected.setHours( dt.getHours() );
2883
+ selected.setMinutes( dt.getMinutes() );
2640
2884
  scope.model = new Date( selected );
2641
2885
  }
2642
2886
 
@@ -2658,6 +2902,7 @@ angular.module('ui.bootstrap.timepicker', [])
2658
2902
  }
2659
2903
  };
2660
2904
  }]);
2905
+
2661
2906
  angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2662
2907
 
2663
2908
  /**
@@ -2697,7 +2942,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2697
2942
  require:'ngModel',
2698
2943
  link:function (originalScope, element, attrs, modelCtrl) {
2699
2944
 
2700
- var selected;
2945
+ //SUPPORTED ATTRIBUTES (OPTIONS)
2701
2946
 
2702
2947
  //minimal no of characters that needs to be entered before typeahead kicks-in
2703
2948
  var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
@@ -2705,16 +2950,26 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2705
2950
  //minimal wait time after last character typed before typehead kicks-in
2706
2951
  var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
2707
2952
 
2708
- //expressions used by typeahead
2709
- var parserResult = typeaheadParser.parse(attrs.typeahead);
2710
-
2711
2953
  //should it restrict model values to the ones selected from the popup only?
2712
2954
  var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
2713
2955
 
2956
+ //binding to a variable that indicates if matches are being retrieved asynchronously
2714
2957
  var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
2715
2958
 
2959
+ //a callback executed when a match is selected
2716
2960
  var onSelectCallback = $parse(attrs.typeaheadOnSelect);
2717
2961
 
2962
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
2963
+
2964
+ //INTERNAL VARIABLES
2965
+
2966
+ //model setter executed upon match selection
2967
+ var $setModelValue = $parse(attrs.ngModel).assign;
2968
+
2969
+ //expressions used by typeahead
2970
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
2971
+
2972
+
2718
2973
  //pop-up element used to display matches
2719
2974
  var popUpEl = angular.element('<typeahead-popup></typeahead-popup>');
2720
2975
  popUpEl.attr({
@@ -2724,6 +2979,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2724
2979
  query: 'query',
2725
2980
  position: 'position'
2726
2981
  });
2982
+ //custom item template
2983
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
2984
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
2985
+ }
2727
2986
 
2728
2987
  //create a child scope for the typeahead directive so we are not polluting original scope
2729
2988
  //with typeahead-specific data (matches, query etc.)
@@ -2783,55 +3042,69 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2783
3042
  //we need to propagate user's query so we can higlight matches
2784
3043
  scope.query = undefined;
2785
3044
 
3045
+ //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3046
+ var timeoutPromise;
3047
+
2786
3048
  //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
2787
3049
  //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
2788
3050
  modelCtrl.$parsers.push(function (inputValue) {
2789
3051
 
2790
- var timeoutId;
2791
-
2792
3052
  resetMatches();
2793
- if (selected) {
2794
- return inputValue;
2795
- } else {
2796
- if (inputValue && inputValue.length >= minSearch) {
2797
- if (waitTime > 0) {
2798
- if (timeoutId) {
2799
- $timeout.cancel(timeoutId);//cancel previous timeout
2800
- }
2801
- timeoutId = $timeout(function () {
2802
- getMatchesAsync(inputValue);
2803
- }, waitTime);
2804
- } else {
2805
- getMatchesAsync(inputValue);
3053
+ if (inputValue && inputValue.length >= minSearch) {
3054
+ if (waitTime > 0) {
3055
+ if (timeoutPromise) {
3056
+ $timeout.cancel(timeoutPromise);//cancel previous timeout
2806
3057
  }
3058
+ timeoutPromise = $timeout(function () {
3059
+ getMatchesAsync(inputValue);
3060
+ }, waitTime);
3061
+ } else {
3062
+ getMatchesAsync(inputValue);
2807
3063
  }
2808
3064
  }
2809
3065
 
2810
3066
  return isEditable ? inputValue : undefined;
2811
3067
  });
2812
3068
 
2813
- modelCtrl.$render = function () {
3069
+ modelCtrl.$formatters.push(function (modelValue) {
3070
+
3071
+ var candidateViewValue, emptyViewValue;
2814
3072
  var locals = {};
2815
- locals[parserResult.itemName] = selected || modelCtrl.$viewValue;
2816
- element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue);
2817
- selected = undefined;
2818
- };
3073
+
3074
+ if (inputFormatter) {
3075
+
3076
+ locals['$model'] = modelValue;
3077
+ return inputFormatter(originalScope, locals);
3078
+
3079
+ } else {
3080
+ locals[parserResult.itemName] = modelValue;
3081
+
3082
+ //it might happen that we don't have enough info to properly render input value
3083
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
3084
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
3085
+ emptyViewValue = parserResult.viewMapper(originalScope, {});
3086
+
3087
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
3088
+ }
3089
+ });
2819
3090
 
2820
3091
  scope.select = function (activeIdx) {
2821
3092
  //called from within the $digest() cycle
2822
3093
  var locals = {};
2823
3094
  var model, item;
2824
- locals[parserResult.itemName] = item = selected = scope.matches[activeIdx].model;
2825
3095
 
2826
- model = parserResult.modelMapper(scope, locals);
2827
- modelCtrl.$setViewValue(model);
2828
- modelCtrl.$render();
2829
- onSelectCallback(scope, {
3096
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
3097
+ model = parserResult.modelMapper(originalScope, locals);
3098
+ $setModelValue(originalScope, model);
3099
+
3100
+ onSelectCallback(originalScope, {
2830
3101
  $item: item,
2831
3102
  $model: model,
2832
- $label: parserResult.viewMapper(scope, locals)
3103
+ $label: parserResult.viewMapper(originalScope, locals)
2833
3104
  });
2834
3105
 
3106
+ //return focus to the input element if a mach was selected via a mouse click event
3107
+ resetMatches();
2835
3108
  element[0].focus();
2836
3109
  };
2837
3110
 
@@ -2888,9 +3161,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2888
3161
  select:'&'
2889
3162
  },
2890
3163
  replace:true,
2891
- templateUrl:'template/typeahead/typeahead.html',
3164
+ templateUrl:'template/typeahead/typeahead-popup.html',
2892
3165
  link:function (scope, element, attrs) {
2893
3166
 
3167
+ scope.templateUrl = attrs.templateUrl;
3168
+
2894
3169
  scope.isOpen = function () {
2895
3170
  return scope.matches.length > 0;
2896
3171
  };
@@ -2910,6 +3185,23 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2910
3185
  };
2911
3186
  })
2912
3187
 
3188
+ .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
3189
+ return {
3190
+ restrict:'E',
3191
+ scope:{
3192
+ index:'=',
3193
+ match:'=',
3194
+ query:'='
3195
+ },
3196
+ link:function (scope, element, attrs) {
3197
+ var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
3198
+ $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
3199
+ element.replaceWith($compile(tplContent.trim())(scope));
3200
+ });
3201
+ }
3202
+ };
3203
+ }])
3204
+
2913
3205
  .filter('typeaheadHighlight', function() {
2914
3206
 
2915
3207
  function escapeRegexp(queryToEscape) {
@@ -2920,7 +3212,6 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2920
3212
  return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : query;
2921
3213
  };
2922
3214
  });
2923
-
2924
3215
  angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
2925
3216
  $templateCache.put("template/accordion/accordion-group.html",
2926
3217
  "<div class=\"accordion-group\">\n" +
@@ -2971,12 +3262,12 @@ angular.module("template/carousel/slide.html", []).run(["$templateCache", functi
2971
3262
 
2972
3263
  angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
2973
3264
  $templateCache.put("template/datepicker/datepicker.html",
2974
- "<table class=\"well well-large\">\n" +
3265
+ "<table>\n" +
2975
3266
  " <thead>\n" +
2976
3267
  " <tr class=\"text-center\">\n" +
2977
- " <th><button class=\"btn pull-left\" ng-click=\"move(-1)\"><i class=\"icon-chevron-left\"></i></button></th>\n" +
2978
- " <th colspan=\"{{rows[0].length - 2 + showWeekNumbers}}\"><button class=\"btn btn-block\" ng-click=\"toggleMode()\"><strong>{{title}}</strong></button></th>\n" +
2979
- " <th><button class=\"btn pull-right\" ng-click=\"move(1)\"><i class=\"icon-chevron-right\"></i></button></th>\n" +
3268
+ " <th><button type=\"button\" class=\"btn pull-left\" ng-click=\"move(-1)\"><i class=\"icon-chevron-left\"></i></button></th>\n" +
3269
+ " <th colspan=\"{{rows[0].length - 2 + showWeekNumbers}}\"><button type=\"button\" class=\"btn btn-block\" ng-click=\"toggleMode()\"><strong>{{title}}</strong></button></th>\n" +
3270
+ " <th><button type=\"button\" class=\"btn pull-right\" ng-click=\"move(1)\"><i class=\"icon-chevron-right\"></i></button></th>\n" +
2980
3271
  " </tr>\n" +
2981
3272
  " <tr class=\"text-center\" ng-show=\"labels.length > 0\">\n" +
2982
3273
  " <th ng-show=\"showWeekNumbers\">#</th>\n" +
@@ -2987,7 +3278,7 @@ angular.module("template/datepicker/datepicker.html", []).run(["$templateCache",
2987
3278
  " <tr ng-repeat=\"row in rows\">\n" +
2988
3279
  " <td ng-show=\"showWeekNumbers\" class=\"text-center\"><em>{{ getWeekNumber(row) }}</em></td>\n" +
2989
3280
  " <td ng-repeat=\"dt in row\" class=\"text-center\">\n" +
2990
- " <button style=\"width:100%;\" class=\"btn\" ng-class=\"{'btn-info': dt.isSelected}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\"><span ng-class=\"{muted: ! dt.isCurrent}\">{{dt.label}}</span></button>\n" +
3281
+ " <button type=\"button\" style=\"width:100%;\" class=\"btn\" ng-class=\"{'btn-info': dt.selected}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\"><span ng-class=\"{muted: dt.secondary}\">{{dt.label}}</span></button>\n" +
2991
3282
  " </td>\n" +
2992
3283
  " </tr>\n" +
2993
3284
  " </tbody>\n" +
@@ -2995,23 +3286,39 @@ angular.module("template/datepicker/datepicker.html", []).run(["$templateCache",
2995
3286
  "");
2996
3287
  }]);
2997
3288
 
3289
+ angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
3290
+ $templateCache.put("template/datepicker/popup.html",
3291
+ "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\" class=\"dropdown-menu\">\n" +
3292
+ " <li ng-transclude></li>\n" +
3293
+ " <li class=\"divider\"></li>\n" +
3294
+ " <li style=\"padding: 9px;\">\n" +
3295
+ " <span class=\"btn-group\">\n" +
3296
+ " <button class=\"btn btn-small btn-inverse\" ng-click=\"today()\">Today</button>\n" +
3297
+ " <button class=\"btn btn-small btn-info\" ng-click=\"showWeeks = ! showWeeks\" ng-class=\"{active: showWeeks}\">Weeks</button>\n" +
3298
+ " <button class=\"btn btn-small btn-danger\" ng-click=\"clear()\">Clear</button>\n" +
3299
+ " </span>\n" +
3300
+ " <button class=\"btn btn-small btn-success pull-right\" ng-click=\"isOpen = false\">Close</button>\n" +
3301
+ " </li>\n" +
3302
+ "</ul>");
3303
+ }]);
3304
+
2998
3305
  angular.module("template/dialog/message.html", []).run(["$templateCache", function($templateCache) {
2999
3306
  $templateCache.put("template/dialog/message.html",
3000
3307
  "<div class=\"modal-header\">\n" +
3001
- " <h3>{{ title }}</h3>\n" +
3308
+ " <h3>{{ title }}</h3>\n" +
3002
3309
  "</div>\n" +
3003
3310
  "<div class=\"modal-body\">\n" +
3004
- " <p>{{ message }}</p>\n" +
3311
+ " <p>{{ message }}</p>\n" +
3005
3312
  "</div>\n" +
3006
3313
  "<div class=\"modal-footer\">\n" +
3007
- " <button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"btn\" ng-class=\"btn.cssClass\">{{ btn.label }}</button>\n" +
3314
+ " <button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"btn\" ng-class=\"btn.cssClass\">{{ btn.label }}</button>\n" +
3008
3315
  "</div>\n" +
3009
3316
  "");
3010
3317
  }]);
3011
3318
 
3012
3319
  angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
3013
3320
  $templateCache.put("template/modal/backdrop.html",
3014
- "<div class=\"modal-backdrop\"></div>");
3321
+ "<div class=\"modal-backdrop fade in\"></div>");
3015
3322
  }]);
3016
3323
 
3017
3324
  angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
@@ -3082,9 +3389,8 @@ angular.module("template/progressbar/progress.html", []).run(["$templateCache",
3082
3389
  angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
3083
3390
  $templateCache.put("template/rating/rating.html",
3084
3391
  "<span ng-mouseleave=\"reset()\">\n" +
3085
- " <i ng-repeat=\"number in range\" ng-mouseenter=\"enter(number)\" ng-click=\"rate(number)\" ng-class=\"{'icon-star': number <= val, 'icon-star-empty': number > val}\"></i>\n" +
3086
- "</span>\n" +
3087
- "");
3392
+ " <i ng-repeat=\"number in range\" ng-mouseenter=\"enter(number)\" ng-click=\"rate(number)\" ng-class=\"{'icon-star': number <= val, 'icon-star-empty': number > val}\"></i>\n" +
3393
+ "</span>");
3088
3394
  }]);
3089
3395
 
3090
3396
  angular.module("template/tabs/pane.html", []).run(["$templateCache", function($templateCache) {
@@ -3114,19 +3420,26 @@ angular.module("template/tabs/tabs.html", []).run(["$templateCache", function($t
3114
3420
  "");
3115
3421
  }]);
3116
3422
 
3423
+ angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) {
3424
+ $templateCache.put("template/tabs/tabset-titles.html",
3425
+ "<ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical}\">\n" +
3426
+ "</ul>\n" +
3427
+ "");
3428
+ }]);
3429
+
3117
3430
  angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
3118
3431
  $templateCache.put("template/tabs/tabset.html",
3119
3432
  "\n" +
3120
- "<div class=\"tabbable\">\n" +
3121
- " <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical}\" ng-transclude>\n" +
3122
- " </ul>\n" +
3433
+ "<div class=\"tabbable\" ng-class=\"{'tabs-right': direction == 'right', 'tabs-left': direction == 'left', 'tabs-below': direction == 'below'}\">\n" +
3434
+ " <div tabset-titles=\"tabsAbove\"></div>\n" +
3123
3435
  " <div class=\"tab-content\">\n" +
3124
3436
  " <div class=\"tab-pane\" \n" +
3125
3437
  " ng-repeat=\"tab in tabs\" \n" +
3126
3438
  " ng-class=\"{active: tab.active}\"\n" +
3127
- " tab-content-transclude=\"tab\" tt=\"tab\">\n" +
3439
+ " tab-content-transclude=\"tab\">\n" +
3128
3440
  " </div>\n" +
3129
3441
  " </div>\n" +
3442
+ " <div tabset-titles=\"!tabsAbove\"></div>\n" +
3130
3443
  "</div>\n" +
3131
3444
  "");
3132
3445
  }]);
@@ -3134,27 +3447,41 @@ angular.module("template/tabs/tabset.html", []).run(["$templateCache", function(
3134
3447
  angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
3135
3448
  $templateCache.put("template/timepicker/timepicker.html",
3136
3449
  "<table class=\"form-inline\">\n" +
3137
- " <tr class=\"text-center\">\n" +
3138
- " <td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><i class=\"icon-chevron-up\"></i></a></td>\n" +
3139
- " <td>&nbsp;</td>\n" +
3140
- " <td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><i class=\"icon-chevron-up\"></i></a></td>\n" +
3141
- " <td ng-show=\"showMeridian\"></td>\n" +
3142
- " </tr>\n" +
3143
- " <tr>\n" +
3144
- " <td class=\"control-group\" ng-class=\"{'error': !validHours}\"><input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"span1 text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\" /></td>\n" +
3145
- " <td>:</td>\n" +
3146
- " <td class=\"control-group\" ng-class=\"{'error': !validMinutes}\"><input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"span1 text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\"></td>\n" +
3147
- " <td ng-show=\"showMeridian\"><button ng-click=\"toggleMeridian()\" class=\"btn text-center\">{{meridian}}</button></td>\n" +
3148
- " </tr>\n" +
3149
- " <tr class=\"text-center\">\n" +
3150
- " <td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><i class=\"icon-chevron-down\"></i></a></td>\n" +
3151
- " <td>&nbsp;</td>\n" +
3152
- " <td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><i class=\"icon-chevron-down\"></i></a></td>\n" +
3153
- " <td ng-show=\"showMeridian\"></td>\n" +
3154
- " </tr>\n" +
3450
+ " <tr class=\"text-center\">\n" +
3451
+ " <td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><i class=\"icon-chevron-up\"></i></a></td>\n" +
3452
+ " <td>&nbsp;</td>\n" +
3453
+ " <td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><i class=\"icon-chevron-up\"></i></a></td>\n" +
3454
+ " <td ng-show=\"showMeridian\"></td>\n" +
3455
+ " </tr>\n" +
3456
+ " <tr>\n" +
3457
+ " <td class=\"control-group\" ng-class=\"{'error': !validHours}\"><input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"span1 text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\" /></td>\n" +
3458
+ " <td>:</td>\n" +
3459
+ " <td class=\"control-group\" ng-class=\"{'error': !validMinutes}\"><input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"span1 text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\"></td>\n" +
3460
+ " <td ng-show=\"showMeridian\"><button ng-click=\"toggleMeridian()\" class=\"btn text-center\">{{meridian}}</button></td>\n" +
3461
+ " </tr>\n" +
3462
+ " <tr class=\"text-center\">\n" +
3463
+ " <td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><i class=\"icon-chevron-down\"></i></a></td>\n" +
3464
+ " <td>&nbsp;</td>\n" +
3465
+ " <td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><i class=\"icon-chevron-down\"></i></a></td>\n" +
3466
+ " <td ng-show=\"showMeridian\"></td>\n" +
3467
+ " </tr>\n" +
3155
3468
  "</table>");
3156
3469
  }]);
3157
3470
 
3471
+ angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
3472
+ $templateCache.put("template/typeahead/typeahead-match.html",
3473
+ "<a tabindex=\"-1\" ng-bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>");
3474
+ }]);
3475
+
3476
+ angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
3477
+ $templateCache.put("template/typeahead/typeahead-popup.html",
3478
+ "<ul class=\"typeahead dropdown-menu\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
3479
+ " <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\">\n" +
3480
+ " <typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></typeahead-match>\n" +
3481
+ " </li>\n" +
3482
+ "</ul>");
3483
+ }]);
3484
+
3158
3485
  angular.module("template/typeahead/typeahead.html", []).run(["$templateCache", function($templateCache) {
3159
3486
  $templateCache.put("template/typeahead/typeahead.html",
3160
3487
  "<ul class=\"typeahead dropdown-menu\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +