Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/* eslint-disable no-empty-function */
2
/**
3
 * The core drag and drop module for Moodle which extends the YUI drag and
4
 * drop functionality with additional features.
5
 *
6
 * @module moodle-core-dragdrop
7
 */
8
var MOVEICON = {
9
    pix: "i/move_2d",
10
    largepix: "i/dragdrop",
11
    component: 'moodle',
12
    cssclass: 'moodle-core-dragdrop-draghandle'
13
};
14
 
15
/**
16
 * General DRAGDROP class, this should not be used directly,
17
 * it is supposed to be extended by your class
18
 *
19
 * @class M.core.dragdrop
20
 * @constructor
21
 * @extends Base
22
 */
23
var DRAGDROP = function() {
24
    DRAGDROP.superclass.constructor.apply(this, arguments);
25
};
26
 
27
Y.extend(DRAGDROP, Y.Base, {
28
    /**
29
     * Whether the item is being moved upwards compared with the last
30
     * location.
31
     *
32
     * @property goingup
33
     * @type Boolean
34
     * @default null
35
     */
36
    goingup: null,
37
 
38
    /**
39
     * Whether the item is being moved upwards compared with the start
40
     * point.
41
     *
42
     * @property absgoingup
43
     * @type Boolean
44
     * @default null
45
     */
46
    absgoingup: null,
47
 
48
    /**
49
     * The class for the object.
50
     *
51
     * @property samenodeclass
52
     * @type String
53
     * @default null
54
     */
55
    samenodeclass: null,
56
 
57
    /**
58
     * The class on the parent of the item being moved.
59
     *
60
     * @property parentnodeclass
61
     * @type String
62
     * @default
63
     */
64
    parentnodeclass: null,
65
 
66
    /**
67
     * The label to use with keyboard drag/drop to describe items of the same Node.
68
     *
69
     * @property samenodelabel
70
     * @type Object
71
     * @default null
72
     */
73
    samenodelabel: null,
74
 
75
    /**
76
     * The label to use with keyboard drag/drop to describe items of the parent Node.
77
     *
78
     * @property samenodelabel
79
     * @type Object
80
     * @default null
81
     */
82
    parentnodelabel: null,
83
 
84
    /**
85
     * The groups for this instance.
86
     *
87
     * @property groups
88
     * @type Array
89
     * @default []
90
     */
91
    groups: [],
92
 
93
    /**
94
     * The previous drop location.
95
     *
96
     * @property lastdroptarget
97
     * @type Node
98
     * @default null
99
     */
100
    lastdroptarget: null,
101
 
102
    /**
103
     * Should the direction of a keyboard drag and drop item be detected.
104
     *
105
     * @property detectkeyboarddirection
106
     * @type Boolean
107
     * @default false
108
     */
109
    detectkeyboarddirection: false,
110
 
111
    /**
112
     * Listeners.
113
     *
114
     * @property listeners
115
     * @type Array
116
     * @default null
117
     */
118
    listeners: null,
119
 
120
    /**
121
     * The initializer which sets up the move action.
122
     *
123
     * @method initializer
124
     * @protected
125
     */
126
    initializer: function() {
127
        this.listeners = [];
128
 
129
        // Listen for all drag:start events.
130
        this.listeners.push(Y.DD.DDM.on('drag:start', this.global_drag_start, this));
131
 
132
        // Listen for all drag:over events.
133
        this.listeners.push(Y.DD.DDM.on('drag:over', this.globalDragOver, this));
134
 
135
        // Listen for all drag:end events.
136
        this.listeners.push(Y.DD.DDM.on('drag:end', this.global_drag_end, this));
137
 
138
        // Listen for all drag:drag events.
139
        this.listeners.push(Y.DD.DDM.on('drag:drag', this.global_drag_drag, this));
140
 
141
        // Listen for all drop:over events.
142
        this.listeners.push(Y.DD.DDM.on('drop:over', this.global_drop_over, this));
143
 
144
        // Listen for all drop:hit events.
145
        this.listeners.push(Y.DD.DDM.on('drop:hit', this.global_drop_hit, this));
146
 
147
        // Listen for all drop:miss events.
148
        this.listeners.push(Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this));
149
 
150
        // Add keybaord listeners for accessible drag/drop
151
        this.listeners.push(Y.one(Y.config.doc.body).delegate('key', this.global_keydown,
152
                'down:32, enter, esc', '.' + MOVEICON.cssclass, this));
153
 
154
        // Make the accessible drag/drop respond to a single click.
155
        this.listeners.push(Y.one(Y.config.doc.body).delegate('click', this.global_keydown,
156
                '.' + MOVEICON.cssclass, this));
157
    },
158
 
159
    /**
160
     * The destructor to shut down the instance of the dragdrop system.
161
     *
162
     * @method destructor
163
     * @protected
164
     */
165
    destructor: function() {
166
        new Y.EventHandle(this.listeners).detach();
167
    },
168
 
169
    /**
170
     * Build a new drag handle Node.
171
     *
172
     * @method get_drag_handle
173
     * @param {String} title The title on the drag handle
174
     * @param {String} classname The name of the class to add to the node
175
     * wrapping the drag icon
176
     * @param {String} iconclass Additional class to add to the icon.
177
     * @return Node The built drag handle.
178
     */
179
    get_drag_handle: function(title, classname, iconclass) {
180
 
181
        var dragelement = Y.Node.create('<span></span>')
182
            .addClass(classname)
183
            .setAttribute('title', title)
184
            .setAttribute('tabIndex', 0)
185
            .setAttribute('data-draggroups', this.groups)
186
            .setAttribute('role', 'button');
187
        dragelement.addClass(MOVEICON.cssclass);
188
 
189
        window.require(['core/templates'], function(Templates) {
190
            Templates.renderPix('i/move_2d', 'core').then(function(html) {
191
                var dragicon = Y.Node.create(html);
192
                dragicon.setStyle('cursor', 'move');
193
                if (typeof iconclass != 'undefined') {
194
                    dragicon.addClass(iconclass);
195
                }
196
                dragelement.appendChild(dragicon);
197
            });
198
        });
199
 
200
        return dragelement;
201
    },
202
 
203
    lock_drag_handle: function(drag, classname) {
204
        drag.removeHandle('.' + classname);
205
    },
206
 
207
    unlock_drag_handle: function(drag, classname) {
208
        drag.addHandle('.' + classname);
209
        drag.get('activeHandle').focus();
210
    },
211
 
212
    ajax_failure: function(response) {
213
        var e = {
214
            name: response.status + ' ' + response.statusText,
215
            message: response.responseText
216
        };
217
        return new M.core.exception(e);
218
    },
219
 
220
    in_group: function(target) {
221
        var ret = false;
222
        Y.each(this.groups, function(v) {
223
            if (target._groups[v]) {
224
                ret = true;
225
            }
226
        }, this);
227
        return ret;
228
    },
229
    /*
230
     * Drag-dropping related functions
231
     */
232
    global_drag_start: function(e) {
233
        // Get our drag object
234
        var drag = e.target;
235
        // Check that drag object belongs to correct group
236
        if (!this.in_group(drag)) {
237
            return;
238
        }
239
        // Store the nodes current style, so we can restore it later.
240
        this.originalstyle = drag.get('node').getAttribute('style');
241
        // Set some general styles here
242
        drag.get('node').setStyle('opacity', '.25');
243
        drag.get('dragNode').setStyles({
244
            opacity: '.75',
245
            borderColor: drag.get('node').getStyle('borderColor'),
246
            backgroundColor: drag.get('node').getStyle('backgroundColor')
247
        });
248
        drag.get('dragNode').empty();
249
        this.drag_start(e);
250
    },
251
 
252
    /**
253
     * Drag-dropping related functions
254
     *
255
     * @param {EventFacade} e
256
     */
257
    globalDragOver: function(e) {
258
        this.dragOver(e);
259
    },
260
 
261
    global_drag_end: function(e) {
262
        var drag = e.target;
263
        // Check that drag object belongs to correct group
264
        if (!this.in_group(drag)) {
265
            return;
266
        }
267
        // Put our general styles back
268
        drag.get('node').setAttribute('style', this.originalstyle);
269
        this.drag_end(e);
270
    },
271
 
272
    global_drag_drag: function(e) {
273
        var drag = e.target,
274
            info = e.info;
275
 
276
        // Check that drag object belongs to correct group
277
        if (!this.in_group(drag)) {
278
            return;
279
        }
280
 
281
        // Note, we test both < and > situations here. We don't want to
282
        // effect a change in direction if the user is only moving side
283
        // to side with no Y position change.
284
 
285
        // Detect changes in the position relative to the start point.
286
        if (info.start[1] < info.xy[1]) {
287
            // We are going up if our final position is higher than our start position.
288
            this.absgoingup = true;
289
 
290
        } else if (info.start[1] > info.xy[1]) {
291
            // Otherwise we're going down.
292
            this.absgoingup = false;
293
        }
294
 
295
        // Detect changes in the position relative to the last movement.
296
        if (info.delta[1] < 0) {
297
            // We are going up if our final position is higher than our start position.
298
            this.goingup = true;
299
 
300
        } else if (info.delta[1] > 0) {
301
            // Otherwise we're going down.
302
            this.goingup = false;
303
        }
304
 
305
        this.drag_drag(e);
306
    },
307
 
308
    global_drop_over: function(e) {
309
        // Check that drop object belong to correct group.
310
        if (!e.drop || !e.drop.inGroup(this.groups)) {
311
            return;
312
        }
313
 
314
        // Get a reference to our drag and drop nodes.
315
        var drag = e.drag.get('node'),
316
            drop = e.drop.get('node');
317
 
318
        // Save last drop target for the case of missed target processing.
319
        this.lastdroptarget = e.drop;
320
 
321
        // Are we dropping within the same parent node?
322
        if (drop.hasClass(this.samenodeclass)) {
323
            var where;
324
 
325
            if (this.goingup) {
326
                where = "before";
327
            } else {
328
                where = "after";
329
            }
330
 
331
            // Add the node contents so that it's moved, otherwise only the drag handle is moved.
332
            drop.insert(drag, where);
333
        } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
334
            // We are dropping on parent node and it is empty
335
            if (this.goingup) {
336
                drop.append(drag);
337
            } else {
338
                drop.prepend(drag);
339
            }
340
        }
341
        this.drop_over(e);
342
    },
343
 
344
    global_drag_dropmiss: function(e) {
345
        // drag:dropmiss does not have e.drag and e.drop properties
346
        // we substitute them for the ease of use. For e.drop we use,
347
        // this.lastdroptarget (ghost node we use for indicating where to drop)
348
        e.drag = e.target;
349
        e.drop = this.lastdroptarget;
350
        // Check that drag object belongs to correct group
351
        if (!this.in_group(e.drag)) {
352
            return;
353
        }
354
        // Check that drop object belong to correct group
355
        if (!e.drop || !e.drop.inGroup(this.groups)) {
356
            return;
357
        }
358
        this.drag_dropmiss(e);
359
    },
360
 
361
    global_drop_hit: function(e) {
362
        // Check that drop object belong to correct group
363
        if (!e.drop || !e.drop.inGroup(this.groups)) {
364
            return;
365
        }
366
        this.drop_hit(e);
367
    },
368
 
369
    /**
370
     * This is used to build the text for the heading of the keyboard
371
     * drag drop menu and the text for the nodes in the list.
372
     * @method find_element_text
373
     * @param {Node} n The node to start searching for a valid text node.
374
     * @return {string} The text of the first text-like child node of n.
375
     */
376
    find_element_text: function(n) {
377
        var text = '';
378
 
379
        // Try to resolve using aria-label first.
380
        text = n.get('aria-label') || '';
381
        if (text.length > 0) {
382
            return text;
383
        }
384
 
385
        // Now try to resolve using aria-labelledby.
386
        var labelledByNode = n.get('aria-labelledby');
387
        if (labelledByNode) {
388
            var labelNode = Y.one('#' + labelledByNode);
389
            if (labelNode && labelNode.get('text').length > 0) {
390
                return labelNode.get('text');
391
            }
392
        }
393
 
394
        // The valid node types to get text from.
395
        var nodes = n.all('h2, h3, h4, h5, span:not(.actions):not(.menu-action-text), p, div.no-overflow, div.dimmed_text');
396
 
397
        nodes.each(function() {
398
            if (text === '') {
399
                if (Y.Lang.trim(this.get('text')) !== '') {
400
                    text = this.get('text');
401
                }
402
            }
403
        });
404
 
405
        if (text !== '') {
406
            return text;
407
        }
408
        return M.util.get_string('emptydragdropregion', 'moodle');
409
    },
410
 
411
    /**
412
     * This is used to initiate a keyboard version of a drag and drop.
413
     * A dialog will open listing all the valid drop targets that can be selected
414
     * using tab, tab, tab, enter.
415
     * @method global_start_keyboard_drag
416
     * @param {Event} e The keydown / click event on the grab handle.
417
     * @param {Node} dragcontainer The resolved draggable node (an ancestor of the drag handle).
418
     * @param {Node} draghandle The node that triggered this action.
419
     */
420
    global_start_keyboard_drag: function(e, draghandle, dragcontainer) {
421
        M.core.dragdrop.keydragcontainer = dragcontainer;
422
        M.core.dragdrop.keydraghandle = draghandle;
423
 
424
        // Get the name of the thing to move.
425
        var nodetitle = this.find_element_text(dragcontainer);
426
        var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
427
 
428
        // Build the list of drop targets.
429
        var droplist = Y.Node.create('<ul></ul>');
430
        droplist.addClass('dragdrop-keyboard-drag');
431
        var listitem, listlink, listitemtext;
432
 
433
        // Search for possible drop targets.
434
        var droptargets = Y.all('.' + this.samenodeclass + ', .' + this.parentnodeclass);
435
 
436
        droptargets.each(function(node) {
437
            var validdrop = false;
438
            var labelroot = node;
439
            var className = node.getAttribute("class").split(' ').join(', .');
440
 
441
            if (node.drop && node.drop.inGroup(this.groups) && node.drop.get('node') !== dragcontainer &&
442
                    !(node.next(className) === dragcontainer && !this.detectkeyboarddirection)) {
443
                // This is a drag and drop target with the same class as the grabbed node.
444
                validdrop = true;
445
            } else {
446
                var elementgroups = node.getAttribute('data-draggroups').split(' ');
447
                var i, j;
448
                for (i = 0; i < elementgroups.length; i++) {
449
                    for (j = 0; j < this.groups.length; j++) {
450
                        if (elementgroups[i] === this.groups[j] && !node.ancestor('.yui3-dd-proxy') && !(node == dragcontainer ||
451
                            node.next(className) === dragcontainer || node.get('children').item(0) == dragcontainer)) {
452
                                // This is a parent node of the grabbed node (used for dropping in empty sections).
453
                                validdrop = true;
454
                                // This node will have no text - so we get the first valid text from the parent.
455
                                labelroot = node.get('parentNode');
456
                                break;
457
                        }
458
                    }
459
                    if (validdrop) {
460
                        break;
461
                    }
462
                }
463
            }
464
 
465
            if (validdrop) {
466
                // It is a valid drop target - create a list item for it.
467
                listitem = Y.Node.create('<li></li>');
468
                listlink = Y.Node.create('<a></a>');
469
                nodetitle = this.find_element_text(labelroot);
470
 
471
                if (this.samenodelabel && node.hasClass(this.samenodeclass)) {
472
                    listitemtext = M.util.get_string(this.samenodelabel.identifier, this.samenodelabel.component, nodetitle);
473
                } else if (this.parentnodelabel && node.hasClass(this.parentnodeclass)) {
474
                    listitemtext = M.util.get_string(this.parentnodelabel.identifier, this.parentnodelabel.component, nodetitle);
475
                } else {
476
                    listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
477
                }
478
                listlink.setContent(listitemtext);
479
 
480
                // Add a data attribute so we can get the real drop target.
481
                listlink.setAttribute('data-drop-target', node.get('id'));
482
                // Allow tabbing to the link.
483
                listlink.setAttribute('tabindex', '0');
484
                listlink.setAttribute('role', 'button');
485
 
486
                // Set the event listeners for enter, space or click.
487
                listlink.on('click', this.global_keyboard_drop, this);
488
                listlink.on('key', this.global_keyboard_drop, 'down:enter,32', this);
489
 
490
                // Add to the list or drop targets.
491
                listitem.append(listlink);
492
                droplist.append(listitem);
493
            }
494
        }, this);
495
 
496
        // Create the dialog for the interaction.
497
        M.core.dragdrop.dropui = new M.core.dialogue({
498
            headerContent: dialogtitle,
499
            bodyContent: droplist,
500
            draggable: true,
501
            visible: true,
502
            center: true,
503
            modal: true
504
        });
505
 
506
        M.core.dragdrop.dropui.after('visibleChange', function(e) {
507
            // After the dialogue has been closed, we call the cancel function. This will
508
            // ensure that tidying up happens (e.g. focusing on the start Node).
509
            if (e.prevVal && !e.newVal) {
510
                this.global_cancel_keyboard_drag();
511
            }
512
        }, this);
513
 
514
        // Focus the first drop target.
515
        if (droplist.one('a')) {
516
            droplist.one('a').focus();
517
        }
518
    },
519
 
520
    /**
521
     * This is used as a simulated drag/drop event in order to prevent any
522
     * subtle bugs from creating a real instance of a drag drop event. This means
523
     * there are no state changes in the Y.DD.DDM and any undefined functions
524
     * will trigger an obvious and fatal error.
525
     * The end result is that we call all our drag/drop handlers but do not bubble the
526
     * event to anyone else.
527
     *
528
     * The functions/properties implemented in the wrapper are:
529
     * e.target
530
     * e.drag
531
     * e.drop
532
     * e.drag.get('node')
533
     * e.drop.get('node')
534
     * e.drag.addHandle()
535
     * e.drag.removeHandle()
536
     *
537
     * @method simulated_drag_drop_event
538
     * @param {Node} dragnode The drag container node
539
     * @param {Node} dropnode The node to initiate the drop on
540
     */
541
    simulated_drag_drop_event: function(dragnode, dropnode) {
542
 
543
        // Subclass for wrapping both drag and drop.
544
        var DragDropWrapper = function(node) {
545
            this.node = node;
546
        };
547
 
548
        // Method e.drag.get() - get the node.
549
        DragDropWrapper.prototype.get = function(param) {
550
            if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
551
                return this.node;
552
            }
553
            if (param === 'activeHandle') {
554
                return this.node.one('.editing_move');
555
            }
556
            return null;
557
        };
558
 
559
        // Method e.drag.inGroup() - we have already run the group checks before triggering the event.
560
        DragDropWrapper.prototype.inGroup = function() {
561
            return true;
562
        };
563
 
564
        // Method e.drag.addHandle() - we don't want to run this.
565
        DragDropWrapper.prototype.addHandle = function() {};
566
        // Method e.drag.removeHandle() - we don't want to run this.
567
        DragDropWrapper.prototype.removeHandle = function() {};
568
 
569
        // Create instances of the DragDropWrapper.
570
        this.drop = new DragDropWrapper(dropnode);
571
        this.drag = new DragDropWrapper(dragnode);
572
        this.target = this.drop;
573
    },
574
 
575
    /**
576
     * This is used to complete a keyboard version of a drag and drop.
577
     * A drop event will be simulated based on the drag and drop nodes.
578
     * @method global_keyboard_drop
579
     * @param {Event} e The keydown / click event on the proxy drop node.
580
     */
581
    global_keyboard_drop: function(e) {
582
        // The drag node was saved.
583
        var dragcontainer = M.core.dragdrop.keydragcontainer;
584
        // The real drop node is stored in an attribute of the proxy.
585
        var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
586
 
587
        // Close the dialog.
588
        M.core.dragdrop.dropui.hide();
589
        // Cancel the event.
590
        e.preventDefault();
591
        // Detect the direction of travel.
592
        if (this.detectkeyboarddirection && dragcontainer.getY() > droptarget.getY()) {
593
            // We can detect the keyboard direction and it is going up.
594
            this.absgoingup = true;
595
            this.goingup = true;
596
        } else {
597
            // The default behaviour is to treat everything as moving down.
598
            this.absgoingup = false;
599
            this.goingup = false;
600
        }
601
        // Convert to drag drop events.
602
        var dragevent = new this.simulated_drag_drop_event(dragcontainer, dragcontainer);
603
        var dropevent = new this.simulated_drag_drop_event(dragcontainer, droptarget);
604
        // Simulate the full sequence.
605
        this.drag_start(dragevent);
606
        this.global_drop_over(dropevent);
607
 
608
        if (droptarget.hasClass(this.parentnodeclass) && droptarget.contains(dragcontainer)) {
609
            // Handle the case where an item is dropped into a container (for example an activity into a new section).
610
            droptarget.prepend(dragcontainer);
611
        }
612
 
613
        this.global_drop_hit(dropevent);
614
    },
615
 
616
    /**
617
     * This is used to cancel a keyboard version of a drag and drop.
618
     *
619
     * @method global_cancel_keyboard_drag
620
     */
621
    global_cancel_keyboard_drag: function() {
622
        if (M.core.dragdrop.keydragcontainer) {
623
            // Focus on the node which was being dragged.
624
            M.core.dragdrop.keydraghandle.focus();
625
            M.core.dragdrop.keydragcontainer = null;
626
        }
627
        if (M.core.dragdrop.dropui) {
628
            M.core.dragdrop.dropui.destroy();
629
        }
630
    },
631
 
632
    /**
633
     * Process key events on the drag handles.
634
     *
635
     * @method global_keydown
636
     * @param {EventFacade} e The keydown / click event on the drag handle.
637
     */
638
    global_keydown: function(e) {
639
        var draghandle = e.target.ancestor('.' + MOVEICON.cssclass, true),
640
            dragcontainer,
641
            draggroups;
642
 
643
        if (draghandle === null) {
644
            // The element clicked did not have a a draghandle in it's lineage.
645
            return;
646
        }
647
 
648
        if (e.keyCode === 27) {
649
            // Escape to cancel from anywhere.
650
            this.global_cancel_keyboard_drag();
651
            e.preventDefault();
652
            return;
653
        }
654
 
655
        // Only process events on a drag handle.
656
        if (!draghandle.hasClass(MOVEICON.cssclass)) {
657
            return;
658
        }
659
 
660
        // Do nothing if not space or enter.
661
        if (e.keyCode !== 13 && e.keyCode !== 32 && e.type !== 'click') {
662
            return;
663
        }
664
 
665
        // Check the drag groups to see if we are the handler for this node.
666
        draggroups = draghandle.getAttribute('data-draggroups').split(' ');
667
        var i, j;
668
        var validgroup = false;
669
 
670
        for (i = 0; i < draggroups.length; i++) {
671
            for (j = 0; j < this.groups.length; j++) {
672
                if (draggroups[i] === this.groups[j]) {
673
                    validgroup = true;
674
                    break;
675
                }
676
            }
677
            if (validgroup) {
678
                break;
679
            }
680
        }
681
        if (!validgroup) {
682
            return;
683
        }
684
 
685
        // Valid event - start the keyboard drag.
686
        dragcontainer = draghandle.ancestor('.yui3-dd-drop');
687
        this.global_start_keyboard_drag(e, draghandle, dragcontainer);
688
 
689
        e.preventDefault();
690
    },
691
 
692
 
693
    // Abstract functions definitions.
694
 
695
    /**
696
     * Callback to use when dragging starts.
697
     *
698
     * @method drag_start
699
     * @param {EventFacade} e
700
     */
701
    drag_start: function() {},
702
 
703
    /**
704
     * Callback to use for the drag:over event.
705
     *
706
     * @method dragOver
707
     * @param {EventFacade} e
708
     */
709
    dragOver: function() {},
710
 
711
    /**
712
     * Callback to use when dragging ends.
713
     *
714
     * @method drag_end
715
     * @param {EventFacade} e
716
     */
717
    drag_end: function() {},
718
 
719
    /**
720
     * Callback to use during dragging.
721
     *
722
     * @method drag_drag
723
     * @param {EventFacade} e
724
     */
725
    drag_drag: function() {},
726
 
727
    /**
728
     * Callback to use when dragging ends and is not over a drop target.
729
     *
730
     * @method drag_dropmiss
731
     * @param {EventFacade} e
732
     */
733
    drag_dropmiss: function() {},
734
 
735
    /**
736
     * Callback to use when a drop over event occurs.
737
     *
738
     * @method drop_over
739
     * @param {EventFacade} e
740
     */
741
    drop_over: function() {},
742
 
743
    /**
744
     * Callback to use on drop:hit.
745
     *
746
     * @method drop_hit
747
     * @param {EventFacade} e
748
     */
749
    drop_hit: function() {}
750
}, {
751
    NAME: 'dragdrop',
752
    ATTRS: {}
753
});
754
 
755
M.core = M.core || {};
756
M.core.dragdrop = DRAGDROP;