[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/yui/build/moodle-core-dragdrop/ -> moodle-core-dragdrop-debug.js (source)

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1