[ Index ]

PHP Cross Reference of Unnamed Project

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1