MDL-67818 check: Rename renderer to be less generic
[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;
70         // Add relevant classes and ID to 'content' block region on Dashboard page.
71         var myhomecontent = Y.Node.all('body#' + CSS.MYINDEX + ' #' + CSS.REGIONMAIN + ' > .' + CSS.REGIONCONTENT);
72         if (myhomecontent.size() > 0) {
73             var contentregion = myhomecontent.item(0);
74             contentregion.addClass(CSS.BLOCKREGION);
75             contentregion.set('id', CSS.REGIONCONTENT);
76             contentregion.one('div').addClass(CSS.REGIONCONTENT);
77         }
79         for (i in regionnames) {
80             regionname = regionnames[i];
81             region = new BLOCKREGION({
82                 manager: this,
83                 region: regionname,
84                 node: Y.one('#block-region-' + regionname)
85             });
86             this.regionobjects[regionname] = region;
88             // Setting blockregion as droptarget (the case when it is empty)
89             // The region-post (the right one)
90             // is very narrow, so add extra padding on the left to drop block on it.
91             new Y.DD.Drop({
92                 node: region.get_droptarget(),
93                 groups: this.groups,
94                 padding: '40 240 40 240'
95             });
97             // Make each div element in the list of blocks draggable
98             dragdelegation = new Y.DD.Delegate({
99                 container: region.get_droptarget(),
100                 nodes: '.' + CSS.BLOCK,
101                 target: true,
102                 handles: [SELECTOR.DRAGHANDLE],
103                 invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake',
104                 dragConfig: {groups: this.groups}
105             });
106             dragdelegation.dd.plug(Y.Plugin.DDProxy, {
107                 // Don't move the node at the end of the drag
108                 moveOnEnd: false
109             });
110             dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
112             // On the DD Manager start operation, we enable all block regions so that they can be drop targets. This
113             // must be done *before* drag:start but after dragging has been initialised.
114             Y.DD.DDM.on('ddm:start', this.enable_all_regions, this);
116             region.change_block_move_icons(this);
117         }
118         Y.log('Initialisation of drag and drop for blocks complete.', 'info');
119     },
121     /**
122      * Returns the ID of the block the given node represents.
123      * @method get_block_id
124      * @param {Node} node
125      * @return {int} The blocks ID in the database.
126      */
127     get_block_id: function(node) {
128         return Number(node.get('id').replace(/inst/i, ''));
129     },
131     /**
132      * Returns the block region that the node is part of or belonging to.
133      * @method get_block_region
134      * @param {Y.Node} node
135      * @return {string} The region name.
136      */
137     get_block_region: function(node) {
138         if (!node.test('[data-blockregion]')) {
139             node = node.ancestor('[data-blockregion]');
140         }
141         return node.getData('blockregion');
142     },
144     /**
145      * Returns the BLOCKREGION instance that represents the block region the given node is part of.
146      * @method get_region_object
147      * @param {Y.Node} node
148      * @return {BLOCKREGION}
149      */
150     get_region_object: function(node) {
151         return this.regionobjects[this.get_block_region(node)];
152     },
154     /**
155      * Enables all fo the regions so that they are all visible while dragging is occuring.
156      *
157      * @method enable_all_regions
158      */
159     enable_all_regions: function() {
160         var groups = Y.DD.DDM.activeDrag.get('groups');
162         // As we're called by Y.DD.DDM, we can't be certain that the call
163         // relates specifically to a block drag/drop operation. Test
164         // whether the relevant group applies here.
165         if (!groups || Y.Array.indexOf(groups, 'block') === -1) {
166             return;
167         }
169         var i;
170         for (i in this.regionobjects) {
171             if (!this.regionobjects.hasOwnProperty(i)) {
172                 continue;
173             }
174             this.regionobjects[i].enable();
175         }
176     },
178     /**
179      * Disables enabled regions if they contain no blocks.
180      * @method disable_regions_if_required
181      */
182     disable_regions_if_required: function() {
183         var i = 0;
184         for (i in this.regionobjects) {
185             this.regionobjects[i].disable_if_required();
186         }
187     },
189     /**
190      * Called by M.core.dragdrop.global_drag_start when dragging starts.
191      * @method drag_start
192      * @param {Event} e
193      */
194     drag_start: function(e) {
195         // Get our drag object
196         var drag = e.target;
198         // Store the parent node of original drag node (block)
199         // we will need it later for show/hide empty regions
201         // Determine skipnodes and store them
202         if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
203             this.skipnodetop = drag.get('node').previous();
204         }
205         if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
206             this.skipnodebottom = drag.get('node').next();
207         }
208     },
210     /**
211      * Called by M.core.dragdrop.global_drop_over when something is dragged over a drop target.
212      * @method drop_over
213      * @param {Event} e
214      */
215     drop_over: function(e) {
216         // Get a reference to our drag and drop nodes
217         var drag = e.drag.get('node');
218         var drop = e.drop.get('node');
220         // We need to fix the case when parent drop over event has determined
221         // 'goingup' and appended the drag node after admin-block.
222         if (drop.hasClass(CSS.REGIONCONTENT) &&
223                 drop.one('.' + CSS.BLOCKADMINBLOCK) &&
224                 drop.one('.' + CSS.BLOCKADMINBLOCK).next('.' + CSS.BLOCK)) {
225             drop.prepend(drag);
226         }
227     },
229     /**
230      * Called by M.core.dragdrop.global_drop_end when a drop has been completed.
231      * @method drop_end
232      */
233     drop_end: function() {
234         // Clear variables.
235         this.skipnodetop = null;
236         this.skipnodebottom = null;
237         this.disable_regions_if_required();
238     },
240     /**
241      * Called by M.core.dragdrop.global_drag_dropmiss when something has been dropped on a node that isn't contained by
242      * a drop target.
243      *
244      * @method drag_dropmiss
245      * @param {Event} e
246      */
247     drag_dropmiss: function(e) {
248         // Missed the target, but we assume the user intended to drop it
249         // on the last ghost node location, e.drag and e.drop should be
250         // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
251         this.drop_hit(e);
252     },
254     /**
255      * Called by M.core.dragdrop.global_drag_hit when something has been dropped on a drop target.
256      * @method drop_hit
257      * @param {Event} e
258      */
259     drop_hit: function(e) {
260         // Get a reference to our drag node
261         var dragnode = e.drag.get('node');
262         var dropnode = e.drop.get('node');
264         // Amend existing skipnodes
265         if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
266             // the one that belongs to block below move below
267             dragnode.insert(dragnode.previous(), 'after');
268         }
269         // Move original skipnodes
270         if (this.skipnodetop) {
271             dragnode.insert(this.skipnodetop, 'before');
272         }
273         if (this.skipnodebottom) {
274             dragnode.insert(this.skipnodebottom, 'after');
275         }
277         // Add lightbox if it not there
278         var lightbox = M.util.add_lightbox(Y, dragnode);
280         // Prepare request parameters
281         var params = {
282             sesskey: M.cfg.sesskey,
283             courseid: this.get('courseid'),
284             pagelayout: this.get('pagelayout'),
285             pagetype: this.get('pagetype'),
286             subpage: this.get('subpage'),
287             contextid: this.get('contextid'),
288             action: 'move',
289             bui_moveid: this.get_block_id(dragnode),
290             bui_newregion: this.get_block_region(dropnode)
291         };
293         if (this.get('cmid')) {
294             params.cmid = this.get('cmid');
295         }
297         if (dragnode.next('.' + CSS.BLOCK) && !dragnode.next('.' + CSS.BLOCK).hasClass(CSS.BLOCKADMINBLOCK)) {
298             params.bui_beforeid = this.get_block_id(dragnode.next('.' + CSS.BLOCK));
299         }
301         // Do AJAX request
302         Y.io(M.cfg.wwwroot + AJAXURL, {
303             method: 'POST',
304             data: params,
305             on: {
306                 start: function() {
307                     lightbox.show();
308                 },
309                 success: function(tid, response) {
310                     window.setTimeout(function() {
311                         lightbox.hide();
312                     }, 250);
313                     try {
314                         var responsetext = Y.JSON.parse(response.responseText);
315                         if (responsetext.error) {
316                             new M.core.ajaxException(responsetext);
317                         }
318                     } catch (e) {
319                         // Ignore.
320                     }
321                 },
322                 failure: function(tid, response) {
323                     this.ajax_failure(response);
324                     lightbox.hide();
325                 },
326                 complete: function() {
327                     this.disable_regions_if_required();
328                 }
329             },
330             context: this
331         });
332     }
333 };
334 Y.extend(MANAGER, M.core.dragdrop, MANAGER.prototype, {
335     NAME: 'core-blocks-dragdrop-manager',
336     ATTRS: {
337         /**
338          * The Course ID if there is one.
339          * @attribute courseid
340          * @type int|null
341          * @default null
342          */
343         courseid: {
344             value: null
345         },
347         /**
348          * The Course Module ID if there is one.
349          * @attribute cmid
350          * @type int|null
351          * @default null
352          */
353         cmid: {
354             value: null
355         },
357         /**
358          * The Context ID.
359          * @attribute contextid
360          * @type int|null
361          * @default null
362          */
363         contextid: {
364             value: null
365         },
367         /**
368          * The current page layout.
369          * @attribute pagelayout
370          * @type string|null
371          * @default null
372          */
373         pagelayout: {
374             value: null
375         },
377         /**
378          * The page type string, should be used as the id for the body tag in the theme.
379          * @attribute pagetype
380          * @type string|null
381          * @default null
382          */
383         pagetype: {
384             value: null
385         },
387         /**
388          * The subpage identifier, if any.
389          * @attribute subpage
390          * @type string|null
391          * @default null
392          */
393         subpage: {
394             value: null
395         },
397         /**
398          * An array of block regions that are present on the page.
399          * @attribute regions
400          * @type array|null
401          * @default Array[]
402          */
403         regions: {
404             value: []
405         }
406     }
407 });