value_select_dropdown.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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) {}
  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. _.each(this.options, option => {
  68. option.selected = false;
  69. });
  70. this.selectionsChanged(false);
  71. }
  72. selectTag(tag) {
  73. tag.selected = !tag.selected;
  74. let tagValuesPromise;
  75. if (!tag.values) {
  76. tagValuesPromise = this.variable.getValuesForTag(tag.text);
  77. } else {
  78. tagValuesPromise = this.$q.when(tag.values);
  79. }
  80. return tagValuesPromise.then(values => {
  81. tag.values = values;
  82. tag.valuesText = values.join(' + ');
  83. _.each(this.options, option => {
  84. if (_.indexOf(tag.values, option.value) !== -1) {
  85. option.selected = tag.selected;
  86. }
  87. });
  88. this.selectionsChanged(false);
  89. });
  90. }
  91. keyDown(evt) {
  92. if (evt.keyCode === 27) {
  93. this.hide();
  94. }
  95. if (evt.keyCode === 40) {
  96. this.moveHighlight(1);
  97. }
  98. if (evt.keyCode === 38) {
  99. this.moveHighlight(-1);
  100. }
  101. if (evt.keyCode === 13) {
  102. if (this.search.options.length === 0) {
  103. this.commitChanges();
  104. } else {
  105. this.selectValue(this.search.options[this.highlightIndex], {}, true, false);
  106. }
  107. }
  108. if (evt.keyCode === 32) {
  109. this.selectValue(this.search.options[this.highlightIndex], {}, false, false);
  110. }
  111. }
  112. moveHighlight(direction) {
  113. this.highlightIndex = (this.highlightIndex + direction) % this.search.options.length;
  114. }
  115. selectValue(option, event, commitChange?, excludeOthers?) {
  116. if (!option) {
  117. return;
  118. }
  119. option.selected = this.variable.multi ? !option.selected : true;
  120. commitChange = commitChange || false;
  121. excludeOthers = excludeOthers || false;
  122. const setAllExceptCurrentTo = newValue => {
  123. _.each(this.options, other => {
  124. if (option !== other) {
  125. other.selected = newValue;
  126. }
  127. });
  128. };
  129. // commit action (enter key), should not deselect it
  130. if (commitChange) {
  131. option.selected = true;
  132. }
  133. if (option.text === 'All' || excludeOthers) {
  134. setAllExceptCurrentTo(false);
  135. commitChange = true;
  136. } else if (!this.variable.multi) {
  137. setAllExceptCurrentTo(false);
  138. commitChange = true;
  139. } else if (event.ctrlKey || event.metaKey || event.shiftKey) {
  140. commitChange = true;
  141. setAllExceptCurrentTo(false);
  142. }
  143. this.selectionsChanged(commitChange);
  144. }
  145. selectionsChanged(commitChange) {
  146. this.selectedValues = _.filter(this.options, { selected: true });
  147. if (this.selectedValues.length > 1) {
  148. if (this.selectedValues[0].text === 'All') {
  149. this.selectedValues[0].selected = false;
  150. this.selectedValues = this.selectedValues.slice(1, this.selectedValues.length);
  151. }
  152. }
  153. // validate selected tags
  154. _.each(this.tags, tag => {
  155. if (tag.selected) {
  156. _.each(tag.values, value => {
  157. if (!_.find(this.selectedValues, { value: value })) {
  158. tag.selected = false;
  159. }
  160. });
  161. }
  162. });
  163. this.selectedTags = _.filter(this.tags, { selected: true });
  164. this.variable.current.value = _.map(this.selectedValues, 'value');
  165. this.variable.current.text = _.map(this.selectedValues, 'text').join(' + ');
  166. this.variable.current.tags = this.selectedTags;
  167. if (!this.variable.multi) {
  168. this.variable.current.value = this.selectedValues[0].value;
  169. }
  170. if (commitChange) {
  171. this.commitChanges();
  172. }
  173. }
  174. commitChanges() {
  175. // if we have a search query and no options use that
  176. if (this.search.options.length === 0 && this.search.query.length > 0) {
  177. this.variable.current = { text: this.search.query, value: this.search.query };
  178. } else if (this.selectedValues.length === 0) {
  179. // make sure one option is selected
  180. this.options[0].selected = true;
  181. this.selectionsChanged(false);
  182. }
  183. this.dropdownVisible = false;
  184. this.updateLinkText();
  185. if (this.variable.current.text !== this.oldVariableText) {
  186. this.onUpdated();
  187. }
  188. }
  189. queryChanged() {
  190. this.highlightIndex = -1;
  191. this.search.options = _.filter(this.options, option => {
  192. return option.text.toLowerCase().indexOf(this.search.query.toLowerCase()) !== -1;
  193. });
  194. this.search.options = this.search.options.slice(0, Math.min(this.search.options.length, 1000));
  195. }
  196. init() {
  197. this.selectedTags = this.variable.current.tags || [];
  198. this.updateLinkText();
  199. }
  200. }
  201. /** @ngInject */
  202. export function valueSelectDropdown($compile, $window, $timeout, $rootScope) {
  203. return {
  204. scope: { variable: '=', onUpdated: '&' },
  205. templateUrl: 'public/app/partials/valueSelectDropdown.html',
  206. controller: 'ValueSelectDropdownCtrl',
  207. controllerAs: 'vm',
  208. bindToController: true,
  209. link: function(scope, elem) {
  210. const bodyEl = angular.element($window.document.body);
  211. const linkEl = elem.find('.variable-value-link');
  212. const inputEl = elem.find('input');
  213. function openDropdown() {
  214. inputEl.css('width', Math.max(linkEl.width(), 80) + 'px');
  215. inputEl.show();
  216. linkEl.hide();
  217. inputEl.focus();
  218. $timeout(
  219. function() {
  220. bodyEl.on('click', bodyOnClick);
  221. },
  222. 0,
  223. false
  224. );
  225. }
  226. function switchToLink() {
  227. inputEl.hide();
  228. linkEl.show();
  229. bodyEl.off('click', bodyOnClick);
  230. }
  231. function bodyOnClick(e) {
  232. if (elem.has(e.target).length === 0) {
  233. scope.$apply(function() {
  234. scope.vm.commitChanges();
  235. });
  236. }
  237. }
  238. scope.$watch('vm.dropdownVisible', newValue => {
  239. if (newValue) {
  240. openDropdown();
  241. } else {
  242. switchToLink();
  243. }
  244. });
  245. const cleanUp = $rootScope.$on('template-variable-value-updated', () => {
  246. scope.vm.updateLinkText();
  247. });
  248. scope.$on('$destroy', () => {
  249. cleanUp();
  250. });
  251. scope.vm.init();
  252. },
  253. };
  254. }
  255. coreModule.controller('ValueSelectDropdownCtrl', ValueSelectDropdownCtrl);
  256. coreModule.directive('valueSelectDropdown', valueSelectDropdown);