MDL-41511 blocks: improved how custom block regions were being rendered.
[moodle.git] / lib / yui / build / moodle-core-blocks / moodle-core-blocks.js
1 YUI.add('moodle-core-blocks', function (Y, NAME) {
3 /**
4  * Provides drag and drop functionality for blocks.
5  *
6  * @module moodle-core-blockdraganddrop
7  */
9 var AJAXURL = '/lib/ajax/blocks.php',
10 CSS = {
11     BLOCK : 'block',
12     BLOCKREGION : 'block-region',
13     BLOCKADMINBLOCK : 'block_adminblock',
14     EDITINGMOVE : 'editing_move',
15     HEADER : 'header',
16     LIGHTBOX : 'lightbox',
17     REGIONCONTENT : 'region-content',
18     SKIPBLOCK : 'skip-block',
19     SKIPBLOCKTO : 'skip-block-to',
20     MYINDEX : 'page-my-index',
21     REGIONMAIN : 'region-main'
22 };
24 var SELECTOR = {
25     DRAGHANDLE : '.' + CSS.HEADER + ' .commands .moodle-core-dragdrop-draghandle'
26 };
28 /**
29  * Legacy drag and drop manager.
30  * This drag and drop manager is specifically designed for themes using side-pre and side-post
31  * that do not make use of the block output methods introduced by MDL-39824.
32  *
33  * @namespace M.core.blockdraganddrop
34  * @class LegacyManager
35  * @constructor
36  * @extends M.core.dragdrop
37  */
38 var DRAGBLOCK = function() {
39     DRAGBLOCK.superclass.constructor.apply(this, arguments);
40 };
41 Y.extend(DRAGBLOCK, M.core.dragdrop, {
42     skipnodetop : null,
43     skipnodebottom : null,
44     dragsourceregion : null,
45     initializer : function() {
46         // Set group for parent class
47         this.groups = ['block'];
48         this.samenodeclass = CSS.BLOCK;
49         this.parentnodeclass = CSS.REGIONCONTENT;
51         // Add relevant classes and ID to 'content' block region on My Home page.
52         var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT);
53         if (myhomecontent.size() > 0) {
54             var contentregion = myhomecontent.item(0);
55             contentregion.addClass(CSS.BLOCKREGION);
56             contentregion.set('id', CSS.REGIONCONTENT);
57             contentregion.one('div').addClass(CSS.REGIONCONTENT);
58         }
60         // Initialise blocks dragging
61         // Find all block regions on the page
62         var blockregionlist = Y.Node.all('div.'+CSS.BLOCKREGION);
64         if (blockregionlist.size() === 0) {
65             return false;
66         }
68         // See if we are missing either of block regions,
69         // if yes we need to add an empty one to use as target
70         if (blockregionlist.size() !== this.get('regions').length) {
71             var blockregion = Y.Node.create('<div></div>')
72                 .addClass(CSS.BLOCKREGION);
73             var regioncontent = Y.Node.create('<div></div>')
74                 .addClass(CSS.REGIONCONTENT);
75             blockregion.appendChild(regioncontent);
76             var pre = blockregionlist.filter('#region-pre');
77             var post = blockregionlist.filter('#region-post');
79             if (pre.size() === 0 && post.size() === 1) {
80                 // pre block is missing, instert it before post
81                 blockregion.setAttrs({id : 'region-pre'});
82                 post.item(0).insert(blockregion, 'before');
83                 blockregionlist.unshift(blockregion);
84             } else if (post.size() === 0 && pre.size() === 1) {
85                 // post block is missing, instert it after pre
86                 blockregion.setAttrs({id : 'region-post'});
87                 pre.item(0).insert(blockregion, 'after');
88                 blockregionlist.push(blockregion);
89             }
90         }
92         blockregionlist.each(function(blockregionnode) {
94             // Setting blockregion as droptarget (the case when it is empty)
95             // The region-post (the right one)
96             // is very narrow, so add extra padding on the left to drop block on it.
97             new Y.DD.Drop({
98                 node: blockregionnode.one('div.'+CSS.REGIONCONTENT),
99                 groups: this.groups,
100                 padding: '40 240 40 240'
101             });
103             // Make each div element in the list of blocks draggable
104             var del = new Y.DD.Delegate({
105                 container: blockregionnode,
106                 nodes: '.'+CSS.BLOCK,
107                 target: true,
108                 handles: [SELECTOR.DRAGHANDLE],
109                 invalid: '.block-hider-hide, .block-hider-show, .moveto',
110                 dragConfig: {groups: this.groups}
111             });
112             del.dd.plug(Y.Plugin.DDProxy, {
113                 // Don't move the node at the end of the drag
114                 moveOnEnd: false
115             });
116             del.dd.plug(Y.Plugin.DDWinScroll);
118             var blocklist = blockregionnode.all('.'+CSS.BLOCK);
119             blocklist.each(function(blocknode) {
120                 var move = blocknode.one('a.'+CSS.EDITINGMOVE);
121                 if (move) {
122                     move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'iconsmall', true));
123                     blocknode.one(SELECTOR.DRAGHANDLE).setStyle('cursor', 'move');
124                 }
125             }, this);
126         }, this);
127     },
129     get_block_id : function(node) {
130         return Number(node.get('id').replace(/inst/i, ''));
131     },
133     get_block_region : function(node) {
134         var region = node.ancestor('div.'+CSS.BLOCKREGION).get('id').replace(/region-/i, '');
135         if (Y.Array.indexOf(this.get('regions'), region) === -1) {
136             // Must be standard side-X
137             if (right_to_left()) {
138                 if (region === 'post') {
139                     region = 'pre';
140                 } else if (region === 'pre') {
141                     region = 'post';
142                 }
143             }
144             return 'side-' + region;
145         }
146         // Perhaps custom region
147         return region;
148     },
150     get_region_id : function(node) {
151         return node.get('id').replace(/region-/i, '');
152     },
154     drag_start : function(e) {
155         // Get our drag object
156         var drag = e.target;
158         // Store the parent node of original drag node (block)
159         // we will need it later for show/hide empty regions
160         this.dragsourceregion = drag.get('node').ancestor('div.'+CSS.BLOCKREGION);
162         // Determine skipnodes and store them
163         if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
164             this.skipnodetop = drag.get('node').previous();
165         }
166         if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
167             this.skipnodebottom = drag.get('node').next();
168         }
169     },
171     drop_over : function(e) {
172         // Get a reference to our drag and drop nodes
173         var drag = e.drag.get('node');
174         var drop = e.drop.get('node');
176         // We need to fix the case when parent drop over event has determined
177         // 'goingup' and appended the drag node after admin-block.
178         if (drop.hasClass(this.parentnodeclass) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) {
179             drop.prepend(drag);
180         }
182         // Block is moved within the same region
183         // stop here, no need to modify anything.
184         if (this.dragsourceregion.contains(drop)) {
185             return false;
186         }
188         // TODO: Hiding-displaying block region only works for base theme blocks
189         // (region-pre, region-post) at the moment. It should be improved
190         // to work with custom block regions as well.
192         // TODO: Fix this for the case when user drag block towards empty section,
193         // then the section appears, then user chnages his mind and moving back to
194         // original section. The opposite section remains opened and empty.
196         var documentbody = Y.one('body');
197         // Moving block towards hidden region-content, display it
198         var regionname = this.get_region_id(this.dragsourceregion);
199         if (documentbody.hasClass('side-'+regionname+'-only')) {
200             documentbody.removeClass('side-'+regionname+'-only');
201         }
203         // Moving from empty region-content towards the opposite one,
204         // hide empty one (only for region-pre, region-post areas at the moment).
205         regionname = this.get_region_id(drop.ancestor('div.'+CSS.BLOCKREGION));
206         if (this.dragsourceregion.all('.'+CSS.BLOCK).size() === 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) {
207             if (!documentbody.hasClass('side-'+regionname+'-only')) {
208                 documentbody.addClass('side-'+regionname+'-only');
209             }
210         }
211     },
213     drop_end : function() {
214         // clear variables
215         this.skipnodetop = null;
216         this.skipnodebottom = null;
217         this.dragsourceregion = null;
218     },
220     drag_dropmiss : function(e) {
221         // Missed the target, but we assume the user intended to drop it
222         // on the last last ghost node location, e.drag and e.drop should be
223         // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
224         this.drop_hit(e);
225     },
227     drop_hit : function(e) {
228         var drag = e.drag;
229         // Get a reference to our drag node
230         var dragnode = drag.get('node');
231         var dropnode = e.drop.get('node');
233         // Amend existing skipnodes
234         if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
235             // the one that belongs to block below move below
236             dragnode.insert(dragnode.previous(), 'after');
237         }
238         // Move original skipnodes
239         if (this.skipnodetop) {
240             dragnode.insert(this.skipnodetop, 'before');
241         }
242         if (this.skipnodebottom) {
243             dragnode.insert(this.skipnodebottom, 'after');
244         }
246         // Add lightbox if it not there
247         var lightbox = M.util.add_lightbox(Y, dragnode);
249         // Prepare request parameters
250         var params = {
251             sesskey : M.cfg.sesskey,
252             courseid : this.get('courseid'),
253             pagelayout : this.get('pagelayout'),
254             pagetype : this.get('pagetype'),
255             subpage : this.get('subpage'),
256             contextid : this.get('contextid'),
257             action : 'move',
258             bui_moveid : this.get_block_id(dragnode),
259             bui_newregion : this.get_block_region(dropnode)
260         };
262         if (this.get('cmid')) {
263             params.cmid = this.get('cmid');
264         }
266         if (dragnode.next('.'+this.samenodeclass) && !dragnode.next('.'+this.samenodeclass).hasClass(CSS.BLOCKADMINBLOCK)) {
267             params.bui_beforeid = this.get_block_id(dragnode.next('.'+this.samenodeclass));
268         }
270         // Do AJAX request
271         Y.io(M.cfg.wwwroot+AJAXURL, {
272             method: 'POST',
273             data: params,
274             on: {
275                 start : function() {
276                     lightbox.show();
277                 },
278                 success: function(tid, response) {
279                     window.setTimeout(function() {
280                         lightbox.hide();
281                     }, 250);
282                     try {
283                         var responsetext = Y.JSON.parse(response.responseText);
284                         if (responsetext.error) {
285                             new M.core.ajaxException(responsetext);
286                         }
287                     } catch (e) {}
288                 },
289                 failure: function(tid, response) {
290                     this.ajax_failure(response);
291                     lightbox.hide();
292                 }
293             },
294             context:this
295         });
296     }
297 }, {
298     NAME : 'core-blocks-dragdrop',
299     ATTRS : {
300         courseid : {
301             value : null
302         },
303         cmid : {
304             value : null
305         },
306         contextid : {
307             value : null
308         },
309         pagelayout : {
310             value : null
311         },
312         pagetype : {
313             value : null
314         },
315         subpage : {
316             value : null
317         },
318         regions : {
319             value : null
320         }
321     }
322 });
324 M.core = M.core || {};
325 M.core.blockdraganddrop = M.core.blockdraganddrop || {};
327 /**
328  * True if the page is using the new blocks methods.
329  * @private
330  * @static
331  * @property M.core.blockdraganddrop._isusingnewblocksmethod
332  * @type Boolean
333  * @default null
334  */
335 M.core.blockdraganddrop._isusingnewblocksmethod = null;
337 /**
338  * Returns true if the page is using the new blocks methods.
339  * @static
340  * @method M.core.blockdraganddrop.is_using_blocks_render_method
341  * @return Boolean
342  */
343 M.core.blockdraganddrop.is_using_blocks_render_method = function() {
344     if (this._isusingnewblocksmethod === null) {
345         var goodregions = Y.all('.block-region[data-blockregion]').size();
346         var allregions = Y.all('.block-region').size();
347         this._isusingnewblocksmethod = (allregions === goodregions);
348         if (goodregions > 0 && allregions > 0) {
349         }
350     }
351     return this._isusingnewblocksmethod;
352 };
354 /**
355  * Initialises a drag and drop manager.
356  * This should only ever be called once for a page.
357  * @static
358  * @method M.core.blockdraganddrop.init
359  * @param {Object} params
360  * @return Manager
361  */
362 M.core.blockdraganddrop.init = function(params) {
363     if (this.is_using_blocks_render_method()) {
364         new MANAGER(params);
365     } else {
366         new DRAGBLOCK(params);
367     }
368 };
370 /*
371  * Legacy code to keep things working.
372  */
373 M.core_blocks = M.core_blocks || {};
374 M.core_blocks.init_dragdrop = function(params) {
375     M.core.blockdraganddrop.init(params);
376 };
377 /**
378  * This file contains the drag and drop manager class.
379  *
380  * Provides drag and drop functionality for blocks.
381  *
382  * @module moodle-core-blockdraganddrop
383  */
385 /**
386  * Constructs a new Block drag and drop manager.
387  *
388  * @namespace M.core.blockdraganddrop
389  * @class Manager
390  * @constructor
391  * @extends M.core.dragdrop
392  */
393 var MANAGER = function() {
394     MANAGER.superclass.constructor.apply(this, arguments);
395 };
396 MANAGER.prototype = {
398     /**
399      * The skip block link from above the block being dragged while a drag is in progress.
400      * Required by the M.core.dragdrop from whom this class extends.
401      * @private
402      * @property skipnodetop
403      * @type Node
404      * @default null
405      */
406     skipnodetop : null,
408     /**
409      * The skip block link from below the block being dragged while a drag is in progress.
410      * Required by the M.core.dragdrop from whom this class extends.
411      * @private
412      * @property skipnodebottom
413      * @type Node
414      * @default null
415      */
416     skipnodebottom : null,
418     /**
419      * An associative object of regions and the
420      * @property regionobjects
421      * @type {Object} Primitive object mocking an associative array.
422      * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being
423      *      an instance of the BLOCKREGION class.
424      */
425     regionobjects : {},
427     /**
428      * Called during the initialisation process of the object.
429      * @method initializer
430      */
431     initializer : function() {
432         var regionnames = this.get('regions'),
433             i = 0,
434             region,
435             regionname,
436             droptarget,
437             dragdelegation;
439         // Evil required by M.core.dragdrop.
440         this.groups = ['block'];
441         this.samenodeclass = CSS.BLOCK;
442         this.parentnodeclass = CSS.BLOCKREGION;
444         // Add relevant classes and ID to 'content' block region on My Home page.
445         var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT);
446         if (myhomecontent.size() > 0) {
447             var contentregion = myhomecontent.item(0);
448             contentregion.addClass(CSS.BLOCKREGION);
449             contentregion.set('id', CSS.REGIONCONTENT);
450             contentregion.one('div').addClass(CSS.REGIONCONTENT);
451         }
453         for (i in regionnames) {
454             regionname = regionnames[i];
455             region = new BLOCKREGION({
456                 manager : this,
457                 region : regionname,
458                 node : Y.one('#block-region-'+regionname)
459             });
460             this.regionobjects[regionname] = region;
462             // Setting blockregion as droptarget (the case when it is empty)
463             // The region-post (the right one)
464             // is very narrow, so add extra padding on the left to drop block on it.
465             droptarget = new Y.DD.Drop({
466                 node: region.get_droptarget(),
467                 groups: this.groups,
468                 padding: '40 240 40 240'
469             });
471             // Make each div element in the list of blocks draggable
472             dragdelegation = new Y.DD.Delegate({
473                 container: region.get_droptarget(),
474                 nodes: '.'+CSS.BLOCK,
475                 target: true,
476                 handles: [SELECTOR.DRAGHANDLE],
477                 invalid: '.block-hider-hide, .block-hider-show, .moveto',
478                 dragConfig: {groups: this.groups}
479             });
480             dragdelegation.dd.plug(Y.Plugin.DDProxy, {
481                 // Don't move the node at the end of the drag
482                 moveOnEnd: false
483             });
484             dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
485             // On the mouse down event we will enable all block regions so that they can be dragged to.
486             // This is VERY important as without it dnd won't work for empty block regions.
487             dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
489             region.change_block_move_icons(this);
490         }
491     },
493     /**
494      * Returns the ID of the block the given node represents.
495      * @method get_block_id
496      * @param {Node} node
497      * @return {int} The blocks ID in the database.
498      */
499     get_block_id : function(node) {
500         return Number(node.get('id').replace(/inst/i, ''));
501     },
503     /**
504      * Returns the block region that the node is part of or belonging to.
505      * @method get_block_region
506      * @param {Y.Node} node
507      * @return {string} The region name.
508      */
509     get_block_region : function(node) {
510         if (!node.test('[data-blockregion]')) {
511             node = node.ancestor('[data-blockregion]');
512         }
513         return node.getData('blockregion');
514     },
516     /**
517      * Returns the BLOCKREGION instance that represents the block region the given node is part of.
518      * @method get_region_object
519      * @param {Y.Node} node
520      * @return {BLOCKREGION}
521      */
522     get_region_object : function(node) {
523         return this.regionobjects[this.get_block_region(node)];
524     },
526     /**
527      * Enables all fo the regions so that they are all visible while dragging is occuring.
528      * @method enable_all_regions
529      */
530     enable_all_regions : function() {
531         var i = 0;
532         for (i in this.regionobjects) {
533             this.regionobjects[i].enable();
534         }
535     },
537     /**
538      * Disables enabled regions if they contain no blocks.
539      * @method disable_regions_if_required
540      */
541     disable_regions_if_required : function() {
542         var i = 0;
543         for (i in this.regionobjects) {
544             this.regionobjects[i].disable_if_required();
545         }
546     },
548     /**
549      * Called by M.core.dragdrop.global_drag_start when dragging starts.
550      * @method drag_start
551      * @param {Event} e
552      */
553     drag_start : function(e) {
554         // Get our drag object
555         var drag = e.target;
557         // Store the parent node of original drag node (block)
558         // we will need it later for show/hide empty regions
560         // Determine skipnodes and store them
561         if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
562             this.skipnodetop = drag.get('node').previous();
563         }
564         if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
565             this.skipnodebottom = drag.get('node').next();
566         }
567     },
569     /**
570      * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target.
571      * @method drop_over
572      * @param {Event} e
573      */
574     drop_over : function(e) {
575         // Get a reference to our drag and drop nodes
576         var drag = e.drag.get('node');
577         var drop = e.drop.get('node');
579         // We need to fix the case when parent drop over event has determined
580         // 'goingup' and appended the drag node after admin-block.
581         if (drop.hasClass(CSS.REGIONCONTENT) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) {
582             drop.prepend(drag);
583         }
584     },
586     /**
587      * Called by M.core.dragdrop.global_drop_end when a drop has been completed.
588      * @method drop_end
589      */
590     drop_end : function() {
591         // Clear variables.
592         this.skipnodetop = null;
593         this.skipnodebottom = null;
594         this.disable_regions_if_required();
595     },
597     /**
598      * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by a drop target.
599      * @method drag_dropmiss
600      * @param {Event} e
601      */
602     drag_dropmiss : function(e) {
603         // Missed the target, but we assume the user intended to drop it
604         // on the last ghost node location, e.drag and e.drop should be
605         // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
606         this.drop_hit(e);
607     },
609     /**
610      * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target.
611      * @method drop_hit
612      * @param {Event} e
613      */
614     drop_hit : function(e) {
615         // Get a reference to our drag node
616         var dragnode = e.drag.get('node');
617         var dropnode = e.drop.get('node');
619         // Amend existing skipnodes
620         if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
621             // the one that belongs to block below move below
622             dragnode.insert(dragnode.previous(), 'after');
623         }
624         // Move original skipnodes
625         if (this.skipnodetop) {
626             dragnode.insert(this.skipnodetop, 'before');
627         }
628         if (this.skipnodebottom) {
629             dragnode.insert(this.skipnodebottom, 'after');
630         }
632         // Add lightbox if it not there
633         var lightbox = M.util.add_lightbox(Y, dragnode);
635         // Prepare request parameters
636         var params = {
637             sesskey : M.cfg.sesskey,
638             courseid : this.get('courseid'),
639             pagelayout : this.get('pagelayout'),
640             pagetype : this.get('pagetype'),
641             subpage : this.get('subpage'),
642             contextid : this.get('contextid'),
643             action : 'move',
644             bui_moveid : this.get_block_id(dragnode),
645             bui_newregion : this.get_block_region(dropnode)
646         };
648         if (this.get('cmid')) {
649             params.cmid = this.get('cmid');
650         }
652         if (dragnode.next('.'+CSS.BLOCK) && !dragnode.next('.'+CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) {
653             params.bui_beforeid = this.get_block_id(dragnode.next('.'+CSS.BLOCK));
654         }
656         // Do AJAX request
657         Y.io(M.cfg.wwwroot+AJAXURL, {
658             method: 'POST',
659             data: params,
660             on: {
661                 start : function() {
662                     lightbox.show();
663                 },
664                 success: function(tid, response) {
665                     window.setTimeout(function() {
666                         lightbox.hide();
667                     }, 250);
668                     try {
669                         var responsetext = Y.JSON.parse(response.responseText);
670                         if (responsetext.error) {
671                             new M.core.ajaxException(responsetext);
672                         }
673                     } catch (e) {}
674                 },
675                 failure: function(tid, response) {
676                     this.ajax_failure(response);
677                     lightbox.hide();
678                 },
679                 complete : function() {
680                     this.disable_regions_if_required();
681                 }
682             },
683             context:this
684         });
685     }
686 };
687 Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
688     NAME : 'core-blocks-dragdrop-manager',
689     ATTRS : {
690         /**
691          * The Course ID if there is one.
692          * @attribute courseid
693          * @type int|null
694          * @default null
695          */
696         courseid : {
697             value : null
698         },
700         /**
701          * The Course Module ID if there is one.
702          * @attribute cmid
703          * @type int|null
704          * @default null
705          */
706         cmid : {
707             value : null
708         },
710         /**
711          * The Context ID.
712          * @attribute contextid
713          * @type int|null
714          * @default null
715          */
716         contextid : {
717             value : null
718         },
720         /**
721          * The current page layout.
722          * @attribute pagelayout
723          * @type string|null
724          * @default null
725          */
726         pagelayout : {
727             value : null
728         },
730         /**
731          * The page type string, should be used as the id for the body tag in the theme.
732          * @attribute pagetype
733          * @type string|null
734          * @default null
735          */
736         pagetype : {
737             value : null
738         },
740         /**
741          * The subpage identifier, if any.
742          * @attribute subpage
743          * @type string|null
744          * @default null
745          */
746         subpage : {
747             value : null
748         },
750         /**
751          * An array of block regions that are present on the page.
752          * @attribute regions
753          * @type array|null
754          * @default Array[]
755          */
756         regions : {
757             value : []
758         }
759     }
760 });
761 /**
762  * This file contains the Block Region class used by the drag and drop manager.
763  *
764  * Provides drag and drop functionality for blocks.
765  *
766  * @module moodle-core-blockdraganddrop
767  */
769 /**
770  * Constructs a new block region object.
771  *
772  * @namespace M.core.blockdraganddrop
773  * @class BlockRegion
774  * @constructor
775  * @extends Base
776  */
777 var BLOCKREGION = function() {
778     BLOCKREGION.superclass.constructor.apply(this, arguments);
779 };
780 BLOCKREGION.prototype = {
781     /**
782      * Called during the initialisation process of the object.
783      * @method initializer
784      */
785     initializer : function() {
786         var node = this.get('node');
787         if (!node) {
788             node = this.create_and_add_node();
789         }
790         var body = Y.one('body'),
791             hasblocks = node.all('.'+CSS.BLOCK).size() > 0,
792             hasregionclass = this.get_has_region_class();
793         this.set('hasblocks', hasblocks);
794         if (!body.hasClass(hasregionclass)) {
795             body.addClass(hasregionclass);
796         }
797         body.addClass((hasblocks) ? this.get_used_region_class() : this.get_empty_region_class());
798         body.removeClass((hasblocks) ? this.get_empty_region_class() : this.get_used_region_class());
799     },
800     /**
801      * Creates a generic block region node and adds it to the DOM at the best guess location.
802      * Any calling of this method is an unfortunate circumstance.
803      * @method create_and_add_node
804      * @return Node The newly created Node
805      */
806     create_and_add_node : function() {
807         var c = Y.Node.create,
808             region = this.get('region'),
809             node = c('<div id="block-region-'+region+'" data-droptarget="1"></div>')
810                 .addClass(CSS.BLOCKREGION)
811                 .setData('blockregion', region),
812             regions = this.get('manager').get('regions'),
813             i,
814             haspre = false,
815             haspost = false,
816             added = false,
817             pre,
818             post;
820         for (i in regions) {
821             if (regions[i].match(/(pre|left)/)) {
822                 haspre = regions[i];
823             } else if (regions[i].match(/(post|right)/)) {
824                 haspost = regions[i];
825             }
826         }
828         if (haspre !== false && haspost !== false) {
829             if (region === haspre) {
830                 post = Y.one('#block-region-'+haspost);
831                 if (post) {
832                     post.insert(node, 'before');
833                     added = true;
834                 }
835             } else {
836                 pre = Y.one('#block-region-'+haspre);
837                 if (pre) {
838                     pre.insert(node, 'after');
839                     added = true;
840                 }
841             }
842         }
843         if (added === false) {
844             Y.one('body').append(node);
845         }
846         this.set('node', node);
848         return node;
849     },
851     /**
852      * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header.
853      * @param M.core.dragdrop the block manager
854      * @method change_block_move_icons
855      */
856     change_block_move_icons : function(manager) {
857         var handle, icon;
858         this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){
859             moveicon.setStyle('cursor', 'move');
860             handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
861             icon = handle.one('img');
862             icon.addClass('iconsmall');
863             icon.removeClass('icon');
864             moveicon.replace(handle);
865         });
866     },
868     /**
869      * Returns the class name on the body that signifies the document knows about this region.
870      * @method get_has_region_class
871      * @return String
872      */
873     get_has_region_class : function() {
874         return 'has-region-'+this.get('region');
875     },
877     /**
878      * Returns the class name to use on the body if the region contains no blocks.
879      * @method get_empty_region_class
880      * @return String
881      */
882     get_empty_region_class : function() {
883         return 'empty-region-'+this.get('region');
884     },
886     /**
887      * Returns the class name to use on the body if the region contains blocks.
888      * @method get_used_region_class
889      * @return String
890      */
891     get_used_region_class : function() {
892         return 'used-region-'+this.get('region');
893     },
895     /**
896      * Returns the node to use as the drop target for this region.
897      * @method get_droptarget
898      * @return Node
899      */
900     get_droptarget : function() {
901         var node = this.get('node');
902         if (node.test('[data-droptarget="1"]')) {
903             return node;
904         }
905         return node.one('[data-droptarget="1"]');
906     },
908     /**
909      * Enables the block region so that we can be sure the user can see it.
910      * This is done even if it is empty.
911      * @method enable
912      */
913     enable : function() {
914         Y.one('body').addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class());
915     },
917     /**
918      * Disables the region if it contains no blocks, essentially hiding it from the user.
919      * @method disable_if_required
920      */
921     disable_if_required : function() {
922         if (this.get('node').all('.'+CSS.BLOCK).size() === 0) {
923             Y.one('body').addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class());
924         }
925     }
926 };
927 Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, {
928     NAME : 'core-blocks-dragdrop-blockregion',
929     ATTRS : {
931         /**
932          * The drag and drop manager that created this block region instance.
933          * @attribute manager
934          * @type M.core.blockdraganddrop.Manager
935          * @writeOnce
936          */
937         manager : {
938             // Can only be set during initialisation and must be set then.
939             writeOnce : 'initOnly',
940             validator : function (value) {
941                 return Y.Lang.isObject(value) && value instanceof MANAGER;
942             }
943         },
945         /**
946          * The name of the block region this object represents.
947          * @attribute region
948          * @type String
949          * @writeOnce
950          */
951         region : {
952             // Can only be set during initialisation and must be set then.
953             writeOnce : 'initOnly',
954             validator : function (value) {
955                 return Y.Lang.isString(value);
956             }
957         },
959         /**
960          * The node the block region HTML starts at.s
961          * @attribute region
962          * @type Y.Node
963          */
964         node : {
965             validator : function (value) {
966                 return Y.Lang.isObject(value) || Y.Lang.isNull(value);
967             }
968         },
970         /**
971          * True if the block region currently contains blocks.
972          * @attribute hasblocks
973          * @type Boolean
974          * @default false
975          */
976         hasblocks : {
977             value : false,
978             validator : function (value) {
979                 return Y.Lang.isBoolean(value);
980             }
981         }
982     }
983 });
986 }, '@VERSION@', {
987     "requires": [
988         "base",
989         "node",
990         "io",
991         "dom",
992         "dd",
993         "dd-scroll",
994         "moodle-core-dragdrop",
995         "moodle-core-notification"
996     ]
997 });