From ee7a52ae91b71fcc05224ee5c5558e8ac7e5155e Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Fri, 7 Jun 2013 13:37:21 +1200 Subject: [PATCH] MDL-39842 js: fixed block drag and drop --- .../moodle-core-blocks-debug.js | 687 +++++++++++++++++- .../moodle-core-blocks-min.js | 3 +- .../moodle-core-blocks/moodle-core-blocks.js | 683 ++++++++++++++++- lib/yui/dragdrop/dragdrop.js | 2 +- lib/yui/src/blocks/build.json | 4 +- lib/yui/src/blocks/js/blockregion.js | 216 ++++++ lib/yui/src/blocks/js/blocks.js | 79 +- lib/yui/src/blocks/js/manager.js | 393 ++++++++++ 8 files changed, 2056 insertions(+), 11 deletions(-) create mode 100644 lib/yui/src/blocks/js/blockregion.js create mode 100644 lib/yui/src/blocks/js/manager.js diff --git a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js index 8430a9b1003..ea694d02bb2 100644 --- a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js +++ b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js @@ -1,5 +1,11 @@ YUI.add('moodle-core-blocks', function (Y, NAME) { +/** + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + var AJAXURL = '/lib/ajax/blocks.php', CSS = { BLOCK : 'block', @@ -15,6 +21,16 @@ CSS = { REGIONMAIN : 'region-main' }; +/** + * Legacy drag and drop manager. + * This drag and drop manager is specifically designed for themes using side-pre and side-post + * that do not make use of the block output methods introduced by MDL-39824. + * + * @namespace M.core.blockdraganddrop + * @class LegacyManager + * @constructor + * @extends M.core.dragdrop + */ var DRAGBLOCK = function() { DRAGBLOCK.superclass.constructor.apply(this, arguments); }; @@ -301,11 +317,676 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, { } }); -M.core_blocks = M.core_blocks || {}; -M.core_blocks.init_dragdrop = function(params) { - new DRAGBLOCK(params); +/** + * Core namespace. + * @static + * @class core + */ +M.core = M.core || {}; + +/** + * Block drag and drop static class. + * @namespace M.core + * @class blockdraganddrop + * @static + */ +M.core.blockdraganddrop = M.core.blockdraganddrop || {}; + +/** + * True if the page is using the new blocks methods. + * @private + * @static + * @property _isusingnewblocksmethod + * @type Boolean + * @default null + */ +M.core.blockdraganddrop._isusingnewblocksmethod = null; + +/** + * Returns true if the page is using the new blocks methods. + * @static + * @method is_using_blocks_render_method + * @return Boolean + */ +M.core.blockdraganddrop.is_using_blocks_render_method = function() { + if (this._isusingnewblocksmethod === null) { + var goodregions = Y.all('.block-region[data-blockregion]').size(); + var allregions = Y.all('.block-region').size(); + this._isusingnewblocksmethod = (allregions === goodregions); + } + return this._isusingnewblocksmethod; }; +/** + * Initialises a drag and drop manager. + * This should only ever be called once for a page. + * @static + * @method init + * @param {Object} params + * @return Manager + */ +M.core.blockdraganddrop.init = function(params) { + if (this.is_using_blocks_render_method()) { + new MANAGER(params); + } else { + new DRAGBLOCK(params); + } +}; + +/** + * Legacy code to keep things working. + */ +M.core_blocks = M.core_blocks || {}; +M.core_blocks.init_dragdrop = function(params) { + M.core.blockdraganddrop.init(params); +};/** + * This file contains the drag and drop manager class. + * + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + +/** + * Constructs a new Block drag and drop manager. + * + * @namespace M.core.blockdraganddrop + * @class Manager + * @constructor + * @extends M.core.dragdrop + */ +var MANAGER = function() { + MANAGER.superclass.constructor.apply(this, arguments); +}; +MANAGER.prototype = { + + /** + * The skip block link from above the block being dragged while a drag is in progress. + * Required by the M.core.dragdrop from whom this class extends. + * @private + * @property skipnodetop + * @type Node + * @default null + */ + skipnodetop : null, + + /** + * The skip block link from below the block being dragged while a drag is in progress. + * Required by the M.core.dragdrop from whom this class extends. + * @private + * @property skipnodebottom + * @type Node + * @default null + */ + skipnodebottom : null, + + /** + * An associative object of regions and the + * @property regionobjects + * @type {Object} Primitive object mocking an associative array. + * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being + * an instance of the BLOCKREGION class. + */ + regionobjects : {}, + + /** + * Called during the initialisation process of the object. + * @method initializer + */ + initializer : function() { + Y.log('Initialising drag and drop for blocks.', 'info'); + var regionnames = this.get('regions'), + i = 0, + region, + regionname, + droptarget, + dragdelegation; + + // Evil required by M.core.dragdrop. + this.groups = ['block']; + this.samenodeclass = CSS.BLOCK; + this.parentnodeclass = CSS.BLOCKREGION; + + // Add relevant classes and ID to 'content' block region on My Home page. + var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT); + if (myhomecontent.size() > 0) { + var contentregion = myhomecontent.item(0); + contentregion.addClass(CSS.BLOCKREGION); + contentregion.set('id', CSS.REGIONCONTENT); + contentregion.one('div').addClass(CSS.REGIONCONTENT); + } + + for (i in regionnames) { + regionname = regionnames[i]; + region = new BLOCKREGION({ + manager : this, + region : regionname, + node : Y.one('#block-region-'+regionname) + }); + this.regionobjects[regionname] = region; + + // Setting blockregion as droptarget (the case when it is empty) + // The region-post (the right one) + // is very narrow, so add extra padding on the left to drop block on it. + droptarget = new Y.DD.Drop({ + node: region.get_droptarget(), + groups: this.groups, + padding: '40 240 40 240' + }); + + // Make each div element in the list of blocks draggable + dragdelegation = new Y.DD.Delegate({ + container: region.get_droptarget(), + nodes: '.'+CSS.BLOCK, + target: true, + handles: ['.'+CSS.HEADER], + invalid: '.block-hider-hide, .block-hider-show, .moveto', + dragConfig: {groups: this.groups} + }); + dragdelegation.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false + }); + dragdelegation.dd.plug(Y.Plugin.DDWinScroll); + // On the mouse down event we will enable all block regions so that they can be dragged to. + // This is VERY important as without it dnd won't work for empty block regions. + dragdelegation.on('drag:mouseDown', this.enable_all_regions, this); + + region.remove_block_move_icons(); + } + Y.log('Initialisation of drag and drop for blocks complete.', 'info'); + }, + + /** + * Returns the ID of the block the given node represents. + * @method get_block_id + * @param {Node} node + * @returns {int} The blocks ID in the database. + */ + get_block_id : function(node) { + return Number(node.get('id').replace(/inst/i, '')); + }, + + /** + * Returns the block region that the node is part of or belonging to. + * @method get_block_region + * @param {Y.Node} node + * @returns {string} The region name. + */ + get_block_region : function(node) { + if (!node.test('[data-blockregion]')) { + node = node.ancestor('[data-blockregion]'); + } + return node.getData('blockregion'); + }, + + /** + * Returns the BLOCKREGION instance that represents the block region the given node is part of. + * @method get_region_object + * @param {Y.Node} node + * @returns {BLOCKREGION} + */ + get_region_object : function(node) { + return this.regionobjects[this.get_block_region(node)]; + }, + + /** + * Enables all fo the regions so that they are all visible while dragging is occuring. + * @method enable_all_regions + * @returns {undefined} + */ + enable_all_regions : function() { + var i = 0; + for (i in this.regionobjects) { + this.regionobjects[i].enable(); + } + }, + + /** + * Disables enabled regions if they contain no blocks. + * @method disable_regions_if_required + * @returns {undefined} + */ + disable_regions_if_required : function() { + var i = 0; + for (i in this.regionobjects) { + this.regionobjects[i].disable_if_required(); + } + }, + + /** + * Called by M.core.dragdrop.global_drag_start when dragging starts. + * @method drag_start + * @param {Event} e + * @returns {undefined} + */ + drag_start : function(e) { + // Get our drag object + var drag = e.target; + + // Store the parent node of original drag node (block) + // we will need it later for show/hide empty regions + + // Determine skipnodes and store them + if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) { + this.skipnodetop = drag.get('node').previous(); + } + if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) { + this.skipnodebottom = drag.get('node').next(); + } + }, + + /** + * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target. + * @method drop_over + * @param {Event} e + * @returns {undefined} + */ + drop_over : function(e) { + // Get a reference to our drag and drop nodes + var drag = e.drag.get('node'); + var drop = e.drop.get('node'); + + // We need to fix the case when parent drop over event has determined + // 'goingup' and appended the drag node after admin-block. + if (drop.hasClass(CSS.REGIONCONTENT) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) { + drop.prepend(drag); + } + }, + + /** + * Called by M.core.dragdrop.global_drop_end when a drop has been completed. + * @method drop_end + * @returns {undefined} + */ + drop_end : function() { + // Clear variables. + this.skipnodetop = null; + this.skipnodebottom = null; + this.disable_regions_if_required(); + }, + + /** + * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by a drop target. + * @method drag_dropmiss + * @param {Event} e + * @returns {undefined} + */ + drag_dropmiss : function(e) { + // Missed the target, but we assume the user intended to drop it + // on the 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); + }, + + /** + * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target. + * @method drop_hit + * @param {Event} e + * @returns {undefined} + */ + drop_hit : function(e) { + // Get a reference to our drag node + var dragnode = e.drag.get('node'); + var dropnode = e.drop.get('node'); + + // Amend existing skipnodes + if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) { + // the one that belongs to block below move below + dragnode.insert(dragnode.previous(), 'after'); + } + // Move original skipnodes + if (this.skipnodetop) { + dragnode.insert(this.skipnodetop, 'before'); + } + if (this.skipnodebottom) { + dragnode.insert(this.skipnodebottom, 'after'); + } + + // Add lightbox if it not there + var lightbox = M.util.add_lightbox(Y, dragnode); + + // Prepare request parameters + var params = { + sesskey : M.cfg.sesskey, + courseid : this.get('courseid'), + pagelayout : this.get('pagelayout'), + pagetype : this.get('pagetype'), + subpage : this.get('subpage'), + contextid : this.get('contextid'), + action : 'move', + bui_moveid : this.get_block_id(dragnode), + bui_newregion : this.get_block_region(dropnode) + }; + + if (this.get('cmid')) { + params.cmid = this.get('cmid'); + } + + if (dragnode.next('.'+CSS.BLOCK) && !dragnode.next('.'+CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) { + params.bui_beforeid = this.get_block_id(dragnode.next('.'+CSS.BLOCK)); + } + + // Do AJAX request + Y.io(M.cfg.wwwroot+AJAXURL, { + method: 'POST', + data: params, + on: { + start : function() { + lightbox.show(); + }, + success: function(tid, response) { + window.setTimeout(function() { + lightbox.hide(); + }, 250); + try { + var responsetext = Y.JSON.parse(response.responseText); + if (responsetext.error) { + new M.core.ajaxException(responsetext); + } + } catch (e) {} + }, + failure: function(tid, response) { + this.ajax_failure(response); + lightbox.hide(); + }, + complete : function() { + this.disable_regions_if_required(); + } + }, + context:this + }); + } +}; +Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, { + NAME : 'core-blocks-dragdrop-manager', + ATTRS : { + /** + * The Course ID if there is one. + * @attribute courseid + * @type int|null + * @default null + */ + courseid : { + value : null + }, + + /** + * The Course Module ID if there is one. + * @attribute cmid + * @type int|null + * @default null + */ + cmid : { + value : null + }, + + /** + * The Context ID. + * @attribute contextid + * @type int|null + * @default null + */ + contextid : { + value : null + }, + + /** + * The current page layout. + * @attribute pagelayout + * @type string|null + * @default null + */ + pagelayout : { + value : null + }, + + /** + * The page type string, should be used as the id for the body tag in the theme. + * @attribute pagetype + * @type string|null + * @default null + */ + pagetype : { + value : null + }, + + /** + * The subpage identifier, if any. + * @attribute subpage + * @type string|null + * @default null + */ + subpage : { + value : null + }, + + /** + * An array of block regions that are present on the page. + * @attribute regions + * @type array|null + * @default Array[] + */ + regions : { + value : [] + } + } +});/** + * This file contains the Block Region class used by the drag and drop manager. + * + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + +/** + * Constructs a new block region object. + * + * @namespace M.core.blockdraganddrop + * @class BlockRegion + * @constructor + * @extends Y.Base + */ +var BLOCKREGION = function() { + BLOCKREGION.superclass.constructor.apply(this, arguments); +}; +BLOCKREGION.prototype = { + /** + * Called during the initialisation process of the object. + * @method initializer + */ + initializer : function() { + var node = this.get('node'); + Y.log('Block region `'+this.get('region')+'` initialising', 'info'); + if (!node) { + Y.log('block region known about but no HTML structure found for it. Guessing structure.', 'warn'); + this.create_and_add_node(); + } + var body = Y.one('body'), + hasblocks = node.all('.'+CSS.BLOCK).size() > 0, + hasregionclass = this.get_has_region_class(); + this.set('hasblocks', hasblocks); + if (!body.hasClass(hasregionclass)) { + body.addClass(hasregionclass); + } + body.addClass((hasblocks) ? this.get_used_region_class() : this.get_empty_region_class()); + body.removeClass((hasblocks) ? this.get_empty_region_class() : this.get_used_region_class()); + }, + /** + * Creates a generic block region node and adds it to the DOM at the best guess location. + * Any calling of this method is an unfortunate circumstance. + * @method create_and_add_node + */ + create_and_add_node : function() { + var c = Y.Node.create, + region = this.get('region'), + node = c('
') + .addClass(CSS.BLOCKREGION) + .setData('blockregion', region), + regions = this.get('manager').get('regions'), + i, + haspre = false, + haspost = false, + added = false, + pre, + post; + + for (i in regions) { + if (regions[i].match(/(pre|left)/)) { + haspre = regions[i]; + } else if (regions[i].match(/(post|right)/)) { + haspost = regions[i]; + } + } + + if (haspre !== false && haspost !== false) { + if (region === haspre) { + post = Y.one('#block-region-'+haspost); + if (post) { + post.insert(node, 'before'); + added = true; + } + } else { + pre = Y.one('#block-region-'+haspre); + if (pre) { + pre.insert(node, 'after'); + added = true; + } + } + } + if (added === false) { + Y.one('body').append(node); + } + this.set('node', node); + }, + + /** + * Removes the move icons and changes the cursor to a move icon when over the header. + * @method remove_block_move_icons + */ + remove_block_move_icons : function() { + this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){ + moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move'); + moveicon.remove(); + }); + }, + + /** + * Returns the class name on the body that signifies the document knows about this region. + * @method get_has_region_class + * @return String + */ + get_has_region_class : function() { + return 'has-region-'+this.get('region'); + }, + + /** + * Returns the class name to use on the body if the region contains no blocks. + * @method get_empty_region_class + * @return String + */ + get_empty_region_class : function() { + return 'empty-region-'+this.get('region'); + }, + + /** + * Returns the class name to use on the body if the region contains blocks. + * @method get_used_region_class + * @return String + */ + get_used_region_class : function() { + return 'used-region-'+this.get('region'); + }, + + /** + * Returns the node to use as the drop target for this region. + * @method get_droptarget + * @return Node + */ + get_droptarget : function() { + var node = this.get('node'); + if (node.test('[data-droptarget="1"]')) { + return node; + } + return node.one('[data-droptarget="1"]'); + }, + + /** + * Enables the block region so that we can be sure the user can see it. + * This is done even if it is empty. + * @method enable + */ + enable : function() { + Y.one('body').addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class()); + }, + + /** + * Disables the region if it contains no blocks, essentially hiding it from the user. + * @method disable_if_required + */ + disable_if_required : function() { + if (this.get('node').all('.'+CSS.BLOCK).size() === 0) { + Y.one('body').addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class()); + } + } +}; +Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, { + NAME : 'core-blocks-dragdrop-blockregion', + ATTRS : { + + /** + * The drag and drop manager that created this block region instance. + * @attribute manager + * @type M.core.blockdraganddrop.Manager + * @writeOnce + */ + manager : { + // Can only be set during initialisation and must be set then. + writeOnce : 'initOnly', + validator : function (value) { + return Y.Lang.isObject(value) && value instanceof MANAGER; + } + }, + + /** + * The name of the block region this object represents. + * @attribute region + * @type String + * @writeOnce + */ + region : { + // Can only be set during initialisation and must be set then. + writeOnce : 'initOnly', + validator : function (value) { + return Y.Lang.isString(value); + } + }, + + /** + * The node the block region HTML starts at.s + * @attribute region + * @type Y.Node + */ + node : { + validator : function (value) { + return Y.Lang.isObject(value) || Y.Lang.isNull(value); + } + }, + + /** + * True if the block region currently contains blocks. + * @attribute hasblocks + * @type Boolean + * @default false + */ + hasblocks : { + value : false, + validator : function (value) { + return Y.Lang.isBoolean(value); + } + } + } +}); }, '@VERSION@', { "requires": [ diff --git a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js index 24f8901728e..31b92ae98da 100644 --- a/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js +++ b/lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js @@ -1 +1,2 @@ -YUI.add("moodle-core-blocks",function(e,t){var n="/lib/ajax/blocks.php",r={BLOCK:"block",BLOCKREGION:"block-region",BLOCKADMINBLOCK:"block_adminblock",EDITINGMOVE:"editing_move",HEADER:"header",LIGHTBOX:"lightbox",REGIONCONTENT:"region-content",SKIPBLOCK:"skip-block",SKIPBLOCKTO:"skip-block-to",MYINDEX:"page-my-index",REGIONMAIN:"region-main"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,M.core.dragdrop,{skipnodetop:null,skipnodebottom:null,dragsourceregion:null,initializer:function(){this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.REGIONCONTENT;var t=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(t.size()>0){var n=t.item(0);n.addClass(r.BLOCKREGION),n.set("id",r.REGIONCONTENT),n.one("div").addClass(r.REGIONCONTENT)}var i=e.Node.all("div."+r.BLOCKREGION);if(i.size()===0)return!1;if(i.size()!=this.get("regions").length){var s=e.Node.create("
").addClass(r.BLOCKREGION),o=e.Node.create("
").addClass(r.REGIONCONTENT);s.appendChild(o);var u=i.filter("#region-pre"),a=i.filter("#region-post");u.size()===0&&a.size()===1?(s.setAttrs({id:"region-pre"}),a.item(0).insert(s,"before"),i.unshift(s)):a.size()===0&&u.size()===1&&(s.setAttrs({id:"region-post"}),u.item(0).insert(s,"after"),i.push(s))}i.each(function(t){var n=new e.DD.Drop({node:t.one("div."+r.REGIONCONTENT),groups:this.groups,padding:"40 240 40 240"}),i=new e.DD.Delegate({container:t,nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}});i.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),i.dd.plug(e.Plugin.DDWinScroll);var s=t.all("."+r.BLOCK);s.each(function(e){var t=e.one("a."+r.EDITINGMOVE);t&&(t.remove(),e.one("."+r.HEADER).setStyle("cursor","move"))},this)},this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(t){var n=t.ancestor("div."+r.BLOCKREGION).get("id").replace(/region-/i,"");return e.Array.indexOf(this.get("regions"),n)===-1?(right_to_left()&&(n==="post"?n="pre":n==="pre"&&(n="post")),"side-"+n):n},get_region_id:function(e){return e.get("id").replace(/region-/i,"")},drag_start:function(e){var t=e.target;this.dragsourceregion=t.get("node").ancestor("div."+r.BLOCKREGION),t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(t){var n=t.drag.get("node"),i=t.drop.get("node");i.hasClass(this.parentnodeclass)&&i.one("."+r.BLOCKADMINBLOCK)&&i.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&i.prepend(n);if(this.dragsourceregion.contains(i))return!1;var s=e.one("body"),o=this.get_region_id(this.dragsourceregion);s.hasClass("side-"+o+"-only")&&s.removeClass("side-"+o+"-only"),o=this.get_region_id(i.ancestor("div."+r.BLOCKREGION)),this.dragsourceregion.all("."+r.BLOCK).size()==0&&this.dragsourceregion.get("id").match(/(region-pre|region-post)/i)&&(s.hasClass("side-"+o+"-only")||s.addClass("side-"+o+"-only"))},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.dragsourceregion=null},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag,s=i.get("node"),o=t.drop.get("node");s.previous()&&s.previous().hasClass(r.SKIPBLOCK)&&s.insert(s.previous(),"after"),this.skipnodetop&&s.insert(this.skipnodetop,"before"),this.skipnodebottom&&s.insert(this.skipnodebottom,"after");var u=M.util.add_lightbox(e,s),a={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(s),bui_newregion:this.get_block_region(o)};this.get("cmid")&&(a.cmid=this.get("cmid")),s.next("."+this.samenodeclass)&&!s.next("."+this.samenodeclass).hasClass(r.BLOCKADMINBLOCK)&&(a.bui_beforeid=this.get_block_id(s.next("."+this.samenodeclass))),e.io(M.cfg.wwwroot+n,{method:"POST",data:a,on:{start:function(){u.show()},success:function(t,n){window.setTimeout(function(){u.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),u.hide()}},context:this})}},{NAME:"core-blocks-dragdrop",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:null}}}),M.core_blocks=M.core_blocks||{},M.core_blocks.init_dragdrop=function(e){new i(e)}},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification"]}); +YUI.add("moodle-core-blocks",function(e,t){var n="/lib/ajax/blocks.php",r={BLOCK:"block",BLOCKREGION:"block-region",BLOCKADMINBLOCK:"block_adminblock",EDITINGMOVE:"editing_move",HEADER:"header",LIGHTBOX:"lightbox",REGIONCONTENT:"region-content",SKIPBLOCK:"skip-block",SKIPBLOCKTO:"skip-block-to",MYINDEX:"page-my-index",REGIONMAIN:"region-main"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,M.core.dragdrop,{skipnodetop:null,skipnodebottom:null,dragsourceregion:null,initializer:function(){this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.REGIONCONTENT;var t=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(t.size()>0){var n=t.item(0);n.addClass(r.BLOCKREGION),n.set("id",r.REGIONCONTENT),n.one("div").addClass(r.REGIONCONTENT)}var i=e.Node.all("div."+r.BLOCKREGION);if(i.size()===0)return!1;if(i.size()!=this.get("regions").length){var s=e.Node.create("
").addClass(r.BLOCKREGION),o=e.Node.create("
").addClass(r.REGIONCONTENT);s.appendChild(o);var u=i.filter("#region-pre"),a=i.filter("#region-post");u.size()===0&&a.size()===1?(s.setAttrs({id:"region-pre"}),a.item(0).insert(s,"before"),i.unshift(s)):a.size()===0&&u.size()===1&&(s.setAttrs({id:"region-post"}),u.item(0).insert(s,"after"),i.push(s))}i.each(function(t){var n=new e.DD.Drop({node:t.one("div."+r.REGIONCONTENT),groups:this.groups,padding:"40 240 40 240"}),i=new e.DD.Delegate({container:t,nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}});i.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),i.dd.plug(e.Plugin.DDWinScroll);var s=t.all("."+r.BLOCK);s.each(function(e){var t=e.one("a."+r.EDITINGMOVE);t&&(t.remove(),e.one("."+r.HEADER).setStyle("cursor","move"))},this)},this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(t){var n=t.ancestor("div."+r.BLOCKREGION).get("id").replace(/region-/i,"");return e.Array.indexOf(this.get("regions"),n)===-1?(right_to_left()&&(n==="post"?n="pre":n==="pre"&&(n="post")),"side-"+n):n},get_region_id:function(e){return e.get("id").replace(/region-/i,"")},drag_start:function(e){var t=e.target;this.dragsourceregion=t.get("node").ancestor("div."+r.BLOCKREGION),t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(t){var n=t.drag.get("node"),i=t.drop.get("node");i.hasClass(this.parentnodeclass)&&i.one("."+r.BLOCKADMINBLOCK)&&i.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&i.prepend(n);if(this.dragsourceregion.contains(i))return!1;var s=e.one("body"),o=this.get_region_id(this.dragsourceregion);s.hasClass("side-"+o+"-only")&&s.removeClass("side-"+o+"-only"),o=this.get_region_id(i.ancestor("div."+r.BLOCKREGION)),this.dragsourceregion.all("."+r.BLOCK).size()==0&&this.dragsourceregion.get("id").match(/(region-pre|region-post)/i)&&(s.hasClass("side-"+o+"-only")||s.addClass("side-"+o+"-only"))},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.dragsourceregion=null},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag,s=i.get("node"),o=t.drop.get("node");s.previous()&&s.previous().hasClass(r.SKIPBLOCK)&&s.insert(s.previous(),"after"),this.skipnodetop&&s.insert(this.skipnodetop,"before"),this.skipnodebottom&&s.insert(this.skipnodebottom,"after");var u=M.util.add_lightbox(e,s),a={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(s),bui_newregion:this.get_block_region(o)};this.get("cmid")&&(a.cmid=this.get("cmid")),s.next("."+this.samenodeclass)&&!s.next("."+this.samenodeclass).hasClass(r.BLOCKADMINBLOCK)&&(a.bui_beforeid=this.get_block_id(s.next("."+this.samenodeclass))),e.io(M.cfg.wwwroot+n,{method:"POST",data:a,on:{start:function(){u.show()},success:function(t,n){window.setTimeout(function(){u.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),u.hide()}},context:this})}},{NAME:"core-blocks-dragdrop",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:null}}}),M.core=M.core||{},M.core.blockdraganddrop=M.core.blockdraganddrop||{},M.core.blockdraganddrop._isusingnewblocksmethod=null,M.core.blockdraganddrop.is_using_blocks_render_method=function(){if(this._isusingnewblocksmethod===null){var t=e.all(".block-region[data-blockregion]").size(),n=e.all(".block-region").size();this._isusingnewblocksmethod=n===t}return this._isusingnewblocksmethod},M.core.blockdraganddrop.init=function(e){this.is_using_blocks_render_method()?new s(e):new i(e)},M.core_blocks=M.core_blocks||{},M.core_blocks.init_dragdrop=function(e){M.core.blockdraganddrop.init(e)};var s=function(){s.superclass.constructor.apply(this,arguments)};s.prototype={skipnodetop:null,skipnodebottom:null,regionobjects:{},initializer:function(){var t=this.get("regions"),n=0,i,s,u,a;this.groups=["block"],this.samenodeclass=r.BLOCK,this.parentnodeclass=r.BLOCKREGION;var f=e.Node.all("body#"+r.MYINDEX+" #"+r.REGIONMAIN+" > ."+r.REGIONCONTENT);if(f.size()>0){var l=f.item(0);l.addClass(r.BLOCKREGION),l.set("id",r.REGIONCONTENT),l.one("div").addClass(r.REGIONCONTENT)}for(n in t)s=t[n],i=new o({manager:this,region:s,node:e.one("#block-region-"+s)}),this.regionobjects[s]=i,u=new e.DD.Drop({node:i.get_droptarget(),groups:this.groups,padding:"40 240 40 240"}),a=new e.DD.Delegate({container:i.get_droptarget(),nodes:"."+r.BLOCK,target:!0,handles:["."+r.HEADER],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}}),a.dd.plug(e.Plugin.DDProxy,{moveOnEnd +:!1}),a.dd.plug(e.Plugin.DDWinScroll),a.on("drag:mouseDown",this.enable_all_regions,this),i.remove_block_move_icons()},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(e){return e.test("[data-blockregion]")||(e=e.ancestor("[data-blockregion]")),e.getData("blockregion")},get_region_object:function(e){return this.regionobjects[this.get_block_region(e)]},enable_all_regions:function(){var e=0;for(e in this.regionobjects)this.regionobjects[e].enable()},disable_regions_if_required:function(){var e=0;for(e in this.regionobjects)this.regionobjects[e].disable_if_required()},drag_start:function(e){var t=e.target;t.get("node").previous()&&t.get("node").previous().hasClass(r.SKIPBLOCK)&&(this.skipnodetop=t.get("node").previous()),t.get("node").next()&&t.get("node").next().hasClass(r.SKIPBLOCKTO)&&(this.skipnodebottom=t.get("node").next())},drop_over:function(e){var t=e.drag.get("node"),n=e.drop.get("node");n.hasClass(r.REGIONCONTENT)&&n.one("."+r.BLOCKADMINBLOCK)&&n.one("."+r.BLOCKADMINBLOCK).next("."+r.BLOCK)&&n.prepend(t)},drop_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.disable_regions_if_required()},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag.get("node"),s=t.drop.get("node");i.previous()&&i.previous().hasClass(r.SKIPBLOCK)&&i.insert(i.previous(),"after"),this.skipnodetop&&i.insert(this.skipnodetop,"before"),this.skipnodebottom&&i.insert(this.skipnodebottom,"after");var o=M.util.add_lightbox(e,i),u={sesskey:M.cfg.sesskey,courseid:this.get("courseid"),pagelayout:this.get("pagelayout"),pagetype:this.get("pagetype"),subpage:this.get("subpage"),contextid:this.get("contextid"),action:"move",bui_moveid:this.get_block_id(i),bui_newregion:this.get_block_region(s)};this.get("cmid")&&(u.cmid=this.get("cmid")),i.next("."+r.BLOCK)&&!i.next("."+r.BLOCK).hasClass(r.BLOCKADMINBLOCK)&&(u.bui_beforeid=this.get_block_id(i.next("."+r.BLOCK))),e.io(M.cfg.wwwroot+n,{method:"POST",data:u,on:{start:function(){o.show()},success:function(t,n){window.setTimeout(function(){o.hide()},250);try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r)}catch(i){}},failure:function(e,t){this.ajax_failure(t),o.hide()},complete:function(){this.disable_regions_if_required()}},context:this})}},e.extend(s,M.core.dragdrop,s.prototype,{NAME:"core-blocks-dragdrop-manager",ATTRS:{courseid:{value:null},cmid:{value:null},contextid:{value:null},pagelayout:{value:null},pagetype:{value:null},subpage:{value:null},regions:{value:[]}}});var o=function(){o.superclass.constructor.apply(this,arguments)};o.prototype={initializer:function(){var t=this.get("node");t||this.create_and_add_node();var n=e.one("body"),i=t.all("."+r.BLOCK).size()>0,s=this.get_has_region_class();this.set("hasblocks",i),n.hasClass(s)||n.addClass(s),n.addClass(i?this.get_used_region_class():this.get_empty_region_class()),n.removeClass(i?this.get_empty_region_class():this.get_used_region_class())},create_and_add_node:function(){var t=e.Node.create,n=this.get("region"),i=t('
').addClass(r.BLOCKREGION).setData("blockregion",n),s=this.get("manager").get("regions"),o,u=!1,a=!1,f=!1,l,c;for(o in s)s[o].match(/(pre|left)/)?u=s[o]:s[o].match(/(post|right)/)&&(a=s[o]);u!==!1&&a!==!1&&(n===u?(c=e.one("#block-region-"+a),c&&(c.insert(i,"before"),f=!0)):(l=e.one("#block-region-"+u),l&&(l.insert(i,"after"),f=!0))),f===!1&&e.one("body").append(i),this.set("node",i)},remove_block_move_icons:function(){this.get("node").all("."+r.BLOCK+" a."+r.EDITINGMOVE).each(function(e){e.ancestor("."+r.BLOCK).one("."+r.HEADER).setStyle("cursor","move"),e.remove()})},get_has_region_class:function(){return"has-region-"+this.get("region")},get_empty_region_class:function(){return"empty-region-"+this.get("region")},get_used_region_class:function(){return"used-region-"+this.get("region")},get_droptarget:function(){var e=this.get("node");return e.test('[data-droptarget="1"]')?e:e.one('[data-droptarget="1"]')},enable:function(){e.one("body").addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class())},disable_if_required:function(){this.get("node").all("."+r.BLOCK).size()===0&&e.one("body").addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class())}},e.extend(o,e.Base,o.prototype,{NAME:"core-blocks-dragdrop-blockregion",ATTRS:{manager:{writeOnce:"initOnly",validator:function(t){return e.Lang.isObject(t)&&t instanceof s}},region:{writeOnce:"initOnly",validator:function(t){return e.Lang.isString(t)}},node:{validator:function(t){return e.Lang.isObject(t)||e.Lang.isNull(t)}},hasblocks:{value:!1,validator:function(t){return e.Lang.isBoolean(t)}}}})},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification"]}); diff --git a/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js b/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js index 8430a9b1003..bc3bb526512 100644 --- a/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js +++ b/lib/yui/build/moodle-core-blocks/moodle-core-blocks.js @@ -1,5 +1,11 @@ YUI.add('moodle-core-blocks', function (Y, NAME) { +/** + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + var AJAXURL = '/lib/ajax/blocks.php', CSS = { BLOCK : 'block', @@ -15,6 +21,16 @@ CSS = { REGIONMAIN : 'region-main' }; +/** + * Legacy drag and drop manager. + * This drag and drop manager is specifically designed for themes using side-pre and side-post + * that do not make use of the block output methods introduced by MDL-39824. + * + * @namespace M.core.blockdraganddrop + * @class LegacyManager + * @constructor + * @extends M.core.dragdrop + */ var DRAGBLOCK = function() { DRAGBLOCK.superclass.constructor.apply(this, arguments); }; @@ -301,11 +317,672 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, { } }); -M.core_blocks = M.core_blocks || {}; -M.core_blocks.init_dragdrop = function(params) { - new DRAGBLOCK(params); +/** + * Core namespace. + * @static + * @class core + */ +M.core = M.core || {}; + +/** + * Block drag and drop static class. + * @namespace M.core + * @class blockdraganddrop + * @static + */ +M.core.blockdraganddrop = M.core.blockdraganddrop || {}; + +/** + * True if the page is using the new blocks methods. + * @private + * @static + * @property _isusingnewblocksmethod + * @type Boolean + * @default null + */ +M.core.blockdraganddrop._isusingnewblocksmethod = null; + +/** + * Returns true if the page is using the new blocks methods. + * @static + * @method is_using_blocks_render_method + * @return Boolean + */ +M.core.blockdraganddrop.is_using_blocks_render_method = function() { + if (this._isusingnewblocksmethod === null) { + var goodregions = Y.all('.block-region[data-blockregion]').size(); + var allregions = Y.all('.block-region').size(); + this._isusingnewblocksmethod = (allregions === goodregions); + } + return this._isusingnewblocksmethod; }; +/** + * Initialises a drag and drop manager. + * This should only ever be called once for a page. + * @static + * @method init + * @param {Object} params + * @return Manager + */ +M.core.blockdraganddrop.init = function(params) { + if (this.is_using_blocks_render_method()) { + new MANAGER(params); + } else { + new DRAGBLOCK(params); + } +}; + +/** + * Legacy code to keep things working. + */ +M.core_blocks = M.core_blocks || {}; +M.core_blocks.init_dragdrop = function(params) { + M.core.blockdraganddrop.init(params); +};/** + * This file contains the drag and drop manager class. + * + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + +/** + * Constructs a new Block drag and drop manager. + * + * @namespace M.core.blockdraganddrop + * @class Manager + * @constructor + * @extends M.core.dragdrop + */ +var MANAGER = function() { + MANAGER.superclass.constructor.apply(this, arguments); +}; +MANAGER.prototype = { + + /** + * The skip block link from above the block being dragged while a drag is in progress. + * Required by the M.core.dragdrop from whom this class extends. + * @private + * @property skipnodetop + * @type Node + * @default null + */ + skipnodetop : null, + + /** + * The skip block link from below the block being dragged while a drag is in progress. + * Required by the M.core.dragdrop from whom this class extends. + * @private + * @property skipnodebottom + * @type Node + * @default null + */ + skipnodebottom : null, + + /** + * An associative object of regions and the + * @property regionobjects + * @type {Object} Primitive object mocking an associative array. + * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being + * an instance of the BLOCKREGION class. + */ + regionobjects : {}, + + /** + * Called during the initialisation process of the object. + * @method initializer + */ + initializer : function() { + var regionnames = this.get('regions'), + i = 0, + region, + regionname, + droptarget, + dragdelegation; + + // Evil required by M.core.dragdrop. + this.groups = ['block']; + this.samenodeclass = CSS.BLOCK; + this.parentnodeclass = CSS.BLOCKREGION; + + // Add relevant classes and ID to 'content' block region on My Home page. + var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT); + if (myhomecontent.size() > 0) { + var contentregion = myhomecontent.item(0); + contentregion.addClass(CSS.BLOCKREGION); + contentregion.set('id', CSS.REGIONCONTENT); + contentregion.one('div').addClass(CSS.REGIONCONTENT); + } + + for (i in regionnames) { + regionname = regionnames[i]; + region = new BLOCKREGION({ + manager : this, + region : regionname, + node : Y.one('#block-region-'+regionname) + }); + this.regionobjects[regionname] = region; + + // Setting blockregion as droptarget (the case when it is empty) + // The region-post (the right one) + // is very narrow, so add extra padding on the left to drop block on it. + droptarget = new Y.DD.Drop({ + node: region.get_droptarget(), + groups: this.groups, + padding: '40 240 40 240' + }); + + // Make each div element in the list of blocks draggable + dragdelegation = new Y.DD.Delegate({ + container: region.get_droptarget(), + nodes: '.'+CSS.BLOCK, + target: true, + handles: ['.'+CSS.HEADER], + invalid: '.block-hider-hide, .block-hider-show, .moveto', + dragConfig: {groups: this.groups} + }); + dragdelegation.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false + }); + dragdelegation.dd.plug(Y.Plugin.DDWinScroll); + // On the mouse down event we will enable all block regions so that they can be dragged to. + // This is VERY important as without it dnd won't work for empty block regions. + dragdelegation.on('drag:mouseDown', this.enable_all_regions, this); + + region.remove_block_move_icons(); + } + }, + + /** + * Returns the ID of the block the given node represents. + * @method get_block_id + * @param {Node} node + * @returns {int} The blocks ID in the database. + */ + get_block_id : function(node) { + return Number(node.get('id').replace(/inst/i, '')); + }, + + /** + * Returns the block region that the node is part of or belonging to. + * @method get_block_region + * @param {Y.Node} node + * @returns {string} The region name. + */ + get_block_region : function(node) { + if (!node.test('[data-blockregion]')) { + node = node.ancestor('[data-blockregion]'); + } + return node.getData('blockregion'); + }, + + /** + * Returns the BLOCKREGION instance that represents the block region the given node is part of. + * @method get_region_object + * @param {Y.Node} node + * @returns {BLOCKREGION} + */ + get_region_object : function(node) { + return this.regionobjects[this.get_block_region(node)]; + }, + + /** + * Enables all fo the regions so that they are all visible while dragging is occuring. + * @method enable_all_regions + * @returns {undefined} + */ + enable_all_regions : function() { + var i = 0; + for (i in this.regionobjects) { + this.regionobjects[i].enable(); + } + }, + + /** + * Disables enabled regions if they contain no blocks. + * @method disable_regions_if_required + * @returns {undefined} + */ + disable_regions_if_required : function() { + var i = 0; + for (i in this.regionobjects) { + this.regionobjects[i].disable_if_required(); + } + }, + + /** + * Called by M.core.dragdrop.global_drag_start when dragging starts. + * @method drag_start + * @param {Event} e + * @returns {undefined} + */ + drag_start : function(e) { + // Get our drag object + var drag = e.target; + + // Store the parent node of original drag node (block) + // we will need it later for show/hide empty regions + + // Determine skipnodes and store them + if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) { + this.skipnodetop = drag.get('node').previous(); + } + if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) { + this.skipnodebottom = drag.get('node').next(); + } + }, + + /** + * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target. + * @method drop_over + * @param {Event} e + * @returns {undefined} + */ + drop_over : function(e) { + // Get a reference to our drag and drop nodes + var drag = e.drag.get('node'); + var drop = e.drop.get('node'); + + // We need to fix the case when parent drop over event has determined + // 'goingup' and appended the drag node after admin-block. + if (drop.hasClass(CSS.REGIONCONTENT) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) { + drop.prepend(drag); + } + }, + + /** + * Called by M.core.dragdrop.global_drop_end when a drop has been completed. + * @method drop_end + * @returns {undefined} + */ + drop_end : function() { + // Clear variables. + this.skipnodetop = null; + this.skipnodebottom = null; + this.disable_regions_if_required(); + }, + + /** + * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by a drop target. + * @method drag_dropmiss + * @param {Event} e + * @returns {undefined} + */ + drag_dropmiss : function(e) { + // Missed the target, but we assume the user intended to drop it + // on the 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); + }, + + /** + * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target. + * @method drop_hit + * @param {Event} e + * @returns {undefined} + */ + drop_hit : function(e) { + // Get a reference to our drag node + var dragnode = e.drag.get('node'); + var dropnode = e.drop.get('node'); + + // Amend existing skipnodes + if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) { + // the one that belongs to block below move below + dragnode.insert(dragnode.previous(), 'after'); + } + // Move original skipnodes + if (this.skipnodetop) { + dragnode.insert(this.skipnodetop, 'before'); + } + if (this.skipnodebottom) { + dragnode.insert(this.skipnodebottom, 'after'); + } + + // Add lightbox if it not there + var lightbox = M.util.add_lightbox(Y, dragnode); + + // Prepare request parameters + var params = { + sesskey : M.cfg.sesskey, + courseid : this.get('courseid'), + pagelayout : this.get('pagelayout'), + pagetype : this.get('pagetype'), + subpage : this.get('subpage'), + contextid : this.get('contextid'), + action : 'move', + bui_moveid : this.get_block_id(dragnode), + bui_newregion : this.get_block_region(dropnode) + }; + + if (this.get('cmid')) { + params.cmid = this.get('cmid'); + } + + if (dragnode.next('.'+CSS.BLOCK) && !dragnode.next('.'+CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) { + params.bui_beforeid = this.get_block_id(dragnode.next('.'+CSS.BLOCK)); + } + + // Do AJAX request + Y.io(M.cfg.wwwroot+AJAXURL, { + method: 'POST', + data: params, + on: { + start : function() { + lightbox.show(); + }, + success: function(tid, response) { + window.setTimeout(function() { + lightbox.hide(); + }, 250); + try { + var responsetext = Y.JSON.parse(response.responseText); + if (responsetext.error) { + new M.core.ajaxException(responsetext); + } + } catch (e) {} + }, + failure: function(tid, response) { + this.ajax_failure(response); + lightbox.hide(); + }, + complete : function() { + this.disable_regions_if_required(); + } + }, + context:this + }); + } +}; +Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, { + NAME : 'core-blocks-dragdrop-manager', + ATTRS : { + /** + * The Course ID if there is one. + * @attribute courseid + * @type int|null + * @default null + */ + courseid : { + value : null + }, + + /** + * The Course Module ID if there is one. + * @attribute cmid + * @type int|null + * @default null + */ + cmid : { + value : null + }, + + /** + * The Context ID. + * @attribute contextid + * @type int|null + * @default null + */ + contextid : { + value : null + }, + + /** + * The current page layout. + * @attribute pagelayout + * @type string|null + * @default null + */ + pagelayout : { + value : null + }, + + /** + * The page type string, should be used as the id for the body tag in the theme. + * @attribute pagetype + * @type string|null + * @default null + */ + pagetype : { + value : null + }, + + /** + * The subpage identifier, if any. + * @attribute subpage + * @type string|null + * @default null + */ + subpage : { + value : null + }, + + /** + * An array of block regions that are present on the page. + * @attribute regions + * @type array|null + * @default Array[] + */ + regions : { + value : [] + } + } +});/** + * This file contains the Block Region class used by the drag and drop manager. + * + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + +/** + * Constructs a new block region object. + * + * @namespace M.core.blockdraganddrop + * @class BlockRegion + * @constructor + * @extends Y.Base + */ +var BLOCKREGION = function() { + BLOCKREGION.superclass.constructor.apply(this, arguments); +}; +BLOCKREGION.prototype = { + /** + * Called during the initialisation process of the object. + * @method initializer + */ + initializer : function() { + var node = this.get('node'); + if (!node) { + this.create_and_add_node(); + } + var body = Y.one('body'), + hasblocks = node.all('.'+CSS.BLOCK).size() > 0, + hasregionclass = this.get_has_region_class(); + this.set('hasblocks', hasblocks); + if (!body.hasClass(hasregionclass)) { + body.addClass(hasregionclass); + } + body.addClass((hasblocks) ? this.get_used_region_class() : this.get_empty_region_class()); + body.removeClass((hasblocks) ? this.get_empty_region_class() : this.get_used_region_class()); + }, + /** + * Creates a generic block region node and adds it to the DOM at the best guess location. + * Any calling of this method is an unfortunate circumstance. + * @method create_and_add_node + */ + create_and_add_node : function() { + var c = Y.Node.create, + region = this.get('region'), + node = c('
') + .addClass(CSS.BLOCKREGION) + .setData('blockregion', region), + regions = this.get('manager').get('regions'), + i, + haspre = false, + haspost = false, + added = false, + pre, + post; + + for (i in regions) { + if (regions[i].match(/(pre|left)/)) { + haspre = regions[i]; + } else if (regions[i].match(/(post|right)/)) { + haspost = regions[i]; + } + } + + if (haspre !== false && haspost !== false) { + if (region === haspre) { + post = Y.one('#block-region-'+haspost); + if (post) { + post.insert(node, 'before'); + added = true; + } + } else { + pre = Y.one('#block-region-'+haspre); + if (pre) { + pre.insert(node, 'after'); + added = true; + } + } + } + if (added === false) { + Y.one('body').append(node); + } + this.set('node', node); + }, + + /** + * Removes the move icons and changes the cursor to a move icon when over the header. + * @method remove_block_move_icons + */ + remove_block_move_icons : function() { + this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){ + moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move'); + moveicon.remove(); + }); + }, + + /** + * Returns the class name on the body that signifies the document knows about this region. + * @method get_has_region_class + * @return String + */ + get_has_region_class : function() { + return 'has-region-'+this.get('region'); + }, + + /** + * Returns the class name to use on the body if the region contains no blocks. + * @method get_empty_region_class + * @return String + */ + get_empty_region_class : function() { + return 'empty-region-'+this.get('region'); + }, + + /** + * Returns the class name to use on the body if the region contains blocks. + * @method get_used_region_class + * @return String + */ + get_used_region_class : function() { + return 'used-region-'+this.get('region'); + }, + + /** + * Returns the node to use as the drop target for this region. + * @method get_droptarget + * @return Node + */ + get_droptarget : function() { + var node = this.get('node'); + if (node.test('[data-droptarget="1"]')) { + return node; + } + return node.one('[data-droptarget="1"]'); + }, + + /** + * Enables the block region so that we can be sure the user can see it. + * This is done even if it is empty. + * @method enable + */ + enable : function() { + Y.one('body').addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class()); + }, + + /** + * Disables the region if it contains no blocks, essentially hiding it from the user. + * @method disable_if_required + */ + disable_if_required : function() { + if (this.get('node').all('.'+CSS.BLOCK).size() === 0) { + Y.one('body').addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class()); + } + } +}; +Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, { + NAME : 'core-blocks-dragdrop-blockregion', + ATTRS : { + + /** + * The drag and drop manager that created this block region instance. + * @attribute manager + * @type M.core.blockdraganddrop.Manager + * @writeOnce + */ + manager : { + // Can only be set during initialisation and must be set then. + writeOnce : 'initOnly', + validator : function (value) { + return Y.Lang.isObject(value) && value instanceof MANAGER; + } + }, + + /** + * The name of the block region this object represents. + * @attribute region + * @type String + * @writeOnce + */ + region : { + // Can only be set during initialisation and must be set then. + writeOnce : 'initOnly', + validator : function (value) { + return Y.Lang.isString(value); + } + }, + + /** + * The node the block region HTML starts at.s + * @attribute region + * @type Y.Node + */ + node : { + validator : function (value) { + return Y.Lang.isObject(value) || Y.Lang.isNull(value); + } + }, + + /** + * True if the block region currently contains blocks. + * @attribute hasblocks + * @type Boolean + * @default false + */ + hasblocks : { + value : false, + validator : function (value) { + return Y.Lang.isBoolean(value); + } + } + } +}); }, '@VERSION@', { "requires": [ diff --git a/lib/yui/dragdrop/dragdrop.js b/lib/yui/dragdrop/dragdrop.js index 6d7fd45f1c4..b8904bbaa37 100644 --- a/lib/yui/dragdrop/dragdrop.js +++ b/lib/yui/dragdrop/dragdrop.js @@ -158,7 +158,7 @@ YUI.add('moodle-core-dragdrop', function(Y) { } //Add the node e.drop.get('node').ancestor().insertBefore(drag, drop); - } else if (drop.hasClass(this.parentnodeclass) && !drop.contains(drag)) { + } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) { // We are dropping on parent node and it is empty if (this.goingup) { drop.append(drag); diff --git a/lib/yui/src/blocks/build.json b/lib/yui/src/blocks/build.json index 41689c9843e..2644ec9ac69 100644 --- a/lib/yui/src/blocks/build.json +++ b/lib/yui/src/blocks/build.json @@ -3,7 +3,9 @@ "builds": { "moodle-core-blocks": { "jsfiles": [ - "blocks.js" + "blocks.js", + "manager.js", + "blockregion.js" ] } } diff --git a/lib/yui/src/blocks/js/blockregion.js b/lib/yui/src/blocks/js/blockregion.js new file mode 100644 index 00000000000..1d854b7d14a --- /dev/null +++ b/lib/yui/src/blocks/js/blockregion.js @@ -0,0 +1,216 @@ +/** + * This file contains the Block Region class used by the drag and drop manager. + * + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + +/** + * Constructs a new block region object. + * + * @namespace M.core.blockdraganddrop + * @class BlockRegion + * @constructor + * @extends Y.Base + */ +var BLOCKREGION = function() { + BLOCKREGION.superclass.constructor.apply(this, arguments); +}; +BLOCKREGION.prototype = { + /** + * Called during the initialisation process of the object. + * @method initializer + */ + initializer : function() { + var node = this.get('node'); + Y.log('Block region `'+this.get('region')+'` initialising', 'info'); + if (!node) { + Y.log('block region known about but no HTML structure found for it. Guessing structure.', 'warn'); + this.create_and_add_node(); + } + var body = Y.one('body'), + hasblocks = node.all('.'+CSS.BLOCK).size() > 0, + hasregionclass = this.get_has_region_class(); + this.set('hasblocks', hasblocks); + if (!body.hasClass(hasregionclass)) { + body.addClass(hasregionclass); + } + body.addClass((hasblocks) ? this.get_used_region_class() : this.get_empty_region_class()); + body.removeClass((hasblocks) ? this.get_empty_region_class() : this.get_used_region_class()); + }, + /** + * Creates a generic block region node and adds it to the DOM at the best guess location. + * Any calling of this method is an unfortunate circumstance. + * @method create_and_add_node + */ + create_and_add_node : function() { + var c = Y.Node.create, + region = this.get('region'), + node = c('
') + .addClass(CSS.BLOCKREGION) + .setData('blockregion', region), + regions = this.get('manager').get('regions'), + i, + haspre = false, + haspost = false, + added = false, + pre, + post; + + for (i in regions) { + if (regions[i].match(/(pre|left)/)) { + haspre = regions[i]; + } else if (regions[i].match(/(post|right)/)) { + haspost = regions[i]; + } + } + + if (haspre !== false && haspost !== false) { + if (region === haspre) { + post = Y.one('#block-region-'+haspost); + if (post) { + post.insert(node, 'before'); + added = true; + } + } else { + pre = Y.one('#block-region-'+haspre); + if (pre) { + pre.insert(node, 'after'); + added = true; + } + } + } + if (added === false) { + Y.one('body').append(node); + } + this.set('node', node); + }, + + /** + * Removes the move icons and changes the cursor to a move icon when over the header. + * @method remove_block_move_icons + */ + remove_block_move_icons : function() { + this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){ + moveicon.ancestor('.'+CSS.BLOCK).one('.'+CSS.HEADER).setStyle('cursor', 'move'); + moveicon.remove(); + }); + }, + + /** + * Returns the class name on the body that signifies the document knows about this region. + * @method get_has_region_class + * @return String + */ + get_has_region_class : function() { + return 'has-region-'+this.get('region'); + }, + + /** + * Returns the class name to use on the body if the region contains no blocks. + * @method get_empty_region_class + * @return String + */ + get_empty_region_class : function() { + return 'empty-region-'+this.get('region'); + }, + + /** + * Returns the class name to use on the body if the region contains blocks. + * @method get_used_region_class + * @return String + */ + get_used_region_class : function() { + return 'used-region-'+this.get('region'); + }, + + /** + * Returns the node to use as the drop target for this region. + * @method get_droptarget + * @return Node + */ + get_droptarget : function() { + var node = this.get('node'); + if (node.test('[data-droptarget="1"]')) { + return node; + } + return node.one('[data-droptarget="1"]'); + }, + + /** + * Enables the block region so that we can be sure the user can see it. + * This is done even if it is empty. + * @method enable + */ + enable : function() { + Y.one('body').addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class()); + }, + + /** + * Disables the region if it contains no blocks, essentially hiding it from the user. + * @method disable_if_required + */ + disable_if_required : function() { + if (this.get('node').all('.'+CSS.BLOCK).size() === 0) { + Y.one('body').addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class()); + } + } +}; +Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, { + NAME : 'core-blocks-dragdrop-blockregion', + ATTRS : { + + /** + * The drag and drop manager that created this block region instance. + * @attribute manager + * @type M.core.blockdraganddrop.Manager + * @writeOnce + */ + manager : { + // Can only be set during initialisation and must be set then. + writeOnce : 'initOnly', + validator : function (value) { + return Y.Lang.isObject(value) && value instanceof MANAGER; + } + }, + + /** + * The name of the block region this object represents. + * @attribute region + * @type String + * @writeOnce + */ + region : { + // Can only be set during initialisation and must be set then. + writeOnce : 'initOnly', + validator : function (value) { + return Y.Lang.isString(value); + } + }, + + /** + * The node the block region HTML starts at.s + * @attribute region + * @type Y.Node + */ + node : { + validator : function (value) { + return Y.Lang.isObject(value) || Y.Lang.isNull(value); + } + }, + + /** + * True if the block region currently contains blocks. + * @attribute hasblocks + * @type Boolean + * @default false + */ + hasblocks : { + value : false, + validator : function (value) { + return Y.Lang.isBoolean(value); + } + } + } +}); \ No newline at end of file diff --git a/lib/yui/src/blocks/js/blocks.js b/lib/yui/src/blocks/js/blocks.js index ff2809d23f2..4597accb9eb 100644 --- a/lib/yui/src/blocks/js/blocks.js +++ b/lib/yui/src/blocks/js/blocks.js @@ -1,3 +1,9 @@ +/** + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + var AJAXURL = '/lib/ajax/blocks.php', CSS = { BLOCK : 'block', @@ -13,6 +19,16 @@ CSS = { REGIONMAIN : 'region-main' }; +/** + * Legacy drag and drop manager. + * This drag and drop manager is specifically designed for themes using side-pre and side-post + * that do not make use of the block output methods introduced by MDL-39824. + * + * @namespace M.core.blockdraganddrop + * @class LegacyManager + * @constructor + * @extends M.core.dragdrop + */ var DRAGBLOCK = function() { DRAGBLOCK.superclass.constructor.apply(this, arguments); }; @@ -299,7 +315,66 @@ Y.extend(DRAGBLOCK, M.core.dragdrop, { } }); +/** + * Core namespace. + * @static + * @class core + */ +M.core = M.core || {}; + +/** + * Block drag and drop static class. + * @namespace M.core + * @class blockdraganddrop + * @static + */ +M.core.blockdraganddrop = M.core.blockdraganddrop || {}; + +/** + * True if the page is using the new blocks methods. + * @private + * @static + * @property _isusingnewblocksmethod + * @type Boolean + * @default null + */ +M.core.blockdraganddrop._isusingnewblocksmethod = null; + +/** + * Returns true if the page is using the new blocks methods. + * @static + * @method is_using_blocks_render_method + * @return Boolean + */ +M.core.blockdraganddrop.is_using_blocks_render_method = function() { + if (this._isusingnewblocksmethod === null) { + var goodregions = Y.all('.block-region[data-blockregion]').size(); + var allregions = Y.all('.block-region').size(); + this._isusingnewblocksmethod = (allregions === goodregions); + } + return this._isusingnewblocksmethod; +}; + +/** + * Initialises a drag and drop manager. + * This should only ever be called once for a page. + * @static + * @method init + * @param {Object} params + * @return Manager + */ +M.core.blockdraganddrop.init = function(params) { + if (this.is_using_blocks_render_method()) { + new MANAGER(params); + } else { + new DRAGBLOCK(params); + } +}; + +/** + * Legacy code to keep things working. + */ M.core_blocks = M.core_blocks || {}; M.core_blocks.init_dragdrop = function(params) { - new DRAGBLOCK(params); -}; + M.core.blockdraganddrop.init(params); +}; \ No newline at end of file diff --git a/lib/yui/src/blocks/js/manager.js b/lib/yui/src/blocks/js/manager.js new file mode 100644 index 00000000000..40490af9ab7 --- /dev/null +++ b/lib/yui/src/blocks/js/manager.js @@ -0,0 +1,393 @@ +/** + * This file contains the drag and drop manager class. + * + * Provides drag and drop functionality for blocks. + * + * @module moodle-core-blockdraganddrop + */ + +/** + * Constructs a new Block drag and drop manager. + * + * @namespace M.core.blockdraganddrop + * @class Manager + * @constructor + * @extends M.core.dragdrop + */ +var MANAGER = function() { + MANAGER.superclass.constructor.apply(this, arguments); +}; +MANAGER.prototype = { + + /** + * The skip block link from above the block being dragged while a drag is in progress. + * Required by the M.core.dragdrop from whom this class extends. + * @private + * @property skipnodetop + * @type Node + * @default null + */ + skipnodetop : null, + + /** + * The skip block link from below the block being dragged while a drag is in progress. + * Required by the M.core.dragdrop from whom this class extends. + * @private + * @property skipnodebottom + * @type Node + * @default null + */ + skipnodebottom : null, + + /** + * An associative object of regions and the + * @property regionobjects + * @type {Object} Primitive object mocking an associative array. + * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being + * an instance of the BLOCKREGION class. + */ + regionobjects : {}, + + /** + * Called during the initialisation process of the object. + * @method initializer + */ + initializer : function() { + Y.log('Initialising drag and drop for blocks.', 'info'); + var regionnames = this.get('regions'), + i = 0, + region, + regionname, + droptarget, + dragdelegation; + + // Evil required by M.core.dragdrop. + this.groups = ['block']; + this.samenodeclass = CSS.BLOCK; + this.parentnodeclass = CSS.BLOCKREGION; + + // Add relevant classes and ID to 'content' block region on My Home page. + var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT); + if (myhomecontent.size() > 0) { + var contentregion = myhomecontent.item(0); + contentregion.addClass(CSS.BLOCKREGION); + contentregion.set('id', CSS.REGIONCONTENT); + contentregion.one('div').addClass(CSS.REGIONCONTENT); + } + + for (i in regionnames) { + regionname = regionnames[i]; + region = new BLOCKREGION({ + manager : this, + region : regionname, + node : Y.one('#block-region-'+regionname) + }); + this.regionobjects[regionname] = region; + + // Setting blockregion as droptarget (the case when it is empty) + // The region-post (the right one) + // is very narrow, so add extra padding on the left to drop block on it. + droptarget = new Y.DD.Drop({ + node: region.get_droptarget(), + groups: this.groups, + padding: '40 240 40 240' + }); + + // Make each div element in the list of blocks draggable + dragdelegation = new Y.DD.Delegate({ + container: region.get_droptarget(), + nodes: '.'+CSS.BLOCK, + target: true, + handles: ['.'+CSS.HEADER], + invalid: '.block-hider-hide, .block-hider-show, .moveto', + dragConfig: {groups: this.groups} + }); + dragdelegation.dd.plug(Y.Plugin.DDProxy, { + // Don't move the node at the end of the drag + moveOnEnd: false + }); + dragdelegation.dd.plug(Y.Plugin.DDWinScroll); + // On the mouse down event we will enable all block regions so that they can be dragged to. + // This is VERY important as without it dnd won't work for empty block regions. + dragdelegation.on('drag:mouseDown', this.enable_all_regions, this); + + region.remove_block_move_icons(); + } + Y.log('Initialisation of drag and drop for blocks complete.', 'info'); + }, + + /** + * Returns the ID of the block the given node represents. + * @method get_block_id + * @param {Node} node + * @returns {int} The blocks ID in the database. + */ + get_block_id : function(node) { + return Number(node.get('id').replace(/inst/i, '')); + }, + + /** + * Returns the block region that the node is part of or belonging to. + * @method get_block_region + * @param {Y.Node} node + * @returns {string} The region name. + */ + get_block_region : function(node) { + if (!node.test('[data-blockregion]')) { + node = node.ancestor('[data-blockregion]'); + } + return node.getData('blockregion'); + }, + + /** + * Returns the BLOCKREGION instance that represents the block region the given node is part of. + * @method get_region_object + * @param {Y.Node} node + * @returns {BLOCKREGION} + */ + get_region_object : function(node) { + return this.regionobjects[this.get_block_region(node)]; + }, + + /** + * Enables all fo the regions so that they are all visible while dragging is occuring. + * @method enable_all_regions + * @returns {undefined} + */ + enable_all_regions : function() { + var i = 0; + for (i in this.regionobjects) { + this.regionobjects[i].enable(); + } + }, + + /** + * Disables enabled regions if they contain no blocks. + * @method disable_regions_if_required + * @returns {undefined} + */ + disable_regions_if_required : function() { + var i = 0; + for (i in this.regionobjects) { + this.regionobjects[i].disable_if_required(); + } + }, + + /** + * Called by M.core.dragdrop.global_drag_start when dragging starts. + * @method drag_start + * @param {Event} e + * @returns {undefined} + */ + drag_start : function(e) { + // Get our drag object + var drag = e.target; + + // Store the parent node of original drag node (block) + // we will need it later for show/hide empty regions + + // Determine skipnodes and store them + if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) { + this.skipnodetop = drag.get('node').previous(); + } + if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) { + this.skipnodebottom = drag.get('node').next(); + } + }, + + /** + * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target. + * @method drop_over + * @param {Event} e + * @returns {undefined} + */ + drop_over : function(e) { + // Get a reference to our drag and drop nodes + var drag = e.drag.get('node'); + var drop = e.drop.get('node'); + + // We need to fix the case when parent drop over event has determined + // 'goingup' and appended the drag node after admin-block. + if (drop.hasClass(CSS.REGIONCONTENT) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) { + drop.prepend(drag); + } + }, + + /** + * Called by M.core.dragdrop.global_drop_end when a drop has been completed. + * @method drop_end + * @returns {undefined} + */ + drop_end : function() { + // Clear variables. + this.skipnodetop = null; + this.skipnodebottom = null; + this.disable_regions_if_required(); + }, + + /** + * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by a drop target. + * @method drag_dropmiss + * @param {Event} e + * @returns {undefined} + */ + drag_dropmiss : function(e) { + // Missed the target, but we assume the user intended to drop it + // on the 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); + }, + + /** + * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target. + * @method drop_hit + * @param {Event} e + * @returns {undefined} + */ + drop_hit : function(e) { + // Get a reference to our drag node + var dragnode = e.drag.get('node'); + var dropnode = e.drop.get('node'); + + // Amend existing skipnodes + if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) { + // the one that belongs to block below move below + dragnode.insert(dragnode.previous(), 'after'); + } + // Move original skipnodes + if (this.skipnodetop) { + dragnode.insert(this.skipnodetop, 'before'); + } + if (this.skipnodebottom) { + dragnode.insert(this.skipnodebottom, 'after'); + } + + // Add lightbox if it not there + var lightbox = M.util.add_lightbox(Y, dragnode); + + // Prepare request parameters + var params = { + sesskey : M.cfg.sesskey, + courseid : this.get('courseid'), + pagelayout : this.get('pagelayout'), + pagetype : this.get('pagetype'), + subpage : this.get('subpage'), + contextid : this.get('contextid'), + action : 'move', + bui_moveid : this.get_block_id(dragnode), + bui_newregion : this.get_block_region(dropnode) + }; + + if (this.get('cmid')) { + params.cmid = this.get('cmid'); + } + + if (dragnode.next('.'+CSS.BLOCK) && !dragnode.next('.'+CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) { + params.bui_beforeid = this.get_block_id(dragnode.next('.'+CSS.BLOCK)); + } + + // Do AJAX request + Y.io(M.cfg.wwwroot+AJAXURL, { + method: 'POST', + data: params, + on: { + start : function() { + lightbox.show(); + }, + success: function(tid, response) { + window.setTimeout(function() { + lightbox.hide(); + }, 250); + try { + var responsetext = Y.JSON.parse(response.responseText); + if (responsetext.error) { + new M.core.ajaxException(responsetext); + } + } catch (e) {} + }, + failure: function(tid, response) { + this.ajax_failure(response); + lightbox.hide(); + }, + complete : function() { + this.disable_regions_if_required(); + } + }, + context:this + }); + } +}; +Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, { + NAME : 'core-blocks-dragdrop-manager', + ATTRS : { + /** + * The Course ID if there is one. + * @attribute courseid + * @type int|null + * @default null + */ + courseid : { + value : null + }, + + /** + * The Course Module ID if there is one. + * @attribute cmid + * @type int|null + * @default null + */ + cmid : { + value : null + }, + + /** + * The Context ID. + * @attribute contextid + * @type int|null + * @default null + */ + contextid : { + value : null + }, + + /** + * The current page layout. + * @attribute pagelayout + * @type string|null + * @default null + */ + pagelayout : { + value : null + }, + + /** + * The page type string, should be used as the id for the body tag in the theme. + * @attribute pagetype + * @type string|null + * @default null + */ + pagetype : { + value : null + }, + + /** + * The subpage identifier, if any. + * @attribute subpage + * @type string|null + * @default null + */ + subpage : { + value : null + }, + + /** + * An array of block regions that are present on the page. + * @attribute regions + * @type array|null + * @default Array[] + */ + regions : { + value : [] + } + } +}); \ No newline at end of file