MDL-37664 course: Fix drag & drop RTL mode issue
[moodle.git] / lib / yui / blocks / blocks.js
1 YUI.add('moodle-core-blocks', function(Y) {
3     var AJAXURL = '/lib/ajax/blocks.php',
4     CSS = {
5         BLOCK : 'block',
6         BLOCKREGION : 'block-region',
7         BLOCKADMINBLOCK : 'block_adminblock',
8         EDITINGMOVE : 'editing_move',
9         HEADER : 'header',
10         LIGHTBOX : 'lightbox',
11         REGIONCONTENT : 'region-content',
12         SKIPBLOCK : 'skip-block',
13         SKIPBLOCKTO : 'skip-block-to'
14     }
16     var DRAGBLOCK = function() {
17         DRAGBLOCK.superclass.constructor.apply(this, arguments);
18     };
19     Y.extend(DRAGBLOCK, M.core.dragdrop, {
20         skipnodetop : null,
21         skipnodebottom : null,
22         dragsourceregion : null,
23         initializer : function(params) {
24             // Set group for parent class
25             this.groups = ['block'];
26             this.samenodeclass = CSS.BLOCK;
27             this.parentnodeclass = CSS.REGIONCONTENT;
29             // Initialise blocks dragging
30             // Find all block regions on the page
31             var blockregionlist = Y.Node.all('div.'+CSS.BLOCKREGION);
33             if (blockregionlist.size() === 0) {
34                 return false;
35             }
37             // See if we are missing either of block regions,
38             // if yes we need to add an empty one to use as target
39             if (blockregionlist.size() != this.get('regions').length) {
40                 var blockregion = Y.Node.create('<div></div>')
41                     .addClass(CSS.BLOCKREGION);
42                 var regioncontent = Y.Node.create('<div></div>')
43                     .addClass(CSS.REGIONCONTENT);
44                 blockregion.appendChild(regioncontent);
46                 var regionid = this.get_region_id(blockregionlist.item(0));
47                 if (regionid === 'post') {
48                     // pre block is missing, instert it before post
49                     blockregion.setAttrs({id : 'region-pre'});
50                     blockregionlist.item(0).insert(blockregion, 'before');
51                     blockregionlist.unshift(blockregion);
52                 } else {
53                     // post block is missing, instert it after pre
54                     blockregion.setAttrs({id : 'region-post'});
55                     blockregionlist.item(0).insert(blockregion, 'after');
56                     blockregionlist.push(blockregion);
57                 }
58             }
60             blockregionlist.each(function(blockregionnode) {
62                 // Setting blockregion as droptarget (the case when it is empty)
63                 // The region-post (the right one)
64                 // is very narrow, so add extra padding on the left to drop block on it.
65                 var tar = new Y.DD.Drop({
66                     node: blockregionnode.one('div.'+CSS.REGIONCONTENT),
67                     groups: this.groups,
68                     padding: '40 240 40 240'
69                 });
71                 // Make each div element in the list of blocks draggable
72                 var del = new Y.DD.Delegate({
73                     container: blockregionnode,
74                     nodes: '.'+CSS.BLOCK,
75                     target: true,
76                     handles: ['.'+CSS.HEADER],
77                     invalid: '.block-hider-hide, .block-hider-show, .moveto',
78                     dragConfig: {groups: this.groups}
79                 });
80                 del.dd.plug(Y.Plugin.DDProxy, {
81                     // Don't move the node at the end of the drag
82                     moveOnEnd: false
83                 });
84                 del.dd.plug(Y.Plugin.DDWinScroll);
86                 var blocklist = blockregionnode.all('.'+CSS.BLOCK);
87                 blocklist.each(function(blocknode) {
88                     var move = blocknode.one('a.'+CSS.EDITINGMOVE);
89                     if (move) {
90                         move.remove();
91                         blocknode.one('.'+CSS.HEADER).setStyle('cursor', 'move');
92                     }
93                 }, this);
94             }, this);
95         },
97         get_block_id : function(node) {
98             return Number(node.get('id').replace(/inst/i, ''));
99         },
101         get_block_region : function(node) {
102             var region = node.ancestor('div.'+CSS.BLOCKREGION).get('id').replace(/region-/i, '');
103             if (Y.Array.indexOf(this.get('regions'), region) === -1) {
104                 // Must be standard side-X
105                 if (right_to_left()) {
106                     if (region == 'post') {
107                         region = 'pre';
108                     } else if (region == 'pre') {
109                         region = 'post';
110                     }
111                 }
112                 return 'side-' + region;
113             }
114             // Perhaps custom region
115             return region;
116         },
118         get_region_id : function(node) {
119             return node.get('id').replace(/region-/i, '');
120         },
122         drag_start : function(e) {
123             // Get our drag object
124             var drag = e.target;
126             // Store the parent node of original drag node (block)
127             // we will need it later for show/hide empty regions
128             this.dragsourceregion = drag.get('node').ancestor('div.'+CSS.BLOCKREGION);
130             // Determine skipnodes and store them
131             if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
132                 this.skipnodetop = drag.get('node').previous();
133             }
134             if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
135                 this.skipnodebottom = drag.get('node').next();
136             }
137         },
139         drop_over : function(e) {
140             // Get a reference to our drag and drop nodes
141             var drag = e.drag.get('node');
142             var drop = e.drop.get('node');
144             // We need to fix the case when parent drop over event has determined
145             // 'goingup' and appended the drag node after admin-block.
146             if (drop.hasClass(this.parentnodeclass) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) {
147                 drop.prepend(drag);
148             }
150             // Block is moved within the same region
151             // stop here, no need to modify anything.
152             if (this.dragsourceregion.contains(drop)) {
153                 return false;
154             }
156             // TODO: Hiding-displaying block region only works for base theme blocks
157             // (region-pre, region-post) at the moment. It should be improved
158             // to work with custom block regions as well.
160             // TODO: Fix this for the case when user drag block towards empty section,
161             // then the section appears, then user chnages his mind and moving back to
162             // original section. The opposite section remains opened and empty.
164             var documentbody = Y.one('body');
165             // Moving block towards hidden region-content, display it
166             var regionname = this.get_region_id(this.dragsourceregion);
167             if (documentbody.hasClass('side-'+regionname+'-only')) {
168                 documentbody.removeClass('side-'+regionname+'-only');
169             }
171             // Moving from empty region-content towards the opposite one,
172             // hide empty one (only for region-pre, region-post areas at the moment).
173             regionname = this.get_region_id(drop.ancestor('div.'+CSS.BLOCKREGION));
174             if (this.dragsourceregion.all('.'+CSS.BLOCK).size() == 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) {
175                 if (!documentbody.hasClass('side-'+regionname+'-only')) {
176                     documentbody.addClass('side-'+regionname+'-only');
177                 }
178             }
179         },
181         drop_end : function(e) {
182             // clear variables
183             this.skipnodetop = null;
184             this.skipnodebottom = null;
185             this.dragsourceregion = null;
186         },
188         drag_dropmiss : function(e) {
189             // Missed the target, but we assume the user intended to drop it
190             // on the last last ghost node location, e.drag and e.drop should be
191             // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
192             this.drop_hit(e);
193         },
195         drop_hit : function(e) {
196             var drag = e.drag;
197             // Get a reference to our drag node
198             var dragnode = drag.get('node');
199             var dropnode = e.drop.get('node');
201             // Amend existing skipnodes
202             if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
203                 // the one that belongs to block below move below
204                 dragnode.insert(dragnode.previous(), 'after');
205             }
206             // Move original skipnodes
207             if (this.skipnodetop) {
208                 dragnode.insert(this.skipnodetop, 'before');
209             }
210             if (this.skipnodebottom) {
211                 dragnode.insert(this.skipnodebottom, 'after');
212             }
214             // Add lightbox if it not there
215             var lightbox = M.util.add_lightbox(Y, dragnode);
217             // Prepare request parameters
218             var params = {
219                 sesskey : M.cfg.sesskey,
220                 courseid : this.get('courseid'),
221                 pagelayout : this.get('pagelayout'),
222                 pagetype : this.get('pagetype'),
223                 subpage : this.get('subpage'),
224                 action : 'move',
225                 bui_moveid : this.get_block_id(dragnode),
226                 bui_newregion : this.get_block_region(dropnode)
227             };
229             if (this.get('cmid')) {
230                 params.cmid = this.get('cmid');
231             }
233             if (dragnode.next('.'+this.samenodeclass) && !dragnode.next('.'+this.samenodeclass).hasClass(CSS.BLOCKADMINBLOCK)) {
234                 params.bui_beforeid = this.get_block_id(dragnode.next('.'+this.samenodeclass));
235             }
237             // Do AJAX request
238             Y.io(M.cfg.wwwroot+AJAXURL, {
239                 method: 'POST',
240                 data: params,
241                 on: {
242                     start : function(tid) {
243                         lightbox.show();
244                     },
245                     success: function(tid, response) {
246                         window.setTimeout(function(e) {
247                             lightbox.hide();
248                         }, 250);
249                         try {
250                             var responsetext = Y.JSON.parse(response.responseText);
251                             if (responsetext.error) {
252                                 new M.core.ajaxException(responsetext);
253                             }
254                         } catch (e) {}
255                     },
256                     failure: function(tid, response) {
257                         this.ajax_failure(response);
258                         lightbox.hide();
259                     }
260                 },
261                 context:this
262             });
263         }
264     }, {
265         NAME : 'core-blocks-dragdrop',
266         ATTRS : {
267             courseid : {
268                 value : null
269             },
270             cmid : {
271                 value : null
272             },
273             pagelayout : {
274                 value : null
275             },
276             pagetype : {
277                 value : null
278             },
279             subpage : {
280                 value : null
281             },
282             regions : {
283                 value : null
284             }
285         }
286     });
288     M.core_blocks = M.core_blocks || {};
289     M.core_blocks.init_dragdrop = function(params) {
290         new DRAGBLOCK(params);
291     }
292 }, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification']});