~gary/juju-gui/service-header

« back to all changes in this revision

Viewing changes to app/views/environment.js

  • Committer: Gary Poster
  • Date: 2012-09-21 03:53:56 UTC
  • mto: This revision was merged to the branch mainline in revision 107.
  • Revision ID: gary.poster@canonical.com-20120921035356-dv7ws5so3ceafw88
now with more linting and less lint!

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
YUI.add('juju-view-environment', function(Y) {
4
4
 
5
 
var views = Y.namespace('juju.views'),
6
 
    Templates = views.Templates;
7
 
 
8
 
var EnvironmentView = Y.Base.create('EnvironmentView', Y.View, [views.JujuBaseView], {
9
 
    events: {
10
 
        '#add-relation-btn': {click: 'add_relation'},
11
 
        '#zoom-out-btn': {click: 'zoom_out'},
12
 
        '#zoom-in-btn': {click: 'zoom_in'}
13
 
    },
14
 
 
15
 
    initializer: function () {
16
 
        console.log('View: Initialized: Env');
17
 
        this.publish('showService', {preventable: false});
18
 
    },
19
 
 
20
 
    render: function () {
21
 
        console.log('View: Render: Env');
22
 
        var container = this.get('container');
23
 
        EnvironmentView.superclass.render.apply(this, arguments);
24
 
        container.setHTML(Templates.overview());
25
 
        this.render_canvas();
26
 
        return this;
27
 
    },
28
 
 
29
 
    render_canvas: function(){
30
 
        var self = this,
31
 
            container = this.get('container'),
32
 
            m = this.get('domain_models'),
33
 
            height = 600,
34
 
            width = 640;
35
 
 
36
 
        var services = m.services.toArray().map(function(s) {
 
5
  var views = Y.namespace('juju.views'),
 
6
      Templates = views.Templates;
 
7
 
 
8
  var EnvironmentView = Y.Base.create('EnvironmentView',
 
9
                                      Y.View, [views.JujuBaseView], {
 
10
        events: {
 
11
          '#add-relation-btn': {click: 'add_relation'},
 
12
          '#zoom-out-btn': {click: 'zoom_out'},
 
13
          '#zoom-in-btn': {click: 'zoom_in'}
 
14
        },
 
15
 
 
16
        initializer: function() {
 
17
          console.log('View: Initialized: Env');
 
18
          this.publish('showService', {preventable: false});
 
19
        },
 
20
 
 
21
        render: function() {
 
22
          console.log('View: Render: Env');
 
23
          var container = this.get('container');
 
24
          EnvironmentView.superclass.render.apply(this, arguments);
 
25
          container.setHTML(Templates.overview());
 
26
          this.render_canvas();
 
27
          return this;
 
28
        },
 
29
 
 
30
        render_canvas: function() {
 
31
 
 
32
          function processRelation(r) {
 
33
            var endpoints = r.get('endpoints'),
 
34
                rel_services = [];
 
35
            Y.each(endpoints, function(ep) {
 
36
              rel_services.push(services.filter(function(d) {
 
37
                return d.get('id') === ep[0];
 
38
              })[0]);
 
39
            });
 
40
            return rel_services;
 
41
          }
 
42
 
 
43
          function processRelations(rels) {
 
44
            var pairs = [];
 
45
            Y.each(rels, function(rel) {
 
46
              var pair = processRelation(rel);
 
47
              // Skip peer for now.
 
48
              if (pair.length === 2) {
 
49
                pairs.push({source: pair[0],
 
50
                  target: pair[1]});
 
51
              }
 
52
 
 
53
            });
 
54
            return pairs;
 
55
          }
 
56
 
 
57
          var self = this,
 
58
              container = this.get('container'),
 
59
              m = this.get('domain_models'),
 
60
              height = 600,
 
61
              width = 640;
 
62
 
 
63
          var services = m.services.toArray().map(function(s) {
37
64
            s.value = s.get('unit_count');
38
65
            return s;
39
 
        });
40
 
        var relations = m.relations.toArray();
41
 
        var fill = d3.scale.category20();
 
66
          });
 
67
          var relations = m.relations.toArray();
 
68
          var fill = d3.scale.category20();
42
69
 
43
 
        var xscale = d3.scale.linear()
 
70
          var xscale = d3.scale.linear()
44
71
            .domain([-width / 2, width / 2])
45
72
            .range([2, width]);
46
73
 
47
 
        var yscale = d3.scale.linear()
 
74
          var yscale = d3.scale.linear()
48
75
            .domain([-height / 2, height / 2])
49
76
            .range([height, 0]);
50
77
 
51
 
        // Create a pan/zoom behavior manager.
52
 
        var zoom = d3.behavior.zoom()
 
78
          // Create a pan/zoom behavior manager.
 
79
          var zoom = d3.behavior.zoom()
53
80
            .x(xscale)
54
81
            .y(yscale)
55
82
            .scaleExtent([0.25, 1.75])
56
83
            .on('zoom', function() {
57
84
                self.rescale(vis, d3.event);
58
 
            });
59
 
        self.set('zoom', zoom);
60
 
 
61
 
        // Scales for unit sizes.
62
 
        // XXX magic numbers will have to change; likely during
63
 
        // the UI work
64
 
        var service_scale_width = d3.scale.log().range([164, 200]);
65
 
        var service_scale_height = d3.scale.log().range([64, 100]);
66
 
 
67
 
        // Set up the visualization with a pack layout.
68
 
        var vis = d3.select(container.getDOMNode())
 
85
              });
 
86
          self.set('zoom', zoom);
 
87
 
 
88
          // Scales for unit sizes.
 
89
          // XXX magic numbers will have to change; likely during
 
90
          // the UI work
 
91
          var service_scale_width = d3.scale.log().range([164, 200]);
 
92
          var service_scale_height = d3.scale.log().range([64, 100]);
 
93
 
 
94
          // Set up the visualization with a pack layout.
 
95
          var vis = d3.select(container.getDOMNode())
69
96
            .selectAll('#canvas')
70
97
            .append('svg:svg')
71
98
            .attr('pointer-events', 'all')
72
99
            .append('svg:g')
73
100
            .call(zoom)
74
101
            .append('g');
75
 
        vis.append('svg:rect')
 
102
          vis.append('svg:rect')
76
103
            .attr('fill', 'white');
77
104
 
78
 
        // Bind visualization resizing on window resize
79
 
        Y.on('windowresize', function() {
80
 
            self.setSizesFromViewport(vis, container, xscale, yscale);
81
 
        });
82
 
 
83
 
        // If the view is bound to the dom, set sizes from viewport
84
 
        if (Y.one('svg')) {
85
 
            self.setSizesFromViewport(vis, container, xscale, yscale);
86
 
        }
87
 
 
88
 
        var tree = d3.layout.pack()
 
105
          // Bind visualization resizing on window resize
 
106
          Y.on('windowresize', function() {
 
107
            self.setSizesFromViewport(vis, container, xscale, yscale);
 
108
          });
 
109
 
 
110
          // If the view is bound to the dom, set sizes from viewport
 
111
          if (Y.one('svg')) {
 
112
            self.setSizesFromViewport(vis, container, xscale, yscale);
 
113
          }
 
114
 
 
115
          var tree = d3.layout.pack()
89
116
            .size([width, height])
90
117
            .padding(200);
91
118
 
92
 
        var rel_data = processRelations(relations);
 
119
          var rel_data = processRelations(relations);
93
120
 
94
 
        function update_links() {
 
121
          function update_links() {
95
122
            var link = vis.selectAll('polyline.relation')
96
123
                .remove();
97
124
            link = vis.selectAll('polyline.relation')
99
126
            link.enter().insert('svg:polyline', 'g.service')
100
127
                .attr('class', 'relation')
101
128
                .attr('points', function(d) { return self.draw_relation(d); });
102
 
        }
 
129
          }
103
130
 
104
 
        var drag = d3.behavior.drag()
105
 
            .on('drag', function(d,i) {
 
131
          var drag = d3.behavior.drag()
 
132
            .on('drag', function(d, i) {
106
133
                d.x += d3.event.dx;
107
134
                d.y += d3.event.dy;
108
 
                d3.select(this).attr('transform', function(d,i){
109
 
                    return 'translate(' + [ d.x,d.y ] + ')';
 
135
                d3.select(this).attr('transform', function(d, i) {
 
136
                  return 'translate(' + [d.x, d.y] + ')';
110
137
                });
111
138
                update_links();
112
 
            });
 
139
              });
113
140
 
114
 
        // Generate a node for each service, draw it as a rect with
115
 
        // labels for service and charm.
116
 
        var node = vis.selectAll('.service')
 
141
          // Generate a node for each service, draw it as a rect with
 
142
          // labels for service and charm.
 
143
          var node = vis.selectAll('.service')
117
144
            .data(self._saved_coords(services) ?
118
 
                services :
119
 
                self._generate_coords(services, tree))
 
145
              services :
 
146
              self._generate_coords(services, tree))
120
147
            .enter().append('g')
121
148
            .attr('class', 'service')
122
 
            .attr('transform', function (d) {
123
 
                return 'translate(' + [d.x,d.y] + ')';
124
 
            })
 
149
            .attr('transform', function(d) {
 
150
                return 'translate(' + [d.x, d.y] + ')';
 
151
              })
125
152
            .on('click', function(m) {
126
153
                // Get the current click action.
127
154
                var curr_click_action =
132
159
                // with the service, the SVG node, and the view
133
160
                // as arguments.
134
161
                (self.service_click_actions[curr_click_action])(m, this, self);
135
 
            })
 
162
              })
136
163
            .call(drag);
137
164
 
138
 
        node.append('rect')
 
165
          node.append('rect')
139
166
            .attr('class', 'service-border')
140
167
            .attr('width', function(d) {
141
168
                var w = service_scale_width(d.get('unit_count'));
142
169
                d.set('width', w);
143
170
                return w;
144
 
                })
 
171
              })
145
172
            .attr('height', function(d) {
146
173
                var h = service_scale_height(d.get('unit_count'));
147
174
                d.set('height', h);
148
175
                return h;});
149
176
 
150
 
        var service_labels = node.append('text').append('tspan')
 
177
          var service_labels = node.append('text').append('tspan')
151
178
            .attr('class', 'name')
152
179
            .attr('x', 54)
153
180
            .attr('y', '1em')
154
181
            .text(function(d) {return d.get('id'); });
155
182
 
156
 
        var charm_labels = node.append('text').append('tspan')
 
183
          var charm_labels = node.append('text').append('tspan')
157
184
            .attr('x', 54)
158
185
            .attr('y', '2.5em')
159
186
            .attr('dy', '3em')
160
187
            .attr('class', 'charm-label')
161
188
            .text(function(d) { return d.get('charm'); });
162
189
 
163
 
        // Show whether or not the service is exposed using an
164
 
        // indicator (currently a simple circle).
165
 
        // TODO this will likely change to an image with UI uodates.
166
 
        var exposed_indicator = node.filter(function(d) {
167
 
                return d.get('exposed');
168
 
            })
 
190
          // Show whether or not the service is exposed using an
 
191
          // indicator (currently a simple circle).
 
192
          // TODO this will likely change to an image with UI uodates.
 
193
          var exposed_indicator = node.filter(function(d) {
 
194
            return d.get('exposed');
 
195
          })
169
196
            .append('circle')
170
197
            .attr('cx', 0)
171
198
            .attr('cy', 10)
172
199
            .attr('r', 5)
173
200
            .attr('class', 'exposed-indicator on');
174
 
        exposed_indicator.append('title')
 
201
          exposed_indicator.append('title')
175
202
            .text(function(d) {
176
203
                return d.get('exposed') ? 'Exposed' : '';
177
 
            });
 
204
              });
178
205
 
179
 
        // Add the relative health of a service in the form of a pie chart
180
 
        // comprised of units styled appropriately.
181
 
        // TODO aggregate statuses into good/bad/pending
182
 
        var status_chart_arc = d3.svg.arc()
 
206
          // Add the relative health of a service in the form of a pie chart
 
207
          // comprised of units styled appropriately.
 
208
          // TODO aggregate statuses into good/bad/pending
 
209
          var status_chart_arc = d3.svg.arc()
183
210
            .innerRadius(10)
184
211
            .outerRadius(25);
185
 
        var status_chart_layout = d3.layout.pie()
 
212
          var status_chart_layout = d3.layout.pie()
186
213
            .value(function(d) { return (d.value ? d.value : 1); });
187
214
 
188
 
        var status_chart = node.append('g')
 
215
          var status_chart = node.append('g')
189
216
            .attr('class', 'service-status')
190
217
            .attr('transform', 'translate(30,32)');
191
 
        var status_arcs = status_chart.selectAll('path')
 
218
          var status_arcs = status_chart.selectAll('path')
192
219
            .data(function(d) {
193
220
                var aggregate_map = d.get('aggregated_status'),
194
221
                    aggregate_list = [];
195
 
 
196
 
                for (var status_name in aggregate_map) {
197
 
                    aggregate_list.push({
198
 
                        name: status_name,
199
 
                        value: aggregate_map[status_name]
200
 
                    });
201
 
                }
 
222
                Y.Object.each(aggregate_map, function(value, name) {
 
223
                  aggregate_list.push({name: name, value: value});
 
224
                });
202
225
 
203
226
                return status_chart_layout(aggregate_list);
204
 
            })
 
227
              })
205
228
            .enter().append('path')
206
229
            .attr('d', status_chart_arc)
207
230
            .attr('class', function(d) { return 'status-' + d.data.name; })
208
231
            .attr('fill-rule', 'evenodd')
209
232
            .append('title').text(function(d) {
210
233
                return d.data.name;
211
 
            });
 
234
              });
212
235
 
213
 
        // Add the unit counts, visible only on hover.
214
 
        var unit_count = status_chart.append('text')
 
236
          // Add the unit counts, visible only on hover.
 
237
          var unit_count = status_chart.append('text')
215
238
            .attr('class', 'unit-count hide-count')
216
239
            .on('mouseover', function() {
217
240
                d3.select(this).attr('class', 'unit-count show-count');
218
 
            })
 
241
              })
219
242
            .on('mouseout', function() {
220
243
                d3.select(this).attr('class', 'unit-count hide-count');
221
 
            })
 
244
              })
222
245
            .text(function(d) {
223
246
                return self.humanizeNumber(d.get('unit_count'));
224
 
            });
225
 
 
226
 
        function processRelation(r) {
227
 
            var endpoints = r.get('endpoints'),
228
 
            rel_services = [];
229
 
            Y.each(endpoints, function(ep) {
230
 
                rel_services.push(services.filter(function(d) {
231
 
                    return d.get('id') == ep[0];
232
 
                })[0]);
233
 
            });
234
 
            return rel_services;
235
 
        }
236
 
 
237
 
        function processRelations(rels) {
238
 
            var pairs = [];
239
 
            Y.each(rels, function(rel) {
240
 
                var pair = processRelation(rel);
241
 
                // Skip peer for now.
242
 
                if (pair.length == 2) {
243
 
                    pairs.push({source: pair[0],
244
 
                               target: pair[1]});
245
 
                }
246
 
 
247
 
            });
248
 
            return pairs;
249
 
        }
250
 
 
251
 
        self.set('tree', tree);
252
 
        self.set('vis', vis);
253
 
        update_links();
254
 
    },
255
 
 
256
 
    /*
 
247
              });
 
248
 
 
249
          self.set('tree', tree);
 
250
          self.set('vis', vis);
 
251
          update_links();
 
252
        },
 
253
 
 
254
        /*
257
255
     * Check to make sure that every service has saved coordinates.
258
256
     */
259
 
    _saved_coords: function(services) {
260
 
        var saved_coords = true;
261
 
        services.forEach(function(service) {
 
257
        _saved_coords: function(services) {
 
258
          var saved_coords = true;
 
259
          services.forEach(function(service) {
262
260
            if (!service.x || !service.y) {
263
 
                saved_coords = false;
 
261
              saved_coords = false;
264
262
            }
265
 
        });
266
 
        return saved_coords;
267
 
    },
 
263
          });
 
264
          return saved_coords;
 
265
        },
268
266
 
269
 
    /*
 
267
        /*
270
268
     * Generates coordinates for those services that are missing them.
271
269
     */
272
 
    _generate_coords: function(services, tree) {
273
 
        services.forEach(function(service) {
 
270
        _generate_coords: function(services, tree) {
 
271
          services.forEach(function(service) {
274
272
            if (service.x && service.y) {
275
 
                service.set('x', service.x);
276
 
                service.set('y', service.y);
 
273
              service.set('x', service.x);
 
274
              service.set('y', service.y);
277
275
            }
278
 
        });
279
 
        var services_with_coords = tree.nodes({children: services})
 
276
          });
 
277
          var services_with_coords = tree.nodes({children: services})
280
278
            .filter(function(d) { return !d.children; });
281
 
        services_with_coords.forEach(function(service) {
 
279
          services_with_coords.forEach(function(service) {
282
280
            if (service.get('x') && service.get('y')) {
283
 
                service.x = service.get('x');
284
 
                service.y = service.get('y');
 
281
              service.x = service.get('x');
 
282
              service.y = service.get('y');
285
283
            }
286
 
        });
287
 
        return services_with_coords;
288
 
    },
 
284
          });
 
285
          return services_with_coords;
 
286
        },
289
287
 
290
 
    /*
 
288
        /*
291
289
     * Draw a relation between services.  Polylines take a list of points
292
290
     * in the form 'x y,( x y,)* x y'.
293
291
     *
294
292
     * TODO For now, just draw a straight line;
295
293
     * will eventually use A* to route around other services.
296
294
     */
297
 
    draw_relation: function(relation) {
298
 
        return (relation.source.x  + (
299
 
                    relation.source.get('width') / 2)) + ' ' +
300
 
            relation.source.y + ', ' +
301
 
            (relation.target.x + (relation.target.get('width') / 2)) + ' ' +
302
 
            relation.target.y;
303
 
    },
 
295
        draw_relation: function(relation) {
 
296
          return (relation.source.x + (
 
297
              relation.source.get('width') / 2)) + ' ' +
 
298
              relation.source.y + ', ' +
 
299
              (relation.target.x + (relation.target.get('width') / 2)) + ' ' +
 
300
              relation.target.y;
 
301
        },
304
302
 
305
 
    /*
 
303
        /*
306
304
     * Event handler for the add relation button.
307
305
     */
308
 
    add_relation: function(evt) {
309
 
        var curr_action = this.get('current_service_click_action'),
310
 
            container = this.get('container');
311
 
        if (curr_action == 'show_service') {
 
306
        add_relation: function(evt) {
 
307
          var curr_action = this.get('current_service_click_action'),
 
308
              container = this.get('container');
 
309
          if (curr_action === 'show_service') {
312
310
            this.set('current_service_click_action', 'add_relation_start');
313
311
 
314
312
            // Add .selectable-service to all .service-border.
315
313
            this.addSVGClass('.service-border', 'selectable-service');
316
314
            container.one('#add-relation-btn').addClass('active');
317
 
        } else if (curr_action == 'add_relation_start' ||
318
 
                curr_action == 'add_relation_end') {
 
315
          } else if (curr_action === 'add_relation_start' ||
 
316
              curr_action === 'add_relation_end') {
319
317
            this.set('current_service_click_action', 'show_service');
320
318
 
321
319
            // Remove selectable border from all nodes.
322
320
            this.removeSVGClass('.service-border', 'selectable-service');
323
321
            container.one('#add-relation-btn').removeClass('active');
324
 
        } // Otherwise do nothing.
325
 
    },
 
322
          } // Otherwise do nothing.
 
323
        },
326
324
 
327
 
    /*
 
325
        /*
328
326
     * Zoom in event handler.
329
327
     */
330
 
    zoom_out: function(evt) {
331
 
        this._fire_zoom(-0.2);
332
 
    },
 
328
        zoom_out: function(evt) {
 
329
          this._fire_zoom(-0.2);
 
330
        },
333
331
 
334
 
    /*
 
332
        /*
335
333
     * Zoom out event handler.
336
334
     */
337
 
    zoom_in: function(evt) {
338
 
        this._fire_zoom(0.2);
339
 
    },
 
335
        zoom_in: function(evt) {
 
336
          this._fire_zoom(0.2);
 
337
        },
340
338
 
341
 
    /*
 
339
        /*
342
340
     * Wraper around the actual rescale method for zoom buttons.
343
341
     */
344
 
    _fire_zoom: function(delta) {
345
 
        var vis = this.get('vis'),
346
 
            zoom = this.get('zoom'),
347
 
            evt = {};
348
 
 
349
 
        // Build a temporary event that rescale can use of a similar
350
 
        // construction to d3.event.
351
 
        evt.translate = zoom.translate();
352
 
        evt.scale = zoom.scale() + delta;
353
 
 
354
 
        // Update the scale in our zoom behavior manager to maintain state.
355
 
        this.get('zoom').scale(evt.scale);
356
 
 
357
 
        this.rescale(vis, evt);
358
 
    },
359
 
 
360
 
    /*
 
342
        _fire_zoom: function(delta) {
 
343
          var vis = this.get('vis'),
 
344
              zoom = this.get('zoom'),
 
345
              evt = {};
 
346
 
 
347
          // Build a temporary event that rescale can use of a similar
 
348
          // construction to d3.event.
 
349
          evt.translate = zoom.translate();
 
350
          evt.scale = zoom.scale() + delta;
 
351
 
 
352
          // Update the scale in our zoom behavior manager to maintain state.
 
353
          this.get('zoom').scale(evt.scale);
 
354
 
 
355
          this.rescale(vis, evt);
 
356
        },
 
357
 
 
358
        /*
361
359
     * Rescale the visualization on a zoom/pan event.
362
360
     */
363
 
    rescale: function(vis, evt) {
364
 
        this.set('scale', evt.scale);
365
 
        vis.attr('transform', 'translate(' + evt.translate + ')' +
366
 
                 ' scale(' + evt.scale + ')');
367
 
    },
 
361
        rescale: function(vis, evt) {
 
362
          this.set('scale', evt.scale);
 
363
          vis.attr('transform', 'translate(' + evt.translate + ')' +
 
364
              ' scale(' + evt.scale + ')');
 
365
        },
368
366
 
369
 
    /*
 
367
        /*
370
368
     * Set the visualization size based on the viewport
371
369
     */
372
 
    setSizesFromViewport: function(vis, container, xscale, yscale) {
373
 
        // start with some reasonable defaults
374
 
        var viewport_height = '100%',
375
 
            viewport_width = parseInt(
376
 
                container.getComputedStyle('width'), 10),
377
 
            svg = container.one('svg'),
378
 
            width = 800,
379
 
            height = 600;
380
 
        if (container.get('winHeight') &&
381
 
                Y.one('#overview-tasks') &&
382
 
                Y.one('.navbar')) {
 
370
        setSizesFromViewport: function(vis, container, xscale, yscale) {
 
371
          // start with some reasonable defaults
 
372
          var viewport_height = '100%',
 
373
              viewport_width = parseInt(
 
374
              container.getComputedStyle('width'), 10),
 
375
              svg = container.one('svg'),
 
376
              width = 800,
 
377
              height = 600;
 
378
          if (container.get('winHeight') &&
 
379
              Y.one('#overview-tasks') &&
 
380
              Y.one('.navbar')) {
383
381
            // Attempt to get the viewport height minus the navbar at top and
384
382
            // control bar at the bottom. Use Y.one() to ensure that the
385
383
            // container is attached first (provides some sensible defaults)
394
392
            // Make sure we don't get sized any smaller than 800x600
395
393
            viewport_height = Math.max(viewport_height, height);
396
394
            if (container.get('winWidth') < width) {
397
 
                viewport_width = width;
 
395
              viewport_width = width;
398
396
            }
399
 
        }
400
 
        // Set the svg sizes
401
 
        svg.setAttribute('width', viewport_width)
 
397
          }
 
398
          // Set the svg sizes
 
399
          svg.setAttribute('width', viewport_width)
402
400
            .setAttribute('height', viewport_height);
403
401
 
404
 
        // Get the resulting computed sizes (in the case of 100%)
405
 
        width = parseInt(svg.getComputedStyle('width'), 10);
406
 
        height = parseInt(svg.getComputedStyle('height'), 10);
 
402
          // Get the resulting computed sizes (in the case of 100%)
 
403
          width = parseInt(svg.getComputedStyle('width'), 10);
 
404
          height = parseInt(svg.getComputedStyle('height'), 10);
407
405
 
408
 
        // Set the internal rect's size
409
 
        svg.one('rect').setAttribute('width', width)
 
406
          // Set the internal rect's size
 
407
          svg.one('rect').setAttribute('width', width)
410
408
            .setAttribute('height', height);
411
409
 
412
 
        // Reset the scale parameters
413
 
        xscale.domain([-width / 2, width / 2])
 
410
          // Reset the scale parameters
 
411
          xscale.domain([-width / 2, width / 2])
414
412
            .range([0, width]);
415
 
        yscale.domain([-height / 2, height / 2])
 
413
          yscale.domain([-height / 2, height / 2])
416
414
            .range([height, 0]);
417
415
 
418
 
    },
 
416
        },
419
417
 
420
 
    /*
 
418
        /*
421
419
     * Actions to be called on clicking a service.
422
420
     */
423
 
    service_click_actions: {
424
 
        /*
 
421
        service_click_actions: {
 
422
          /*
425
423
         * Default action: view a service
426
424
         */
427
 
        show_service: function(m, context, view) {
 
425
          show_service: function(m, context, view) {
428
426
            view.fire('showService', {service: m});
429
 
        },
 
427
          },
430
428
 
431
 
        /*
 
429
          /*
432
430
         * Fired when clicking the first service in the add relation
433
431
         * flow.
434
432
         */
435
 
        add_relation_start: function(m, context, view) {
 
433
          add_relation_start: function(m, context, view) {
436
434
            // Remove selectable border from current node.
437
435
            var node = Y.one(context).one('.service-border');
438
436
            view.removeSVGClass(node, 'selectable-service');
440
438
            view.set('add_relation_start_service', m);
441
439
            // Set click action.
442
440
            view.set('current_service_click_action',
443
 
                    'add_relation_end');
444
 
        },
 
441
                'add_relation_end');
 
442
          },
445
443
 
446
 
        /*
 
444
          /*
447
445
         * Fired when clicking the second service is clicked in the
448
446
         * add relation flow.
449
447
         */
450
 
        add_relation_end: function(m, context, view) {
 
448
          add_relation_end: function(m, context, view) {
451
449
            // Remove selectable border from all nodes
452
450
            view.removeSVGClass('.selectable-service', 'selectable-service');
453
451
 
457
455
                env = view.get('env'),
458
456
                container = view.get('container'),
459
457
                rel = {
460
 
                    source: view.get('add_relation_start_service'),
461
 
                    target: m
 
458
                  source: view.get('add_relation_start_service'),
 
459
                  target: m
462
460
                };
463
461
 
464
462
            // Add temp relation between services.
473
471
                rel.source.get('id'),
474
472
                rel.target.get('id'),
475
473
                function(resp) {
476
 
                    container.one('#add-relation-btn').removeClass('active');
477
 
                    if (resp.err) {
478
 
                        console.log('Error adding relation');
479
 
                    }
 
474
                  container.one('#add-relation-btn').removeClass('active');
 
475
                  if (resp.err) {
 
476
                    console.log('Error adding relation');
 
477
                  }
480
478
                });
481
479
            // For now, set back to show_service.
482
480
            view.set('current_service_click_action', 'show_service');
483
 
        }
484
 
    }
485
 
 
486
 
}, {
487
 
    ATTRS: {
488
 
        current_service_click_action: { value: 'show_service' }
489
 
    }
490
 
});
491
 
 
492
 
views.environment = EnvironmentView;
 
481
          }
 
482
        }
 
483
 
 
484
      }, {
 
485
        ATTRS: {
 
486
          current_service_click_action: { value: 'show_service' }
 
487
        }
 
488
      });
 
489
 
 
490
  views.environment = EnvironmentView;
493
491
}, '0.1.0', {
494
 
    requires: ['juju-templates',
495
 
               'juju-view-utils',
496
 
               'd3',
497
 
               'base-build',
498
 
               'handlebars-base',
499
 
               'node',
500
 
               'event-resize',
501
 
               'view']
 
492
  requires: ['juju-templates',
 
493
    'juju-view-utils',
 
494
    'd3',
 
495
    'base-build',
 
496
    'handlebars-base',
 
497
    'node',
 
498
    'event-resize',
 
499
    'view']
502
500
});