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