Merge branch 'MDL-70112_310' of https://github.com/timhunt/moodle into MOODLE_310_STABLE
[moodle.git] / question / type / ddmarker / amd / src / form.js
CommitLineData
5d4b3421
JB
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/>.
15
5d4b3421 16/**
ed7e30fa 17 * This class provides the enhancements to the drag-drop marker editing form.
5d4b3421
JB
18 *
19 * @package qtype_ddmarker
20 * @subpackage form
21 * @copyright 2018 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
ed7e30fa 25define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDrop, Shapes) {
5d4b3421
JB
26
27 "use strict";
28
29 /**
ed7e30fa
TH
30 * Create the manager object that deals with keeping everything synchronised for one drop zone.
31 *
32 * @param {int} dropzoneNo the index of this drop zone in the form. 0, 1, ....
33 * @constructor
5d4b3421 34 */
ed7e30fa
TH
35 function DropZoneManager(dropzoneNo) {
36 this.dropzoneNo = dropzoneNo;
37 this.svgEl = null;
5d4b3421 38
ed7e30fa
TH
39 this.shape = Shapes.make(this.getShapeType(), this.getLabel());
40 this.updateCoordinatesFromForm();
41 }
5d4b3421 42
ed7e30fa
TH
43 /**
44 * Update the coordinates from a particular string.
00f09d8f
TH
45 *
46 * @param {SVGElement} [svg] the SVG element that is the preview.
ed7e30fa 47 */
00f09d8f
TH
48 DropZoneManager.prototype.updateCoordinatesFromForm = function(svg) {
49 var coordinates = this.getCoordinates(),
50 currentNumPoints = this.shape.getType() === 'polygon' && this.shape.points.length;
ed7e30fa
TH
51 if (this.shape.getCoordinates() === coordinates) {
52 return;
53 }
d2fa9e79
HN
54 // We don't need to scale the shape for editing form.
55 if (!this.shape.parse(coordinates, 1)) {
00f09d8f
TH
56 // Invalid coordinates. Don't update the preview.
57 return;
58 }
59
60 if (this.shape.getType() === 'polygon' && currentNumPoints !== this.shape.points.length) {
61 // Polygon, and size has changed.
62 var currentyActive = this.isActive();
63 this.removeFromSvg();
64 if (svg) {
65 this.addToSvg(svg);
66 if (currentyActive) {
67 this.setActive();
68 }
69 }
70 } else {
71 // Simple update.
ed7e30fa
TH
72 this.updateSvgEl();
73 }
d2fa9e79
HN
74 // Update the rounded coordinates if needed.
75 this.setCoordinatesInForm();
ed7e30fa 76 };
5d4b3421 77
ed7e30fa
TH
78 /**
79 * Update the label.
80 */
81 DropZoneManager.prototype.updateLabel = function() {
82 var label = this.getLabel();
83 if (this.shape.label !== label) {
84 this.shape.label = label;
85 this.updateSvgEl();
86 }
87 };
88
89 /**
90 * Handle if the type of shape has changed.
91 *
92 * @param {SVGElement} [svg] an SVG element to add this new shape to.
93 */
94 DropZoneManager.prototype.changeShape = function(svg) {
95 var newShapeType = this.getShapeType(),
96 currentyActive = this.isActive();
97
98 if (newShapeType === this.shape.getType()) {
99 return;
100 }
101
102 // It has really changed.
103 this.removeFromSvg();
104 this.shape = Shapes.getSimilar(newShapeType, this.shape);
105 if (svg) {
106 this.addToSvg(svg);
107 if (currentyActive) {
108 this.setActive();
5d4b3421 109 }
ed7e30fa
TH
110 }
111 this.setCoordinatesInForm();
112 };
5d4b3421 113
ed7e30fa
TH
114 /**
115 * Add this drop zone to an SVG graphic.
116 *
117 * @param {SVGElement} svg the SVG image to which to add this drop zone.
118 */
119 DropZoneManager.prototype.addToSvg = function(svg) {
120 if (this.svgEl !== null) {
121 throw new Error('this.svgEl already set');
122 }
123 this.svgEl = this.shape.makeSvg(svg);
124 if (!this.svgEl) {
125 return;
126 }
127 this.svgEl.setAttribute('class', 'dropzone');
128 this.svgEl.setAttribute('data-dropzone-no', this.dropzoneNo);
5d4b3421 129
ed7e30fa
TH
130 // Add handles.
131 var handles = this.shape.getHandlePositions();
132 if (handles === null) {
133 return;
134 }
5d4b3421 135
ed7e30fa
TH
136 var moveHandle = Shapes.createSvgElement(this.svgEl, 'circle');
137 moveHandle.setAttribute('cx', handles.moveHandle.x);
138 moveHandle.setAttribute('cy', handles.moveHandle.y);
139 moveHandle.setAttribute('r', 7);
140 moveHandle.setAttribute('class', 'handle move');
5d4b3421 141
ed7e30fa
TH
142 for (var i = 0; i < handles.editHandles.length; ++i) {
143 this.makeEditHandle(i, handles.editHandles[i]);
144 }
145 };
5d4b3421 146
ed7e30fa
TH
147 /**
148 * Add a new edit handle.
149 *
150 * @param {int} index the handle index.
151 * @param {Point} point the point at which to add the handle.
152 */
153 DropZoneManager.prototype.makeEditHandle = function(index, point) {
154 var editHandle = Shapes.createSvgElement(this.svgEl, 'rect');
155 editHandle.setAttribute('x', point.x - 6);
156 editHandle.setAttribute('y', point.y - 6);
157 editHandle.setAttribute('width', 11);
158 editHandle.setAttribute('height', 11);
159 editHandle.setAttribute('class', 'handle edit');
160 editHandle.setAttribute('data-edit-handle-no', index);
161 };
5d4b3421 162
ed7e30fa
TH
163 /**
164 * Remove this drop zone from an SVG image.
165 */
166 DropZoneManager.prototype.removeFromSvg = function() {
167 if (this.svgEl !== null) {
168 this.svgEl.parentNode.removeChild(this.svgEl);
169 this.svgEl = null;
170 }
171 };
5d4b3421 172
ed7e30fa
TH
173 /**
174 * Update the shape of this drop zone (but not type) in an SVG image.
175 */
176 DropZoneManager.prototype.updateSvgEl = function() {
177 if (this.svgEl === null) {
178 return;
179 }
5d4b3421 180
ed7e30fa 181 this.shape.updateSvg(this.svgEl);
5d4b3421 182
ed7e30fa
TH
183 // Adjust handles.
184 var handles = this.shape.getHandlePositions();
185 if (handles === null) {
186 return;
187 }
5d4b3421 188
ed7e30fa 189 // Move handle.
00f09d8f
TH
190 // The shape + its label are the first two children of svgEl.
191 // Then come the move handle followed by the edit handles.
ed7e30fa
TH
192 this.svgEl.childNodes[2].setAttribute('cx', handles.moveHandle.x);
193 this.svgEl.childNodes[2].setAttribute('cy', handles.moveHandle.y);
5d4b3421 194
ed7e30fa
TH
195 // Edit handles.
196 for (var i = 0; i < handles.editHandles.length; ++i) {
197 this.svgEl.childNodes[3 + i].setAttribute('x', handles.editHandles[i].x - 6);
198 this.svgEl.childNodes[3 + i].setAttribute('y', handles.editHandles[i].y - 6);
199 }
200 };
201
202 /**
203 * Find out of this drop zone is currently being edited.
204 *
205 * @return {boolean} true if it is.
206 */
207 DropZoneManager.prototype.isActive = function() {
208 return this.svgEl !== null && this.svgEl.getAttribute('class').match(/\bactive\b/);
209 };
210
211 /**
212 * Set this drop zone as being edited.
213 */
214 DropZoneManager.prototype.setActive = function() {
00f09d8f
TH
215 // Move this one to last, so that it is always on top.
216 // (Otherwise the handles may not be able to receive events.)
217 var parent = this.svgEl.parentNode;
218 parent.removeChild(this.svgEl);
219 parent.appendChild(this.svgEl);
ed7e30fa
TH
220 this.svgEl.setAttribute('class', this.svgEl.getAttribute('class') + ' active');
221 };
222
223 /**
224 * Set the coordinates in the form to match the current shape.
225 */
226 DropZoneManager.prototype.setCoordinatesInForm = function() {
227 dragDropForm.form.setFormValue('drops', [this.dropzoneNo, 'coords'], this.shape.getCoordinates());
228 };
229
230 /**
231 * Returns the coordinates for a drop zone from the text input in the form.
232 * @returns {string} the coordinates.
233 */
234 DropZoneManager.prototype.getCoordinates = function() {
235 return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'coords']).replace(/\s*/g, '');
236 };
237
238 /**
239 * Returns the selected marker number from the dropdown in the form.
240 * @returns {int} choice number.
241 */
242 DropZoneManager.prototype.getChoiceNo = function() {
243 return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'choice']);
244 };
245
246 /**
247 * Returns the selected marker number from the dropdown in the form.
248 * @returns {String} marker label text.
249 */
250 DropZoneManager.prototype.getLabel = function() {
251 return dragDropForm.form.getMarkerText(this.getChoiceNo());
252 };
253
254
255 /**
256 * Returns the selected type of shape in the form.
257 * @returns {String} 'circle', 'rectangle' or 'polygon'.
258 */
259 DropZoneManager.prototype.getShapeType = function() {
260 return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'shape']);
261 };
262
263 /**
264 * Start responding to dragging the move handle.
265 * @param {Event} e Event object
266 */
267 DropZoneManager.prototype.handleMove = function(e) {
268 var info = dragDrop.prepare(e);
269 if (!info.start) {
270 return;
271 }
272
273 var movingDropZone = this,
274 lastX = info.x,
275 lastY = info.y,
00f09d8f
TH
276 dragProxy = this.makeDragProxy(info.x, info.y),
277 bgImg = $('fieldset#id_previewareaheader .dropbackground'),
278 maxX = bgImg.width(),
279 maxY = bgImg.height();
280
ed7e30fa 281 dragDrop.start(e, $(dragProxy), function(pageX, pageY) {
00f09d8f 282 movingDropZone.shape.move(pageX - lastX, pageY - lastY, maxX, maxY);
ed7e30fa
TH
283 lastX = pageX;
284 lastY = pageY;
285 movingDropZone.updateSvgEl();
286 movingDropZone.setCoordinatesInForm();
287 }, function() {
288 document.body.removeChild(dragProxy);
289 });
290 };
291
292 /**
293 * Start responding to dragging the move handle.
294 * @param {Event} e Event object
295 * @param {int} handleIndex
296 * @param {SVGElement} [svg] an SVG element to add this new shape to.
297 */
298 DropZoneManager.prototype.handleEdit = function(e, handleIndex, svg) {
299 var info = dragDrop.prepare(e);
300 if (!info.start) {
301 return;
302 }
303
304 // For polygons, CTRL + drag adds a new point.
00f09d8f 305 if (this.shape.getType() === 'polygon' && (e.ctrlKey || e.metaKey)) {
ed7e30fa
TH
306 this.shape.addNewPointAfter(handleIndex);
307 this.removeFromSvg();
308 this.addToSvg(svg);
309 this.setActive();
310 }
311
312 var changingDropZone = this,
313 lastX = info.x,
314 lastY = info.y,
00f09d8f
TH
315 dragProxy = this.makeDragProxy(info.x, info.y),
316 bgImg = $('fieldset#id_previewareaheader .dropbackground'),
317 maxX = bgImg.width(),
318 maxY = bgImg.height();
319
ed7e30fa 320 dragDrop.start(e, $(dragProxy), function(pageX, pageY) {
00f09d8f 321 changingDropZone.shape.edit(handleIndex, pageX - lastX, pageY - lastY, maxX, maxY);
ed7e30fa
TH
322 lastX = pageX;
323 lastY = pageY;
324 changingDropZone.updateSvgEl();
325 changingDropZone.setCoordinatesInForm();
326 }, function() {
327 document.body.removeChild(dragProxy);
328 changingDropZone.shape.normalizeShape();
329 changingDropZone.updateSvgEl();
330 changingDropZone.setCoordinatesInForm();
331 });
332 };
333
334 /**
335 * Make an invisible drag proxy.
336 *
337 * @param {int} x x position .
338 * @param {int} y y position.
339 * @returns {HTMLElement} the drag proxy.
340 */
341 DropZoneManager.prototype.makeDragProxy = function(x, y) {
342 var dragProxy = document.createElement('div');
343 dragProxy.style.position = 'absolute';
344 dragProxy.style.top = y + 'px';
345 dragProxy.style.left = x + 'px';
346 dragProxy.style.width = '1px';
347 dragProxy.style.height = '1px';
348 document.body.appendChild(dragProxy);
349 return dragProxy;
350 };
351
352 /**
353 * Singleton object for managing all the parts of the form.
354 */
355 var dragDropForm = {
5d4b3421 356
ed7e30fa
TH
357 /**
358 * @var {object} for interacting with the file pickers.
359 */
360 fp: null, // Object containing functions associated with the file picker.
5d4b3421
JB
361
362 /**
ed7e30fa 363 * @var {int} the number of drop-zones on the form.
5d4b3421 364 */
ed7e30fa 365 noDropZones: null,
5d4b3421
JB
366
367 /**
ed7e30fa 368 * @var {DropZoneManager[]} the drop zones in the preview, indexed by drop zone number.
5d4b3421 369 */
ed7e30fa 370 dropZones: [],
5d4b3421
JB
371
372 /**
ed7e30fa 373 * Initialise the form.
5d4b3421 374 */
d0311165 375 init: function() {
ed7e30fa
TH
376 dragDropForm.fp = dragDropForm.filePickers();
377 dragDropForm.noDropZones = dragDropForm.form.getFormValue('nodropzone', []);
378 dragDropForm.setupPreviewArea();
379 dragDropForm.setOptionsForDragItemSelectors();
380 dragDropForm.createShapes();
381 dragDropForm.setupEventHandlers();
382 dragDropForm.waitForFilePickerToInitialise();
5d4b3421
JB
383 },
384
385 /**
386 * Add html for the preview area.
387 */
388 setupPreviewArea: function() {
ed7e30fa
TH
389 $('fieldset#id_previewareaheader div.fcontainer').append(
390 '<div class="ddarea que ddmarker">' +
5d4b3421
JB
391 ' <div id="ddm-droparea" class="droparea">' +
392 ' <img class="dropbackground" />' +
ed7e30fa
TH
393 ' <div id="ddm-dropzone" class="dropzones">' +
394 ' </div>' +
5d4b3421
JB
395 ' </div>' +
396 '</div>');
397 },
398
399 /**
400 * When a new marker is added this function updates the Marker dropdown controls in Drop zones.
401 */
402 setOptionsForDragItemSelectors: function() {
403 var dragItemsOptions = {'0': ''};
ed7e30fa 404 var noItems = dragDropForm.form.getFormValue('noitems', []);
5d4b3421
JB
405 var selectedValues = [];
406 var selector;
407 var i, label;
408 for (i = 1; i <= noItems; i++) {
ed7e30fa 409 label = dragDropForm.form.getMarkerText(i);
5d4b3421
JB
410 if (label !== "") {
411 // HTML escape the label.
412 dragItemsOptions[i] = $('<div/>').text(label).html();
413 }
414 }
415 // Get all the currently selected drags for each drop.
ed7e30fa 416 for (i = 0; i < dragDropForm.noDropZones; i++) {
5d4b3421
JB
417 selector = $('#id_drops_' + i + '_choice');
418 selectedValues[i] = Number(selector.val());
419 }
ed7e30fa 420 for (i = 0; i < dragDropForm.noDropZones; i++) {
5d4b3421
JB
421 selector = $('#id_drops_' + i + '_choice');
422 // Remove all options for drag choice.
423 selector.find('option').remove();
424 // And recreate the options.
425 for (var value in dragItemsOptions) {
426 value = Number(value);
427 var option = '<option value="' + value + '">' + dragItemsOptions[value] + '</option>';
428 selector.append(option);
429 var optionnode = selector.find('option[value="' + value + '"]');
ed7e30fa
TH
430
431
432 if (value === 0) {
433 continue; // The 'no item' option is always selectable.
434 }
435
5d4b3421
JB
436 // Is this the currently selected value?
437 if (value === selectedValues[i]) {
438 optionnode.attr('selected', true);
ed7e30fa
TH
439 continue; // If it s selected, we must leave it enabled.
440 }
441
442 // Count how many times it is used, and if necessary, disable.
443 var noofdrags = dragDropForm.form.getFormValue('drags', [value - 1, 'noofdrags']);
444 if (Number(noofdrags) === 0) { // 'noofdrags === 0' means infinite.
445 continue; // Nothing to check.
446 }
447
448 // Go through all selected values in drop downs.
449 for (var k in selectedValues) {
450 if (Number(selectedValues[k]) !== value) {
451 continue;
452 }
453
454 // Count down 'noofdrags' and if reach zero then set disabled option for this drag item.
455 if (Number(noofdrags) === 1) {
456 optionnode.attr('disabled', true);
457 break;
458 } else {
459 noofdrags--;
5d4b3421
JB
460 }
461 }
462 }
ed7e30fa
TH
463
464 if (dragDropForm.dropZones.length > 0) {
465 dragDropForm.dropZones[i].updateLabel();
466 }
5d4b3421
JB
467 }
468 },
469
470 /**
ed7e30fa 471 * Create the shape representation of each dropZone.
5d4b3421 472 */
ed7e30fa
TH
473 createShapes: function() {
474 for (var dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {
475 dragDropForm.dropZones[dropzoneNo] = new DropZoneManager(dropzoneNo);
5d4b3421 476 }
5d4b3421
JB
477 },
478
479 /**
480 * Events linked to form actions.
481 */
ed7e30fa 482 setupEventHandlers: function() {
5d4b3421 483 // Changes to labels in the Markers section.
ed7e30fa
TH
484 $('fieldset#id_draggableitemheader').on('change input', 'input, select', function() {
485 dragDropForm.setOptionsForDragItemSelectors();
5d4b3421 486 });
ed7e30fa
TH
487
488 // Changes to Drop zones section: shape, coordinates and marker.
489 $('fieldset#id_dropzoneheader').on('change input', 'input, select', function(e) {
490 var ids = e.currentTarget.name.match(/^drops\[(\d+)]\[([a-z]*)]$/);
491 if (!ids) {
5d4b3421
JB
492 return;
493 }
ed7e30fa
TH
494
495 var dropzoneNo = ids[1],
496 inputType = ids[2],
497 dropZone = dragDropForm.dropZones[dropzoneNo];
498
499 switch (inputType) {
500 case 'shape':
501 dropZone.changeShape(dragDropForm.form.getSvg());
502 break;
503
504 case 'coords':
00f09d8f 505 dropZone.updateCoordinatesFromForm(dragDropForm.form.getSvg());
ed7e30fa
TH
506 break;
507
508 case 'choice':
509 dropZone.updateLabel();
510 break;
511 }
512 });
513
514 // Click to toggle graphical editing.
515 var previewArea = $('fieldset#id_previewareaheader');
516 previewArea.on('click', 'g.dropzone', function(e) {
517 var dropzoneNo = $(e.currentTarget).data('dropzone-no'),
518 currentlyActive = dragDropForm.dropZones[dropzoneNo].isActive();
519
520 $(dragDropForm.form.getSvg()).find('.dropzone.active').removeClass('active');
521
522 if (!currentlyActive) {
523 dragDropForm.dropZones[dropzoneNo].setActive();
5d4b3421
JB
524 }
525 });
ed7e30fa
TH
526
527 // Drag start on a move handle.
528 previewArea.on('mousedown touchstart', '.dropzone .handle.move', function(e) {
529 var dropzoneNo = $(e.currentTarget).closest('g').data('dropzoneNo');
530
531 dragDropForm.dropZones[dropzoneNo].handleMove(e);
532 });
533
534 // Drag start on a move handle.
535 previewArea.on('mousedown touchstart', '.dropzone .handle.edit', function(e) {
536 var dropzoneNo = $(e.currentTarget).closest('g').data('dropzoneNo'),
537 handleIndex = e.currentTarget.getAttribute('data-edit-handle-no');
538
539 dragDropForm.dropZones[dropzoneNo].handleEdit(e, handleIndex, dragDropForm.form.getSvg());
5d4b3421
JB
540 });
541 },
542
543 /**
ed7e30fa
TH
544 * Prevents adding drop zones until the preview background image is ready to load.
545 */
546 waitForFilePickerToInitialise: function() {
547 if (dragDropForm.fp.file('bgimage').href === null) {
548 // It would be better to use an onload or onchange event rather than this timeout.
549 // Unfortunately attempts to do this early are overwritten by filepicker during its loading.
550 setTimeout(dragDropForm.waitForFilePickerToInitialise, 1000);
551 return;
552 }
553
554 // From now on, when a new file gets loaded into the filepicker, update the preview.
555 // This is not in the setupEventHandlers section as it needs to be delayed until
556 // after filepicker's javascript has finished.
a8efb077 557 $('form.mform[data-qtype="ddmarker"]').on('change', '#id_bgimage', dragDropForm.loadPreviewImage);
ed7e30fa
TH
558
559 dragDropForm.loadPreviewImage();
560 },
561
562 /**
563 * Loads the preview background image.
564 */
565 loadPreviewImage: function() {
566 $('fieldset#id_previewareaheader .dropbackground')
567 .one('load', dragDropForm.afterPreviewImageLoaded)
568 .attr('src', dragDropForm.fp.file('bgimage').href);
569 },
570
571 /**
572 * Functions to run after background image loaded.
573 */
574 afterPreviewImageLoaded: function() {
575 var bgImg = $('fieldset#id_previewareaheader .dropbackground');
ed7e30fa
TH
576 // Place the dropzone area over the background image (adding one to account for the border).
577 $('#ddm-dropzone').css('position', 'relative').css('top', (bgImg.height() + 1) * -1);
578 $('#ddm-droparea').css('height', bgImg.height() + 20);
579 dragDropForm.updateSvgDisplay();
580 },
581
ed7e30fa
TH
582 /**
583 * Draws or re-draws all dropzones in the preview area based on form data.
584 * Call this function when there is a change in the form data.
585 */
586 updateSvgDisplay: function() {
587 var bgImg = $('fieldset#id_previewareaheader .dropbackground'),
588 dropzoneNo;
589
590 if (dragDropForm.form.getSvg()) {
591 // Already exists, just need to be updated.
592 for (dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {
593 dragDropForm.dropZones[dropzoneNo].updateSvgEl();
594 }
595
596 } else {
597 // Create.
598 $('#ddm-dropzone').html('<svg xmlns="http://www.w3.org/2000/svg" class="dropzones" ' +
599 'width="' + bgImg.outerWidth() + '" ' +
600 'height="' + bgImg.outerHeight() + '"></svg>');
601 for (dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {
602 dragDropForm.dropZones[dropzoneNo].addToSvg(dragDropForm.form.getSvg());
603 }
604 }
605 },
606
607 /**
608 * Helper to make it easy to work with form elements with names like "drops[0][shape]".
5d4b3421
JB
609 */
610 form: {
ed7e30fa
TH
611 /**
612 * Returns the label text for a marker.
613 * @param {int} markerNo
614 * @returns {string} Marker text
615 */
616 getMarkerText: function(markerNo) {
617 if (Number(markerNo) !== 0) {
618 var label = dragDropForm.form.getFormValue('drags', [markerNo - 1, 'label']);
619 return label.replace(new RegExp("^\\s*(.*)\\s*$"), "$1");
620 } else {
621 return '';
622 }
623 },
624
625 /**
626 * Get the SVG element, if there is one, otherwise return null.
627 *
628 * @returns {SVGElement|null} the SVG element or null.
629 */
630 getSvg: function() {
631 var svg = $('fieldset#id_previewareaheader svg');
632 if (svg.length === 0) {
633 return null;
634 } else {
635 return svg[0];
636 }
637 },
638
5d4b3421
JB
639 toNameWithIndex: function(name, indexes) {
640 var indexString = name;
641 for (var i = 0; i < indexes.length; i++) {
642 indexString = indexString + '[' + indexes[i] + ']';
643 }
644 return indexString;
645 },
ed7e30fa 646
5d4b3421 647 getEl: function(name, indexes) {
a8efb077 648 var form = $('form.mform[data-qtype="ddmarker"]')[0];
5d4b3421
JB
649 return form.elements[this.toNameWithIndex(name, indexes)];
650 },
ed7e30fa
TH
651
652 /**
653 * Helper to get the value of a form elements with name like "drops[0][shape]".
654 *
655 * @param {String} name the base name, e.g. 'drops'.
656 * @param {String[]} indexes the indexes, e.g. ['0', 'shape'].
657 * @return {String} the value of that field.
658 */
5d4b3421
JB
659 getFormValue: function(name, indexes) {
660 var el = this.getEl(name, indexes);
661 if (el.type === 'checkbox') {
662 return el.checked;
663 } else {
664 return el.value;
665 }
666 },
ed7e30fa
TH
667
668 /**
669 * Helper to get the value of a form elements with name like "drops[0][shape]".
670 *
671 * @param {String} name the base name, e.g. 'drops'.
672 * @param {String[]} indexes the indexes, e.g. ['0', 'shape'].
673 * @param {String} value the value to set.
674 */
5d4b3421
JB
675 setFormValue: function(name, indexes, value) {
676 var el = this.getEl(name, indexes);
677 if (el.type === 'checkbox') {
678 el.checked = value;
679 } else {
680 el.value = value;
681 }
682 }
683 },
684
685 /**
686 * Utility to get the file name and url from the filepicker.
687 * @returns {Object} object containing functions {file, name}
688 */
689 filePickers: function() {
690 var draftItemIdsToName;
691 var nameToParentNode;
5d4b3421
JB
692 if (draftItemIdsToName === undefined) {
693 draftItemIdsToName = {};
694 nameToParentNode = {};
ed7e30fa 695 $('form.mform input.filepickerhidden').each(function(key, filepicker) {
5d4b3421
JB
696 draftItemIdsToName[filepicker.value] = filepicker.name;
697 nameToParentNode[filepicker.name] = filepicker.parentNode;
698 });
699 }
700 return {
701 file: function(name) {
ed7e30fa 702 var fileAnchor = $(nameToParentNode[name]).find('div.filepicker-filelist a');
5d4b3421
JB
703 if (fileAnchor.length) {
704 return {href: fileAnchor.get(0).href, name: fileAnchor.get(0).innerHTML};
705 } else {
706 return {href: null, name: null};
707 }
708 },
709 name: function(draftitemid) {
710 return draftItemIdsToName[draftitemid];
711 }
712 };
713 }
714 };
715
ed7e30fa
TH
716 /**
717 * @alias module:qtype_ddmarker/form
718 */
719 return {
720 /**
721 * Initialise the form javascript features.
722 * @param {Object} maxBgimageSize object with two properties: width and height.
723 */
724 init: dragDropForm.init
725 };
5d4b3421 726});