value_select_dropdown.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import angular from 'angular';
  2. import _ from 'lodash';
  3. import coreModule from '../core_module';
  4. export class ValueSelectDropdownCtrl {
  5. dropdownVisible: any;
  6. highlightIndex: any;
  7. linkText: any;
  8. oldVariableText: any;
  9. options: any;
  10. search: any;
  11. selectedTags: any;
  12. selectedValues: any;
  13. tags: any;
  14. variable: any;
  15. hide: any;
  16. onUpdated: any;
  17. /** @ngInject */
  18. constructor(private $q: any) {}
  19. show() {
  20. this.oldVariableText = this.variable.current.text;
  21. this.highlightIndex = -1;
  22. this.options = this.variable.options;
  23. this.selectedValues = _.filter(this.options, { selected: true });
  24. this.tags = _.map(this.variable.tags, value => {
  25. let tag = { text: value, selected: false };
  26. _.each(this.variable.current.tags, tagObj => {
  27. if (tagObj.text === value) {
  28. tag = tagObj;
  29. }
  30. });
  31. return tag;
  32. });
  33. this.search = {
  34. query: '',
  35. options: this.options.slice(0, Math.min(this.options.length, 1000)),
  36. };
  37. this.dropdownVisible = true;
  38. }
  39. updateLinkText() {
  40. const current = this.variable.current;
  41. if (current.tags && current.tags.length) {
  42. // filer out values that are in selected tags
  43. const selectedAndNotInTag = _.filter(this.variable.options, option => {
  44. if (!option.selected) {
  45. return false;
  46. }
  47. for (let i = 0; i < current.tags.length; i++) {
  48. const tag = current.tags[i];
  49. if (_.indexOf(tag.values, option.value) !== -1) {
  50. return false;
  51. }
  52. }
  53. return true;
  54. });
  55. // convert values to text
  56. const currentTexts = _.map(selectedAndNotInTag, 'text');
  57. // join texts
  58. this.linkText = currentTexts.join(' + ');
  59. if (this.linkText.length > 0) {
  60. this.linkText += ' + ';
  61. }
  62. } else {
  63. this.linkText = this.variable.current.text;
  64. }
  65. }
  66. clearSelections() {
  67. this.selectedValues = _.filter(this.options, { selected: true });
  68. if (this.selectedValues.length > 1) {
  69. _.each(this.options, option => {
  70. option.selected = false;
  71. });
  72. } else {
  73. _.each(this.search.options, option => {
  74. option.selected = true;
  75. });
  76. }
  77. this.selectionsChanged(false);
  78. }
  79. selectTag(tag: any) {
  80. tag.selected = !tag.selected;
  81. let tagValuesPromise;
  82. if (!tag.values) {
  83. tagValuesPromise = this.variable.getValuesForTag(tag.text);
  84. } else {
  85. tagValuesPromise = this.$q.when(tag.values);
  86. }
  87. return tagValuesPromise.then((values: any) => {
  88. tag.values = values;
  89. tag.valuesText = values.join(' + ');
  90. _.each(this.options, option => {
  91. if (_.indexOf(tag.values, option.value) !== -1) {
  92. option.selected = tag.selected;
  93. }
  94. });
  95. this.selectionsChanged(false);
  96. });
  97. }
  98. keyDown(evt: any) {
  99. if (evt.keyCode === 27) {
  100. this.hide();
  101. }
  102. if (evt.keyCode === 40) {
  103. this.moveHighlight(1);
  104. }
  105. if (evt.keyCode === 38) {
  106. this.moveHighlight(-1);
  107. }
  108. if (evt.keyCode === 13) {
  109. if (this.search.options.length === 0) {
  110. this.commitChanges();
  111. } else {
  112. this.selectValue(this.search.options[this.highlightIndex], {}, true, false);
  113. }
  114. }
  115. if (evt.keyCode === 32) {
  116. this.selectValue(this.search.options[this.highlightIndex], {}, false, false);
  117. }
  118. }
  119. moveHighlight(direction: number) {
  120. this.highlightIndex = (this.highlightIndex + direction) % this.search.options.length;
  121. }
  122. selectValue(option: any, event: any, commitChange?: boolean, excludeOthers?: boolean) {
  123. if (!option) {
  124. return;
  125. }
  126. option.selected = this.variable.multi ? !option.selected : true;
  127. commitChange = commitChange || false;
  128. excludeOthers = excludeOthers || false;
  129. const setAllExceptCurrentTo = (newValue: any) => {
  130. _.each(this.options, other => {
  131. if (option !== other) {
  132. other.selected = newValue;
  133. }
  134. });
  135. };
  136. // commit action (enter key), should not deselect it
  137. if (commitChange) {
  138. option.selected = true;
  139. }
  140. if (option.text === 'All' || excludeOthers) {
  141. setAllExceptCurrentTo(false);
  142. commitChange = true;
  143. } else if (!this.variable.multi) {
  144. setAllExceptCurrentTo(false);
  145. commitChange = true;
  146. } else if (event.ctrlKey || event.metaKey || event.shiftKey) {
  147. commitChange = true;
  148. setAllExceptCurrentTo(false);
  149. }
  150. this.selectionsChanged(commitChange);
  151. }
  152. selectionsChanged(commitChange: boolean) {
  153. this.selectedValues = _.filter(this.options, { selected: true });
  154. if (this.selectedValues.length > 1) {
  155. if (this.selectedValues[0].text === 'All') {
  156. this.selectedValues[0].selected = false;
  157. this.selectedValues = this.selectedValues.slice(1, this.selectedValues.length);
  158. }
  159. }
  160. // validate selected tags
  161. _.each(this.tags, tag => {
  162. if (tag.selected) {
  163. _.each(tag.values, value => {
  164. if (!_.find(this.selectedValues, { value: value })) {
  165. tag.selected = false;
  166. }
  167. });
  168. }
  169. });
  170. this.selectedTags = _.filter(this.tags, { selected: true });
  171. this.variable.current.value = _.map(this.selectedValues, 'value');
  172. this.variable.current.text = _.map(this.selectedValues, 'text').join(' + ');
  173. this.variable.current.tags = this.selectedTags;
  174. if (!this.variable.multi) {
  175. this.variable.current.value = this.selectedValues[0].value;
  176. }
  177. if (commitChange) {
  178. this.commitChanges();
  179. }
  180. }
  181. commitChanges() {
  182. // if we have a search query and no options use that
  183. if (this.search.options.length === 0 && this.search.query.length > 0) {
  184. this.variable.current = { text: this.search.query, value: this.search.query };
  185. } else if (this.selectedValues.length === 0) {
  186. // make sure one option is selected
  187. this.options[0].selected = true;
  188. this.selectionsChanged(false);
  189. }
  190. this.dropdownVisible = false;
  191. this.updateLinkText();
  192. if (this.variable.current.text !== this.oldVariableText) {
  193. this.onUpdated();
  194. }
  195. }
  196. queryChanged() {
  197. this.highlightIndex = -1;
  198. this.search.options = _.filter(this.options, option => {
  199. return option.text.toLowerCase().indexOf(this.search.query.toLowerCase()) !== -1;
  200. });
  201. this.search.options = this.search.options.slice(0, Math.min(this.search.options.length, 1000));
  202. }
  203. init() {
  204. this.selectedTags = this.variable.current.tags || [];
  205. this.updateLinkText();
  206. }
  207. }
  208. /** @ngInject */
  209. export function valueSelectDropdown($compile: any, $window: any, $timeout: any, $rootScope: any) {
  210. return {
  211. scope: { dashboard: '=', variable: '=', onUpdated: '&' },
  212. templateUrl: 'public/app/partials/valueSelectDropdown.html',
  213. controller: 'ValueSelectDropdownCtrl',
  214. controllerAs: 'vm',
  215. bindToController: true,
  216. link: (scope: any, elem: any) => {
  217. const bodyEl = angular.element($window.document.body);
  218. const linkEl = elem.find('.variable-value-link');
  219. const inputEl = elem.find('input');
  220. function openDropdown() {
  221. inputEl.css('width', Math.max(linkEl.width(), 80) + 'px');
  222. inputEl.show();
  223. linkEl.hide();
  224. inputEl.focus();
  225. $timeout(
  226. () => {
  227. bodyEl.on('click', bodyOnClick);
  228. },
  229. 0,
  230. false
  231. );
  232. }
  233. function switchToLink() {
  234. inputEl.hide();
  235. linkEl.show();
  236. bodyEl.off('click', bodyOnClick);
  237. }
  238. function bodyOnClick(e: any) {
  239. if (elem.has(e.target).length === 0) {
  240. scope.$apply(() => {
  241. scope.vm.commitChanges();
  242. });
  243. }
  244. }
  245. scope.$watch('vm.dropdownVisible', (newValue: any) => {
  246. if (newValue) {
  247. openDropdown();
  248. } else {
  249. switchToLink();
  250. }
  251. });
  252. scope.vm.dashboard.on(
  253. 'template-variable-value-updated',
  254. () => {
  255. scope.vm.updateLinkText();
  256. },
  257. scope
  258. );
  259. scope.vm.init();
  260. },
  261. };
  262. }
  263. coreModule.controller('ValueSelectDropdownCtrl', ValueSelectDropdownCtrl);
  264. coreModule.directive('valueSelectDropdown', valueSelectDropdown);