MDL-33997 Repository should not support references by default
[moodle.git] / course / yui / dragdrop / dragdrop.js
1 YUI.add('moodle-course-dragdrop', function(Y) {
3     var CSS = {
4         ACTIVITY : 'activity',
5         COMMANDSPAN : 'span.commands',
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',
20         SUMMARY : 'summary'
21     };
23     var DRAGSECTION = function() {
24         DRAGSECTION.superclass.constructor.apply(this, arguments);
25     };
26     Y.extend(DRAGSECTION, M.core.dragdrop, {
27         sectionlistselector : null,
29         initializer : function(params) {
30             // Set group for parent class
31             this.groups = ['section'];
32             this.samenodeclass = M.course.format.get_sectionwrapperclass();
33             this.parentnodeclass = M.course.format.get_containerclass();
35             // Check if we are in single section mode
36             if (Y.Node.one('.'+CSS.JUMPMENU)) {
37                 return false;
38             }
39             // Initialise sections dragging
40             this.sectionlistselector = M.course.format.get_section_wrapper(Y);
41             if (this.sectionlistselector) {
42                 this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector;
43                 this.setup_for_section(this.sectionlistselector);
44             }
45         },
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);
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);
62                     var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP);
64                     // Add dragger icon
65                     var title = M.util.get_string('movesection', 'moodle', sectionid);
66                     var cssleft = sectionnode.one('.'+CSS.LEFT);
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));
73                         if (moveup) {
74                             moveup.remove();
75                         }
76                         if (movedown) {
77                             movedown.remove();
78                         }
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
94                         });
95                     }
96                 }
97             }, this);
98         },
100         get_section_id : function(node) {
101             return Number(node.get('id').replace(/section-/i, ''));
102         },
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
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);
119             drag.get('dragNode').addClass(CSS.COURSECONTENT);
120         },
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         },
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));
138             var loopstart = dragnodeid;
139             var loopend = dropnodeid;
141             if (this.goingup) {
142                 loopstart = dropnodeid;
143                 loopend = dragnodeid;
144             }
146             // Get the list of nodes
147             drag.get('dragNode').removeClass(CSS.COURSECONTENT);
148             var sectionlist = Y.Node.all(this.sectionlistselector);
150             // Add lightbox if it not there
151             var lightbox = M.util.add_lightbox(Y, dragnode);
153             var params = {};
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             }
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;
167             params.value = dropnodeid;
169             // Do AJAX request
170             var uri = M.cfg.wwwroot + this.get('ajaxurl');
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) {
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) {}
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);
200                                     // See what format needs to swap
201                                     M.course.format.swap_sections(Y, i-1, i);
202                                     // Update flag
203                                     swapped = true;
204                                 }
205                             }
206                             loopend = loopend - 1;
207                         } while (swapped);
209                         // Finally, hide the lightbox
210                         window.setTimeout(function(e) {
211                             lightbox.hide();
212                         }, 250);
213                     },
214                     failure: function(tid, response) {
215                         this.ajax_failure(response);
216                         lightbox.hide();
217                     }
218                 },
219                 context:this
220             });
221         }
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     });
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;
248             // Go through all sections
249             var sectionlistselector = M.course.format.get_section_selector(Y);
250             if (sectionlistselector) {
251                 sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector;
252                 this.setup_for_section(sectionlistselector);
253                 M.course.coursebase.register_module(this);
254                 M.course.dragres = this;
255             }
256         },
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                 }
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
281                 this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
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
309                     });
310                 }
311             }, this);
312         },
314         get_section_id : function(node) {
315             return Number(node.get('id').replace(/section-/i, ''));
316         },
318         get_resource_id : function(node) {
319             return Number(node.get('id').replace(/module-/i, ''));
320         },
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         },
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         },
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');
342             // Add spinner if it not there
343             var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN));
345             var params = {};
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             }
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));
359             params.sectionId = this.get_section_id(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));
361             if (dragnode.next()) {
362                 params.beforeId = Number(this.get_resource_id(dragnode.next()));
363             }
365             // Do AJAX request
366             var uri = M.cfg.wwwroot + this.get('ajaxurl');
368             Y.io(uri, {
369                 method: 'POST',
370                 data: params,
371                 on: {
372                     start : function(tid) {
373                         this.lock_drag_handle(drag, CSS.EDITINGMOVE);
374                         spinner.show();
375                     },
376                     success: function(tid, response) {
377                         this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
378                         window.setTimeout(function(e) {
379                             spinner.hide();
380                         }, 250);
381                     },
382                     failure: function(tid, response) {
383                         this.ajax_failure(response);
384                         this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
385                         spinner.hide();
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     });
407     M.course = M.course || {};
408     M.course.init_resource_dragdrop = function(params) {
409         new DRAGRESOURCE(params);
410     }
411     M.course.init_section_dragdrop = function(params) {
412         new DRAGSECTION(params);
413     }
414 }, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'moodle-core-dragdrop', 'moodle-enrol-notification', 'moodle-course-coursebase']});