cardCustomFields.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import { DatePicker } from '/client/lib/datepicker';
  2. import moment from 'moment';
  3. import Cards from '/models/cards';
  4. Template.cardCustomFieldsPopup.helpers({
  5. hasCustomField() {
  6. const card = Cards.findOne(Session.get('currentCard'));
  7. const customFieldId = this._id;
  8. return card.customFieldIndex(customFieldId) > -1;
  9. },
  10. });
  11. Template.cardCustomFieldsPopup.events({
  12. 'click .js-select-field'(event) {
  13. const card = Cards.findOne(Session.get('currentCard'));
  14. const customFieldId = this._id;
  15. card.toggleCustomField(customFieldId);
  16. event.preventDefault();
  17. },
  18. 'click .js-settings'(event) {
  19. EscapeActions.executeUpTo('detailsPane');
  20. Sidebar.setView('customFields');
  21. event.preventDefault();
  22. },
  23. });
  24. // cardCustomField
  25. const CardCustomField = BlazeComponent.extendComponent({
  26. getTemplate() {
  27. return `cardCustomField-${this.data().definition.type}`;
  28. },
  29. onCreated() {
  30. const self = this;
  31. self.card = Cards.findOne(Session.get('currentCard'));
  32. self.customFieldId = this.data()._id;
  33. },
  34. canModifyCard() {
  35. return (
  36. Meteor.user() &&
  37. Meteor.user().isBoardMember() &&
  38. !Meteor.user().isCommentOnly()
  39. );
  40. },
  41. });
  42. CardCustomField.register('cardCustomField');
  43. // cardCustomField-text
  44. (class extends CardCustomField {
  45. onCreated() {
  46. super.onCreated();
  47. }
  48. events() {
  49. return [
  50. {
  51. 'submit .js-card-customfield-text'(event) {
  52. event.preventDefault();
  53. const value = this.currentComponent().getValue();
  54. this.card.setCustomField(this.customFieldId, value);
  55. },
  56. },
  57. ];
  58. }
  59. }.register('cardCustomField-text'));
  60. // cardCustomField-number
  61. (class extends CardCustomField {
  62. onCreated() {
  63. super.onCreated();
  64. }
  65. events() {
  66. return [
  67. {
  68. 'submit .js-card-customfield-number'(event) {
  69. event.preventDefault();
  70. const value = parseInt(this.find('input').value, 10);
  71. this.card.setCustomField(this.customFieldId, value);
  72. },
  73. },
  74. ];
  75. }
  76. }.register('cardCustomField-number'));
  77. // cardCustomField-checkbox
  78. (class extends CardCustomField {
  79. onCreated() {
  80. super.onCreated();
  81. }
  82. toggleItem() {
  83. this.card.setCustomField(this.customFieldId, !this.data().value);
  84. }
  85. events() {
  86. return [
  87. {
  88. 'click .js-checklist-item .check-box-container': this.toggleItem,
  89. },
  90. ];
  91. }
  92. }.register('cardCustomField-checkbox'));
  93. // cardCustomField-currency
  94. (class extends CardCustomField {
  95. onCreated() {
  96. super.onCreated();
  97. this.currencyCode = this.data().definition.settings.currencyCode;
  98. }
  99. formattedValue() {
  100. const locale = TAPi18n.getLanguage();
  101. return new Intl.NumberFormat(locale, {
  102. style: 'currency',
  103. currency: this.currencyCode,
  104. }).format(this.data().value);
  105. }
  106. events() {
  107. return [
  108. {
  109. 'submit .js-card-customfield-currency'(event) {
  110. event.preventDefault();
  111. // To allow input separated by comma, the comma is replaced by a period.
  112. const value = Number(this.find('input').value.replace(/,/i, '.'), 10);
  113. this.card.setCustomField(this.customFieldId, value);
  114. },
  115. },
  116. ];
  117. }
  118. }.register('cardCustomField-currency'));
  119. // cardCustomField-date
  120. (class extends CardCustomField {
  121. onCreated() {
  122. super.onCreated();
  123. const self = this;
  124. self.date = ReactiveVar();
  125. self.now = ReactiveVar(moment());
  126. window.setInterval(() => {
  127. self.now.set(moment());
  128. }, 60000);
  129. self.autorun(() => {
  130. self.date.set(moment(self.data().value));
  131. });
  132. }
  133. showDate() {
  134. // this will start working once mquandalle:moment
  135. // is updated to at least moment.js 2.10.5
  136. // until then, the date is displayed in the "L" format
  137. return this.date.get().calendar(null, {
  138. sameElse: 'llll',
  139. });
  140. }
  141. showISODate() {
  142. return this.date.get().toISOString();
  143. }
  144. classes() {
  145. if (
  146. this.date.get().isBefore(this.now.get(), 'minute') &&
  147. this.now.get().isBefore(this.data().value)
  148. ) {
  149. return 'current';
  150. }
  151. return '';
  152. }
  153. showTitle() {
  154. return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
  155. }
  156. events() {
  157. return [
  158. {
  159. 'click .js-edit-date': Popup.open('cardCustomField-date'),
  160. },
  161. ];
  162. }
  163. }.register('cardCustomField-date'));
  164. // cardCustomField-datePopup
  165. (class extends DatePicker {
  166. onCreated() {
  167. super.onCreated();
  168. const self = this;
  169. self.card = Cards.findOne(Session.get('currentCard'));
  170. self.customFieldId = this.data()._id;
  171. this.data().value && this.date.set(moment(this.data().value));
  172. }
  173. _storeDate(date) {
  174. this.card.setCustomField(this.customFieldId, date);
  175. }
  176. _deleteDate() {
  177. this.card.setCustomField(this.customFieldId, '');
  178. }
  179. }.register('cardCustomField-datePopup'));
  180. // cardCustomField-dropdown
  181. (class extends CardCustomField {
  182. onCreated() {
  183. super.onCreated();
  184. this._items = this.data().definition.settings.dropdownItems;
  185. this.items = this._items.slice(0);
  186. this.items.unshift({
  187. _id: '',
  188. name: TAPi18n.__('custom-field-dropdown-none'),
  189. });
  190. }
  191. selectedItem() {
  192. const selected = this._items.find(item => {
  193. return item._id === this.data().value;
  194. });
  195. return selected
  196. ? selected.name
  197. : TAPi18n.__('custom-field-dropdown-unknown');
  198. }
  199. events() {
  200. return [
  201. {
  202. 'submit .js-card-customfield-dropdown'(event) {
  203. event.preventDefault();
  204. const value = this.find('select').value;
  205. this.card.setCustomField(this.customFieldId, value);
  206. },
  207. },
  208. ];
  209. }
  210. }.register('cardCustomField-dropdown'));
  211. // cardCustomField-stringtemplate
  212. (class extends CardCustomField {
  213. onCreated() {
  214. super.onCreated();
  215. this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat;
  216. this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator;
  217. this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
  218. }
  219. formattedValue() {
  220. return (this.data().value ?? [])
  221. .filter(value => !!value.trim())
  222. .map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value))
  223. .join(this.stringtemplateSeparator ?? '');
  224. }
  225. getItems() {
  226. return Array.from(this.findAll('input'))
  227. .map(input => input.value)
  228. .filter(value => !!value.trim());
  229. }
  230. events() {
  231. return [
  232. {
  233. 'submit .js-card-customfield-stringtemplate'(event) {
  234. event.preventDefault();
  235. const items = this.getItems();
  236. this.card.setCustomField(this.customFieldId, items);
  237. },
  238. 'keydown .js-card-customfield-stringtemplate-item'(event) {
  239. if (event.keyCode === 13) {
  240. event.preventDefault();
  241. if (event.metaKey || event.ctrlKey) {
  242. this.find('button[type=submit]').click();
  243. } else if (event.target.value.trim()) {
  244. const inputLast = this.find('input.last');
  245. let items = this.getItems();
  246. if (event.target === inputLast) {
  247. inputLast.value = '';
  248. } else if (event.target.nextSibling === inputLast) {
  249. inputLast.focus();
  250. } else {
  251. event.target.blur();
  252. const idx = Array.from(this.findAll('input')).indexOf(
  253. event.target,
  254. );
  255. items.splice(idx + 1, 0, '');
  256. Tracker.afterFlush(() => {
  257. const element = this.findAll('input')[idx + 1];
  258. element.focus();
  259. element.value = '';
  260. });
  261. }
  262. this.stringtemplateItems.set(items);
  263. }
  264. }
  265. },
  266. 'blur .js-card-customfield-stringtemplate-item'(event) {
  267. if (
  268. !event.target.value.trim() ||
  269. event.target === this.find('input.last')
  270. ) {
  271. const items = this.getItems();
  272. this.stringtemplateItems.set(items);
  273. this.find('input.last').value = '';
  274. }
  275. },
  276. 'click .js-close-inlined-form'(event) {
  277. this.stringtemplateItems.set(this.data().value ?? []);
  278. },
  279. },
  280. ];
  281. }
  282. }.register('cardCustomField-stringtemplate'));