Merge branch 'MDL-67818-check-api-fixes' of https://github.com/brendanheywood/moodle
[moodle.git] / course / yui / src / dragdrop / js / section.js
1 /**
2  * Section drag and drop.
3  *
4  * @class M.course.dragdrop.section
5  * @constructor
6  * @extends M.core.dragdrop
7  */
8 var DRAGSECTION = function() {
9     DRAGSECTION.superclass.constructor.apply(this, arguments);
10 };
11 Y.extend(DRAGSECTION, M.core.dragdrop, {
12     sectionlistselector: null,
14     initializer: function() {
15         // Set group for parent class
16         this.groups = [CSS.SECTIONDRAGGABLE];
17         this.samenodeclass = M.course.format.get_sectionwrapperclass();
18         this.parentnodeclass = M.course.format.get_containerclass();
19         // Detect the direction of travel.
20         this.detectkeyboarddirection = true;
22         // Check if we are in single section mode
23         if (Y.Node.one('.' + CSS.JUMPMENU)) {
24             return false;
25         }
26         // Initialise sections dragging
27         this.sectionlistselector = M.course.format.get_section_wrapper(Y);
28         if (this.sectionlistselector) {
29             this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector;
31             this.setup_for_section(this.sectionlistselector);
33             // Make each li element in the lists of sections draggable
34             var del = new Y.DD.Delegate({
35                 container: '.' + CSS.COURSECONTENT,
36                 nodes: '.' + CSS.SECTIONDRAGGABLE,
37                 target: true,
38                 handles: ['.' + CSS.LEFT],
39                 dragConfig: {groups: this.groups}
40             });
41             del.dd.plug(Y.Plugin.DDProxy, {
42                 // Don't move the node at the end of the drag
43                 moveOnEnd: false
44             });
45             del.dd.plug(Y.Plugin.DDConstrained, {
46                 // Keep it inside the .course-content
47                 constrain: '#' + CSS.PAGECONTENT,
48                 stickY: true
49             });
50             del.dd.plug(Y.Plugin.DDWinScroll);
51         }
52     },
54      /**
55      * Apply dragdrop features to the specified selector or node that refers to section(s)
56      *
57      * @method setup_for_section
58      * @param {String} baseselector The CSS selector or node to limit scope to
59      */
60     setup_for_section: function(baseselector) {
61         Y.Node.all(baseselector).each(function(sectionnode) {
62             // Determine the section ID
63             var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode);
65             // We skip the top section as it is not draggable
66             if (sectionid > 0) {
67                 // Remove move icons
68                 var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN);
69                 var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP);
71                 // Add dragger icon
72                 var title = M.util.get_string('movesection', 'moodle', sectionid);
73                 var cssleft = sectionnode.one('.' + CSS.LEFT);
75                 if ((movedown || moveup) && cssleft) {
76                     cssleft.setStyle('cursor', 'move');
77                     cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
79                     if (moveup) {
80                         if (moveup.previous('br')) {
81                             moveup.previous('br').remove();
82                         } else if (moveup.next('br')) {
83                             moveup.next('br').remove();
84                         }
86                         if (moveup.ancestor('.section_action_menu') && moveup.ancestor().get('nodeName').toLowerCase() == 'li') {
87                             moveup.ancestor().remove();
88                         } else {
89                             moveup.remove();
90                         }
91                     }
92                     if (movedown) {
93                         if (movedown.previous('br')) {
94                             movedown.previous('br').remove();
95                         } else if (movedown.next('br')) {
96                             movedown.next('br').remove();
97                         }
99                         var movedownParentType = movedown.ancestor().get('nodeName').toLowerCase();
100                         if (movedown.ancestor('.section_action_menu') && movedownParentType == 'li') {
101                             movedown.ancestor().remove();
102                         } else {
103                             movedown.remove();
104                         }
105                     }
107                     // This section can be moved - add the class to indicate this to Y.DD.
108                     sectionnode.addClass(CSS.SECTIONDRAGGABLE);
109                 }
110             }
111         }, this);
112     },
114     /*
115      * Drag-dropping related functions
116      */
117     drag_start: function(e) {
118         // Get our drag object
119         var drag = e.target;
120         // This is the node that the user started to drag.
121         var node = drag.get('node');
122         // This is the container node that will follow the mouse around,
123         // or during a keyboard drag and drop the original node.
124         var dragnode = drag.get('dragNode');
125         if (node === dragnode) {
126             return;
127         }
128         // Creat a dummy structure of the outer elemnents for clean styles application
129         var containernode = Y.Node.create('<' + M.course.format.get_containernode() +
130                 '></' + M.course.format.get_containernode() + '>');
131         containernode.addClass(M.course.format.get_containerclass());
132         var sectionnode = Y.Node.create('<' + M.course.format.get_sectionwrappernode() +
133                 '></' + M.course.format.get_sectionwrappernode() + '>');
134         sectionnode.addClass(M.course.format.get_sectionwrapperclass());
135         sectionnode.setStyle('margin', 0);
136         sectionnode.setContent(node.get('innerHTML'));
137         containernode.appendChild(sectionnode);
138         dragnode.setContent(containernode);
139         dragnode.addClass(CSS.COURSECONTENT);
140     },
142     drag_dropmiss: function(e) {
143         // Missed the target, but we assume the user intended to drop it
144         // on the last last ghost node location, e.drag and e.drop should be
145         // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
146         this.drop_hit(e);
147     },
149     get_section_index: function(node) {
150         var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.course.format.get_section_selector(Y),
151             sectionList = Y.all(sectionlistselector),
152             nodeIndex = sectionList.indexOf(node),
153             zeroIndex = sectionList.indexOf(Y.one('#section-0'));
155         return (nodeIndex - zeroIndex);
156     },
158     drop_hit: function(e) {
159         var drag = e.drag;
161         // Get references to our nodes and their IDs.
162         var dragnode = drag.get('node'),
163             dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode),
164             loopstart = dragnodeid,
166             dropnodeindex = this.get_section_index(dragnode),
167             loopend = dropnodeindex;
169         if (dragnodeid === dropnodeindex) {
170             Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-course-dragdrop');
171             return;
172         }
174         Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-course-dragdrop');
176         if (loopstart > loopend) {
177             // If we're going up, we need to swap the loop order
178             // because loops can't go backwards.
179             loopstart = dropnodeindex;
180             loopend = dragnodeid;
181         }
183         // Get the list of nodes.
184         drag.get('dragNode').removeClass(CSS.COURSECONTENT);
185         var sectionlist = Y.Node.all(this.sectionlistselector);
187         // Add a lightbox if it's not there.
188         var lightbox = M.util.add_lightbox(Y, dragnode);
190         // Handle any variables which we must pass via AJAX.
191         var params = {},
192             pageparams = this.get('config').pageparams,
193             varname;
195         for (varname in pageparams) {
196             if (!pageparams.hasOwnProperty(varname)) {
197                 continue;
198             }
199             params[varname] = pageparams[varname];
200         }
202         // Prepare request parameters
203         params.sesskey = M.cfg.sesskey;
204         params.courseId = this.get('courseid');
205         params['class'] = 'section';
206         params.field = 'move';
207         params.id = dragnodeid;
208         params.value = dropnodeindex;
210         // Perform the AJAX request.
211         var uri = M.cfg.wwwroot + this.get('ajaxurl');
212         Y.io(uri, {
213             method: 'POST',
214             data: params,
215             on: {
216                 start: function() {
217                     lightbox.show();
218                 },
219                 success: function(tid, response) {
220                     // Update section titles, we can't simply swap them as
221                     // they might have custom title
222                     try {
223                         var responsetext = Y.JSON.parse(response.responseText);
224                         if (responsetext.error) {
225                             new M.core.ajaxException(responsetext);
226                         }
227                         M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
228                     } catch (e) {
229                         // Ignore.
230                     }
232                     // Update all of the section IDs - first unset them, then set them
233                     // to avoid duplicates in the DOM.
234                     var index;
236                     // Classic bubble sort algorithm is applied to the section
237                     // nodes between original drag node location and the new one.
238                     var swapped = false;
239                     do {
240                         swapped = false;
241                         for (index = loopstart; index <= loopend; index++) {
242                             if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) >
243                                         Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) {
244                                 Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) +
245                                         " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index)));
246                                 // Swap section id.
247                                 var sectionid = sectionlist.item(index - 1).get('id');
248                                 sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id'));
249                                 sectionlist.item(index).set('id', sectionid);
251                                 // See what format needs to swap.
252                                 M.course.format.swap_sections(Y, index - 1, index);
254                                 // Update flag.
255                                 swapped = true;
256                             }
257                         }
258                         loopend = loopend - 1;
259                     } while (swapped);
261                     window.setTimeout(function() {
262                         lightbox.hide();
263                     }, 250);
264                 },
266                 failure: function(tid, response) {
267                     this.ajax_failure(response);
268                     lightbox.hide();
269                 }
270             },
271             context: this
272         });
273     }
275 }, {
276     NAME: 'course-dragdrop-section',
277     ATTRS: {
278         courseid: {
279             value: null
280         },
281         ajaxurl: {
282             value: 0
283         },
284         config: {
285             value: 0
286         }
287     }
288 });
290 M.course = M.course || {};
291 M.course.init_section_dragdrop = function(params) {
292     new DRAGSECTION(params);
293 };