angular-ui-bootstrap-rails 0.4.0.0 → 0.5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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" +