Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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