MDL-41511 blocks: improved how custom block regions were being rendered.
[moodle.git] / lib / yui / src / blocks / js / blocks.js
CommitLineData
018721e6
SH
1/**
2 * Provides drag and drop functionality for blocks.
3 *
4 * @module moodle-core-blockdraganddrop
5 */
6
a94dd7d2
ARN
7var AJAXURL = '/lib/ajax/blocks.php',
8CSS = {
9 BLOCK : 'block',
10 BLOCKREGION : 'block-region',
11 BLOCKADMINBLOCK : 'block_adminblock',
12 EDITINGMOVE : 'editing_move',
13 HEADER : 'header',
14 LIGHTBOX : 'lightbox',
15 REGIONCONTENT : 'region-content',
16 SKIPBLOCK : 'skip-block',
17 SKIPBLOCKTO : 'skip-block-to',
18 MYINDEX : 'page-my-index',
19 REGIONMAIN : 'region-main'
20};
21
45fdcb7c
DW
22var SELECTOR = {
23 DRAGHANDLE : '.' + CSS.HEADER + ' .commands .moodle-core-dragdrop-draghandle'
24};
25
018721e6
SH
26/**
27 * Legacy drag and drop manager.
28 * This drag and drop manager is specifically designed for themes using side-pre and side-post
29 * that do not make use of the block output methods introduced by MDL-39824.
30 *
31 * @namespace M.core.blockdraganddrop
32 * @class LegacyManager
33 * @constructor
34 * @extends M.core.dragdrop
35 */
a94dd7d2
ARN
36var DRAGBLOCK = function() {
37 DRAGBLOCK.superclass.constructor.apply(this, arguments);
38};
39Y.extend(DRAGBLOCK, M.core.dragdrop, {
40 skipnodetop : null,
41 skipnodebottom : null,
42 dragsourceregion : null,
43 initializer : function() {
44 // Set group for parent class
45 this.groups = ['block'];
46 this.samenodeclass = CSS.BLOCK;
47 this.parentnodeclass = CSS.REGIONCONTENT;
48
49 // Add relevant classes and ID to 'content' block region on My Home page.
50 var myhomecontent = Y.Node.all('body#'+CSS.MYINDEX+' #'+CSS.REGIONMAIN+' > .'+CSS.REGIONCONTENT);
51 if (myhomecontent.size() > 0) {
52 var contentregion = myhomecontent.item(0);
53 contentregion.addClass(CSS.BLOCKREGION);
54 contentregion.set('id', CSS.REGIONCONTENT);
55 contentregion.one('div').addClass(CSS.REGIONCONTENT);
56 }
57
58 // Initialise blocks dragging
59 // Find all block regions on the page
60 var blockregionlist = Y.Node.all('div.'+CSS.BLOCKREGION);
61
62 if (blockregionlist.size() === 0) {
63 return false;
64 }
65
66 // See if we are missing either of block regions,
67 // if yes we need to add an empty one to use as target
2db01537 68 if (blockregionlist.size() !== this.get('regions').length) {
a94dd7d2
ARN
69 var blockregion = Y.Node.create('<div></div>')
70 .addClass(CSS.BLOCKREGION);
71 var regioncontent = Y.Node.create('<div></div>')
72 .addClass(CSS.REGIONCONTENT);
73 blockregion.appendChild(regioncontent);
74 var pre = blockregionlist.filter('#region-pre');
75 var post = blockregionlist.filter('#region-post');
76
77 if (pre.size() === 0 && post.size() === 1) {
78 // pre block is missing, instert it before post
79 blockregion.setAttrs({id : 'region-pre'});
80 post.item(0).insert(blockregion, 'before');
81 blockregionlist.unshift(blockregion);
82 } else if (post.size() === 0 && pre.size() === 1) {
83 // post block is missing, instert it after pre
84 blockregion.setAttrs({id : 'region-post'});
85 pre.item(0).insert(blockregion, 'after');
86 blockregionlist.push(blockregion);
87 }
88 }
89
90 blockregionlist.each(function(blockregionnode) {
91
92 // Setting blockregion as droptarget (the case when it is empty)
93 // The region-post (the right one)
94 // is very narrow, so add extra padding on the left to drop block on it.
2db01537 95 new Y.DD.Drop({
a94dd7d2
ARN
96 node: blockregionnode.one('div.'+CSS.REGIONCONTENT),
97 groups: this.groups,
98 padding: '40 240 40 240'
99 });
100
101 // Make each div element in the list of blocks draggable
102 var del = new Y.DD.Delegate({
103 container: blockregionnode,
104 nodes: '.'+CSS.BLOCK,
105 target: true,
45fdcb7c 106 handles: [SELECTOR.DRAGHANDLE],
a94dd7d2
ARN
107 invalid: '.block-hider-hide, .block-hider-show, .moveto',
108 dragConfig: {groups: this.groups}
109 });
110 del.dd.plug(Y.Plugin.DDProxy, {
111 // Don't move the node at the end of the drag
112 moveOnEnd: false
113 });
114 del.dd.plug(Y.Plugin.DDWinScroll);
115
116 var blocklist = blockregionnode.all('.'+CSS.BLOCK);
117 blocklist.each(function(blocknode) {
118 var move = blocknode.one('a.'+CSS.EDITINGMOVE);
119 if (move) {
a65a8f25 120 move.replace(this.get_drag_handle(move.getAttribute('title'), '', 'iconsmall', true));
45fdcb7c 121 blocknode.one(SELECTOR.DRAGHANDLE).setStyle('cursor', 'move');
a94dd7d2
ARN
122 }
123 }, this);
124 }, this);
125 },
126
127 get_block_id : function(node) {
128 return Number(node.get('id').replace(/inst/i, ''));
129 },
130
131 get_block_region : function(node) {
132 var region = node.ancestor('div.'+CSS.BLOCKREGION).get('id').replace(/region-/i, '');
133 if (Y.Array.indexOf(this.get('regions'), region) === -1) {
134 // Must be standard side-X
135 if (right_to_left()) {
136 if (region === 'post') {
137 region = 'pre';
138 } else if (region === 'pre') {
139 region = 'post';
140 }
141 }
142 return 'side-' + region;
143 }
144 // Perhaps custom region
145 return region;
146 },
147
148 get_region_id : function(node) {
149 return node.get('id').replace(/region-/i, '');
150 },
151
152 drag_start : function(e) {
153 // Get our drag object
154 var drag = e.target;
155
156 // Store the parent node of original drag node (block)
157 // we will need it later for show/hide empty regions
158 this.dragsourceregion = drag.get('node').ancestor('div.'+CSS.BLOCKREGION);
159
160 // Determine skipnodes and store them
161 if (drag.get('node').previous() && drag.get('node').previous().hasClass(CSS.SKIPBLOCK)) {
162 this.skipnodetop = drag.get('node').previous();
163 }
164 if (drag.get('node').next() && drag.get('node').next().hasClass(CSS.SKIPBLOCKTO)) {
165 this.skipnodebottom = drag.get('node').next();
166 }
167 },
168
169 drop_over : function(e) {
170 // Get a reference to our drag and drop nodes
171 var drag = e.drag.get('node');
172 var drop = e.drop.get('node');
173
174 // We need to fix the case when parent drop over event has determined
175 // 'goingup' and appended the drag node after admin-block.
176 if (drop.hasClass(this.parentnodeclass) && drop.one('.'+CSS.BLOCKADMINBLOCK) && drop.one('.'+CSS.BLOCKADMINBLOCK).next('.'+CSS.BLOCK)) {
177 drop.prepend(drag);
178 }
179
180 // Block is moved within the same region
181 // stop here, no need to modify anything.
182 if (this.dragsourceregion.contains(drop)) {
183 return false;
184 }
185
186 // TODO: Hiding-displaying block region only works for base theme blocks
187 // (region-pre, region-post) at the moment. It should be improved
188 // to work with custom block regions as well.
189
190 // TODO: Fix this for the case when user drag block towards empty section,
191 // then the section appears, then user chnages his mind and moving back to
192 // original section. The opposite section remains opened and empty.
193
194 var documentbody = Y.one('body');
195 // Moving block towards hidden region-content, display it
196 var regionname = this.get_region_id(this.dragsourceregion);
197 if (documentbody.hasClass('side-'+regionname+'-only')) {
198 documentbody.removeClass('side-'+regionname+'-only');
199 }
200
201 // Moving from empty region-content towards the opposite one,
202 // hide empty one (only for region-pre, region-post areas at the moment).
203 regionname = this.get_region_id(drop.ancestor('div.'+CSS.BLOCKREGION));
2db01537 204 if (this.dragsourceregion.all('.'+CSS.BLOCK).size() === 0 && this.dragsourceregion.get('id').match(/(region-pre|region-post)/i)) {
a94dd7d2
ARN
205 if (!documentbody.hasClass('side-'+regionname+'-only')) {
206 documentbody.addClass('side-'+regionname+'-only');
207 }
208 }
209 },
210
211 drop_end : function() {
212 // clear variables
213 this.skipnodetop = null;
214 this.skipnodebottom = null;
215 this.dragsourceregion = null;
216 },
217
218 drag_dropmiss : function(e) {
219 // Missed the target, but we assume the user intended to drop it
220 // on the last last ghost node location, e.drag and e.drop should be
221 // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
222 this.drop_hit(e);
223 },
224
225 drop_hit : function(e) {
226 var drag = e.drag;
227 // Get a reference to our drag node
228 var dragnode = drag.get('node');
229 var dropnode = e.drop.get('node');
230
231 // Amend existing skipnodes
232 if (dragnode.previous() && dragnode.previous().hasClass(CSS.SKIPBLOCK)) {
233 // the one that belongs to block below move below
234 dragnode.insert(dragnode.previous(), 'after');
235 }
236 // Move original skipnodes
237 if (this.skipnodetop) {
238 dragnode.insert(this.skipnodetop, 'before');
239 }
240 if (this.skipnodebottom) {
241 dragnode.insert(this.skipnodebottom, 'after');
242 }
243
244 // Add lightbox if it not there
245 var lightbox = M.util.add_lightbox(Y, dragnode);
246
247 // Prepare request parameters
248 var params = {
249 sesskey : M.cfg.sesskey,
250 courseid : this.get('courseid'),
251 pagelayout : this.get('pagelayout'),
252 pagetype : this.get('pagetype'),
253 subpage : this.get('subpage'),
254 contextid : this.get('contextid'),
255 action : 'move',
256 bui_moveid : this.get_block_id(dragnode),
257 bui_newregion : this.get_block_region(dropnode)
258 };
259
260 if (this.get('cmid')) {
261 params.cmid = this.get('cmid');
262 }
263
264 if (dragnode.next('.'+this.samenodeclass) && !dragnode.next('.'+this.samenodeclass).hasClass(CSS.BLOCKADMINBLOCK)) {
265 params.bui_beforeid = this.get_block_id(dragnode.next('.'+this.samenodeclass));
266 }
267
268 // Do AJAX request
269 Y.io(M.cfg.wwwroot+AJAXURL, {
270 method: 'POST',
271 data: params,
272 on: {
273 start : function() {
274 lightbox.show();
275 },
276 success: function(tid, response) {
277 window.setTimeout(function() {
278 lightbox.hide();
279 }, 250);
280 try {
281 var responsetext = Y.JSON.parse(response.responseText);
282 if (responsetext.error) {
283 new M.core.ajaxException(responsetext);
284 }
285 } catch (e) {}
286 },
287 failure: function(tid, response) {
288 this.ajax_failure(response);
289 lightbox.hide();
290 }
291 },
292 context:this
293 });
294 }
295}, {
296 NAME : 'core-blocks-dragdrop',
297 ATTRS : {
298 courseid : {
299 value : null
300 },
301 cmid : {
302 value : null
303 },
304 contextid : {
305 value : null
306 },
307 pagelayout : {
308 value : null
309 },
310 pagetype : {
311 value : null
312 },
313 subpage : {
314 value : null
315 },
316 regions : {
317 value : null
318 }
319 }
320});
321
018721e6 322M.core = M.core || {};
018721e6
SH
323M.core.blockdraganddrop = M.core.blockdraganddrop || {};
324
325/**
326 * True if the page is using the new blocks methods.
327 * @private
328 * @static
1f777e5c 329 * @property M.core.blockdraganddrop._isusingnewblocksmethod
018721e6
SH
330 * @type Boolean
331 * @default null
332 */
333M.core.blockdraganddrop._isusingnewblocksmethod = null;
334
335/**
336 * Returns true if the page is using the new blocks methods.
337 * @static
1f777e5c 338 * @method M.core.blockdraganddrop.is_using_blocks_render_method
018721e6
SH
339 * @return Boolean
340 */
341M.core.blockdraganddrop.is_using_blocks_render_method = function() {
342 if (this._isusingnewblocksmethod === null) {
343 var goodregions = Y.all('.block-region[data-blockregion]').size();
344 var allregions = Y.all('.block-region').size();
345 this._isusingnewblocksmethod = (allregions === goodregions);
225c418f
SH
346 if (goodregions > 0 && allregions > 0) {
347 Y.log('Both core_renderer::blocks and core_renderer::blocks_for_region have been used.', 'warn', 'moodle-core_blocks');
348 }
018721e6
SH
349 }
350 return this._isusingnewblocksmethod;
351};
352
353/**
354 * Initialises a drag and drop manager.
355 * This should only ever be called once for a page.
356 * @static
1f777e5c 357 * @method M.core.blockdraganddrop.init
018721e6
SH
358 * @param {Object} params
359 * @return Manager
360 */
361M.core.blockdraganddrop.init = function(params) {
362 if (this.is_using_blocks_render_method()) {
225c418f 363 Y.log('Block drag and drop initialised for the blocks method.', 'info', 'moodle-core_blocks');
018721e6
SH
364 new MANAGER(params);
365 } else {
225c418f 366 Y.log('Block drag and drop initialised with the legacy manager (blocks_for_region used).', 'info', 'moodle-core_blocks');
018721e6
SH
367 new DRAGBLOCK(params);
368 }
369};
370
1f777e5c 371/*
018721e6
SH
372 * Legacy code to keep things working.
373 */
a94dd7d2
ARN
374M.core_blocks = M.core_blocks || {};
375M.core_blocks.init_dragdrop = function(params) {
018721e6 376 M.core.blockdraganddrop.init(params);
dd66b6ab 377};