mod/lti/amd/src/tool_configure_controller.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. * Standard Ajax wrapper for Moodle. It calls the central Ajax script,
  17. * which can call any existing webservice using the current session.
  18. * In addition, it can batch multiple requests and return multiple responses.
  19. *
  20. * @module mod_lti/tool_configure_controller
  21. * @copyright 2015 Ryan Wyllie <ryan@moodle.com>
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. * @since 3.1
  24. */
  25. define(['jquery', 'core/ajax', 'core/paged_content_factory', 'core/notification', 'core/templates', 'mod_lti/events',
  26. 'mod_lti/keys', 'mod_lti/tool_types_and_proxies', 'mod_lti/tool_type', 'mod_lti/tool_proxy', 'core/str', 'core/config'],
  27. function($, ajax,
  28. pagedContentFactory, notification, templates, ltiEvents, KEYS,
  29. toolTypesAndProxies, toolType, toolProxy, str, config) {
  30. var SELECTORS = {
  31. EXTERNAL_REGISTRATION_CONTAINER: '#external-registration-container',
  32. EXTERNAL_REGISTRATION_PAGE_CONTAINER: '#external-registration-page-container',
  33. EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER: '#external-registration-template-container',
  34. CARTRIDGE_REGISTRATION_CONTAINER: '#cartridge-registration-container',
  35. CARTRIDGE_REGISTRATION_FORM: '#cartridge-registration-form',
  36. ADD_TOOL_FORM: '#add-tool-form',
  37. TOOL_CARD_CONTAINER: '#tool-card-container',
  38. TOOL_LIST_CONTAINER: '#tool-list-container',
  39. TOOL_CREATE_BUTTON: '#tool-create-button',
  40. TOOL_CREATE_LTILEGACY_BUTTON: '#tool-createltilegacy-button',
  41. REGISTRATION_CHOICE_CONTAINER: '#registration-choice-container',
  42. TOOL_URL: '#tool-url'
  43. };
  44. /**
  45. * Get the tool list container element.
  46. *
  47. * @method getToolListContainer
  48. * @private
  49. * @return {Object} jQuery object
  50. */
  51. var getToolListContainer = function() {
  52. return $(SELECTORS.TOOL_LIST_CONTAINER);
  53. };
  54. /**
  55. * Get the tool card container element.
  56. *
  57. * @method getToolCardContainer
  58. * @private
  59. * @return {Object} jQuery object
  60. */
  61. const getToolCardContainer = function() {
  62. return $(SELECTORS.TOOL_CARD_CONTAINER);
  63. };
  64. /**
  65. * Get the external registration container element.
  66. *
  67. * @method getExternalRegistrationContainer
  68. * @private
  69. * @return {Object} jQuery object
  70. */
  71. var getExternalRegistrationContainer = function() {
  72. return $(SELECTORS.EXTERNAL_REGISTRATION_CONTAINER);
  73. };
  74. /**
  75. * Get the cartridge registration container element.
  76. *
  77. * @method getCartridgeRegistrationContainer
  78. * @private
  79. * @return {Object} jQuery object
  80. */
  81. var getCartridgeRegistrationContainer = function() {
  82. return $(SELECTORS.CARTRIDGE_REGISTRATION_CONTAINER);
  83. };
  84. /**
  85. * Get the registration choice container element.
  86. *
  87. * @method getRegistrationChoiceContainer
  88. * @private
  89. * @return {Object} jQuery object
  90. */
  91. var getRegistrationChoiceContainer = function() {
  92. return $(SELECTORS.REGISTRATION_CHOICE_CONTAINER);
  93. };
  94. /**
  95. * Close the LTI Advantage Registration IFrame.
  96. *
  97. * @private
  98. * @param {Object} e post message event sent from the registration frame.
  99. */
  100. var closeLTIAdvRegistration = function(e) {
  101. if (e.data && 'org.imsglobal.lti.close' === e.data.subject) {
  102. $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER).empty();
  103. hideExternalRegistration();
  104. showRegistrationChoices();
  105. showToolList();
  106. showRegistrationChoices();
  107. reloadToolList();
  108. }
  109. };
  110. /**
  111. * Load the external registration template and render it in the DOM and display it.
  112. *
  113. * @method initiateRegistration
  114. * @private
  115. * @param {String} url where to send the registration request
  116. */
  117. var initiateRegistration = function(url) {
  118. // Show the external registration page in an iframe.
  119. $(SELECTORS.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass('hidden');
  120. var container = $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);
  121. container.append($("<iframe src='startltiadvregistration.php?url="
  122. + encodeURIComponent(url) + "&sesskey=" + config.sesskey + "'></iframe>"));
  123. showExternalRegistration();
  124. window.addEventListener("message", closeLTIAdvRegistration, false);
  125. };
  126. /**
  127. * Get the tool type URL.
  128. *
  129. * @method getToolURL
  130. * @private
  131. * @return {String} the tool type url
  132. */
  133. var getToolURL = function() {
  134. return $(SELECTORS.TOOL_URL).val();
  135. };
  136. /**
  137. * Hide the external registration container.
  138. *
  139. * @method hideExternalRegistration
  140. * @private
  141. */
  142. var hideExternalRegistration = function() {
  143. getExternalRegistrationContainer().addClass('hidden');
  144. };
  145. /**
  146. * Hide the cartridge registration container.
  147. *
  148. * @method hideCartridgeRegistration
  149. * @private
  150. */
  151. var hideCartridgeRegistration = function() {
  152. getCartridgeRegistrationContainer().addClass('hidden');
  153. };
  154. /**
  155. * Hide the registration choice container.
  156. *
  157. * @method hideRegistrationChoices
  158. * @private
  159. */
  160. var hideRegistrationChoices = function() {
  161. getRegistrationChoiceContainer().addClass('hidden');
  162. };
  163. /**
  164. * Display the external registration panel and hides the other
  165. * panels.
  166. *
  167. * @method showExternalRegistration
  168. * @private
  169. */
  170. var showExternalRegistration = function() {
  171. hideCartridgeRegistration();
  172. hideRegistrationChoices();
  173. getExternalRegistrationContainer().removeClass('hidden');
  174. screenReaderAnnounce(getExternalRegistrationContainer());
  175. };
  176. /**
  177. * Display the cartridge registration panel and hides the other
  178. * panels.
  179. *
  180. * @method showCartridgeRegistration
  181. * @param {String} url
  182. * @private
  183. */
  184. var showCartridgeRegistration = function(url) {
  185. hideExternalRegistration();
  186. hideRegistrationChoices();
  187. // Don't save the key and secret from the last tool.
  188. var container = getCartridgeRegistrationContainer();
  189. container.find('input').val('');
  190. container.removeClass('hidden');
  191. container.find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).attr('data-cartridge-url', url);
  192. screenReaderAnnounce(container);
  193. };
  194. /**
  195. * Display the registration choices panel and hides the other
  196. * panels.
  197. *
  198. * @method showRegistrationChoices
  199. * @private
  200. */
  201. var showRegistrationChoices = function() {
  202. hideExternalRegistration();
  203. hideCartridgeRegistration();
  204. getRegistrationChoiceContainer().removeClass('hidden');
  205. screenReaderAnnounce(getRegistrationChoiceContainer());
  206. };
  207. /**
  208. * JAWS does not notice visibility changes with aria-live.
  209. * Remove and add the content back to force it to read it out.
  210. * This function can be removed once JAWS supports visibility.
  211. *
  212. * @method screenReaderAnnounce
  213. * @param {Object} element
  214. * @private
  215. */
  216. var screenReaderAnnounce = function(element) {
  217. var children = element.children().detach();
  218. children.appendTo(element);
  219. };
  220. /**
  221. * Hides the list of tool types.
  222. *
  223. * @method hideToolList
  224. * @private
  225. */
  226. var hideToolList = function() {
  227. getToolListContainer().addClass('hidden');
  228. };
  229. /**
  230. * Display the list of tool types.
  231. *
  232. * @method hideToolList
  233. * @private
  234. */
  235. var showToolList = function() {
  236. getToolListContainer().removeClass('hidden');
  237. };
  238. /**
  239. * Display the registration feedback alert and hide the other panels.
  240. *
  241. * @method showRegistrationFeedback
  242. * @param {Object} data
  243. * @private
  244. */
  245. var showRegistrationFeedback = function(data) {
  246. var type = data.error ? 'error' : 'success';
  247. notification.addNotification({
  248. message: data.message,
  249. type: type
  250. });
  251. };
  252. /**
  253. * Show the loading animation
  254. *
  255. * @method startLoading
  256. * @private
  257. * @param {Object} element jQuery object
  258. */
  259. var startLoading = function(element) {
  260. element.addClass("loading");
  261. };
  262. /**
  263. * Hide the loading animation
  264. *
  265. * @method stopLoading
  266. * @private
  267. * @param {Object} element jQuery object
  268. */
  269. var stopLoading = function(element) {
  270. element.removeClass("loading");
  271. };
  272. /**
  273. * Refresh the list of tool types and render the new ones.
  274. *
  275. * @method reloadToolList
  276. * @private
  277. */
  278. var reloadToolList = function() {
  279. // Behat tests should wait for the tool list to load.
  280. M.util.js_pending('reloadToolList');
  281. const cardContainer = getToolCardContainer();
  282. const listContainer = getToolListContainer();
  283. const limit = 60;
  284. // Get initial data with zero limit and offset.
  285. fetchToolCount().done(function(data) {
  286. pagedContentFactory.createWithTotalAndLimit(
  287. data.count,
  288. limit,
  289. function(pagesData) {
  290. return pagesData.map(function(pageData) {
  291. return fetchToolData(pageData.limit, pageData.offset)
  292. .then(function(data) {
  293. return renderToolData(data);
  294. });
  295. });
  296. },
  297. {
  298. 'showFirstLast': true
  299. })
  300. .done(function(html, js) {
  301. // Add the paged content into the page.
  302. templates.replaceNodeContents(cardContainer, html, js);
  303. })
  304. .always(function() {
  305. stopLoading(listContainer);
  306. M.util.js_complete('reloadToolList');
  307. });
  308. });
  309. startLoading(listContainer);
  310. };
  311. /**
  312. * Fetch the count of tool type and proxy datasets.
  313. *
  314. * @return {*|void}
  315. */
  316. const fetchToolCount = function() {
  317. return toolTypesAndProxies.count({'orphanedonly': true})
  318. .done(function(data) {
  319. return data;
  320. }).catch(function(error) {
  321. // Add debug message, then return empty data.
  322. notification.exception(error);
  323. return {
  324. 'count': 0
  325. };
  326. });
  327. };
  328. /**
  329. * Fetch the data for tool type and proxy cards.
  330. *
  331. * @param {number} limit Maximum number of datasets to get.
  332. * @param {number} offset Offset count for fetching the data.
  333. * @return {*|void}
  334. */
  335. const fetchToolData = function(limit, offset) {
  336. const args = {'orphanedonly': true};
  337. // Only add limit and offset to args if they are integers and not null, otherwise defaults will be used.
  338. if (limit !== null && !Number.isNaN(limit)) {
  339. args.limit = limit;
  340. }
  341. if (offset !== null && !Number.isNaN(offset)) {
  342. args.offset = offset;
  343. }
  344. return toolTypesAndProxies.query(args)
  345. .done(function(data) {
  346. return data;
  347. }).catch(function(error) {
  348. // Add debug message, then return empty data.
  349. notification.exception(error);
  350. return {
  351. 'types': [],
  352. 'proxies': [],
  353. 'limit': limit,
  354. 'offset': offset
  355. };
  356. });
  357. };
  358. /**
  359. * Render Tool and Proxy cards from data.
  360. *
  361. * @param {Object} data Contains arrays of data objects to populate cards.
  362. * @return {*}
  363. */
  364. const renderToolData = function(data) {
  365. const context = {
  366. tools: data.types,
  367. proxies: data.proxies,
  368. };
  369. return templates.render('mod_lti/tool_list', context)
  370. .done(function(html, js) {
  371. return {html, js};
  372. }
  373. );
  374. };
  375. /**
  376. * Start the LTI Advantage registration.
  377. *
  378. * @method addLTIAdvTool
  379. * @private
  380. */
  381. var addLTIAdvTool = function() {
  382. var url = getToolURL().trim();
  383. if (url) {
  384. $(SELECTORS.TOOL_URL).val('');
  385. hideToolList();
  386. initiateRegistration(url);
  387. }
  388. };
  389. /**
  390. * Trigger appropriate registration process process for the user input
  391. * URL. It can either be a cartridge or a registration url.
  392. *
  393. * @method addLTILegacyTool
  394. * @private
  395. * @return {Promise} jQuery Deferred object
  396. */
  397. var addLTILegacyTool = function() {
  398. var url = getToolURL().trim();
  399. if (url === "") {
  400. return $.Deferred().resolve();
  401. }
  402. var toolButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);
  403. startLoading(toolButton);
  404. var promise = toolType.isCartridge(url);
  405. promise.always(function() {
  406. stopLoading(toolButton);
  407. });
  408. promise.done(function(result) {
  409. if (result.iscartridge) {
  410. $(SELECTORS.TOOL_URL).val('');
  411. $(document).trigger(ltiEvents.START_CARTRIDGE_REGISTRATION, url);
  412. } else {
  413. $(document).trigger(ltiEvents.START_EXTERNAL_REGISTRATION, {url: url});
  414. }
  415. });
  416. promise.fail(function() {
  417. str.get_string('errorbadurl', 'mod_lti')
  418. .done(function(s) {
  419. $(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, {
  420. message: s,
  421. error: true
  422. });
  423. })
  424. .fail(notification.exception);
  425. });
  426. return promise;
  427. };
  428. /**
  429. * Sets up the listeners for user interaction on the page.
  430. *
  431. * @method registerEventListeners
  432. * @private
  433. */
  434. var registerEventListeners = function() {
  435. // These are events fired by the registration processes. Either
  436. // the cartridge registration or the external registration url.
  437. $(document).on(ltiEvents.NEW_TOOL_TYPE, function() {
  438. reloadToolList();
  439. });
  440. $(document).on(ltiEvents.START_EXTERNAL_REGISTRATION, function() {
  441. showExternalRegistration();
  442. $(SELECTORS.TOOL_URL).val('');
  443. hideToolList();
  444. });
  445. $(document).on(ltiEvents.STOP_EXTERNAL_REGISTRATION, function() {
  446. showToolList();
  447. showRegistrationChoices();
  448. });
  449. $(document).on(ltiEvents.START_CARTRIDGE_REGISTRATION, function(event, url) {
  450. showCartridgeRegistration(url);
  451. });
  452. $(document).on(ltiEvents.STOP_CARTRIDGE_REGISTRATION, function() {
  453. getCartridgeRegistrationContainer().find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).removeAttr('data-cartridge-url');
  454. showRegistrationChoices();
  455. });
  456. $(document).on(ltiEvents.REGISTRATION_FEEDBACK, function(event, data) {
  457. showRegistrationFeedback(data);
  458. });
  459. var addLegacyButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);
  460. addLegacyButton.click(function(e) {
  461. e.preventDefault();
  462. addLTILegacyTool();
  463. });
  464. var addLTIButton = $(SELECTORS.TOOL_CREATE_BUTTON);
  465. addLTIButton.click(function(e) {
  466. e.preventDefault();
  467. addLTIAdvTool();
  468. });
  469. };
  470. return /** @alias module:mod_lti/cartridge_registration_form */ {
  471. /**
  472. * Initialise this module.
  473. */
  474. init: function() {
  475. registerEventListeners();
  476. reloadToolList();
  477. }
  478. };
  479. });