Commit | Line | Data |
---|---|---|
15e2552f RK |
1 | YUI.add('moodle-course-dragdrop', function(Y) { |
2 | ||
3 | var CSS = { | |
4 | ACTIVITY : 'activity', | |
45b364b9 | 5 | COMMANDSPAN : 'span.commands', |
15e2552f RK |
6 | CONTENT : 'content', |
7 | COURSECONTENT : 'course-content', | |
8 | EDITINGMOVE : 'editing_move', | |
9 | ICONCLASS : 'iconsmall', | |
10 | JUMPMENU : 'jumpmenu', | |
11 | LEFT : 'left', | |
12 | LIGHTBOX : 'lightbox', | |
13 | MOVEDOWN : 'movedown', | |
14 | MOVEUP : 'moveup', | |
15 | PAGECONTENT : 'page-content', | |
16 | RIGHT : 'right', | |
17 | SECTION : 'section', | |
18 | SECTIONADDMENUS : 'section_add_menus', | |
19 | SECTIONHANDLE : 'section-handle', | |
405eaac2 | 20 | SUMMARY : 'summary' |
15e2552f RK |
21 | }; |
22 | ||
23 | var DRAGSECTION = function() { | |
24 | DRAGSECTION.superclass.constructor.apply(this, arguments); | |
25 | }; | |
26 | Y.extend(DRAGSECTION, M.core.dragdrop, { | |
c77582fe RK |
27 | sectionlistselector : null, |
28 | ||
15e2552f RK |
29 | initializer : function(params) { |
30 | // Set group for parent class | |
31 | this.groups = ['section']; | |
405eaac2 RK |
32 | this.samenodeclass = M.course.format.get_sectionwrapperclass(); |
33 | this.parentnodeclass = M.course.format.get_containerclass(); | |
15e2552f RK |
34 | |
35 | // Check if we are in single section mode | |
36 | if (Y.Node.one('.'+CSS.JUMPMENU)) { | |
37 | return false; | |
38 | } | |
39 | // Initialise sections dragging | |
405eaac2 RK |
40 | this.sectionlistselector = M.course.format.get_section_wrapper(Y); |
41 | if (this.sectionlistselector) { | |
42 | this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector; | |
d95b77bd | 43 | this.setup_for_section(this.sectionlistselector); |
c77582fe | 44 | } |
15e2552f RK |
45 | }, |
46 | ||
47 | /** | |
48 | * Apply dragdrop features to the specified selector or node that refers to section(s) | |
49 | * | |
50 | * @param baseselector The CSS selector or node to limit scope to | |
51 | * @return void | |
52 | */ | |
53 | setup_for_section : function(baseselector) { | |
54 | Y.Node.all(baseselector).each(function(sectionnode) { | |
55 | // Determine the section ID | |
56 | var sectionid = this.get_section_id(sectionnode); | |
57 | ||
58 | // We skip the top section as it is not draggable | |
59 | if (sectionid > 0) { | |
60 | // Remove move icons | |
61 | var movedown = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEDOWN); | |
15e2552f | 62 | var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP); |
19d041bf | 63 | |
15e2552f RK |
64 | // Add dragger icon |
65 | var title = M.util.get_string('movesection', 'moodle', sectionid); | |
66 | var cssleft = sectionnode.one('.'+CSS.LEFT); | |
15e2552f | 67 | |
19d041bf ARN |
68 | if ((movedown || moveup) && cssleft) { |
69 | cssleft.setStyle('cursor', 'move'); | |
70 | cssleft.appendChild(Y.Node.create('<br />')); | |
71 | cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE)); | |
72 | ||
73 | if (moveup) { | |
74 | moveup.remove(); | |
75 | } | |
76 | if (movedown) { | |
77 | movedown.remove(); | |
78 | } | |
79 | ||
80 | // Make each li element in the lists of sections draggable | |
81 | var dd = new Y.DD.Drag({ | |
82 | node: sectionnode, | |
83 | // Make each li a Drop target too | |
84 | groups: this.groups, | |
85 | target: true, | |
86 | handles: ['.'+CSS.LEFT] | |
87 | }).plug(Y.Plugin.DDProxy, { | |
88 | // Don't move the node at the end of the drag | |
89 | moveOnEnd: false | |
90 | }).plug(Y.Plugin.DDConstrained, { | |
91 | // Keep it inside the .course-content | |
92 | constrain: '#'+CSS.PAGECONTENT, | |
93 | stickY: true | |
26992cf1 | 94 | }).plug(Y.Plugin.DDWinScroll); |
19d041bf | 95 | } |
15e2552f RK |
96 | } |
97 | }, this); | |
98 | }, | |
99 | ||
100 | get_section_id : function(node) { | |
101 | return Number(node.get('id').replace(/section-/i, '')); | |
102 | }, | |
103 | ||
104 | /* | |
105 | * Drag-dropping related functions | |
106 | */ | |
107 | drag_start : function(e) { | |
108 | // Get our drag object | |
109 | var drag = e.target; | |
110 | // Creat a dummy structure of the outer elemnents for clean styles application | |
405eaac2 RK |
111 | var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'></'+M.course.format.get_containernode()+'>'); |
112 | containernode.addClass(M.course.format.get_containerclass()); | |
113 | var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'></'+ M.course.format.get_sectionwrappernode()+'>'); | |
114 | sectionnode.addClass( M.course.format.get_sectionwrapperclass()); | |
115 | sectionnode.setStyle('margin', 0); | |
116 | sectionnode.setContent(drag.get('node').get('innerHTML')); | |
117 | containernode.appendChild(sectionnode); | |
118 | drag.get('dragNode').setContent(containernode); | |
15e2552f RK |
119 | drag.get('dragNode').addClass(CSS.COURSECONTENT); |
120 | }, | |
121 | ||
243e9bf9 RK |
122 | drag_dropmiss : function(e) { |
123 | // Missed the target, but we assume the user intended to drop it | |
124 | // on the last last ghost node location, e.drag and e.drop should be | |
125 | // prepared by global_drag_dropmiss parent so simulate drop_hit(e). | |
126 | this.drop_hit(e); | |
127 | }, | |
128 | ||
15e2552f RK |
129 | drop_hit : function(e) { |
130 | var drag = e.drag; | |
131 | // Get a reference to our drag node | |
132 | var dragnode = drag.get('node'); | |
133 | var dropnode = e.drop.get('node'); | |
134 | // Prepare some variables | |
135 | var dragnodeid = Number(this.get_section_id(dragnode)); | |
136 | var dropnodeid = Number(this.get_section_id(dropnode)); | |
137 | ||
15e2552f RK |
138 | var loopstart = dragnodeid; |
139 | var loopend = dropnodeid; | |
140 | ||
141 | if (this.goingup) { | |
15e2552f RK |
142 | loopstart = dropnodeid; |
143 | loopend = dragnodeid; | |
144 | } | |
145 | ||
146 | // Get the list of nodes | |
147 | drag.get('dragNode').removeClass(CSS.COURSECONTENT); | |
c77582fe | 148 | var sectionlist = Y.Node.all(this.sectionlistselector); |
15e2552f RK |
149 | |
150 | // Add lightbox if it not there | |
151 | var lightbox = M.util.add_lightbox(Y, dragnode); | |
152 | ||
153 | var params = {}; | |
154 | ||
155 | // Handle any variables which we must pass back through to | |
156 | var pageparams = this.get('config').pageparams; | |
157 | for (varname in pageparams) { | |
158 | params[varname] = pageparams[varname]; | |
159 | } | |
160 | ||
161 | // Prepare request parameters | |
162 | params.sesskey = M.cfg.sesskey; | |
163 | params.courseId = this.get('courseid'); | |
164 | params['class'] = 'section'; | |
165 | params.field = 'move'; | |
166 | params.id = dragnodeid; | |
eb01aa2c | 167 | params.value = dropnodeid; |
15e2552f RK |
168 | |
169 | // Do AJAX request | |
170 | var uri = M.cfg.wwwroot + this.get('ajaxurl'); | |
171 | ||
172 | Y.io(uri, { | |
173 | method: 'POST', | |
174 | data: params, | |
175 | on: { | |
176 | start : function(tid) { | |
177 | lightbox.show(); | |
178 | }, | |
179 | success: function(tid, response) { | |
9f3015ec RK |
180 | // Update section titles, we can't simply swap them as |
181 | // they might have custom title | |
182 | try { | |
183 | var responsetext = Y.JSON.parse(response.responseText); | |
184 | if (responsetext.error) { | |
185 | new M.core.ajaxException(responsetext); | |
186 | } | |
187 | M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend); | |
188 | } catch (e) {} | |
189 | ||
15e2552f RK |
190 | // Classic bubble sort algorithm is applied to the section |
191 | // nodes between original drag node location and the new one. | |
192 | do { | |
193 | var swapped = false; | |
194 | for (var i = loopstart; i <= loopend; i++) { | |
195 | if (this.get_section_id(sectionlist.item(i-1)) > this.get_section_id(sectionlist.item(i))) { | |
196 | // Swap section id | |
197 | var sectionid = sectionlist.item(i-1).get('id'); | |
198 | sectionlist.item(i-1).set('id', sectionlist.item(i).get('id')); | |
199 | sectionlist.item(i).set('id', sectionid); | |
405eaac2 RK |
200 | // See what format needs to swap |
201 | M.course.format.swap_sections(Y, i-1, i); | |
15e2552f RK |
202 | // Update flag |
203 | swapped = true; | |
204 | } | |
205 | } | |
206 | loopend = loopend - 1; | |
207 | } while (swapped); | |
9f3015ec RK |
208 | |
209 | // Finally, hide the lightbox | |
210 | window.setTimeout(function(e) { | |
211 | lightbox.hide(); | |
212 | }, 250); | |
15e2552f RK |
213 | }, |
214 | failure: function(tid, response) { | |
215 | this.ajax_failure(response); | |
216 | lightbox.hide(); | |
217 | } | |
218 | }, | |
219 | context:this | |
220 | }); | |
221 | } | |
222 | ||
223 | }, { | |
224 | NAME : 'course-dragdrop-section', | |
225 | ATTRS : { | |
226 | courseid : { | |
227 | value : null | |
228 | }, | |
229 | ajaxurl : { | |
230 | 'value' : 0 | |
231 | }, | |
232 | config : { | |
233 | 'value' : 0 | |
234 | } | |
235 | } | |
236 | }); | |
237 | ||
238 | var DRAGRESOURCE = function() { | |
239 | DRAGRESOURCE.superclass.constructor.apply(this, arguments); | |
240 | }; | |
241 | Y.extend(DRAGRESOURCE, M.core.dragdrop, { | |
242 | initializer : function(params) { | |
243 | // Set group for parent class | |
244 | this.groups = ['resource']; | |
245 | this.samenodeclass = CSS.ACTIVITY; | |
246 | this.parentnodeclass = CSS.SECTION; | |
247 | ||
248 | // Go through all sections | |
405eaac2 RK |
249 | var sectionlistselector = M.course.format.get_section_selector(Y); |
250 | if (sectionlistselector) { | |
251 | sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector; | |
d95b77bd RK |
252 | this.setup_for_section(sectionlistselector); |
253 | M.course.coursebase.register_module(this); | |
254 | M.course.dragres = this; | |
c77582fe | 255 | } |
15e2552f RK |
256 | }, |
257 | ||
258 | /** | |
259 | * Apply dragdrop features to the specified selector or node that refers to section(s) | |
260 | * | |
261 | * @param baseselector The CSS selector or node to limit scope to | |
262 | * @return void | |
263 | */ | |
264 | setup_for_section : function(baseselector) { | |
265 | Y.Node.all(baseselector).each(function(sectionnode) { | |
266 | var resources = sectionnode.one('.'+CSS.CONTENT+' ul.'+CSS.SECTION); | |
267 | // See if resources ul exists, if not create one | |
268 | if (!resources) { | |
269 | var resources = Y.Node.create('<ul></ul>'); | |
270 | resources.addClass(CSS.SECTION); | |
271 | sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after'); | |
272 | } | |
273 | ||
274 | // Define each ul as droptarget, so that item could be moved to empty list | |
275 | var tar = new Y.DD.Drop({ | |
276 | node: resources, | |
277 | groups: this.groups, | |
278 | padding: '20 0 20 0' | |
279 | }); | |
280 | // Go through each li element and make them draggable | |
405eaac2 | 281 | this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY); |
15e2552f RK |
282 | }, this); |
283 | }, | |
284 | /** | |
285 | * Apply dragdrop features to the specified selector or node that refers to resource(s) | |
286 | * | |
287 | * @param baseselector The CSS selector or node to limit scope to | |
288 | * @return void | |
289 | */ | |
290 | setup_for_resource : function(baseselector) { | |
291 | Y.Node.all(baseselector).each(function(resourcesnode) { | |
292 | // Replace move icons | |
293 | var move = resourcesnode.one('a.'+CSS.EDITINGMOVE); | |
294 | if (move) { | |
295 | move.replace(this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS)); | |
296 | // Make each li element in the lists of sections draggable | |
297 | var dd = new Y.DD.Drag({ | |
298 | node: resourcesnode, | |
299 | groups: this.groups, | |
300 | // Make each li a Drop target too | |
301 | target: true, | |
302 | handles: ['.' + CSS.EDITINGMOVE] | |
303 | }).plug(Y.Plugin.DDProxy, { | |
304 | // Don't move the node at the end of the drag | |
305 | moveOnEnd: false | |
306 | }).plug(Y.Plugin.DDConstrained, { | |
307 | // Keep it inside the .course-content | |
308 | constrain: '#'+CSS.PAGECONTENT | |
26992cf1 | 309 | }).plug(Y.Plugin.DDWinScroll); |
15e2552f RK |
310 | } |
311 | }, this); | |
312 | }, | |
313 | ||
314 | get_section_id : function(node) { | |
315 | return Number(node.get('id').replace(/section-/i, '')); | |
316 | }, | |
317 | ||
318 | get_resource_id : function(node) { | |
319 | return Number(node.get('id').replace(/module-/i, '')); | |
320 | }, | |
321 | ||
322 | drag_start : function(e) { | |
323 | // Get our drag object | |
324 | var drag = e.target; | |
325 | drag.get('dragNode').setContent(drag.get('node').get('innerHTML')); | |
326 | drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline'); | |
327 | }, | |
328 | ||
243e9bf9 RK |
329 | drag_dropmiss : function(e) { |
330 | // Missed the target, but we assume the user intended to drop it | |
331 | // on the last last ghost node location, e.drag and e.drop should be | |
332 | // prepared by global_drag_dropmiss parent so simulate drop_hit(e). | |
333 | this.drop_hit(e); | |
334 | }, | |
335 | ||
15e2552f RK |
336 | drop_hit : function(e) { |
337 | var drag = e.drag; | |
338 | // Get a reference to our drag node | |
339 | var dragnode = drag.get('node'); | |
340 | var dropnode = e.drop.get('node'); | |
341 | ||
45b364b9 ARN |
342 | // Add spinner if it not there |
343 | var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN)); | |
344 | ||
15e2552f RK |
345 | var params = {}; |
346 | ||
347 | // Handle any variables which we must pass back through to | |
348 | var pageparams = this.get('config').pageparams; | |
349 | for (varname in pageparams) { | |
350 | params[varname] = pageparams[varname]; | |
351 | } | |
352 | ||
353 | // Prepare request parameters | |
354 | params.sesskey = M.cfg.sesskey; | |
355 | params.courseId = this.get('courseid'); | |
356 | params['class'] = 'resource'; | |
357 | params.field = 'move'; | |
358 | params.id = Number(this.get_resource_id(dragnode)); | |
405eaac2 | 359 | params.sectionId = this.get_section_id(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true)); |
15e2552f RK |
360 | |
361 | if (dragnode.next()) { | |
362 | params.beforeId = Number(this.get_resource_id(dragnode.next())); | |
363 | } | |
364 | ||
365 | // Do AJAX request | |
366 | var uri = M.cfg.wwwroot + this.get('ajaxurl'); | |
367 | ||
368 | Y.io(uri, { | |
369 | method: 'POST', | |
370 | data: params, | |
371 | on: { | |
372 | start : function(tid) { | |
373 | this.lock_drag_handle(drag, CSS.EDITINGMOVE); | |
45b364b9 | 374 | spinner.show(); |
15e2552f RK |
375 | }, |
376 | success: function(tid, response) { | |
377 | this.unlock_drag_handle(drag, CSS.EDITINGMOVE); | |
45b364b9 ARN |
378 | window.setTimeout(function(e) { |
379 | spinner.hide(); | |
380 | }, 250); | |
15e2552f RK |
381 | }, |
382 | failure: function(tid, response) { | |
383 | this.ajax_failure(response); | |
384 | this.unlock_drag_handle(drag, CSS.SECTIONHANDLE); | |
45b364b9 | 385 | spinner.hide(); |
15e2552f RK |
386 | // TODO: revert nodes location |
387 | } | |
388 | }, | |
389 | context:this | |
390 | }); | |
391 | } | |
392 | }, { | |
393 | NAME : 'course-dragdrop-resource', | |
394 | ATTRS : { | |
395 | courseid : { | |
396 | value : null | |
397 | }, | |
398 | ajaxurl : { | |
399 | 'value' : 0 | |
400 | }, | |
401 | config : { | |
402 | 'value' : 0 | |
403 | } | |
404 | } | |
405 | }); | |
406 | ||
c77582fe RK |
407 | M.course = M.course || {}; |
408 | M.course.init_resource_dragdrop = function(params) { | |
15e2552f RK |
409 | new DRAGRESOURCE(params); |
410 | } | |
c77582fe | 411 | M.course.init_section_dragdrop = function(params) { |
15e2552f RK |
412 | new DRAGSECTION(params); |
413 | } | |
d2a27ab0 | 414 | }, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification', 'moodle-course-coursebase']}); |