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