Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(371)

Side by Side Diff: app/views/topology/mega.js

Issue 6971045: Panzoom Module
Patch Set: Panzoom Module Created 12 years, 4 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « app/views/environment.js ('k') | app/views/topology/panzoom.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 'use strict'; 1 'use strict';
2 2
3 /** 3 /**
4 * IMPORTANT 4 * IMPORTANT
5 * 5 *
6 * This module represents a single step in the refactor of the environment 6 * This module represents a single step in the refactor of the environment
7 * view. This module is THROW AWAY CODE. Each forthcoming branch should 7 * view. This module is THROW AWAY CODE. Each forthcoming branch should
8 * begin by moving relevant code to the proper module, binding that 8 * begin by moving relevant code to the proper module, binding that
9 * module to Topo and removing code from here. 9 * module to Topo and removing code from here.
10 * 10 *
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
46 d3.select(this) 46 d3.select(this)
47 .select('.unit-count') 47 .select('.unit-count')
48 .attr('class', 'unit-count show-count'); 48 .attr('class', 'unit-count show-count');
49 }}, 49 }},
50 mouseout: {callback: function(d, self) { 50 mouseout: {callback: function(d, self) {
51 d3.select(this) 51 d3.select(this)
52 .select('.unit-count') 52 .select('.unit-count')
53 .attr('class', 'unit-count hide-count'); 53 .attr('class', 'unit-count hide-count');
54 }} 54 }}
55 }, 55 },
56
57 '.rel-label': { 56 '.rel-label': {
58 click: 'relationClick', 57 click: 'relationClick',
59 mousemove: 'mousemove' 58 mousemove: 'mousemove'
60 }, 59 },
61
62 '.topology .crosshatch-background rect:first-child': { 60 '.topology .crosshatch-background rect:first-child': {
63 /** 61 /**
64 * If the user clicks on the background we cancel any active add 62 * If the user clicks on the background we cancel any active add
65 * relation. 63 * relation.
66 */ 64 */
67 click: {callback: function(d, self) { 65 click: {callback: function(d, self) {
68 var container = self.get('container'); 66 var container = self.get('container');
69 container.all('.environment-menu.active').removeClass('active'); 67 container.all('.environment-menu.active').removeClass('active');
70 self.service_click_actions.toggleControlPanel(null, self); 68 self.service_click_actions.toggleControlPanel(null, self);
71 self.cancelRelationBuild(); 69 self.cancelRelationBuild();
72 self.hideSubordinateRelations(); 70 self.hideSubordinateRelations();
73 }}, 71 }},
74 mousemove: 'mousemove' 72 mousemove: 'mousemove'
75 }, 73 },
76 '.dragline': { 74 '.dragline': {
77 /** The user clicked while the dragline was active. */ 75 /** The user clicked while the dragline was active. */
78 click: {callback: function(d, self) { 76 click: {callback: function(d, self) {
79 // It was technically the dragline that was clicked, but the 77 // It was technically the dragline that was clicked, but the
80 // intent was to click on the background, so... 78 // intent was to click on the background, so...
81 self.backgroundClicked(); 79 self.backgroundClicked();
82 }} 80 }}
83 }, 81 },
84
85 '#zoom-out-btn': {click: 'zoom_out'},
86 '#zoom-in-btn': {click: 'zoom_in'},
87 '.graph-list-picker .picker-button': { 82 '.graph-list-picker .picker-button': {
88 click: 'showGraphListPicker' 83 click: 'showGraphListPicker'
89 }, 84 },
90 '.graph-list-picker .picker-expanded': { 85 '.graph-list-picker .picker-expanded': {
91 click: 'hideGraphListPicker' 86 click: 'hideGraphListPicker'
92 }, 87 },
93 // Menu/Controls 88 // Menu/Controls
94 '.add-relation': { 89 '.add-relation': {
95 /** The user clicked on the "Build Relation" menu item. */ 90 /** The user clicked on the "Build Relation" menu item. */
96 click: { 91 click: {
(...skipping 28 matching lines...) Expand all
125 service = context.serviceForBox(box); 120 service = context.serviceForBox(box);
126 context.service_click_actions 121 context.service_click_actions
127 .toggleControlPanel(box, context); 122 .toggleControlPanel(box, context);
128 context.service_click_actions 123 context.service_click_actions
129 .destroyServiceConfirm(service, context); 124 .destroyServiceConfirm(service, context);
130 }} 125 }}
131 } 126 }
132 }, 127 },
133 d3: { 128 d3: {
134 '.service': { 129 '.service': {
135 'mousedown.addrel': {callback: function(d, self) { 130 'mousedown.addrel': {callback: function(d, context) {
136 var evt = d3.event; 131 var evt = d3.event;
137 self.longClickTimer = Y.later(750, this, function(d, e) { 132 context.longClickTimer = Y.later(750, this, function(d, e) {
138 // Provide some leeway for accidental dragging. 133 // Provide some leeway for accidental dragging.
139 if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) / 134 if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) /
140 2 > 5) { 135 2 > 5) {
141 return; 136 return;
142 } 137 }
143 138
144 // Sometimes mouseover is fired after the mousedown, so ensure 139 // Sometimes mouseover is fired after the mousedown, so ensure
145 // we have the correct event in d3.event for d3.mouse(). 140 // we have the correct event in d3.event for d3.mouse().
146 d3.event = e; 141 d3.event = e;
147 142
148 // Start the process of adding a relation 143 // Start the process of adding a relation
149 self.addRelationDragStart(d, self); 144 context.addRelationDragStart(d, context);
150 }, [d, evt], false); 145 }, [d, evt], false);
151 }}, 146 }},
152 'mouseup.addrel': {callback: function(d, self) { 147 'mouseup.addrel': {callback: function(d, context) {
153 // Cancel the long-click timer if it exists. 148 // Cancel the long-click timer if it exists.
154 if (self.longClickTimer) { 149 if (context.longClickTimer) {
155 self.longClickTimer.cancel(); 150 context.longClickTimer.cancel();
156 } 151 }
157 }} 152 }}
158 } 153 }
159 }, 154 },
160 yui: { 155 yui: {
161 windowresize: 'setSizesFromViewport' 156 windowresize: {
157 callback: 'setSizesFromViewport',
158 context: 'module'},
159 rendered: 'renderedHandler'
162 } 160 }
163 }, 161 },
164 162
165 initializer: function(options) { 163 initializer: function(options) {
166 MegaModule.superclass.constructor.apply(this, arguments); 164 MegaModule.superclass.constructor.apply(this, arguments);
167 this.publish('navigateTo', {preventable: false});
168 165
169 // Build a service.id -> BoundingBox map for services. 166 // Build a service.id -> BoundingBox map for services.
170 this.service_boxes = {}; 167 this.service_boxes = {};
171 168
172 // Set a default 169 // Set a default
173 this.set('currentServiceClickAction', 'toggleControlPanel'); 170 this.set('currentServiceClickAction', 'toggleControlPanel');
174 }, 171 },
175 172
176 render: function() {
177 MegaModule.superclass.render.apply(this, arguments);
178 var container = this.get('container');
179 container.setHTML(Templates.overview());
180 this.svg = container.one('.topology');
181
182 this.renderOnce();
183
184 return this;
185 },
186 /*
187 * Construct a persistent scene that is managed in update.
188 */
189 renderOnce: function() {
190 var self = this,
191 container = this.get('container'),
192 height = 600,
193 width = 640,
194 fill = d3.scale.category20();
195
196 this.service_scale = d3.scale.log().range([150, 200]);
197 this.service_scale_width = d3.scale.log().range([164, 200]),
198 this.service_scale_height = d3.scale.log().range([64, 100]);
199 this.xscale = d3.scale.linear()
200 .domain([-width / 2, width / 2])
201 .range([0, width]),
202 this.yscale = d3.scale.linear()
203 .domain([-height / 2, height / 2])
204 .range([height, 0]);
205
206 // Create a pan/zoom behavior manager.
207 var zoom = d3.behavior.zoom()
208 .x(this.xscale)
209 .y(this.yscale)
210 .scaleExtent([0.25, 2.0])
211 .on('zoom', function() {
212 // Keep the slider up to date with the scale on other sorts
213 // of zoom interactions
214 var s = self.slider;
215 s.set('value', Math.floor(d3.event.scale * 100));
216 self.rescale(vis, d3.event);
217 });
218 self.zoom = zoom;
219
220 // Set up the visualization with a pack layout.
221 var vis = d3.select(container.getDOMNode())
222 .select('.crosshatch-background')
223 .append('svg:svg')
224 .attr('pointer-events', 'all')
225 .attr('width', width)
226 .attr('height', height)
227 .append('svg:g')
228 .call(zoom)
229 // Disable zoom on double click.
230 .on('dblclick.zoom', null)
231 .append('g');
232
233 vis.append('svg:rect')
234 .attr('class', 'graph')
235 .attr('fill', 'rgba(255,255,255,0)');
236
237 this.vis = vis;
238 this.tree = d3.layout.pack()
239 .size([width, height])
240 .value(function(d) {
241 return Math.max(d.unit_count, 1);
242 })
243 .padding(300);
244
245 this.updateCanvas();
246 },
247
248 serviceClick: function(d, context) { 173 serviceClick: function(d, context) {
249 // Ignore if we clicked outside the actual service node. 174 // Ignore if we clicked outside the actual service node.
250 var container = context.get('container'), 175 var topo = context.get('component'),
251 mouse_coords = d3.mouse(container.one('svg').getDOMNode()); 176 container = context.get('container'),
252 if (!d.containsPoint(mouse_coords, context.zoom)) { 177 mouse_coords = d3.mouse(container.one('svg').getDOMNode());
178 if (!d.containsPoint(mouse_coords, topo.zoom)) {
253 return; 179 return;
254 } 180 }
255 // Get the current click action 181 // Get the current click action
256 var curr_click_action = context.get('currentServiceClickAction'); 182 var curr_click_action = context.get('currentServiceClickAction');
257 // Fire the action named in the following scheme: 183 // Fire the action named in the following scheme:
258 // service_click_action.<action> 184 // service_click_action.<action>
259 // with the service, the SVG node, and the view 185 // with the service, the SVG node, and the view
260 // as arguments. 186 // as arguments.
261 (context.service_click_actions[curr_click_action])( 187 (context.service_click_actions[curr_click_action])(
262 d, context, this); 188 d, context, this);
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 self.addRelationDrag 240 self.addRelationDrag
315 .call(self, self.get('addRelationStart_service'), node); 241 .call(self, self.get('addRelationStart_service'), node);
316 } 242 }
317 }, 243 },
318 244
319 /* 245 /*
320 * Sync view models with current db.models. 246 * Sync view models with current db.models.
321 */ 247 */
322 updateData: function() { 248 updateData: function() {
323 //model data 249 //model data
324 var vis = this.vis, 250 var topo = this.get('component'),
325 db = this.get('component').get('db'), 251 vis = topo.vis,
252 db = topo.get('db'),
326 relations = db.relations.toArray(), 253 relations = db.relations.toArray(),
327 services = db.services.map(views.toBoundingBox); 254 services = db.services.map(views.toBoundingBox);
328 255
329 this.services = services; 256 this.services = services;
330 257
331 Y.each(services, function(service) { 258 Y.each(services, function(service) {
332 // Update services with existing positions. 259 // Update services with existing positions.
333 var existing = this.service_boxes[service.id]; 260 var existing = this.service_boxes[service.id];
334 if (existing) { 261 if (existing) {
335 service.pos = existing.pos; 262 service.pos = existing.pos;
336 } 263 }
337 service.margins(service.subordinate ? 264 service.margins(service.subordinate ?
338 { 265 {
339 top: 0.05, 266 top: 0.05,
340 bottom: 0.1, 267 bottom: 0.1,
341 left: 0.084848, 268 left: 0.084848,
342 right: 0.084848} : 269 right: 0.084848} :
343 { 270 {
344 top: 0, 271 top: 0,
345 bottom: 0.1667, 272 bottom: 0.1667,
346 left: 0.086758, 273 left: 0.086758,
347 right: 0.086758}); 274 right: 0.086758});
348 this.service_boxes[service.id] = service; 275 this.service_boxes[service.id] = service;
349 }, this); 276 }, this);
350 this.rel_pairs = this.processRelations(relations); 277 this.rel_pairs = this.processRelations(relations);
351 278
352 // Nodes are mapped by modelId tuples. 279 // Nodes are mapped by modelId tuples.
353 this.node = vis.selectAll('.service') 280 this.node = vis.selectAll('.service')
354 .data(services, function(d) { 281 .data(services, function(d) {
355 return d.modelId();}); 282 return d.modelId();});
356 }, 283 },
357 284
358 /* 285 /*
359 * Attempt to reuse as much of the existing graph and view models 286 * Attempt to reuse as much of the existing graph and view models
360 * as possible to re-render the graph. 287 * as possible to re-render the graph.
361 */ 288 */
362 updateCanvas: function() { 289 update: function() {
363 var self = this, 290 var self = this,
364 tree = this.tree, 291 topo = this.get('component'),
365 vis = this.vis; 292 width = topo.get('width'),
293 height = topo.get('height');
294
295 if (!this.service_scale) {
296 this.service_scale = d3.scale.log().range([150, 200]);
297 this.service_scale_width = d3.scale.log().range([164, 200]),
298 this.service_scale_height = d3.scale.log().range([64, 100]);
299 }
300
301 if (!this.tree) {
302 this.tree = d3.layout.pack()
303 .size([width, height])
304 .value(function(d) {
305 return Math.max(d.unit_count, 1);
306 })
307 .padding(300);
308 }
366 309
367 //Process any changed data. 310 //Process any changed data.
368 this.updateData(); 311 this.updateData();
369 312
370 var drag = d3.behavior.drag() 313 var drag = d3.behavior.drag()
371 .on('dragstart', function(d) { 314 .on('dragstart', function(d) {
372 d.oldX = d.x; 315 d.oldX = d.x;
373 d.oldY = d.y; 316 d.oldY = d.y;
374 self.get('container').all('.environment-menu.active') 317 self.get('container').all('.environment-menu.active')
375 .removeClass('active'); 318 .removeClass('active');
(...skipping 13 matching lines...) Expand all
389 d3.select(this).attr('transform', function(d, i) { 332 d3.select(this).attr('transform', function(d, i) {
390 return d.translateStr(); 333 return d.translateStr();
391 }); 334 });
392 if (self.get('active_service') === d) { 335 if (self.get('active_service') === d) {
393 self.updateServiceMenuLocation(); 336 self.updateServiceMenuLocation();
394 } 337 }
395 338
396 // Clear any state while dragging. 339 // Clear any state while dragging.
397 self.get('container').all('.environment-menu.active') 340 self.get('container').all('.environment-menu.active')
398 .removeClass('active'); 341 .removeClass('active');
399 self.service_click_actions.toggleControlPanel(null, self);
400 self.cancelRelationBuild(); 342 self.cancelRelationBuild();
401 343
402 // Update relation lines for just this service. 344 // Update relation lines for just this service.
403 updateLinkEndpoints(d); 345 updateLinkEndpoints(d);
404 } 346 }
405 }) 347 })
406 .on('dragend', function(d, i) { 348 .on('dragend', function(d, i) {
407 if (self.buildingRelation) { 349 if (self.buildingRelation) {
408 self.addRelationDragEnd(); 350 self.addRelationDragEnd();
409 } 351 }
(...skipping 15 matching lines...) Expand all
425 .getConnectorPair(relation.target()), 367 .getConnectorPair(relation.target()),
426 s = connectors[0], 368 s = connectors[0],
427 t = connectors[1]; 369 t = connectors[1];
428 rel_group.select('line') 370 rel_group.select('line')
429 .attr('x1', s[0]) 371 .attr('x1', s[0])
430 .attr('y1', s[1]) 372 .attr('y1', s[1])
431 .attr('x2', t[0]) 373 .attr('x2', t[0])
432 .attr('y2', t[1]); 374 .attr('y2', t[1]);
433 rel_group.select('.rel-label') 375 rel_group.select('.rel-label')
434 .attr('transform', function(d) { 376 .attr('transform', function(d) {
435 // XXX: This has to happen on update, not enter
436 return 'translate(' + 377 return 'translate(' +
437 [Math.max(s[0], t[0]) - 378 [Math.max(s[0], t[0]) -
438 Math.abs((s[0] - t[0]) / 2), 379 Math.abs((s[0] - t[0]) / 2),
439 Math.max(s[1], t[1]) - 380 Math.max(s[1], t[1]) -
440 Math.abs((s[1] - t[1]) / 2)] + ')'; 381 Math.abs((s[1] - t[1]) / 2)] + ')';
441 }); 382 });
442 }); 383 });
443 } 384 }
444 385
445 // Generate a node for each service, draw it as a rect with 386 // Generate a node for each service, draw it as a rect with
446 // labels for service and charm. 387 // labels for service and charm.
447 var node = this.node; 388 var node = this.node;
448 389
449 // Rerun the pack layout. 390 // Rerun the pack layout.
450 // Pack doesn't honor existing positions and will 391 // Pack doesn't honor existing positions and will
451 // re-layout the entire graph. As a short term work 392 // re-layout the entire graph. As a short term work
452 // around we layout only new nodes. This has the side 393 // around we layout only new nodes. This has the side
453 // effect that node nodes can overlap and will 394 // effect that node nodes can overlap and will
454 // be fixed later. 395 // be fixed later.
455 var new_services = this.services.filter(function(boundingBox) { 396 var new_services = this.services.filter(function(boundingBox) {
456 return !Y.Lang.isNumber(boundingBox.x); 397 return !Y.Lang.isNumber(boundingBox.x);
457 }); 398 });
458 this.tree.nodes({children: new_services}); 399 this.tree.nodes({children: new_services});
459 400
460 // enter 401 // enter
461 node 402 node
462 .enter().append('g') 403 .enter().append('g')
463 .attr('class', function(d) { 404 .attr('class', function(d) {
464 return (d.subordinate ? 'subordinate ' : '') + 'service'; 405 return (d.subordinate ? 'subordinate ' : '') + 'service';
465 }) 406 })
466 .call(drag) 407 .call(drag)
467 .on('mousedown.addrel', function(d) { 408 .attr('transform', function(d) {
468 self.d3Events['.service']['mousedown.addrel'] 409 return d.translateStr();
469 .call(this, d, self, d3.event); 410 });
470 })
471 .on('mouseup.addrel', function(d) {
472 self.d3Events['.service']['mouseup.addrel']
473 .call(this, d, self, d3.event);
474 })
475 .attr('transform', function(d) {
476 return d.translateStr();});
477 411
478 // Update 412 // Update
479 this.drawService(node); 413 this.drawService(node);
480 414
481 // Exit 415 // Exit
482 node.exit() 416 node.exit()
483 .call(function(d) { 417 .remove();
484 // TODO: update the service_boxes
485 // removing the bound data
486 })
487 .remove();
488 418
489 function updateLinks() { 419 function updateLinks() {
490 // Enter. 420 // Enter.
491 var g = self.drawRelationGroup(), 421 var g = self.drawRelationGroup(),
492 link = g.selectAll('line.relation'); 422 link = g.selectAll('line.relation');
493 423
494 // Update (+ enter selection). 424 // Update (+ enter selection).
495 link.each(self.drawRelation); 425 link.each(self.drawRelation);
496 426
497 // Exit 427 // Exit
498 g.exit().remove(); 428 g.exit().remove();
499 } 429 }
500 430
501 // Draw or schedule redraw of links. 431 // Draw or schedule redraw of links.
502 updateLinks(); 432 updateLinks();
503 433
504 }, 434 },
505 435
506 /* 436 /*
507 * Draw a new relation link with label and controls. 437 * Draw a new relation link with label and controls.
508 */ 438 */
509 drawRelationGroup: function() { 439 drawRelationGroup: function() {
510 // Add a labelgroup. 440 // Add a labelgroup.
511 var self = this, 441 var self = this,
512 g = self.vis.selectAll('g.rel-group') 442 vis = this.get('component').vis,
513 .data(self.rel_pairs, function(r) { 443 g = vis.selectAll('g.rel-group')
514 return r.modelIds(); 444 .data(self.rel_pairs, function(r) {
515 }); 445 return r.modelIds();
446 });
516 447
517 var enter = g.enter(); 448 var enter = g.enter();
518 449
519 enter.insert('g', 'g.service') 450 enter.insert('g', 'g.service')
520 .attr('id', function(d) { 451 .attr('id', function(d) {
521 return d.id; 452 return d.id;
522 }) 453 })
523 .attr('class', function(d) { 454 .attr('class', function(d) {
524 // Mark the rel-group as a subordinate relation if need be. 455 // Mark the rel-group as a subordinate relation if need be.
525 return (d.scope === 'container' ? 456 return (d.scope === 'container' ?
526 'subordinate-rel-group ' : '') + 457 'subordinate-rel-group ' : '') +
527 'rel-group'; 458 'rel-group';
528 }) 459 })
529 .append('svg:line', 'g.service') 460 .append('svg:line', 'g.service')
530 .attr('class', function(d) { 461 .attr('class', function(d) {
531 // Style relation lines differently depending on status. 462 // Style relation lines differently depending on status.
532 return (d.pending ? 'pending-relation ' : '') + 463 return (d.pending ? 'pending-relation ' : '') +
533 (d.scope === 'container' ? 'subordinate-relation ' : '') + 464 (d.scope === 'container' ? 'subordinate-relation ' : '') +
534 'relation'; 465 'relation';
535 }); 466 });
536 467
537 g.selectAll('rel-label').remove(); 468 g.selectAll('.rel-label').remove();
538 g.selectAll('text').remove(); 469 g.selectAll('text').remove();
539 g.selectAll('rect').remove(); 470 g.selectAll('rect').remove();
540 var label = g.append('g') 471 var label = g.append('g')
541 .attr('class', 'rel-label') 472 .attr('class', 'rel-label')
542 .attr('transform', function(d) { 473 .attr('transform', function(d) {
543 // XXX: This has to happen on update, not enter 474 // XXX: This has to happen on update, not enter
544 var connectors = d.source().getConnectorPair(d.target()), 475 var connectors = d.source().getConnectorPair(d.target()),
545 s = connectors[0], 476 s = connectors[0],
546 t = connectors[1]; 477 t = connectors[1];
547 return 'translate(' + 478 return 'translate(' +
(...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after
844 775
845 /* 776 /*
846 * Utility function to get subordinate relations for a service. 777 * Utility function to get subordinate relations for a service.
847 */ 778 */
848 subordinateRelationsForService: function(service) { 779 subordinateRelationsForService: function(service) {
849 return this.rel_pairs.filter(function(p) { 780 return this.rel_pairs.filter(function(p) {
850 return p.modelIds().indexOf(service.modelId()) !== -1 && 781 return p.modelIds().indexOf(service.modelId()) !== -1 &&
851 p.scope === 'container'; 782 p.scope === 'container';
852 }); 783 });
853 }, 784 },
854 renderSlider: function() {
855 var self = this,
856 value = 100,
857 currentScale = this.get('scale');
858 // Build a slider to control zoom level
859 if (currentScale) {
860 value = currentScale * 100;
861 }
862 var slider = new Y.Slider({
863 min: 25,
864 max: 200,
865 value: value
866 });
867 slider.render('#slider-parent');
868 slider.after('valueChange', function(evt) {
869 // Don't fire a zoom if there's a zoom event already in progress;
870 // that will run rescale for us.
871 if (d3.event && d3.event.scale && d3.event.translate) {
872 return;
873 }
874 self._fire_zoom((evt.newVal - evt.prevVal) / 100);
875 });
876 self.slider = slider;
877 },
878
879 /* 785 /*
880 * Utility method to get a service object from the DB 786 * Utility method to get a service object from the DB
881 * given a BoundingBox. 787 * given a BoundingBox.
882 */ 788 */
883 serviceForBox: function(boundingBox) { 789 serviceForBox: function(boundingBox) {
884 var db = this.get('component').get('db'); 790 var db = this.get('component').get('db');
885 return db.services.getById(boundingBox.id); 791 return db.services.getById(boundingBox.id);
886 }, 792 },
887 793
888 794
(...skipping 23 matching lines...) Expand all
912 * Finish DOM-dependent rendering 818 * Finish DOM-dependent rendering
913 * 819 *
914 * Some portions of the visualization require information pulled 820 * Some portions of the visualization require information pulled
915 * from the DOM, such as the clientRects used for sizing relation 821 * from the DOM, such as the clientRects used for sizing relation
916 * labels and the viewport size used for sizing the whole graph. This 822 * labels and the viewport size used for sizing the whole graph. This
917 * is called after the view is attached to the DOM in order to 823 * is called after the view is attached to the DOM in order to
918 * perform all of that work. In the app, it's called as a callback 824 * perform all of that work. In the app, it's called as a callback
919 * in app.showView(), and in testing, it needs to be called manually, 825 * in app.showView(), and in testing, it needs to be called manually,
920 * if the test relies on any of this data. 826 * if the test relies on any of this data.
921 */ 827 */
922 postRender: function() { 828 renderedHandler: function() {
923 var container = this.get('container'); 829 var container = this.get('container');
924 830
831 this.update();
832
925 // Set the sizes from the viewport. 833 // Set the sizes from the viewport.
926 this.setSizesFromViewport(); 834 this.setSizesFromViewport();
927 835
928 // Ensure relation labels are sized properly. 836 // Ensure relation labels are sized properly.
929 container.all('.rel-label').each(function(label) { 837 container.all('.rel-label').each(function(label) {
930 var width = label.one('text').getClientRect().width + 10; 838 var width = label.one('text').getClientRect().width + 10;
931 label.one('rect').setAttribute('width', width) 839 label.one('rect').setAttribute('width', width)
932 .setAttribute('x', -width / 2); 840 .setAttribute('x', -width / 2);
933 }); 841 });
934 842
935 // Preserve zoom when the scene is updated.
936 var changed = false,
937 currentScale = this.get('scale'),
938 currentTranslate = this.get('translate');
939 if (currentTranslate && currentTranslate !== this.zoom.translate()) {
940 this.zoom.translate(currentTranslate);
941 changed = true;
942 }
943 if (currentScale && currentScale !== this.zoom.scale()) {
944 this.zoom.scale(currentScale);
945 changed = true;
946 }
947 if (changed) {
948 this._fire_zoom(0);
949 }
950
951 // Render the slider after the view is attached.
952 // Although there is a .syncUI() method on sliders, it does not
953 // seem to play well with the app framework: the slider will render
954 // the first time, but on navigation away and back, will not
955 // re-render within the view.
956 this.renderSlider();
957
958 // Chainable method. 843 // Chainable method.
959 return this; 844 return this;
960 }, 845 },
961 846
962 /* 847 /*
963 * Event handler for the add relation button. 848 * Event handler for the add relation button.
964 */ 849 */
965 addRelation: function(evt) { 850 addRelation: function(evt) {
966 var curr_action = this.get('currentServiceClickAction'), 851 var curr_action = this.get('currentServiceClickAction'),
967 container = this.get('container'); 852 container = this.get('container');
968 if (curr_action === 'show_service') { 853 if (curr_action === 'show_service') {
969 this.set('currentServiceClickAction', 'addRelationStart'); 854 this.set('currentServiceClickAction', 'addRelationStart');
970 } else if (curr_action === 'addRelationStart' || 855 } else if (curr_action === 'addRelationStart' ||
971 curr_action === 'ambiguousAddRelationCheck') { 856 curr_action === 'ambiguousAddRelationCheck') {
972 this.set('currentServiceClickAction', 'toggleControlPanel'); 857 this.set('currentServiceClickAction', 'toggleControlPanel');
973 } // Otherwise do nothing. 858 } // Otherwise do nothing.
974 }, 859 },
975 860
976 addRelationDragStart: function(d, context) { 861 addRelationDragStart: function(d, context) {
977 // Create a pending drag-line. 862 // Create a pending drag-line.
978 var dragline = this.vis.append('line') 863 var vis = this.get('component').vis,
979 .attr('class', 'relation pending-relation dragline dragging'), 864 dragline = vis.append('line')
980 self = this; 865 .attr('class',
866 'relation pending-relation dragline dragging'),
867 self = this;
981 868
982 // Start the line between the cursor and the nearest connector 869 // Start the line between the cursor and the nearest connector
983 // point on the service. 870 // point on the service.
984 var mouse = d3.mouse(Y.one('svg').getDOMNode()); 871 var mouse = d3.mouse(Y.one('.topology svg').getDOMNode());
985 self.cursorBox = views.BoundingBox(); 872 self.cursorBox = new views.BoundingBox();
986 self.cursorBox.pos = {x: mouse[0], y: mouse[1], w: 0, h: 0}; 873 self.cursorBox.pos = {x: mouse[0], y: mouse[1], w: 0, h: 0};
987 var point = self.cursorBox.getConnectorPair(d); 874 var point = self.cursorBox.getConnectorPair(d);
988 dragline.attr('x1', point[0][0]) 875 dragline.attr('x1', point[0][0])
989 .attr('y1', point[0][1]) 876 .attr('y1', point[0][1])
990 .attr('x2', point[1][0]) 877 .attr('x2', point[1][0])
991 .attr('y2', point[1][1]); 878 .attr('y2', point[1][1]);
992 self.dragline = dragline; 879 self.dragline = dragline;
993 880
994 // Start the add-relation process. 881 // Start the add-relation process.
995 context.service_click_actions 882 context.service_click_actions
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
1084 Y.bind(function(ev) { 971 Y.bind(function(ev) {
1085 ev.preventDefault(); 972 ev.preventDefault();
1086 var confirmButton = ev.target; 973 var confirmButton = ev.target;
1087 confirmButton.set('disabled', true); 974 confirmButton.set('disabled', true);
1088 view.removeRelation(d, context, view, confirmButton); 975 view.removeRelation(d, context, view, confirmButton);
1089 }, 976 },
1090 this))); 977 this)));
1091 }, 978 },
1092 979
1093 cancelRelationBuild: function() { 980 cancelRelationBuild: function() {
981 var vis = this.get('component').vis;
1094 if (this.dragline) { 982 if (this.dragline) {
1095 // Get rid of our drag line 983 // Get rid of our drag line
1096 this.dragline.remove(); 984 this.dragline.remove();
1097 this.dragline = null; 985 this.dragline = null;
1098 } 986 }
1099 this.clickAddRelation = null; 987 this.clickAddRelation = null;
1100 this.set('currentServiceClickAction', 'toggleControlPanel'); 988 this.set('currentServiceClickAction', 'toggleControlPanel');
1101 this.buildingRelation = false; 989 this.buildingRelation = false;
1102 this.show(this.vis.selectAll('.service')) 990 this.show(vis.selectAll('.service'))
1103 .classed('selectable-service', false); 991 .classed('selectable-service', false);
1104 }, 992 },
1105 993
1106 /** 994 /**
1107 * The user clicked on the environment view background. 995 * The user clicked on the environment view background.
1108 * 996 *
1109 * If we are in the middle of adding a relation, cancel the relation 997 * If we are in the middle of adding a relation, cancel the relation
1110 * adding. 998 * adding.
1111 * 999 *
1112 * @method backgroundClicked 1000 * @method backgroundClicked
1113 * @return {undefined} Side effects only. 1001 * @return {undefined} Side effects only.
1114 */ 1002 */
1115 backgroundClicked: function() { 1003 backgroundClicked: function() {
1116 if (this.clickAddRelation) { 1004 if (this.clickAddRelation) {
1117 this.cancelRelationBuild(); 1005 this.cancelRelationBuild();
1118 } 1006 }
1119 }, 1007 },
1120 1008
1121 /** 1009 /**
1122 * An "add relation" action has been initiated by the user. 1010 * An "add relation" action has been initiated by the user.
1123 * 1011 *
1124 * @method startRelation 1012 * @method startRelation
1125 * @param {object} service The service that is the source of the 1013 * @param {object} service The service that is the source of the
1126 * relation. 1014 * relation.
1127 * @return {undefined} Side effects only. 1015 * @return {undefined} Side effects only.
1128 */ 1016 */
1129 startRelation: function(service) { 1017 startRelation: function(service) {
1130 // Set flags on the view that indicate we are building a relation. 1018 // Set flags on the view that indicate we are building a relation.
1019 var vis = this.get('component').vis;
1020
1131 this.buildingRelation = true; 1021 this.buildingRelation = true;
1132 this.clickAddRelation = true; 1022 this.clickAddRelation = true;
1133 1023
1134 this.show(this.vis.selectAll('.service')); 1024 this.show(vis.selectAll('.service'));
1135 1025
1136 var db = this.get('component').get('db'), 1026 var db = this.get('component').get('db'),
1137 getServiceEndpoints = this.get('component') 1027 getServiceEndpoints = this.get('component')
1138 .get('getServiceEndpoints'), 1028 .get('getServiceEndpoints'),
1139 endpoints = models.getEndpoints( 1029 endpoints = models.getEndpoints(
1140 service, getServiceEndpoints(), db), 1030 service, getServiceEndpoints(), db),
1141 // Transform endpoints into a list of relatable services (to the 1031 // Transform endpoints into a list of relatable services (to the
1142 // service). 1032 // service).
1143 possible_relations = Y.Array.map( 1033 possible_relations = Y.Array.map(
1144 Y.Array.flatten(Y.Object.values(endpoints)), 1034 Y.Array.flatten(Y.Object.values(endpoints)),
1145 function(ep) {return ep.service;}), 1035 function(ep) {return ep.service;}),
1146 invalidRelationTargets = {}; 1036 invalidRelationTargets = {};
1147 1037
1148 // Iterate services and invert the possibles list. 1038 // Iterate services and invert the possibles list.
1149 db.services.each(function(s) { 1039 db.services.each(function(s) {
1150 if (Y.Array.indexOf(possible_relations, 1040 if (Y.Array.indexOf(possible_relations,
1151 s.get('id')) === -1) { 1041 s.get('id')) === -1) {
1152 invalidRelationTargets[s.get('id')] = true; 1042 invalidRelationTargets[s.get('id')] = true;
1153 } 1043 }
1154 }); 1044 });
1155 1045
1156 // Fade elements to which we can't relate. 1046 // Fade elements to which we can't relate.
1157 // Rather than two loops this marks 1047 // Rather than two loops this marks
1158 // all services as selectable and then 1048 // all services as selectable and then
1159 // removes the invalid ones. 1049 // removes the invalid ones.
1160 this.fade(this.vis.selectAll('.service') 1050 this.fade(vis.selectAll('.service')
1161 .classed('selectable-service', true) 1051 .classed('selectable-service', true)
1162 .filter(function(d) { 1052 .filter(function(d) {
1163 return (d.id in invalidRelationTargets && 1053 return (d.id in invalidRelationTargets &&
1164 d.id !== service.id); 1054 d.id !== service.id);
1165 })) 1055 }))
1166 .classed('selectable-service', false); 1056 .classed('selectable-service', false);
1167 1057
1168 // Store possible endpoints. 1058 // Store possible endpoints.
1169 this.set('addRelationStart_possibleEndpoints', endpoints); 1059 this.set('addRelationStart_possibleEndpoints', endpoints);
1170 // Set click action. 1060 // Set click action.
1171 this.set('currentServiceClickAction', 'ambiguousAddRelationCheck'); 1061 this.set('currentServiceClickAction', 'ambiguousAddRelationCheck');
1172 }, 1062 },
1173 1063
1174
1175 /*
1176 * Zoom in event handler.
1177 */
1178 zoom_out: function(data, context) {
1179 var slider = context.slider,
1180 val = slider.get('value');
1181 slider.set('value', val - 25);
1182 },
1183
1184 /*
1185 * Zoom out event handler.
1186 */
1187 zoom_in: function(data, context) {
1188 var slider = context.slider,
1189 val = slider.get('value');
1190 slider.set('value', val + 25);
1191 },
1192
1193 /*
1194 * Wraper around the actual rescale method for zoom buttons.
1195 */
1196 _fire_zoom: function(delta) {
1197 var vis = this.vis,
1198 zoom = this.zoom,
1199 evt = {};
1200
1201 // Build a temporary event that rescale can use of a similar
1202 // construction to d3.event.
1203 evt.translate = zoom.translate();
1204 evt.scale = zoom.scale() + delta;
1205
1206 // Update the scale in our zoom behavior manager to maintain state.
1207 zoom.scale(evt.scale);
1208
1209 // Update the translate so that we scale from the center
1210 // instead of the origin.
1211 var rect = vis.select('rect');
1212 evt.translate[0] -= parseInt(rect.attr('width'), 10) / 2 * delta;
1213 evt.translate[1] -= parseInt(rect.attr('height'), 10) / 2 * delta;
1214 zoom.translate(evt.translate);
1215
1216 this.rescale(vis, evt);
1217 },
1218
1219 /*
1220 * Rescale the visualization on a zoom/pan event.
1221 */
1222 rescale: function(vis, evt) {
1223 // Make sure we don't scale outside of our bounds.
1224 // This check is needed because we're messing with d3's zoom
1225 // behavior outside of mouse events (e.g.: with the slider),
1226 // and can't trust that zoomExtent will play well.
1227 var new_scale = Math.floor(evt.scale * 100);
1228 if (new_scale < 25 || new_scale > 200) {
1229 evt.scale = this.get('scale');
1230 }
1231 // Store the current value of scale so that it can be restored later.
1232 this.set('scale', evt.scale);
1233 // Store the current value of translate as well, by copying the event
1234 // array in order to avoid reference sharing.
1235 this.set('translate', evt.translate.slice(0));
1236 vis.attr('transform', 'translate(' + evt.translate + ')' +
1237 ' scale(' + evt.scale + ')');
1238 this.updateServiceMenuLocation();
1239 },
1240
1241 /* 1064 /*
1242 * Event handler to show the graph-list picker 1065 * Event handler to show the graph-list picker
1243 */ 1066 */
1244 showGraphListPicker: function(evt) { 1067 showGraphListPicker: function(evt) {
1245 var container = this.get('container'), 1068 var container = this.get('container'),
1246 picker = container.one('.graph-list-picker'); 1069 picker = container.one('.graph-list-picker');
1247 picker.addClass('inactive'); 1070 picker.addClass('inactive');
1248 picker.one('.picker-expanded').addClass('active'); 1071 picker.one('.picker-expanded').addClass('active');
1249 }, 1072 },
1250 1073
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
1286 }, 1109 },
1287 1110
1288 /* 1111 /*
1289 * Set the visualization size based on the viewport 1112 * Set the visualization size based on the viewport
1290 */ 1113 */
1291 setSizesFromViewport: function() { 1114 setSizesFromViewport: function() {
1292 // This event allows other page components that may unintentionally 1115 // This event allows other page components that may unintentionally
1293 // affect the page size, such as the charm panel, to get out of the 1116 // affect the page size, such as the charm panel, to get out of the
1294 // way before we compute sizes. Note the 1117 // way before we compute sizes. Note the
1295 // "afterPageSizeRecalculation" event at the end of this function. 1118 // "afterPageSizeRecalculation" event at the end of this function.
1296 Y.fire('beforePageSizeRecalculation');
1297 // start with some reasonable defaults 1119 // start with some reasonable defaults
1298 var vis = this.vis, 1120 console.log('setSizesFromViewPort', this, arguments);
1299 container = this.get('container'), 1121 var topo = this.get('component'),
1300 xscale = this.xscale, 1122 container = this.get('container'),
1301 yscale = this.yscale, 1123 vis = topo.vis,
1302 svg = container.one('svg'), 1124 xscale = topo.xScale,
1303 canvas = container.one('.crosshatch-background'); 1125 yscale = topo.yScale,
1126 svg = container.one('svg'),
1127 canvas = container.one('.topology-canvas');
1128
1129 topo.fire('beforePageSizeRecalculation');
1304 // Get the canvas out of the way so we can calculate the size 1130 // Get the canvas out of the way so we can calculate the size
1305 // correctly (the canvas contains the svg). We want it to be the 1131 // correctly (the canvas contains the svg). We want it to be the
1306 // smallest size we accept--no smaller or bigger--or else the 1132 // smallest size we accept--no smaller or bigger--or else the
1307 // presence or absence of scrollbars may affect our calculations 1133 // presence or absence of scrollbars may affect our calculations
1308 // incorrectly. 1134 // incorrectly.
1309 canvas.setStyles({height: 600, width: 800}); 1135 canvas.setStyles({height: 600, width: 800});
1310 var dimensions = utils.getEffectiveViewportSize(true, 800, 600); 1136 var dimensions = utils.getEffectiveViewportSize(true, 800, 600);
1311 // Set the svg sizes. 1137 // Set the svg sizes.
1312 svg.setAttribute('width', dimensions.width) 1138 svg.setAttribute('width', dimensions.width)
1313 .setAttribute('height', dimensions.height); 1139 .setAttribute('height', dimensions.height);
1314 1140
1315 // Set the internal rect's size. 1141 // Set the internal rect's size.
1316 svg.one('rect') 1142 svg.one('rect')
1317 .setAttribute('width', dimensions.width) 1143 .setAttribute('width', dimensions.width)
1318 .setAttribute('height', dimensions.height); 1144 .setAttribute('height', dimensions.height);
1319 canvas 1145 canvas
1320 .setStyle('height', dimensions.height) 1146 .setStyle('height', dimensions.height)
1321 .setStyle('width', dimensions.width); 1147 .setStyle('width', dimensions.width);
1322 1148
1323 // Reset the scale parameters 1149 // Reset the scale parameters
1324 this.xscale.domain([-dimensions.width / 2, dimensions.width / 2]) 1150 topo.xScale.domain([-dimensions.width / 2, dimensions.width / 2])
1325 .range([0, dimensions.width]); 1151 .range([0, dimensions.width]);
1326 this.yscale.domain([-dimensions.height / 2, dimensions.height / 2]) 1152 topo.yScale.domain([-dimensions.height / 2, dimensions.height / 2])
1327 .range([dimensions.height, 0]); 1153 .range([dimensions.height, 0]);
1328 1154
1329 this.width = dimensions.width; 1155 topo.set('size', [dimensions.width, dimensions.height]);
1330 this.height = dimensions.height; 1156 topo.fire('afterPageSizeRecalculation');
1331 Y.fire('afterPageSizeRecalculation');
1332 }, 1157 },
1333 1158
1334 /* 1159 /*
1335 * Update the location of the active service panel 1160 * Update the location of the active service panel
1336 */ 1161 */
1337 updateServiceMenuLocation: function() { 1162 updateServiceMenuLocation: function() {
1338 var container = this.get('container'), 1163 var topo = this.get('component'),
1339 cp = container.one('.environment-menu.active'), 1164 container = this.get('container'),
1340 service = this.get('active_service'), 1165 cp = container.one('.environment-menu.active'),
1341 tr = this.zoom.translate(), 1166 service = this.get('active_service'),
1342 z = this.zoom.scale(); 1167 tr = topo.get('translate'),
1168 z = topo.get('scale');
1169
1343 if (service && cp) { 1170 if (service && cp) {
1344 var cp_width = cp.getClientRect().width, 1171 var cp_width = cp.getClientRect().width,
1345 menu_left = service.x * z + service.w * z / 2 < 1172 menu_left = service.x * z + service.w * z / 2 <
1346 this.width * z / 2, 1173 this.width * z / 2,
1347 service_center = service.getRelativeCenter(); 1174 service_center = service.getRelativeCenter();
1348 if (menu_left) { 1175 if (menu_left) {
1349 cp.removeClass('left') 1176 cp.removeClass('left')
1350 .addClass('right'); 1177 .addClass('right');
1351 } else { 1178 } else {
1352 cp.removeClass('right') 1179 cp.removeClass('right')
(...skipping 15 matching lines...) Expand all
1368 }, 1195 },
1369 1196
1370 serviceMouseEnter: function(d, context) { 1197 serviceMouseEnter: function(d, context) {
1371 var rect = Y.one(this); 1198 var rect = Y.one(this);
1372 // Do not fire if this service isn't selectable. 1199 // Do not fire if this service isn't selectable.
1373 if (!utils.hasSVGClass(rect, 'selectable-service')) { 1200 if (!utils.hasSVGClass(rect, 'selectable-service')) {
1374 return; 1201 return;
1375 } 1202 }
1376 1203
1377 // Do not fire unless we're within the service box. 1204 // Do not fire unless we're within the service box.
1378 var container = context.get('container'), 1205 var topo = context.get('component'),
1206 container = context.get('container'),
1379 mouse_coords = d3.mouse(container.one('svg').getDOMNode()); 1207 mouse_coords = d3.mouse(container.one('svg').getDOMNode());
1380 if (!d.containsPoint(mouse_coords, context.zoom)) { 1208 if (!d.containsPoint(mouse_coords, topo.zoom)) {
1381 return; 1209 return;
1382 } 1210 }
1383 1211
1384 // Do not fire if we're on the same service. 1212 // Do not fire if we're on the same service.
1385 if (d === context.get('addRelationStart_service')) { 1213 if (d === context.get('addRelationStart_service')) {
1386 return; 1214 return;
1387 } 1215 }
1388 1216
1389 context.set('potential_drop_point_service', d); 1217 context.set('potential_drop_point_service', d);
1390 context.set('potential_drop_point_rect', rect); 1218 context.set('potential_drop_point_rect', rect);
(...skipping 15 matching lines...) Expand all
1406 } 1234 }
1407 }, 1235 },
1408 1236
1409 serviceMouseLeave: function(d, self) { 1237 serviceMouseLeave: function(d, self) {
1410 // Do not fire if we aren't looking for a relation endpoint. 1238 // Do not fire if we aren't looking for a relation endpoint.
1411 if (!self.get('potential_drop_point_rect')) { 1239 if (!self.get('potential_drop_point_rect')) {
1412 return; 1240 return;
1413 } 1241 }
1414 1242
1415 // Do not fire if we're within the service box. 1243 // Do not fire if we're within the service box.
1416 var container = self.get('container'), 1244 var topo = this.get('component'),
1245 container = self.get('container'),
1417 mouse_coords = d3.mouse(container.one('svg').getDOMNode()); 1246 mouse_coords = d3.mouse(container.one('svg').getDOMNode());
1418 if (d.containsPoint(mouse_coords, self.zoom)) { 1247 if (d.containsPoint(mouse_coords, topo.zoom)) {
1419 return; 1248 return;
1420 } 1249 }
1421 var rect = Y.one(this).one('.service-border'); 1250 var rect = Y.one(this).one('.service-border');
1422 self.set('potential_drop_point_service', null); 1251 self.set('potential_drop_point_service', null);
1423 self.set('potential_drop_point_rect', null); 1252 self.set('potential_drop_point_rect', null);
1424 utils.removeSVGClass(rect, 'hover'); 1253 utils.removeSVGClass(rect, 'hover');
1425 1254
1426 if (self.dragline) { 1255 if (self.dragline) {
1427 self.dragline.attr('class', 1256 self.dragline.attr('class',
1428 'relation pending-relation dragline dragging'); 1257 'relation pending-relation dragline dragging');
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
1479 view.set('active_context', context); 1308 view.set('active_context', context);
1480 cp.addClass('active'); 1309 cp.addClass('active');
1481 view.updateServiceMenuLocation(); 1310 view.updateServiceMenuLocation();
1482 } 1311 }
1483 }, 1312 },
1484 1313
1485 /* 1314 /*
1486 * View a service 1315 * View a service
1487 */ 1316 */
1488 show_service: function(m, context) { 1317 show_service: function(m, context) {
1489 context.get('component') 1318 var topo = context.get('component');
1490 .fire('navigateTo', {url: '/service/' + m.get('id') + '/'}); 1319 topo.detachContainer();
1320 topo.fire('navigateTo', {url: '/service/' + m.get('id') + '/'});
1491 }, 1321 },
1492 1322
1493 /* 1323 /*
1494 * Show a dialog before destroying a service 1324 * Show a dialog before destroying a service
1495 */ 1325 */
1496 destroyServiceConfirm: function(m, view) { 1326 destroyServiceConfirm: function(m, view) {
1497 // Set service in view. 1327 // Set service in view.
1498 view.set('destroy_service', m); 1328 view.set('destroy_service', m);
1499 1329
1500 // Show dialog. 1330 // Show dialog.
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
1560 view.startRelation(service); 1390 view.startRelation(service);
1561 // Store start service in attrs. 1391 // Store start service in attrs.
1562 view.set('addRelationStart_service', m); 1392 view.set('addRelationStart_service', m);
1563 }, 1393 },
1564 1394
1565 /* 1395 /*
1566 * Test if the pending relation is ambiguous. Display a menu if so, 1396 * Test if the pending relation is ambiguous. Display a menu if so,
1567 * create the relation if not. 1397 * create the relation if not.
1568 */ 1398 */
1569 ambiguousAddRelationCheck: function(m, view, context) { 1399 ambiguousAddRelationCheck: function(m, view, context) {
1570 var endpoints = view 1400 var endpoints = view.get(
1571 .get('addRelationStart_possibleEndpoints')[m.id], 1401 'addRelationStart_possibleEndpoints')[m.id],
1572 container = view.get('container'); 1402 container = view.get('container'),
1403 topo = view.get('component');
1573 1404
1574 if (endpoints.length === 1) { 1405 if (endpoints && endpoints.length === 1) {
1575 // Create a relation with the only available endpoint. 1406 // Create a relation with the only available endpoint.
1576 var ep = endpoints[0], 1407 var ep = endpoints[0],
1577 endpoints_item = [ 1408 endpoints_item = [
1578 [ep[0].service, { 1409 [ep[0].service, {
1579 name: ep[0].name, 1410 name: ep[0].name,
1580 role: 'server' }], 1411 role: 'server' }],
1581 [ep[1].service, { 1412 [ep[1].service, {
1582 name: ep[1].name, 1413 name: ep[1].name,
1583 role: 'client' }]]; 1414 role: 'client' }]];
1584 view.service_click_actions 1415 view.service_click_actions
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1622 .addRelationEnd(endpoints_item, view, context); 1453 .addRelationEnd(endpoints_item, view, context);
1623 }); 1454 });
1624 1455
1625 // Add a cancel item. 1456 // Add a cancel item.
1626 menu.one('.cancel').on('click', function(evt) { 1457 menu.one('.cancel').on('click', function(evt) {
1627 menu.removeClass('active'); 1458 menu.removeClass('active');
1628 view.cancelRelationBuild(); 1459 view.cancelRelationBuild();
1629 }); 1460 });
1630 1461
1631 // Display the menu at the service endpoint. 1462 // Display the menu at the service endpoint.
1632 var tr = view.zoom.translate(), 1463 var tr = topo.zoom.translate(),
1633 z = view.zoom.scale(); 1464 z = topo.zoom.scale();
1634 menu.setStyle('top', m.y * z + tr[1]); 1465 menu.setStyle('top', m.y * z + tr[1]);
1635 menu.setStyle('left', m.x * z + m.w * z + tr[0]); 1466 menu.setStyle('left', m.x * z + m.w * z + tr[0]);
1636 menu.addClass('active'); 1467 menu.addClass('active');
1637 view.set('active_service', m); 1468 view.set('active_service', m);
1638 view.set('active_context', context); 1469 view.set('active_context', context);
1639 view.updateServiceMenuLocation(); 1470 view.updateServiceMenuLocation();
1640 }, 1471 },
1641 1472
1642 /* 1473 /*
1643 * Fired when clicking the second service is clicked in the 1474 * Fired when clicking the second service is clicked in the
1644 * add relation flow. 1475 * add relation flow.
1645 * 1476 *
1646 * :param endpoints: array of two endpoints, each in the form 1477 * :param endpoints: array of two endpoints, each in the form
1647 * ['service name', { 1478 * ['service name', {
1648 * name: 'endpoint type', 1479 * name: 'endpoint type',
1649 * role: 'client or server' 1480 * role: 'client or server'
1650 * }] 1481 * }]
1651 */ 1482 */
1652 addRelationEnd: function(endpoints, view, context) { 1483 addRelationEnd: function(endpoints, view, context) {
1653 // Redisplay all services 1484 // Redisplay all services
1654 view.cancelRelationBuild(); 1485 view.cancelRelationBuild();
1655 1486
1656 // Get the vis, and links, build the new relation. 1487 // Get the vis, and links, build the new relation.
1657 var vis = view.vis, 1488 var vis = view.get('component').vis,
1658 env = view.get('component').get('env'), 1489 env = view.get('component').get('env'),
1659 db = view.get('component').get('db'), 1490 db = view.get('component').get('db'),
1660 source = view.get('addRelationStart_service'), 1491 source = view.get('addRelationStart_service'),
1661 relation_id = 'pending:' + endpoints[0][0] + endpoints[1][0]; 1492 relation_id = 'pending:' + endpoints[0][0] + endpoints[1][0];
1662 1493
1663 if (endpoints[0][0] === endpoints[1][0]) { 1494 if (endpoints[0][0] === endpoints[1][0]) {
1664 view.set('currentServiceClickAction', 'toggleControlPanel'); 1495 view.set('currentServiceClickAction', 'toggleControlPanel');
1665 return; 1496 return;
1666 } 1497 }
1667 1498
1668 // Create a pending relation in the database between the 1499 // Create a pending relation in the database between the
1669 // two services. 1500 // two services.
1670 db.relations.create({ 1501 db.relations.create({
1671 relation_id: relation_id, 1502 relation_id: relation_id,
1672 display_name: 'pending', 1503 display_name: 'pending',
1673 endpoints: endpoints, 1504 endpoints: endpoints,
1674 pending: true 1505 pending: true
1675 }); 1506 });
1676 1507
1677 // Firing the update event on the db will properly redraw the 1508 // Firing the update event on the db will properly redraw the
1678 // graph and reattach events. 1509 // graph and reattach events.
1679 db.fire('update'); 1510 //db.fire('update');
1511 view.get('component').bindAllD3Events();
1512 view.update();
1680 1513
1681 // Fire event to add relation in juju. 1514 // Fire event to add relation in juju.
1682 // This needs to specify interface in the future. 1515 // This needs to specify interface in the future.
1683 env.add_relation( 1516 env.add_relation(
1684 endpoints[0][0] + ':' + endpoints[0][1].name, 1517 endpoints[0][0] + ':' + endpoints[0][1].name,
1685 endpoints[1][0] + ':' + endpoints[1][1].name, 1518 endpoints[1][0] + ':' + endpoints[1][1].name,
1686 Y.bind(this._addRelationCallback, this, view, relation_id) 1519 Y.bind(this._addRelationCallback, this, view, relation_id)
1687 ); 1520 );
1688 view.set('currentServiceClickAction', 'toggleControlPanel'); 1521 view.set('currentServiceClickAction', 'toggleControlPanel');
1689 }, 1522 },
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
1732 requires: [ 1565 requires: [
1733 'd3', 1566 'd3',
1734 'd3-components', 1567 'd3-components',
1735 'juju-templates', 1568 'juju-templates',
1736 'node', 1569 'node',
1737 'event', 1570 'event',
1738 'juju-models', 1571 'juju-models',
1739 'juju-env' 1572 'juju-env'
1740 ] 1573 ]
1741 }); 1574 });
OLDNEW
« no previous file with comments | « app/views/environment.js ('k') | app/views/topology/panzoom.js » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b