MDL-41511 blocks: improved how custom block regions were being rendered.
[moodle.git] / lib / yui / build / moodle-core-blocks / moodle-core-blocks-debug.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             Y.log('Both core_renderer::blocks and core_renderer::blocks_for_region have been used.', 'warn', 'moodle-core_blocks');
350         }
351     }
352     return this._isusingnewblocksmethod;
353 };
355 /**
356  * Initialises a drag and drop manager.
357  * This should only ever be called once for a page.
358  * @static
359  * @method M.core.blockdraganddrop.init
360  * @param {Object} params
361  * @return Manager
362  */
363 M.core.blockdraganddrop.init = function(params) {
364     if (this.is_using_blocks_render_method()) {
365         Y.log('Block drag and drop initialised for the blocks method.', 'info', 'moodle-core_blocks');
366         new MANAGER(params);
367     } else {
368         Y.log('Block drag and drop initialised with the legacy manager (blocks_for_region used).', 'info', 'moodle-core_blocks');
369         new DRAGBLOCK(params);
370     }
371 };
373 /*
374  * Legacy code to keep things working.
375  */
376 M.core_blocks = M.core_blocks || {};
377 M.core_blocks.init_dragdrop = function(params) {
378     M.core.blockdraganddrop.init(params);
379 };
380 /**
381  * This file contains the drag and drop manager class.
382  *
383  * Provides drag and drop functionality for blocks.
384  *
385  * @module moodle-core-blockdraganddrop
386  */
388 /**
389  * Constructs a new Block drag and drop manager.
390  *
391  * @namespace M.core.blockdraganddrop
392  * @class Manager
393  * @constructor
394  * @extends M.core.dragdrop
395  */
396 var MANAGER = function() {
397     MANAGER.superclass.constructor.apply(this, arguments);
398 };
399 MANAGER.prototype = {
401     /**
402      * The skip block link from above the block being dragged while a drag is in progress.
403      * Required by the M.core.dragdrop from whom this class extends.
404      * @private
405      * @property skipnodetop
406      * @type Node
407      * @default null
408      */
409     skipnodetop : null,
411     /**
412      * The skip block link from below the block being dragged while a drag is in progress.
413      * Required by the M.core.dragdrop from whom this class extends.
414      * @private
415      * @property skipnodebottom
416      * @type Node
417      * @default null
418      */
419     skipnodebottom : null,
421     /**
422      * An associative object of regions and the
423      * @property regionobjects
424      * @type {Object} Primitive object mocking an associative array.
425      * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being
426      *      an instance of the BLOCKREGION class.
427      */
428     regionobjects : {},
430     /**
431      * Called during the initialisation process of the object.
432      * @method initializer
433      */
434     initializer : function() {
435         Y.log('Initialising drag and drop for blocks.', 'info');
436         var regionnames = this.get('regions'),
437             i = 0,
438             region,
439             regionname,
440             droptarget,
441             dragdelegation;
443         // Evil required by M.core.dragdrop.
444         this.groups = ['block'];
445         this.samenodeclass = CSS.BLOCK;
446         this.parentnodeclass = CSS.BLOCKREGION;
448         // Add relevant classes and ID to 'content' block region on My Home page.
449         var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT);
450         if (myhomecontent.size() > 0) {
451             var contentregion = myhomecontent.item(0);
452             contentregion.addClass(CSS.BLOCKREGION);
453             contentregion.set('id', CSS.REGIONCONTENT);
454             contentregion.one('div').addClass(CSS.REGIONCONTENT);
455         }
457         for (i in regionnames) {
458             regionname = regionnames[i];
459             region = new BLOCKREGION({
460                 manager : this,
461                 region : regionname,
462                 node : Y.one('#block-region-'+regionname)
463             });
464             this.regionobjects[regionname] = region;
466             // Setting blockregion as droptarget (the case when it is empty)
467             // The region-post (the right one)
468             // is very narrow, so add extra padding on the left to drop block on it.
469             droptarget = new Y.DD.Drop({
470                 node: region.get_droptarget(),
471                 groups: this.groups,
472                 padding: '40 240 40 240'
473             });
475             // Make each div element in the list of blocks draggable
476             dragdelegation = new Y.DD.Delegate({
477                 container: region.get_droptarget(),
478                 nodes: '.'+CSS.BLOCK,
479                 target: true,
480                 handles: [SELECTOR.DRAGHANDLE],
481                 invalid: '.block-hider-hide, .block-hider-show, .moveto',
482                 dragConfig: {groups: this.groups}
483             });
484             dragdelegation.dd.plug(Y.Plugin.DDProxy, {
485                 // Don't move the node at the end of the drag
486                 moveOnEnd: false
487             });
488             dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
489             // On the mouse down event we will enable all block regions so that they can be dragged to.
490             // This is VERY important as without it dnd won't work for empty block regions.
491             dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
493             region.change_block_move_icons(this);
494         }
495         Y.log('Initialisation of drag and drop for blocks complete.', 'info');
496     },
498     /**
499      * Returns the ID of the block the given node represents.
500      * @method get_block_id
501      * @param {Node} node
502      * @return {int} The blocks ID in the database.
503      */
504     get_block_id : function(node) {
505         return Number(node.get('id').replace(/inst/i, ''));
506     },
508     /**
509      * Returns the block region that the node is part of or belonging to.
510      * @method get_block_region
511      * @param {Y.Node} node
512      * @return {string} The region name.
513      */
514     get_block_region : function(node) {
515         if (!node.test('[data-blockregion]')) {
516             node = node.ancestor('[data-blockregion]');
517         }
518         return node.getData('blockregion');
519     },
521     /**
522      * Returns the BLOCKREGION instance that represents the block region the given node is part of.
523      * @method get_region_object
524      * @param {Y.Node} node
525      * @return {BLOCKREGION}
526      */
527     get_region_object : function(node) {
528         return this.regionobjects[this.get_block_region(node)];
529     },
531     /**
532      * Enables all fo the regions so that they are all visible while dragging is occuring.
533      * @method enable_all_regions
534      */
535     enable_all_regions : function() {
536         var i = 0;
537         for (i in this.regionobjects) {
538             this.regionobjects[i].enable();
539         }
540     },
542     /**
543      * Disables enabled regions if they contain no blocks.
544      * @method disable_regions_if_required
545      */
546     disable_regions_if_required : function() {
547         var i = 0;
548         for (i in this.regionobjects) {
549             this.regionobjects[i].disable_if_required();
550         }
551     },
553     /**
554      * Called by M.core.dragdrop.global_drag_start when dragging starts.
555      * @method drag_start
556      * @param {Event} e
557      */
558     drag_start : function(e) {
559         // Get our drag object
560         var drag = e.target;
562         // Store the parent node of original drag node (block)
563         // we will need it later for show/hide empty regions
565         // Determine skipnodes and store them
566         if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
567             this.skipnodetop = drag.get('node').previous();
568         }
569         if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
570             this.skipnodebottom = drag.get('node').next();
571         }
572     },
574     /**
575      * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target.
576      * @method drop_over
577      * @param {Event} e
578      */
579     drop_over : function(e) {
580         // Get a reference to our drag and drop nodes
581         var drag = e.drag.get('node');
582         var drop = e.drop.get('node');
584         // We need to fix the case when parent drop over event has determined
585         // 'goingup' and appended the drag node after admin-block.
586         if (drop.hasClass(CSS.REGIONCONTENT) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) {
587             drop.prepend(drag);
588         }
589     },
591     /**
592      * Called by M.core.dragdrop.global_drop_end when a drop has been completed.
593      * @method drop_end
594      */
595     drop_end : function() {
596         // Clear variables.
597         this.skipnodetop = null;
598         this.skipnodebottom = null;
599         this.disable_regions_if_required();
600     },
602     /**
603      * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by a drop target.
604      * @method drag_dropmiss
605      * @param {Event} e
606      */
607     drag_dropmiss : function(e) {
608         // Missed the target, but we assume the user intended to drop it
609         // on the last ghost node location, e.drag and e.drop should be
610         // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
611         this.drop_hit(e);
612     },
614     /**
615      * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target.
616      * @method drop_hit
617      * @param {Event} e
618      */
619     drop_hit : function(e) {
620         // Get a reference to our drag node
621         var dragnode = e.drag.get('node');
622         var dropnode = e.drop.get('node');
624         // Amend existing skipnodes
625         if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
626             // the one that belongs to block below move below
627             dragnode.insert(dragnode.previous(), 'after');
628         }
629         // Move original skipnodes
630         if (this.skipnodetop) {
631             dragnode.insert(this.skipnodetop, 'before');
632         }
633         if (this.skipnodebottom) {
634             dragnode.insert(this.skipnodebottom, 'after');
635         }
637         // Add lightbox if it not there
638         var lightbox = M.util.add_lightbox(Y, dragnode);
640         // Prepare request parameters
641         var params = {
642             sesskey : M.cfg.sesskey,
643             courseid : this.get('courseid'),
644             pagelayout : this.get('pagelayout'),
645             pagetype : this.get('pagetype'),
646             subpage : this.get('subpage'),
647             contextid : this.get('contextid'),
648             action : 'move',
649             bui_moveid : this.get_block_id(dragnode),
650             bui_newregion : this.get_block_region(dropnode)
651         };
653         if (this.get('cmid')) {
654             params.cmid = this.get('cmid');
655         }
657         if (dragnode.next('.'+CSS.BLOCK) && !dragnode.next('.'+CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) {
658             params.bui_beforeid = this.get_block_id(dragnode.next('.'+CSS.BLOCK));
659         }
661         // Do AJAX request
662         Y.io(M.cfg.wwwroot+AJAXURL, {
663             method: 'POST',
664             data: params,
665             on: {
666                 start : function() {
667                     lightbox.show();
668                 },
669                 success: function(tid, response) {
670                     window.setTimeout(function() {
671                         lightbox.hide();
672                     }, 250);
673                     try {
674                         var responsetext = Y.JSON.parse(response.responseText);
675                         if (responsetext.error) {
676                             new M.core.ajaxException(responsetext);
677                         }
678                     } catch (e) {}
679                 },
680                 failure: function(tid, response) {
681                     this.ajax_failure(response);
682                     lightbox.hide();
683                 },
684                 complete : function() {
685                     this.disable_regions_if_required();
686                 }
687             },
688             context:this
689         });
690     }
691 };
692 Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
693     NAME : 'core-blocks-dragdrop-manager',
694     ATTRS : {
695         /**
696          * The Course ID if there is one.
697          * @attribute courseid
698          * @type int|null
699          * @default null
700          */
701         courseid : {
702             value : null
703         },
705         /**
706          * The Course Module ID if there is one.
707          * @attribute cmid
708          * @type int|null
709          * @default null
710          */
711         cmid : {
712             value : null
713         },
715         /**
716          * The Context ID.
717          * @attribute contextid
718          * @type int|null
719          * @default null
720          */
721         contextid : {
722             value : null
723         },
725         /**
726          * The current page layout.
727          * @attribute pagelayout
728          * @type string|null
729          * @default null
730          */
731         pagelayout : {
732             value : null
733         },
735         /**
736          * The page type string, should be used as the id for the body tag in the theme.
737          * @attribute pagetype
738          * @type string|null
739          * @default null
740          */
741         pagetype : {
742             value : null
743         },
745         /**
746          * The subpage identifier, if any.
747          * @attribute subpage
748          * @type string|null
749          * @default null
750          */
751         subpage : {
752             value : null
753         },
755         /**
756          * An array of block regions that are present on the page.
757          * @attribute regions
758          * @type array|null
759          * @default Array[]
760          */
761         regions : {
762             value : []
763         }
764     }
765 });
766 /**
767  * This file contains the Block Region class used by the drag and drop manager.
768  *
769  * Provides drag and drop functionality for blocks.
770  *
771  * @module moodle-core-blockdraganddrop
772  */
774 /**
775  * Constructs a new block region object.
776  *
777  * @namespace M.core.blockdraganddrop
778  * @class BlockRegion
779  * @constructor
780  * @extends Base
781  */
782 var BLOCKREGION = function() {
783     BLOCKREGION.superclass.constructor.apply(this, arguments);
784 };
785 BLOCKREGION.prototype = {
786     /**
787      * Called during the initialisation process of the object.
788      * @method initializer
789      */
790     initializer : function() {
791         var node = this.get('node');
792         Y.log('Block region `'+this.get('region')+'` initialising', 'info');
793         if (!node) {
794             Y.log('block region known about but no HTML structure found for it. Guessing structure.', 'warn');
795             node = this.create_and_add_node();
796         }
797         var body = Y.one('body'),
798             hasblocks = node.all('.'+CSS.BLOCK).size() > 0,
799             hasregionclass = this.get_has_region_class();
800         this.set('hasblocks', hasblocks);
801         if (!body.hasClass(hasregionclass)) {
802             body.addClass(hasregionclass);
803         }
804         body.addClass((hasblocks) ? this.get_used_region_class() : this.get_empty_region_class());
805         body.removeClass((hasblocks) ? this.get_empty_region_class() : this.get_used_region_class());
806     },
807     /**
808      * Creates a generic block region node and adds it to the DOM at the best guess location.
809      * Any calling of this method is an unfortunate circumstance.
810      * @method create_and_add_node
811      * @return Node The newly created Node
812      */
813     create_and_add_node : function() {
814         var c = Y.Node.create,
815             region = this.get('region'),
816             node = c('<div id="block-region-'+region+'" data-droptarget="1"></div>')
817                 .addClass(CSS.BLOCKREGION)
818                 .setData('blockregion', region),
819             regions = this.get('manager').get('regions'),
820             i,
821             haspre = false,
822             haspost = false,
823             added = false,
824             pre,
825             post;
827         for (i in regions) {
828             if (regions[i].match(/(pre|left)/)) {
829                 haspre = regions[i];
830             } else if (regions[i].match(/(post|right)/)) {
831                 haspost = regions[i];
832             }
833         }
835         if (haspre !== false && haspost !== false) {
836             if (region === haspre) {
837                 post = Y.one('#block-region-'+haspost);
838                 if (post) {
839                     post.insert(node, 'before');
840                     added = true;
841                 }
842             } else {
843                 pre = Y.one('#block-region-'+haspre);
844                 if (pre) {
845                     pre.insert(node, 'after');
846                     added = true;
847                 }
848             }
849         }
850         if (added === false) {
851             Y.one('body').append(node);
852         }
853         this.set('node', node);
855         return node;
856     },
858     /**
859      * Change the move icons to enhanced drag handles and changes the cursor to a move icon when over the header.
860      * @param M.core.dragdrop the block manager
861      * @method change_block_move_icons
862      */
863     change_block_move_icons : function(manager) {
864         var handle, icon;
865         this.get('node').all('.'+CSS.BLOCK+' a.'+CSS.EDITINGMOVE).each(function(moveicon){
866             moveicon.setStyle('cursor', 'move');
867             handle = manager.get_drag_handle(moveicon.getAttribute('title'), '', 'icon', true);
868             icon = handle.one('img');
869             icon.addClass('iconsmall');
870             icon.removeClass('icon');
871             moveicon.replace(handle);
872         });
873     },
875     /**
876      * Returns the class name on the body that signifies the document knows about this region.
877      * @method get_has_region_class
878      * @return String
879      */
880     get_has_region_class : function() {
881         return 'has-region-'+this.get('region');
882     },
884     /**
885      * Returns the class name to use on the body if the region contains no blocks.
886      * @method get_empty_region_class
887      * @return String
888      */
889     get_empty_region_class : function() {
890         return 'empty-region-'+this.get('region');
891     },
893     /**
894      * Returns the class name to use on the body if the region contains blocks.
895      * @method get_used_region_class
896      * @return String
897      */
898     get_used_region_class : function() {
899         return 'used-region-'+this.get('region');
900     },
902     /**
903      * Returns the node to use as the drop target for this region.
904      * @method get_droptarget
905      * @return Node
906      */
907     get_droptarget : function() {
908         var node = this.get('node');
909         if (node.test('[data-droptarget="1"]')) {
910             return node;
911         }
912         return node.one('[data-droptarget="1"]');
913     },
915     /**
916      * Enables the block region so that we can be sure the user can see it.
917      * This is done even if it is empty.
918      * @method enable
919      */
920     enable : function() {
921         Y.one('body').addClass(this.get_used_region_class()).removeClass(this.get_empty_region_class());
922     },
924     /**
925      * Disables the region if it contains no blocks, essentially hiding it from the user.
926      * @method disable_if_required
927      */
928     disable_if_required : function() {
929         if (this.get('node').all('.'+CSS.BLOCK).size() === 0) {
930             Y.one('body').addClass(this.get_empty_region_class()).removeClass(this.get_used_region_class());
931         }
932     }
933 };
934 Y.extend(BLOCKREGION, Y.Base, BLOCKREGION.prototype, {
935     NAME : 'core-blocks-dragdrop-blockregion',
936     ATTRS : {
938         /**
939          * The drag and drop manager that created this block region instance.
940          * @attribute manager
941          * @type M.core.blockdraganddrop.Manager
942          * @writeOnce
943          */
944         manager : {
945             // Can only be set during initialisation and must be set then.
946             writeOnce : 'initOnly',
947             validator : function (value) {
948                 return Y.Lang.isObject(value) && value instanceof MANAGER;
949             }
950         },
952         /**
953          * The name of the block region this object represents.
954          * @attribute region
955          * @type String
956          * @writeOnce
957          */
958         region : {
959             // Can only be set during initialisation and must be set then.
960             writeOnce : 'initOnly',
961             validator : function (value) {
962                 return Y.Lang.isString(value);
963             }
964         },
966         /**
967          * The node the block region HTML starts at.s
968          * @attribute region
969          * @type Y.Node
970          */
971         node : {
972             validator : function (value) {
973                 return Y.Lang.isObject(value) || Y.Lang.isNull(value);
974             }
975         },
977         /**
978          * True if the block region currently contains blocks.
979          * @attribute hasblocks
980          * @type Boolean
981          * @default false
982          */
983         hasblocks : {
984             value : false,
985             validator : function (value) {
986                 return Y.Lang.isBoolean(value);
987             }
988         }
989     }
990 });
993 }, '@VERSION@', {
994     "requires": [
995         "base",
996         "node",
997         "io",
998         "dom",
999         "dd",
1000         "dd-scroll",
1001         "moodle-core-dragdrop",
1002         "moodle-core-notification"
1003     ]
1004 });