Configure drag & drop in the structure view
The 7.4.0 release of FontoXML introduces experimental drag and drop support in the structure view (also known as "Outline"). To enable this experiment, ensure that the enable-experiment/drag-and-drop-in-structure-view-sidebar configuration value is set to true
.
Drag and drop works by computing the state of one of a number of operations for positions where an item can be dropped. Such operations can be added by using the add
Creating a drag & drop operation
A drag & drop operation is passed information in three parts: the item that is moving (passed as an array moving
), the item that is to be the new parent (passed as new
) and the position where the item is being dropped (passed as drop
, as a combination of a parent
and reference
). To allow maximum flexibility in creating drag & drop operations to, for instance, outdent items, the new parent may not be the same parent under which the item was dropped, but may also be an ancestor.
The current implementation of drag & drop does not support dragging more than a single item (not counting descendants). Therefore, the moving
array will, for now, always hold a single entry. A future release may extend this to allow multiple items to be dragged and dropped.
Each of these is based on the configured structure view items, and are therefore composed of:
-
hierarchy
, representing the HierarchyNode Id Node Id of the closest hierarchy node -
context
, representing the NodeNode Id Id of the target node -
source
, representing (for items in the hierarchy) the NodeNode Id Id of the source node of the document reference for the hierarchy node corresponding to the item, or null otherwise
The root level of the structure view is represented by the parent items being set to null.
Due to the asynchronous nature of the UI, drag and drop operations may get their state queried in situations where the nodes referenced by these properties no longer appear in their documents. This could cause functions such as unsafeis
method to check whether relevant nodes are still part of their document.
Example
The following is the source of the built-in custom mutation to support drag & drop in a single document. This can be a good starting point for creating your own drag & drop operation:
moveHierarchyNodeInDom.js
JavaScript
import CustomMutationResult from 'fontoxml-base-flow/src/CustomMutationResult.js';
import blueprintMutations from 'fontoxml-blueprints/src/blueprintMutations.js';
import blueprintQuery from 'fontoxml-blueprints/src/blueprintQuery.js';
import domInfo from 'fontoxml-dom-utils/src/domInfo.js';
import evaluateXPathToNumber from 'fontoxml-selectors/src/evaluateXPathToNumber.js';
function getContextNode(blueprint, ids) {
return (ids && blueprint.lookup(ids.contextNodeId)) || null;
}
export default function moveHierarchyNodeInDom(stepData, blueprint, _format, _selection) {
// Moving multiple nodes is not yet supported by the UI nor allowed by this custom mutation
if (stepData.movingNodes.length !== 1) {
return CustomMutationResult.notAllowed();
}
var movingNode = getContextNode(blueprint, stepData.movingNodes[0]);
var newParentNode = getContextNode(blueprint, stepData.newParentNode);
var dropParentNode = getContextNode(blueprint, stepData.dropPosition.parentNode);
var dropReferenceNode = getContextNode(blueprint, stepData.dropPosition.referenceNode);
if (
!newParentNode ||
!dropParentNode ||
!movingNode ||
!blueprintQuery.isInDocument(blueprint, movingNode) ||
domInfo.isDocument(newParentNode) ||
movingNode.ownerDocument !== newParentNode.ownerDocument
) {
// This operation does not support drag & drop to the root level (which could
// possibly create a new document?), or between different documents.
// Additionally, ignore any operation state computation for nodes that are no
// longer in their document (see warning above).
return CustomMutationResult.notAllowed();
}
if (blueprintQuery.contains(blueprint, movingNode, newParentNode)) {
// A node should not be inserted as its own descendant
return CustomMutationResult.notAllowed();
}
if (
dropReferenceNode !== null &&
blueprint.getParentNode(dropReferenceNode) !== dropParentNode
) {
// The drop position reference node and parent node are not directly related, try to
// find an ancestor of the reference node that is a child of the parent node.
dropReferenceNode = blueprintQuery.findClosestAncestor(
blueprint,
dropReferenceNode,
function(ancestor) {
return blueprint.getParentNode(ancestor) === dropParentNode;
}
);
if (dropReferenceNode === null) {
// No descendant relation at all, block the operation
return CustomMutationResult.notAllowed();
}
}
// Check if we can traverse from the drop position to the new parent without skipping over
// following elements or text, and determine the corresponding insertion position for the
// moved node.
var referenceNode = dropReferenceNode;
for (
var ancestor = dropParentNode;
ancestor !== newParentNode;
referenceNode = blueprint.getNextSibling(ancestor),
ancestor = blueprint.getParentNode(ancestor)
) {
if (referenceNode === movingNode) {
referenceNode = blueprint.getNextSibling(referenceNode);
}
if (referenceNode === null) {
continue;
}
if (
domInfo.isElement(referenceNode) ||
domInfo.isTextNode(referenceNode) ||
evaluateXPathToNumber(
'count(following-sibling::*) + count(following-sibling::text())',
referenceNode,
blueprint
) > 0
) {
return CustomMutationResult.notAllowed();
}
}
// Dropping a node before itself has no effect
if (movingNode !== referenceNode) {
// We can now just move the moving node to the new position
blueprintMutations.unsafeMoveNodes(
movingNode,
movingNode,
blueprint,
newParentNode,
referenceNode,
false
);
}
// If we reached this point, the drop was successful. Fonto will validate the result to
// ensure it is valid according to the schema, and disable the operation otherwise.
return CustomMutationResult.ok();
}
Disabling drag and drop for a specific item
Preventing a user from drag and dropping a specific item in the structure view can be done through basic configuration. This is done by using the property is
of configure
Example
configureSxModule.js
JavaScript
import configureAsStructureViewItem from 'fontoxml-families/src/configureAsStructureViewItem.js';
export default function configureSxModule() {
configureAsStructureViewItem(sxModule, 'self::p', {
isDraggable: false,
icon: 'arrow-circle-down'
});
}