cardCustomFields.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import { DatePicker } from '/client/lib/datepicker';
  2. import Cards from '/models/cards';
  3. Template.cardCustomFieldsPopup.helpers({
  4. hasCustomField() {
  5. const card = Utils.getCurrentCard();
  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 = Utils.getCurrentCard();
  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 = Utils.getCurrentCard();
  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. showWeek() {
  133. return this.date.get().week().toString();
  134. }
  135. showDate() {
  136. // this will start working once mquandalle:moment
  137. // is updated to at least moment.js 2.10.5
  138. // until then, the date is displayed in the "L" format
  139. return this.date.get().calendar(null, {
  140. sameElse: 'llll',
  141. });
  142. }
  143. showISODate() {
  144. return this.date.get().toISOString();
  145. }
  146. classes() {
  147. if (
  148. this.date.get().isBefore(this.now.get(), 'minute') &&
  149. this.now.get().isBefore(this.data().value)
  150. ) {
  151. return 'current';
  152. }
  153. return '';
  154. }
  155. showTitle() {
  156. return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
  157. }
  158. events() {
  159. return [
  160. {
  161. 'click .js-edit-date': Popup.open('cardCustomField-date'),
  162. },
  163. ];
  164. }
  165. }.register('cardCustomField-date'));
  166. // cardCustomField-datePopup
  167. (class extends DatePicker {
  168. onCreated() {
  169. super.onCreated();
  170. const self = this;
  171. self.card = Utils.getCurrentCard();
  172. self.customFieldId = this.data()._id;
  173. this.data().value && this.date.set(moment(this.data().value));
  174. }
  175. _storeDate(date) {
  176. this.card.setCustomField(this.customFieldId, date);
  177. }
  178. _deleteDate() {
  179. this.card.setCustomField(this.customFieldId, '');
  180. }
  181. }.register('cardCustomField-datePopup'));
  182. // cardCustomField-dropdown
  183. (class extends CardCustomField {
  184. onCreated() {
  185. super.onCreated();
  186. this._items = this.data().definition.settings.dropdownItems;
  187. this.items = this._items.slice(0);
  188. this.items.unshift({
  189. _id: '',
  190. name: TAPi18n.__('custom-field-dropdown-none'),
  191. });
  192. }
  193. selectedItem() {
  194. const selected = this._items.find(item => {
  195. return item._id === this.data().value;
  196. });
  197. return selected
  198. ? selected.name
  199. : TAPi18n.__('custom-field-dropdown-unknown');
  200. }
  201. events() {
  202. return [
  203. {
  204. 'submit .js-card-customfield-dropdown'(event) {
  205. event.preventDefault();
  206. const value = this.find('select').value;
  207. this.card.setCustomField(this.customFieldId, value);
  208. },
  209. },
  210. ];
  211. }
  212. }.register('cardCustomField-dropdown'));
  213. // cardCustomField-stringtemplate
  214. (class extends CardCustomField {
  215. onCreated() {
  216. super.onCreated();
  217. this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat;
  218. this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator;
  219. this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
  220. }
  221. formattedValue() {
  222. return (this.data().value ?? [])
  223. .filter(value => !!value.trim())
  224. .map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value))
  225. .join(this.stringtemplateSeparator ?? '');
  226. }
  227. getItems() {
  228. return Array.from(this.findAll('input'))
  229. .map(input => input.value)
  230. .filter(value => !!value.trim());
  231. }
  232. events() {
  233. return [
  234. {
  235. 'submit .js-card-customfield-stringtemplate'(event) {
  236. event.preventDefault();
  237. const items = this.stringtemplateItems.get()
  238. this.card.setCustomField(this.customFieldId, items);
  239. },
  240. 'keydown .js-card-customfield-stringtemplate-item'(event) {
  241. if (event.keyCode === 13) {
  242. event.preventDefault();
  243. if (event.metaKey || event.ctrlKey) {
  244. this.find('button[type=submit]').click();
  245. } else if (event.target.value.trim()) {
  246. const inputLast = this.find('input.last');
  247. let items = this.getItems();
  248. if (event.target === inputLast) {
  249. inputLast.value = '';
  250. } else if (event.target.nextSibling === inputLast) {
  251. inputLast.focus();
  252. } else {
  253. event.target.blur();
  254. const idx = Array.from(this.findAll('input')).indexOf(
  255. event.target,
  256. );
  257. items.splice(idx + 1, 0, '');
  258. Tracker.afterFlush(() => {
  259. const element = this.findAll('input')[idx + 1];
  260. element.focus();
  261. element.value = '';
  262. });
  263. }
  264. this.stringtemplateItems.set(items);
  265. }
  266. }
  267. },
  268. 'blur .js-card-customfield-stringtemplate-item'(event) {
  269. if (
  270. !event.target.value.trim() ||
  271. event.target === this.find('input.last')
  272. ) {
  273. const items = this.getItems();
  274. this.stringtemplateItems.set(items);
  275. this.find('input.last').value = '';
  276. }
  277. },
  278. 'click .js-close-inlined-form'(event) {
  279. this.stringtemplateItems.set(this.data().value ?? []);
  280. },
  281. },
  282. ];
  283. }
  284. }.register('cardCustomField-stringtemplate'));