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