cardCustomFields.js 8.1 KB

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