MDL-32873 Separate out course ajax enabled detection into new function
[moodle.git] / course / yui / toolboxes / toolboxes.js
CommitLineData
ebaa29d1
ARN
1YUI.add('moodle-course-toolboxes', function(Y) {
2 WAITICON = {'pix':"i/loading_small",'component':'moodle'};
3 // The CSS selectors we use
4 var CSS = {
5 ACTIVITYLI : 'li.activity',
178f3514 6 COMMANDSPAN : 'li.activity span.commands',
b3c35188 7 SPINNERCOMMANDSPAN : 'span.commands',
eac3ed52 8 CONTENTAFTERLINK : 'div.contentafterlink',
ebaa29d1
ARN
9 DELETE : 'a.editing_delete',
10 DIMCLASS : 'dimmed',
eac3ed52 11 DIMMEDTEXT : 'dimmed_text',
ebaa29d1
ARN
12 EDITTITLECLASS : 'edittitle',
13 GENERICICONCLASS : 'iconsmall',
14 GROUPSNONE : 'a.editing_groupsnone',
15 GROUPSSEPARATE : 'a.editing_groupsseparate',
16 GROUPSVISIBLE : 'a.editing_groupsvisible',
17 HASLABEL : 'label',
18 HIDE : 'a.editing_hide',
19 HIGHLIGHT : 'a.editing_highlight',
20 INSTANCENAME : 'span.instancename',
21 LIGHTBOX : 'lightbox',
22 MODINDENTCOUNT : 'mod-indent-',
23 MODINDENTDIV : 'div.mod-indent',
95ef704d 24 MODINDENTHUGE : 'mod-indent-huge',
ebaa29d1
ARN
25 MODULEIDPREFIX : 'module-',
26 MOVELEFT : 'a.editing_moveleft',
27 MOVELEFTCLASS : 'editing_moveleft',
28 MOVERIGHT : 'a.editing_moveright',
29 PAGECONTENT : 'div#page-content',
30 RIGHTDIV : 'div.right',
31 SECTIONHIDDENCLASS : 'hidden',
32 SECTIONIDPREFIX : 'section-',
33 SECTIONLI : 'li.section',
34 SHOW : 'a.editing_show',
35 SHOWHIDE : 'a.editing_showhide'
36 };
37
38 /**
39 * The toolbox classes
40 *
41 * TOOLBOX is a generic class which should never be directly instantiated
42 * RESOURCETOOLBOX is a class extending TOOLBOX containing code specific to resources
43 * SECTIONTOOLBOX is a class extending TOOLBOX containing code specific to sections
44 */
45 var TOOLBOX = function() {
46 TOOLBOX.superclass.constructor.apply(this, arguments);
47 }
48
49 Y.extend(TOOLBOX, Y.Base, {
50 /**
51 * Replace the button click at the selector with the specified
52 * callback
53 *
54 * @param toolboxtarget The selector of the working area
55 * @param selector The 'button' to replace
56 * @param callback The callback to apply
57 * @param cursor An optional cursor style to apply
58 */
59 replace_button : function(toolboxtarget, selector, callback, cursor) {
60 if (!cursor) {
61 // Set the default cursor type to pointer to match the
62 // anchor
63 cursor = 'pointer';
64 }
65 var button = Y.one(toolboxtarget).all(selector)
ebaa29d1
ARN
66 .setStyle('cursor', cursor);
67
68 // on isn't chainable and will return an event
69 button.on('click', callback, this);
70
71 return button;
72 },
73 /**
74 * Toggle the visibility and availability for the specified
75 * resource show/hide button
76 */
77 toggle_hide_resource_ui : function(button) {
78 var element = button.ancestor(CSS.ACTIVITYLI);
79 var hideicon = button.one('img');
80
81 var dimarea;
82 var toggle_class;
83 if (this.is_label(element)) {
eac3ed52 84 toggle_class = CSS.DIMMEDTEXT;
ebaa29d1
ARN
85 dimarea = element.one(CSS.MODINDENTDIV + ' div');
86 } else {
87 toggle_class = CSS.DIMCLASS;
88 dimarea = element.one('a');
89 }
90
91 var status = '';
92 var value;
93 if (dimarea.hasClass(toggle_class)) {
94 status = 'hide';
95 value = 1;
96 } else {
97 status = 'show';
98 value = 0;
99 }
100
101 // Change the UI
102 dimarea.toggleClass(toggle_class);
eac3ed52
ARN
103 // We need to toggle dimming on the description too
104 element.all(CSS.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
ebaa29d1
ARN
105 var newstring = M.util.get_string(status, 'moodle');
106 hideicon.setAttrs({
107 'alt' : newstring,
108 'title' : newstring,
109 'src' : M.util.image_url('t/' + status)
110 });
111 button.set('title', newstring);
112 button.set('className', 'editing_'+status);
113
114 return value;
115 },
116 /**
117 * Send a request using the REST API
118 *
119 * @param data The data to submit
45b364b9 120 * @param statusspinner (optional) A statusspinner which may contain a section loader
ebaa29d1
ARN
121 * @param optionalconfig (optional) Any additional configuration to submit
122 * @return response responseText field from responce
123 */
45b364b9 124 send_request : function(data, statusspinner, optionalconfig) {
ebaa29d1
ARN
125 // Default data structure
126 if (!data) {
127 data = {};
128 }
129 // Handle any variables which we must pass back through to
130 var pageparams = this.get('config').pageparams;
131 for (varname in pageparams) {
132 data[varname] = pageparams[varname];
133 }
134
ebaa29d1
ARN
135 data.sesskey = M.cfg.sesskey;
136 data.courseId = this.get('courseid');
137
138 var uri = M.cfg.wwwroot + this.get('ajaxurl');
139
140 // Define the configuration to send with the request
141 var responsetext = [];
142 var config = {
143 method: 'POST',
144 data: data,
145 on: {
146 success: function(tid, response) {
147 try {
148 responsetext = Y.JSON.parse(response.responseText);
149 if (responsetext.error) {
150 new M.core.ajaxException(responsetext);
151 }
152 } catch (e) {}
45b364b9 153 if (statusspinner) {
ebaa29d1 154 window.setTimeout(function(e) {
45b364b9
ARN
155 statusspinner.hide();
156 }, 400);
ebaa29d1
ARN
157 }
158 },
159 failure : function(tid, response) {
45b364b9
ARN
160 if (statusspinner) {
161 statusspinner.hide();
ebaa29d1
ARN
162 }
163 new M.core.ajaxException(response);
164 }
165 },
166 context: this,
167 sync: true
168 }
169
170 // Apply optional config
171 if (optionalconfig) {
172 for (varname in optionalconfig) {
173 config[varname] = optionalconfig[varname];
174 }
175 }
176
45b364b9
ARN
177 if (statusspinner) {
178 statusspinner.show();
ebaa29d1
ARN
179 }
180
181 // Send the request
182 Y.io(uri, config);
183 return responsetext;
184 },
185 is_label : function(target) {
186 return target.hasClass(CSS.HASLABEL);
187 },
188 /**
189 * Return the module ID for the specified element
190 *
191 * @param element The <li> element to determine a module-id number for
192 * @return string The module ID
193 */
194 get_element_id : function(element) {
195 return element.get('id').replace(CSS.MODULEIDPREFIX, '');
196 },
197 /**
198 * Return the module ID for the specified element
199 *
200 * @param element The <li> element to determine a module-id number for
201 * @return string The module ID
202 */
203 get_section_id : function(section) {
204 return section.get('id').replace(CSS.SECTIONIDPREFIX, '');
ebaa29d1
ARN
205 }
206 },
207 {
208 NAME : 'course-toolbox',
209 ATTRS : {
210 // The ID of the current course
211 courseid : {
212 'value' : 0
213 },
214 ajaxurl : {
215 'value' : 0
216 },
217 config : {
218 'value' : 0
219 }
220 }
221 }
222 );
223
224
225 var RESOURCETOOLBOX = function() {
226 RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
227 }
228
229 Y.extend(RESOURCETOOLBOX, TOOLBOX, {
230 // Variables
231 GROUPS_NONE : 0,
232 GROUPS_SEPARATE : 1,
233 GROUPS_VISIBLE : 2,
234
235 /**
236 * Initialize the resource toolbox
237 *
238 * Updates all span.commands with relevant handlers and other required changes
239 */
240 initializer : function(config) {
241 this.setup_for_resource();
242 M.course.coursebase.register_module(this);
243 },
244
245 /**
246 * Update any span.commands within the scope of the specified
247 * selector with AJAX equivelants
248 *
249 * @param baseselector The selector to limit scope to
250 * @return void
251 */
252 setup_for_resource : function(baseselector) {
253 if (!baseselector) {
254 var baseselector = CSS.PAGECONTENT;
255 }
256
257 Y.all(baseselector).each(this._setup_for_resource, this);
258 },
259 _setup_for_resource : function(toolboxtarget) {
260 // Move left and right
261 this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.MOVELEFT, this.move_left);
262 this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.MOVERIGHT, this.move_right);
263
264 // Delete
265 this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.DELETE, this.delete_resource);
266
267 // Show/Hide
268 var showhide = this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.HIDE, this.toggle_hide_resource);
269 var shown = this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.SHOW, this.toggle_hide_resource);
270
271 showhide = showhide.concat(shown);
272 showhide.each(function(node) {
273 var section = node.ancestor(CSS.SECTIONLI);
274 if (section && section.hasClass(CSS.SECTIONHIDDENCLASS)) {
275 node.setStyle('cursor', 'auto');
276 }
277 });
278
279 // Change Group Mode
280 var groups;
281 groups = this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.GROUPSNONE, this.toggle_groupmode);
282 groups.setAttribute('groupmode', this.GROUPS_NONE);
283
284 groups = this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.GROUPSSEPARATE, this.toggle_groupmode);
285 groups.setAttribute('groupmode', this.GROUPS_SEPARATE);
286
287 groups = this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.GROUPSVISIBLE, this.toggle_groupmode);
288 groups.setAttribute('groupmode', this.GROUPS_VISIBLE);
289 },
290 move_left : function(e) {
45b364b9 291 this.move_leftright(e, -1);
ebaa29d1
ARN
292 },
293 move_right : function(e) {
45b364b9 294 this.move_leftright(e, 1);
ebaa29d1 295 },
45b364b9 296 move_leftright : function(e, direction) {
5bd03e12
ARN
297 // Prevent the default button action
298 e.preventDefault();
299
ebaa29d1
ARN
300 // Get the element we're working on
301 var element = e.target.ancestor(CSS.ACTIVITYLI);
302
303 // And we need to determine the current and new indent level
304 var indentdiv = element.one(CSS.MODINDENTDIV);
305 var indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/);
306
307 if (indent) {
308 var oldindent = parseInt(indent[1]);
309 var newindent = Math.max(0, (oldindent + parseInt(direction)));
310 indentdiv.removeClass(indent[0]);
311 } else {
312 var oldindent = 0;
313 var newindent = 1;
314 }
315
316 // Perform the move
317 indentdiv.addClass(CSS.MODINDENTCOUNT + newindent);
318 var data = {
319 'class' : 'resource',
320 'field' : 'indent',
321 'value' : newindent,
322 'id' : this.get_element_id(element)
323 };
b3c35188 324 var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
45b364b9 325 this.send_request(data, spinner);
ebaa29d1
ARN
326
327 // Handle removal/addition of the moveleft button
328 if (newindent == 0) {
45b364b9 329 element.one(CSS.MOVELEFT).remove();
ebaa29d1
ARN
330 } else if (newindent == 1 && oldindent == 0) {
331 this.add_moveleft(element);
332 }
95ef704d
ARN
333
334 // Handle massive indentation to match non-ajax display
335 var hashugeclass = indentdiv.hasClass(CSS.MODINDENTHUGE);
336 if (newindent > 15 && !hashugeclass) {
337 indentdiv.addClass(CSS.MODINDENTHUGE);
338 } else if (newindent <= 15 && hashugeclass) {
339 indentdiv.removeClass(CSS.MODINDENTHUGE);
340 }
ebaa29d1
ARN
341 },
342 delete_resource : function(e) {
5bd03e12
ARN
343 // Prevent the default button action
344 e.preventDefault();
345
ebaa29d1
ARN
346 // Get the element we're working on
347 var element = e.target.ancestor(CSS.ACTIVITYLI);
348
349 var confirmstring = '';
350 if (this.is_label(element)) {
351 // Labels are slightly different to other activities
352 var plugindata = {
353 type : M.util.get_string('pluginname', 'label')
354 }
355 confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata)
356 } else {
357 var plugindata = {
358 type : M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1]),
359 name : element.one(CSS.INSTANCENAME).get('firstChild').get('data')
360 }
361 confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
362 }
363
364 // Confirm element removal
365 if (!confirm(confirmstring)) {
366 return false;
367 }
368
369 // Actually remove the element
370 element.remove();
371 var data = {
372 'class' : 'resource',
373 'action' : 'DELETE',
374 'id' : this.get_element_id(element)
375 };
376 this.send_request(data);
377 },
378 toggle_hide_resource : function(e) {
5bd03e12
ARN
379 // Prevent the default button action
380 e.preventDefault();
381
ebaa29d1
ARN
382 // Return early if the current section is hidden
383 var section = e.target.ancestor(CSS.SECTIONLI);
384 if (section && section.hasClass(CSS.SECTIONHIDDENCLASS)) {
385 return;
386 }
387
388 // Get the element we're working on
389 var element = e.target.ancestor(CSS.ACTIVITYLI);
390
391 var button = e.target.ancestor('a', true);
392
393 var value = this.toggle_hide_resource_ui(button);
394
395 // Send the request
396 var data = {
397 'class' : 'resource',
398 'field' : 'visible',
399 'value' : value,
400 'id' : this.get_element_id(element)
401 };
b3c35188 402 var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
45b364b9 403 this.send_request(data, spinner);
ebaa29d1
ARN
404 },
405 toggle_groupmode : function(e) {
5bd03e12
ARN
406 // Prevent the default button action
407 e.preventDefault();
408
ebaa29d1
ARN
409 // Get the element we're working on
410 var element = e.target.ancestor(CSS.ACTIVITYLI);
411
412 var button = e.target.ancestor('a', true);
413 var icon = button.one('img');
414
415 // Current Mode
416 var groupmode = button.getAttribute('groupmode');
417 groupmode++;
418 if (groupmode > 2) {
419 groupmode = 0;
420 }
421 button.setAttribute('groupmode', groupmode);
422
423 var newtitle = '';
424 var iconsrc = '';
425 switch (groupmode) {
426 case this.GROUPS_NONE:
427 newtitle = 'groupsnone';
428 iconsrc = M.util.image_url('t/groupn');
429 break;
430 case this.GROUPS_SEPARATE:
431 newtitle = 'groupsseparate';
432 iconsrc = M.util.image_url('t/groups');
433 break;
434 case this.GROUPS_VISIBLE:
435 newtitle = 'groupsvisible';
436 iconsrc = M.util.image_url('t/groupv');
437 break;
438 }
439 newtitle = M.util.get_string('clicktochangeinbrackets', 'moodle',
440 M.util.get_string(newtitle, 'moodle'));
441
442 // Change the UI
443 icon.setAttrs({
444 'alt' : newtitle,
445 'title' : newtitle,
446 'src' : iconsrc
447 });
448 button.setAttribute('title', newtitle);
449
450 // And send the request
451 var data = {
452 'class' : 'resource',
453 'field' : 'groupmode',
454 'value' : groupmode,
455 'id' : this.get_element_id(element)
456 };
b3c35188 457 var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
45b364b9 458 this.send_request(data, spinner);
ebaa29d1
ARN
459 },
460 /**
461 * Add the moveleft button
462 * This is required after moving left from an initial position of 0
463 *
464 * @param target The encapsulating <li> element
465 */
466 add_moveleft : function(target) {
467 var left_string = M.util.get_string('moveleft', 'moodle');
468 var newicon = Y.Node.create('<img />')
469 .addClass(CSS.GENERICICONCLASS)
470 .setAttrs({
471 'src' : M.util.image_url('t/left', 'moodle'),
472 'title' : left_string,
473 'alt' : left_string
474 });
5bd03e12
ARN
475 var moveright = target.one(CSS.MOVERIGHT);
476 var newlink = moveright.getAttribute('href').replace('indent=1', 'indent=-1');
ebaa29d1
ARN
477 var anchor = new Y.Node.create('<a />')
478 .setStyle('cursor', 'pointer')
479 .addClass(CSS.MOVELEFTCLASS)
5bd03e12
ARN
480 .setAttribute('href', newlink)
481 .setAttribute('title', left_string);
ebaa29d1
ARN
482 anchor.appendChild(newicon);
483 anchor.on('click', this.move_left, this);
5bd03e12 484 moveright.insert(anchor, 'before');
ebaa29d1
ARN
485 }
486 }, {
487 NAME : 'course-resource-toolbox',
488 ATTRS : {
489 courseid : {
490 'value' : 0
491 },
492 format : {
493 'value' : 'topics'
494 }
495 }
496 });
497
498 var SECTIONTOOLBOX = function() {
499 SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
500 }
501
502 Y.extend(SECTIONTOOLBOX, TOOLBOX, {
503 /**
504 * Initialize the toolboxes module
505 *
506 * Updates all span.commands with relevant handlers and other required changes
507 */
508 initializer : function(config) {
509 this.setup_for_section();
510 M.course.coursebase.register_module(this);
511 },
512 /**
513 * Update any section areas within the scope of the specified
514 * selector with AJAX equivelants
515 *
516 * @param baseselector The selector to limit scope to
517 * @return void
518 */
519 setup_for_section : function(baseselector) {
520 if (!baseselector) {
521 var baseselector = CSS.PAGECONTENT;
522 }
523
524 Y.all(baseselector).each(this._setup_for_section, this);
525 },
526 _setup_for_section : function(toolboxtarget) {
527 // Section Highlighting
528 this.replace_button(toolboxtarget, CSS.RIGHTDIV + ' ' + CSS.HIGHLIGHT, this.toggle_highlight);
529
530 // Section Visibility
531 this.replace_button(toolboxtarget, CSS.RIGHTDIV + ' ' + CSS.SHOWHIDE, this.toggle_hide_section);
532 },
533 toggle_hide_section : function(e) {
5bd03e12
ARN
534 // Prevent the default button action
535 e.preventDefault();
536
ebaa29d1
ARN
537 // Get the section we're working on
538 var section = e.target.ancestor(CSS.SECTIONLI);
539 var button = e.target.ancestor('a', true);
540 var hideicon = button.one('img');
541
542 // The value to submit
543 var value;
544 // The status text for strings and images
545 var status;
546
547 if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
548 section.addClass(CSS.SECTIONHIDDENCLASS);
549 value = 0;
550 status = 'show';
551
552 } else {
553 section.removeClass(CSS.SECTIONHIDDENCLASS);
554 value = 1;
555 status = 'hide';
556 }
557
558 var newstring = M.util.get_string(status + 'fromothers', 'format_' + this.get('format'));
559 hideicon.setAttrs({
560 'alt' : newstring,
561 'title' : newstring,
562 'src' : M.util.image_url('i/' + status)
563 });
564 button.set('title', newstring);
565
566 // Change the highlight status
567 var data = {
568 'class' : 'section',
569 'field' : 'visible',
570 'id' : this.get_section_id(section),
571 'value' : value
572 };
573
6a14c4ff 574 var lightbox = M.util.add_lightbox(Y, section);
ebaa29d1
ARN
575 lightbox.show();
576
45b364b9 577 var response = this.send_request(data, lightbox);
ebaa29d1
ARN
578
579 var activities = section.all(CSS.ACTIVITYLI);
580 activities.each(function(node) {
581 if (node.one(CSS.SHOW)) {
582 var button = node.one(CSS.SHOW);
583 } else {
584 var button = node.one(CSS.HIDE);
585 }
586 var activityid = this.get_element_id(node);
587
588 if (Y.Array.indexOf(response.resourcestotoggle, activityid) != -1) {
589 this.toggle_hide_resource_ui(button);
590 }
591
592 if (value == 0) {
593 button.setStyle('cursor', 'auto');
594 } else {
595 button.setStyle('cursor', 'pointer');
596 }
597 }, this);
598 },
599 toggle_highlight : function(e) {
5bd03e12
ARN
600 // Prevent the default button action
601 e.preventDefault();
602
ebaa29d1
ARN
603 // Get the section we're working on
604 var section = e.target.ancestor(CSS.SECTIONLI);
605 var button = e.target.ancestor('a', true);
606 var buttonicon = button.one('img');
607
608 // Determine whether the marker is currently set
609 var togglestatus = section.hasClass('current');
610 var value = 0;
611
612 // Set the current highlighted item text
613 var old_string = M.util.get_string('markthistopic', 'moodle');
614 Y.one(CSS.PAGECONTENT)
615 .all(CSS.SECTIONLI + '.current ' + CSS.HIGHLIGHT)
616 .set('title', old_string);
617 Y.one(CSS.PAGECONTENT)
618 .all(CSS.SECTIONLI + '.current ' + CSS.HIGHLIGHT + ' img')
619 .set('title', old_string)
620 .set('alt', old_string)
621 .set('src', M.util.image_url('i/marker'));
622
623 // Remove the highlighting from all sections
624 var allsections = Y.one(CSS.PAGECONTENT).all(CSS.SECTIONLI)
625 .removeClass('current');
626
627 // Then add it if required to the selected section
628 if (!togglestatus) {
629 section.addClass('current');
630 value = this.get_section_id(section);
631 var new_string = M.util.get_string('markedthistopic', 'moodle');
632 button
633 .set('title', new_string);
634 buttonicon
635 .set('title', new_string)
636 .set('alt', new_string)
637 .set('src', M.util.image_url('i/marked'));
638 }
639
640 // Change the highlight status
641 var data = {
642 'class' : 'course',
643 'field' : 'marker',
644 'value' : value
645 };
6a14c4ff 646 var lightbox = M.util.add_lightbox(Y, section);
ebaa29d1 647 lightbox.show();
45b364b9 648 this.send_request(data, lightbox);
ebaa29d1
ARN
649 }
650 }, {
651 NAME : 'course-section-toolbox',
652 ATTRS : {
653 courseid : {
654 'value' : 0
655 },
656 format : {
657 'value' : 'topics'
658 }
659 }
660 });
661
662 M.course = M.course || {};
663
664 M.course.init_resource_toolbox = function(config) {
665 return new RESOURCETOOLBOX(config);
666 };
667
668 M.course.init_section_toolbox = function(config) {
669 return new SECTIONTOOLBOX(config);
670 };
671
672},
673'@VERSION@', {
674 requires : ['base', 'node', 'io', 'moodle-course-coursebase']
675}
676);