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