lib/amd/src/tag.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * AJAX helper for the tag management page.
  17. *
  18. * @module core/tag
  19. * @copyright 2015 Marina Glancy
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. * @since 3.0
  22. */
  23. define([
  24. 'jquery',
  25. 'core/ajax',
  26. 'core/templates',
  27. 'core/notification',
  28. 'core/str',
  29. 'core/modal_factory',
  30. 'core/modal_events',
  31. 'core/pending',
  32. ], function(
  33. $,
  34. ajax,
  35. templates,
  36. notification,
  37. str,
  38. ModalFactory,
  39. ModalEvents,
  40. Pending
  41. ) {
  42. return /** @alias module:core/tag */ {
  43. /**
  44. * Initialises tag index page.
  45. *
  46. * @method initTagindexPage
  47. */
  48. initTagindexPage: function() {
  49. // Click handler for changing tag type.
  50. $('body').delegate('.tagarea[data-ta] a[data-quickload=1]', 'click', function(e) {
  51. var pendingPromise = new Pending('core/tag:initTagindexPage');
  52. e.preventDefault();
  53. var target = $(this);
  54. var query = target[0].search.replace(/^\?/, '');
  55. var tagarea = target.closest('.tagarea[data-ta]');
  56. var args = query.split('&').reduce(function(s, c) {
  57. var t = c.split('=');
  58. s[t[0]] = decodeURIComponent(t[1]);
  59. return s;
  60. }, {});
  61. ajax.call([{
  62. methodname: 'core_tag_get_tagindex',
  63. args: {tagindex: args}
  64. }])[0]
  65. .then(function(data) {
  66. return templates.render('core_tag/index', data);
  67. })
  68. .then(function(html, js) {
  69. templates.replaceNode(tagarea, html, js);
  70. return;
  71. })
  72. .always(pendingPromise.resolve)
  73. .catch(notification.exception);
  74. });
  75. },
  76. /**
  77. * Initialises tag management page.
  78. *
  79. * @method initManagePage
  80. */
  81. initManagePage: function() {
  82. // Set cell 'time modified' to 'now' when any of the element is updated in this row.
  83. $('body').on('updated', '[data-inplaceeditable]', function(e) {
  84. var pendingPromise = new Pending('core/tag:initManagePage');
  85. str.get_strings([
  86. {
  87. key: 'selecttag',
  88. component: 'core_tag',
  89. },
  90. {
  91. key: 'now',
  92. component: 'core',
  93. },
  94. ])
  95. .then(function(result) {
  96. $('label[for="tagselect' + e.ajaxreturn.itemid + '"]').html(result[0]);
  97. $(e.target).closest('tr').find('td.col-timemodified').html(result[1]);
  98. return;
  99. })
  100. .always(pendingPromise.resolve)
  101. .catch(notification.exception);
  102. if (e.ajaxreturn.itemtype === 'tagflag') {
  103. var row = $(e.target).closest('tr');
  104. if (e.ajaxreturn.value === '0') {
  105. row.removeClass('table-warning');
  106. } else {
  107. row.addClass('table-warning');
  108. }
  109. }
  110. });
  111. // Confirmation for single tag delete link.
  112. $('.tag-management-table').delegate('a.tagdelete', 'click', function(e) {
  113. var pendingPromise = new Pending('core/tag:tagdelete');
  114. e.preventDefault();
  115. var href = $(this).attr('href');
  116. str.get_strings([
  117. {key: 'delete', component: 'core'},
  118. {key: 'confirmdeletetag', component: 'tag'},
  119. {key: 'yes', component: 'core'},
  120. {key: 'no', component: 'core'},
  121. ])
  122. .then(function(s) {
  123. return notification.confirm(s[0], s[1], s[2], s[3], function() {
  124. window.location.href = href;
  125. });
  126. })
  127. .always(pendingPromise.resolve)
  128. .catch(notification.exception);
  129. });
  130. // Confirmation for bulk tag delete button.
  131. $("#tag-management-delete").click(function(e) {
  132. var form = $(this).closest('form').get(0);
  133. var cnt = $(form).find("input[data-togglegroup='tags-manage'][data-toggle='slave']:checked").length;
  134. if (!cnt) {
  135. return;
  136. }
  137. var pendingPromise = new Pending('core/tag:tag-management-delete');
  138. var tempElement = $("<input type='hidden'/>").attr('name', this.name);
  139. e.preventDefault();
  140. str.get_strings([
  141. {key: 'delete', component: 'core'},
  142. {key: 'confirmdeletetags', component: 'tag'},
  143. {key: 'yes', component: 'core'},
  144. {key: 'no', component: 'core'},
  145. ])
  146. .then(function(s) {
  147. return notification.confirm(s[0], s[1], s[2], s[3], function() {
  148. tempElement.appendTo(form);
  149. form.submit();
  150. });
  151. })
  152. .always(pendingPromise.resolve)
  153. .catch(notification.exception);
  154. });
  155. // Confirmation for bulk tag combine button.
  156. $("#tag-management-combine").click(function(e) {
  157. var pendingPromise = new Pending('core/tag:tag-management-combine');
  158. e.preventDefault();
  159. var form = $(this).closest('form').get(0);
  160. var tags = $(form).find("input[data-togglegroup='tags-manage'][data-toggle='slave']:checked");
  161. if (tags.length <= 1) {
  162. str.get_strings([
  163. {key: 'combineselected', component: 'tag'},
  164. {key: 'selectmultipletags', component: 'tag'},
  165. {key: 'ok'},
  166. ])
  167. .then(function(s) {
  168. return notification.alert(s[0], s[1], s[2]);
  169. })
  170. .always(pendingPromise.resolve)
  171. .catch(notification.exception);
  172. return;
  173. }
  174. var tempElement = $("<input type='hidden'/>").attr('name', this.name);
  175. var saveButtonText = '';
  176. var tagOptions = [];
  177. tags.each(function() {
  178. var tagid = $(this).val(),
  179. tagname = $('.inplaceeditable[data-itemtype=tagname][data-itemid=' + tagid + ']').attr('data-value');
  180. tagOptions.push({
  181. id: tagid,
  182. name: tagname
  183. });
  184. });
  185. str.get_strings([
  186. {key: 'combineselected', component: 'tag'},
  187. {key: 'continue', component: 'core'}
  188. ])
  189. .then(function(langStrings) {
  190. var modalTitle = langStrings[0];
  191. saveButtonText = langStrings[1];
  192. var templateContext = {
  193. tags: tagOptions
  194. };
  195. return ModalFactory.create({
  196. title: modalTitle,
  197. body: templates.render('core_tag/combine_tags', templateContext),
  198. type: ModalFactory.types.SAVE_CANCEL
  199. });
  200. })
  201. .then(function(modal) {
  202. modal.setSaveButtonText(saveButtonText);
  203. return modal;
  204. })
  205. .then(function(modal) {
  206. // Handle save event.
  207. modal.getRoot().on(ModalEvents.save, function(e) {
  208. e.preventDefault();
  209. // Append this temp element in the form in the tags list, not the form in the modal. Confusing, right?!?
  210. tempElement.appendTo(form);
  211. // Get the selected tag from the modal.
  212. var maintag = $('input[name=maintag]:checked', '#combinetags_form').val();
  213. // Append this in the tags list form.
  214. $("<input type='hidden'/>").attr('name', 'maintag').attr('value', maintag).appendTo(form);
  215. // Submit the tags list form.
  216. form.submit();
  217. });
  218. // Handle hidden event.
  219. modal.getRoot().on(ModalEvents.hidden, function() {
  220. // Destroy when hidden.
  221. modal.destroy();
  222. });
  223. modal.show();
  224. // Tick the first option.
  225. $('#combinetags_form input[type=radio]').first().focus().prop('checked', true);
  226. return;
  227. })
  228. .always(pendingPromise.resolve)
  229. .catch(notification.exception);
  230. });
  231. // When user changes tag name to some name that already exists suggest to combine the tags.
  232. $('body').on('updatefailed', '[data-inplaceeditable][data-itemtype=tagname]', function(e) {
  233. var exception = e.exception; // The exception object returned by the callback.
  234. var newvalue = e.newvalue; // The value that user tried to udpated the element to.
  235. var tagid = $(e.target).attr('data-itemid');
  236. if (exception.errorcode === 'namesalreadybeeingused') {
  237. var pendingPromise = new Pending('core/tag:updatefailed');
  238. e.preventDefault(); // This will prevent default error dialogue.
  239. str.get_strings([
  240. {key: 'confirm', component: 'core'},
  241. {key: 'nameuseddocombine', component: 'tag'},
  242. {key: 'yes', component: 'core'},
  243. {key: 'cancel', component: 'core'},
  244. ])
  245. .then(function(s) {
  246. return notification.confirm(s[0], s[1], s[2], s[3], function() {
  247. window.location.href = window.location.href + "&newname=" + encodeURIComponent(newvalue) +
  248. "&tagid=" + encodeURIComponent(tagid) +
  249. '&action=renamecombine&sesskey=' + M.cfg.sesskey;
  250. });
  251. })
  252. .always(pendingPromise.resolve)
  253. .catch(notification.exception);
  254. }
  255. });
  256. // Form for adding standard tags.
  257. $('body').on('click', 'a[data-action=addstandardtag]', function(e) {
  258. var pendingPromise = new Pending('core/tag:addstandardtag');
  259. e.preventDefault();
  260. return ModalFactory.create({
  261. title: str.get_string('addotags', 'tag'),
  262. body: templates.render('core_tag/add_tags', {
  263. actionurl: window.location.href,
  264. sesskey: M.cfg.sesskey
  265. }),
  266. type: ModalFactory.types.SAVE_CANCEL
  267. })
  268. .then(function(modal) {
  269. modal.setSaveButtonText(str.get_string('continue', 'core'));
  270. // Handle save event.
  271. modal.getRoot().on(ModalEvents.save, function(e) {
  272. var tagsInput = $(e.currentTarget).find('#id_tagslist');
  273. var name = tagsInput.val().trim();
  274. // Set the text field's value to the trimmed value.
  275. tagsInput.val(name);
  276. // Add submit event listener to the form.
  277. var tagsForm = $('#addtags_form');
  278. tagsForm.on('submit', function(e) {
  279. // Validate the form.
  280. var form = $('#addtags_form');
  281. if (form[0].checkValidity() === false) {
  282. e.preventDefault();
  283. e.stopPropagation();
  284. }
  285. form.addClass('was-validated');
  286. // BS2 compatibility.
  287. $('[data-region="tagslistinput"]').addClass('error');
  288. var errorMessage = $('#id_tagslist_error_message');
  289. errorMessage.removeAttr('hidden');
  290. errorMessage.addClass('help-block');
  291. });
  292. // Try to submit the form.
  293. tagsForm.submit();
  294. return false;
  295. });
  296. // Handle hidden event.
  297. modal.getRoot().on(ModalEvents.hidden, function() {
  298. // Destroy when hidden.
  299. modal.destroy();
  300. });
  301. modal.show();
  302. return;
  303. })
  304. .always(pendingPromise.resolve)
  305. .catch(notification.exception);
  306. });
  307. },
  308. /**
  309. * Initialises tag collection management page.
  310. *
  311. * @method initManageCollectionsPage
  312. */
  313. initManageCollectionsPage: function() {
  314. $('body').on('updated', '[data-inplaceeditable]', function(e) {
  315. var pendingPromise = new Pending('core/tag:initManageCollectionsPage-updated');
  316. var ajaxreturn = e.ajaxreturn,
  317. areaid, collid, isenabled;
  318. if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareaenable') {
  319. areaid = $(this).attr('data-itemid');
  320. $(".tag-collections-table ul[data-collectionid] li[data-areaid=" + areaid + "]").hide();
  321. isenabled = ajaxreturn.value;
  322. if (isenabled === '1') {
  323. $(this).closest('tr').removeClass('dimmed_text');
  324. collid = $(this).closest('tr').find('[data-itemtype="tagareacollection"]').attr("data-value");
  325. $(".tag-collections-table ul[data-collectionid=" + collid + "] li[data-areaid=" + areaid + "]").show();
  326. } else {
  327. $(this).closest('tr').addClass('dimmed_text');
  328. }
  329. }
  330. if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareacollection') {
  331. areaid = $(this).attr('data-itemid');
  332. $(".tag-collections-table ul[data-collectionid] li[data-areaid=" + areaid + "]").hide();
  333. collid = $(this).attr('data-value');
  334. isenabled = $(this).closest('tr').find('[data-itemtype="tagareaenable"]').attr("data-value");
  335. if (isenabled === "1") {
  336. $(".tag-collections-table ul[data-collectionid=" + collid + "] li[data-areaid=" + areaid + "]").show();
  337. }
  338. }
  339. pendingPromise.resolve();
  340. });
  341. $('body').on('click', '.addtagcoll > a', function(e) {
  342. var pendingPromise = new Pending('core/tag:initManageCollectionsPage-addtagcoll');
  343. e.preventDefault();
  344. var keys = [
  345. {
  346. key: 'addtagcoll',
  347. component: 'tag'
  348. },
  349. {
  350. key: 'create',
  351. component: 'core'
  352. }
  353. ];
  354. var href = $(this).attr('data-url');
  355. var saveButtonText = '';
  356. str.get_strings(keys)
  357. .then(function(langStrings) {
  358. var modalTitle = langStrings[0];
  359. saveButtonText = langStrings[1];
  360. var templateContext = {
  361. actionurl: href,
  362. sesskey: M.cfg.sesskey
  363. };
  364. return ModalFactory.create({
  365. title: modalTitle,
  366. body: templates.render('core_tag/add_tag_collection', templateContext),
  367. type: ModalFactory.types.SAVE_CANCEL
  368. });
  369. })
  370. .then(function(modal) {
  371. modal.setSaveButtonText(saveButtonText);
  372. // Handle save event.
  373. modal.getRoot().on(ModalEvents.save, function(e) {
  374. var collectionInput = $(e.currentTarget).find('#addtagcoll_name');
  375. var name = collectionInput.val().trim();
  376. // Set the text field's value to the trimmed value.
  377. collectionInput.val(name);
  378. // Add submit event listener to the form.
  379. var form = $('#addtagcoll_form');
  380. form.on('submit', function(e) {
  381. // Validate the form.
  382. if (form[0].checkValidity() === false) {
  383. e.preventDefault();
  384. e.stopPropagation();
  385. }
  386. form.addClass('was-validated');
  387. // BS2 compatibility.
  388. $('[data-region="addtagcoll_nameinput"]').addClass('error');
  389. var errorMessage = $('#id_addtagcoll_name_error_message');
  390. errorMessage.removeAttr('hidden');
  391. errorMessage.addClass('help-block');
  392. });
  393. // Try to submit the form.
  394. form.submit();
  395. return false;
  396. });
  397. // Handle hidden event.
  398. modal.getRoot().on(ModalEvents.hidden, function() {
  399. // Destroy when hidden.
  400. modal.destroy();
  401. });
  402. modal.show();
  403. return modal;
  404. })
  405. .always(pendingPromise.resolve)
  406. .catch(notification.exception);
  407. });
  408. $('body').on('click', '.tag-collections-table .action_delete', function(e) {
  409. var pendingPromise = new Pending('core/tag:initManageCollectionsPage-action_delete');
  410. e.preventDefault();
  411. var href = $(this).attr('data-url') + '&sesskey=' + M.cfg.sesskey;
  412. str.get_strings([
  413. {key: 'delete'},
  414. {key: 'suredeletecoll', component: 'tag', param: $(this).attr('data-collname')},
  415. {key: 'yes'},
  416. {key: 'no'},
  417. ])
  418. .then(function(s) {
  419. return notification.confirm(s[0], s[1], s[2], s[3], function() {
  420. window.location.href = href;
  421. });
  422. })
  423. .always(pendingPromise.resolve)
  424. .catch(notification.exception);
  425. });
  426. }
  427. };
  428. });