[ PHPXref.com ] [ Generated: Sun Jul 20 16:47:37 2008 ] [ Caravel CMS 3.0 ]
[ Index ]     [ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/include/ -> dragdrop.js (source)

   1  // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
   2  // 
   3  // Element.Class part Copyright (c) 2005 by Rick Olson
   4  // 
   5  // Permission is hereby granted, free of charge, to any person obtaining
   6  // a copy of this software and associated documentation files (the
   7  // "Software"), to deal in the Software without restriction, including
   8  // without limitation the rights to use, copy, modify, merge, publish,
   9  // distribute, sublicense, and/or sell copies of the Software, and to
  10  // permit persons to whom the Software is furnished to do so, subject to
  11  // the following conditions:
  12  // 
  13  // The above copyright notice and this permission notice shall be
  14  // included in all copies or substantial portions of the Software.
  15  // 
  16  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23  
  24  
  25  /*--------------------------------------------------------------------------*/
  26  
  27  var Droppables = {
  28    drops: false,
  29    
  30    remove: function(element) {
  31      for(var i = 0; i < this.drops.length; i++)
  32        if(this.drops[i].element == element)
  33          this.drops.splice(i,1);
  34    },
  35    
  36    add: function(element) {
  37      var element = $(element);
  38      var options = Object.extend({
  39        greedy:     true,
  40        hoverclass: null  
  41      }, arguments[1] || {});
  42      
  43      // cache containers
  44      if(options.containment) {
  45        options._containers = new Array();
  46        var containment = options.containment;
  47        if((typeof containment == 'object') && 
  48          (containment.constructor == Array)) {
  49          for(var i=0; i<containment.length; i++)
  50            options._containers.push($(containment[i]));
  51        } else {
  52          options._containers.push($(containment));
  53        }
  54        options._containers_length = 
  55          options._containers.length-1;
  56      }
  57      
  58      Element.makePositioned(element); // fix IE
  59      
  60      options.element = element;
  61      
  62      // activate the droppable    
  63      if(!this.drops) this.drops = [];
  64      this.drops.push(options);
  65    },
  66    
  67    isContained: function(element, drop) {
  68      var containers = drop._containers;
  69      var parentNode = element.parentNode;
  70      var i = drop._containers_length;
  71      do { if(parentNode==containers[i]) return true; } while (i--);
  72      return false;
  73    },
  74    
  75    isAffected: function(pX, pY, element, drop) {
  76      return (
  77        (drop.element!=element) &&
  78        ((!drop._containers) ||
  79          this.isContained(element, drop)) &&
  80        ((!drop.accept) ||
  81          (Element.Class.has_any(element, drop.accept))) &&
  82        Position.within(drop.element, pX, pY) );
  83    },
  84    
  85    deactivate: function(drop) {
  86      Element.Class.remove(drop.element, drop.hoverclass);
  87      this.last_active = null;
  88    },
  89    
  90    activate: function(drop) {
  91      if(this.last_active) this.deactivate(this.last_active);
  92      if(drop.hoverclass)
  93        Element.Class.add(drop.element, drop.hoverclass);
  94      this.last_active = drop;
  95    },
  96    
  97    show: function(event, element) {
  98      if(!this.drops) return;
  99      var pX = Event.pointerX(event);
 100      var pY = Event.pointerY(event);
 101      Position.prepare();
 102      
 103      var i = this.drops.length-1; do {
 104        var drop = this.drops[i];
 105        if(this.isAffected(pX, pY, element, drop)) {
 106          if(drop.onHover)
 107             drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
 108          if(drop.greedy) { 
 109            this.activate(drop);
 110            return;
 111          }
 112        }
 113      } while (i--);
 114    },
 115    
 116    fire: function(event, element) {
 117      if(!this.last_active) return;
 118      Position.prepare();
 119      
 120      if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
 121        if (this.last_active.onDrop) 
 122          this.last_active.onDrop(element, this.last_active);
 123      
 124    },
 125    
 126    reset: function() {
 127      if(this.last_active)
 128        this.deactivate(this.last_active);
 129    }
 130  }
 131  
 132  Draggables = {
 133    observers: new Array(),
 134    addObserver: function(observer) {
 135      this.observers.push(observer);    
 136    },
 137    removeObserver: function(element) {  // element instead of obsever fixes mem leaks
 138      for(var i = 0; i < this.observers.length; i++)
 139        if(this.observers[i].element && (this.observers[i].element == element))
 140          this.observers.splice(i,1);
 141    },
 142    notify: function(eventName, draggable) {  // 'onStart', 'onEnd'
 143      for(var i = 0; i < this.observers.length; i++)
 144        this.observers[i][eventName](draggable);
 145    }
 146  }
 147  
 148  /*--------------------------------------------------------------------------*/
 149  
 150  Draggable = Class.create();
 151  Draggable.prototype = {
 152    initialize: function(element) {
 153      var options = Object.extend({
 154        handle: false,
 155        starteffect: function(element) { 
 156          new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
 157        },
 158        reverteffect: function(element, top_offset, left_offset) {
 159          var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
 160          new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
 161        },
 162        endeffect: function(element) { 
 163           new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
 164        },
 165        zindex: 1000,
 166        revert: false
 167      }, arguments[1] || {});
 168      
 169      this.element      = $(element);
 170      this.handle       = options.handle ? $(options.handle) : this.element;
 171      
 172      Element.makePositioned(this.element); // fix IE    
 173      
 174      this.offsetX      = 0;
 175      this.offsetY      = 0;
 176      this.originalLeft = this.currentLeft();
 177      this.originalTop  = this.currentTop();
 178      this.originalX    = this.element.offsetLeft;
 179      this.originalY    = this.element.offsetTop;
 180      this.originalZ    = parseInt(this.element.style.zIndex || "0");
 181      
 182      this.options      = options;
 183      
 184      this.active       = false;
 185      this.dragging     = false;   
 186      
 187      this.eventMouseDown = this.startDrag.bindAsEventListener(this);
 188      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
 189      this.eventMouseMove = this.update.bindAsEventListener(this);
 190      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
 191      
 192      Event.observe(this.handle, "mousedown", this.eventMouseDown);
 193      Event.observe(document, "mouseup", this.eventMouseUp);
 194      Event.observe(document, "mousemove", this.eventMouseMove);
 195      Event.observe(document, "keypress", this.eventKeypress);
 196    },
 197    destroy: function() {
 198      Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
 199      Event.stopObserving(document, "mouseup", this.eventMouseUp);
 200      Event.stopObserving(document, "mousemove", this.eventMouseMove);
 201      Event.stopObserving(document, "keypress", this.eventKeypress);
 202    },
 203    currentLeft: function() {
 204      return parseInt(this.element.style.left || '0');
 205    },
 206    currentTop: function() {
 207      return parseInt(this.element.style.top || '0')
 208    },
 209    startDrag: function(event) {
 210      if(Event.isLeftClick(event)) {
 211        this.active = true;
 212        var pointer = [Event.pointerX(event), Event.pointerY(event)];
 213        var offsets = Position.cumulativeOffset(this.element);
 214        this.offsetX =  (pointer[0] - offsets[0]);
 215        this.offsetY =  (pointer[1] - offsets[1]);
 216        Event.stop(event);
 217      }
 218    },
 219    finishDrag: function(event, success) {
 220      this.active = false;
 221      this.dragging = false;
 222      
 223      if(success) Droppables.fire(event, this.element);
 224      Draggables.notify('onEnd', this);
 225      
 226      var revert = this.options.revert;
 227      if(revert && typeof revert == 'function') revert = revert(this.element);
 228      
 229      if(this.options.ghosting) {
 230        Position.relativize(this.element);
 231        Element.remove(this._clone);
 232        this._clone = null;
 233      }
 234        
 235      if(revert && this.options.reverteffect) {
 236        this.options.reverteffect(this.element, 
 237        this.currentTop()-this.originalTop,
 238        this.currentLeft()-this.originalLeft);
 239      } else {
 240        this.originalLeft = this.currentLeft();
 241        this.originalTop  = this.currentTop();
 242      }
 243      
 244      this.element.style.zIndex = this.originalZ;
 245        
 246      if(this.options.endeffect) 
 247        this.options.endeffect(this.element);
 248        
 249      
 250      Droppables.reset();
 251    },
 252    keyPress: function(event) {
 253      if(this.active) {
 254        if(event.keyCode==Event.KEY_ESC) {
 255          this.finishDrag(event, false);
 256          Event.stop(event);
 257        }
 258      }
 259    },
 260    endDrag: function(event) {
 261      if(this.active && this.dragging) {
 262        this.finishDrag(event, true);
 263        Event.stop(event);
 264      }
 265      this.active = false;
 266      this.dragging = false;
 267    },
 268    draw: function(event) {
 269      var pointer = [Event.pointerX(event), Event.pointerY(event)];
 270      var offsets = Position.cumulativeOffset(this.element);
 271      offsets[0] -= this.currentLeft();
 272      offsets[1] -= this.currentTop();
 273      var style = this.element.style;
 274      if((!this.options.constraint) || (this.options.constraint=='horizontal'))
 275        style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
 276      if((!this.options.constraint) || (this.options.constraint=='vertical'))
 277        style.top  = (pointer[1] - offsets[1] - this.offsetY) + "px";
 278      if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
 279    },
 280    update: function(event) {
 281     if(this.active) {
 282        if(!this.dragging) {
 283          var style = this.element.style;
 284          this.dragging = true;
 285          if(style.position=="") style.position = "relative";
 286          style.zIndex = this.options.zindex;
 287          
 288          if(this.options.ghosting) {
 289            this._clone = this.element.cloneNode(true);
 290            Position.absolutize(this.element);
 291            this.element.parentNode.insertBefore(this._clone, this.element);
 292          }
 293          
 294          Draggables.notify('onStart', this);
 295          if(this.options.starteffect) this.options.starteffect(this.element);
 296        }
 297        
 298        Droppables.show(event, this.element);
 299        this.draw(event);
 300        if(this.options.change) this.options.change(this);
 301        
 302        // fix AppleWebKit rendering
 303        if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
 304        
 305        Event.stop(event);
 306     }
 307    }
 308  }
 309  
 310  /*--------------------------------------------------------------------------*/
 311  
 312  SortableObserver = Class.create();
 313  SortableObserver.prototype = {
 314    initialize: function(element, observer) {
 315      this.element   = $(element);
 316      this.observer  = observer;
 317      this.lastValue = Sortable.serialize(this.element);
 318    },
 319    onStart: function() {
 320      this.lastValue = Sortable.serialize(this.element);
 321    },
 322    onEnd: function() {
 323      Sortable.unmark();
 324      if(this.lastValue != Sortable.serialize(this.element))
 325        this.observer(this.element)
 326    }
 327  }
 328  
 329  Sortable = {
 330    sortables: new Array(),
 331    options: function(element){
 332      var element = $(element);
 333      for(var i=0;i<this.sortables.length;i++)
 334        if(this.sortables[i].element == element)
 335          return this.sortables[i];
 336      return null;        
 337    },
 338    destroy: function(element){
 339      var element = $(element);
 340      for(var i=0;i<this.sortables.length;i++) {
 341        if(this.sortables[i].element == element) {
 342          var s = this.sortables[i];
 343          Draggables.removeObserver(s.element);
 344          for(var j=0;j<s.droppables.length;j++)
 345            Droppables.remove(s.droppables[j]);
 346          for(var j=0;j<s.draggables.length;j++)
 347            s.draggables[j].destroy();
 348          this.sortables.splice(i,1);
 349        }
 350      }
 351    },
 352    create: function(element) {
 353      var element = $(element);
 354      var options = Object.extend({ 
 355        element:     element,
 356        tag:         'li',       // assumes li children, override with tag: 'tagname'
 357        dropOnEmpty: false,
 358        tree:        false,      // fixme: unimplemented
 359        overlap:     'vertical', // one of 'vertical', 'horizontal'
 360        constraint:  'vertical', // one of 'vertical', 'horizontal', false
 361        containment: element,    // also takes array of elements (or id's); or false
 362        handle:      false,      // or a CSS class
 363        only:        false,
 364        hoverclass:  null,
 365        ghosting:    false,
 366        onChange:    function() {},
 367        onUpdate:    function() {}
 368      }, arguments[1] || {});
 369      
 370      // clear any old sortable with same element
 371      this.destroy(element);
 372      
 373      // build options for the draggables
 374      var options_for_draggable = {
 375        revert:      true,
 376        ghosting:    options.ghosting,
 377        constraint:  options.constraint,
 378        handle:      handle };
 379     
 380      if(options.starteffect)
 381        options_for_draggable.starteffect = options.starteffect;
 382     
 383      if(options.reverteffect)
 384        options_for_draggable.reverteffect = options.reverteffect;
 385      else
 386        if(options.ghosting) options_for_draggable.reverteffect = function(element) {
 387          element.style.top  = 0;
 388          element.style.left = 0;
 389        };
 390     
 391      if(options.endeffect)
 392        options_for_draggable.endeffect = options.endeffect;
 393     
 394      if(options.zindex)
 395        options_for_draggable.zindex = options.zindex;
 396      
 397      // build options for the droppables  
 398      var options_for_droppable = {
 399        overlap:     options.overlap,
 400        containment: options.containment,
 401        hoverclass:  options.hoverclass,
 402        onHover:     Sortable.onHover,
 403        greedy:      !options.dropOnEmpty
 404      }
 405  
 406      // fix for gecko engine
 407      Element.cleanWhitespace(element); 
 408      
 409      options.draggables = [];
 410      options.droppables = [];
 411      
 412      // make it so
 413      
 414      // drop on empty handling
 415      if(options.dropOnEmpty) {
 416        Droppables.add(element,
 417          {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
 418        options.droppables.push(element);
 419      }
 420       
 421      var elements = this.findElements(element, options);
 422      if(elements) {
 423        for (var i = 0; i < elements.length; i++) {
 424          // handles are per-draggable
 425          var handle = options.handle ? 
 426            Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
 427              options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
 428              Droppables.add(elements[i], options_for_droppable);
 429          
 430          options.droppables.push(elements[i]);
 431        }
 432      }
 433     
 434      // keep reference
 435      this.sortables.push(options);
 436      
 437      // for onupdate
 438      Draggables.addObserver(new SortableObserver(element, options.onUpdate));
 439  
 440    },
 441    
 442    // return all suitable-for-sortable elements in a guaranteed order
 443    findElements: function(element, options) {
 444      if(!element.hasChildNodes()) return null;
 445      var elements = [];
 446      var children = element.childNodes;
 447      for(var i = 0; i<children.length; i++) {
 448        if(children[i].tagName && children[i].tagName==options.tag.toUpperCase() &&
 449          (!options.only || (Element.Class.has(children[i], options.only))))
 450            elements.push(children[i]);
 451        if(options.tree) {
 452          var grandchildren = this.findElements(children[i], options);
 453          if(grandchildren) elements.push(grandchildren);
 454        }
 455      }
 456    
 457      return (elements.length>0 ? elements.flatten() : null);
 458    },
 459    
 460    onHover: function(element, dropon, overlap) {
 461      if(overlap>0.5) {
 462        Sortable.mark(dropon, 'before');
 463        if(dropon.previousSibling != element) {
 464          var oldParentNode = element.parentNode;
 465          element.style.visibility = "hidden"; // fix gecko rendering
 466          dropon.parentNode.insertBefore(element, dropon);
 467          if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) 
 468            oldParentNode.sortable.onChange(element);
 469          if(dropon.parentNode.sortable)
 470            dropon.parentNode.sortable.onChange(element);
 471        }
 472      } else {
 473        Sortable.mark(dropon, 'after');
 474        var nextElement = dropon.nextSibling || null;
 475        if(nextElement != element) {
 476          var oldParentNode = element.parentNode;
 477          element.style.visibility = "hidden"; // fix gecko rendering
 478          dropon.parentNode.insertBefore(element, nextElement);
 479          if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) 
 480            oldParentNode.sortable.onChange(element);
 481          if(dropon.parentNode.sortable)
 482            dropon.parentNode.sortable.onChange(element);
 483        }
 484      }
 485    },
 486    
 487    onEmptyHover: function(element, dropon) {
 488      if(element.parentNode!=dropon) {
 489        dropon.appendChild(element);
 490      }
 491    },
 492    
 493    unmark: function() {
 494      if(Sortable._marker) Element.hide(Sortable._marker);
 495    },
 496    
 497    mark: function(dropon, position) {
 498      // mark on ghosting only
 499      var sortable = Sortable.options(dropon.parentNode);
 500      if(sortable && !sortable.ghosting) return; 
 501      
 502      if(!Sortable._marker) {
 503        Sortable._marker = $('dropmarker') || document.createElement('DIV');
 504        Element.hide(Sortable._marker);
 505        Element.Class.add(Sortable._marker, 'dropmarker');
 506        Sortable._marker.style.position = 'absolute';
 507        document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
 508      }    
 509      var offsets = Position.cumulativeOffset(dropon);
 510      Sortable._marker.style.top  = offsets[1] + 'px';
 511      if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
 512      Sortable._marker.style.left = offsets[0] + 'px';
 513      Element.show(Sortable._marker);
 514    },
 515    
 516    serialize: function(element) {
 517      var element = $(element);
 518      var sortableOptions = this.options(element);
 519      var options = Object.extend({
 520        tag:  sortableOptions.tag,
 521        only: sortableOptions.only,
 522        name: element.id
 523      }, arguments[1] || {});
 524      
 525      var items = $(element).childNodes;
 526      var queryComponents = new Array();
 527   
 528      for(var i=0; i<items.length; i++)
 529        if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
 530          (!options.only || (Element.Class.has(items[i], options.only))))
 531          queryComponents.push(
 532            encodeURIComponent(options.name) + "[]=" + 
 533            encodeURIComponent(items[i].id.split("_")[1]));
 534  
 535      return queryComponents.join("&");
 536    }
 537  } 


[ Powered by PHPXref - Served by Debian GNU/Linux ]