maxGraph/javascript/examples/touch.html

460 lines
14 KiB
HTML
Raw Normal View History

2013-06-28 19:07:06 +00:00
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
<!--
$Id: touch.html,v 1.6 2013/06/21 12:19:17 gaudenz Exp $
Copyright (c) 2006-2010, JGraph Ltd
Touch example for mxGraph. This example demonstrates handling of touch,
mouse and msPointerEvents.
-->
<!DOCTYPE html>
<html>
<head>
<title>Touch example for mxGraph</title>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalabale=no">
<!-- Increases size of popup menu entries. See menustyle.html for more styling options. -->
<style type="text/css">
body div.mxPopupMenu {
position: absolute;
padding: 3px;
}
body table.mxPopupMenu {
border-collapse: collapse;
margin: 0px;
}
body tr.mxPopupMenuItem {
cursor: default;
}
body td.mxPopupMenuItem {
padding: 10px 60px 10px 30px;
font-family: Arial;
font-size: 9pt;
}
body td.mxPopupMenuIcon {
padding: 0px;
}
table.mxPopupMenu hr {
border-top: solid 1px #cccccc;
}
table.mxPopupMenu tr {
font-size: 4pt;
}
</style>
<!-- Sets the basepath for the library if not in same directory -->
<script type="text/javascript">
mxBasePath = '../src';
</script>
<!-- Loads and initializes the library -->
<script type="text/javascript" src="../src/js/mxClient.js"></script>
<!-- Example code -->
<script type="text/javascript">
// Program starts here. Creates a sample graph in the
// DOM node with the specified ID. This function is invoked
// from the onLoad event handler of the document (see below).
function main(container)
{
// Checks if the browser is supported
if (!mxClient.isBrowserSupported())
{
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
}
else
{
// To detect if touch events are actually supported, the condition following is recommended:
// mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
// Disables built-in text selection and context menu while not editing text
var textEditing = mxUtils.bind(this, function(evt)
{
return graph.isEditing();
});
container.onselectstart = textEditing;
container.onmousedown = textEditing;
if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
{
mxEvent.addListener(container, 'contextmenu', textEditing);
}
else
{
container.oncontextmenu = textEditing;
}
// Creates the graph inside the given container
var graph = new mxGraph(container);
graph.setConnectable(true);
graph.setPanning(true);
// Creates rubberband selection
var rubberband = new mxRubberband(graph);
graph.popupMenuHandler.autoExpand = true;
graph.popupMenuHandler.isSelectOnPopup = function(me)
{
return mxEvent.isMouseEvent(me.getEvent());
};
// Installs context menu
graph.popupMenuHandler.factoryMethod = function(menu, cell, evt)
{
menu.addItem('Item 1', null, function()
{
alert('Item 1');
});
menu.addSeparator();
var submenu1 = menu.addItem('Submenu 1', null, null);
menu.addItem('Subitem 1', null, function()
{
alert('Subitem 1');
}, submenu1);
menu.addItem('Subitem 1', null, function()
{
alert('Subitem 2');
}, submenu1);
};
// Context menu trigger implementation depending on current selection state
// combined with support for normal popup trigger.
var cellSelected = false;
var selectionEmpty = false;
var menuShowing = false;
graph.fireMouseEvent = function(evtName, me, sender)
{
if (evtName == mxEvent.MOUSE_DOWN)
{
// For hit detection on edges
me = this.updateMouseEvent(me);
cellSelected = this.isCellSelected(me.getCell());
selectionEmpty = this.isSelectionEmpty();
menuShowing = graph.popupMenuHandler.isMenuShowing();
}
mxGraph.prototype.fireMouseEvent.apply(this, arguments);
};
// Shows popup menu if cell was selected or selection was empty and background was clicked
graph.popupMenuHandler.mouseUp = function(sender, me)
{
this.popupTrigger = !graph.isEditing() && (this.popupTrigger || (!menuShowing &&
!graph.isEditing() && !mxEvent.isMouseEvent(me.getEvent()) &&
((selectionEmpty && me.getCell() == null && graph.isSelectionEmpty()) ||
(cellSelected && graph.isCellSelected(me.getCell())))));
mxPopupMenuHandler.prototype.mouseUp.apply(this, arguments);
};
// Tap and hold on background starts rubberband for multiple selected
// cells the cell associated with the event is deselected
graph.addListener(mxEvent.TAP_AND_HOLD, function(sender, evt)
{
var me = evt.getProperty('event');
var cell = evt.getProperty('cell');
if (cell == null)
{
var pt = mxUtils.convertPoint(this.container,
mxEvent.getClientX(me), mxEvent.getClientY(me));
rubberband.start(pt.x, pt.y);
}
else if (graph.getSelectionCount() > 1 && graph.isCellSelected(cell))
{
graph.removeSelectionCell(cell);
}
// Blocks further processing of the event
evt.consume();
});
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try
{
var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v1, v2);
}
finally
{
// Updates the display
graph.getModel().endUpdate();
}
// Disables pinch to resize
graph.handleGesture = function()
{
// do nothing
};
// Disables new connections via "hotspot"
graph.connectionHandler.marker.isEnabled = function()
{
return this.graph.connectionHandler.first != null;
};
// Adds custom hit detection if native hit detection found no cell
graph.updateMouseEvent = function(me)
{
var me = mxGraph.prototype.updateMouseEvent.apply(this, arguments);
if (me.getState() == null)
{
var cell = this.getCellAt(me.graphX, me.graphY);
if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY))
{
cell = null;
}
else
{
me.state = this.view.getState(cell);
if (me.state != null && me.state.shape != null)
{
this.container.style.cursor = me.state.shape.node.style.cursor;
}
}
}
if (me.getState() == null)
{
this.container.style.cursor = 'default';
}
return me;
};
}
};
(function()
{
// Enables rotation handle
mxVertexHandler.prototype.rotationEnabled = true;
// Enables managing of sizers
mxVertexHandler.prototype.manageSizers = true;
// Enables live preview
mxVertexHandler.prototype.livePreview = true;
// Sets constants for touch style
mxConstants.HANDLE_SIZE = 16;
mxConstants.LABEL_HANDLE_SIZE = 7;
// Larger tolerance and grid for real touch devices
if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)
{
mxShape.prototype.svgStrokeTolerance = 18;
mxVertexHandler.prototype.tolerance = 12;
mxEdgeHandler.prototype.tolerance = 12;
mxGraph.prototype.tolerance = 12;
}
// One finger pans (no rubberband selection) must start regardless of mouse button
mxPanningHandler.prototype.isPanningTrigger = function(me)
{
var evt = me.getEvent();
return (me.getState() == null && !mxEvent.isMouseEvent(evt)) ||
(mxEvent.isPopupTrigger(evt) && (me.getState() == null || mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)));
};
// Don't clear selection if multiple cells selected
var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
mxGraphHandler.prototype.mouseDown = function(sender, me)
{
graphHandlerMouseDown.apply(this, arguments);
if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1)
{
this.delayedSelection = false;
}
};
// On connect the target is selected and we clone the cell of the preview edge for insert
mxConnectionHandler.prototype.selectCells = function(edge, target)
{
if (target != null)
{
this.graph.setSelectionCell(target);
}
else
{
this.graph.setSelectionCell(edge);
}
};
// Overrides double click handling to use the tolerance
// FIXME: Double click on edges in iPad needs focus on textarea
var graphDblClick = mxGraph.prototype.dblClick;
mxGraph.prototype.dblClick = function(evt, cell)
{
if (cell == null)
{
var pt = mxUtils.convertPoint(this.container,
mxEvent.getClientX(evt), mxEvent.getClientY(evt));
cell = this.getCellAt(pt.x, pt.y);
}
graphDblClick.call(this, evt, cell);
};
// Rounded edge and vertex handles
var touchHandle = new mxImage('grapheditor/www/images/touch-handle.png', 16, 16);
mxVertexHandler.prototype.handleImage = touchHandle;
mxEdgeHandler.prototype.handleImage = touchHandle;
mxOutline.prototype.sizerImage = touchHandle;
// Pre-fetches touch handle
new Image().src = touchHandle.src;
// Adds connect icon to selected vertex
var connectorSrc = 'grapheditor/www/images/touch-connector.png';
var vertexHandlerInit = mxVertexHandler.prototype.init;
mxVertexHandler.prototype.init = function()
{
// TODO: Use 4 sizers, move outside of shape
//this.singleSizer = this.state.width < 30 && this.state.height < 30;
vertexHandlerInit.apply(this, arguments);
// Only show connector image on one cell and do not show on containers
if (this.graph.connectionHandler.isEnabled() &&
this.graph.isCellConnectable(this.state.cell) &&
this.graph.getSelectionCount() == 1)
{
this.connectorImg = mxUtils.createImage(connectorSrc);
this.connectorImg.style.cursor = 'pointer';
this.connectorImg.style.width = '29px';
this.connectorImg.style.height = '29px';
this.connectorImg.style.position = 'absolute';
if (!mxClient.IS_TOUCH)
{
this.connectorImg.setAttribute('title', mxResources.get('connect'));
mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
}
// Starts connecting on touch/mouse down
mxEvent.addGestureListeners(this.connectorImg,
mxUtils.bind(this, function(evt)
{
this.graph.popupMenuHandler.hideMenu();
this.graph.stopEditing(false);
var pt = mxUtils.convertPoint(this.graph.container,
mxEvent.getClientX(evt), mxEvent.getClientY(evt));
this.graph.connectionHandler.start(this.state, pt.x, pt.y);
this.graph.isMouseDown = true;
this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
mxEvent.consume(evt);
})
);
this.graph.container.appendChild(this.connectorImg);
}
this.redrawHandles();
};
var vertexHandlerHideSizers = mxVertexHandler.prototype.hideSizers;
mxVertexHandler.prototype.hideSizers = function()
{
vertexHandlerHideSizers.apply(this, arguments);
if (this.connectorImg != null)
{
this.connectorImg.style.visibility = 'hidden';
}
};
var vertexHandlerReset = mxVertexHandler.prototype.reset;
mxVertexHandler.prototype.reset = function()
{
vertexHandlerReset.apply(this, arguments);
if (this.connectorImg != null)
{
this.connectorImg.style.visibility = '';
}
};
var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles;
mxVertexHandler.prototype.redrawHandles = function()
{
vertexHandlerRedrawHandles.apply(this);
if (this.state != null && this.connectorImg != null)
{
var pt = new mxPoint();
var s = this.state;
// Top right for single-sizer
if (mxVertexHandler.prototype.singleSizer)
{
pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2;
pt.y = s.y - this.connectorImg.offsetHeight / 2;
}
else
{
pt.x = s.x + s.width + mxConstants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2;
pt.y = s.y + s.height / 2;
}
var alpha = mxUtils.toRadians(mxUtils.getValue(s.style, mxConstants.STYLE_ROTATION, 0));
if (alpha != 0)
{
var cos = Math.cos(alpha);
var sin = Math.sin(alpha);
var ct = new mxPoint(s.getCenterX(), s.getCenterY());
pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
}
this.connectorImg.style.left = (pt.x - this.connectorImg.offsetWidth / 2) + 'px';
this.connectorImg.style.top = (pt.y - this.connectorImg.offsetHeight / 2) + 'px';
}
};
var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
mxVertexHandler.prototype.destroy = function(sender, me)
{
vertexHandlerDestroy.apply(this, arguments);
if (this.connectorImg != null)
{
this.connectorImg.parentNode.removeChild(this.connectorImg);
this.connectorImg = null;
}
};
// Pre-fetches touch connector
new Image().src = connectorSrc;
})();
</script>
</head>
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="position:relative;overflow:hidden;width:640px;height:480px;background:url('editors/images/grid.gif');cursor:default;">
</div>
</body>
</html>