Merge branch 'MDL-67818-check-api-fixes' of https://github.com/brendanheywood/moodle
[moodle.git] / lib / yui / src / blocks / js / manager.js
1 /* global BLOCKREGION, SELECTOR, AJAXURL */
3 /**
4  * This file contains the drag and drop manager class.
5  *
6  * Provides drag and drop functionality for blocks.
7  *
8  * @module moodle-core-blockdraganddrop
9  */
11 /**
12  * Constructs a new Block drag and drop manager.
13  *
14  * @namespace M.core.blockdraganddrop
15  * @class Manager
16  * @constructor
17  * @extends M.core.dragdrop
18  */
19 var MANAGER = function() {
20     MANAGER.superclass.constructor.apply(this, arguments);
21 };
22 MANAGER.prototype = {
24     /**
25      * The skip block link from above the block being dragged while a drag is in progress.
26      * Required by the M.core.dragdrop from whom this class extends.
27      * @private
28      * @property skipnodetop
29      * @type Node
30      * @default null
31      */
32     skipnodetop: null,
34     /**
35      * The skip block link from below the block being dragged while a drag is in progress.
36      * Required by the M.core.dragdrop from whom this class extends.
37      * @private
38      * @property skipnodebottom
39      * @type Node
40      * @default null
41      */
42     skipnodebottom: null,
44     /**
45      * An associative object of regions and the
46      * @property regionobjects
47      * @type {Object} Primitive object mocking an associative array.
48      * @type {BLOCKREGION} [regionname]* Each item uses the region name as the key with the value being
49      *      an instance of the BLOCKREGION class.
50      */
51     regionobjects: {},
53     /**
54      * Called during the initialisation process of the object.
55      * @method initializer
56      */
57     initializer: function() {
58         Y.log('Initialising drag and drop for blocks.', 'info');
59         var regionnames = this.get('regions'),
60             i = 0,
61             region,
62             regionname,
63             dragdelegation;
65         // Evil required by M.core.dragdrop.
66         this.groups = ['block'];
67         this.samenodeclass = CSS.BLOCK;
68         this.parentnodeclass = CSS.BLOCKREGION;
69         // Detect the direction of travel.
70         this.detectkeyboarddirection = true;
72         // Add relevant classes and ID to 'content' block region on Dashboard page.
73         var myhomecontent = Y.Node.all('body#' + CSS.MYINDEX + ' #' + CSS.REGIONMAIN + ' > .' + CSS.REGIONCONTENT);
74         if (myhomecontent.size() > 0) {
75             var contentregion = myhomecontent.item(0);
76             contentregion.addClass(CSS.BLOCKREGION);
77             contentregion.set('id', CSS.REGIONCONTENT);
78             contentregion.one('div').addClass(CSS.REGIONCONTENT);
79         }
81         for (i in regionnames) {
82             regionname = regionnames[i];
83             region = new BLOCKREGION({
84                 manager: this,
85                 region: regionname,
86                 node: Y.one('#block-region-' + regionname)
87             });
88             this.regionobjects[regionname] = region;
90             // Setting blockregion as droptarget (the case when it is empty)
91             // The region-post (the right one)
92             // is very narrow, so add extra padding on the left to drop block on it.
93             new Y.DD.Drop({
94                 node: region.get_droptarget(),
95                 groups: this.groups,
96                 padding: '40 240 40 240'
97             });
99             // Make each div element in the list of blocks draggable
100             dragdelegation = new Y.DD.Delegate({
101                 container: region.get_droptarget(),
102                 nodes: '.' + CSS.BLOCK,
103                 target: true,
104                 handles: [SELECTOR.DRAGHANDLE],
105                 invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake',
106                 dragConfig: {groups: this.groups}
107             });
108             dragdelegation.dd.plug(Y.Plugin.DDProxy, {
109                 // Don't move the node at the end of the drag
110                 moveOnEnd: false
111             });
112             dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
114             // On the DD Manager start operation, we enable all block regions so that they can be drop targets. This
115             // must be done *before* drag:start but after dragging has been initialised.
116             Y.DD.DDM.on('ddm:start', this.enable_all_regions, this);
118             region.change_block_move_icons(this);
119         }
120         Y.log('Initialisation of drag and drop for blocks complete.', 'info');
121     },
123     /**
124      * Returns the ID of the block the given node represents.
125      * @method get_block_id
126      * @param {Node} node
127      * @return {int} The blocks ID in the database.
128      */
129     get_block_id: function(node) {
130         return Number(node.get('id').replace(/inst/i, ''));
131     },
133     /**
134      * Returns the block region that the node is part of or belonging to.
135      * @method get_block_region
136      * @param {Y.Node} node
137      * @return {string} The region name.
138      */
139     get_block_region: function(node) {
140         if (!node.test('[data-blockregion]')) {
141             node = node.ancestor('[data-blockregion]');
142         }
143         return node.getData('blockregion');
144     },
146     /**
147      * Returns the BLOCKREGION instance that represents the block region the given node is part of.
148      * @method get_region_object
149      * @param {Y.Node} node
150      * @return {BLOCKREGION}
151      */
152     get_region_object: function(node) {
153         return this.regionobjects[this.get_block_region(node)];
154     },
156     /**
157      * Enables all fo the regions so that they are all visible while dragging is occuring.
158      *
159      * @method enable_all_regions
160      */
161     enable_all_regions: function() {
162         var groups = Y.DD.DDM.activeDrag.get('groups');
164         // As we're called by Y.DD.DDM, we can't be certain that the call
165         // relates specifically to a block drag/drop operation. Test
166         // whether the relevant group applies here.
167         if (!groups || Y.Array.indexOf(groups, 'block') === -1) {
168             return;
169         }
171         var i;
172         for (i in this.regionobjects) {
173             if (!this.regionobjects.hasOwnProperty(i)) {
174                 continue;
175             }
176             this.regionobjects[i].enable();
177         }
178     },
180     /**
181      * Disables enabled regions if they contain no blocks.
182      * @method disable_regions_if_required
183      */
184     disable_regions_if_required: function() {
185         var i = 0;
186         for (i in this.regionobjects) {
187             this.regionobjects[i].disable_if_required();
188         }
189     },
191     /**
192      * Called by M.core.dragdrop.global_drag_start when dragging starts.
193      * @method drag_start
194      * @param {Event} e
195      */
196     drag_start: function(e) {
197         // Get our drag object
198         var drag = e.target;
200         // Store the parent node of original drag node (block)
201         // we will need it later for show/hide empty regions
203         // Determine skipnodes and store them
204         if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
205             this.skipnodetop = drag.get('node').previous();
206         }
207         if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
208             this.skipnodebottom = drag.get('node').next();
209         }
210     },
212     /**
213      * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target.
214      * @method drop_over
215      * @param {Event} e
216      */
217     drop_over: function(e) {
218         // Get a reference to our drag and drop nodes
219         var drag = e.drag.get('node');
220         var drop = e.drop.get('node');
222         // We need to fix the case when parent drop over event has determined
223         // 'goingup' and appended the drag node after admin-block.
224         if (drop.hasClass(CSS.REGIONCONTENT) &&
225                 drop.one('.' + CSS.BLOCKADMINBLOCK) &&
226                 drop.one('.' + CSS.BLOCKADMINBLOCK).next('.' + CSS.BLOCK)) {
227             drop.prepend(drag);
228         }
229     },
231     /**
232      * Called by M.core.dragdrop.global_drop_end when a drop has been completed.
233      * @method drop_end
234      */
235     drop_end: function() {
236         // Clear variables.
237         this.skipnodetop = null;
238         this.skipnodebottom = null;
239         this.disable_regions_if_required();
240     },
242     /**
243      * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by
244      * a drop target.
245      *
246      * @method drag_dropmiss
247      * @param {Event} e
248      */
249     drag_dropmiss: function(e) {
250         // Missed the target, but we assume the user intended to drop it
251         // on the last ghost node location, e.drag and e.drop should be
252         // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
253         this.drop_hit(e);
254     },
256     /**
257      * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target.
258      * @method drop_hit
259      * @param {Event} e
260      */
261     drop_hit: function(e) {
262         // Get a reference to our drag node
263         var dragnode = e.drag.get('node');
264         var dropnode = e.drop.get('node');
266         // Amend existing skipnodes
267         if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
268             // the one that belongs to block below move below
269             dragnode.insert(dragnode.previous(), 'after');
270         }
271         // Move original skipnodes
272         if (this.skipnodetop) {
273             dragnode.insert(this.skipnodetop, 'before');
274         }
275         if (this.skipnodebottom) {
276             dragnode.insert(this.skipnodebottom, 'after');
277         }
279         // Add lightbox if it not there
280         var lightbox = M.util.add_lightbox(Y, dragnode);
282         // Prepare request parameters
283         var params = {
284             sesskey: M.cfg.sesskey,
285             courseid: this.get('courseid'),
286             pagelayout: this.get('pagelayout'),
287             pagetype: this.get('pagetype'),
288             subpage: this.get('subpage'),
289             contextid: this.get('contextid'),
290             action: 'move',
291             bui_moveid: this.get_block_id(dragnode),
292             bui_newregion: this.get_block_region(dropnode)
293         };
295         if (this.get('cmid')) {
296             params.cmid = this.get('cmid');
297         }
299         if (dragnode.next('.' + CSS.BLOCK) && !dragnode.next('.' + CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) {
300             params.bui_beforeid = this.get_block_id(dragnode.next('.' + CSS.BLOCK));
301         }
303         // Do AJAX request
304         Y.io(M.cfg.wwwroot + AJAXURL, {
305             method: 'POST',
306             data: params,
307             on: {
308                 start: function() {
309                     lightbox.show();
310                 },
311                 success: function(tid, response) {
312                     window.setTimeout(function() {
313                         lightbox.hide();
314                     }, 250);
315                     try {
316                         var responsetext = Y.JSON.parse(response.responseText);
317                         if (responsetext.error) {
318                             new M.core.ajaxException(responsetext);
319                         }
320                     } catch (e) {
321                         // Ignore.
322                     }
323                 },
324                 failure: function(tid, response) {
325                     this.ajax_failure(response);
326                     lightbox.hide();
327                 },
328                 complete: function() {
329                     this.disable_regions_if_required();
330                 }
331             },
332             context: this
333         });
334     }
335 };
336 Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
337     NAME: 'core-blocks-dragdrop-manager',
338     ATTRS: {
339         /**
340          * The Course ID if there is one.
341          * @attribute courseid
342          * @type int|null
343          * @default null
344          */
345         courseid: {
346             value: null
347         },
349         /**
350          * The Course Module ID if there is one.
351          * @attribute cmid
352          * @type int|null
353          * @default null
354          */
355         cmid: {
356             value: null
357         },
359         /**
360          * The Context ID.
361          * @attribute contextid
362          * @type int|null
363          * @default null
364          */
365         contextid: {
366             value: null
367         },
369         /**
370          * The current page layout.
371          * @attribute pagelayout
372          * @type string|null
373          * @default null
374          */
375         pagelayout: {
376             value: null
377         },
379         /**
380          * The page type string, should be used as the id for the body tag in the theme.
381          * @attribute pagetype
382          * @type string|null
383          * @default null
384          */
385         pagetype: {
386             value: null
387         },
389         /**
390          * The subpage identifier, if any.
391          * @attribute subpage
392          * @type string|null
393          * @default null
394          */
395         subpage: {
396             value: null
397         },
399         /**
400          * An array of block regions that are present on the page.
401          * @attribute regions
402          * @type array|null
403          * @default Array[]
404          */
405         regions: {
406             value: []
407         }
408     }
409 });