UNPKG

20.6 kBJavaScriptView Raw
1'use strict';
2/*
3* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
4*/
5
6// These are the definitions for all of the predefined buttons.
7// You do not need to load this file in order to use buttons.
8
9// A 'Button' is a Panel that has a Shape surrounding some content
10// and that has mouseEnter/mouseLeave behavior to highlight the button.
11// The content of the button, whether a TextBlock or a Picture or a complicated Panel,
12// must be supplied by the caller.
13// The caller must also provide a click event handler.
14
15// Typical usage:
16// $('Button',
17// $(go.TextBlock, 'Click me!'), // the content is just the text label
18// { click: function(e, obj) { alert('I was clicked'); } }
19// )
20
21// Note that a button click event handler is not invoked upon a click if isEnabledObject() returns false.
22
23go.GraphObject.defineBuilder('Button', function (args) {
24 // default colors for 'Button' shape
25 var buttonFillNormal = '#F5F5F5';
26 var buttonStrokeNormal = '#BDBDBD';
27 var buttonFillOver = '#E0E0E0';
28 var buttonStrokeOver = '#9E9E9E';
29 var buttonFillPressed = '#BDBDBD'; // set to null for no button pressed effects
30 var buttonStrokePressed = '#9E9E9E';
31 var buttonFillDisabled = '#E5E5E5';
32
33 // padding inside the ButtonBorder to match sizing from previous versions
34 var paddingHorizontal = 2.76142374915397;
35 var paddingVertical = 2.761423749153969;
36
37 var button = /** @type {Panel} */ (
38 go.GraphObject.make(go.Panel, 'Auto',
39 {
40 isActionable: true, // needed so that the ActionTool intercepts mouse events
41 enabledChanged: function (btn, enabled) {
42 var shape = btn.findObject('ButtonBorder');
43 if (shape !== null) {
44 shape.fill = enabled ? btn['_buttonFillNormal'] : btn['_buttonFillDisabled'];
45 }
46 },
47 cursor: 'pointer',
48 // save these values for the mouseEnter and mouseLeave event handlers
49 '_buttonFillNormal': buttonFillNormal,
50 '_buttonStrokeNormal': buttonStrokeNormal,
51 '_buttonFillOver': buttonFillOver,
52 '_buttonStrokeOver': buttonStrokeOver,
53 '_buttonFillPressed': buttonFillPressed,
54 '_buttonStrokePressed': buttonStrokePressed,
55 '_buttonFillDisabled': buttonFillDisabled
56 },
57 go.GraphObject.make(go.Shape, // the border
58 {
59 name: 'ButtonBorder',
60 figure: 'RoundedRectangle',
61 spot1: new go.Spot(0, 0, paddingHorizontal, paddingVertical),
62 spot2: new go.Spot(1, 1, -paddingHorizontal, -paddingVertical),
63 parameter1: 2,
64 parameter2: 2,
65 fill: buttonFillNormal,
66 stroke: buttonStrokeNormal
67 }
68 )
69 )
70 );
71
72 // There's no GraphObject inside the button shape -- it must be added as part of the button definition.
73 // This way the object could be a TextBlock or a Shape or a Picture or arbitrarily complex Panel.
74
75 // mouse-over behavior
76 button.mouseEnter = function (e, btn, prev) {
77 if (!btn.isEnabledObject()) return;
78 var shape = btn.findObject('ButtonBorder'); // the border Shape
79 if (shape instanceof go.Shape) {
80 var brush = btn['_buttonFillOver'];
81 btn['_buttonFillNormal'] = shape.fill;
82 shape.fill = brush;
83 brush = btn['_buttonStrokeOver'];
84 btn['_buttonStrokeNormal'] = shape.stroke;
85 shape.stroke = brush;
86 }
87 };
88
89 button.mouseLeave = function (e, btn, prev) {
90 if (!btn.isEnabledObject()) return;
91 var shape = btn.findObject('ButtonBorder'); // the border Shape
92 if (shape instanceof go.Shape) {
93 shape.fill = btn['_buttonFillNormal'];
94 shape.stroke = btn['_buttonStrokeNormal'];
95 }
96 };
97
98 // mousedown/mouseup behavior
99 button.actionDown = function (e, btn) {
100 if (!btn.isEnabledObject()) return;
101 if (btn['_buttonFillPressed'] === null) return;
102 if (e.button !== 0) return;
103 var shape = btn.findObject('ButtonBorder'); // the border Shape
104 if (shape instanceof go.Shape) {
105 var diagram = e.diagram;
106 var oldskip = diagram.skipsUndoManager;
107 diagram.skipsUndoManager = true;
108 var brush = btn['_buttonFillPressed'];
109 btn['_buttonFillOver'] = shape.fill;
110 shape.fill = brush;
111 brush = btn['_buttonStrokePressed'];
112 btn['_buttonStrokeOver'] = shape.stroke;
113 shape.stroke = brush;
114 diagram.skipsUndoManager = oldskip;
115 }
116 };
117
118 button.actionUp = function (e, btn) {
119 if (!btn.isEnabledObject()) return;
120 if (btn['_buttonFillPressed'] === null) return;
121 if (e.button !== 0) return;
122 var shape = btn.findObject('ButtonBorder'); // the border Shape
123 if (shape instanceof go.Shape) {
124 var diagram = e.diagram;
125 var oldskip = diagram.skipsUndoManager;
126 diagram.skipsUndoManager = true;
127 if (overButton(e, btn)) {
128 shape.fill = btn['_buttonFillOver'];
129 shape.stroke = btn['_buttonStrokeOver'];
130 } else {
131 shape.fill = btn['_buttonFillNormal'];
132 shape.stroke = btn['_buttonStrokeNormal'];
133 }
134 diagram.skipsUndoManager = oldskip;
135 }
136 };
137
138 button.actionCancel = function (e, btn) {
139 if (!btn.isEnabledObject()) return;
140 if (btn['_buttonFillPressed'] === null) return;
141 var shape = btn.findObject('ButtonBorder'); // the border Shape
142 if (shape instanceof go.Shape) {
143 var diagram = e.diagram;
144 var oldskip = diagram.skipsUndoManager;
145 diagram.skipsUndoManager = true;
146 if (overButton(e, btn)) {
147 shape.fill = btn['_buttonFillOver'];
148 shape.stroke = btn['_buttonStrokeOver'];
149 } else {
150 shape.fill = btn['_buttonFillNormal'];
151 shape.stroke = btn['_buttonStrokeNormal'];
152 }
153 diagram.skipsUndoManager = oldskip;
154 }
155 };
156
157 button.actionMove = function (e, btn) {
158 if (!btn.isEnabledObject()) return;
159 if (btn['_buttonFillPressed'] === null) return;
160 var diagram = e.diagram;
161 if (diagram.firstInput.button !== 0) return;
162 diagram.currentTool.standardMouseOver();
163 if (overButton(e, btn)) {
164 var shape = btn.findObject('ButtonBorder');
165 if (shape instanceof go.Shape) {
166 var oldskip = diagram.skipsUndoManager;
167 diagram.skipsUndoManager = true;
168 let brush = btn['_buttonFillPressed'];
169 if (shape.fill !== brush) shape.fill = brush;
170 brush = btn['_buttonStrokePressed'];
171 if (shape.stroke !== brush) shape.stroke = brush;
172 diagram.skipsUndoManager = oldskip;
173 }
174 }
175 };
176
177 function overButton(e, btn) {
178 var over = e.diagram.findObjectAt(
179 e.documentPoint,
180 function (x) {
181 while (x.panel !== null) {
182 if (x.isActionable) return x;
183 x = x.panel;
184 }
185 return x;
186 },
187 function (x) { return x === btn; }
188 );
189 return over !== null;
190 }
191
192 return button;
193});
194
195
196// This is a complete Button that you can have in a Node template
197// to allow the user to collapse/expand the subtree beginning at that Node.
198
199// Typical usage within a Node template:
200// $('TreeExpanderButton')
201
202go.GraphObject.defineBuilder('TreeExpanderButton', function (args) {
203 var button = /** @type {Panel} */ (
204 go.GraphObject.make('Button',
205 { // set these values for the isTreeExpanded binding conversion
206 '_treeExpandedFigure': 'MinusLine',
207 '_treeCollapsedFigure': 'PlusLine'
208 },
209 go.GraphObject.make(go.Shape, // the icon
210 {
211 name: 'ButtonIcon',
212 figure: 'MinusLine', // default value for isTreeExpanded is true
213 stroke: '#424242',
214 strokeWidth: 2,
215 desiredSize: new go.Size(8, 8)
216 },
217 // bind the Shape.figure to the Node.isTreeExpanded value using this converter:
218 new go.Binding('figure', 'isTreeExpanded',
219 function (exp, shape) {
220 var but = shape.panel;
221 return exp ? but['_treeExpandedFigure'] : but['_treeCollapsedFigure'];
222 }
223 ).ofObject()
224 ),
225 // assume initially not visible because there are no links coming out
226 { visible: false },
227 // bind the button visibility to whether it's not a leaf node
228 new go.Binding('visible', 'isTreeLeaf',
229 function (leaf) { return !leaf; }
230 ).ofObject()
231 )
232 );
233
234 // tree expand/collapse behavior
235 button.click = function (e, btn) {
236 var node = btn.part;
237 if (node instanceof go.Adornment) node = node.adornedPart;
238 if (!(node instanceof go.Node)) return;
239 var diagram = node.diagram;
240 if (diagram === null) return;
241 var cmd = diagram.commandHandler;
242 if (node.isTreeExpanded) {
243 if (!cmd.canCollapseTree(node)) return;
244 } else {
245 if (!cmd.canExpandTree(node)) return;
246 }
247 e.handled = true;
248 if (node.isTreeExpanded) {
249 cmd.collapseTree(node);
250 } else {
251 cmd.expandTree(node);
252 }
253 };
254
255 return button;
256});
257
258
259// This is a complete Button that you can have in a Group template
260// to allow the user to collapse/expand the subgraph that the Group holds.
261
262// Typical usage within a Group template:
263// $('SubGraphExpanderButton')
264
265go.GraphObject.defineBuilder('SubGraphExpanderButton', function (args) {
266 var button = /** @type {Panel} */ (
267 go.GraphObject.make('Button',
268 { // set these values for the isSubGraphExpanded binding conversion
269 '_subGraphExpandedFigure': 'MinusLine',
270 '_subGraphCollapsedFigure': 'PlusLine'
271 },
272 go.GraphObject.make(go.Shape, // the icon
273 {
274 name: 'ButtonIcon',
275 figure: 'MinusLine', // default value for isSubGraphExpanded is true
276 stroke: '#424242',
277 strokeWidth: 2,
278 desiredSize: new go.Size(8, 8)
279 },
280 // bind the Shape.figure to the Group.isSubGraphExpanded value using this converter:
281 new go.Binding('figure', 'isSubGraphExpanded',
282 function (exp, shape) {
283 var but = shape.panel;
284 return exp ? but['_subGraphExpandedFigure'] : but['_subGraphCollapsedFigure'];
285 }
286 ).ofObject()
287 )
288 )
289 );
290
291 // subgraph expand/collapse behavior
292 button.click = function (e, btn) {
293 var group = btn.part;
294 if (group instanceof go.Adornment) group = group.adornedPart;
295 if (!(group instanceof go.Group)) return;
296 var diagram = group.diagram;
297 if (diagram === null) return;
298 var cmd = diagram.commandHandler;
299 if (group.isSubGraphExpanded) {
300 if (!cmd.canCollapseSubGraph(group)) return;
301 } else {
302 if (!cmd.canExpandSubGraph(group)) return;
303 }
304 e.handled = true;
305 if (group.isSubGraphExpanded) {
306 cmd.collapseSubGraph(group);
307 } else {
308 cmd.expandSubGraph(group);
309 }
310 };
311
312 return button;
313});
314
315
316// This is just an "Auto" Adornment that can hold some contents within a light gray, shadowed box.
317
318// Typical usage:
319// toolTip:
320// $("ToolTip",
321// $(go.TextBlock, . . .)
322// )
323go.GraphObject.defineBuilder('ToolTip', function (args) {
324 var ad = go.GraphObject.make(go.Adornment, 'Auto',
325 {
326 isShadowed: true,
327 shadowColor: 'rgba(0, 0, 0, .4)',
328 shadowOffset: new go.Point(0, 3),
329 shadowBlur: 5
330 },
331 go.GraphObject.make(go.Shape,
332 {
333 name: 'Border',
334 figure: 'RoundedRectangle',
335 parameter1: 1,
336 parameter2: 1,
337 fill: '#F5F5F5',
338 stroke: '#F0F0F0',
339 spot1: new go.Spot(0, 0, 4, 6),
340 spot2: new go.Spot(1, 1, -4, -4)
341 }
342 )
343 );
344 return ad;
345});
346
347
348// This is just a "Vertical" Adornment that can hold some "ContextMenuButton"s.
349
350// Typical usage:
351// contextMenu:
352// $("ContextMenu",
353// $("ContextMenuButton",
354// $(go.TextBlock, . . .),
355// { click: . . .}
356// ),
357// $("ContextMenuButton", . . .)
358// )
359go.GraphObject.defineBuilder('ContextMenu', function (args) {
360 var ad = go.GraphObject.make(go.Adornment, 'Vertical',
361 {
362 background: '#F5F5F5',
363 isShadowed: true,
364 shadowColor: 'rgba(0, 0, 0, .4)',
365 shadowOffset: new go.Point(0, 3),
366 shadowBlur: 5
367 },
368 // don't set the background if the ContextMenu is adorning something and there's a Placeholder
369 new go.Binding('background', '', function (obj) {
370 var part = obj.adornedPart;
371 if (part !== null && obj.placeholder !== null) return null;
372 return '#F5F5F5';
373 })
374 );
375 return ad;
376});
377
378
379// This just holds the 'ButtonBorder' Shape that acts as the border
380// around the button contents, which must be supplied by the caller.
381// The button contents are usually a TextBlock or Panel consisting of a Shape and a TextBlock.
382
383// Typical usage within an Adornment that is either a GraphObject.contextMenu or a Diagram.contextMenu:
384// $('ContextMenuButton',
385// $(go.TextBlock, text),
386// { click: function(e, obj) { alert('Command for ' + obj.part.adornedPart); } },
387// new go.Binding('visible', '', function(data) { return ...OK to perform Command...; })
388// )
389
390go.GraphObject.defineBuilder('ContextMenuButton', function (args) {
391 var button = /** @type {Panel} */ (go.GraphObject.make('Button'));
392 button.stretch = go.GraphObject.Horizontal;
393 var border = button.findObject('ButtonBorder');
394 if (border instanceof go.Shape) {
395 border.figure = 'Rectangle';
396 border.spot1 = new go.Spot(0, 0, 2, 3);
397 border.spot2 = new go.Spot(1, 1, -2, -2);
398 }
399 return button;
400});
401
402
403// This button is used to toggle the visibility of a GraphObject named
404// by the second argument to GraphObject.make. If the second argument is not present
405// or if it is not a string, this assumes that the element name is 'COLLAPSIBLE'.
406// You can only control the visibility of one element in a Part at a time,
407// although that element might be an arbitrarily complex Panel.
408
409// Typical usage:
410// $(go.Panel, . . .,
411// $('PanelExpanderButton', 'COLLAPSIBLE'),
412// . . .,
413// $(go.Panel, . . .,
414// { name: 'COLLAPSIBLE' },
415// . . . stuff to be hidden or shown as the PanelExpanderButton is clicked . . .
416// ),
417// . . .
418// )
419
420go.GraphObject.defineBuilder('PanelExpanderButton', function (args) {
421 var eltname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args, 'COLLAPSIBLE'));
422
423 var button = /** @type {Panel} */ (
424 go.GraphObject.make('Button',
425 { // set these values for the button's look
426 '_buttonExpandedFigure': 'M0 0 M0 6 L4 2 8 6 M8 8',
427 '_buttonCollapsedFigure': 'M0 0 M0 2 L4 6 8 2 M8 8',
428 '_buttonFillNormal': 'rgba(0, 0, 0, 0)',
429 '_buttonStrokeNormal': null,
430 '_buttonFillOver': 'rgba(0, 0, 0, .2)',
431 '_buttonStrokeOver': null,
432 '_buttonFillPressed': 'rgba(0, 0, 0, .4)',
433 '_buttonStrokePressed': null
434 },
435 go.GraphObject.make(go.Shape,
436 { name: 'ButtonIcon', strokeWidth: 2 },
437 new go.Binding('geometryString', 'visible',
438 function (vis) { return vis ? button['_buttonExpandedFigure'] : button['_buttonCollapsedFigure']; }
439 ).ofObject(eltname)
440 )
441 )
442 );
443
444 var border = button.findObject('ButtonBorder');
445 if (border instanceof go.Shape) {
446 border.stroke = null;
447 border.fill = 'rgba(0, 0, 0, 0)';
448 }
449
450 button.click = function (e, btn) {
451 var diagram = btn.diagram;
452 if (diagram === null) return;
453 if (diagram.isReadOnly) return;
454 var elt = btn.findTemplateBinder();
455 if (elt === null) elt = btn.part;
456 if (elt !== null) {
457 var pan = elt.findObject(eltname);
458 if (pan !== null) {
459 e.handled = true;
460 diagram.startTransaction('Collapse/Expand Panel');
461 pan.visible = !pan.visible;
462 diagram.commitTransaction('Collapse/Expand Panel');
463 }
464 }
465 };
466
467 return button;
468});
469
470
471// Define a common checkbox button; the first argument is the name of the data property
472// to which the state of this checkbox is data bound. If the first argument is not a string,
473// it raises an error. If no data binding of the checked state is desired,
474// pass an empty string as the first argument.
475
476// Examples:
477// $('CheckBoxButton', 'dataPropertyName', ...)
478// or:
479// $('CheckBoxButton', '', { '_doClick': function(e, obj) { alert('clicked!'); } })
480
481go.GraphObject.defineBuilder('CheckBoxButton', function (args) {
482 // process the one required string argument for this kind of button
483 var propname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args));
484
485 var button = /** @type {Panel} */ (
486 go.GraphObject.make('Button',
487 { desiredSize: new go.Size(14, 14) },
488 go.GraphObject.make(go.Shape,
489 {
490 name: 'ButtonIcon',
491 geometryString: 'M0 0 M0 8.85 L4.9 13.75 16.2 2.45 M16.2 16.2', // a 'check' mark
492 strokeWidth: 2,
493 stretch: go.GraphObject.Fill, // this Shape expands to fill the Button
494 geometryStretch: go.GraphObject.Uniform, // the check mark fills the Shape without distortion
495 visible: false // visible set to false: not checked, unless data.PROPNAME is true
496 },
497 // create a data Binding only if PROPNAME is supplied and not the empty string
498 (propname !== '' ? new go.Binding('visible', propname).makeTwoWay() : []))
499 )
500 );
501
502 button.click = function (e, btn) {
503 var diagram = e.diagram;
504 if (diagram === null || diagram.isReadOnly) return;
505 if (propname !== '' && diagram.model.isReadOnly) return;
506 e.handled = true;
507 var shape = btn.findObject('ButtonIcon');
508 diagram.startTransaction('checkbox');
509 shape.visible = !shape.visible; // this toggles data.checked due to TwoWay Binding
510 // support extra side-effects without clobbering the click event handler:
511 if (typeof btn['_doClick'] === 'function') btn['_doClick'](e, btn);
512 diagram.commitTransaction('checkbox');
513 };
514
515 return button;
516});
517
518
519// This defines a whole check-box -- including both a 'CheckBoxButton' and whatever you want as the check box label.
520// Note that mouseEnter/mouseLeave/click events apply to everything in the panel, not just in the 'CheckBoxButton'.
521
522// Examples:
523// $('CheckBox', 'aBooleanDataProperty', $(go.TextBlock, 'the checkbox label'))
524// or
525// $('CheckBox', 'someProperty', $(go.TextBlock, 'A choice'),
526// { '_doClick': function(e, obj) { ... perform extra side-effects ... } })
527
528go.GraphObject.defineBuilder('CheckBox', function (args) {
529 // process the one required string argument for this kind of button
530 var propname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args));
531
532 var button = /** @type {Panel} */ (
533 go.GraphObject.make('CheckBoxButton', propname, // bound to this data property
534 {
535 name: 'Button',
536 isActionable: false, // actionable is set on the whole horizontal panel
537 margin: new go.Margin(0, 1, 0, 0)
538 })
539 );
540
541 var box = /** @type {Panel} */ (
542 go.GraphObject.make(go.Panel, 'Horizontal',
543 button,
544 {
545 isActionable: true,
546 cursor: button.cursor,
547 margin: 1,
548 // transfer CheckBoxButton properties over to this new CheckBox panel
549 '_buttonFillNormal': button['_buttonFillNormal'],
550 '_buttonStrokeNormal': button['_buttonStrokeNormal'],
551 '_buttonFillOver': button['_buttonFillOver'],
552 '_buttonStrokeOver': button['_buttonStrokeOver'],
553 '_buttonFillPressed': button['_buttonFillPressed'],
554 '_buttonStrokePressed': button['_buttonStrokePressed'],
555 '_buttonFillDisabled': button['_buttonFillDisabled'],
556 mouseEnter: button.mouseEnter,
557 mouseLeave: button.mouseLeave,
558 actionDown: button.actionDown,
559 actionUp: button.actionUp,
560 actionCancel: button.actionCancel,
561 actionMove: button.actionMove,
562 click: button.click,
563 // also save original Button behavior, for potential use in a Panel.click event handler
564 '_buttonClick': button.click
565 }
566 )
567 );
568 // avoid potentially conflicting event handlers on the 'CheckBoxButton'
569 button.mouseEnter = null;
570 button.mouseLeave = null;
571 button.actionDown = null;
572 button.actionUp = null;
573 button.actionCancel = null;
574 button.actionMove = null;
575 button.click = null;
576 return box;
577});