angular-strap.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /**
  2. * AngularStrap - Twitter Bootstrap directives for AngularJS
  3. * @version v0.7.5 - 2013-07-21
  4. * @link http://mgcrea.github.com/angular-strap
  5. * @author Olivier Louvignes <olivier@mg-crea.com>
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. angular.module('$strap.config', []).value('$strapConfig', {});
  9. angular.module('$strap.filters', ['$strap.config']);
  10. angular.module('$strap.directives', ['$strap.config']);
  11. angular.module('$strap', [
  12. '$strap.filters',
  13. '$strap.directives',
  14. '$strap.config'
  15. ]);
  16. 'use strict';
  17. angular.module('$strap.directives').directive('bsDatepicker', [
  18. '$timeout',
  19. '$strapConfig',
  20. function ($timeout, $strapConfig) {
  21. var isAppleTouch = /(iP(a|o)d|iPhone)/g.test(navigator.userAgent);
  22. var regexpMap = function regexpMap(language) {
  23. language = language || 'en';
  24. return {
  25. '/': '[\\/]',
  26. '-': '[-]',
  27. '.': '[.]',
  28. ' ': '[\\s]',
  29. 'dd': '(?:(?:[0-2]?[0-9]{1})|(?:[3][01]{1}))',
  30. 'd': '(?:(?:[0-2]?[0-9]{1})|(?:[3][01]{1}))',
  31. 'mm': '(?:[0]?[1-9]|[1][012])',
  32. 'm': '(?:[0]?[1-9]|[1][012])',
  33. 'DD': '(?:' + $.fn.datepicker.dates[language].days.join('|') + ')',
  34. 'D': '(?:' + $.fn.datepicker.dates[language].daysShort.join('|') + ')',
  35. 'MM': '(?:' + $.fn.datepicker.dates[language].months.join('|') + ')',
  36. 'M': '(?:' + $.fn.datepicker.dates[language].monthsShort.join('|') + ')',
  37. 'yyyy': '(?:(?:[1]{1}[0-9]{1}[0-9]{1}[0-9]{1})|(?:[2]{1}[0-9]{3}))(?![[0-9]])',
  38. 'yy': '(?:(?:[0-9]{1}[0-9]{1}))(?![[0-9]])'
  39. };
  40. };
  41. var regexpForDateFormat = function regexpForDateFormat(format, language) {
  42. var re = format, map = regexpMap(language), i;
  43. i = 0;
  44. angular.forEach(map, function (v, k) {
  45. re = re.split(k).join('${' + i + '}');
  46. i++;
  47. });
  48. i = 0;
  49. angular.forEach(map, function (v, k) {
  50. re = re.split('${' + i + '}').join(v);
  51. i++;
  52. });
  53. return new RegExp('^' + re + '$', ['i']);
  54. };
  55. return {
  56. restrict: 'A',
  57. require: '?ngModel',
  58. link: function postLink(scope, element, attrs, controller) {
  59. var options = angular.extend({ autoclose: true }, $strapConfig.datepicker || {}), type = attrs.dateType || options.type || 'date';
  60. angular.forEach([
  61. 'format',
  62. 'weekStart',
  63. 'calendarWeeks',
  64. 'startDate',
  65. 'endDate',
  66. 'daysOfWeekDisabled',
  67. 'autoclose',
  68. 'startView',
  69. 'minViewMode',
  70. 'todayBtn',
  71. 'todayHighlight',
  72. 'keyboardNavigation',
  73. 'language',
  74. 'forceParse'
  75. ], function (key) {
  76. if (angular.isDefined(attrs[key]))
  77. options[key] = attrs[key];
  78. });
  79. var language = options.language || 'en', readFormat = attrs.dateFormat || options.format || $.fn.datepicker.dates[language] && $.fn.datepicker.dates[language].format || 'mm/dd/yyyy', format = isAppleTouch ? 'yyyy-mm-dd' : readFormat, dateFormatRegexp = regexpForDateFormat(format, language);
  80. if (controller) {
  81. controller.$formatters.unshift(function (modelValue) {
  82. return type === 'date' && angular.isString(modelValue) && modelValue ? $.fn.datepicker.DPGlobal.parseDate(new Date(modelValue), $.fn.datepicker.DPGlobal.parseFormat(readFormat), language) : modelValue;
  83. });
  84. controller.$parsers.unshift(function (viewValue) {
  85. if (!viewValue) {
  86. controller.$setValidity('date', true);
  87. return null;
  88. } else if (type === 'date' && angular.isDate(viewValue)) {
  89. controller.$setValidity('date', true);
  90. return viewValue;
  91. } else if (angular.isString(viewValue) && dateFormatRegexp.test(viewValue)) {
  92. controller.$setValidity('date', true);
  93. if (isAppleTouch)
  94. return new Date(viewValue);
  95. return type === 'string' ? viewValue : $.fn.datepicker.DPGlobal.parseDate(viewValue, $.fn.datepicker.DPGlobal.parseFormat(format), language);
  96. } else {
  97. controller.$setValidity('date', false);
  98. return undefined;
  99. }
  100. });
  101. controller.$render = function ngModelRender() {
  102. if (isAppleTouch) {
  103. var date = controller.$viewValue ? $.fn.datepicker.DPGlobal.formatDate(controller.$viewValue, $.fn.datepicker.DPGlobal.parseFormat(format), language) : '';
  104. element.val(date);
  105. return date;
  106. }
  107. if (!controller.$viewValue)
  108. element.val('');
  109. return element.datepicker('update', controller.$viewValue);
  110. };
  111. }
  112. if (isAppleTouch) {
  113. element.prop('type', 'date').css('-webkit-appearance', 'textfield');
  114. } else {
  115. if (controller) {
  116. element.on('changeDate', function (ev) {
  117. scope.$apply(function () {
  118. controller.$setViewValue(type === 'string' ? element.val() : ev.date);
  119. });
  120. });
  121. }
  122. element.datepicker(angular.extend(options, {
  123. format: format,
  124. language: language
  125. }));
  126. scope.$on('$destroy', function () {
  127. var datepicker = element.data('datepicker');
  128. if (datepicker) {
  129. datepicker.picker.remove();
  130. element.data('datepicker', null);
  131. }
  132. });
  133. attrs.$observe('startDate', function (value) {
  134. element.datepicker('setStartDate', value);
  135. });
  136. attrs.$observe('endDate', function (value) {
  137. element.datepicker('setEndDate', value);
  138. });
  139. }
  140. var component = element.siblings('[data-toggle="datepicker"]');
  141. if (component.length) {
  142. component.on('click', function () {
  143. if (!element.prop('disabled')) {
  144. element.trigger('focus');
  145. }
  146. });
  147. }
  148. }
  149. };
  150. }
  151. ]);
  152. 'use strict';
  153. angular.module('$strap.directives').factory('$modal', [
  154. '$rootScope',
  155. '$compile',
  156. '$http',
  157. '$timeout',
  158. '$q',
  159. '$templateCache',
  160. '$strapConfig',
  161. function ($rootScope, $compile, $http, $timeout, $q, $templateCache, $strapConfig) {
  162. var ModalFactory = function ModalFactory(config) {
  163. function Modal(config) {
  164. var options = angular.extend({ show: true }, $strapConfig.modal, config), scope = options.scope ? options.scope : $rootScope.$new(), templateUrl = options.template;
  165. return $q.when($templateCache.get(templateUrl) || $http.get(templateUrl, { cache: true }).then(function (res) {
  166. return res.data;
  167. })).then(function onSuccess(template) {
  168. var id = templateUrl.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id;
  169. // grafana change, removed fade
  170. var $modal = $('<div class="modal hide" tabindex="-1"></div>').attr('id', id).html(template);
  171. if (options.modalClass)
  172. $modal.addClass(options.modalClass);
  173. $('body').append($modal);
  174. $timeout(function () {
  175. $compile($modal)(scope);
  176. });
  177. scope.$modal = function (name) {
  178. $modal.modal(name);
  179. };
  180. angular.forEach([
  181. 'show',
  182. 'hide'
  183. ], function (name) {
  184. scope[name] = function () {
  185. $modal.modal(name);
  186. };
  187. });
  188. scope.dismiss = scope.hide;
  189. angular.forEach([
  190. 'show',
  191. 'shown',
  192. 'hide',
  193. 'hidden'
  194. ], function (name) {
  195. $modal.on(name, function (ev) {
  196. scope.$emit('modal-' + name, ev);
  197. });
  198. });
  199. $modal.on('shown', function (ev) {
  200. $('input[autofocus], textarea[autofocus]', $modal).first().trigger('focus');
  201. });
  202. $modal.on('hidden', function (ev) {
  203. if (!options.persist)
  204. scope.$destroy();
  205. });
  206. scope.$on('$destroy', function () {
  207. $modal.remove();
  208. });
  209. $modal.modal(options);
  210. return $modal;
  211. });
  212. }
  213. return new Modal(config);
  214. };
  215. return ModalFactory;
  216. }
  217. ])
  218. 'use strict';
  219. angular.module('$strap.directives').directive('bsTabs', [
  220. '$parse',
  221. '$compile',
  222. '$timeout',
  223. function ($parse, $compile, $timeout) {
  224. var template = '<div class="tabs">' + '<ul class="nav nav-tabs">' + '<li ng-repeat="pane in panes" ng-class="{active:pane.active}">' + '<a data-target="#{{pane.id}}" data-index="{{$index}}" data-toggle="tab">{{pane.title}}</a>' + '</li>' + '</ul>' + '<div class="tab-content" ng-transclude>' + '</div>';
  225. return {
  226. restrict: 'A',
  227. require: '?ngModel',
  228. priority: 0,
  229. scope: true,
  230. template: template,
  231. replace: true,
  232. transclude: true,
  233. compile: function compile(tElement, tAttrs, transclude) {
  234. return function postLink(scope, iElement, iAttrs, controller) {
  235. var getter = $parse(iAttrs.bsTabs), setter = getter.assign, value = getter(scope);
  236. scope.panes = [];
  237. var $tabs = iElement.find('ul.nav-tabs');
  238. var $panes = iElement.find('div.tab-content');
  239. var activeTab = 0, id, title, active;
  240. $timeout(function () {
  241. $panes.find('[data-title], [data-tab]').each(function (index) {
  242. var $this = angular.element(this);
  243. id = 'tab-' + scope.$id + '-' + index;
  244. title = $this.data('title') || $this.data('tab');
  245. active = !active && $this.hasClass('active');
  246. $this.attr('id', id).addClass('tab-pane');
  247. if (iAttrs.fade)
  248. $this.addClass('fade');
  249. scope.panes.push({
  250. id: id,
  251. title: title,
  252. content: this.innerHTML,
  253. active: active
  254. });
  255. });
  256. if (scope.panes.length && !active) {
  257. $panes.find('.tab-pane:first-child').addClass('active' + (iAttrs.fade ? ' in' : ''));
  258. scope.panes[0].active = true;
  259. }
  260. });
  261. if (controller) {
  262. iElement.on('show', function (ev) {
  263. var $target = $(ev.target);
  264. scope.$apply(function () {
  265. controller.$setViewValue($target.data('index'));
  266. });
  267. });
  268. scope.$watch(iAttrs.ngModel, function (newValue, oldValue) {
  269. if (angular.isUndefined(newValue))
  270. return;
  271. activeTab = newValue;
  272. setTimeout(function () {
  273. // Check if we're still on the same tab before making the switch
  274. if(activeTab === newValue) {
  275. var $next = $($tabs[0].querySelectorAll('li')[newValue * 1]);
  276. if (!$next.hasClass('active')) {
  277. $next.children('a').tab('show');
  278. }
  279. }
  280. });
  281. });
  282. }
  283. };
  284. }
  285. };
  286. }
  287. ]);
  288. 'use strict';
  289. angular.module('$strap.directives').directive('bsTooltip', [
  290. '$parse',
  291. '$compile',
  292. function ($parse, $compile) {
  293. return {
  294. restrict: 'A',
  295. scope: true,
  296. link: function postLink(scope, element, attrs, ctrl) {
  297. var getter = $parse(attrs.bsTooltip), setter = getter.assign, value = getter(scope);
  298. scope.$watch(attrs.bsTooltip, function (newValue, oldValue) {
  299. if (newValue !== oldValue) {
  300. value = newValue;
  301. }
  302. });
  303. // Grafana change, always hide other tooltips
  304. if (true) {
  305. element.on('show', function (ev) {
  306. $('.tooltip.in').each(function () {
  307. var $this = $(this), tooltip = $this.data('tooltip');
  308. if (tooltip && !tooltip.$element.is(element)) {
  309. $this.tooltip('hide');
  310. }
  311. });
  312. });
  313. }
  314. element.tooltip({
  315. title: function () {
  316. return angular.isFunction(value) ? value.apply(null, arguments) : value;
  317. },
  318. html: true,
  319. container: 'body', // Grafana change
  320. });
  321. var tooltip = element.data('tooltip');
  322. tooltip.show = function () {
  323. var r = $.fn.tooltip.Constructor.prototype.show.apply(this, arguments);
  324. this.tip().data('tooltip', this);
  325. return r;
  326. };
  327. scope._tooltip = function (event) {
  328. element.tooltip(event);
  329. };
  330. scope.hide = function () {
  331. element.tooltip('hide');
  332. };
  333. scope.show = function () {
  334. element.tooltip('show');
  335. };
  336. scope.dismiss = scope.hide;
  337. }
  338. };
  339. }
  340. ]);
  341. 'use strict';
  342. angular.module('$strap.directives').directive('bsTypeahead', [
  343. '$parse',
  344. function ($parse) {
  345. return {
  346. restrict: 'A',
  347. require: '?ngModel',
  348. link: function postLink(scope, element, attrs, controller) {
  349. var getter = $parse(attrs.bsTypeahead), setter = getter.assign, value = getter(scope);
  350. scope.$watch(attrs.bsTypeahead, function (newValue, oldValue) {
  351. if (newValue !== oldValue) {
  352. value = newValue;
  353. }
  354. });
  355. element.attr('data-provide', 'typeahead');
  356. element.typeahead({
  357. source: function (query) {
  358. return angular.isFunction(value) ? value.apply(null, arguments) : value;
  359. },
  360. minLength: attrs.minLength || 1,
  361. items: attrs.items,
  362. updater: function (value) {
  363. if (controller) {
  364. scope.$apply(function () {
  365. controller.$setViewValue(value);
  366. });
  367. }
  368. scope.$emit('typeahead-updated', value);
  369. return value;
  370. }
  371. });
  372. var typeahead = element.data('typeahead');
  373. typeahead.lookup = function (ev) {
  374. var items;
  375. this.query = this.$element.val() || '';
  376. if (this.query.length < this.options.minLength) {
  377. return this.shown ? this.hide() : this;
  378. }
  379. items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source;
  380. return items ? this.process(items) : this;
  381. };
  382. if (!!attrs.matchAll) {
  383. typeahead.matcher = function (item) {
  384. return true;
  385. };
  386. }
  387. if (attrs.minLength === '0') {
  388. setTimeout(function () {
  389. element.on('focus', function () {
  390. element.val().length === 0 && setTimeout(element.typeahead.bind(element, 'lookup'), 200);
  391. });
  392. });
  393. }
  394. }
  395. };
  396. }
  397. ]);