123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- define([
- "dojo/_base/declare",
- "dojo/_base/lang",
- "dojo/_base/array",
- "dojo/_base/Deferred",
- "dojo/aspect",
- "dojo/on",
- "dojo/topic",
- "dojo/has",
- "dojo/dnd/Source",
- "dojo/dnd/Manager",
- "dojo/_base/NodeList",
- "put-selector/put",
- "../Selection",
- "dojo/has!touch?../util/touch",
- "dojo/has!touch?./_DnD-touch-autoscroll",
- "xstyle/css!dojo/resources/dnd.css"
- ], function(declare, lang, arrayUtil, Deferred, aspect, on, topic, has, DnDSource, DnDManager, NodeList, put, Selection, touchUtil){
- // Requirements
- // * requires a store (sounds obvious, but not all Lists/Grids have stores...)
- // * must support options.before in put calls
- // (if undefined, put at end)
- // * should support copy
- // (copy should also support options.before as above)
-
- // TODOs
- // * consider sending items rather than nodes to onDropExternal/Internal
- // * consider emitting store errors via OnDemandList._trackError
-
- var GridDnDSource = declare(DnDSource, {
- grid: null,
-
- getObject: function(node){
- // summary:
- // getObject is a method which should be defined on any source intending
- // on interfacing with dgrid DnD.
-
- var grid = this.grid;
- // Extract item id from row node id (gridID-row-*).
- return grid.store.get(node.id.slice(grid.id.length + 5));
- },
- _legalMouseDown: function(evt){
- // Fix _legalMouseDown to only allow starting drag from an item
- // (not from bodyNode outside contentNode).
- var legal = this.inherited(arguments);
- return legal && evt.target != this.grid.bodyNode;
- },
- // DnD method overrides
- onDrop: function(sourceSource, nodes, copy){
- var targetSource = this,
- targetRow = this._targetAnchor = this.targetAnchor, // save for Internal
- grid = this.grid,
- store = grid.store;
-
- if(!this.before && targetRow){
- // target before next node if dropped within bottom half of this node
- // (unless there's no node to target at all)
- targetRow = targetRow.nextSibling;
- }
- targetRow = targetRow && grid.row(targetRow);
-
- Deferred.when(targetRow && store.get(targetRow.id), function(target){
- // Note: if dropping after the last row, or into an empty grid,
- // target will be undefined. Thus, it is important for store to place
- // item last in order if options.before is undefined.
-
- // Delegate to onDropInternal or onDropExternal for rest of logic.
- // These are passed the target item as an additional argument.
- if(targetSource != sourceSource){
- targetSource.onDropExternal(sourceSource, nodes, copy, target);
- }else{
- targetSource.onDropInternal(nodes, copy, target);
- }
- });
- },
- onDropInternal: function(nodes, copy, targetItem){
- var store = this.grid.store,
- targetSource = this,
- grid = this.grid,
- anchor = targetSource._targetAnchor,
- targetRow;
-
- if(anchor){ // (falsy if drop occurred in empty space after rows)
- targetRow = this.before ? anchor.previousSibling : anchor.nextSibling;
- }
-
- // Don't bother continuing if the drop is really not moving anything.
- // (Don't need to worry about edge first/last cases since dropping
- // directly on self doesn't fire onDrop, but we do have to worry about
- // dropping last node into empty space beyond rendered rows.)
- if(!copy && (targetRow === nodes[0] ||
- (!targetItem && grid.down(grid.row(nodes[0])).element == nodes[0]))){
- return;
- }
-
- nodes.forEach(function(node){
- Deferred.when(targetSource.getObject(node), function(object){
- // For copy DnD operations, copy object, if supported by store;
- // otherwise settle for put anyway.
- // (put will relocate an existing item with the same id, i.e. move).
- store[copy && store.copy ? "copy" : "put"](object, {
- before: targetItem
- });
- });
- });
- },
- onDropExternal: function(sourceSource, nodes, copy, targetItem){
- // Note: this default implementation expects that two grids do not
- // share the same store. There may be more ideal implementations in the
- // case of two grids using the same store (perhaps differentiated by
- // query), dragging to each other.
- var store = this.grid.store,
- sourceGrid = sourceSource.grid;
-
- // TODO: bail out if sourceSource.getObject isn't defined?
- nodes.forEach(function(node, i){
- Deferred.when(sourceSource.getObject(node), function(object){
- if(!copy){
- if(sourceGrid){
- // Remove original in the case of inter-grid move.
- // (Also ensure dnd source is cleaned up properly)
- Deferred.when(sourceGrid.store.getIdentity(object), function(id){
- !i && sourceSource.selectNone(); // deselect all, one time
- sourceSource.delItem(node.id);
- sourceGrid.store.remove(id);
- });
- }else{
- sourceSource.deleteSelectedNodes();
- }
- }
- // Copy object, if supported by store; otherwise settle for put
- // (put will relocate an existing item with the same id).
- // Note that we use store.copy if available even for non-copy dnd:
- // since this coming from another dnd source, always behave as if
- // it is a new store item if possible, rather than replacing existing.
- store[store.copy ? "copy" : "put"](object, {
- before: targetItem
- });
- });
- });
- },
-
- onDndStart: function(source, nodes, copy){
- // Listen for start events to apply style change to avatar.
-
- this.inherited(arguments); // DnDSource.prototype.onDndStart.apply(this, arguments);
- if(source == this){
- // If TouchScroll is in use, cancel any pending scroll operation.
- if(this.grid.cancelTouchScroll){ this.grid.cancelTouchScroll(); }
-
- // Set avatar width to half the grid's width.
- // Kind of a naive default, but prevents ridiculously wide avatars.
- DnDManager.manager().avatar.node.style.width =
- this.grid.domNode.offsetWidth / 2 + "px";
- }
- },
-
- onMouseDown: function(evt){
- // Cancel the drag operation on presence of more than one contact point.
- // (This check will evaluate to false under non-touch circumstances.)
- if(has("touch") && this.isDragging &&
- touchUtil.countCurrentTouches(evt, this.grid.touchNode) > 1){
- topic.publish("/dnd/cancel");
- DnDManager.manager().stopDrag();
- }else{
- this.inherited(arguments);
- }
- },
-
- onMouseMove: function(evt){
- // If we're handling touchmove, only respond to single-contact events.
- if(!has("touch") || touchUtil.countCurrentTouches(evt, this.grid.touchNode) === 1){
- this.inherited(arguments);
- }
- },
-
- checkAcceptance: function(source, nodes){
- // Augment checkAcceptance to block drops from sources without getObject.
- return source.getObject &&
- DnDSource.prototype.checkAcceptance.apply(this, arguments);
- },
- getSelectedNodes: function(){
- // If dgrid's Selection mixin is in use, synchronize with it, using a
- // map of node references (updated on dgrid-[de]select events).
-
- if(!this.grid.selection){
- return this.inherited(arguments);
- }
- var t = new NodeList(),
- id;
- for(id in this.grid.selection){
- t.push(this._selectedNodes[id]);
- }
- return t; // NodeList
- }
- // TODO: could potentially also implement copyState to jive with default
- // onDrop* implementations (checking whether store.copy is available);
- // not doing that just yet until we're sure about default impl.
- });
-
- // Mix in Selection for more resilient dnd handling, particularly when part
- // of the selection is scrolled out of view and unrendered (which we
- // handle below).
- var DnD = declare(Selection, {
- // dndSourceType: String
- // Specifies the type which will be set for DnD items in the grid,
- // as well as what will be accepted by it by default.
- dndSourceType: "dgrid-row",
-
- // dndParams: Object
- // Object containing params to be passed to the DnD Source constructor.
- dndParams: null,
-
- // dndConstructor: Function
- // Constructor from which to instantiate the DnD Source.
- // Defaults to the GridSource constructor defined/exposed by this module.
- dndConstructor: GridDnDSource,
-
- postMixInProperties: function(){
- this.inherited(arguments);
- // ensure dndParams is initialized
- this.dndParams = lang.mixin({ accept: [this.dndSourceType] }, this.dndParams);
- },
-
- postCreate: function(){
- this.inherited(arguments);
-
- // Make the grid's content a DnD source/target.
- this.dndSource = new (this.dndConstructor || GridDnDSource)(
- this.bodyNode,
- lang.mixin(this.dndParams, {
- // add cross-reference to grid for potential use in inter-grid drop logic
- grid: this,
- dropParent: this.contentNode
- })
- );
-
- // Set up select/deselect handlers to maintain references, in case selected
- // rows are scrolled out of view and unrendered, but then dragged.
- var selectedNodes = this.dndSource._selectedNodes = {};
-
- function selectRow(row){
- selectedNodes[row.id] = row.element;
- }
- function deselectRow(row){
- delete selectedNodes[row.id];
- }
-
- this.on("dgrid-select", function(event){
- arrayUtil.forEach(event.rows, selectRow);
- });
- this.on("dgrid-deselect", function(event){
- arrayUtil.forEach(event.rows, deselectRow);
- });
-
- aspect.after(this, "destroy", function(){
- delete this.dndSource._selectedNodes;
- selectedNodes = null;
- this.dndSource.destroy();
- }, true);
- },
-
- insertRow: function(object){
- // override to add dojoDndItem class to make the rows draggable
- var row = this.inherited(arguments),
- type = typeof this.getObjectDndType == "function" ?
- this.getObjectDndType(object) : [this.dndSourceType];
-
- put(row, ".dojoDndItem");
- this.dndSource.setItem(row.id, {
- data: object,
- type: type instanceof Array ? type : [type]
- });
- return row;
- },
-
- removeRow: function (rowElement) {
- this.dndSource.delItem(this.row(rowElement));
- this.inherited(arguments);
- }
- });
- DnD.GridSource = GridDnDSource;
-
- return DnD;
- });
|