diff --git a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-debug.js b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-debug.js new file mode 100644 index 00000000000..1f1a6ae1b0d --- /dev/null +++ b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-debug.js @@ -0,0 +1,492 @@ +YUI.add('moodle-course-dragdrop', function (Y, NAME) { + +/** + * Drag and Drop for course sections and course modules. + * + * @module moodle-course-dragdrop + */ + +var CSS = { + ACTIONAREA: '.actions', + ACTIVITY: 'activity', + ACTIVITYINSTANCE: 'activityinstance', + CONTENT: 'content', + COURSECONTENT: 'course-content', + EDITINGMOVE: 'editing_move', + ICONCLASS: 'iconsmall', + JUMPMENU: 'jumpmenu', + LEFT: 'left', + LIGHTBOX: 'lightbox', + MOVEDOWN: 'movedown', + MOVEUP: 'moveup', + PAGECONTENT: 'page-content', + RIGHT: 'right', + SECTION: 'section', + SECTIONADDMENUS: 'section_add_menus', + SECTIONHANDLE: 'section-handle', + SUMMARY: 'summary', + SECTIONDRAGGABLE: 'sectiondraggable' +}; + +M.course = M.course || {}; +/** + * Section drag and drop. + * + * @class M.course.dragdrop.section + * @constructor + * @extends M.core.dragdrop + */ +var DRAGSECTION = function() { + DRAGSECTION.superclass.constructor.apply(this, arguments); +}; +Y.extend(DRAGSECTION, M.core.dragdrop, { + sectionlistselector: null, + + initializer: function() { + // Set group for parent class + this.groups = [ CSS.SECTIONDRAGGABLE ]; + this.samenodeclass = M.course.format.get_sectionwrapperclass(); + this.parentnodeclass = M.course.format.get_containerclass(); + + // Check if we are in single section mode + if (Y.Node.one('.' + CSS.JUMPMENU)) { + return false; + } + // Initialise sections dragging + this.sectionlistselector = M.course.format.get_section_wrapper(Y); + if (this.sectionlistselector) { + this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector; + + this.setup_for_section(this.sectionlistselector); + + // Make each li element in the lists of sections draggable + var del = new Y.DD.Delegate({ + container: '.' + CSS.COURSECONTENT, + nodes: '.' + CSS.SECTIONDRAGGABLE, + target: true, + handles: ['.' + CSS.LEFT], + dragConfig: {groups: this.groups} + }); + del.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false + }); + del.dd.plug(Y.Plugin.DDConstrained, { + // Keep it inside the .course-content + constrain: '#' + CSS.PAGECONTENT, + stickY: true + }); + del.dd.plug(Y.Plugin.DDWinScroll); + } + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to section(s) + * + * @method setup_for_section + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_section: function(baseselector) { + Y.Node.all(baseselector).each(function(sectionnode) { + // Determine the section ID + var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode); + + // We skip the top section as it is not draggable + if (sectionid > 0) { + // Remove move icons + var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN); + var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP); + + // Add dragger icon + var title = M.util.get_string('movesection', 'moodle', sectionid); + var cssleft = sectionnode.one('.' + CSS.LEFT); + + if ((movedown || moveup) && cssleft) { + cssleft.setStyle('cursor', 'move'); + cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true)); + + if (moveup) { + moveup.remove(); + } + if (movedown) { + movedown.remove(); + } + + // This section can be moved - add the class to indicate this to Y.DD. + sectionnode.addClass(CSS.SECTIONDRAGGABLE); + } + } + }, this); + }, + + /* + * Drag-dropping related functions + */ + drag_start: function(e) { + // Get our drag object + var drag = e.target; + // Creat a dummy structure of the outer elemnents for clean styles application + var containernode = Y.Node.create('<' + M.course.format.get_containernode() + '>'); + containernode.addClass(M.course.format.get_containerclass()); + var sectionnode = Y.Node.create('<' + M.course.format.get_sectionwrappernode() + '>'); + sectionnode.addClass( M.course.format.get_sectionwrapperclass()); + sectionnode.setStyle('margin', 0); + sectionnode.setContent(drag.get('node').get('innerHTML')); + containernode.appendChild(sectionnode); + drag.get('dragNode').setContent(containernode); + drag.get('dragNode').addClass(CSS.COURSECONTENT); + }, + + drag_dropmiss: function(e) { + // Missed the target, but we assume the user intended to drop it + // on the last last ghost node location, e.drag and e.drop should be + // prepared by global_drag_dropmiss parent so simulate drop_hit(e). + this.drop_hit(e); + }, + + get_section_index: function(node) { + var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y), + sectionList = Y.all(sectionlistselector), + nodeIndex = sectionList.indexOf(node), + zeroIndex = sectionList.indexOf(Y.one('#section-0')); + + return (nodeIndex - zeroIndex); + }, + + drop_hit: function(e) { + var drag = e.drag; + + // Get references to our nodes and their IDs. + var dragnode = drag.get('node'), + dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode), + loopstart = dragnodeid, + + dropnodeindex = this.get_section_index(dragnode), + loopend = dropnodeindex; + + if (dragnodeid === dropnodeindex) { + Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-course-dragdrop'); + return; + } + + Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-course-dragdrop'); + + if (loopstart > loopend) { + // If we're going up, we need to swap the loop order + // because loops can't go backwards. + loopstart = dropnodeindex; + loopend = dragnodeid; + } + + // Get the list of nodes. + drag.get('dragNode').removeClass(CSS.COURSECONTENT); + var sectionlist = Y.Node.all(this.sectionlistselector); + + // Add a lightbox if it's not there. + var lightbox = M.util.add_lightbox(Y, dragnode); + + // Handle any variables which we must pass via AJAX. + var params = {}, + pageparams = this.get('config').pageparams, + varname; + + for (varname in pageparams) { + if (!pageparams.hasOwnProperty(varname)) { + continue; + } + params[varname] = pageparams[varname]; + } + + // Prepare request parameters + params.sesskey = M.cfg.sesskey; + params.courseId = this.get('courseid'); + params['class'] = 'section'; + params.field = 'move'; + params.id = dragnodeid; + params.value = dropnodeindex; + + // Perform the AJAX request. + var uri = M.cfg.wwwroot + this.get('ajaxurl'); + Y.io(uri, { + method: 'POST', + data: params, + on: { + start: function() { + lightbox.show(); + }, + success: function(tid, response) { + // Update section titles, we can't simply swap them as + // they might have custom title + try { + var responsetext = Y.JSON.parse(response.responseText); + if (responsetext.error) { + new M.core.ajaxException(responsetext); + } + M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend); + } catch (e) {} + + // Update all of the section IDs - first unset them, then set them + // to avoid duplicates in the DOM. + var index; + + // Classic bubble sort algorithm is applied to the section + // nodes between original drag node location and the new one. + var swapped = false; + do { + swapped = false; + for (index = loopstart; index <= loopend; index++) { + if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) > + Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) { + Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) + + " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index))); + // Swap section id. + var sectionid = sectionlist.item(index - 1).get('id'); + sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id')); + sectionlist.item(index).set('id', sectionid); + + // See what format needs to swap. + M.course.format.swap_sections(Y, index - 1, index); + + // Update flag. + swapped = true; + } + } + loopend = loopend - 1; + } while (swapped); + + window.setTimeout(function() { + lightbox.hide(); + }, 250); + }, + + failure: function(tid, response) { + this.ajax_failure(response); + lightbox.hide(); + } + }, + context:this + }); + } + +}, { + NAME: 'course-dragdrop-section', + ATTRS: { + courseid: { + value: null + }, + ajaxurl: { + value: 0 + }, + config: { + value: 0 + } + } +}); + +M.course = M.course || {}; +M.course.init_section_dragdrop = function(params) { + new DRAGSECTION(params); +}; +/** + * Resource drag and drop. + * + * @class M.course.dragdrop.resource + * @constructor + * @extends M.core.dragdrop + */ +var DRAGRESOURCE = function() { + DRAGRESOURCE.superclass.constructor.apply(this, arguments); +}; +Y.extend(DRAGRESOURCE, M.core.dragdrop, { + initializer: function() { + // Set group for parent class + this.groups = ['resource']; + this.samenodeclass = CSS.ACTIVITY; + this.parentnodeclass = CSS.SECTION; + this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true); + + // Go through all sections + var sectionlistselector = M.course.format.get_section_selector(Y); + if (sectionlistselector) { + sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector; + this.setup_for_section(sectionlistselector); + + // Initialise drag & drop for all resources/activities + var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY; + var del = new Y.DD.Delegate({ + container: '.' + CSS.COURSECONTENT, + nodes: nodeselector, + target: true, + handles: ['.' + CSS.EDITINGMOVE], + dragConfig: {groups: this.groups} + }); + del.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false, + cloneNode: true + }); + del.dd.plug(Y.Plugin.DDConstrained, { + // Keep it inside the .course-content + constrain: '#' + CSS.PAGECONTENT + }); + del.dd.plug(Y.Plugin.DDWinScroll); + + M.course.coursebase.register_module(this); + M.course.dragres = this; + } + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to section(s) + * + * @method setup_for_section + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_section: function(baseselector) { + Y.Node.all(baseselector).each(function(sectionnode) { + var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION); + // See if resources ul exists, if not create one + if (!resources) { + resources = Y.Node.create(''); + resources.addClass(CSS.SECTION); + sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after'); + } + resources.setAttribute('data-draggroups', this.groups.join(' ')); + // Define empty ul as droptarget, so that item could be moved to empty list + new Y.DD.Drop({ + node: resources, + groups: this.groups, + padding: '20 0 20 0' + }); + + // Initialise each resource/activity in this section + this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY); + }, this); + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to resource(s) + * + * @method setup_for_resource + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_resource: function(baseselector) { + Y.Node.all(baseselector).each(function(resourcesnode) { + // Replace move icons + var move = resourcesnode.one('a.' + CSS.EDITINGMOVE); + if (move) { + move.replace(this.resourcedraghandle.cloneNode(true)); + } + }, this); + }, + + drag_start: function(e) { + // Get our drag object + var drag = e.target; + drag.get('dragNode').setContent(drag.get('node').get('innerHTML')); + drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline'); + }, + + drag_dropmiss: function(e) { + // Missed the target, but we assume the user intended to drop it + // on the last last ghost node location, e.drag and e.drop should be + // prepared by global_drag_dropmiss parent so simulate drop_hit(e). + this.drop_hit(e); + }, + + drop_hit: function(e) { + var drag = e.drag; + // Get a reference to our drag node + var dragnode = drag.get('node'); + var dropnode = e.drop.get('node'); + + // Add spinner if it not there + var actionarea = dragnode.one(CSS.ACTIONAREA); + var spinner = M.util.add_spinner(Y, actionarea); + + var params = {}; + + // Handle any variables which we must pass back through to + var pageparams = this.get('config').pageparams; + var varname; + for (varname in pageparams) { + params[varname] = pageparams[varname]; + } + + // Prepare request parameters + params.sesskey = M.cfg.sesskey; + params.courseId = this.get('courseid'); + params['class'] = 'resource'; + params.field = 'move'; + params.id = Number(Y.Moodle.core_course.util.cm.getId(dragnode)); + params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true)); + + if (dragnode.next()) { + params.beforeId = Number(Y.Moodle.core_course.util.cm.getId(dragnode.next())); + } + + // Do AJAX request + var uri = M.cfg.wwwroot + this.get('ajaxurl'); + + Y.io(uri, { + method: 'POST', + data: params, + on: { + start: function() { + this.lock_drag_handle(drag, CSS.EDITINGMOVE); + spinner.show(); + }, + success: function(tid, response) { + var responsetext = Y.JSON.parse(response.responseText); + var params = {element: dragnode, visible: responsetext.visible}; + M.course.coursebase.invoke_function('set_visibility_resource_ui', params); + this.unlock_drag_handle(drag, CSS.EDITINGMOVE); + window.setTimeout(function() { + spinner.hide(); + }, 250); + }, + failure: function(tid, response) { + this.ajax_failure(response); + this.unlock_drag_handle(drag, CSS.SECTIONHANDLE); + spinner.hide(); + // TODO: revert nodes location + } + }, + context:this + }); + } +}, { + NAME: 'course-dragdrop-resource', + ATTRS: { + courseid: { + value: null + }, + ajaxurl: { + value: 0 + }, + config: { + value: 0 + } + } +}); + +M.course = M.course || {}; +M.course.init_resource_dragdrop = function(params) { + new DRAGRESOURCE(params); +}; + + +}, '@VERSION@', { + "requires": [ + "base", + "node", + "io", + "dom", + "dd", + "dd-scroll", + "moodle-core-dragdrop", + "moodle-core-notification", + "moodle-course-coursebase", + "moodle-course-util" + ] +}); diff --git a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js new file mode 100644 index 00000000000..90f29135e47 --- /dev/null +++ b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js @@ -0,0 +1,2 @@ +YUI.add("moodle-course-dragdrop",function(e,t){var n={ACTIONAREA:".actions",ACTIVITY:"activity",ACTIVITYINSTANCE:"activityinstance",CONTENT:"content",COURSECONTENT:"course-content",EDITINGMOVE:"editing_move",ICONCLASS:"iconsmall",JUMPMENU:"jumpmenu",LEFT:"left",LIGHTBOX:"lightbox",MOVEDOWN:"movedown",MOVEUP:"moveup",PAGECONTENT:"page-content",RIGHT:"right",SECTION:"section",SECTIONADDMENUS:"section_add_menus",SECTIONHANDLE:"section-handle",SUMMARY:"summary",SECTIONDRAGGABLE:"sectiondraggable"};M.course=M.course||{};var r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,M.core.dragdrop,{sectionlistselector:null,initializer:function(){this.groups=[n.SECTIONDRAGGABLE],this.samenodeclass=M.course.format.get_sectionwrapperclass(),this.parentnodeclass=M.course.format.get_containerclass();if(e.Node.one("."+n.JUMPMENU))return!1;this.sectionlistselector=M.course.format.get_section_wrapper(e);if(this.sectionlistselector){this.sectionlistselector="."+n.COURSECONTENT+" "+this.sectionlistselector,this.setup_for_section(this.sectionlistselector);var t=new e.DD.Delegate({container:"."+n.COURSECONTENT,nodes:"."+n.SECTIONDRAGGABLE,target:!0,handles:["."+n.LEFT],dragConfig:{groups:this.groups}});t.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),t.dd.plug(e.Plugin.DDConstrained,{constrain:"#"+n.PAGECONTENT,stickY:!0}),t.dd.plug(e.Plugin.DDWinScroll)}},setup_for_section:function(t){e.Node.all(t).each(function(t){var r=e.Moodle.core_course.util.section.getId(t);if(r>0){var i=t.one("."+n.RIGHT+" a."+n.MOVEDOWN),s=t.one("."+n.RIGHT+" a."+n.MOVEUP),o=M.util.get_string("movesection","moodle",r),u=t.one("."+n.LEFT);(i||s)&&u&&(u.setStyle("cursor","move"),u.appendChild(this.get_drag_handle(o,n.SECTIONHANDLE,"icon",!0)),s&&s.remove(),i&&i.remove(),t.addClass(n.SECTIONDRAGGABLE))}},this)},drag_start:function(t){var r=t.target,i=e.Node.create("<"+M.course.format.get_containernode()+">");i.addClass(M.course.format.get_containerclass());var s=e.Node.create("<"+M.course.format.get_sectionwrappernode()+">");s.addClass(M.course.format.get_sectionwrapperclass()),s.setStyle("margin",0),s.setContent(r.get("node").get("innerHTML")),i.appendChild(s),r.get("dragNode").setContent(i),r.get("dragNode").addClass(n.COURSECONTENT)},drag_dropmiss:function(e){this.drop_hit(e)},get_section_index:function(t){var r="."+n.COURSECONTENT+" "+M.course.format.get_section_selector(e),i=e.all(r),s=i.indexOf(t),o=i.indexOf(e.one("#section-0"));return s-o},drop_hit:function(t){var r=t.drag,i=r.get("node"),s=e.Moodle.core_course.util.section.getId(i),o=s,u=this.get_section_index(i),a=u;if(s===u)return;o>a&&(o=u,a=s),r.get("dragNode").removeClass(n.COURSECONTENT);var f=e.Node.all(this.sectionlistselector),l=M.util.add_lightbox(e,i),c={},h=this.get("config").pageparams,p;for(p in h){if(!h.hasOwnProperty(p))continue;c[p]=h[p]}c.sesskey=M.cfg.sesskey,c.courseId=this.get("courseid"),c["class"]="section",c.field="move",c.id=s,c.value=u;var d=M.cfg.wwwroot+this.get("ajaxurl");e.io(d,{method:"POST",data:c,on:{start:function(){l.show()},success:function(t,n){try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r),M.course.format.process_sections(e,f,r,o,a)}catch(i){}var s,u=!1;do{u=!1;for(s=o;s<=a;s++)if(e.Moodle.core_course.util.section.getId(f.item(s-1))>e.Moodle.core_course.util.section.getId(f.item(s))){var c=f.item(s-1).get("id");f.item(s-1).set("id",f.item(s).get("id")),f.item(s).set("id",c),M.course.format.swap_sections(e,s-1,s),u=!0}a-=1}while(u);window.setTimeout(function(){l.hide()},250)},failure:function(e,t){this.ajax_failure(t),l.hide()}},context:this})}},{NAME:"course-dragdrop-section",ATTRS:{courseid:{value:null},ajaxurl:{value:0},config:{value:0}}}),M.course=M.course||{},M.course.init_section_dragdrop=function(e){new r(e)};var i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,M.core.dragdrop,{initializer:function(){this.groups=["resource"],this.samenodeclass=n.ACTIVITY,this.parentnodeclass=n.SECTION,this.resourcedraghandle=this.get_drag_handle(M.str.moodle.move,n.EDITINGMOVE,n.ICONCLASS,!0);var t=M.course.format.get_section_selector(e);if(t){t="."+n.COURSECONTENT+" "+t,this.setup_for_section(t);var r=t.slice(n.COURSECONTENT.length+2)+" li."+n.ACTIVITY,i=new e.DD.Delegate({container:"."+n.COURSECONTENT,nodes:r,target:!0,handles:["."+n.EDITINGMOVE],dragConfig:{groups:this.groups}});i.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1,cloneNode:!0}),i.dd.plug(e.Plugin.DDConstrained,{constrain:"#"+n.PAGECONTENT}),i.dd.plug(e.Plugin.DDWinScroll),M.course.coursebase.register_module(this),M.course.dragres=this}},setup_for_section:function(t){e.Node.all(t).each(function(t){var r=t.one("."+n.CONTENT+" ul."+n.SECTION);r||(r=e.Node.create(""),r.addClass(n.SECTION),t.one("."+n.CONTENT+" div."+n.SUMMARY).insert(r,"after")),r.setAttribute("data-draggroups",this.groups.join(" ")),new e.DD.Drop({node:r,groups:this.groups,padding:"20 0 20 0"}),this.setup_for_resource("#"+t.get("id")+" li."+n.ACTIVITY)},this)},setup_for_resource:function(t){e.Node.all(t).each(function(e){var t=e.one("a."+n.EDITINGMOVE);t&&t.replace(this.resourcedraghandle.cloneNode(!0))},this)},drag_start:function(e){var t=e.target;t.get("dragNode").setContent(t.get("node").get("innerHTML")),t.get("dragNode").all("img.iconsmall").setStyle("vertical-align","baseline")},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var r=t.drag,i=r.get("node"),s=t.drop.get("node"),o=i.one(n.ACTIONAREA),u=M.util.add_spinner(e,o),a={},f=this.get("config").pageparams,l;for(l in f)a[l]=f[l];a.sesskey=M.cfg.sesskey,a.courseId=this.get("courseid"),a["class"]="resource",a.field="move",a.id=Number(e.Moodle.core_course.util.cm.getId(i)),a.sectionId=e.Moodle.core_course.util.section.getId(s.ancestor(M.course.format.get_section_wrapper(e),!0)),i.next()&&(a.beforeId=Number(e.Moodle.core_course.util.cm.getId(i.next())));var c=M.cfg.wwwroot+this.get("ajaxurl");e.io(c, +{method:"POST",data:a,on:{start:function(){this.lock_drag_handle(r,n.EDITINGMOVE),u.show()},success:function(t,s){var o=e.JSON.parse(s.responseText),a={element:i,visible:o.visible};M.course.coursebase.invoke_function("set_visibility_resource_ui",a),this.unlock_drag_handle(r,n.EDITINGMOVE),window.setTimeout(function(){u.hide()},250)},failure:function(e,t){this.ajax_failure(t),this.unlock_drag_handle(r,n.SECTIONHANDLE),u.hide()}},context:this})}},{NAME:"course-dragdrop-resource",ATTRS:{courseid:{value:null},ajaxurl:{value:0},config:{value:0}}}),M.course=M.course||{},M.course.init_resource_dragdrop=function(e){new i(e)}},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification","moodle-course-coursebase","moodle-course-util"]}); diff --git a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop.js b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop.js new file mode 100644 index 00000000000..795c5bbf0e3 --- /dev/null +++ b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop.js @@ -0,0 +1,488 @@ +YUI.add('moodle-course-dragdrop', function (Y, NAME) { + +/** + * Drag and Drop for course sections and course modules. + * + * @module moodle-course-dragdrop + */ + +var CSS = { + ACTIONAREA: '.actions', + ACTIVITY: 'activity', + ACTIVITYINSTANCE: 'activityinstance', + CONTENT: 'content', + COURSECONTENT: 'course-content', + EDITINGMOVE: 'editing_move', + ICONCLASS: 'iconsmall', + JUMPMENU: 'jumpmenu', + LEFT: 'left', + LIGHTBOX: 'lightbox', + MOVEDOWN: 'movedown', + MOVEUP: 'moveup', + PAGECONTENT: 'page-content', + RIGHT: 'right', + SECTION: 'section', + SECTIONADDMENUS: 'section_add_menus', + SECTIONHANDLE: 'section-handle', + SUMMARY: 'summary', + SECTIONDRAGGABLE: 'sectiondraggable' +}; + +M.course = M.course || {}; +/** + * Section drag and drop. + * + * @class M.course.dragdrop.section + * @constructor + * @extends M.core.dragdrop + */ +var DRAGSECTION = function() { + DRAGSECTION.superclass.constructor.apply(this, arguments); +}; +Y.extend(DRAGSECTION, M.core.dragdrop, { + sectionlistselector: null, + + initializer: function() { + // Set group for parent class + this.groups = [ CSS.SECTIONDRAGGABLE ]; + this.samenodeclass = M.course.format.get_sectionwrapperclass(); + this.parentnodeclass = M.course.format.get_containerclass(); + + // Check if we are in single section mode + if (Y.Node.one('.' + CSS.JUMPMENU)) { + return false; + } + // Initialise sections dragging + this.sectionlistselector = M.course.format.get_section_wrapper(Y); + if (this.sectionlistselector) { + this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector; + + this.setup_for_section(this.sectionlistselector); + + // Make each li element in the lists of sections draggable + var del = new Y.DD.Delegate({ + container: '.' + CSS.COURSECONTENT, + nodes: '.' + CSS.SECTIONDRAGGABLE, + target: true, + handles: ['.' + CSS.LEFT], + dragConfig: {groups: this.groups} + }); + del.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false + }); + del.dd.plug(Y.Plugin.DDConstrained, { + // Keep it inside the .course-content + constrain: '#' + CSS.PAGECONTENT, + stickY: true + }); + del.dd.plug(Y.Plugin.DDWinScroll); + } + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to section(s) + * + * @method setup_for_section + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_section: function(baseselector) { + Y.Node.all(baseselector).each(function(sectionnode) { + // Determine the section ID + var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode); + + // We skip the top section as it is not draggable + if (sectionid > 0) { + // Remove move icons + var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN); + var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP); + + // Add dragger icon + var title = M.util.get_string('movesection', 'moodle', sectionid); + var cssleft = sectionnode.one('.' + CSS.LEFT); + + if ((movedown || moveup) && cssleft) { + cssleft.setStyle('cursor', 'move'); + cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true)); + + if (moveup) { + moveup.remove(); + } + if (movedown) { + movedown.remove(); + } + + // This section can be moved - add the class to indicate this to Y.DD. + sectionnode.addClass(CSS.SECTIONDRAGGABLE); + } + } + }, this); + }, + + /* + * Drag-dropping related functions + */ + drag_start: function(e) { + // Get our drag object + var drag = e.target; + // Creat a dummy structure of the outer elemnents for clean styles application + var containernode = Y.Node.create('<' + M.course.format.get_containernode() + '>'); + containernode.addClass(M.course.format.get_containerclass()); + var sectionnode = Y.Node.create('<' + M.course.format.get_sectionwrappernode() + '>'); + sectionnode.addClass( M.course.format.get_sectionwrapperclass()); + sectionnode.setStyle('margin', 0); + sectionnode.setContent(drag.get('node').get('innerHTML')); + containernode.appendChild(sectionnode); + drag.get('dragNode').setContent(containernode); + drag.get('dragNode').addClass(CSS.COURSECONTENT); + }, + + drag_dropmiss: function(e) { + // Missed the target, but we assume the user intended to drop it + // on the last last ghost node location, e.drag and e.drop should be + // prepared by global_drag_dropmiss parent so simulate drop_hit(e). + this.drop_hit(e); + }, + + get_section_index: function(node) { + var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y), + sectionList = Y.all(sectionlistselector), + nodeIndex = sectionList.indexOf(node), + zeroIndex = sectionList.indexOf(Y.one('#section-0')); + + return (nodeIndex - zeroIndex); + }, + + drop_hit: function(e) { + var drag = e.drag; + + // Get references to our nodes and their IDs. + var dragnode = drag.get('node'), + dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode), + loopstart = dragnodeid, + + dropnodeindex = this.get_section_index(dragnode), + loopend = dropnodeindex; + + if (dragnodeid === dropnodeindex) { + return; + } + + + if (loopstart > loopend) { + // If we're going up, we need to swap the loop order + // because loops can't go backwards. + loopstart = dropnodeindex; + loopend = dragnodeid; + } + + // Get the list of nodes. + drag.get('dragNode').removeClass(CSS.COURSECONTENT); + var sectionlist = Y.Node.all(this.sectionlistselector); + + // Add a lightbox if it's not there. + var lightbox = M.util.add_lightbox(Y, dragnode); + + // Handle any variables which we must pass via AJAX. + var params = {}, + pageparams = this.get('config').pageparams, + varname; + + for (varname in pageparams) { + if (!pageparams.hasOwnProperty(varname)) { + continue; + } + params[varname] = pageparams[varname]; + } + + // Prepare request parameters + params.sesskey = M.cfg.sesskey; + params.courseId = this.get('courseid'); + params['class'] = 'section'; + params.field = 'move'; + params.id = dragnodeid; + params.value = dropnodeindex; + + // Perform the AJAX request. + var uri = M.cfg.wwwroot + this.get('ajaxurl'); + Y.io(uri, { + method: 'POST', + data: params, + on: { + start: function() { + lightbox.show(); + }, + success: function(tid, response) { + // Update section titles, we can't simply swap them as + // they might have custom title + try { + var responsetext = Y.JSON.parse(response.responseText); + if (responsetext.error) { + new M.core.ajaxException(responsetext); + } + M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend); + } catch (e) {} + + // Update all of the section IDs - first unset them, then set them + // to avoid duplicates in the DOM. + var index; + + // Classic bubble sort algorithm is applied to the section + // nodes between original drag node location and the new one. + var swapped = false; + do { + swapped = false; + for (index = loopstart; index <= loopend; index++) { + if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) > + Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) { + // Swap section id. + var sectionid = sectionlist.item(index - 1).get('id'); + sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id')); + sectionlist.item(index).set('id', sectionid); + + // See what format needs to swap. + M.course.format.swap_sections(Y, index - 1, index); + + // Update flag. + swapped = true; + } + } + loopend = loopend - 1; + } while (swapped); + + window.setTimeout(function() { + lightbox.hide(); + }, 250); + }, + + failure: function(tid, response) { + this.ajax_failure(response); + lightbox.hide(); + } + }, + context:this + }); + } + +}, { + NAME: 'course-dragdrop-section', + ATTRS: { + courseid: { + value: null + }, + ajaxurl: { + value: 0 + }, + config: { + value: 0 + } + } +}); + +M.course = M.course || {}; +M.course.init_section_dragdrop = function(params) { + new DRAGSECTION(params); +}; +/** + * Resource drag and drop. + * + * @class M.course.dragdrop.resource + * @constructor + * @extends M.core.dragdrop + */ +var DRAGRESOURCE = function() { + DRAGRESOURCE.superclass.constructor.apply(this, arguments); +}; +Y.extend(DRAGRESOURCE, M.core.dragdrop, { + initializer: function() { + // Set group for parent class + this.groups = ['resource']; + this.samenodeclass = CSS.ACTIVITY; + this.parentnodeclass = CSS.SECTION; + this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true); + + // Go through all sections + var sectionlistselector = M.course.format.get_section_selector(Y); + if (sectionlistselector) { + sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector; + this.setup_for_section(sectionlistselector); + + // Initialise drag & drop for all resources/activities + var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY; + var del = new Y.DD.Delegate({ + container: '.' + CSS.COURSECONTENT, + nodes: nodeselector, + target: true, + handles: ['.' + CSS.EDITINGMOVE], + dragConfig: {groups: this.groups} + }); + del.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false, + cloneNode: true + }); + del.dd.plug(Y.Plugin.DDConstrained, { + // Keep it inside the .course-content + constrain: '#' + CSS.PAGECONTENT + }); + del.dd.plug(Y.Plugin.DDWinScroll); + + M.course.coursebase.register_module(this); + M.course.dragres = this; + } + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to section(s) + * + * @method setup_for_section + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_section: function(baseselector) { + Y.Node.all(baseselector).each(function(sectionnode) { + var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION); + // See if resources ul exists, if not create one + if (!resources) { + resources = Y.Node.create(''); + resources.addClass(CSS.SECTION); + sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after'); + } + resources.setAttribute('data-draggroups', this.groups.join(' ')); + // Define empty ul as droptarget, so that item could be moved to empty list + new Y.DD.Drop({ + node: resources, + groups: this.groups, + padding: '20 0 20 0' + }); + + // Initialise each resource/activity in this section + this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY); + }, this); + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to resource(s) + * + * @method setup_for_resource + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_resource: function(baseselector) { + Y.Node.all(baseselector).each(function(resourcesnode) { + // Replace move icons + var move = resourcesnode.one('a.' + CSS.EDITINGMOVE); + if (move) { + move.replace(this.resourcedraghandle.cloneNode(true)); + } + }, this); + }, + + drag_start: function(e) { + // Get our drag object + var drag = e.target; + drag.get('dragNode').setContent(drag.get('node').get('innerHTML')); + drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline'); + }, + + drag_dropmiss: function(e) { + // Missed the target, but we assume the user intended to drop it + // on the last last ghost node location, e.drag and e.drop should be + // prepared by global_drag_dropmiss parent so simulate drop_hit(e). + this.drop_hit(e); + }, + + drop_hit: function(e) { + var drag = e.drag; + // Get a reference to our drag node + var dragnode = drag.get('node'); + var dropnode = e.drop.get('node'); + + // Add spinner if it not there + var actionarea = dragnode.one(CSS.ACTIONAREA); + var spinner = M.util.add_spinner(Y, actionarea); + + var params = {}; + + // Handle any variables which we must pass back through to + var pageparams = this.get('config').pageparams; + var varname; + for (varname in pageparams) { + params[varname] = pageparams[varname]; + } + + // Prepare request parameters + params.sesskey = M.cfg.sesskey; + params.courseId = this.get('courseid'); + params['class'] = 'resource'; + params.field = 'move'; + params.id = Number(Y.Moodle.core_course.util.cm.getId(dragnode)); + params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true)); + + if (dragnode.next()) { + params.beforeId = Number(Y.Moodle.core_course.util.cm.getId(dragnode.next())); + } + + // Do AJAX request + var uri = M.cfg.wwwroot + this.get('ajaxurl'); + + Y.io(uri, { + method: 'POST', + data: params, + on: { + start: function() { + this.lock_drag_handle(drag, CSS.EDITINGMOVE); + spinner.show(); + }, + success: function(tid, response) { + var responsetext = Y.JSON.parse(response.responseText); + var params = {element: dragnode, visible: responsetext.visible}; + M.course.coursebase.invoke_function('set_visibility_resource_ui', params); + this.unlock_drag_handle(drag, CSS.EDITINGMOVE); + window.setTimeout(function() { + spinner.hide(); + }, 250); + }, + failure: function(tid, response) { + this.ajax_failure(response); + this.unlock_drag_handle(drag, CSS.SECTIONHANDLE); + spinner.hide(); + // TODO: revert nodes location + } + }, + context:this + }); + } +}, { + NAME: 'course-dragdrop-resource', + ATTRS: { + courseid: { + value: null + }, + ajaxurl: { + value: 0 + }, + config: { + value: 0 + } + } +}); + +M.course = M.course || {}; +M.course.init_resource_dragdrop = function(params) { + new DRAGRESOURCE(params); +}; + + +}, '@VERSION@', { + "requires": [ + "base", + "node", + "io", + "dom", + "dd", + "dd-scroll", + "moodle-core-dragdrop", + "moodle-core-notification", + "moodle-course-coursebase", + "moodle-course-util" + ] +}); diff --git a/course/yui/dragdrop/dragdrop.js b/course/yui/dragdrop/dragdrop.js deleted file mode 100644 index 7d8a9dd0a68..00000000000 --- a/course/yui/dragdrop/dragdrop.js +++ /dev/null @@ -1,454 +0,0 @@ -YUI.add('moodle-course-dragdrop', function(Y) { - - var CSS = { - ACTIONAREA: '.actions', - ACTIVITY : 'activity', - ACTIVITYINSTANCE : 'activityinstance', - CONTENT : 'content', - COURSECONTENT : 'course-content', - EDITINGMOVE : 'editing_move', - ICONCLASS : 'iconsmall', - JUMPMENU : 'jumpmenu', - LEFT : 'left', - LIGHTBOX : 'lightbox', - MOVEDOWN : 'movedown', - MOVEUP : 'moveup', - PAGECONTENT : 'page-content', - RIGHT : 'right', - SECTION : 'section', - SECTIONADDMENUS : 'section_add_menus', - SECTIONHANDLE : 'section-handle', - SUMMARY : 'summary', - SECTIONDRAGGABLE: 'sectiondraggable' - }; - - var DRAGSECTION = function() { - DRAGSECTION.superclass.constructor.apply(this, arguments); - }; - Y.extend(DRAGSECTION, M.core.dragdrop, { - sectionlistselector : null, - - initializer : function() { - // Set group for parent class - this.groups = [ CSS.SECTIONDRAGGABLE ]; - this.samenodeclass = M.course.format.get_sectionwrapperclass(); - this.parentnodeclass = M.course.format.get_containerclass(); - - // Check if we are in single section mode - if (Y.Node.one('.'+CSS.JUMPMENU)) { - return false; - } - // Initialise sections dragging - this.sectionlistselector = M.course.format.get_section_wrapper(Y); - if (this.sectionlistselector) { - this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector; - - this.setup_for_section(this.sectionlistselector); - - // Make each li element in the lists of sections draggable - var del = new Y.DD.Delegate({ - container: '.'+CSS.COURSECONTENT, - nodes: '.' + CSS.SECTIONDRAGGABLE, - target: true, - handles: ['.'+CSS.LEFT], - dragConfig: {groups: this.groups} - }); - del.dd.plug(Y.Plugin.DDProxy, { - // Don't move the node at the end of the drag - moveOnEnd: false - }); - del.dd.plug(Y.Plugin.DDConstrained, { - // Keep it inside the .course-content - constrain: '#'+CSS.PAGECONTENT, - stickY: true - }); - del.dd.plug(Y.Plugin.DDWinScroll); - } - }, - - /** - * Apply dragdrop features to the specified selector or node that refers to section(s) - * - * @param baseselector The CSS selector or node to limit scope to - * @return void - */ - setup_for_section : function(baseselector) { - Y.Node.all(baseselector).each(function(sectionnode) { - // Determine the section ID - var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode); - - // We skip the top section as it is not draggable - if (sectionid > 0) { - // Remove move icons - var movedown = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEDOWN); - var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP); - - // Add dragger icon - var title = M.util.get_string('movesection', 'moodle', sectionid); - var cssleft = sectionnode.one('.'+CSS.LEFT); - - if ((movedown || moveup) && cssleft) { - cssleft.setStyle('cursor', 'move'); - cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true)); - - if (moveup) { - moveup.remove(); - } - if (movedown) { - movedown.remove(); - } - - // This section can be moved - add the class to indicate this to Y.DD. - sectionnode.addClass(CSS.SECTIONDRAGGABLE); - } - } - }, this); - }, - - /* - * Drag-dropping related functions - */ - drag_start : function(e) { - // Get our drag object - var drag = e.target; - // Creat a dummy structure of the outer elemnents for clean styles application - var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'>'); - containernode.addClass(M.course.format.get_containerclass()); - var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'>'); - sectionnode.addClass( M.course.format.get_sectionwrapperclass()); - sectionnode.setStyle('margin', 0); - sectionnode.setContent(drag.get('node').get('innerHTML')); - containernode.appendChild(sectionnode); - drag.get('dragNode').setContent(containernode); - drag.get('dragNode').addClass(CSS.COURSECONTENT); - }, - - drag_dropmiss : function(e) { - // Missed the target, but we assume the user intended to drop it - // on the last last ghost node location, e.drag and e.drop should be - // prepared by global_drag_dropmiss parent so simulate drop_hit(e). - this.drop_hit(e); - }, - - get_section_index: function(node) { - var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y), - sectionList = Y.all(sectionlistselector), - nodeIndex = sectionList.indexOf(node), - zeroIndex = sectionList.indexOf(Y.one('#section-0')); - - return (nodeIndex - zeroIndex); - }, - - drop_hit : function(e) { - var drag = e.drag; - - // Get references to our nodes and their IDs. - var dragnode = drag.get('node'), - dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode), - loopstart = dragnodeid, - - dropnodeindex = this.get_section_index(dragnode), - loopend = dropnodeindex; - - if (dragnodeid === dropnodeindex) { - Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-course-dragdrop'); - return; - } - - Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-course-dragdrop'); - - if (loopstart > loopend) { - // If we're going up, we need to swap the loop order - // because loops can't go backwards. - loopstart = dropnodeindex; - loopend = dragnodeid; - } - - // Get the list of nodes. - drag.get('dragNode').removeClass(CSS.COURSECONTENT); - var sectionlist = Y.Node.all(this.sectionlistselector); - - // Add a lightbox if it's not there. - var lightbox = M.util.add_lightbox(Y, dragnode); - - // Handle any variables which we must pass via AJAX. - var params = {}, - pageparams = this.get('config').pageparams, - varname; - - for (varname in pageparams) { - if (!pageparams.hasOwnProperty(varname)) { - continue; - } - params[varname] = pageparams[varname]; - } - - // Prepare request parameters - params.sesskey = M.cfg.sesskey; - params.courseId = this.get('courseid'); - params['class'] = 'section'; - params.field = 'move'; - params.id = dragnodeid; - params.value = dropnodeindex; - - // Perform the AJAX request. - var uri = M.cfg.wwwroot + this.get('ajaxurl'); - Y.io(uri, { - method: 'POST', - data: params, - on: { - start : function() { - lightbox.show(); - }, - success: function(tid, response) { - // Update section titles, we can't simply swap them as - // they might have custom title - try { - var responsetext = Y.JSON.parse(response.responseText); - if (responsetext.error) { - new M.core.ajaxException(responsetext); - } - M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend); - } catch (e) {} - - // Update all of the section IDs - first unset them, then set them - // to avoid duplicates in the DOM. - var index; - - // Classic bubble sort algorithm is applied to the section - // nodes between original drag node location and the new one. - var swapped = false; - do { - swapped = false; - for (index = loopstart; index <= loopend; index++) { - if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) > - Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) { - Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) + - " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index))); - // Swap section id. - var sectionid = sectionlist.item(index - 1).get('id'); - sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id')); - sectionlist.item(index).set('id', sectionid); - - // See what format needs to swap. - M.course.format.swap_sections(Y, index - 1, index); - - // Update flag. - swapped = true; - } - } - loopend = loopend - 1; - } while (swapped); - - window.setTimeout(function() { - lightbox.hide(); - }, 250); - }, - - failure: function(tid, response) { - this.ajax_failure(response); - lightbox.hide(); - } - }, - context:this - }); - } - - }, { - NAME : 'course-dragdrop-section', - ATTRS : { - courseid : { - value : null - }, - ajaxurl : { - 'value' : 0 - }, - config : { - 'value' : 0 - } - } - }); - - var DRAGRESOURCE = function() { - DRAGRESOURCE.superclass.constructor.apply(this, arguments); - }; - Y.extend(DRAGRESOURCE, M.core.dragdrop, { - initializer : function(params) { - // Set group for parent class - this.groups = ['resource']; - this.samenodeclass = CSS.ACTIVITY; - this.parentnodeclass = CSS.SECTION; - this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true); - - // Go through all sections - var sectionlistselector = M.course.format.get_section_selector(Y); - if (sectionlistselector) { - sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector; - this.setup_for_section(sectionlistselector); - - // Initialise drag & drop for all resources/activities - var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length+2)+' li.'+CSS.ACTIVITY; - var del = new Y.DD.Delegate({ - container: '.'+CSS.COURSECONTENT, - nodes: nodeselector, - target: true, - handles: ['.' + CSS.EDITINGMOVE], - dragConfig: {groups: this.groups} - }); - del.dd.plug(Y.Plugin.DDProxy, { - // Don't move the node at the end of the drag - moveOnEnd: false, - cloneNode: true - }); - del.dd.plug(Y.Plugin.DDConstrained, { - // Keep it inside the .course-content - constrain: '#'+CSS.PAGECONTENT - }); - del.dd.plug(Y.Plugin.DDWinScroll); - - M.course.coursebase.register_module(this); - M.course.dragres = this; - } - }, - - /** - * Apply dragdrop features to the specified selector or node that refers to section(s) - * - * @param baseselector The CSS selector or node to limit scope to - * @return void - */ - setup_for_section : function(baseselector) { - Y.Node.all(baseselector).each(function(sectionnode) { - var resources = sectionnode.one('.'+CSS.CONTENT+' ul.'+CSS.SECTION); - // See if resources ul exists, if not create one - if (!resources) { - var resources = Y.Node.create(''); - resources.addClass(CSS.SECTION); - sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after'); - } - resources.setAttribute('data-draggroups', this.groups.join(' ')); - // Define empty ul as droptarget, so that item could be moved to empty list - var tar = new Y.DD.Drop({ - node: resources, - groups: this.groups, - padding: '20 0 20 0' - }); - - // Initialise each resource/activity in this section - this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY); - }, this); - }, - /** - * Apply dragdrop features to the specified selector or node that refers to resource(s) - * - * @param baseselector The CSS selector or node to limit scope to - * @return void - */ - setup_for_resource : function(baseselector) { - Y.Node.all(baseselector).each(function(resourcesnode) { - // Replace move icons - var move = resourcesnode.one('a.'+CSS.EDITINGMOVE); - if (move) { - move.replace(this.resourcedraghandle.cloneNode(true)); - } - }, this); - }, - - drag_start : function(e) { - // Get our drag object - var drag = e.target; - drag.get('dragNode').setContent(drag.get('node').get('innerHTML')); - drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline'); - }, - - drag_dropmiss : function(e) { - // Missed the target, but we assume the user intended to drop it - // on the last last ghost node location, e.drag and e.drop should be - // prepared by global_drag_dropmiss parent so simulate drop_hit(e). - this.drop_hit(e); - }, - - drop_hit : function(e) { - var drag = e.drag; - // Get a reference to our drag node - var dragnode = drag.get('node'); - var dropnode = e.drop.get('node'); - - // Add spinner if it not there - var actionarea = dragnode.one(CSS.ACTIONAREA); - var spinner = M.util.add_spinner(Y, actionarea); - - var params = {}; - - // Handle any variables which we must pass back through to - var pageparams = this.get('config').pageparams; - var varname; - for (varname in pageparams) { - params[varname] = pageparams[varname]; - } - - // Prepare request parameters - params.sesskey = M.cfg.sesskey; - params.courseId = this.get('courseid'); - params['class'] = 'resource'; - params.field = 'move'; - params.id = Number(Y.Moodle.core_course.util.cm.getId(dragnode)); - params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true)); - - if (dragnode.next()) { - params.beforeId = Number(Y.Moodle.core_course.util.cm.getId(dragnode.next())); - } - - // Do AJAX request - var uri = M.cfg.wwwroot + this.get('ajaxurl'); - - Y.io(uri, { - method: 'POST', - data: params, - on: { - start : function(tid) { - this.lock_drag_handle(drag, CSS.EDITINGMOVE); - spinner.show(); - }, - success: function(tid, response) { - var responsetext = Y.JSON.parse(response.responseText); - var params = {element: dragnode, visible: responsetext.visible}; - M.course.coursebase.invoke_function('set_visibility_resource_ui', params); - this.unlock_drag_handle(drag, CSS.EDITINGMOVE); - window.setTimeout(function(e) { - spinner.hide(); - }, 250); - }, - failure: function(tid, response) { - this.ajax_failure(response); - this.unlock_drag_handle(drag, CSS.SECTIONHANDLE); - spinner.hide(); - // TODO: revert nodes location - } - }, - context:this - }); - } - }, { - NAME : 'course-dragdrop-resource', - ATTRS : { - courseid : { - value : null - }, - ajaxurl : { - 'value' : 0 - }, - config : { - 'value' : 0 - } - } - }); - - M.course = M.course || {}; - M.course.init_resource_dragdrop = function(params) { - new DRAGRESOURCE(params); - } - M.course.init_section_dragdrop = function(params) { - new DRAGSECTION(params); - } -}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification', 'moodle-course-coursebase', 'moodle-course-util']}); diff --git a/course/yui/src/dragdrop/build.json b/course/yui/src/dragdrop/build.json new file mode 100644 index 00000000000..fb8e3cef30e --- /dev/null +++ b/course/yui/src/dragdrop/build.json @@ -0,0 +1,12 @@ +{ + "name": "moodle-course-dragdrop", + "builds": { + "moodle-course-dragdrop": { + "jsfiles": [ + "dragdrop.js", + "section.js", + "resource.js" + ] + } + } +} diff --git a/course/yui/src/dragdrop/js/dragdrop.js b/course/yui/src/dragdrop/js/dragdrop.js new file mode 100644 index 00000000000..00b30d4a493 --- /dev/null +++ b/course/yui/src/dragdrop/js/dragdrop.js @@ -0,0 +1,29 @@ +/** + * Drag and Drop for course sections and course modules. + * + * @module moodle-course-dragdrop + */ + +var CSS = { + ACTIONAREA: '.actions', + ACTIVITY: 'activity', + ACTIVITYINSTANCE: 'activityinstance', + CONTENT: 'content', + COURSECONTENT: 'course-content', + EDITINGMOVE: 'editing_move', + ICONCLASS: 'iconsmall', + JUMPMENU: 'jumpmenu', + LEFT: 'left', + LIGHTBOX: 'lightbox', + MOVEDOWN: 'movedown', + MOVEUP: 'moveup', + PAGECONTENT: 'page-content', + RIGHT: 'right', + SECTION: 'section', + SECTIONADDMENUS: 'section_add_menus', + SECTIONHANDLE: 'section-handle', + SUMMARY: 'summary', + SECTIONDRAGGABLE: 'sectiondraggable' +}; + +M.course = M.course || {}; diff --git a/course/yui/src/dragdrop/js/resource.js b/course/yui/src/dragdrop/js/resource.js new file mode 100644 index 00000000000..6523e8bbf24 --- /dev/null +++ b/course/yui/src/dragdrop/js/resource.js @@ -0,0 +1,187 @@ +/** + * Resource drag and drop. + * + * @class M.course.dragdrop.resource + * @constructor + * @extends M.core.dragdrop + */ +var DRAGRESOURCE = function() { + DRAGRESOURCE.superclass.constructor.apply(this, arguments); +}; +Y.extend(DRAGRESOURCE, M.core.dragdrop, { + initializer: function() { + // Set group for parent class + this.groups = ['resource']; + this.samenodeclass = CSS.ACTIVITY; + this.parentnodeclass = CSS.SECTION; + this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true); + + // Go through all sections + var sectionlistselector = M.course.format.get_section_selector(Y); + if (sectionlistselector) { + sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector; + this.setup_for_section(sectionlistselector); + + // Initialise drag & drop for all resources/activities + var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY; + var del = new Y.DD.Delegate({ + container: '.' + CSS.COURSECONTENT, + nodes: nodeselector, + target: true, + handles: ['.' + CSS.EDITINGMOVE], + dragConfig: {groups: this.groups} + }); + del.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false, + cloneNode: true + }); + del.dd.plug(Y.Plugin.DDConstrained, { + // Keep it inside the .course-content + constrain: '#' + CSS.PAGECONTENT + }); + del.dd.plug(Y.Plugin.DDWinScroll); + + M.course.coursebase.register_module(this); + M.course.dragres = this; + } + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to section(s) + * + * @method setup_for_section + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_section: function(baseselector) { + Y.Node.all(baseselector).each(function(sectionnode) { + var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION); + // See if resources ul exists, if not create one + if (!resources) { + resources = Y.Node.create(''); + resources.addClass(CSS.SECTION); + sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after'); + } + resources.setAttribute('data-draggroups', this.groups.join(' ')); + // Define empty ul as droptarget, so that item could be moved to empty list + new Y.DD.Drop({ + node: resources, + groups: this.groups, + padding: '20 0 20 0' + }); + + // Initialise each resource/activity in this section + this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY); + }, this); + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to resource(s) + * + * @method setup_for_resource + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_resource: function(baseselector) { + Y.Node.all(baseselector).each(function(resourcesnode) { + // Replace move icons + var move = resourcesnode.one('a.' + CSS.EDITINGMOVE); + if (move) { + move.replace(this.resourcedraghandle.cloneNode(true)); + } + }, this); + }, + + drag_start: function(e) { + // Get our drag object + var drag = e.target; + drag.get('dragNode').setContent(drag.get('node').get('innerHTML')); + drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline'); + }, + + drag_dropmiss: function(e) { + // Missed the target, but we assume the user intended to drop it + // on the last last ghost node location, e.drag and e.drop should be + // prepared by global_drag_dropmiss parent so simulate drop_hit(e). + this.drop_hit(e); + }, + + drop_hit: function(e) { + var drag = e.drag; + // Get a reference to our drag node + var dragnode = drag.get('node'); + var dropnode = e.drop.get('node'); + + // Add spinner if it not there + var actionarea = dragnode.one(CSS.ACTIONAREA); + var spinner = M.util.add_spinner(Y, actionarea); + + var params = {}; + + // Handle any variables which we must pass back through to + var pageparams = this.get('config').pageparams; + var varname; + for (varname in pageparams) { + params[varname] = pageparams[varname]; + } + + // Prepare request parameters + params.sesskey = M.cfg.sesskey; + params.courseId = this.get('courseid'); + params['class'] = 'resource'; + params.field = 'move'; + params.id = Number(Y.Moodle.core_course.util.cm.getId(dragnode)); + params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true)); + + if (dragnode.next()) { + params.beforeId = Number(Y.Moodle.core_course.util.cm.getId(dragnode.next())); + } + + // Do AJAX request + var uri = M.cfg.wwwroot + this.get('ajaxurl'); + + Y.io(uri, { + method: 'POST', + data: params, + on: { + start: function() { + this.lock_drag_handle(drag, CSS.EDITINGMOVE); + spinner.show(); + }, + success: function(tid, response) { + var responsetext = Y.JSON.parse(response.responseText); + var params = {element: dragnode, visible: responsetext.visible}; + M.course.coursebase.invoke_function('set_visibility_resource_ui', params); + this.unlock_drag_handle(drag, CSS.EDITINGMOVE); + window.setTimeout(function() { + spinner.hide(); + }, 250); + }, + failure: function(tid, response) { + this.ajax_failure(response); + this.unlock_drag_handle(drag, CSS.SECTIONHANDLE); + spinner.hide(); + // TODO: revert nodes location + } + }, + context:this + }); + } +}, { + NAME: 'course-dragdrop-resource', + ATTRS: { + courseid: { + value: null + }, + ajaxurl: { + value: 0 + }, + config: { + value: 0 + } + } +}); + +M.course = M.course || {}; +M.course.init_resource_dragdrop = function(params) { + new DRAGRESOURCE(params); +}; diff --git a/course/yui/src/dragdrop/js/section.js b/course/yui/src/dragdrop/js/section.js new file mode 100644 index 00000000000..d1380306f1d --- /dev/null +++ b/course/yui/src/dragdrop/js/section.js @@ -0,0 +1,258 @@ +/** + * Section drag and drop. + * + * @class M.course.dragdrop.section + * @constructor + * @extends M.core.dragdrop + */ +var DRAGSECTION = function() { + DRAGSECTION.superclass.constructor.apply(this, arguments); +}; +Y.extend(DRAGSECTION, M.core.dragdrop, { + sectionlistselector: null, + + initializer: function() { + // Set group for parent class + this.groups = [ CSS.SECTIONDRAGGABLE ]; + this.samenodeclass = M.course.format.get_sectionwrapperclass(); + this.parentnodeclass = M.course.format.get_containerclass(); + + // Check if we are in single section mode + if (Y.Node.one('.' + CSS.JUMPMENU)) { + return false; + } + // Initialise sections dragging + this.sectionlistselector = M.course.format.get_section_wrapper(Y); + if (this.sectionlistselector) { + this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector; + + this.setup_for_section(this.sectionlistselector); + + // Make each li element in the lists of sections draggable + var del = new Y.DD.Delegate({ + container: '.' + CSS.COURSECONTENT, + nodes: '.' + CSS.SECTIONDRAGGABLE, + target: true, + handles: ['.' + CSS.LEFT], + dragConfig: {groups: this.groups} + }); + del.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false + }); + del.dd.plug(Y.Plugin.DDConstrained, { + // Keep it inside the .course-content + constrain: '#' + CSS.PAGECONTENT, + stickY: true + }); + del.dd.plug(Y.Plugin.DDWinScroll); + } + }, + + /** + * Apply dragdrop features to the specified selector or node that refers to section(s) + * + * @method setup_for_section + * @param {String} baseselector The CSS selector or node to limit scope to + */ + setup_for_section: function(baseselector) { + Y.Node.all(baseselector).each(function(sectionnode) { + // Determine the section ID + var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode); + + // We skip the top section as it is not draggable + if (sectionid > 0) { + // Remove move icons + var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN); + var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP); + + // Add dragger icon + var title = M.util.get_string('movesection', 'moodle', sectionid); + var cssleft = sectionnode.one('.' + CSS.LEFT); + + if ((movedown || moveup) && cssleft) { + cssleft.setStyle('cursor', 'move'); + cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true)); + + if (moveup) { + moveup.remove(); + } + if (movedown) { + movedown.remove(); + } + + // This section can be moved - add the class to indicate this to Y.DD. + sectionnode.addClass(CSS.SECTIONDRAGGABLE); + } + } + }, this); + }, + + /* + * Drag-dropping related functions + */ + drag_start: function(e) { + // Get our drag object + var drag = e.target; + // Creat a dummy structure of the outer elemnents for clean styles application + var containernode = Y.Node.create('<' + M.course.format.get_containernode() + '>'); + containernode.addClass(M.course.format.get_containerclass()); + var sectionnode = Y.Node.create('<' + M.course.format.get_sectionwrappernode() + '>'); + sectionnode.addClass( M.course.format.get_sectionwrapperclass()); + sectionnode.setStyle('margin', 0); + sectionnode.setContent(drag.get('node').get('innerHTML')); + containernode.appendChild(sectionnode); + drag.get('dragNode').setContent(containernode); + drag.get('dragNode').addClass(CSS.COURSECONTENT); + }, + + drag_dropmiss: function(e) { + // Missed the target, but we assume the user intended to drop it + // on the last last ghost node location, e.drag and e.drop should be + // prepared by global_drag_dropmiss parent so simulate drop_hit(e). + this.drop_hit(e); + }, + + get_section_index: function(node) { + var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y), + sectionList = Y.all(sectionlistselector), + nodeIndex = sectionList.indexOf(node), + zeroIndex = sectionList.indexOf(Y.one('#section-0')); + + return (nodeIndex - zeroIndex); + }, + + drop_hit: function(e) { + var drag = e.drag; + + // Get references to our nodes and their IDs. + var dragnode = drag.get('node'), + dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode), + loopstart = dragnodeid, + + dropnodeindex = this.get_section_index(dragnode), + loopend = dropnodeindex; + + if (dragnodeid === dropnodeindex) { + Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-course-dragdrop'); + return; + } + + Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-course-dragdrop'); + + if (loopstart > loopend) { + // If we're going up, we need to swap the loop order + // because loops can't go backwards. + loopstart = dropnodeindex; + loopend = dragnodeid; + } + + // Get the list of nodes. + drag.get('dragNode').removeClass(CSS.COURSECONTENT); + var sectionlist = Y.Node.all(this.sectionlistselector); + + // Add a lightbox if it's not there. + var lightbox = M.util.add_lightbox(Y, dragnode); + + // Handle any variables which we must pass via AJAX. + var params = {}, + pageparams = this.get('config').pageparams, + varname; + + for (varname in pageparams) { + if (!pageparams.hasOwnProperty(varname)) { + continue; + } + params[varname] = pageparams[varname]; + } + + // Prepare request parameters + params.sesskey = M.cfg.sesskey; + params.courseId = this.get('courseid'); + params['class'] = 'section'; + params.field = 'move'; + params.id = dragnodeid; + params.value = dropnodeindex; + + // Perform the AJAX request. + var uri = M.cfg.wwwroot + this.get('ajaxurl'); + Y.io(uri, { + method: 'POST', + data: params, + on: { + start: function() { + lightbox.show(); + }, + success: function(tid, response) { + // Update section titles, we can't simply swap them as + // they might have custom title + try { + var responsetext = Y.JSON.parse(response.responseText); + if (responsetext.error) { + new M.core.ajaxException(responsetext); + } + M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend); + } catch (e) {} + + // Update all of the section IDs - first unset them, then set them + // to avoid duplicates in the DOM. + var index; + + // Classic bubble sort algorithm is applied to the section + // nodes between original drag node location and the new one. + var swapped = false; + do { + swapped = false; + for (index = loopstart; index <= loopend; index++) { + if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) > + Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) { + Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) + + " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index))); + // Swap section id. + var sectionid = sectionlist.item(index - 1).get('id'); + sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id')); + sectionlist.item(index).set('id', sectionid); + + // See what format needs to swap. + M.course.format.swap_sections(Y, index - 1, index); + + // Update flag. + swapped = true; + } + } + loopend = loopend - 1; + } while (swapped); + + window.setTimeout(function() { + lightbox.hide(); + }, 250); + }, + + failure: function(tid, response) { + this.ajax_failure(response); + lightbox.hide(); + } + }, + context:this + }); + } + +}, { + NAME: 'course-dragdrop-section', + ATTRS: { + courseid: { + value: null + }, + ajaxurl: { + value: 0 + }, + config: { + value: 0 + } + } +}); + +M.course = M.course || {}; +M.course.init_section_dragdrop = function(params) { + new DRAGSECTION(params); +}; diff --git a/course/yui/src/dragdrop/meta/dragdrop.json b/course/yui/src/dragdrop/meta/dragdrop.json new file mode 100644 index 00000000000..5757d2822e7 --- /dev/null +++ b/course/yui/src/dragdrop/meta/dragdrop.json @@ -0,0 +1,16 @@ +{ + "moodle-course-dragdrop": { + "requires": [ + "base", + "node", + "io", + "dom", + "dd", + "dd-scroll", + "moodle-core-dragdrop", + "moodle-core-notification", + "moodle-course-coursebase", + "moodle-course-util" + ] + } +}