[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* global TOOLBOX, BODY, SELECTOR */ 2 3 /** 4 * Resource and activity toolbox class. 5 * 6 * This class is responsible for managing AJAX interactions with activities and resources 7 * when viewing a quiz in editing mode. 8 * 9 * @module mod_quiz-resource-toolbox 10 * @namespace M.mod_quiz.resource_toolbox 11 */ 12 13 /** 14 * Resource and activity toolbox class. 15 * 16 * This is a class extending TOOLBOX containing code specific to resources 17 * 18 * This class is responsible for managing AJAX interactions with activities and resources 19 * when viewing a quiz in editing mode. 20 * 21 * @class resources 22 * @constructor 23 * @extends M.course.toolboxes.toolbox 24 */ 25 var RESOURCETOOLBOX = function() { 26 RESOURCETOOLBOX.superclass.constructor.apply(this, arguments); 27 }; 28 29 Y.extend(RESOURCETOOLBOX, TOOLBOX, { 30 /** 31 * An Array of events added when editing a max mark field. 32 * These should all be detached when editing is complete. 33 * 34 * @property editmaxmarkevents 35 * @protected 36 * @type Array 37 * @protected 38 */ 39 editmaxmarkevents: [], 40 41 /** 42 * 43 */ 44 NODE_PAGE: 1, 45 NODE_SLOT: 2, 46 NODE_JOIN: 3, 47 48 /** 49 * Initialize the resource toolbox 50 * 51 * For each activity the commands are updated and a reference to the activity is attached. 52 * This way it doesn't matter where the commands are going to called from they have a reference to the 53 * activity that they relate to. 54 * This is essential as some of the actions are displayed in an actionmenu which removes them from the 55 * page flow. 56 * 57 * This function also creates a single event delegate to manage all AJAX actions for all activities on 58 * the page. 59 * 60 * @method initializer 61 * @protected 62 */ 63 initializer: function() { 64 M.mod_quiz.quizbase.register_module(this); 65 Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this); 66 Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this); 67 }, 68 69 /** 70 * Handles the delegation event. When this is fired someone has triggered an action. 71 * 72 * Note not all actions will result in an AJAX enhancement. 73 * 74 * @protected 75 * @method handle_data_action 76 * @param {EventFacade} ev The event that was triggered. 77 * @returns {boolean} 78 */ 79 handle_data_action: function(ev) { 80 // We need to get the anchor element that triggered this event. 81 var node = ev.target; 82 if (!node.test('a')) { 83 node = node.ancestor(SELECTOR.ACTIVITYACTION); 84 } 85 86 // From the anchor we can get both the activity (added during initialisation) and the action being 87 // performed (added by the UI as a data attribute). 88 var action = node.getData('action'), 89 activity = node.ancestor(SELECTOR.ACTIVITYLI); 90 91 if (!node.test('a') || !action || !activity) { 92 // It wasn't a valid action node. 93 return; 94 } 95 96 // Switch based upon the action and do the desired thing. 97 switch (action) { 98 case 'editmaxmark': 99 // The user wishes to edit the maxmark of the resource. 100 this.edit_maxmark(ev, node, activity, action); 101 break; 102 case 'delete': 103 // The user is deleting the activity. 104 this.delete_with_confirmation(ev, node, activity, action); 105 break; 106 case 'addpagebreak': 107 case 'removepagebreak': 108 // The user is adding or removing a page break. 109 this.update_page_break(ev, node, activity, action); 110 break; 111 case 'adddependency': 112 case 'removedependency': 113 // The user is adding or removing a dependency between questions. 114 this.update_dependency(ev, node, activity, action); 115 break; 116 default: 117 // Nothing to do here! 118 break; 119 } 120 }, 121 122 /** 123 * Add a loading icon to the specified activity. 124 * 125 * The icon is added within the action area. 126 * 127 * @method add_spinner 128 * @param {Node} activity The activity to add a loading icon to 129 * @return {Node|null} The newly created icon, or null if the action area was not found. 130 */ 131 add_spinner: function(activity) { 132 var actionarea = activity.one(SELECTOR.ACTIONAREA); 133 if (actionarea) { 134 return M.util.add_spinner(Y, actionarea); 135 } 136 return null; 137 }, 138 139 /** 140 * Deletes the given activity or resource after confirmation. 141 * 142 * @protected 143 * @method delete_with_confirmation 144 * @param {EventFacade} ev The event that was fired. 145 * @param {Node} button The button that triggered this action. 146 * @param {Node} activity The activity node that this action will be performed on. 147 * @chainable 148 */ 149 delete_with_confirmation: function(ev, button, activity) { 150 // Prevent the default button action. 151 ev.preventDefault(); 152 153 // Get the element we're working on. 154 var element = activity, 155 // Create confirm string (different if element has or does not have name) 156 confirmstring = '', 157 qtypename = M.util.get_string('pluginname', 158 'qtype_' + element.getAttribute('class').match(/qtype_([^\s]*)/)[1]); 159 confirmstring = M.util.get_string('confirmremovequestion', 'quiz', qtypename); 160 161 // Create the confirmation dialogue. 162 var confirm = new M.core.confirm({ 163 question: confirmstring, 164 modal: true 165 }); 166 167 // If it is confirmed. 168 confirm.on('complete-yes', function() { 169 170 var spinner = this.add_spinner(element); 171 var data = { 172 'class': 'resource', 173 'action': 'DELETE', 174 'id': Y.Moodle.mod_quiz.util.slot.getId(element) 175 }; 176 this.send_request(data, spinner, function(response) { 177 if (response.deleted) { 178 // Actually remove the element. 179 Y.Moodle.mod_quiz.util.slot.remove(element); 180 this.reorganise_edit_page(); 181 if (M.core.actionmenu && M.core.actionmenu.instance) { 182 M.core.actionmenu.instance.hideMenu(ev); 183 } 184 } 185 }); 186 187 }, this); 188 189 return this; 190 }, 191 192 193 /** 194 * Edit the maxmark for the resource 195 * 196 * @protected 197 * @method edit_maxmark 198 * @param {EventFacade} ev The event that was fired. 199 * @param {Node} button The button that triggered this action. 200 * @param {Node} activity The activity node that this action will be performed on. 201 * @param {String} action The action that has been requested. 202 * @return Boolean 203 */ 204 edit_maxmark: function(ev, button, activity) { 205 // Get the element we're working on 206 var instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK), 207 instance = activity.one(SELECTOR.ACTIVITYINSTANCE), 208 currentmaxmark = instancemaxmark.get('firstChild'), 209 oldmaxmark = currentmaxmark.get('data'), 210 maxmarktext = oldmaxmark, 211 thisevent, 212 anchor = instancemaxmark, // Grab the anchor so that we can swap it with the edit form. 213 data = { 214 'class': 'resource', 215 'field': 'getmaxmark', 216 'id': Y.Moodle.mod_quiz.util.slot.getId(activity) 217 }; 218 219 // Prevent the default actions. 220 ev.preventDefault(); 221 222 this.send_request(data, null, function(response) { 223 if (M.core.actionmenu && M.core.actionmenu.instance) { 224 M.core.actionmenu.instance.hideMenu(ev); 225 } 226 227 // Try to retrieve the existing string from the server. 228 if (response.instancemaxmark) { 229 maxmarktext = response.instancemaxmark; 230 } 231 232 // Create the editor and submit button. 233 var editform = Y.Node.create('<form action="#" />'); 234 var editinstructions = Y.Node.create('<span class="' + CSS.EDITINSTRUCTIONS + '" id="id_editinstructions" />') 235 .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle')); 236 var editor = Y.Node.create('<input name="maxmark" type="text" class="' + CSS.TITLEEDITOR + '" />').setAttrs({ 237 'value': maxmarktext, 238 'autocomplete': 'off', 239 'aria-describedby': 'id_editinstructions', 240 'maxLength': '12', 241 'size': parseInt(this.get('config').questiondecimalpoints, 10) + 2 242 }); 243 244 // Clear the existing content and put the editor in. 245 editform.appendChild(editor); 246 editform.setData('anchor', anchor); 247 instance.insert(editinstructions, 'before'); 248 anchor.replace(editform); 249 250 // We hide various components whilst editing: 251 activity.addClass(CSS.EDITINGMAXMARK); 252 253 // Focus and select the editor text. 254 editor.focus().select(); 255 256 // Cancel the edit if we lose focus or the escape key is pressed. 257 thisevent = editor.on('blur', this.edit_maxmark_cancel, this, activity, false); 258 this.editmaxmarkevents.push(thisevent); 259 thisevent = editor.on('key', this.edit_maxmark_cancel, 'esc', this, activity, true); 260 this.editmaxmarkevents.push(thisevent); 261 262 // Handle form submission. 263 thisevent = editform.on('submit', this.edit_maxmark_submit, this, activity, oldmaxmark); 264 this.editmaxmarkevents.push(thisevent); 265 }); 266 }, 267 268 /** 269 * Handles the submit event when editing the activity or resources maxmark. 270 * 271 * @protected 272 * @method edit_maxmark_submit 273 * @param {EventFacade} ev The event that triggered this. 274 * @param {Node} activity The activity whose maxmark we are altering. 275 * @param {String} originalmaxmark The original maxmark the activity or resource had. 276 */ 277 edit_maxmark_submit: function(ev, activity, originalmaxmark) { 278 // We don't actually want to submit anything. 279 ev.preventDefault(); 280 var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value')); 281 var spinner = this.add_spinner(activity); 282 this.edit_maxmark_clear(activity); 283 activity.one(SELECTOR.INSTANCEMAXMARK).setContent(newmaxmark); 284 if (newmaxmark !== null && newmaxmark !== "" && newmaxmark !== originalmaxmark) { 285 var data = { 286 'class': 'resource', 287 'field': 'updatemaxmark', 288 'maxmark': newmaxmark, 289 'id': Y.Moodle.mod_quiz.util.slot.getId(activity) 290 }; 291 this.send_request(data, spinner, function(response) { 292 if (response.instancemaxmark) { 293 activity.one(SELECTOR.INSTANCEMAXMARK).setContent(response.instancemaxmark); 294 } 295 }); 296 } 297 }, 298 299 /** 300 * Handles the cancel event when editing the activity or resources maxmark. 301 * 302 * @protected 303 * @method edit_maxmark_cancel 304 * @param {EventFacade} ev The event that triggered this. 305 * @param {Node} activity The activity whose maxmark we are altering. 306 * @param {Boolean} preventdefault If true we should prevent the default action from occuring. 307 */ 308 edit_maxmark_cancel: function(ev, activity, preventdefault) { 309 if (preventdefault) { 310 ev.preventDefault(); 311 } 312 this.edit_maxmark_clear(activity); 313 }, 314 315 /** 316 * Handles clearing the editing UI and returning things to the original state they were in. 317 * 318 * @protected 319 * @method edit_maxmark_clear 320 * @param {Node} activity The activity whose maxmark we were altering. 321 */ 322 edit_maxmark_clear: function(activity) { 323 // Detach all listen events to prevent duplicate triggers 324 new Y.EventHandle(this.editmaxmarkevents).detach(); 325 326 var editform = activity.one(SELECTOR.ACTIVITYFORM), 327 instructions = activity.one('#id_editinstructions'); 328 if (editform) { 329 editform.replace(editform.getData('anchor')); 330 } 331 if (instructions) { 332 instructions.remove(); 333 } 334 335 // Remove the editing class again to revert the display. 336 activity.removeClass(CSS.EDITINGMAXMARK); 337 338 // Refocus the link which was clicked originally so the user can continue using keyboard nav. 339 Y.later(100, this, function() { 340 activity.one(SELECTOR.EDITMAXMARK).focus(); 341 }); 342 343 // TODO MDL-50768 This hack is to keep Behat happy until they release a version of 344 // MinkSelenium2Driver that fixes 345 // https://github.com/Behat/MinkSelenium2Driver/issues/80. 346 if (!Y.one('input[name=maxmark')) { 347 Y.one('body').append('<input type="text" name="maxmark" style="display: none">'); 348 } 349 }, 350 351 /** 352 * Joins or separates the given slot with the page of the previous slot. Reorders the pages of 353 * the other slots 354 * 355 * @protected 356 * @method update_page_break 357 * @param {EventFacade} ev The event that was fired. 358 * @param {Node} button The button that triggered this action. 359 * @param {Node} activity The activity node that this action will be performed on. 360 * @param {String} action The action, addpagebreak or removepagebreak. 361 * @chainable 362 */ 363 update_page_break: function(ev, button, activity, action) { 364 // Prevent the default button action 365 ev.preventDefault(); 366 367 var nextactivity = activity.next('li.activity.slot'); 368 var spinner = this.add_spinner(nextactivity); 369 var value = action === 'removepagebreak' ? 1 : 2; 370 371 var data = { 372 'class': 'resource', 373 'field': 'updatepagebreak', 374 'id': Y.Moodle.mod_quiz.util.slot.getId(nextactivity), 375 'value': value 376 }; 377 378 this.send_request(data, spinner, function(response) { 379 if (response.slots) { 380 if (action === 'addpagebreak') { 381 Y.Moodle.mod_quiz.util.page.add(activity); 382 } else { 383 var page = activity.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE); 384 Y.Moodle.mod_quiz.util.page.remove(page, true); 385 } 386 this.reorganise_edit_page(); 387 } 388 }); 389 390 return this; 391 }, 392 393 /** 394 * Updates a slot to either require the question in the previous slot to 395 * have been answered, or not, 396 * 397 * @protected 398 * @method update_page_break 399 * @param {EventFacade} ev The event that was fired. 400 * @param {Node} button The button that triggered this action. 401 * @param {Node} activity The activity node that this action will be performed on. 402 * @param {String} action The action, adddependency or removedependency. 403 * @chainable 404 */ 405 update_dependency: function(ev, button, activity, action) { 406 // Prevent the default button action. 407 ev.preventDefault(); 408 var spinner = this.add_spinner(activity); 409 410 var data = { 411 'class': 'resource', 412 'field': 'updatedependency', 413 'id': Y.Moodle.mod_quiz.util.slot.getId(activity), 414 'value': action === 'adddependency' ? 1 : 0 415 }; 416 417 this.send_request(data, spinner, function(response) { 418 if (response.hasOwnProperty('requireprevious')) { 419 Y.Moodle.mod_quiz.util.slot.updateDependencyIcon(activity, response.requireprevious); 420 } 421 }); 422 423 return this; 424 }, 425 426 /** 427 * Reorganise the UI after every edit action. 428 * 429 * @protected 430 * @method reorganise_edit_page 431 */ 432 reorganise_edit_page: function() { 433 Y.Moodle.mod_quiz.util.slot.reorderSlots(); 434 Y.Moodle.mod_quiz.util.slot.reorderPageBreaks(); 435 Y.Moodle.mod_quiz.util.page.reorderPages(); 436 Y.Moodle.mod_quiz.util.slot.updateOneSlotSections(); 437 Y.Moodle.mod_quiz.util.slot.updateAllDependencyIcons(); 438 }, 439 440 NAME: 'mod_quiz-resource-toolbox', 441 ATTRS: { 442 courseid: { 443 'value': 0 444 }, 445 quizid: { 446 'value': 0 447 } 448 } 449 }); 450 451 M.mod_quiz.resource_toolbox = null; 452 M.mod_quiz.init_resource_toolbox = function(config) { 453 M.mod_quiz.resource_toolbox = new RESOURCETOOLBOX(config); 454 return M.mod_quiz.resource_toolbox; 455 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |