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