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

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

Issue 6999047: Relations Topology Module
Patch Set: Relations Topology Module Created 12 years, 3 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
OLDNEW
1 'use strict'; 1 'use strict';
2 2
3 YUI.add('juju-topology-relation', function(Y) { 3 YUI.add('juju-topology-relation', function(Y) {
4 var views = Y.namespace('juju.views'), 4 var views = Y.namespace('juju.views'),
5 models = Y.namespace('juju.models'), 5 models = Y.namespace('juju.models'),
6 d3ns = Y.namespace('d3'); 6 utils = Y.namespace('juju.views.utils'),
7 d3ns = Y.namespace('d3'),
8 Templates = views.Templates;
bcsaller 2013/01/02 21:53:07 Hard to identify what changed in porting things to
7 9
8 /** 10 /**
9 * @module topology-relations 11 * @module topology-relations
10 * @class RelationModule 12 * @class RelationModule
11 * @namespace views 13 * @namespace views
12 **/ 14 **/
13 var RelationModule = Y.Base.create('RelationModule', d3ns.Module, [], { 15 var RelationModule = Y.Base.create('RelationModule', d3ns.Module, [], {
16
17 events: {
18 scene: {
19 '.sub-rel-block': {
20 mouseenter: 'subRelBlockMouseEnter',
21 mouseleave: 'subRelBlockMouseLeave',
22 click: 'subRelBlockClick'
23 },
24 '.rel-label': {
25 click: 'relationClick'
26 },
27 '.dragline': {
28 /** The user clicked while the dragline was active. */
29 click: {callback: 'draglineClicked'}
30 },
31 '.add-relation': {
32 /** The user clicked on the "Build Relation" menu item. */
33 click: {callback: 'addRelButtonClicked'}
34 }
35 },
36 yui: {
37 rendered: {callback: 'renderedHandler'},
38 clearState: {callback: 'cancelRelationBuild'},
39 serviceMoved: {callback: 'updateLinkEndpoints'},
40 servicesRendered: {callback: 'updateLinks'},
41 snapToService: {callback: 'snapToService'},
42 snapOutOfService: {callback: 'snapOutOfService'},
43 cancelRelationBuild: {callback: 'cancelRelationBuild'},
44 addRelationDragStart: {callback: 'addRelationDragStart'},
45 addRelationDrag: {callback: 'addRelationDrag'},
46 addRelationDragEnd: {callback: 'addRelationDragEnd'}
47 }
48 },
49
14 initializer: function(options) { 50 initializer: function(options) {
15 RelationModule.superclass.constructor.apply(this, arguments); 51 RelationModule.superclass.constructor.apply(this, arguments);
52 this.relPairs = [];
16 }, 53 },
17 54
18 render: function() { 55 render: function() {
19 RelationModule.superclass.render.apply(this, arguments); 56 RelationModule.superclass.render.apply(this, arguments);
20 return this; 57 return this;
21 }, 58 },
22 59
23 update: function() { 60 update: function() {
24 RelationModule.superclass.update.apply(this, arguments); 61 RelationModule.superclass.update.apply(this, arguments);
62
63 var topo = this.get('component');
64 var db = topo.get('db');
65 var relations = db.relations.toArray();
66 this.relPairs = this.processRelations(relations);
67 topo.relPairs = this.relPairs;
68 this.updateLinks();
69 this.updateSubordinateRelationsCount();
70
25 return this; 71 return this;
72 },
73
74 renderedHandler: function() {
75 this.update();
76 },
77
78 processRelation: function(r) {
79 var self = this;
80 var topo = self.get('component');
81 var endpoints = r.get('endpoints');
82 var rel_services = [];
83
84 Y.each(endpoints, function(ep) {
85 rel_services.push([ep[1].name, topo.service_boxes[ep[0]]]);
86 });
87 return rel_services;
88 },
89
90 processRelations: function(rels) {
91 var self = this;
92 var pairs = [];
93 Y.each(rels, function(rel) {
94 var pair = self.processRelation(rel);
95
96 // skip peer for now
97 if (pair.length === 2) {
98 var bpair = views.BoxPair()
99 .model(rel)
100 .source(pair[0][1])
101 .target(pair[1][1]);
102 // Copy the relation type to the box.
103 if (bpair.display_name === undefined) {
104 bpair.display_name = pair[0][0];
105 }
106 pairs.push(bpair);
107 }
108 });
109 return pairs;
110 },
111
112 updateLinks: function() {
113 // Enter.
114 var g = this.drawRelationGroup();
115 var link = g.selectAll('line.relation');
116
117 // Update (+ enter selection).
118 link.each(this.drawRelation);
119
120 // Exit
121 g.exit().remove();
122 },
123
124 /**
125 * Update relation line endpoints for a given service.
126 *
127 * @method updateLinkEndpoints
128 * @param {Object} service The service module that has been moved.
129 */
130 updateLinkEndpoints: function(evt) {
131 var self = this;
132 var service = evt.service;
133 Y.each(Y.Array.filter(self.relPairs, function(relation) {
134 return relation.source() === service ||
135 relation.target() === service;
136 }), function(relation) {
137 var rel_group = d3.select('#' + relation.id);
138 var connectors = relation.source()
139 .getConnectorPair(relation.target());
140 var s = connectors[0];
141 var t = connectors[1];
142 rel_group.select('line')
143 .attr('x1', s[0])
144 .attr('y1', s[1])
145 .attr('x2', t[0])
146 .attr('y2', t[1]);
147 rel_group.select('.rel-label')
148 .attr('transform', function(d) {
149 return 'translate(' +
150 [Math.max(s[0], t[0]) -
151 Math.abs((s[0] - t[0]) / 2),
152 Math.max(s[1], t[1]) -
153 Math.abs((s[1] - t[1]) / 2)] + ')';
154 });
155 });
156 },
157
158 drawRelationGroup: function() {
159 // Add a labelgroup.
160 var self = this;
161 var vis = this.get('component').vis;
162 var g = vis.selectAll('g.rel-group')
163 .data(self.relPairs, function(r) {
164 return r.modelIds();
165 });
166
167 var enter = g.enter();
168
169 enter.insert('g', 'g.service')
170 .attr('id', function(d) {
171 return d.id;
172 })
173 .attr('class', function(d) {
174 // Mark the rel-group as a subordinate relation if need be.
175 return (d.scope === 'container' ?
176 'subordinate-rel-group ' : '') +
177 'rel-group';
178 })
179 .append('svg:line', 'g.service')
180 .attr('class', function(d) {
181 // Style relation lines differently depending on status.
182 return (d.pending ? 'pending-relation ' : '') +
183 (d.scope === 'container' ? 'subordinate-relation ' : '') +
184 'relation';
185 });
186
187 g.selectAll('.rel-label').remove();
188 g.selectAll('text').remove();
189 g.selectAll('rect').remove();
190 var label = g.append('g')
191 .attr('class', 'rel-label')
192 .attr('transform', function(d) {
193 // XXX: This has to happen on update, not enter
194 var connectors = d.source().getConnectorPair(d.target());
195 var s = connectors[0];
196 var t = connectors[1];
197 return 'translate(' +
198 [Math.max(s[0], t[0]) -
199 Math.abs((s[0] - t[0]) / 2),
200 Math.max(s[1], t[1]) -
201 Math.abs((s[1] - t[1]) / 2)] + ')';
202 });
203 label.append('text')
204 .append('tspan')
205 .text(function(d) {return d.display_name; });
206 label.insert('rect', 'text')
207 .attr('width', function(d) {
208 return d.display_name.length * 10 + 10;
209 })
210 .attr('height', 20)
211 .attr('x', function() {
212 return -parseInt(d3.select(this).attr('width'), 10) / 2;
213 })
214 .attr('y', -10)
215 .attr('rx', 10)
216 .attr('ry', 10);
217
218 return g;
219 },
220
221 drawRelation: function(relation) {
222 var connectors = relation.source()
223 .getConnectorPair(relation.target());
224 var s = connectors[0];
225 var t = connectors[1];
226 var link = d3.select(this);
227
228 link
229 .attr('x1', s[0])
230 .attr('y1', s[1])
231 .attr('x2', t[0])
232 .attr('y2', t[1]);
233 return link;
234 },
235
236 updateSubordinateRelationsCount: function() {
237 var topo = this.get('component');
238 var vis = topo.vis;
239 var self = this;
240
241 vis.selectAll('.service')
242 .filter(function(d) {
243 return d.subordinate;
244 })
245 .select('.sub-rel-block tspan')
246 .text(function(d) {
247 return self.subordinateRelationsForService(d).length;
248 });
249 },
250
251 draglineClicked: function(d, self) {
252 // It was technically the dragline that was clicked, but the
253 // intent was to click on the background, so...
254 self.backgroundClicked();
255 },
256
257 addRelButtonClicked: function(data, context) {
258 var topo = context.get('component');
259 var box = topo.get('active_service');
260 var service = topo.serviceForBox(box);
261 var origin = topo.get('active_context');
262 context.addRelationDragStart({service: box});
263 topo.fire('toggleControlPanel');
264 context.addRelationStart(box, context, origin);
265 },
266
267 /*
268 * Event handler for the add relation button.
269 */
270 addRelation: function(evt) {
271 var curr_action = this.get('currentServiceClickAction');
272 if (curr_action === 'show_service') {
273 this.set('currentServiceClickAction', 'addRelationStart');
274 } else if (curr_action === 'addRelationStart' ||
275 curr_action === 'ambiguousAddRelationCheck') {
276 this.set('currentServiceClickAction', 'toggleControlPanel');
277 } // Otherwise do nothing.
278 },
279
280 snapToService: function(evt) {
281 var d = evt.service;
282 var rect = evt.rect;
283
284 // Do not fire if we're on the same service.
285 if (d === this.get('addRelationStart_service')) {
286 return;
287 }
288 this.set('potential_drop_point_service', d);
289 this.set('potential_drop_point_rect', rect);
290 utils.addSVGClass(rect, 'hover');
291
292 // If we have an active dragline, stop redrawing it on mousemove
293 // and draw the line between the two nearest connector points of
294 // the two services.
295 if (this.dragline) {
296 var connectors = d.getConnectorPair(
297 this.get('addRelationStart_service'));
298 var s = connectors[0];
299 var t = connectors[1];
300 this.dragline.attr('x1', t[0])
301 .attr('y1', t[1])
302 .attr('x2', s[0])
303 .attr('y2', s[1])
304 .attr('class', 'relation pending-relation dragline');
305 this.draglineOverService = true;
306 }
307 },
308
309 snapOutOfService: function() {
310 // Do not fire if we aren't looking for a relation endpoint.
311 if (!this.get('potential_drop_point_rect')) {
312 return;
313 }
314
315 this.set('potential_drop_point_service', null);
316 this.set('potential_drop_point_rect', null);
317
318 if (this.dragline) {
319 this.dragline.attr('class',
320 'relation pending-relation dragline dragging');
321 this.draglineOverService = false;
322 }
323 },
324
325 addRelationDragStart: function(evt) {
326 var d = evt.service;
327 // Create a pending drag-line.
328 var vis = this.get('component').vis;
329 var dragline = vis.append('line')
330 .attr('class',
331 'relation pending-relation dragline dragging');
332 var self = this;
333
334 // Start the line between the cursor and the nearest connector
335 // point on the service.
336 var mouse = d3.mouse(Y.one('.topology svg').getDOMNode());
337 self.cursorBox = new views.BoundingBox();
338 self.cursorBox.pos = {x: mouse[0], y: mouse[1], w: 0, h: 0};
339 var point = self.cursorBox.getConnectorPair(d);
340 dragline.attr('x1', point[0][0])
341 .attr('y1', point[0][1])
342 .attr('x2', point[1][0])
343 .attr('y2', point[1][1]);
344 self.dragline = dragline;
345
346 // Start the add-relation process.
347 self.addRelationStart(d, self);
348 },
349
350 addRelationDrag: function(evt) {
351 var d = evt.box;
352
353 // Rubberband our potential relation line if we're not currently
354 // hovering over a potential drop-point.
355 if (!this.get('potential_drop_point_service') &&
356 !this.draglineOverService) {
357 // Create a BoundingBox for our cursor.
358 this.cursorBox.pos = {x: d3.event.x, y: d3.event.y, w: 0, h: 0};
359
360 // Draw the relation line from the connector point nearest the
361 // cursor to the cursor itself.
362 var connectors = this.cursorBox.getConnectorPair(d),
363 s = connectors[1];
364 this.dragline.attr('x1', s[0])
365 .attr('y1', s[1])
366 .attr('x2', d3.event.x)
367 .attr('y2', d3.event.y);
368 }
369 },
370
371
372 addRelationDragEnd: function() {
373 // Get the line, the endpoint service, and the target <rect>.
374 var self = this;
375 var topo = self.get('component');
376 var rect = self.get('potential_drop_point_rect');
377 var endpoint = self.get('potential_drop_point_service');
378
379 topo.buildingRelation = false;
380 self.cursorBox = null;
381
382 // If we landed on a rect, add relation, otherwise, cancel.
383 if (rect) {
384 self.ambiguousAddRelationCheck(endpoint, self, rect);
385 } else {
386 // TODO clean up, abstract
387 self.cancelRelationBuild();
388 self.addRelation(); // Will clear the state.
389 }
390 },
391 removeRelation: function(d, context, view, confirmButton) {
392 var env = this.get('component').get('env');
393 var endpoints = d.endpoints;
394 var relationElement = Y.one(context.parentNode).one('.relation');
395 utils.addSVGClass(relationElement, 'to-remove pending-relation');
396 env.remove_relation(
397 endpoints[0][0] + ':' + endpoints[0][1].name,
398 endpoints[1][0] + ':' + endpoints[1][1].name,
399 Y.bind(this._removeRelationCallback, this, view,
400 relationElement, d.relation_id, confirmButton));
401 },
402
403 _removeRelationCallback: function(view,
404 relationElement, relationId, confirmButton, ev) {
405 var db = this.get('component').get('db');
406 var service = this.get('model');
407 if (ev.err) {
408 db.notifications.add(
409 new models.Notification({
410 title: 'Error deleting relation',
411 message: 'Relation ' + ev.endpoint_a + ' to ' + ev.endpoint_b,
412 level: 'error'
413 })
414 );
415 utils.removeSVGClass(this.relationElement,
416 'to-remove pending-relation');
417 } else {
418 // Remove the relation from the DB.
419 db.relations.remove(db.relations.getById(relationId));
420 // Redraw the graph and reattach events.
421 db.fire('update');
422 }
423 view.get('rmrelation_dialog').hide();
424 view.get('rmrelation_dialog').destroy();
425 confirmButton.set('disabled', false);
426 },
427
428 removeRelationConfirm: function(d, context, view) {
429 // Destroy the dialog if it already exists to prevent cluttering
430 // up the DOM.
431 if (!Y.Lang.isUndefined(view.get('rmrelation_dialog'))) {
432 view.get('rmrelation_dialog').destroy();
433 }
434 view.set('rmrelation_dialog', views.createModalPanel(
435 'Are you sure you want to remove this relation? ' +
436 'This cannot be undone.',
437 '#rmrelation-modal-panel',
438 'Remove Relation',
439 Y.bind(function(ev) {
440 ev.preventDefault();
441 var confirmButton = ev.target;
442 confirmButton.set('disabled', true);
443 view.removeRelation(d, context, view, confirmButton);
444 },
445 this)));
446 },
447
448 cancelRelationBuild: function() {
449 var topo = this.get('component');
450 var vis = topo.vis;
451 if (this.dragline) {
452 // Get rid of our drag line
453 this.dragline.remove();
454 this.dragline = null;
455 }
456 this.clickAddRelation = null;
457 this.set('currentServiceClickAction', 'toggleControlPanel');
458 topo.buildingRelation = false;
459 topo.fire('show', { selection: vis.selectAll('.service') });
460 vis.selectAll('.service').classed('selectable-service', false);
461 },
462
463 /**
464 * An "add relation" action has been initiated by the user.
465 *
466 * @method startRelation
467 * @param {object} service The service that is the source of the
468 * relation.
469 * @return {undefined} Side effects only.
470 */
471 startRelation: function(service) {
472 // Set flags on the view that indicate we are building a relation.
473 var topo = this.get('component');
474 var vis = topo.vis;
475
476 topo.buildingRelation = true;
477 this.clickAddRelation = true;
478
479 topo.fire('show', { selection: vis.selectAll('.service') });
480
481 var db = this.get('component').get('db');
482 var getServiceEndpoints = this.get('component')
483 .get('getServiceEndpoints');
484 var endpoints = models.getEndpoints(
485 service, getServiceEndpoints(), db);
486 // Transform endpoints into a list of relatable services (to the
487 // service).
488 var possible_relations = Y.Array.map(
489 Y.Array.flatten(Y.Object.values(endpoints)),
490 function(ep) {return ep.service;});
491 var invalidRelationTargets = {};
492
493 // Iterate services and invert the possibles list.
494 db.services.each(function(s) {
495 if (Y.Array.indexOf(possible_relations,
496 s.get('id')) === -1) {
497 invalidRelationTargets[s.get('id')] = true;
498 }
499 });
500
501 // Fade elements to which we can't relate.
502 // Rather than two loops this marks
503 // all services as selectable and then
504 // removes the invalid ones.
505 var sel = vis.selectAll('.service')
506 .classed('selectable-service', true)
507 .filter(function(d) {
508 return (d.id in invalidRelationTargets &&
509 d.id !== service.id);
510 });
511 topo.fire('fade', { selection: sel });
512 sel.classed('selectable-service', false);
513
514 // Store possible endpoints.
515 this.set('addRelationStart_possibleEndpoints', endpoints);
516 // Set click action.
517 this.set('currentServiceClickAction', 'ambiguousAddRelationCheck');
518 },
519
520 /*
521 * Fired when clicking the first service in the add relation
522 * flow.
523 */
524 addRelationStart: function(m, view, context) {
525 var topo = view.get('component');
526 var service = topo.serviceForBox(m);
527 view.startRelation(service);
528 // Store start service in attrs.
529 view.set('addRelationStart_service', m);
530 },
531
532 /*
533 * Test if the pending relation is ambiguous. Display a menu if so,
534 * create the relation if not.
535 */
536 ambiguousAddRelationCheck: function(m, view, context) {
537 var endpoints = view.get(
538 'addRelationStart_possibleEndpoints')[m.id];
539 var container = view.get('container');
540 var topo = view.get('component');
541
542 if (endpoints && endpoints.length === 1) {
543 // Create a relation with the only available endpoint.
544 var ep = endpoints[0],
545 endpoints_item = [
546 [ep[0].service, {
547 name: ep[0].name,
548 role: 'server' }],
549 [ep[1].service, {
550 name: ep[1].name,
551 role: 'client' }]];
552 view.addRelationEnd(endpoints_item, view, context);
553 return;
554 }
555
556 // Sort the endpoints alphabetically by relation name.
557 endpoints = endpoints.sort(function(a, b) {
558 return a[0].name + a[1].name < b[0].name + b[1].name;
559 });
560
561 // Stop rubberbanding on mousemove.
562 view.clickAddRelation = null;
563
564 // Display menu with available endpoints.
565 var menu = container.one('#ambiguous-relation-menu');
566 if (menu.one('.menu')) {
567 menu.one('.menu').remove(true);
568 }
569
570 menu.append(Templates
571 .ambiguousRelationList({endpoints: endpoints}));
572
573 // For each endpoint choice, bind an an event to 'click' to
574 // add the specified relation.
575 menu.all('li').on('click', function(evt) {
576 if (evt.currentTarget.hasClass('cancel')) {
577 return;
578 }
579 var el = evt.currentTarget,
580 endpoints_item = [
581 [el.getData('startservice'), {
582 name: el.getData('startname'),
583 role: 'server' }],
584 [el.getData('endservice'), {
585 name: el.getData('endname'),
586 role: 'client' }]];
587 menu.removeClass('active');
588 view.addRelationEnd(endpoints_item, view, context);
589 });
590
591 // Add a cancel item.
592 menu.one('.cancel').on('click', function(evt) {
593 menu.removeClass('active');
594 view.cancelRelationBuild();
595 });
596
597 // Display the menu at the service endpoint.
598 var tr = topo.zoom.translate();
599 var z = topo.zoom.scale();
600 menu.setStyle('top', m.y * z + tr[1]);
601 menu.setStyle('left', m.x * z + m.w * z + tr[0]);
602 menu.addClass('active');
603 topo.set('active_service', m);
604 topo.set('active_context', context);
605
606 // Firing resized will ensure the menu's positioned properly.
607 topo.fire('resized');
608 },
609
610 /*
611 * Fired when clicking the second service is clicked in the
612 * add relation flow.
613 *
614 * :param endpoints: array of two endpoints, each in the form
615 * ['service name', {
616 * name: 'endpoint type',
617 * role: 'client or server'
618 * }]
619 */
620 addRelationEnd: function(endpoints, view, context) {
621 // Redisplay all services
622 view.cancelRelationBuild();
623
624 // Get the vis, and links, build the new relation.
625 var vis = view.get('component').vis;
626 var env = view.get('component').get('env');
627 var db = view.get('component').get('db');
628 var source = view.get('addRelationStart_service');
629 var relation_id = 'pending-' + endpoints[0][0] + endpoints[1][0];
630
631 if (endpoints[0][0] === endpoints[1][0]) {
632 view.set('currentServiceClickAction', 'toggleControlPanel');
633 return;
634 }
635
636 // Create a pending relation in the database between the
637 // two services.
638 db.relations.create({
639 relation_id: relation_id,
640 display_name: 'pending',
641 endpoints: endpoints,
642 pending: true
643 });
644
645 // Firing the update event on the db will properly redraw the
646 // graph and reattach events.
647 //db.fire('update');
648 view.get('component').bindAllD3Events();
649 view.update();
650
651 // Fire event to add relation in juju.
652 // This needs to specify interface in the future.
653 env.add_relation(
654 endpoints[0][0] + ':' + endpoints[0][1].name,
655 endpoints[1][0] + ':' + endpoints[1][1].name,
656 Y.bind(this._addRelationCallback, this, view, relation_id)
657 );
658 view.set('currentServiceClickAction', 'toggleControlPanel');
659 },
660
661 _addRelationCallback: function(view, relation_id, ev) {
662 console.log('addRelationCallback reached');
663 var topo = view.get('component');
664 var db = topo.get('db');
665 var vis = topo.vis;
666 // Remove our pending relation from the DB, error or no.
667 db.relations.remove(
668 db.relations.getById(relation_id));
669 vis.select('#' + relation_id).remove();
670 if (ev.err) {
671 db.notifications.add(
672 new models.Notification({
673 title: 'Error adding relation',
674 message: 'Relation ' + ev.endpoint_a +
675 ' to ' + ev.endpoint_b,
676 level: 'error'
677 })
678 );
679 } else {
680 // Create a relation in the database between the two services.
681 var result = ev.result;
682 var endpoints = Y.Array.map(result.endpoints, function(item) {
683 var id = Y.Object.keys(item)[0];
684 return [id, item[id]];
685 });
686 db.relations.create({
687 relation_id: ev.result.id,
688 type: result['interface'],
689 endpoints: endpoints,
690 pending: false,
691 scope: result.scope,
692 // endpoints[1][1].name should be the same
693 display_name: endpoints[0][1].name
694 });
695 }
696 // Redraw the graph and reattach events.
697 //db.fire('update');
698 view.get('component').bindAllD3Events();
699 view.update();
700 },
701
702 /*
703 * Utility function to get subordinate relations for a service.
704 */
705 subordinateRelationsForService: function(service) {
706 return this.relPairs.filter(function(p) {
707 return p.modelIds().indexOf(service.modelId()) !== -1 &&
708 p.scope === 'container';
709 });
710 },
711
712 subRelBlockMouseEnter: function(d, self) {
713 // Add an 'active' class to all of the subordinate relations
714 // belonging to this service.
715 self.subordinateRelationsForService(d)
716 .forEach(function(p) {
717 utils.addSVGClass('#' + p.id, 'active');
718 });
719 },
720
721 subRelBlockMouseLeave: function(d, self) {
722 // Remove 'active' class from all subordinate relations.
723 if (!self.keepSubRelationsVisible) {
724 utils.removeSVGClass('.subordinate-rel-group', 'active');
725 }
726 },
727
728 /**
729 * Toggle the visibility of subordinate relations for visibility
730 * or removal.
731 * @param {object} d The data-bound object (the subordinate).
732 * @param {object} self The view.
733 **/
734 subRelBlockClick: function(d, self) {
735 if (self.keepSubRelationsVisible) {
736 self.hideSubordinateRelations();
737 } else {
738 self.showSubordinateRelations(this);
739 }
740 },
741
742 /**
743 * Show subordinate relations for a service.
744 *
745 * @method showSubordinateRelations
746 * @param {Object} subordinate The sub-rel-block g element in the form
747 * of a DOM node.
748 * @return {undefined} nothing.
749 */
750 showSubordinateRelations: function(subordinate) {
751 this.keepSubRelationsVisible = true;
752 utils.addSVGClass(Y.one(subordinate).one('.sub-rel-count'), 'active');
753 },
754
755 /**
756 * Hide subordinate relations.
757 *
758 * @method hideSubordinateRelations
759 * @return {undefined} nothing.
760 */
761 hideSubordinateRelations: function() {
762 var container = this.get('container');
763 utils.removeSVGClass('.subordinate-rel-group', 'active');
764 this.keepSubRelationsVisible = false;
765 utils.removeSVGClass(container.one('.sub-rel-count.active'),
766 'active');
767 },
768
769 relationClick: function(d, self) {
770 if (d.scope === 'container') {
771 var subRelDialog = views.createModalPanel(
772 'You may not remove a subordinate relation.',
773 '#rmsubrelation-modal-panel');
774 subRelDialog.addButton(
775 { value: 'Cancel',
776 section: Y.WidgetStdMod.FOOTER,
777 /**
778 * @method action Hides the dialog on click.
779 * @param {object} e The click event.
780 * @return {undefined} nothing.
781 */
782 action: function(e) {
783 e.preventDefault();
784 subRelDialog.hide();
785 subRelDialog.destroy();
786 },
787 classNames: ['btn']
788 });
789 subRelDialog.get('boundingBox').all('.yui3-button')
790 .removeClass('yui3-button');
791 } else {
792 self.removeRelationConfirm(d, this, self);
793 }
26 } 794 }
27 795
28 }, { 796 }, {
29 ATTRS: {} 797 ATTRS: {}
30 798
31 }); 799 });
32 views.RelationModule = RelationModule; 800 views.RelationModule = RelationModule;
33 }, '0.1.0', { 801 }, '0.1.0', {
34 requires: [ 802 requires: [
35 'd3', 803 'd3',
36 'd3-components', 804 'd3-components',
37 'node', 805 'node',
38 'event', 806 'event',
39 'juju-models', 807 'juju-models',
40 'juju-env' 808 'juju-env'
41 ] 809 ]
42 }); 810 });
OLDNEW

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