1 /** 2 * @fileOverview dd support for kissy, drag for dd 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add('dd/draggable', function (S, UA, Node, Base, DDM) { 6 7 var each = S.each, 8 ie = UA['ie'], 9 NULL = null, 10 PREFIX_CLS = DDM.PREFIX_CLS, 11 doc = S.Env.host.document; 12 13 /** 14 * @extends Base 15 * @class Provide abilities to make specified node draggable 16 * @memberOf DD 17 */ 18 function Draggable() { 19 var self = this; 20 Draggable.superclass.constructor.apply(self, arguments); 21 self.addTarget(DDM); 22 S.each([ 23 24 /** 25 * @name DD.DDM#dragalign 26 * @description fired when need to compute draggable's posititon during dragging 27 * @event 28 * @param e 29 * @param e.drag current draggable object 30 */ 31 32 /** 33 * @name DD.Draggable#dragalign 34 * @description fired when need to get draggable 's position during dragging 35 * @event 36 * @param e 37 * @param e.drag current draggable object 38 */ 39 "dragalign", 40 41 /** 42 * @name DD.DDM#drag 43 * @description fired during dragging 44 * @event 45 * @param e 46 * @param e.drag current draggable object 47 */ 48 49 /** 50 * @name DD.Draggable#drag 51 * @description fired during dragging 52 * @event 53 * @param e 54 * @param e.drag current draggable object 55 */ 56 "drag", 57 58 /** 59 * @name DD.DDM#dragdrophit 60 * @description fired after drop a draggable onto a droppable object 61 * @event 62 * @param e 63 * @param e.drag current draggable object 64 * @param e.drop current droppable object 65 */ 66 67 /** 68 * @name DD.Draggable#dragdrophit 69 * @description fired after drop a draggable onto a droppable object 70 * @event 71 * @param e 72 * @param e.drag current draggable object 73 * @param e.drop current droppable object 74 */ 75 "dragdrophit", 76 77 78 /** 79 * @name DD.DDM#dragend 80 * @description fired after drag 81 * @event 82 * @param e 83 * @param e.drag current draggable object 84 */ 85 86 /** 87 * @name DD.Draggable#dragend 88 * @description fired after drag 89 * @event 90 * @param e 91 * @param e.drag current draggable object 92 */ 93 "dragend", 94 95 96 /** 97 * @name DD.DDM#dragdropmiss 98 * @description fired after drop a draggable onto nothing 99 * @event 100 * @param e 101 * @param e.drag current draggable object 102 */ 103 104 /** 105 * @name DD.Draggable#dragdropmiss 106 * @description fired after drop a draggable onto nothing 107 * @event 108 * @param e 109 * @param e.drag current draggable object 110 */ 111 "dragdropmiss", 112 113 114 /** 115 * @name DD.DDM#dragexit 116 * @description fired after a draggable leaves a droppable 117 * @event 118 * @param e 119 * @param e.drag current draggable object 120 * @param e.drop current droppable object 121 */ 122 123 /** 124 * @name DD.Draggable#dragexit 125 * @description fired after a draggable leaves a droppable 126 * @event 127 * @param e 128 * @param e.drag current draggable object 129 * @param e.drop current droppable object 130 */ 131 "dragexit", 132 133 134 /** 135 * @name DD.DDM#dragenter 136 * @description fired after a draggable object mouseenter a droppable object 137 * @event 138 * @param e 139 * @param e.drag current draggable object 140 * @param e.drop current droppable object 141 */ 142 143 /** 144 * @name DD.Draggable#dragenter 145 * @description fired after a draggable object mouseenter a droppable object 146 * @event 147 * @param e 148 * @param e.drag current draggable object 149 * @param e.drop current droppable object 150 */ 151 "dragenter", 152 153 154 /** 155 * @name DD.DDM#dragover 156 * @description fired after a draggable object mouseover a droppable object 157 * @event 158 * @param e 159 * @param e.drag current draggable object 160 * @param e.drop current droppable object 161 */ 162 163 /** 164 * @name DD.Draggable#dragover 165 * @description fired after a draggable object mouseover a droppable object 166 * @event 167 * @param e 168 * @param e.drag current draggable object 169 * @param e.drop current droppable object 170 */ 171 "dragover", 172 173 174 /** 175 * @name DD.DDM#dragstart 176 * @description fired after a draggable object start to drag 177 * @event 178 * @param e 179 * @param e.drag current draggable object 180 */ 181 182 /** 183 * @name DD.Draggable#dragstart 184 * @description fired after a draggable object start to drag 185 * @event 186 * @param e 187 * @param e.drag current draggable object 188 */ 189 "dragstart" 190 ], function (e) { 191 self.publish(e, { 192 bubbles:1 193 }); 194 }); 195 // dragNode is equal to node in single mode 196 self.__set("dragNode", self.get("node")); 197 self.on("afterDisabledChange", self._uiSetDisabledChange, self); 198 var disabled; 199 if (disabled = self.get("disabled")) { 200 self._uiSetDisabledChange(disabled); 201 } 202 self._init(); 203 } 204 205 Draggable['POINT'] = "point"; 206 Draggable.INTERSECT = "intersect"; 207 Draggable.STRICT = "strict"; 208 /** 209 * @ignore 210 */ 211 Draggable.ATTRS = 212 /** 213 * @lends DD.Draggable# 214 */ 215 { 216 217 /** 218 * the dragged node. maybe a proxy node. 219 * @type HTMLElement 220 */ 221 node:{ 222 setter:function (v) { 223 if (!(v instanceof Node)) { 224 return Node.one(v); 225 } 226 } 227 }, 228 229 /** 230 * the number of pixels to move to start a drag operation,default is 3. 231 * @type Number 232 */ 233 clickPixelThresh:{ 234 valueFn:function () { 235 return DDM.get("clickPixelThresh"); 236 } 237 }, 238 239 /** 240 * the number of milliseconds to start a drag operation after mousedown,default is 1000 241 * @type Number 242 */ 243 bufferTime:{ 244 valueFn:function () { 245 return DDM.get("bufferTime"); 246 } 247 }, 248 249 /** 250 * the draggable element 251 * @type HTMLElement 252 */ 253 dragNode:{}, 254 255 /** 256 * use protective shim to cross iframe.default:true 257 * @type Boolean 258 */ 259 shim:{ 260 value:true 261 }, 262 263 /** 264 * valid handlers to initiate a drag operation 265 * @type HTMLElement[]|Function[]|String[] 266 */ 267 handlers:{ 268 value:[], 269 getter:function (vs) { 270 var self = this; 271 if (!vs.length) { 272 vs[0] = self.get("node"); 273 } 274 each(vs, function (v, i) { 275 if (S.isFunction(v)) { 276 v = v.call(self); 277 } 278 if (S.isString(v) || v.nodeType) { 279 v = Node.one(v); 280 } 281 vs[i] = v; 282 }); 283 self.__set("handlers", vs); 284 return vs; 285 } 286 }, 287 288 /** 289 * the handler which fired the drag event. 290 * @type NodeList 291 */ 292 activeHandler:{}, 293 294 /** 295 * indicate whether this draggable object is being dragged 296 * @type Boolean 297 */ 298 dragging:{ 299 value:false, 300 setter:function (d) { 301 var self = this; 302 self.get("dragNode")[d ? 'addClass' : 'removeClass'] 303 (PREFIX_CLS + "dragging"); 304 } 305 }, 306 307 /** 308 * <pre> 309 * can be set 'point' or 'intersect' or 'strict' 310 * In point mode, a Drop is targeted by the cursor being over the Target 311 * In intersect mode, a Drop is targeted by "part" of the drag node being over the Target 312 * In strict mode, a Drop is targeted by the "entire" drag node being over the Target 313 * </pre> 314 * @type String 315 */ 316 mode:{ 317 value:'point' 318 }, 319 320 /** 321 * set to disable this draggable so that it can not be dragged. default:false 322 * @type Boolean 323 */ 324 disabled:{ 325 value:false 326 }, 327 328 /** 329 * whether the drag node moves with cursor.default:false,can be used to resize element. 330 * @type Boolean 331 */ 332 move:{ 333 value:false 334 }, 335 336 /** 337 * whether a drag operation can only be trigged by primary(left) mouse button. 338 * Setting false will allow for all mousedown events to trigger drag. 339 * @type Boolean 340 */ 341 primaryButtonOnly:{ 342 value:true 343 }, 344 345 /** 346 * whether halt mousedown event. default:true 347 * @type Boolean 348 */ 349 halt:{ 350 value:true 351 }, 352 353 /** 354 * groups this draggable object belongs to 355 * @type Object 356 * @example 357 * <code> 358 * { 359 * "group1":1, 360 * "group2":1 361 * } 362 * </code> 363 */ 364 groups:{ 365 value:{} 366 } 367 368 }; 369 370 371 var _ieSelectBack; 372 373 function fixIEMouseUp() { 374 doc.body.onselectstart = _ieSelectBack; 375 } 376 377 /** 378 * prevent select text in ie 379 */ 380 function fixIEMouseDown() { 381 _ieSelectBack = doc.body.onselectstart; 382 doc.body.onselectstart = fixIESelect; 383 } 384 385 /** 386 * keeps IE from blowing up on images as drag handlers. 387 * 防止 html5 draggable 元素的拖放默认行为 388 * @param e 389 */ 390 function fixDragStart(e) { 391 e.preventDefault(); 392 } 393 394 /** 395 * keeps IE from selecting text 396 */ 397 function fixIESelect() { 398 return false; 399 } 400 401 402 /** 403 * 鼠标按下时,查看触发源是否是属于 handler 集合, 404 * 保存当前状态 405 * 通知全局管理器开始作用 406 * @param ev 407 */ 408 function handleMouseDown(ev) { 409 var self = this, 410 t = ev.target; 411 412 if (self._checkMouseDown(ev)) { 413 414 if (!self._check(t)) { 415 return; 416 } 417 418 self._prepare(ev); 419 } 420 } 421 422 S.extend(Draggable, Base, 423 /** 424 * @lends DD.Draggable# 425 */ 426 { 427 428 /** 429 * 开始拖时鼠标所在位置,例如 430 * {x:100,y:200} 431 * @type Object 432 * @private 433 */ 434 startMousePos:NULL, 435 436 /** 437 * 开始拖时节点所在位置,例如 438 * {x:100,y:200} 439 * @type Object 440 * @private 441 */ 442 startNodePos:NULL, 443 444 /** 445 * 开始拖时鼠标和节点所在位置的差值 446 */ 447 _diff:NULL, 448 449 /** 450 * mousedown 1秒后自动开始拖的定时器 451 */ 452 _bufferTimer:NULL, 453 454 _uiSetDisabledChange:function (d) { 455 this.get("dragNode")[d ? 'addClass' : 456 'removeClass'](PREFIX_CLS + '-disabled'); 457 }, 458 459 _init:function () { 460 var self = this, 461 node = self.get('node'); 462 node.on('mousedown', handleMouseDown, self) 463 .on('dragstart', self._fixDragStart); 464 }, 465 466 _fixDragStart:fixDragStart, 467 468 /** 469 * 470 * @param {HTMLElement} t 471 */ 472 _check:function (t) { 473 var self = this, 474 handlers = self.get('handlers'), 475 ret = 0; 476 each(handlers, function (handler) { 477 //子区域内点击也可以启动 478 if (handler.contains(t) || 479 handler[0] == t) { 480 ret = 1; 481 self.__set("activeHandler", handler); 482 return false; 483 } 484 }); 485 return ret; 486 }, 487 488 _checkMouseDown:function (ev) { 489 if (this.get('primaryButtonOnly') && ev.button > 1 || 490 this.get("disabled")) { 491 return 0; 492 } 493 return 1; 494 }, 495 496 _prepare:function (ev) { 497 498 var self = this; 499 500 if (ie) { 501 fixIEMouseDown(); 502 } 503 504 // 防止 firefox/chrome 选中 text 505 if (self.get("halt")) { 506 ev.halt(); 507 } else { 508 ev.preventDefault(); 509 } 510 511 var node = self.get("node"), 512 mx = ev.pageX, 513 my = ev.pageY, 514 nxy = node.offset(); 515 self.startMousePos = self.mousePos = { 516 left:mx, 517 top:my 518 }; 519 self.startNodePos = nxy; 520 self._diff = { 521 left:mx - nxy.left, 522 top:my - nxy.top 523 }; 524 DDM._regToDrag(self); 525 526 var bufferTime = self.get("bufferTime"); 527 528 // 是否中央管理,强制限制拖放延迟 529 if (bufferTime) { 530 self._bufferTimer = setTimeout(function () { 531 // 事件到了,仍然是 mousedown 触发! 532 //S.log("drag start by timeout"); 533 self._start(); 534 }, bufferTime); 535 } 536 537 }, 538 539 _clearBufferTimer:function () { 540 var self = this; 541 if (self._bufferTimer) { 542 clearTimeout(self._bufferTimer); 543 self._bufferTimer = 0; 544 } 545 }, 546 547 _move:function (ev) { 548 var self = this, 549 ret, 550 diff = self._diff, 551 startMousePos = self.startMousePos, 552 pageX = ev.pageX, 553 pageY = ev.pageY, 554 left = pageX - diff.left, 555 top = pageY - diff.top; 556 557 558 if (!self.get("dragging")) { 559 var clickPixelThresh = self.get("clickPixelThresh"); 560 // 鼠标经过了一定距离,立即开始 561 if (Math.abs(pageX - startMousePos.left) >= clickPixelThresh || 562 Math.abs(pageY - startMousePos.top) >= clickPixelThresh) { 563 //S.log("start drag by pixel : " + l1 + " : " + l2); 564 self._start(); 565 } 566 // 开始后,下轮 move 开始触发 drag 事件 567 return; 568 } 569 570 self.mousePos = { 571 left:pageX, 572 top:pageY 573 }; 574 575 ret = { 576 left:left, 577 top:top, 578 pageX:pageX, 579 pageY:pageY, 580 drag:self 581 }; 582 583 self.fire("dragalign", { 584 info:ret 585 }); 586 587 var def = 1; 588 589 if (self.fire("drag", ret) === false) { 590 def = 0; 591 } 592 593 if (def && self.get("move")) { 594 // 取 'node' , 改 node 可能是代理哦 595 self.get('node').offset(ret); 596 } 597 }, 598 599 /** 600 * force to stop this drag operation 601 */ 602 stopDrag:function () { 603 if (this.get("dragging")) { 604 DDM._end(); 605 } 606 }, 607 608 _end:function () { 609 var self = this, 610 activeDrop; 611 612 // 否则清除定时器即可 613 self._clearBufferTimer(); 614 if (ie) { 615 fixIEMouseUp(); 616 } 617 // 如果已经开始,收尾工作 618 if (self.get("dragging")) { 619 self.get("node") 620 .removeClass(PREFIX_CLS + "drag-over"); 621 if (activeDrop = DDM.get("activeDrop")) { 622 self.fire('dragdrophit', { 623 drag:self, 624 drop:activeDrop 625 }); 626 } else { 627 self.fire('dragdropmiss', { 628 drag:self 629 }); 630 } 631 self.__set("dragging", 0); 632 self.fire("dragend", { 633 drag:self 634 }); 635 } 636 }, 637 638 _handleOut:function () { 639 var self = this; 640 self.get("node").removeClass(PREFIX_CLS + "drag-over"); 641 /** 642 * html5 => dragleave 643 */ 644 self.fire("dragexit", { 645 drag:self, 646 drop:DDM.get("activeDrop") 647 }); 648 }, 649 650 /** 651 * 652 * @param e 653 */ 654 _handleEnter:function (e) { 655 var self = this; 656 self.get("node").addClass(PREFIX_CLS + "drag-over"); 657 //第一次先触发 dropenter, dragenter 658 self.fire("dragenter", e); 659 }, 660 661 662 _handleOver:function (e) { 663 this.fire("dragover", e); 664 }, 665 666 _start:function () { 667 var self = this; 668 self._clearBufferTimer(); 669 self.__set("dragging", 1); 670 DDM._start(); 671 self.fire("dragstart", { 672 drag:self 673 }); 674 }, 675 676 /** 677 * make the drag node undraggable 678 */ 679 destroy:function () { 680 var self = this, 681 node = self.get('dragNode'); 682 node.detach('mousedown', handleMouseDown, self) 683 .detach('dragstart', self._fixDragStart); 684 self.detach(); 685 } 686 }); 687 688 return Draggable; 689 690 }, { 691 requires:["ua", "node", "base", "./ddm"] 692 }); 693