container/wb/Clipboard.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Gareth Johnson
 */

/* eslint-env browser */
/*global niagara*/

/**
 * @module bajaux/container/wb/Clipboard
 */
define([
  "baja!",
  "Promise",
  "bajaux/container/wb/StringList" ], function (
  baja,
  Promise,
  StringList) {

  "use strict";  

  /**
   * A fake Clipboard that uses BajaScript to support drag and drop in Workbench.
   *
   * @class
   * @alias module:bajaux/container/wb/Clipboard
   */
  var Clipboard = function Clipboard() {
    var that = this;
    
    that.$data = {};
    
    /**
     * The current drop effect being used by the clipboard.
     * @type {String}
     */
    that.dropEffect = "none";

    /**
     * The current effect allowed.
     * @type {String}
     */
    that.effectAllowed = "all";
    
    /**
     * A list of the files being dragged (always empty).
     * @see http://dev.w3.org/2006/webapi/FileAPI/#dfn-filelist
     */
    that.files = {
      length: 0,
      item: function () {}
    };
     
    /**
     * The drag data.
     * @type {StringList}
     */
    that.items = new StringList();
  };
    
  /**
   * Return data from the clipboard for the specified format.
   * 
   * @param  {String} mimeType The format of the data to return.
   * @returns the request data (or undefined if nothing can be found).
   */
  Clipboard.prototype.getData = function (mimeType) {
    return this.$data[mimeType];
  };
  
  /**
   * Adds the specified data to the clipboard.
   * 
   * @param {String} mimeType The format of the data being added.
   * @param data The data to be added to the clipboard.
   * @see http://www.w3.org/TR/html5/editing.html#dom-datatransfer-setdata
   */
  Clipboard.prototype.setData = function (mimeType, data) {
    this.$data[mimeType] = data;
    this.items.add(mimeType);
  };
  
  /**
   * Clear the data from the clipboard for the specified format.
   * @param  {String} mimeType The format of the data being cleared.
   */
  Clipboard.prototype.clearData = function (mimeType) {
    if (mimeType && this.$data.hasOwnProperty(mimeType)) {
      delete this.$data[mimeType];
      this.items.remove(mimeType);
    }
  };
  
  /**
   * Sets a drag image (currently is a no-op).
   */
  Clipboard.prototype.setDragImage = function () {};
  
  function makeEvent(eventName, x, y, navNodes) {
    // Create event
    var event = document.createEvent("HTMLEvents");
    event.initEvent(eventName, /*bubbles*/true, /*cancelable*/true);

    // Add drag and drop data.
    event.clientX = x;
    event.clientY = y;
    event.screenX = window.screenX + x;
    event.screenY = window.screenY + y;
    event.dataTransfer = new Clipboard();

    // Set the data to transfer
    event.dataTransfer.setData("Text", JSON.stringify({
      mime: "niagara/navnodes",
      data: navNodes
    }));
    return event;
  }

  function fireEvent(eventName, x, y, navNodesStr) {
    var element = document.elementFromPoint(x, y),
        event,
        navNodes,
        typeSpecs,
        dragCallback = typeof niagara !== 'undefined' && 
                       niagara.wb && 
                       niagara.wb.util && 
                       niagara.wb.util.dragCallback;

    /**
     * @param {boolean} eventWasProcessed true if event processed, false if cancelled
     * @returns {Promise.<boolean>}
     */
    function ok(eventWasProcessed) {
      if (dragCallback) {
        dragCallback(eventWasProcessed);
      }
      return Promise.resolve(eventWasProcessed);
    }

    // Bail if we can't find an element to drag and drop onto
    if (!element) {
      return ok(/*eventWasProcessed*/true);
    }

    // Decode the Nav Node JSON
    navNodes = JSON.parse(navNodesStr);

    event = makeEvent(eventName, x, y, navNodes);

    // Make sure all the Types the NavNodes are referring too are imported.
    typeSpecs = navNodes.map(function (navNode) { return navNode.typeSpec; });

    // Require the Types and then make the dispatch
    return baja.importTypes({ typeSpecs })
      .then(() => {
        // Dispatch the event to the DOM element and return
        // the result of whether it was cancelled or not.
        return ok(element.dispatchEvent(event));
      })
      .catch((err) => {
        if (dragCallback) {
          dragCallback(true);
        }
        throw err;
      });
  }
  
  /**
   * The drag over function to be exported for Workbench to use.
   * 
   * @param {Number} x The x co-ordinate of the drag.
   * @param {Number} y The y co-ordinate of the drag.
   * @param {String} navNodesStr The Nav Node JSON encoded in a String.
   * @returns {Promise} A promise that's resolved once the drag operation has completed.
   */
  Clipboard.dragover = function dragover(x, y, navNodesStr) {
    return fireEvent("dragover", x, y, navNodesStr);
  };
  
  /**
   * The drop function to be exported for Workbench to use.
   * 
   * @param {Number} x The x co-ordinate of the drop.
   * @param {Number} y The y co-ordinate of the drop.
   * @param {String} navNodesStr The Nav Node JSON encoded in a String.
   * @returns {Promise} A promise that's resolved once the drag operation has completed.
   */
  Clipboard.drop = function drop(x, y, navNodesStr) {    
    return fireEvent("drop", x, y, navNodesStr);
  };

  return Clipboard;
});