MDL-47494 ddimageortext: Fix editing form javascript.
[moodle.git] / question / type / ddimageortext / yui / src / form / js / form.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * This is the question editing form code.
18  */
19 var DDIMAGEORTEXTFORMNAME = 'moodle-qtype_ddimageortext-form';
20 var DDIMAGEORTEXT_FORM = function() {
21     DDIMAGEORTEXT_FORM.superclass.constructor.apply(this, arguments);
22 };
24 Y.extend(DDIMAGEORTEXT_FORM, M.qtype_ddimageortext.dd_base_class, {
25     pendingid: '',
26     fp : null,
28     initializer : function() {
29         this.pendingid = 'qtype_ddimageortext-form-' + Math.random().toString(36).slice(2); // Random string.
30         M.util.js_pending(this.pendingid);
31         this.fp = this.file_pickers();
32         var tn = Y.one(this.get('topnode'));
33         tn.one('div.fcontainer').append('<div class="ddarea"><div class="droparea"></div><div class="dragitems"></div>' +
34                 '<div class="dropzones"></div></div>');
35         this.doc = this.doc_structure(this);
36         this.draw_dd_area();
37     },
39     draw_dd_area : function() {
40         var bgimageurl = this.fp.file('bgimage').href;
41         this.stop_selector_events();
42         this.set_options_for_drag_item_selectors();
43         if (bgimageurl !== null) {
44             this.doc.load_bg_img(bgimageurl);
45             this.load_drag_homes();
47             var drop = new Y.DD.Drop({
48                 node: this.doc.bg_img()
49             });
50             //Listen for a drop:hit on the background image
51             drop.on('drop:hit', function(e) {
52                 e.drag.get('node').setData('gooddrop', true);
53             });
55             this.afterimageloaddone = false;
56             this.doc.bg_img().on('load', this.constrain_image_size, this, 'bgimage');
57             this.doc.drag_item_homes()
58                                     .on('load', this.constrain_image_size, this, 'dragimage');
59             this.doc.bg_img().after('load', this.poll_for_image_load, this,
60                                                     true, 0, this.after_all_images_loaded);
61             this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
62                                                     true, 0, this.after_all_images_loaded);
63         } else {
64             this.setup_form_events();
65             M.util.js_complete(this.pendingid);
66         }
67         this.update_visibility_of_file_pickers();
68     },
70     after_all_images_loaded : function () {
71         this.update_padding_sizes_all();
72         this.update_drag_instances();
73         this.reposition_drags_for_form();
74         this.set_options_for_drag_item_selectors();
75         this.setup_form_events();
76         Y.later(500, this, this.reposition_drags_for_form, [], true);
77     },
79     constrain_image_size : function (e, imagetype) {
80         var maxsize = this.get('maxsizes')[imagetype];
81         var reduceby = Math.max(e.target.get('width') / maxsize.width,
82                                 e.target.get('height') / maxsize.height);
83         if (reduceby > 1) {
84             e.target.set('width', Math.floor(e.target.get('width') / reduceby));
85         }
86         e.target.addClass('constrained');
87         e.target.detach('load', this.constrain_image_size);
88     },
90     load_drag_homes : function () {
91         // Set up drag items homes.
92         for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
93             this.load_drag_home(i);
94         }
95     },
97     load_drag_home : function (dragitemno) {
98         var url = null;
99         if ('image' === this.form.get_form_value('drags', [dragitemno, 'dragitemtype'])) {
100             url = this.fp.file(this.form.to_name_with_index('dragitem', [dragitemno])).href;
101         }
102         this.doc.add_or_update_drag_item_home(dragitemno, url,
103                 this.form.get_form_value('draglabel', [dragitemno]),
104                 this.form.get_form_value('drags', [dragitemno, 'draggroup']));
105     },
107     update_drag_instances : function () {
108         // Set up drop zones.
109         for (var i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
110             var dragitemno = this.form.get_form_value('drops', [i, 'choice']);
111             if (dragitemno !== '0' && (this.doc.drag_item(i) === null)) {
112                 var drag = this.doc.clone_new_drag_item(i, dragitemno - 1);
113                 if (drag !== null) {
114                     this.doc.draggable_for_form(drag);
115                 }
116             }
117         }
118     },
119     set_options_for_drag_item_selectors : function () {
120         var dragitemsoptions = {0: ''};
121         for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
122             var label = this.form.get_form_value('draglabel', [i]);
123             var file = this.fp.file(this.form.to_name_with_index('dragitem', [i]));
124             if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])
125                                                                     && file.name !== null) {
126                 dragitemsoptions[i + 1] = (i + 1) + '. ' + label + ' (' + file.name + ')';
127             } else if (label !== '') {
128                 dragitemsoptions[i + 1] = (i + 1) + '. ' + label;
129             }
130         }
131         for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
132             var selector = Y.one('#id_drops_' + i + '_choice');
133             var selectedvalue = selector.get('value');
134             selector.all('option').remove(true);
135             for (var value in dragitemsoptions) {
136                 value = + value;
137                 var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
138                 selector.append(option);
139                 var optionnode = selector.one('option[value="' + value + '"]');
140                 if (value === + selectedvalue) {
141                     optionnode.set('selected', true);
142                 } else {
143                     if (value !== 0) { // No item option is always selectable.
144                         var cbel = Y.one('#id_drags_' + (value - 1) + '_infinite');
145                         if (cbel && !cbel.get('checked')) {
146                             Y.all('fieldset#id_dropzoneheader select').some(function (selector) {
147                                 if (Number(selector.get('value')) === value) {
148                                     optionnode.set('disabled', true);
149                                     return true; // Stop looping.
150                                 }
151                                 return false;
152                             }, this);
153                         }
154                     }
155                 }
156             }
157         }
158     },
160     stop_selector_events : function () {
161         Y.all('fieldset#id_dropzoneheader select').detachAll();
162     },
164     setup_form_events : function () {
165         // Events triggered by changes to form data.
167         // X and y coordinates.
168         Y.all('fieldset#id_dropzoneheader input').on('blur', function (e) {
169             var name = e.target.getAttribute('name');
170             var draginstanceno = this.form.from_name_with_index(name).indexes[0];
171             var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
172                             this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
173             var constrainedxy = this.constrain_xy(draginstanceno, fromform);
174             this.form.set_form_value('drops', [draginstanceno, 'xleft'], constrainedxy[0]);
175             this.form.set_form_value('drops', [draginstanceno, 'ytop'], constrainedxy[1]);
176         }, this);
178         // Change in selected item.
179         Y.all('fieldset#id_dropzoneheader select').on('change', function (e) {
180             var name = e.target.getAttribute('name');
181             var draginstanceno = this.form.from_name_with_index(name).indexes[0];
182             var old = this.doc.drag_item(draginstanceno);
183             if (old !== null) {
184                 old.remove(true);
185             }
186             this.draw_dd_area();
187         }, this);
189         for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
190             // Change to group selector.
191             Y.all('#fgroup_id_drags_' + i + ' select.draggroup').on(
192                     'change', function () {
193                 this.doc.drag_items().remove(true);
194                 this.draw_dd_area();
195             }, this);
196             Y.all('#fgroup_id_drags_' + i + ' select.dragitemtype').on(
197                     'change', function () {
198                 this.doc.drag_items().remove(true);
199                 this.draw_dd_area();
200             }, this);
201             Y.all('fieldset#draggableitemheader_' + i + ' input[type="text"]')
202                                 .on('blur', this.set_options_for_drag_item_selectors, this);
203             // Change to infinite checkbox.
204             Y.all('fieldset#draggableitemheader_' + i + ' input[type="checkbox"]')
205                                 .on('change', this.set_options_for_drag_item_selectors, this);
206         }
207         // Event on file picker new file selection.
208         Y.after(function (e) {
209             var name = this.fp.name(e.id);
210             if (name !== 'bgimage') {
211                 this.doc.drag_items().remove(true);
212             }
213             this.draw_dd_area();
214         }, M.form_filepicker, 'callback', this);
215     },
217     update_visibility_of_file_pickers : function() {
218         for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
219             if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])) {
220                 Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
221                             .setStyle('display', 'block');
222             } else {
223                 Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
224                             .setStyle('display', 'none');
225             }
226         }
227     },
229     reposition_drags_for_form : function() {
230         this.doc.drag_items().each(function (drag) {
231             var draginstanceno = drag.getData('draginstanceno');
232             this.reposition_drag_for_form(draginstanceno);
233         }, this);
234         M.util.js_complete(this.pendingid);
235     },
237     reposition_drag_for_form : function (draginstanceno) {
238         var drag = this.doc.drag_item(draginstanceno);
239         if (null !== drag && !drag.hasClass('yui3-dd-dragging')) {
240             var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
241                             this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
242             if (fromform[0] === '' && fromform[1] === '') {
243                 var dragitemno = drag.getData('dragitemno');
244                 drag.setXY(this.doc.drag_item_home(dragitemno).getXY());
245             } else {
246                 drag.setXY(this.convert_to_window_xy(fromform));
247             }
248         }
249     },
250     set_drag_xy : function (draginstanceno, xy) {
251         xy = this.constrain_xy(draginstanceno, this.convert_to_bg_img_xy(xy));
252         this.form.set_form_value('drops', [draginstanceno, 'xleft'], Math.round(xy[0]));
253         this.form.set_form_value('drops', [draginstanceno, 'ytop'], Math.round(xy[1]));
254     },
255     reset_drag_xy : function (draginstanceno) {
256         this.form.set_form_value('drops', [draginstanceno, 'xleft'], '');
257         this.form.set_form_value('drops', [draginstanceno, 'ytop'], '');
258     },
260     //make sure xy value is not out of bounds of bg image
261     constrain_xy : function (draginstanceno, bgimgxy) {
262         var drag = this.doc.drag_item(draginstanceno);
263         var xleftconstrained =
264             Math.min(bgimgxy[0], this.doc.bg_img().get('width') - drag.get('offsetWidth'));
265         var ytopconstrained =
266             Math.min(bgimgxy[1], this.doc.bg_img().get('height') - drag.get('offsetHeight'));
267         xleftconstrained = Math.max(xleftconstrained, 0);
268         ytopconstrained = Math.max(ytopconstrained, 0);
269         return [xleftconstrained, ytopconstrained];
270     },
271     convert_to_bg_img_xy : function (windowxy) {
272         return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
273                 Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
274     },
276     /**
277      * Low level operations on form.
278      */
279     form : {
280         to_name_with_index : function(name, indexes) {
281             var indexstring = name;
282             for (var i = 0; i < indexes.length; i++) {
283                 indexstring = indexstring + '[' + indexes[i] + ']';
284             }
285             return indexstring;
286         },
287         get_el : function (name, indexes) {
288             var form = document.getElementById('mform1');
289             return form.elements[this.to_name_with_index(name, indexes)];
290         },
291         get_form_value : function(name, indexes) {
292             var el = this.get_el(name, indexes);
293             if (el.type === 'checkbox') {
294                 return el.checked;
295             } else {
296                 return el.value;
297             }
298         },
299         set_form_value : function(name, indexes, value) {
300             var el = this.get_el(name, indexes);
301             if (el.type === 'checkbox') {
302                 el.checked = value;
303             } else {
304                 el.value = value;
305             }
306         },
307         from_name_with_index : function(name) {
308             var toreturn = {};
309             toreturn.indexes = [];
310             var bracket = name.indexOf('[');
311             toreturn.name = name.substring(0, bracket);
312             while (bracket !== -1) {
313                 var end = name.indexOf(']', bracket + 1);
314                 toreturn.indexes.push(name.substring(bracket + 1, end));
315                 bracket = name.indexOf('[', end + 1);
316             }
317             return toreturn;
318         }
319     },
321     file_pickers : function () {
322         var draftitemidstoname;
323         var nametoparentnode;
324         if (draftitemidstoname === undefined) {
325             draftitemidstoname = {};
326             nametoparentnode = {};
327             var filepickers = Y.all('form.mform input.filepickerhidden');
328             filepickers.each(function(filepicker) {
329                 draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
330                 nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
331             }, this);
332         }
333         var toreturn = {
334             file : function (name) {
335                 var parentnode = nametoparentnode[name];
336                 var fileanchor = parentnode.one('div.filepicker-filelist a');
337                 if (fileanchor) {
338                     return {href : fileanchor.get('href'), name : fileanchor.get('innerHTML')};
339                 } else {
340                     return {href : null, name : null};
341                 }
342             },
343             name : function (draftitemid) {
344                 return draftitemidstoname[draftitemid];
345             }
346         };
347         return toreturn;
348     }
349 }, {NAME : DDIMAGEORTEXTFORMNAME, ATTRS : {maxsizes:{value:null}}});
350 M.qtype_ddimageortext = M.qtype_ddimageortext || {};
351 M.qtype_ddimageortext.init_form = function(config) {
352     return new DDIMAGEORTEXT_FORM(config);
353 };