1 YUI.add('moodle-course-dragdrop', function(Y) {
5 COMMANDSPAN : '.commands',
7 COURSECONTENT : 'course-content',
8 EDITINGMOVE : 'editing_move',
9 ICONCLASS : 'iconsmall',
10 JUMPMENU : 'jumpmenu',
12 LIGHTBOX : 'lightbox',
13 MOVEDOWN : 'movedown',
15 PAGECONTENT : 'page-content',
18 SECTIONADDMENUS : 'section_add_menus',
19 SECTIONHANDLE : 'section-handle',
21 SECTIONDRAGGABLE: 'sectiondraggable'
24 var DRAGSECTION = function() {
25 DRAGSECTION.superclass.constructor.apply(this, arguments);
27 Y.extend(DRAGSECTION, M.core.dragdrop, {
28 sectionlistselector : null,
30 initializer : function(params) {
31 // Set group for parent class
32 this.groups = [ CSS.SECTIONDRAGGABLE ];
33 this.samenodeclass = M.course.format.get_sectionwrapperclass();
34 this.parentnodeclass = M.course.format.get_containerclass();
36 // Check if we are in single section mode
37 if (Y.Node.one('.'+CSS.JUMPMENU)) {
40 // Initialise sections dragging
41 this.sectionlistselector = M.course.format.get_section_wrapper(Y);
42 if (this.sectionlistselector) {
43 this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector;
45 this.setup_for_section(this.sectionlistselector);
47 // Make each li element in the lists of sections draggable
48 var del = new Y.DD.Delegate({
49 container: '.'+CSS.COURSECONTENT,
50 nodes: '.' + CSS.SECTIONDRAGGABLE,
52 handles: ['.'+CSS.LEFT],
53 dragConfig: {groups: this.groups}
55 del.dd.plug(Y.Plugin.DDProxy, {
56 // Don't move the node at the end of the drag
59 del.dd.plug(Y.Plugin.DDConstrained, {
60 // Keep it inside the .course-content
61 constrain: '#'+CSS.PAGECONTENT,
64 del.dd.plug(Y.Plugin.DDWinScroll);
69 * Apply dragdrop features to the specified selector or node that refers to section(s)
71 * @param baseselector The CSS selector or node to limit scope to
74 setup_for_section : function(baseselector) {
75 Y.Node.all(baseselector).each(function(sectionnode) {
76 // Determine the section ID
77 var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode);
79 // We skip the top section as it is not draggable
82 var movedown = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEDOWN);
83 var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP);
86 var title = M.util.get_string('movesection', 'moodle', sectionid);
87 var cssleft = sectionnode.one('.'+CSS.LEFT);
89 if ((movedown || moveup) && cssleft) {
90 cssleft.setStyle('cursor', 'move');
91 cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
100 // This section can be moved - add the class to indicate this to Y.DD.
101 sectionnode.addClass(CSS.SECTIONDRAGGABLE);
108 * Drag-dropping related functions
110 drag_start : function(e) {
111 // Get our drag object
113 // Creat a dummy structure of the outer elemnents for clean styles application
114 var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'></'+M.course.format.get_containernode()+'>');
115 containernode.addClass(M.course.format.get_containerclass());
116 var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'></'+ M.course.format.get_sectionwrappernode()+'>');
117 sectionnode.addClass( M.course.format.get_sectionwrapperclass());
118 sectionnode.setStyle('margin', 0);
119 sectionnode.setContent(drag.get('node').get('innerHTML'));
120 containernode.appendChild(sectionnode);
121 drag.get('dragNode').setContent(containernode);
122 drag.get('dragNode').addClass(CSS.COURSECONTENT);
125 drag_dropmiss : function(e) {
126 // Missed the target, but we assume the user intended to drop it
127 // on the last last ghost node location, e.drag and e.drop should be
128 // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
132 get_section_index: function(node) {
133 var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y),
134 sectionList = Y.all(sectionlistselector),
135 nodeIndex = sectionList.indexOf(node),
136 zeroIndex = sectionList.indexOf(Y.one('#section-0'));
138 return (nodeIndex - zeroIndex);
141 drop_hit : function(e) {
144 // Get references to our nodes and their IDs.
145 var dragnode = drag.get('node'),
146 dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode),
147 loopstart = dragnodeid,
149 dropnodeindex = this.get_section_index(dragnode),
150 loopend = dropnodeindex;
152 if (dragnodeid === dropnodeindex) {
153 Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-course-dragdrop');
157 Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-course-dragdrop');
159 if (loopstart > loopend) {
160 // If we're going up, we need to swap the loop order
161 // because loops can't go backwards.
162 loopstart = dropnodeindex;
163 loopend = dragnodeid;
166 // Get the list of nodes
167 drag.get('dragNode').removeClass(CSS.COURSECONTENT);
168 var sectionlist = Y.Node.all(this.sectionlistselector);
170 // Add lightbox if it not there
171 var lightbox = M.util.add_lightbox(Y, dragnode);
175 // Handle any variables which we must pass back through to
176 var pageparams = this.get('config').pageparams;
177 for (varname in pageparams) {
178 params[varname] = pageparams[varname];
181 // Prepare request parameters
182 params.sesskey = M.cfg.sesskey;
183 params.courseId = this.get('courseid');
184 params['class'] = 'section';
185 params.field = 'move';
186 params.id = dragnodeid;
187 params.value = dropnodeindex;
190 var uri = M.cfg.wwwroot + this.get('ajaxurl');
196 start : function(tid) {
199 success: function(tid, response) {
200 // Update section titles, we can't simply swap them as
201 // they might have custom title
203 var responsetext = Y.JSON.parse(response.responseText);
204 if (responsetext.error) {
205 new M.core.ajaxException(responsetext);
207 M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
210 // Update all of the section IDs - first unset them, then set them
211 // to avoid duplicates in the DOM.
214 // Classic bubble sort algorithm is applied to the section
215 // nodes between original drag node location and the new one.
219 for (index = loopstart; index <= loopend; index++) {
220 if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) >
221 Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) {
222 Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) +
223 " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index)));
225 var sectionid = sectionlist.item(index - 1).get('id');
226 sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id'));
227 sectionlist.item(index).set('id', sectionid);
229 // See what format needs to swap.
230 M.course.format.swap_sections(Y, index - 1, index);
236 loopend = loopend - 1;
239 window.setTimeout(function() {
243 failure: function(tid, response) {
244 this.ajax_failure(response);
253 NAME : 'course-dragdrop-section',
267 var DRAGRESOURCE = function() {
268 DRAGRESOURCE.superclass.constructor.apply(this, arguments);
270 Y.extend(DRAGRESOURCE, M.core.dragdrop, {
271 initializer : function(params) {
272 // Set group for parent class
273 this.groups = ['resource'];
274 this.samenodeclass = CSS.ACTIVITY;
275 this.parentnodeclass = CSS.SECTION;
276 this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS);
278 // Go through all sections
279 var sectionlistselector = M.course.format.get_section_selector(Y);
280 if (sectionlistselector) {
281 sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector;
282 this.setup_for_section(sectionlistselector);
284 // Initialise drag & drop for all resources/activities
285 var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length+2)+' li.'+CSS.ACTIVITY;
286 var del = new Y.DD.Delegate({
287 container: '.'+CSS.COURSECONTENT,
290 handles: ['.' + CSS.EDITINGMOVE],
291 dragConfig: {groups: this.groups}
293 del.dd.plug(Y.Plugin.DDProxy, {
294 // Don't move the node at the end of the drag
298 del.dd.plug(Y.Plugin.DDConstrained, {
299 // Keep it inside the .course-content
300 constrain: '#'+CSS.PAGECONTENT
302 del.dd.plug(Y.Plugin.DDWinScroll);
304 M.course.coursebase.register_module(this);
305 M.course.dragres = this;
310 * Apply dragdrop features to the specified selector or node that refers to section(s)
312 * @param baseselector The CSS selector or node to limit scope to
315 setup_for_section : function(baseselector) {
316 Y.Node.all(baseselector).each(function(sectionnode) {
317 var resources = sectionnode.one('.'+CSS.CONTENT+' ul.'+CSS.SECTION);
318 // See if resources ul exists, if not create one
320 var resources = Y.Node.create('<ul></ul>');
321 resources.addClass(CSS.SECTION);
322 sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
324 resources.setAttribute('data-draggroups', this.groups.join(' '));
325 // Define empty ul as droptarget, so that item could be moved to empty list
326 var tar = new Y.DD.Drop({
332 // Initialise each resource/activity in this section
333 this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
337 * Apply dragdrop features to the specified selector or node that refers to resource(s)
339 * @param baseselector The CSS selector or node to limit scope to
342 setup_for_resource : function(baseselector) {
343 Y.Node.all(baseselector).each(function(resourcesnode) {
344 // Replace move icons
345 var move = resourcesnode.one('a.'+CSS.EDITINGMOVE);
347 move.replace(this.resourcedraghandle.cloneNode(true));
352 drag_start : function(e) {
353 // Get our drag object
355 drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
356 drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
359 drag_dropmiss : function(e) {
360 // Missed the target, but we assume the user intended to drop it
361 // on the last last ghost node location, e.drag and e.drop should be
362 // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
366 drop_hit : function(e) {
368 // Get a reference to our drag node
369 var dragnode = drag.get('node');
370 var dropnode = e.drop.get('node');
372 // Add spinner if it not there
373 var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN));
377 // Handle any variables which we must pass back through to
378 var pageparams = this.get('config').pageparams;
379 for (varname in pageparams) {
380 params[varname] = pageparams[varname];
383 // Prepare request parameters
384 params.sesskey = M.cfg.sesskey;
385 params.courseId = this.get('courseid');
386 params['class'] = 'resource';
387 params.field = 'move';
388 params.id = Number(Y.Moodle.core_course.util.cm.getId(dragnode));
389 params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));
391 if (dragnode.next()) {
392 params.beforeId = Number(Y.Moodle.core_course.util.cm.getId(dragnode.next()));
396 var uri = M.cfg.wwwroot + this.get('ajaxurl');
402 start : function(tid) {
403 this.lock_drag_handle(drag, CSS.EDITINGMOVE);
406 success: function(tid, response) {
407 var responsetext = Y.JSON.parse(response.responseText);
408 var params = {element: dragnode, visible: responsetext.visible};
409 M.course.coursebase.invoke_function('set_visibility_resource_ui', params);
410 this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
411 window.setTimeout(function(e) {
415 failure: function(tid, response) {
416 this.ajax_failure(response);
417 this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
419 // TODO: revert nodes location
426 NAME : 'course-dragdrop-resource',
440 M.course = M.course || {};
441 M.course.init_resource_dragdrop = function(params) {
442 new DRAGRESOURCE(params);
444 M.course.init_section_dragdrop = function(params) {
445 new DRAGSECTION(params);
447 }, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification', 'moodle-course-coursebase', 'moodle-course-util']});