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