ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç moodle-core-blocks-debug.js000064400000101773152267422110011661 0ustar00YUI.add('moodle-core-blocks', function (Y, NAME) { var MANAGER; var BLOCKREGION; var DRAGBLOCK; /** * Provides drag and drop functionality for blocks. * * @module moodle-core-blockdraganddrop */ var AJAXURL = '/lib/ajax/blocks.php', CSS = { 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', BLOCKSMOVING: 'blocks-moving' }; var SELECTOR = { DRAGHANDLE: '.' + CSS.HEADER + ' .commands .moodle-core-dragdrop-draghandle' }; /** * 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 */ DRAGBLOCK = function() { DRAGBLOCK.superclass.constructor.apply(this, arguments); }; Y.extend(DRAGBLOCK, M.core.dragdrop, { skipnodetop: null, skipnodebottom: null, dragsourceregion: null, initializer: function() { // Set group for parent class this.groups = ['block']; this.samenodeclass = CSS.BLOCK; this.parentnodeclass = CSS.REGIONCONTENT; // Add relevant classes and ID to 'content' block region on Dashboard 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); } // Initialise blocks dragging // Find all block regions on the page var blockregionlist = Y.Node.all('div.' + CSS.BLOCKREGION); if (blockregionlist.size() === 0) { return false; } // See if we are missing either of block regions, // if yes we need to add an empty one to use as target if (blockregionlist.size() !== this.get('regions').length) { var blockregion = Y.Node.create('
') .addClass(CSS.BLOCKREGION); var regioncontent = Y.Node.create('
') .addClass(CSS.REGIONCONTENT); blockregion.appendChild(regioncontent); var pre = blockregionlist.filter('#region-pre'); var post = blockregionlist.filter('#region-post'); if (pre.size() === 0 && post.size() === 1) { // pre block is missing, instert it before post blockregion.setAttrs({id: 'region-pre'}); post.item(0).insert(blockregion, 'before'); blockregionlist.unshift(blockregion); } else if (post.size() === 0 && pre.size() === 1) { // post block is missing, instert it after pre blockregion.setAttrs({id: 'region-post'}); pre.item(0).insert(blockregion, 'after'); blockregionlist.push(blockregion); } } blockregionlist.each(function(blockregionnode) { // 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. new Y.DD.Drop({ node: blockregionnode.one('div.' + CSS.REGIONCONTENT), groups: this.groups, padding: '40 240 40 240' }); // Make each div element in the list of blocks draggable var del = new Y.DD.Delegate({ container: blockregionnode, nodes: '.' + CSS.BLOCK, target: true, handles: [SELECTOR.DRAGHANDLE], invalid: '.block-hider-hide, .block-hider-show, .moveto', 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.DDWinScroll); var blocklist = blockregionnode.all('.' + CSS.BLOCK); blocklist.each(function(blocknode) { var move = blocknode.one('a.' + CSS.EDITINGMOVE); if (move) { move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'iconsmall', true)); blocknode.one(SELECTOR.DRAGHANDLE).setStyle('cursor', 'move'); } }, this); }, this); }, get_block_id: function(node) { return Number(node.get('id').replace(/inst/i, '')); }, get_block_region: function(node) { var region = node.ancestor('div.' + CSS.BLOCKREGION).get('id').replace(/region-/i, ''); if (Y.Array.indexOf(this.get('regions'), region) === -1) { // Must be standard side-X if (window.right_to_left()) { if (region === 'post') { region = 'pre'; } else if (region === 'pre') { region = 'post'; } } return 'side-' + region; } // Perhaps custom region return region; }, get_region_id: function(node) { return node.get('id').replace(/region-/i, ''); }, 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 this.dragsourceregion = drag.get('node').ancestor('div.' + CSS.BLOCKREGION); // 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(); } // Add the blocks-moving class so that the theme can respond if need be. Y.one('body').addClass(CSS.BLOCKSMOVING); }, 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(this.parentnodeclass) && drop.one('.' + CSS.BLOCKADMINBLOCK) && drop.one('.' + CSS.BLOCKADMINBLOCK).next('.' + CSS.BLOCK)) { drop.prepend(drag); } // Block is moved within the same region // stop here, no need to modify anything. if (this.dragsourceregion.contains(drop)) { return false; } // TODO: Hiding-displaying block region only works for base theme blocks // (region-pre, region-post) at the moment. It should be improved // to work with custom block regions as well. // TODO: Fix this for the case when user drag block towards empty section, // then the section appears, then user chnages his mind and moving back to // original section. The opposite section remains opened and empty. var documentbody = Y.one('body'); // Moving block towards hidden region-content, display it var regionname = this.get_region_id(this.dragsourceregion); if (documentbody.hasClass('side-' + regionname + '-only')) { documentbody.removeClass('side-' + regionname + '-only'); } // Moving from empty region-content towards the opposite one, // hide empty one (only for region-pre, region-post areas at the moment). regionname = this.get_region_id(drop.ancestor('div.' + CSS.BLOCKREGION)); if (this.dragsourceregion.all('.' + CSS.BLOCK).size() === 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) { if (!documentbody.hasClass('side-' + regionname + '-only')) { documentbody.addClass('side-' + regionname + '-only'); } } }, drag_end: function() { // clear variables this.skipnodetop = null; this.skipnodebottom = null; this.dragsourceregion = null; // Remove the blocks moving class once the drag-drop is over. Y.one('body').removeClass(CSS.BLOCKSMOVING); }, 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'); // 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, pagehash: this.get('pagehash'), 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('.' + this.samenodeclass) && !dragnode.next('.' + this.samenodeclass).hasClass(CSS.BLOCKADMINBLOCK)) { params.bui_beforeid = this.get_block_id(dragnode.next('.' + this.samenodeclass)); } // 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) { // Ignore. } }, failure: function(tid, response) { this.ajax_failure(response); lightbox.hide(); } }, context: this }); } }, { NAME: 'core-blocks-dragdrop', ATTRS: { pagehash: { value: null }, regions: { value: null } } }); M.core = M.core || {}; M.core.blockdraganddrop = M.core.blockdraganddrop || {}; /** * True if the page is using the new blocks methods. * @private * @static * @property M.core.blockdraganddrop._isusingnewblocksmethod * @type Boolean * @default null */ M.core.blockdraganddrop._isusingnewblocksmethod = null; /** * Returns true if the page is using the new blocks methods. * @static * @method M.core.blockdraganddrop.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); if (goodregions > 0 && allregions > 0 && goodregions !== allregions) { Y.log('Both core_renderer::blocks and core_renderer::blocks_for_region have been used.', 'warn', 'moodle-core_blocks'); } } return this._isusingnewblocksmethod; }; /** * Initialises a drag and drop manager. * This should only ever be called once for a page. * @static * @method M.core.blockdraganddrop.init * @param {Object} params * @return Manager */ M.core.blockdraganddrop.init = function(params) { if (this.is_using_blocks_render_method()) { Y.log('Block drag and drop initialised for the blocks method.', 'info', 'moodle-core_blocks'); new MANAGER(params); } else { Y.log('Block drag and drop initialised with the legacy manager (blocks_for_region used).', 'info', 'moodle-core_blocks'); 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 */ 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'); var i = 0; var dragContainer; var dragdelegation; var region; var regionContainer; var regionname; // Evil required by M.core.dragdrop. this.groups = ['block']; this.samenodeclass = CSS.BLOCK; this.parentnodeclass = CSS.BLOCKREGION; // Detect the direction of travel. this.detectkeyboarddirection = true; // Add relevant classes and ID to 'content' block region on Dashboard 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]; regionContainer = Y.one('#block-region-' + regionname); region = new BLOCKREGION({ manager: this, region: regionname, node: regionContainer, }); 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. 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: [SELECTOR.DRAGHANDLE], invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake', dragConfig: {groups: this.groups} }); dragdelegation.dd.plug(Y.Plugin.DDProxy, { // Don't move the node at the end of the drag moveOnEnd: false }); if (regionContainer === null) { dragdelegation.dd.plug(Y.Plugin.DDWinScroll); } else { dragContainer = regionContainer.ancestor('.drag-container', true); if (dragContainer) { dragdelegation.dd.plug(Y.Plugin.DDNodeScroll, { node: dragContainer, }); } else { dragdelegation.dd.plug(Y.Plugin.DDWinScroll); } } // On the DD Manager start operation, we enable all block regions so that they can be drop targets. This // must be done *before* drag:start but after dragging has been initialised. Y.DD.DDM.on('ddm:start', this.enable_all_regions, this); region.change_block_move_icons(this); } 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 * @return {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 * @return {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 * @return {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 */ enable_all_regions: function() { var groups = Y.DD.DDM.activeDrag.get('groups'); // As we're called by Y.DD.DDM, we can't be certain that the call // relates specifically to a block drag/drop operation. Test // whether the relevant group applies here. if (!groups || Y.Array.indexOf(groups, 'block') === -1) { return; } var i; for (i in this.regionobjects) { if (!this.regionobjects.hasOwnProperty(i)) { continue; } this.regionobjects[i].enable(); } }, /** * Disables enabled regions if they contain no blocks. * @method disable_regions_if_required */ 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 */ 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(); } }, dragOver: function(e) { var nearestRegion = e.drop.get('node').ancestor('.drag-container', true); if (nearestRegion) { if (e.drag[Y.Plugin.DDNodeScroll]) { if (e.drag[Y.Plugin.DDNodeScroll].get('node') === nearestRegion) { // Do not bother resetting the region - it has not changed. return; } else { e.drag.unplug(Y.Plugin.DDNodeScroll); } } e.drag.plug(Y.Plugin.DDNodeScroll, { node: nearestRegion, }); } }, /** * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target. * @method drop_over * @param {Event} e */ 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 */ 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 */ 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 */ 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, pagehash: this.get('pagehash'), 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) { // Ignore. } }, 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 page identifier. * @attribute pagehash * @type string|null * @default null */ pagehash: { 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 Base */ 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'); 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 * @return Node The newly created 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); return node; }, /** * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header. * @param M.core.dragdrop the block manager * @method change_block_move_icons */ change_block_move_icons: function(manager) { var handle; this.get('node').all('.' + CSS.BLOCK + ' a.' + CSS.EDITINGMOVE).each(function(moveicon) { moveicon.setStyle('cursor', 'move'); handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true); // Dragdrop module assigns this with a button role by default. // However, the block move icon is part of a menubar, so it should have a menuitem role. handle.setAttribute('role', 'menuitem'); moveicon.replace(handle); }); }, /** * 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": [ "base", "node", "io", "dom", "dd", "dd-scroll", "moodle-core-dragdrop", "moodle-core-notification" ] }); moodle-core-blocks-min.js000064400000024320152267422110011346 0ustar00YUI.add("moodle-core-blocks",function(d,e){var o,a,r="/lib/ajax/blocks.php",g="block",l="block-region",s="block_adminblock",n="editing_move",h="header",c="region-content",t="skip-block",p="skip-block-to",_="page-my-index",b="region-main",f="blocks-moving",u={DRAGHANDLE:"."+h+" .commands .moodle-core-dragdrop-draghandle"},i=function(){i.superclass.constructor.apply(this,arguments)};d.extend(i,M.core.dragdrop,{skipnodetop:null,skipnodebottom:null,dragsourceregion:null,initializer:function(){var e,o,t,i;if(this.groups=["block"],this.samenodeclass=g,this.parentnodeclass=c,0<(e=d.Node.all("body#"+_+" #"+b+" > ."+c)).size()&&((e=e.item(0)).addClass(l),e.set("id",c),e.one("div").addClass(c)),0===(e=d.Node.all("div."+l)).size())return!1;e.size()!==this.get("regions").length&&(o=d.Node.create("
").addClass(l),t=d.Node.create("
").addClass(c),o.appendChild(t),t=e.filter("#region-pre"),i=e.filter("#region-post"),0===t.size()&&1===i.size()?(o.setAttrs({id:"region-pre"}),i.item(0).insert(o,"before"),e.unshift(o)):0===i.size()&&1===t.size()&&(o.setAttrs({id:"region-post"}),t.item(0).insert(o,"after"),e.push(o))),e.each(function(e){var o;new d.DD.Drop({node:e.one("div."+c),groups:this.groups,padding:"40 240 40 240"}),(o=new d.DD.Delegate({container:e,nodes:"."+g,target:!0,handles:[u.DRAGHANDLE],invalid:".block-hider-hide, .block-hider-show, .moveto",dragConfig:{groups:this.groups}})).dd.plug(d.Plugin.DDProxy,{moveOnEnd:!1}),o.dd.plug(d.Plugin.DDWinScroll),e.all("."+g).each(function(e){var o=e.one("a."+n);o&&(o.replace(this.get_drag_handle(o.getAttribute("title"),"","iconsmall",!0)),e.one(u.DRAGHANDLE).setStyle("cursor","move"))},this)},this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(e){e=e.ancestor("div."+l).get("id").replace(/region-/i,"");return-1===d.Array.indexOf(this.get("regions"),e)?(window.right_to_left()&&("post"===e?e="pre":"pre"===e&&(e="post")),"side-"+e):e},get_region_id:function(e){return e.get("id").replace(/region-/i,"")},drag_start:function(e){e=e.target;this.dragsourceregion=e.get("node").ancestor("div."+l),e.get("node").previous()&&e.get("node").previous().hasClass(t)&&(this.skipnodetop=e.get("node").previous()),e.get("node").next()&&e.get("node").next().hasClass(p)&&(this.skipnodebottom=e.get("node").next()),d.one("body").addClass(f)},drop_over:function(e){var o,t=e.drag.get("node"),e=e.drop.get("node");if(e.hasClass(this.parentnodeclass)&&e.one("."+s)&&e.one("."+s).next("."+g)&&e.prepend(t),this.dragsourceregion.contains(e))return!1;t=d.one("body"),o=this.get_region_id(this.dragsourceregion),t.hasClass("side-"+o+"-only")&&t.removeClass("side-"+o+"-only"),o=this.get_region_id(e.ancestor("div."+l)),0===this.dragsourceregion.all("."+g).size()&&this.dragsourceregion.get("id").match(/(region-pre|region-post)/i)&&(t.hasClass("side-"+o+"-only")||t.addClass("side-"+o+"-only"))},drag_end:function(){this.skipnodetop=null,this.skipnodebottom=null,this.dragsourceregion=null,d.one("body").removeClass(f)},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(i){var n,e=i.drag,e=e.get("node"),o=i.drop.get("node");e.previous()&&e.previous().hasClass(t)&&e.insert(e.previous(),"after"),this.skipnodetop&&e.insert(this.skipnodetop,"before"),this.skipnodebottom&&e.insert(this.skipnodebottom,"after"),n=M.util.add_lightbox(d,e),o={sesskey:M.cfg.sesskey,pagehash:this.get("pagehash"),action:"move",bui_moveid:this.get_block_id(e),bui_newregion:this.get_block_region(o)},this.get("cmid")&&(o.cmid=this.get("cmid")),e.next("."+this.samenodeclass)&&!e.next("."+this.samenodeclass).hasClass(s)&&(o.bui_beforeid=this.get_block_id(e.next("."+this.samenodeclass))),d.io(M.cfg.wwwroot+r,{method:"POST",data:o,on:{start:function(){n.show()},success:function(e,o){window.setTimeout(function(){n.hide()},250);try{var t=d.JSON.parse(o.responseText);t.error&&new M.core.ajaxException(t)}catch(i){}},failure:function(e,o){this.ajax_failure(o),n.hide()}},context:this})}},{NAME:"core-blocks-dragdrop",ATTRS:{pagehash:{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(){var e,o;return null===this._isusingnewblocksmethod&&(e=d.all(".block-region[data-blockregion]").size(),o=d.all(".block-region").size(),this._isusingnewblocksmethod=o===e),this._isusingnewblocksmethod},M.core.blockdraganddrop.init=function(e){new(this.is_using_blocks_render_method()?o:i)(e)},M.core_blocks=M.core_blocks||{},M.core_blocks.init_dragdrop=function(e){M.core.blockdraganddrop.init(e)},(o=function(){o.superclass.constructor.apply(this,arguments)}).prototype={skipnodetop:null,skipnodebottom:null,regionobjects:{},initializer:function(){var e,o,t,i,n,s=this.get("regions"),r=0;for(r in this.groups=["block"],this.samenodeclass=g,this.parentnodeclass=l,this.detectkeyboarddirection=!0,0<(n=d.Node.all("body#"+_+" #"+b+" > ."+c)).size()&&((n=n.item(0)).addClass(l),n.set("id",c),n.one("div").addClass(c)),s)i=s[r],t=d.one("#block-region-"+i),o=new a({manager:this,region:i,node:t}),this.regionobjects[i]=o,new d.DD.Drop({node:o.get_droptarget(),groups:this.groups,padding:"40 240 40 240"}),(i=new d.DD.Delegate({container:o.get_droptarget(),nodes:"."+g,target:!0,handles:[u.DRAGHANDLE],invalid:".block-hider-hide, .block-hider-show, .moveto, .block_fake",dragConfig:{groups:this.groups}})).dd.plug(d.Plugin.DDProxy,{moveOnEnd:!1}),null!==t&&(e=t.ancestor(".drag-container",!0))?i.dd.plug(d.Plugin.DDNodeScroll,{node:e}):i.dd.plug(d.Plugin.DDWinScroll),d.DD.DDM.on("ddm:start",this.enable_all_regions,this),o.change_block_move_icons(this)},get_block_id:function(e){return Number(e.get("id").replace(/inst/i,""))},get_block_region:function(e){return(e=e.test("[data-blockregion]")?e:e.ancestor("[data-blockregion]")).getData("blockregion")},get_region_object:function(e){return this.regionobjects[this.get_block_region(e)]}, enable_all_regions:function(){var e,o=d.DD.DDM.activeDrag.get("groups");if(o&&-1!==d.Array.indexOf(o,"block"))for(e in this.regionobjects)this.regionobjects.hasOwnProperty(e)&&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){e=e.target;e.get("node").previous()&&e.get("node").previous().hasClass(t)&&(this.skipnodetop=e.get("node").previous()),e.get("node").next()&&e.get("node").next().hasClass(p)&&(this.skipnodebottom=e.get("node").next())},dragOver:function(e){var o=e.drop.get("node").ancestor(".drag-container",!0);if(o){if(e.drag[d.Plugin.DDNodeScroll]){if(e.drag[d.Plugin.DDNodeScroll].get("node")===o)return;e.drag.unplug(d.Plugin.DDNodeScroll)}e.drag.plug(d.Plugin.DDNodeScroll,{node:o})}},drop_over:function(e){var o=e.drag.get("node"),e=e.drop.get("node");e.hasClass(c)&&e.one("."+s)&&e.one("."+s).next("."+g)&&e.prepend(o)},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(i){var n,e=i.drag.get("node"),o=i.drop.get("node");e.previous()&&e.previous().hasClass(t)&&e.insert(e.previous(),"after"),this.skipnodetop&&e.insert(this.skipnodetop,"before"),this.skipnodebottom&&e.insert(this.skipnodebottom,"after"),n=M.util.add_lightbox(d,e),o={sesskey:M.cfg.sesskey,pagehash:this.get("pagehash"),action:"move",bui_moveid:this.get_block_id(e),bui_newregion:this.get_block_region(o)},this.get("cmid")&&(o.cmid=this.get("cmid")),e.next("."+g)&&!e.next("."+g).hasClass(s)&&(o.bui_beforeid=this.get_block_id(e.next("."+g))),d.io(M.cfg.wwwroot+r,{method:"POST",data:o,on:{start:function(){n.show()},success:function(e,o){window.setTimeout(function(){n.hide()},250);try{var t=d.JSON.parse(o.responseText);t.error&&new M.core.ajaxException(t)}catch(i){}},failure:function(e,o){this.ajax_failure(o),n.hide()},complete:function(){this.disable_regions_if_required()}},context:this})}},d.extend(o,M.core.dragdrop,o.prototype,{NAME:"core-blocks-dragdrop-manager",ATTRS:{pagehash:{value:null},regions:{value:[]}}}),(a=function(){a.superclass.constructor.apply(this,arguments)}).prototype={initializer:function(){var e=this.get("node"),e=e||this.create_and_add_node(),o=d.one("body"),e=0').addClass(l).setData("blockregion",t),i=this.get("manager").get("regions"),n=!1,s=!1,r=!1;for(e in i)i[e].match(/(pre|left)/)?n=i[e]:i[e].match(/(post|right)/)&&(s=i[e]);return!1!==n&&!1!==s&&(t===n?(t=d.one("#block-region-"+s))&&(t.insert(o,"before"),r=!0):(t=d.one("#block-region-"+n))&&(t.insert(o,"after"),r=!0)),!1===r&&d.one("body").append(o),this.set("node",o),o},change_block_move_icons:function(o){var t;this.get("node").all("."+g+" a."+n).each(function(e){e.setStyle("cursor","move"),(t=o.get_drag_handle(e.getAttribute("title"),"","icon",!0)).setAttribute("role","menuitem"),e.replace(t)})},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(){d.one("body").addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class())},disable_if_required:function(){0===this.get("node").all("."+g).size()&&d.one("body").addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class())}},d.extend(a,d.Base,a.prototype,{NAME:"core-blocks-dragdrop-blockregion",ATTRS:{manager:{writeOnce:"initOnly",validator:function(e){return d.Lang.isObject(e)&&e instanceof o}},region:{writeOnce:"initOnly",validator:function(e){return d.Lang.isString(e)}},node:{validator:function(e){return d.Lang.isObject(e)||d.Lang.isNull(e)}},hasblocks:{value:!1,validator:function(e){return d.Lang.isBoolean(e)}}}})},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification"]});moodle-core-blocks.js000064400000100476152267422110010574 0ustar00YUI.add('moodle-core-blocks', function (Y, NAME) { var MANAGER; var BLOCKREGION; var DRAGBLOCK; /** * Provides drag and drop functionality for blocks. * * @module moodle-core-blockdraganddrop */ var AJAXURL = '/lib/ajax/blocks.php', CSS = { 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', BLOCKSMOVING: 'blocks-moving' }; var SELECTOR = { DRAGHANDLE: '.' + CSS.HEADER + ' .commands .moodle-core-dragdrop-draghandle' }; /** * 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 */ DRAGBLOCK = function() { DRAGBLOCK.superclass.constructor.apply(this, arguments); }; Y.extend(DRAGBLOCK, M.core.dragdrop, { skipnodetop: null, skipnodebottom: null, dragsourceregion: null, initializer: function() { // Set group for parent class this.groups = ['block']; this.samenodeclass = CSS.BLOCK; this.parentnodeclass = CSS.REGIONCONTENT; // Add relevant classes and ID to 'content' block region on Dashboard 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); } // Initialise blocks dragging // Find all block regions on the page var blockregionlist = Y.Node.all('div.' + CSS.BLOCKREGION); if (blockregionlist.size() === 0) { return false; } // See if we are missing either of block regions, // if yes we need to add an empty one to use as target if (blockregionlist.size() !== this.get('regions').length) { var blockregion = Y.Node.create('
') .addClass(CSS.BLOCKREGION); var regioncontent = Y.Node.create('
') .addClass(CSS.REGIONCONTENT); blockregion.appendChild(regioncontent); var pre = blockregionlist.filter('#region-pre'); var post = blockregionlist.filter('#region-post'); if (pre.size() === 0 && post.size() === 1) { // pre block is missing, instert it before post blockregion.setAttrs({id: 'region-pre'}); post.item(0).insert(blockregion, 'before'); blockregionlist.unshift(blockregion); } else if (post.size() === 0 && pre.size() === 1) { // post block is missing, instert it after pre blockregion.setAttrs({id: 'region-post'}); pre.item(0).insert(blockregion, 'after'); blockregionlist.push(blockregion); } } blockregionlist.each(function(blockregionnode) { // 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. new Y.DD.Drop({ node: blockregionnode.one('div.' + CSS.REGIONCONTENT), groups: this.groups, padding: '40 240 40 240' }); // Make each div element in the list of blocks draggable var del = new Y.DD.Delegate({ container: blockregionnode, nodes: '.' + CSS.BLOCK, target: true, handles: [SELECTOR.DRAGHANDLE], invalid: '.block-hider-hide, .block-hider-show, .moveto', 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.DDWinScroll); var blocklist = blockregionnode.all('.' + CSS.BLOCK); blocklist.each(function(blocknode) { var move = blocknode.one('a.' + CSS.EDITINGMOVE); if (move) { move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'iconsmall', true)); blocknode.one(SELECTOR.DRAGHANDLE).setStyle('cursor', 'move'); } }, this); }, this); }, get_block_id: function(node) { return Number(node.get('id').replace(/inst/i, '')); }, get_block_region: function(node) { var region = node.ancestor('div.' + CSS.BLOCKREGION).get('id').replace(/region-/i, ''); if (Y.Array.indexOf(this.get('regions'), region) === -1) { // Must be standard side-X if (window.right_to_left()) { if (region === 'post') { region = 'pre'; } else if (region === 'pre') { region = 'post'; } } return 'side-' + region; } // Perhaps custom region return region; }, get_region_id: function(node) { return node.get('id').replace(/region-/i, ''); }, 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 this.dragsourceregion = drag.get('node').ancestor('div.' + CSS.BLOCKREGION); // 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(); } // Add the blocks-moving class so that the theme can respond if need be. Y.one('body').addClass(CSS.BLOCKSMOVING); }, 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(this.parentnodeclass) && drop.one('.' + CSS.BLOCKADMINBLOCK) && drop.one('.' + CSS.BLOCKADMINBLOCK).next('.' + CSS.BLOCK)) { drop.prepend(drag); } // Block is moved within the same region // stop here, no need to modify anything. if (this.dragsourceregion.contains(drop)) { return false; } // TODO: Hiding-displaying block region only works for base theme blocks // (region-pre, region-post) at the moment. It should be improved // to work with custom block regions as well. // TODO: Fix this for the case when user drag block towards empty section, // then the section appears, then user chnages his mind and moving back to // original section. The opposite section remains opened and empty. var documentbody = Y.one('body'); // Moving block towards hidden region-content, display it var regionname = this.get_region_id(this.dragsourceregion); if (documentbody.hasClass('side-' + regionname + '-only')) { documentbody.removeClass('side-' + regionname + '-only'); } // Moving from empty region-content towards the opposite one, // hide empty one (only for region-pre, region-post areas at the moment). regionname = this.get_region_id(drop.ancestor('div.' + CSS.BLOCKREGION)); if (this.dragsourceregion.all('.' + CSS.BLOCK).size() === 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) { if (!documentbody.hasClass('side-' + regionname + '-only')) { documentbody.addClass('side-' + regionname + '-only'); } } }, drag_end: function() { // clear variables this.skipnodetop = null; this.skipnodebottom = null; this.dragsourceregion = null; // Remove the blocks moving class once the drag-drop is over. Y.one('body').removeClass(CSS.BLOCKSMOVING); }, 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'); // 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, pagehash: this.get('pagehash'), 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('.' + this.samenodeclass) && !dragnode.next('.' + this.samenodeclass).hasClass(CSS.BLOCKADMINBLOCK)) { params.bui_beforeid = this.get_block_id(dragnode.next('.' + this.samenodeclass)); } // 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) { // Ignore. } }, failure: function(tid, response) { this.ajax_failure(response); lightbox.hide(); } }, context: this }); } }, { NAME: 'core-blocks-dragdrop', ATTRS: { pagehash: { value: null }, regions: { value: null } } }); M.core = M.core || {}; M.core.blockdraganddrop = M.core.blockdraganddrop || {}; /** * True if the page is using the new blocks methods. * @private * @static * @property M.core.blockdraganddrop._isusingnewblocksmethod * @type Boolean * @default null */ M.core.blockdraganddrop._isusingnewblocksmethod = null; /** * Returns true if the page is using the new blocks methods. * @static * @method M.core.blockdraganddrop.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); if (goodregions > 0 && allregions > 0 && goodregions !== allregions) { } } return this._isusingnewblocksmethod; }; /** * Initialises a drag and drop manager. * This should only ever be called once for a page. * @static * @method M.core.blockdraganddrop.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 */ 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'); var i = 0; var dragContainer; var dragdelegation; var region; var regionContainer; var regionname; // Evil required by M.core.dragdrop. this.groups = ['block']; this.samenodeclass = CSS.BLOCK; this.parentnodeclass = CSS.BLOCKREGION; // Detect the direction of travel. this.detectkeyboarddirection = true; // Add relevant classes and ID to 'content' block region on Dashboard 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]; regionContainer = Y.one('#block-region-' + regionname); region = new BLOCKREGION({ manager: this, region: regionname, node: regionContainer, }); 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. 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: [SELECTOR.DRAGHANDLE], invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake', dragConfig: {groups: this.groups} }); dragdelegation.dd.plug(Y.Plugin.DDProxy, { // Don't move the node at the end of the drag moveOnEnd: false }); if (regionContainer === null) { dragdelegation.dd.plug(Y.Plugin.DDWinScroll); } else { dragContainer = regionContainer.ancestor('.drag-container', true); if (dragContainer) { dragdelegation.dd.plug(Y.Plugin.DDNodeScroll, { node: dragContainer, }); } else { dragdelegation.dd.plug(Y.Plugin.DDWinScroll); } } // On the DD Manager start operation, we enable all block regions so that they can be drop targets. This // must be done *before* drag:start but after dragging has been initialised. Y.DD.DDM.on('ddm:start', this.enable_all_regions, this); region.change_block_move_icons(this); } }, /** * Returns the ID of the block the given node represents. * @method get_block_id * @param {Node} node * @return {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 * @return {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 * @return {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 */ enable_all_regions: function() { var groups = Y.DD.DDM.activeDrag.get('groups'); // As we're called by Y.DD.DDM, we can't be certain that the call // relates specifically to a block drag/drop operation. Test // whether the relevant group applies here. if (!groups || Y.Array.indexOf(groups, 'block') === -1) { return; } var i; for (i in this.regionobjects) { if (!this.regionobjects.hasOwnProperty(i)) { continue; } this.regionobjects[i].enable(); } }, /** * Disables enabled regions if they contain no blocks. * @method disable_regions_if_required */ 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 */ 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(); } }, dragOver: function(e) { var nearestRegion = e.drop.get('node').ancestor('.drag-container', true); if (nearestRegion) { if (e.drag[Y.Plugin.DDNodeScroll]) { if (e.drag[Y.Plugin.DDNodeScroll].get('node') === nearestRegion) { // Do not bother resetting the region - it has not changed. return; } else { e.drag.unplug(Y.Plugin.DDNodeScroll); } } e.drag.plug(Y.Plugin.DDNodeScroll, { node: nearestRegion, }); } }, /** * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target. * @method drop_over * @param {Event} e */ 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 */ 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 */ 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 */ 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, pagehash: this.get('pagehash'), 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) { // Ignore. } }, 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 page identifier. * @attribute pagehash * @type string|null * @default null */ pagehash: { 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 Base */ 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) { 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 * @return Node The newly created 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); return node; }, /** * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header. * @param M.core.dragdrop the block manager * @method change_block_move_icons */ change_block_move_icons: function(manager) { var handle; this.get('node').all('.' + CSS.BLOCK + ' a.' + CSS.EDITINGMOVE).each(function(moveicon) { moveicon.setStyle('cursor', 'move'); handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true); // Dragdrop module assigns this with a button role by default. // However, the block move icon is part of a menubar, so it should have a menuitem role. handle.setAttribute('role', 'menuitem'); moveicon.replace(handle); }); }, /** * 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": [ "base", "node", "io", "dom", "dd", "dd-scroll", "moodle-core-dragdrop", "moodle-core-notification" ] });