MDL-50516 behat: Back/next button reloads page
[moodle.git] / course / yui / build / moodle-course-dragdrop / moodle-course-dragdrop.js
CommitLineData
fd03a33c
AN
1YUI.add('moodle-course-dragdrop', function (Y, NAME) {
2
3/**
4 * Drag and Drop for course sections and course modules.
5 *
6 * @module moodle-course-dragdrop
7 */
8
9var CSS = {
10 ACTIONAREA: '.actions',
11 ACTIVITY: 'activity',
12 ACTIVITYINSTANCE: 'activityinstance',
13 CONTENT: 'content',
14 COURSECONTENT: 'course-content',
15 EDITINGMOVE: 'editing_move',
16 ICONCLASS: 'iconsmall',
17 JUMPMENU: 'jumpmenu',
18 LEFT: 'left',
19 LIGHTBOX: 'lightbox',
20 MOVEDOWN: 'movedown',
21 MOVEUP: 'moveup',
22 PAGECONTENT: 'page-content',
23 RIGHT: 'right',
24 SECTION: 'section',
25 SECTIONADDMENUS: 'section_add_menus',
26 SECTIONHANDLE: 'section-handle',
27 SUMMARY: 'summary',
28 SECTIONDRAGGABLE: 'sectiondraggable'
29};
30
31M.course = M.course || {};
32/**
33 * Section drag and drop.
34 *
35 * @class M.course.dragdrop.section
36 * @constructor
37 * @extends M.core.dragdrop
38 */
39var DRAGSECTION = function() {
40 DRAGSECTION.superclass.constructor.apply(this, arguments);
41};
42Y.extend(DRAGSECTION, M.core.dragdrop, {
43 sectionlistselector: null,
44
45 initializer: function() {
46 // Set group for parent class
47 this.groups = [ CSS.SECTIONDRAGGABLE ];
48 this.samenodeclass = M.course.format.get_sectionwrapperclass();
49 this.parentnodeclass = M.course.format.get_containerclass();
50
51 // Check if we are in single section mode
52 if (Y.Node.one('.' + CSS.JUMPMENU)) {
53 return false;
54 }
55 // Initialise sections dragging
56 this.sectionlistselector = M.course.format.get_section_wrapper(Y);
57 if (this.sectionlistselector) {
58 this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector;
59
60 this.setup_for_section(this.sectionlistselector);
61
62 // Make each li element in the lists of sections draggable
63 var del = new Y.DD.Delegate({
64 container: '.' + CSS.COURSECONTENT,
65 nodes: '.' + CSS.SECTIONDRAGGABLE,
66 target: true,
67 handles: ['.' + CSS.LEFT],
68 dragConfig: {groups: this.groups}
69 });
70 del.dd.plug(Y.Plugin.DDProxy, {
71 // Don't move the node at the end of the drag
72 moveOnEnd: false
73 });
74 del.dd.plug(Y.Plugin.DDConstrained, {
75 // Keep it inside the .course-content
76 constrain: '#' + CSS.PAGECONTENT,
77 stickY: true
78 });
79 del.dd.plug(Y.Plugin.DDWinScroll);
80 }
81 },
82
83 /**
84 * Apply dragdrop features to the specified selector or node that refers to section(s)
85 *
86 * @method setup_for_section
87 * @param {String} baseselector The CSS selector or node to limit scope to
88 */
89 setup_for_section: function(baseselector) {
90 Y.Node.all(baseselector).each(function(sectionnode) {
91 // Determine the section ID
92 var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode);
93
94 // We skip the top section as it is not draggable
95 if (sectionid > 0) {
96 // Remove move icons
97 var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN);
98 var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP);
99
100 // Add dragger icon
101 var title = M.util.get_string('movesection', 'moodle', sectionid);
102 var cssleft = sectionnode.one('.' + CSS.LEFT);
103
104 if ((movedown || moveup) && cssleft) {
105 cssleft.setStyle('cursor', 'move');
106 cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
107
108 if (moveup) {
109 moveup.remove();
110 }
111 if (movedown) {
112 movedown.remove();
113 }
114
115 // This section can be moved - add the class to indicate this to Y.DD.
116 sectionnode.addClass(CSS.SECTIONDRAGGABLE);
117 }
118 }
119 }, this);
120 },
121
122 /*
123 * Drag-dropping related functions
124 */
125 drag_start: function(e) {
126 // Get our drag object
127 var drag = e.target;
128 // Creat a dummy structure of the outer elemnents for clean styles application
557f44d9
AN
129 var containernode = Y.Node.create('<' + M.course.format.get_containernode() +
130 '></' + M.course.format.get_containernode() + '>');
fd03a33c 131 containernode.addClass(M.course.format.get_containerclass());
557f44d9
AN
132 var sectionnode = Y.Node.create('<' + M.course.format.get_sectionwrappernode() +
133 '></' + M.course.format.get_sectionwrappernode() + '>');
fd03a33c
AN
134 sectionnode.addClass( M.course.format.get_sectionwrapperclass());
135 sectionnode.setStyle('margin', 0);
136 sectionnode.setContent(drag.get('node').get('innerHTML'));
137 containernode.appendChild(sectionnode);
138 drag.get('dragNode').setContent(containernode);
139 drag.get('dragNode').addClass(CSS.COURSECONTENT);
140 },
141
142 drag_dropmiss: function(e) {
143 // Missed the target, but we assume the user intended to drop it
144 // on the last last ghost node location, e.drag and e.drop should be
145 // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
146 this.drop_hit(e);
147 },
148
149 get_section_index: function(node) {
150 var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y),
151 sectionList = Y.all(sectionlistselector),
152 nodeIndex = sectionList.indexOf(node),
153 zeroIndex = sectionList.indexOf(Y.one('#section-0'));
154
155 return (nodeIndex - zeroIndex);
156 },
157
158 drop_hit: function(e) {
159 var drag = e.drag;
160
161 // Get references to our nodes and their IDs.
162 var dragnode = drag.get('node'),
163 dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode),
164 loopstart = dragnodeid,
165
166 dropnodeindex = this.get_section_index(dragnode),
167 loopend = dropnodeindex;
168
169 if (dragnodeid === dropnodeindex) {
170 return;
171 }
172
173
174 if (loopstart > loopend) {
175 // If we're going up, we need to swap the loop order
176 // because loops can't go backwards.
177 loopstart = dropnodeindex;
178 loopend = dragnodeid;
179 }
180
181 // Get the list of nodes.
182 drag.get('dragNode').removeClass(CSS.COURSECONTENT);
183 var sectionlist = Y.Node.all(this.sectionlistselector);
184
185 // Add a lightbox if it's not there.
186 var lightbox = M.util.add_lightbox(Y, dragnode);
187
188 // Handle any variables which we must pass via AJAX.
189 var params = {},
190 pageparams = this.get('config').pageparams,
191 varname;
192
193 for (varname in pageparams) {
194 if (!pageparams.hasOwnProperty(varname)) {
195 continue;
196 }
197 params[varname] = pageparams[varname];
198 }
199
200 // Prepare request parameters
201 params.sesskey = M.cfg.sesskey;
202 params.courseId = this.get('courseid');
203 params['class'] = 'section';
204 params.field = 'move';
205 params.id = dragnodeid;
206 params.value = dropnodeindex;
207
208 // Perform the AJAX request.
209 var uri = M.cfg.wwwroot + this.get('ajaxurl');
210 Y.io(uri, {
211 method: 'POST',
212 data: params,
213 on: {
214 start: function() {
215 lightbox.show();
216 },
217 success: function(tid, response) {
218 // Update section titles, we can't simply swap them as
219 // they might have custom title
220 try {
221 var responsetext = Y.JSON.parse(response.responseText);
222 if (responsetext.error) {
223 new M.core.ajaxException(responsetext);
224 }
225 M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
226 } catch (e) {}
227
228 // Update all of the section IDs - first unset them, then set them
229 // to avoid duplicates in the DOM.
230 var index;
231
232 // Classic bubble sort algorithm is applied to the section
233 // nodes between original drag node location and the new one.
234 var swapped = false;
235 do {
236 swapped = false;
237 for (index = loopstart; index <= loopend; index++) {
238 if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) >
239 Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) {
240 // Swap section id.
241 var sectionid = sectionlist.item(index - 1).get('id');
242 sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id'));
243 sectionlist.item(index).set('id', sectionid);
244
245 // See what format needs to swap.
246 M.course.format.swap_sections(Y, index - 1, index);
247
248 // Update flag.
249 swapped = true;
250 }
251 }
252 loopend = loopend - 1;
253 } while (swapped);
254
255 window.setTimeout(function() {
256 lightbox.hide();
257 }, 250);
258 },
259
260 failure: function(tid, response) {
261 this.ajax_failure(response);
262 lightbox.hide();
263 }
264 },
265 context:this
266 });
267 }
268
269}, {
270 NAME: 'course-dragdrop-section',
271 ATTRS: {
272 courseid: {
273 value: null
274 },
275 ajaxurl: {
276 value: 0
277 },
278 config: {
279 value: 0
280 }
281 }
282});
283
284M.course = M.course || {};
285M.course.init_section_dragdrop = function(params) {
286 new DRAGSECTION(params);
287};
288/**
289 * Resource drag and drop.
290 *
291 * @class M.course.dragdrop.resource
292 * @constructor
293 * @extends M.core.dragdrop
294 */
295var DRAGRESOURCE = function() {
296 DRAGRESOURCE.superclass.constructor.apply(this, arguments);
297};
298Y.extend(DRAGRESOURCE, M.core.dragdrop, {
299 initializer: function() {
300 // Set group for parent class
301 this.groups = ['resource'];
302 this.samenodeclass = CSS.ACTIVITY;
303 this.parentnodeclass = CSS.SECTION;
557f44d9
AN
304 this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'),
305 CSS.EDITINGMOVE, CSS.ICONCLASS, true);
34bcc6a9
AN
306
307 this.samenodelabel = {
308 identifier: 'afterresource',
309 component: 'moodle'
310 };
311 this.parentnodelabel = {
312 identifier: 'totopofsection',
313 component: 'moodle'
314 };
fd03a33c
AN
315
316 // Go through all sections
317 var sectionlistselector = M.course.format.get_section_selector(Y);
318 if (sectionlistselector) {
319 sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector;
320 this.setup_for_section(sectionlistselector);
321
322 // Initialise drag & drop for all resources/activities
323 var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY;
324 var del = new Y.DD.Delegate({
325 container: '.' + CSS.COURSECONTENT,
326 nodes: nodeselector,
327 target: true,
328 handles: ['.' + CSS.EDITINGMOVE],
329 dragConfig: {groups: this.groups}
330 });
331 del.dd.plug(Y.Plugin.DDProxy, {
332 // Don't move the node at the end of the drag
333 moveOnEnd: false,
334 cloneNode: true
335 });
336 del.dd.plug(Y.Plugin.DDConstrained, {
337 // Keep it inside the .course-content
338 constrain: '#' + CSS.PAGECONTENT
339 });
340 del.dd.plug(Y.Plugin.DDWinScroll);
341
342 M.course.coursebase.register_module(this);
343 M.course.dragres = this;
344 }
345 },
346
347 /**
348 * Apply dragdrop features to the specified selector or node that refers to section(s)
349 *
350 * @method setup_for_section
351 * @param {String} baseselector The CSS selector or node to limit scope to
352 */
353 setup_for_section: function(baseselector) {
354 Y.Node.all(baseselector).each(function(sectionnode) {
355 var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION);
356 // See if resources ul exists, if not create one
357 if (!resources) {
358 resources = Y.Node.create('<ul></ul>');
359 resources.addClass(CSS.SECTION);
360 sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after');
361 }
362 resources.setAttribute('data-draggroups', this.groups.join(' '));
363 // Define empty ul as droptarget, so that item could be moved to empty list
364 new Y.DD.Drop({
365 node: resources,
366 groups: this.groups,
367 padding: '20 0 20 0'
368 });
369
370 // Initialise each resource/activity in this section
371 this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY);
372 }, this);
373 },
374
375 /**
376 * Apply dragdrop features to the specified selector or node that refers to resource(s)
377 *
378 * @method setup_for_resource
379 * @param {String} baseselector The CSS selector or node to limit scope to
380 */
381 setup_for_resource: function(baseselector) {
382 Y.Node.all(baseselector).each(function(resourcesnode) {
2fafa90b
AN
383 var draggroups = resourcesnode.getData('draggroups');
384 if (!draggroups) {
385 // This Drop Node has not been set up. Configure it now.
386 resourcesnode.setAttribute('data-draggroups', this.groups.join(' '));
387 // Define empty ul as droptarget, so that item could be moved to empty list
388 new Y.DD.Drop({
389 node: resourcesnode,
390 groups: this.groups,
391 padding: '20 0 20 0'
392 });
393 }
394
fd03a33c
AN
395 // Replace move icons
396 var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
397 if (move) {
398 move.replace(this.resourcedraghandle.cloneNode(true));
399 }
400 }, this);
401 },
402
403 drag_start: function(e) {
404 // Get our drag object
405 var drag = e.target;
406 drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
407 drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
408 },
409
410 drag_dropmiss: function(e) {
411 // Missed the target, but we assume the user intended to drop it
412 // on the last last ghost node location, e.drag and e.drop should be
413 // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
414 this.drop_hit(e);
415 },
416
417 drop_hit: function(e) {
418 var drag = e.drag;
419 // Get a reference to our drag node
420 var dragnode = drag.get('node');
421 var dropnode = e.drop.get('node');
422
423 // Add spinner if it not there
424 var actionarea = dragnode.one(CSS.ACTIONAREA);
425 var spinner = M.util.add_spinner(Y, actionarea);
426
427 var params = {};
428
429 // Handle any variables which we must pass back through to
430 var pageparams = this.get('config').pageparams;
431 var varname;
432 for (varname in pageparams) {
433 params[varname] = pageparams[varname];
434 }
435
436 // Prepare request parameters
437 params.sesskey = M.cfg.sesskey;
438 params.courseId = this.get('courseid');
439 params['class'] = 'resource';
440 params.field = 'move';
441 params.id = Number(Y.Moodle.core_course.util.cm.getId(dragnode));
442 params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));
443
444 if (dragnode.next()) {
445 params.beforeId = Number(Y.Moodle.core_course.util.cm.getId(dragnode.next()));
446 }
447
448 // Do AJAX request
449 var uri = M.cfg.wwwroot + this.get('ajaxurl');
450
451 Y.io(uri, {
452 method: 'POST',
453 data: params,
454 on: {
455 start: function() {
456 this.lock_drag_handle(drag, CSS.EDITINGMOVE);
457 spinner.show();
458 },
459 success: function(tid, response) {
460 var responsetext = Y.JSON.parse(response.responseText);
461 var params = {element: dragnode, visible: responsetext.visible};
462 M.course.coursebase.invoke_function('set_visibility_resource_ui', params);
463 this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
464 window.setTimeout(function() {
465 spinner.hide();
466 }, 250);
467 },
468 failure: function(tid, response) {
469 this.ajax_failure(response);
470 this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
471 spinner.hide();
472 // TODO: revert nodes location
473 }
474 },
475 context:this
476 });
477 }
478}, {
479 NAME: 'course-dragdrop-resource',
480 ATTRS: {
481 courseid: {
482 value: null
483 },
484 ajaxurl: {
485 value: 0
486 },
487 config: {
488 value: 0
489 }
490 }
491});
492
493M.course = M.course || {};
494M.course.init_resource_dragdrop = function(params) {
495 new DRAGRESOURCE(params);
496};
497
498
499}, '@VERSION@', {
500 "requires": [
501 "base",
502 "node",
503 "io",
504 "dom",
505 "dd",
506 "dd-scroll",
507 "moodle-core-dragdrop",
508 "moodle-core-notification",
509 "moodle-course-coursebase",
510 "moodle-course-util"
511 ]
512});