对开发者来说ExtJS的一个最大的交互性设计模式就是拖拽,使用拖拽的时候,无需有太多的思考,直接做好就行了。五步可以优雅的完成一个拖拽。
拖拽的定义
拖操作的意思就是鼠标在一个UI界面上按住鼠标左键不放,并且移动。拽操作是在拖操作中鼠标释放的时候触发的操作。抽象层次上,下图就可以对拖拽进行一个总结。
为了加速开发者的开发,有一个Ext.dd类专门管理基础决策,本指导中,覆盖到的是拖拽操作的移除,非法拖拽修复和拖拽成功之后的处理。
组织拖拽类
第一眼看到Ext.dd类,你可能会被吓到。但是当我们细看的时候就会发现他们都是从FragDrop类分支来的,并且大部分都可以归为Drag和Drop组,继续深挖,在拖拽交互中可以发现有single和multi组织区分。
我们先通过插入到DOM节点的方式学习single拖拽交互,我们会利用DD和DDTarget类,它实现了拖拽操作,我们需要先明确性我们的目标。
目标
需求就是说,有家租车公司,提供cars和trucks,有三种状态,租赁,维修和正常。需要将不同状态的车放在三个不同的容器中。
我们使用DD让cars和trucks可以拖拽。我们需要创建三个容纳拖拽目标容器(DDTarget),最后,使用不同的拖拽分组确保不同的车放在不同的分组中。
第一步:开始于drag
在实例化DD的时候,获取循环div数字元素设置为可拖拽。
Ext.onReady(function() {
// Create anobject that we'll use to implement and override drag behaviors a little later
var overrides ={};
// Configure thecars to be draggable
var carElements =Ext.get('cars').select('div');
Ext.each(carElements.elements, function(el) {
var dd =Ext.create('Ext.dd.DD', el, 'carsDDGroup', {
isTarget : false
});
//Apply theoverrides object to the newly created instance of DD
Ext.apply(dd,overrides);
});
var truckElements= Ext.get('trucks').select('div');
Ext.each(truckElements.elements, function(el) {
var dd = Ext.create('Ext.dd.DD', el,'trucksDDGroup', {
isTarget : false
});
Ext.apply(dd,overrides);
});
});
这一步,定义了一个空的overrides,存放一系列的需要的action,通过DomQuery方法获取子div元素,我们通过创建DD实例来让车可拖拽,最后通过Ext.apply将事件都放置到dd里面。
窥视拖曳节点如何受到影响
当你拖拽汽车或卡车周围的元素时,你会发现的第一件事就是它们会粘在任何被丢弃的地方。这是可以的,因为我们刚刚开始实施。重要的是要了解拖曳节点是如何受到影响的。这将有助于我们在返回到它们原来的位置时,将它们丢弃到任何不是有效的丢弃目标的对象上,这被称为“无效滴”。下面的插图使用Fixbug的HTML检查面板,并突出显示当拖动操作应用到CAMARO元素时所做的更改。
在拖动操作期间检查拖拽元素时,我们可以看到一个样式属性添加到元素中,填充了三个CSS值:位置、顶部和左侧。进一步的检查表明,当节点被拖动时,位置属性被设置为相对和左上和左属性更新。在拖动手势完成之后,样式属性与其中包含的样式保持一致。这是我们必须清理的代码,当我们修复一个无效的下降。在我们设置正确的丢弃目标之前,所有的丢弃操作都被认为是无效的。
第二步 修复无效拖拽
最不抵抗的路径是通过复位拖动操作期间应用的样式属性来修复无效的删除。这意味着拖曳元素会从鼠标下方消失,并重新出现在它起源的地方,将会非常乏味。为了使它更平滑,我们将使用Ext.FX动画这个动作。请记住,拖放类被设计为具有重写的方法。要实现修复,我们需要重写B4StastRoad、OnValueDLoad和EnDRAW方法。让我们将下面的方法添加到上面的覆盖对象中,我们将讨论它们是什么和做什么。
var overrides = {
// Called theinstance the element is dragged.
b4StartDrag :function() {
// Cache thedrag element
if (!this.el){
this.el =Ext.get(this.getEl());
}
//Cache theoriginal XY Coordinates of the element, we'll use this later.
this.originalXY = this.el.getXY();
},
// Called whenelement is dropped in a spot without a dropzone, or in a dropzone withoutmatching a ddgroup.
onInvalidDrop :function() {
// Set a flagto invoke the animated repair
this.invalidDrop = true;
},
// Called whenthe drag operation completes
endDrag :function() {
// Invoke the animation if theinvalidDrop flag is set to true
if(this.invalidDrop === true) {
// Removethe drop invitation
this.el.removeCls('dropOK');
// Createthe animation configuration object
varanimCfgObj = {
easing : 'elasticOut',
duration : 1,
scope : this,
callback : function() {
// Remove the position attribute
this.el.dom.style.position = '';
}
};
// Applythe repair animation
this.el.setXY(this.originalXY, animCfgObj);
delete this.invalidDrop;
}
},
在上面的代码中,我们首先重写B4StastRoad方法,它被称为拖拽元素在屏幕周围被拖动的瞬间,并使它成为缓存拖拽元素和原始XY坐标的理想位置,稍后我们将在这个过程中使用它。接下来,我们重写ValueDLoad,这是在拖动节点被丢弃到参与同一拖放组的丢弃目标之外的任何对象时调用的。此重写只需将本地无效DROPULL属性设置为true,将在下一个方法中使用。我们重写的最后一个方法是EntDRAW,当拖动元素不再被拖动在屏幕周围,拖拽元素不再被鼠标移动控制时被调用。此覆盖将使用动画将拖曳元素移回其原来的X和Y位置。我们在动画结束时配置动画,使用弹出式放松来提供一个凉爽有趣的弹跳效果。
第三步 配置拖拽目标
我们的要求规定,我们将允许汽车和卡车掉落在租用和修理集装箱以及他们各自的原始集装箱。要做到这一点,我们需要实例化DDATAL类的实例。这就是它的做法。
// Instantiate instances of Ext.dd.DDTarget for the cars andtrucks container
var carsDDTarget = Ext.create('Ext.dd.DDTarget','cars','carsDDGroup');
var trucksDDTarget = Ext.create('Ext.dd.DDTarget', 'trucks','trucksDDGroup');
// Instantiate instances of DDTarget for the rented and repairdrop target elements
var rentedDDTarget = Ext.create('Ext.dd.DDTarget', 'rented','carsDDGroup');
var repairDDTarget = Ext.create('Ext.dd.DDTarget', 'repair','carsDDGroup');
// Ensure that the rented and repair DDTargets will participatein the trucksDDGroup
rentedDDTarget.addToGroup('trucksDDGroup');
repairDDTarget.addToGroup('trucksDDGroup');
在上面的代码片段中,我们已经为汽车、卡车、租赁和修复元素设置了下降目标。注意,CARS容器元素只参与“CARSDDGROUP”,卡车容器元素参与“TrutksDDGROUP”。这有助于强制要求汽车和卡车只能掉落在原容器中。接下来,我们实例化租用和修复元素的实例DDATAL。最初,它们被配置为只参与“CARSDDGROUP”。为了允许他们参与“TrutksDD群”,我们必须通过AdtoToGy添加它。好,现在我们已经配置了我们的下降目标。让我们看看当我们把汽车或卡车停在一个有效的掉落元件上时会发生什么。
在执行跌落目标时,我们看到拖曳元素恰好保持其下降。也就是说,图像可以落在落下目标上的任何地方,并停留在那里。这意味着我们的辍学实现还没有完成。为了完成它,我们需要通过“另一个重写”来实现“完全删除”操作的代码,这是我们之前创建的DD实例的另一个重写。
第四步 完成拖拽
要完成该拖拽,我们需要使用DOM工具将元素从父元素实际拖动到DROP目标元素。这是通过重写DD OnDracDRAP方法实现的。将下列方法添加到重写对象中。
var overrides = {
...
// Called uponsuccessful drop of an element on a DDTarget with the same
onDragDrop :function(evtObj, targetElId) {
// Wrap the droptarget element with Ext.Element
var dropEl =Ext.get(targetElId);
// Perform thenode move only if the drag element's
// parent is notthe same as the drop target
if(this.el.dom.parentNode.id != targetElId) {
// Move theelement
dropEl.appendChild(this.el);
// Remove thedrag invitation
this.onDragOut(evtObj, targetElId);
// Clear thestyles
this.el.dom.style.position ='';
this.el.dom.style.top = '';
this.el.dom.style.left = '';
}
else {
// This wasan invalid drop, initiate a repair
this.onInvalidDrop();
}
},
在上面的覆盖中,拖动元素被移动到下拉目标元素,但仅当它与拖动元素的父节点不一样时。在拖动元件移动之后,从其上清除样式。如果DROP元素与拖拽元素的父元素相同,则通过调用SIT.OnSimultDROP确保修复操作发生。
一旦成功拖拽,拖拽元素现在将从它们的父元素移动到下拉目标。用户如何知道它们是否悬停在有效的下降目标之上?我们将通过配置删除邀请来给用户一些视觉反馈。
第五步 拖放的校验增加
为了使拖放更加有用,我们需要向用户提供关于是否可以成功地进行跌落操作的反馈。这意味着我们必须重写OnDrand和OnDRAWOUT方法,将这些最后两种方法添加到重写对象中。
var overrides = {
...
// Only calledwhen the drag element is dragged over the a drop target with the
same ddgroup
onDragEnter :function(evtObj, targetElId) {
// Colorizethe drag target if the drag node's parent is not the same as the
drop target
if(targetElId != this.el.dom.parentNode.id) {
this.el.addCls('dropOK');
}
else {
// Removethe invitation
this.onDragOut();
}
},
// Only calledwhen element is dragged out of a dropzone with the same ddgroup
onDragOut :function(evtObj, targetElId) {
this.el.removeCls('dropOK');
}
};
在上面的代码中,我们重写了OnDrand和OnDRAWOUT方法,这两种方法只在拖拽元素与参与同一拖拽组的下拉目标交互时才使用。只有当鼠标指针与拖动目标的边界相交,而拖动项处于拖动模式时,才调用OnDRAGTENT方法。同样,当拖动模式下鼠标光标首先被拖动到下拉目标的边界之外时,调用OnDRAGOUT。
通过向OnDrand和OnDRAGOUT方法添加重写,我们可以看到当鼠标光标首先相交一个有效的下拉目标时,拖动元素的背景会变绿,并且当它离开下拉目标或被丢弃时将失去其绿色背景。这就完成了DOM元素拖放的实现。