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