Merge lp:~bcsaller/juju-gui/topology-panzoom into lp:juju-gui/experimental
- topology-panzoom
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 292 | ||||
Proposed branch: | lp:~bcsaller/juju-gui/topology-panzoom | ||||
Merge into: | lp:juju-gui/experimental | ||||
Diff against target: |
1720 lines (+618/-503) 11 files modified
app/app.js (+16/-31) app/assets/javascripts/d3-components.js (+116/-56) app/templates/overview.handlebars (+1/-1) app/views/environment.js (+24/-16) app/views/topology/mega.js (+120/-287) app/views/topology/panzoom.js (+155/-6) app/views/topology/topology.js (+89/-15) test/test_d3_components.js (+6/-4) test/test_environment_view.js (+10/-7) test/test_topology.js (+3/-2) undocumented (+78/-78) |
||||
To merge this branch: | bzr merge lp:~bcsaller/juju-gui/topology-panzoom | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+140671@code.launchpad.net |
Commit message
Description of the change
Panzoom Module
This branch breaks out the first module
from Mega into a more module unit. While this
module is small a number of changes occur to make
this happen. The framework underwent some improvemnts,
changes around interaction with App and view replacement
occured, event bindings had to be updated. A pattern for
cross module event firing was established.
In future modules, the topo/component fires the events and
modules are bubble targets.
Benjamin Saller (bcsaller) wrote : | # |
- 299. By Benjamin Saller
-
wip
- 300. By Benjamin Saller
-
merge trunk
- 301. By Benjamin Saller
-
restore bind origin change
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
- 302. By Benjamin Saller
-
remove only
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
Gary Poster (gary) wrote : | # |
Thanks for helping me understand this. As I expected, I'm +1 on the
code. I would like a bug/card to fix the prod tests on start. I'd like
to see if we can fix the viewport resize issue one way or another before
we land.
Thank you
Gary
https:/
File app/assets/
https:/
app/assets/
{
Comment describing behavior and saying that the expected client of this
is the component might be nice.
https:/
File app/views/
https:/
app/views/
method for zoom buttons.
Wrapper
Kapil Thangavelu (hazmat) wrote : | # |
quick items
https:/
File app/assets/
https:/
app/assets/
eventPhase, name);
s/console.log // console.debug ..
s/'Bind'/'d3 components bind'
- 303. By Benjamin Saller
-
review changes, fix from makyo for window events
Benjamin Saller (bcsaller) wrote : | # |
*** Submitted:
Panzoom Module
This branch breaks out the first module
from Mega into a more module unit. While this
module is small a number of changes occur to make
this happen. The framework underwent some improvemnts,
changes around interaction with App and view replacement
occured, event bindings had to be updated. A pattern for
cross module event firing was established.
In future modules, the topo/component fires the events and
modules are bubble targets.
R=gary.poster, hazmat
CC=
https:/
https:/
File app/views/
https:/
app/views/
Test again w/o broadcast.
https:/
File app/views/
https:/
app/views/
views.BoundingB
need new here?
Brad Crittenden (bac) wrote : | # |
I make several suggestions but they are all straight-forward so no
re-look is necessary before landing.
Recently I *think* we agreed to the 'one var per variable' idea which
you didn't follow for the newly added code. Perhaps it was "When in
Rome" or you just forgot. I'm just mentioning as a point of discussion
not a suggestion to change as it would delay this important work from
landing.
https:/
File app/assets/
https:/
app/assets/
These two cases are mutually exclusive, right, so 'else if' would be
clearer.
https:/
app/assets/
(Y.Array.
I don't understand why the indexOf is used instead of just a comparison.
https:/
app/assets/
s/adaptor/adapter
https:/
File app/views/
https:/
app/views/
This line is a complete thought and needs a period to be more readable.
https:/
app/views/
I'm unclear what you're saying here.
https:/
File app/views/
https:/
app/views/
typo: the a
Pick one? :)
https:/
app/views/
typo: container
Is that variable even used?
https:/
app/views/
parseInt(
I wish we'd made a wrapper early on for parseInt that defaulted to base
10. So annoying to see everywhere.
https:/
File app/views/
https:/
app/views/
Emitted
Preview Diff
1 | === modified file 'app/app.js' |
2 | --- app/app.js 2012-12-14 20:25:16 +0000 |
3 | +++ app/app.js 2012-12-20 16:25:28 +0000 |
4 | @@ -537,37 +537,22 @@ |
5 | * @method show_environment |
6 | */ |
7 | show_environment: function(req, res, next) { |
8 | - var view = this.getViewInfo('environment'), |
9 | - instance = view.instance, |
10 | - self = this; |
11 | - if (!instance) { |
12 | - console.log('new env view'); |
13 | - this.showView('environment', |
14 | - { getModelURL: Y.bind(this.getModelURL, this), |
15 | - /** A simple closure so changes to the value are available.*/ |
16 | - getServiceEndpoints: function() {return self.serviceEndpoints;}, |
17 | - loadService: this.loadService, |
18 | - db: this.db, |
19 | - env: this.env}, |
20 | - {render: true}); |
21 | - } else { |
22 | - /* The current impl makes extensive use of |
23 | - * event handlers which are not being properly rebound |
24 | - * when the view is attached. There is a workable pattern |
25 | - * to enable this but we have to land the basics of this branch |
26 | - * first. |
27 | - */ |
28 | - this.showView('environment', |
29 | - { getModelURL: Y.bind(this.getModelURL, this), |
30 | - /** A simple closure so changes to the value are available.*/ |
31 | - getServiceEndpoints: function() {return self.serviceEndpoints;}, |
32 | - loadService: this.loadService, |
33 | - db: this.db, |
34 | - env: this.env}, |
35 | - { update: false, |
36 | - render: true, |
37 | - callback: function(view) {view.postRender();}}); |
38 | - } |
39 | + var self = this, |
40 | + view = this.getViewInfo('environment'), |
41 | + options = { |
42 | + getModelURL: Y.bind(this.getModelURL, this), |
43 | + /** A simple closure so changes to the value are available.*/ |
44 | + getServiceEndpoints: function() { |
45 | + return self.serviceEndpoints;}, |
46 | + loadService: this.loadService, |
47 | + db: this.db, |
48 | + env: this.env}; |
49 | + |
50 | + this.showView('environment', options, { |
51 | + callback: function() { |
52 | + this.views.environment.instance.postRender(); |
53 | + }, |
54 | + render: true}); |
55 | }, |
56 | |
57 | /** |
58 | |
59 | === modified file 'app/assets/javascripts/d3-components.js' |
60 | --- app/assets/javascripts/d3-components.js 2012-12-14 15:25:55 +0000 |
61 | +++ app/assets/javascripts/d3-components.js 2012-12-20 16:25:28 +0000 |
62 | @@ -34,6 +34,7 @@ |
63 | initializer: function() { |
64 | this.events = Y.mix(this.events, this._defaultEvents, |
65 | false, undefined, 0, true); |
66 | + |
67 | }, |
68 | |
69 | componentBound: function() {}, |
70 | @@ -116,6 +117,9 @@ |
71 | this.events[module.name] = modEvents; |
72 | this.bind(module.name); |
73 | module.componentBound(); |
74 | + |
75 | + // Add Module as an event target of Component |
76 | + this.addTarget(module); |
77 | return this; |
78 | }, |
79 | |
80 | @@ -129,10 +133,51 @@ |
81 | removeModule: function(moduleName) { |
82 | this.unbind(moduleName); |
83 | delete this.events[moduleName]; |
84 | + this.removeTarget(this.modules[moduleName]); |
85 | delete this.modules[moduleName]; |
86 | return this; |
87 | }, |
88 | |
89 | + // Return a resolved handler object in the form |
90 | + // {phase: str, callback: function} |
91 | + _normalizeHandler: function(handler, module, selector) { |
92 | + var result = {}; |
93 | + |
94 | + if (L.isString(handler)) { |
95 | + result.callback = module[handler]; |
96 | + result.phase = 'on'; |
97 | + } |
98 | + |
99 | + if (L.isObject(handler)) { |
100 | + result.phase = handler.phase || 'on'; |
101 | + result.callback = handler.callback; |
102 | + } |
103 | + |
104 | + if (L.isString(result.callback)) { |
105 | + result.callback = module[result.callback]; |
106 | + } |
107 | + |
108 | + if (!result.callback) { |
109 | + console.error('No Event handler for', selector, module.name); |
110 | + return; |
111 | + } |
112 | + if (!L.isFunction(result.callback)) { |
113 | + console.error('Unable to resolve a proper callback for', |
114 | + selector, handler, module.name, result); |
115 | + return; |
116 | + } |
117 | + // Set up binding context for callback. |
118 | + result.context = module; |
119 | + if (handler.context) { |
120 | + if (handler.context === 'component') { |
121 | + result.context = this; |
122 | + } else if (handler.context === 'window') { |
123 | + result.context = Y.one('window'); |
124 | + } |
125 | + } |
126 | + return result; |
127 | + }, |
128 | + |
129 | /** |
130 | * Internal implementation of binding both Module.events.scene and |
131 | * Module.events.yui. |
132 | @@ -163,49 +208,12 @@ |
133 | Y.delegate(name, d3Adaptor, container, selector, context)); |
134 | } |
135 | |
136 | - // Return a resolved handler object in the form |
137 | - // {phase: str, callback: function} |
138 | - function _normalizeHandler(handler, module, selector) { |
139 | - var result = {}; |
140 | - |
141 | - if (L.isString(handler)) { |
142 | - result.callback = module[handler]; |
143 | - result.phase = 'on'; |
144 | - } |
145 | - |
146 | - if (L.isObject(handler)) { |
147 | - result.phase = handler.phase || 'on'; |
148 | - result.callback = handler.callback; |
149 | - } |
150 | - |
151 | - if (L.isString(result.callback)) { |
152 | - result.callback = module[result.callback]; |
153 | - } |
154 | - |
155 | - if (!result.callback) { |
156 | - console.error('No Event handler for', selector, modName); |
157 | - return; |
158 | - } |
159 | - if (!L.isFunction(result.callback)) { |
160 | - console.error('Unable to resolve a proper callback for', |
161 | - selector, handler, modName, result); |
162 | - return; |
163 | - } |
164 | - // Set up binding context for callback. |
165 | - result.context = module; |
166 | - if (handler.context && |
167 | - handler.context === 'component') { |
168 | - result.context = self; |
169 | - } |
170 | - return result; |
171 | - } |
172 | - |
173 | this.unbind(modName); |
174 | |
175 | // Bind 'scene' events |
176 | Y.each(modEvents.scene, function(handlers, selector, sceneEvents) { |
177 | Y.each(handlers, function(handler, trigger) { |
178 | - handler = _normalizeHandler(handler, module, selector); |
179 | + handler = self._normalizeHandler(handler, module, selector); |
180 | if (L.isValue(handler)) { |
181 | _bindEvent(trigger, handler.callback, |
182 | container, selector, handler.context); |
183 | @@ -222,7 +230,7 @@ |
184 | Y.each(['after', 'before', 'on'], function(eventPhase) { |
185 | var resolvedHandler = {}; |
186 | Y.each(modEvents.yui, function(handler, name) { |
187 | - handler = _normalizeHandler(handler, module, name); |
188 | + handler = self._normalizeHandler(handler, module, name); |
189 | if (!handler || handler.phase !== eventPhase) { |
190 | return; |
191 | } |
192 | @@ -235,8 +243,20 @@ |
193 | // this signature: Y.on(event, callback, target, context). |
194 | // For this reason, it is not possible here to just pass the |
195 | // context as third argument. |
196 | - var callback = Y.bind(handler.callback, handler.context); |
197 | - subscriptions.push(Y[eventPhase](name, callback)); |
198 | + var target = self, |
199 | + callback = Y.bind(handler.callback, handler.context); |
200 | + if (Y.Array.indexOf(['windowresize'], name) !== -1) { |
201 | + target = Y; |
202 | + handler.context = null; |
203 | + } else { |
204 | + // (re)Register the event to bubble. |
205 | + self.publish(name, {emitFacade: true}); |
206 | + } |
207 | + console.debug('d3 component yui event binding', target.toString(), |
208 | + eventPhase, name); |
209 | + subscriptions.push( |
210 | + target[eventPhase]( |
211 | + name, callback, handler.context)); |
212 | }); |
213 | } |
214 | }); |
215 | @@ -278,31 +298,63 @@ |
216 | _bindD3Events: function(modName) { |
217 | // Walk each selector for a given module 'name', doing a |
218 | // d3 selection and an 'on' binding. |
219 | - var modEvents = this.events[modName], |
220 | + var self = this, |
221 | + modEvents = this.events[modName], |
222 | owns = Y.Object.owns, |
223 | module; |
224 | + |
225 | if (!modEvents || !modEvents.d3) { |
226 | return; |
227 | } |
228 | modEvents = modEvents.d3; |
229 | module = this.modules[modName]; |
230 | |
231 | - function _normalizeHandler(handler, module) { |
232 | - if (handler && !L.isFunction(handler)) { |
233 | - handler = module[handler]; |
234 | - } |
235 | - return handler; |
236 | - } |
237 | - |
238 | Y.each(modEvents, function(handlers, selector) { |
239 | Y.each(handlers, function(handler, trigger) { |
240 | - handler = _normalizeHandler(handler, module); |
241 | - d3.selectAll(selector).on(trigger, handler); |
242 | + var adapter; |
243 | + handler = self._normalizeHandler(handler, module); |
244 | + // Create an adaptor |
245 | + adapter = function() { |
246 | + var selection = d3.select(this), |
247 | + d = selection.data()[0]; |
248 | + return handler.callback.call(this, d, handler.context); |
249 | + }; |
250 | + d3.selectAll(selector).on(trigger, adapter); |
251 | }); |
252 | }); |
253 | }, |
254 | |
255 | /** |
256 | + * Allow d3 event rebinding after rendering. |
257 | + * |
258 | + **/ |
259 | + bindAllD3Events: function() { |
260 | + var self = this; |
261 | + Y.each(this.modules, function(mod, name) { |
262 | + self._bindD3Events(name); |
263 | + }); |
264 | + }, |
265 | + |
266 | + /** |
267 | + * Register a manual event subscription on |
268 | + * behalf of a module. |
269 | + * |
270 | + * @method recordSubscription |
271 | + * @param {Module} module to record relative to. |
272 | + * @param {Object} YUI event subscription. |
273 | + * @chainable |
274 | + **/ |
275 | + recordSubscription: function(module, subscription) { |
276 | + if (!(module.name in this.events)) { |
277 | + throw 'Unable able to recordSubscription, module not added.'; |
278 | + } |
279 | + if (!subscription) { |
280 | + throw 'Invalid/undefined subscription object cannot be recorded.'; |
281 | + } |
282 | + this.events[module.name].subscriptions.push(subscription); |
283 | + }, |
284 | + |
285 | + /** |
286 | Internal Detail. Called by unbind automatically. |
287 | * D3 events follow a 'slot' like system. Setting the |
288 | * event to null unbinds existing handlers. |
289 | @@ -360,8 +412,9 @@ |
290 | * |
291 | * Called the first time render is invoked. See {render}. |
292 | **/ |
293 | - renderOnce: function() {}, |
294 | - |
295 | + renderOnce: function() { |
296 | + this.all('renderOnce'); |
297 | + }, |
298 | /** |
299 | * Render each module bound to the canvas. The first call to |
300 | * render() will automatically call renderOnce (a noop by default) |
301 | @@ -432,10 +485,17 @@ |
302 | * @chainable |
303 | */ |
304 | update: function() { |
305 | - Y.each(Y.Object.values(this.modules), function(mod) { |
306 | - mod.update(); |
307 | + this.all('update'); |
308 | + return this; |
309 | + }, |
310 | + |
311 | + all: function(methodName) { |
312 | + Y.each(this.modules, function(mod, name) { |
313 | + if (methodName in mod) { |
314 | + console.log('Component', methodName, 'on', name); |
315 | + mod[methodName](); |
316 | + } |
317 | }); |
318 | - return this; |
319 | } |
320 | }, { |
321 | ATTRS: { |
322 | |
323 | === modified file 'app/templates/overview.handlebars' |
324 | --- app/templates/overview.handlebars 2012-12-11 04:11:39 +0000 |
325 | +++ app/templates/overview.handlebars 2012-12-20 16:25:28 +0000 |
326 | @@ -1,5 +1,5 @@ |
327 | <div class="topology"> |
328 | - <div class="crosshatch-background"> |
329 | + <div class="topology-canvas crosshatch-background"> |
330 | <div class="environment-menu" id="service-menu"> |
331 | <div class="triangle"> </div> |
332 | <ul> |
333 | |
334 | === modified file 'app/views/environment.js' |
335 | --- app/views/environment.js 2012-12-11 03:58:03 +0000 |
336 | +++ app/views/environment.js 2012-12-20 16:25:28 +0000 |
337 | @@ -23,7 +23,9 @@ |
338 | { |
339 | initializer: function() { |
340 | console.log('View: Initialized: Env'); |
341 | - this.publish('navigateTo', {preventable: false}); |
342 | + this.publish('navigateTo', { |
343 | + broadcast: true, |
344 | + preventable: false}); |
345 | }, |
346 | |
347 | render: function() { |
348 | @@ -48,18 +50,24 @@ |
349 | container: container}); |
350 | // Bind all the behaviors we need as modules. |
351 | topo.addModule(views.MegaModule); |
352 | + topo.addModule(views.PanZoomModule); |
353 | |
354 | topo.addTarget(this); |
355 | this.topo = topo; |
356 | } |
357 | + |
358 | topo.render(); |
359 | return this; |
360 | }, |
361 | - // XXX: This method is a pass through, |
362 | - // it will be removed when we move to |
363 | - // incremental rendering. |
364 | + |
365 | postRender: function() { |
366 | - this.topo.modules.MegaModule.postRender(); |
367 | + this.topo.attachContainer(); |
368 | + this.topo.fire('rendered'); |
369 | + // Bind d3 events (manually) |
370 | + // this needs to be postRender and |
371 | + // the jiggle in phases has broken |
372 | + // the existing (from change to showView) |
373 | + this.topo.bindAllD3Events(); |
374 | } |
375 | }, { |
376 | ATTRS: {} |
377 | @@ -68,15 +76,15 @@ |
378 | views.environment = EnvironmentView; |
379 | }, '0.1.0', { |
380 | requires: ['juju-templates', |
381 | - 'juju-view-utils', |
382 | - 'juju-models', |
383 | - 'd3', |
384 | - 'd3-components', |
385 | - 'base-build', |
386 | - 'handlebars-base', |
387 | - 'node', |
388 | - 'svg-layouts', |
389 | - 'event-resize', |
390 | - 'slider', |
391 | - 'view'] |
392 | + 'juju-view-utils', |
393 | + 'juju-models', |
394 | + 'd3', |
395 | + 'd3-components', |
396 | + 'base-build', |
397 | + 'handlebars-base', |
398 | + 'node', |
399 | + 'svg-layouts', |
400 | + 'event-resize', |
401 | + 'slider', |
402 | + 'view'] |
403 | }); |
404 | |
405 | === modified file 'app/views/topology/mega.js' |
406 | --- app/views/topology/mega.js 2012-12-11 03:58:03 +0000 |
407 | +++ app/views/topology/mega.js 2012-12-20 16:25:28 +0000 |
408 | @@ -53,12 +53,10 @@ |
409 | .attr('class', 'unit-count hide-count'); |
410 | }} |
411 | }, |
412 | - |
413 | '.rel-label': { |
414 | click: 'relationClick', |
415 | mousemove: 'mousemove' |
416 | }, |
417 | - |
418 | '.topology .crosshatch-background rect:first-child': { |
419 | /** |
420 | * If the user clicks on the background we cancel any active add |
421 | @@ -81,9 +79,6 @@ |
422 | self.backgroundClicked(); |
423 | }} |
424 | }, |
425 | - |
426 | - '#zoom-out-btn': {click: 'zoom_out'}, |
427 | - '#zoom-in-btn': {click: 'zoom_in'}, |
428 | '.graph-list-picker .picker-button': { |
429 | click: 'showGraphListPicker' |
430 | }, |
431 | @@ -132,9 +127,9 @@ |
432 | }, |
433 | d3: { |
434 | '.service': { |
435 | - 'mousedown.addrel': {callback: function(d, self) { |
436 | + 'mousedown.addrel': {callback: function(d, context) { |
437 | var evt = d3.event; |
438 | - self.longClickTimer = Y.later(750, this, function(d, e) { |
439 | + context.longClickTimer = Y.later(750, this, function(d, e) { |
440 | // Provide some leeway for accidental dragging. |
441 | if ((Math.abs(d.x - d.oldX) + Math.abs(d.y - d.oldY)) / |
442 | 2 > 5) { |
443 | @@ -146,25 +141,27 @@ |
444 | d3.event = e; |
445 | |
446 | // Start the process of adding a relation |
447 | - self.addRelationDragStart(d, self); |
448 | + context.addRelationDragStart(d, context); |
449 | }, [d, evt], false); |
450 | }}, |
451 | - 'mouseup.addrel': {callback: function(d, self) { |
452 | + 'mouseup.addrel': {callback: function(d, context) { |
453 | // Cancel the long-click timer if it exists. |
454 | - if (self.longClickTimer) { |
455 | - self.longClickTimer.cancel(); |
456 | + if (context.longClickTimer) { |
457 | + context.longClickTimer.cancel(); |
458 | } |
459 | }} |
460 | } |
461 | }, |
462 | yui: { |
463 | - windowresize: 'setSizesFromViewport' |
464 | + windowresize: { |
465 | + callback: 'setSizesFromViewport', |
466 | + context: 'module'}, |
467 | + rendered: 'renderedHandler' |
468 | } |
469 | }, |
470 | |
471 | initializer: function(options) { |
472 | MegaModule.superclass.constructor.apply(this, arguments); |
473 | - this.publish('navigateTo', {preventable: false}); |
474 | |
475 | // Build a service.id -> BoundingBox map for services. |
476 | this.service_boxes = {}; |
477 | @@ -173,83 +170,12 @@ |
478 | this.set('currentServiceClickAction', 'toggleControlPanel'); |
479 | }, |
480 | |
481 | - render: function() { |
482 | - MegaModule.superclass.render.apply(this, arguments); |
483 | - var container = this.get('container'); |
484 | - container.setHTML(Templates.overview()); |
485 | - this.svg = container.one('.topology'); |
486 | - |
487 | - this.renderOnce(); |
488 | - |
489 | - return this; |
490 | - }, |
491 | - /* |
492 | - * Construct a persistent scene that is managed in update. |
493 | - */ |
494 | - renderOnce: function() { |
495 | - var self = this, |
496 | - container = this.get('container'), |
497 | - height = 600, |
498 | - width = 640, |
499 | - fill = d3.scale.category20(); |
500 | - |
501 | - this.service_scale = d3.scale.log().range([150, 200]); |
502 | - this.service_scale_width = d3.scale.log().range([164, 200]), |
503 | - this.service_scale_height = d3.scale.log().range([64, 100]); |
504 | - this.xscale = d3.scale.linear() |
505 | - .domain([-width / 2, width / 2]) |
506 | - .range([0, width]), |
507 | - this.yscale = d3.scale.linear() |
508 | - .domain([-height / 2, height / 2]) |
509 | - .range([height, 0]); |
510 | - |
511 | - // Create a pan/zoom behavior manager. |
512 | - var zoom = d3.behavior.zoom() |
513 | - .x(this.xscale) |
514 | - .y(this.yscale) |
515 | - .scaleExtent([0.25, 2.0]) |
516 | - .on('zoom', function() { |
517 | - // Keep the slider up to date with the scale on other sorts |
518 | - // of zoom interactions |
519 | - var s = self.slider; |
520 | - s.set('value', Math.floor(d3.event.scale * 100)); |
521 | - self.rescale(vis, d3.event); |
522 | - }); |
523 | - self.zoom = zoom; |
524 | - |
525 | - // Set up the visualization with a pack layout. |
526 | - var vis = d3.select(container.getDOMNode()) |
527 | - .select('.crosshatch-background') |
528 | - .append('svg:svg') |
529 | - .attr('pointer-events', 'all') |
530 | - .attr('width', width) |
531 | - .attr('height', height) |
532 | - .append('svg:g') |
533 | - .call(zoom) |
534 | - // Disable zoom on double click. |
535 | - .on('dblclick.zoom', null) |
536 | - .append('g'); |
537 | - |
538 | - vis.append('svg:rect') |
539 | - .attr('class', 'graph') |
540 | - .attr('fill', 'rgba(255,255,255,0)'); |
541 | - |
542 | - this.vis = vis; |
543 | - this.tree = d3.layout.pack() |
544 | - .size([width, height]) |
545 | - .value(function(d) { |
546 | - return Math.max(d.unit_count, 1); |
547 | - }) |
548 | - .padding(300); |
549 | - |
550 | - this.updateCanvas(); |
551 | - }, |
552 | - |
553 | serviceClick: function(d, context) { |
554 | // Ignore if we clicked outside the actual service node. |
555 | - var container = context.get('container'), |
556 | - mouse_coords = d3.mouse(container.one('svg').getDOMNode()); |
557 | - if (!d.containsPoint(mouse_coords, context.zoom)) { |
558 | + var topo = context.get('component'), |
559 | + container = context.get('container'), |
560 | + mouse_coords = d3.mouse(container.one('svg').getDOMNode()); |
561 | + if (!d.containsPoint(mouse_coords, topo.zoom)) { |
562 | return; |
563 | } |
564 | // Get the current click action |
565 | @@ -321,8 +247,9 @@ |
566 | */ |
567 | updateData: function() { |
568 | //model data |
569 | - var vis = this.vis, |
570 | - db = this.get('component').get('db'), |
571 | + var topo = this.get('component'), |
572 | + vis = topo.vis, |
573 | + db = topo.get('db'), |
574 | relations = db.relations.toArray(), |
575 | services = db.services.map(views.toBoundingBox); |
576 | |
577 | @@ -352,17 +279,33 @@ |
578 | // Nodes are mapped by modelId tuples. |
579 | this.node = vis.selectAll('.service') |
580 | .data(services, function(d) { |
581 | - return d.modelId();}); |
582 | + return d.modelId();}); |
583 | }, |
584 | |
585 | /* |
586 | * Attempt to reuse as much of the existing graph and view models |
587 | * as possible to re-render the graph. |
588 | */ |
589 | - updateCanvas: function() { |
590 | + update: function() { |
591 | var self = this, |
592 | - tree = this.tree, |
593 | - vis = this.vis; |
594 | + topo = this.get('component'), |
595 | + width = topo.get('width'), |
596 | + height = topo.get('height'); |
597 | + |
598 | + if (!this.service_scale) { |
599 | + this.service_scale = d3.scale.log().range([150, 200]); |
600 | + this.service_scale_width = d3.scale.log().range([164, 200]), |
601 | + this.service_scale_height = d3.scale.log().range([64, 100]); |
602 | + } |
603 | + |
604 | + if (!this.tree) { |
605 | + this.tree = d3.layout.pack() |
606 | + .size([width, height]) |
607 | + .value(function(d) { |
608 | + return Math.max(d.unit_count, 1); |
609 | + }) |
610 | + .padding(300); |
611 | + } |
612 | |
613 | //Process any changed data. |
614 | this.updateData(); |
615 | @@ -396,7 +339,6 @@ |
616 | // Clear any state while dragging. |
617 | self.get('container').all('.environment-menu.active') |
618 | .removeClass('active'); |
619 | - self.service_click_actions.toggleControlPanel(null, self); |
620 | self.cancelRelationBuild(); |
621 | |
622 | // Update relation lines for just this service. |
623 | @@ -432,7 +374,6 @@ |
624 | .attr('y2', t[1]); |
625 | rel_group.select('.rel-label') |
626 | .attr('transform', function(d) { |
627 | - // XXX: This has to happen on update, not enter |
628 | return 'translate(' + |
629 | [Math.max(s[0], t[0]) - |
630 | Math.abs((s[0] - t[0]) / 2), |
631 | @@ -459,32 +400,21 @@ |
632 | |
633 | // enter |
634 | node |
635 | - .enter().append('g') |
636 | - .attr('class', function(d) { |
637 | + .enter().append('g') |
638 | + .attr('class', function(d) { |
639 | return (d.subordinate ? 'subordinate ' : '') + 'service'; |
640 | }) |
641 | - .call(drag) |
642 | - .on('mousedown.addrel', function(d) { |
643 | - self.d3Events['.service']['mousedown.addrel'] |
644 | - .call(this, d, self, d3.event); |
645 | - }) |
646 | - .on('mouseup.addrel', function(d) { |
647 | - self.d3Events['.service']['mouseup.addrel'] |
648 | - .call(this, d, self, d3.event); |
649 | - }) |
650 | - .attr('transform', function(d) { |
651 | - return d.translateStr();}); |
652 | + .call(drag) |
653 | + .attr('transform', function(d) { |
654 | + return d.translateStr(); |
655 | + }); |
656 | |
657 | // Update |
658 | this.drawService(node); |
659 | |
660 | // Exit |
661 | node.exit() |
662 | - .call(function(d) { |
663 | - // TODO: update the service_boxes |
664 | - // removing the bound data |
665 | - }) |
666 | - .remove(); |
667 | + .remove(); |
668 | |
669 | function updateLinks() { |
670 | // Enter. |
671 | @@ -509,10 +439,11 @@ |
672 | drawRelationGroup: function() { |
673 | // Add a labelgroup. |
674 | var self = this, |
675 | - g = self.vis.selectAll('g.rel-group') |
676 | - .data(self.rel_pairs, function(r) { |
677 | - return r.modelIds(); |
678 | - }); |
679 | + vis = this.get('component').vis, |
680 | + g = vis.selectAll('g.rel-group') |
681 | + .data(self.rel_pairs, function(r) { |
682 | + return r.modelIds(); |
683 | + }); |
684 | |
685 | var enter = g.enter(); |
686 | |
687 | @@ -534,7 +465,7 @@ |
688 | 'relation'; |
689 | }); |
690 | |
691 | - g.selectAll('rel-label').remove(); |
692 | + g.selectAll('.rel-label').remove(); |
693 | g.selectAll('text').remove(); |
694 | g.selectAll('rect').remove(); |
695 | var label = g.append('g') |
696 | @@ -851,31 +782,6 @@ |
697 | p.scope === 'container'; |
698 | }); |
699 | }, |
700 | - renderSlider: function() { |
701 | - var self = this, |
702 | - value = 100, |
703 | - currentScale = this.get('scale'); |
704 | - // Build a slider to control zoom level |
705 | - if (currentScale) { |
706 | - value = currentScale * 100; |
707 | - } |
708 | - var slider = new Y.Slider({ |
709 | - min: 25, |
710 | - max: 200, |
711 | - value: value |
712 | - }); |
713 | - slider.render('#slider-parent'); |
714 | - slider.after('valueChange', function(evt) { |
715 | - // Don't fire a zoom if there's a zoom event already in progress; |
716 | - // that will run rescale for us. |
717 | - if (d3.event && d3.event.scale && d3.event.translate) { |
718 | - return; |
719 | - } |
720 | - self._fire_zoom((evt.newVal - evt.prevVal) / 100); |
721 | - }); |
722 | - self.slider = slider; |
723 | - }, |
724 | - |
725 | /* |
726 | * Utility method to get a service object from the DB |
727 | * given a BoundingBox. |
728 | @@ -919,9 +825,11 @@ |
729 | * in app.showView(), and in testing, it needs to be called manually, |
730 | * if the test relies on any of this data. |
731 | */ |
732 | - postRender: function() { |
733 | + renderedHandler: function() { |
734 | var container = this.get('container'); |
735 | |
736 | + this.update(); |
737 | + |
738 | // Set the sizes from the viewport. |
739 | this.setSizesFromViewport(); |
740 | |
741 | @@ -932,29 +840,6 @@ |
742 | .setAttribute('x', -width / 2); |
743 | }); |
744 | |
745 | - // Preserve zoom when the scene is updated. |
746 | - var changed = false, |
747 | - currentScale = this.get('scale'), |
748 | - currentTranslate = this.get('translate'); |
749 | - if (currentTranslate && currentTranslate !== this.zoom.translate()) { |
750 | - this.zoom.translate(currentTranslate); |
751 | - changed = true; |
752 | - } |
753 | - if (currentScale && currentScale !== this.zoom.scale()) { |
754 | - this.zoom.scale(currentScale); |
755 | - changed = true; |
756 | - } |
757 | - if (changed) { |
758 | - this._fire_zoom(0); |
759 | - } |
760 | - |
761 | - // Render the slider after the view is attached. |
762 | - // Although there is a .syncUI() method on sliders, it does not |
763 | - // seem to play well with the app framework: the slider will render |
764 | - // the first time, but on navigation away and back, will not |
765 | - // re-render within the view. |
766 | - this.renderSlider(); |
767 | - |
768 | // Chainable method. |
769 | return this; |
770 | }, |
771 | @@ -975,14 +860,16 @@ |
772 | |
773 | addRelationDragStart: function(d, context) { |
774 | // Create a pending drag-line. |
775 | - var dragline = this.vis.append('line') |
776 | - .attr('class', 'relation pending-relation dragline dragging'), |
777 | - self = this; |
778 | + var vis = this.get('component').vis, |
779 | + dragline = vis.append('line') |
780 | + .attr('class', |
781 | + 'relation pending-relation dragline dragging'), |
782 | + self = this; |
783 | |
784 | // Start the line between the cursor and the nearest connector |
785 | // point on the service. |
786 | - var mouse = d3.mouse(Y.one('svg').getDOMNode()); |
787 | - self.cursorBox = views.BoundingBox(); |
788 | + var mouse = d3.mouse(Y.one('.topology svg').getDOMNode()); |
789 | + self.cursorBox = new views.BoundingBox(); |
790 | self.cursorBox.pos = {x: mouse[0], y: mouse[1], w: 0, h: 0}; |
791 | var point = self.cursorBox.getConnectorPair(d); |
792 | dragline.attr('x1', point[0][0]) |
793 | @@ -1091,6 +978,7 @@ |
794 | }, |
795 | |
796 | cancelRelationBuild: function() { |
797 | + var vis = this.get('component').vis; |
798 | if (this.dragline) { |
799 | // Get rid of our drag line |
800 | this.dragline.remove(); |
801 | @@ -1099,7 +987,7 @@ |
802 | this.clickAddRelation = null; |
803 | this.set('currentServiceClickAction', 'toggleControlPanel'); |
804 | this.buildingRelation = false; |
805 | - this.show(this.vis.selectAll('.service')) |
806 | + this.show(vis.selectAll('.service')) |
807 | .classed('selectable-service', false); |
808 | }, |
809 | |
810 | @@ -1128,10 +1016,12 @@ |
811 | */ |
812 | startRelation: function(service) { |
813 | // Set flags on the view that indicate we are building a relation. |
814 | + var vis = this.get('component').vis; |
815 | + |
816 | this.buildingRelation = true; |
817 | this.clickAddRelation = true; |
818 | |
819 | - this.show(this.vis.selectAll('.service')); |
820 | + this.show(vis.selectAll('.service')); |
821 | |
822 | var db = this.get('component').get('db'), |
823 | getServiceEndpoints = this.get('component') |
824 | @@ -1157,7 +1047,7 @@ |
825 | // Rather than two loops this marks |
826 | // all services as selectable and then |
827 | // removes the invalid ones. |
828 | - this.fade(this.vis.selectAll('.service') |
829 | + this.fade(vis.selectAll('.service') |
830 | .classed('selectable-service', true) |
831 | .filter(function(d) { |
832 | return (d.id in invalidRelationTargets && |
833 | @@ -1171,73 +1061,6 @@ |
834 | this.set('currentServiceClickAction', 'ambiguousAddRelationCheck'); |
835 | }, |
836 | |
837 | - |
838 | - /* |
839 | - * Zoom in event handler. |
840 | - */ |
841 | - zoom_out: function(data, context) { |
842 | - var slider = context.slider, |
843 | - val = slider.get('value'); |
844 | - slider.set('value', val - 25); |
845 | - }, |
846 | - |
847 | - /* |
848 | - * Zoom out event handler. |
849 | - */ |
850 | - zoom_in: function(data, context) { |
851 | - var slider = context.slider, |
852 | - val = slider.get('value'); |
853 | - slider.set('value', val + 25); |
854 | - }, |
855 | - |
856 | - /* |
857 | - * Wraper around the actual rescale method for zoom buttons. |
858 | - */ |
859 | - _fire_zoom: function(delta) { |
860 | - var vis = this.vis, |
861 | - zoom = this.zoom, |
862 | - evt = {}; |
863 | - |
864 | - // Build a temporary event that rescale can use of a similar |
865 | - // construction to d3.event. |
866 | - evt.translate = zoom.translate(); |
867 | - evt.scale = zoom.scale() + delta; |
868 | - |
869 | - // Update the scale in our zoom behavior manager to maintain state. |
870 | - zoom.scale(evt.scale); |
871 | - |
872 | - // Update the translate so that we scale from the center |
873 | - // instead of the origin. |
874 | - var rect = vis.select('rect'); |
875 | - evt.translate[0] -= parseInt(rect.attr('width'), 10) / 2 * delta; |
876 | - evt.translate[1] -= parseInt(rect.attr('height'), 10) / 2 * delta; |
877 | - zoom.translate(evt.translate); |
878 | - |
879 | - this.rescale(vis, evt); |
880 | - }, |
881 | - |
882 | - /* |
883 | - * Rescale the visualization on a zoom/pan event. |
884 | - */ |
885 | - rescale: function(vis, evt) { |
886 | - // Make sure we don't scale outside of our bounds. |
887 | - // This check is needed because we're messing with d3's zoom |
888 | - // behavior outside of mouse events (e.g.: with the slider), |
889 | - // and can't trust that zoomExtent will play well. |
890 | - var new_scale = Math.floor(evt.scale * 100); |
891 | - if (new_scale < 25 || new_scale > 200) { |
892 | - evt.scale = this.get('scale'); |
893 | - } |
894 | - // Store the current value of scale so that it can be restored later. |
895 | - this.set('scale', evt.scale); |
896 | - // Store the current value of translate as well, by copying the event |
897 | - // array in order to avoid reference sharing. |
898 | - this.set('translate', evt.translate.slice(0)); |
899 | - vis.attr('transform', 'translate(' + evt.translate + ')' + |
900 | - ' scale(' + evt.scale + ')'); |
901 | - this.updateServiceMenuLocation(); |
902 | - }, |
903 | - |
904 | /* |
905 | * Event handler to show the graph-list picker |
906 | */ |
907 | @@ -1293,14 +1116,17 @@ |
908 | // affect the page size, such as the charm panel, to get out of the |
909 | // way before we compute sizes. Note the |
910 | // "afterPageSizeRecalculation" event at the end of this function. |
911 | - Y.fire('beforePageSizeRecalculation'); |
912 | // start with some reasonable defaults |
913 | - var vis = this.vis, |
914 | - container = this.get('container'), |
915 | - xscale = this.xscale, |
916 | - yscale = this.yscale, |
917 | - svg = container.one('svg'), |
918 | - canvas = container.one('.crosshatch-background'); |
919 | + console.log('setSizesFromViewPort', this, arguments); |
920 | + var topo = this.get('component'), |
921 | + container = this.get('container'), |
922 | + vis = topo.vis, |
923 | + xscale = topo.xScale, |
924 | + yscale = topo.yScale, |
925 | + svg = container.one('svg'), |
926 | + canvas = container.one('.topology-canvas'); |
927 | + |
928 | + topo.fire('beforePageSizeRecalculation'); |
929 | // Get the canvas out of the way so we can calculate the size |
930 | // correctly (the canvas contains the svg). We want it to be the |
931 | // smallest size we accept--no smaller or bigger--or else the |
932 | @@ -1321,25 +1147,26 @@ |
933 | .setStyle('width', dimensions.width); |
934 | |
935 | // Reset the scale parameters |
936 | - this.xscale.domain([-dimensions.width / 2, dimensions.width / 2]) |
937 | + topo.xScale.domain([-dimensions.width / 2, dimensions.width / 2]) |
938 | .range([0, dimensions.width]); |
939 | - this.yscale.domain([-dimensions.height / 2, dimensions.height / 2]) |
940 | + topo.yScale.domain([-dimensions.height / 2, dimensions.height / 2]) |
941 | .range([dimensions.height, 0]); |
942 | |
943 | - this.width = dimensions.width; |
944 | - this.height = dimensions.height; |
945 | - Y.fire('afterPageSizeRecalculation'); |
946 | + topo.set('size', [dimensions.width, dimensions.height]); |
947 | + topo.fire('afterPageSizeRecalculation'); |
948 | }, |
949 | |
950 | /* |
951 | * Update the location of the active service panel |
952 | */ |
953 | updateServiceMenuLocation: function() { |
954 | - var container = this.get('container'), |
955 | - cp = container.one('.environment-menu.active'), |
956 | - service = this.get('active_service'), |
957 | - tr = this.zoom.translate(), |
958 | - z = this.zoom.scale(); |
959 | + var topo = this.get('component'), |
960 | + container = this.get('container'), |
961 | + cp = container.one('.environment-menu.active'), |
962 | + service = this.get('active_service'), |
963 | + tr = topo.get('translate'), |
964 | + z = topo.get('scale'); |
965 | + |
966 | if (service && cp) { |
967 | var cp_width = cp.getClientRect().width, |
968 | menu_left = service.x * z + service.w * z / 2 < |
969 | @@ -1375,9 +1202,10 @@ |
970 | } |
971 | |
972 | // Do not fire unless we're within the service box. |
973 | - var container = context.get('container'), |
974 | + var topo = context.get('component'), |
975 | + container = context.get('container'), |
976 | mouse_coords = d3.mouse(container.one('svg').getDOMNode()); |
977 | - if (!d.containsPoint(mouse_coords, context.zoom)) { |
978 | + if (!d.containsPoint(mouse_coords, topo.zoom)) { |
979 | return; |
980 | } |
981 | |
982 | @@ -1413,9 +1241,10 @@ |
983 | } |
984 | |
985 | // Do not fire if we're within the service box. |
986 | - var container = self.get('container'), |
987 | + var topo = this.get('component'), |
988 | + container = self.get('container'), |
989 | mouse_coords = d3.mouse(container.one('svg').getDOMNode()); |
990 | - if (d.containsPoint(mouse_coords, self.zoom)) { |
991 | + if (d.containsPoint(mouse_coords, topo.zoom)) { |
992 | return; |
993 | } |
994 | var rect = Y.one(this).one('.service-border'); |
995 | @@ -1486,8 +1315,9 @@ |
996 | * View a service |
997 | */ |
998 | show_service: function(m, context) { |
999 | - context.get('component') |
1000 | - .fire('navigateTo', {url: '/service/' + m.get('id') + '/'}); |
1001 | + var topo = context.get('component'); |
1002 | + topo.detachContainer(); |
1003 | + topo.fire('navigateTo', {url: '/service/' + m.get('id') + '/'}); |
1004 | }, |
1005 | |
1006 | /* |
1007 | @@ -1567,11 +1397,12 @@ |
1008 | * create the relation if not. |
1009 | */ |
1010 | ambiguousAddRelationCheck: function(m, view, context) { |
1011 | - var endpoints = view |
1012 | - .get('addRelationStart_possibleEndpoints')[m.id], |
1013 | - container = view.get('container'); |
1014 | + var endpoints = view.get( |
1015 | + 'addRelationStart_possibleEndpoints')[m.id], |
1016 | + container = view.get('container'), |
1017 | + topo = view.get('component'); |
1018 | |
1019 | - if (endpoints.length === 1) { |
1020 | + if (endpoints && endpoints.length === 1) { |
1021 | // Create a relation with the only available endpoint. |
1022 | var ep = endpoints[0], |
1023 | endpoints_item = [ |
1024 | @@ -1629,8 +1460,8 @@ |
1025 | }); |
1026 | |
1027 | // Display the menu at the service endpoint. |
1028 | - var tr = view.zoom.translate(), |
1029 | - z = view.zoom.scale(); |
1030 | + var tr = topo.zoom.translate(), |
1031 | + z = topo.zoom.scale(); |
1032 | menu.setStyle('top', m.y * z + tr[1]); |
1033 | menu.setStyle('left', m.x * z + m.w * z + tr[0]); |
1034 | menu.addClass('active'); |
1035 | @@ -1640,25 +1471,25 @@ |
1036 | }, |
1037 | |
1038 | /* |
1039 | - * Fired when clicking the second service is clicked in the |
1040 | - * add relation flow. |
1041 | - * |
1042 | - * :param endpoints: array of two endpoints, each in the form |
1043 | - * ['service name', { |
1044 | - * name: 'endpoint type', |
1045 | - * role: 'client or server' |
1046 | - * }] |
1047 | - */ |
1048 | + * Fired when clicking the second service is clicked in the |
1049 | + * add relation flow. |
1050 | + * |
1051 | + * :param endpoints: array of two endpoints, each in the form |
1052 | + * ['service name', { |
1053 | + * name: 'endpoint type', |
1054 | + * role: 'client or server' |
1055 | + * }] |
1056 | + */ |
1057 | addRelationEnd: function(endpoints, view, context) { |
1058 | // Redisplay all services |
1059 | view.cancelRelationBuild(); |
1060 | |
1061 | // Get the vis, and links, build the new relation. |
1062 | - var vis = view.vis, |
1063 | - env = view.get('component').get('env'), |
1064 | - db = view.get('component').get('db'), |
1065 | - source = view.get('addRelationStart_service'), |
1066 | - relation_id = 'pending:' + endpoints[0][0] + endpoints[1][0]; |
1067 | + var vis = view.get('component').vis, |
1068 | + env = view.get('component').get('env'), |
1069 | + db = view.get('component').get('db'), |
1070 | + source = view.get('addRelationStart_service'), |
1071 | + relation_id = 'pending:' + endpoints[0][0] + endpoints[1][0]; |
1072 | |
1073 | if (endpoints[0][0] === endpoints[1][0]) { |
1074 | view.set('currentServiceClickAction', 'toggleControlPanel'); |
1075 | @@ -1676,7 +1507,9 @@ |
1076 | |
1077 | // Firing the update event on the db will properly redraw the |
1078 | // graph and reattach events. |
1079 | - db.fire('update'); |
1080 | + //db.fire('update'); |
1081 | + view.get('component').bindAllD3Events(); |
1082 | + view.update(); |
1083 | |
1084 | // Fire event to add relation in juju. |
1085 | // This needs to specify interface in the future. |
1086 | |
1087 | === modified file 'app/views/topology/panzoom.js' |
1088 | --- app/views/topology/panzoom.js 2012-12-11 03:58:03 +0000 |
1089 | +++ app/views/topology/panzoom.js 2012-12-20 16:25:28 +0000 |
1090 | @@ -6,25 +6,174 @@ |
1091 | d3ns = Y.namespace('d3'); |
1092 | |
1093 | /** |
1094 | + * Handle PanZoom within the a Topology. |
1095 | + * |
1096 | + * Emitted events: |
1097 | + * |
1098 | + * rescaled: post-zoom event, after the scene has been rescaled, |
1099 | + * queried object positions should be accurate. |
1100 | + * |
1101 | * @module topology-panzoom |
1102 | * @class PanZoomModule |
1103 | * @namespace views |
1104 | **/ |
1105 | var PanZoomModule = Y.Base.create('PanZoomModule', d3ns.Module, [], { |
1106 | + |
1107 | + events: { |
1108 | + scene: { |
1109 | + '#zoom-out-btn': {click: 'zoom_out'}, |
1110 | + '#zoom-in-btn': {click: 'zoom_in'} |
1111 | + }, |
1112 | + yui: { |
1113 | + zoom: {callback: 'zoomHandler'}, |
1114 | + rendered: {callback: 'renderedHandler'} |
1115 | + } |
1116 | + }, |
1117 | + |
1118 | initializer: function(options) { |
1119 | PanZoomModule.superclass.constructor.apply(this, arguments); |
1120 | - }, |
1121 | - |
1122 | - render: function() { |
1123 | - PanZoomModule.superclass.render.apply(this, arguments); |
1124 | - return this; |
1125 | + this._translate = [0, 0]; |
1126 | + this._scale = 1.0; |
1127 | + }, |
1128 | + |
1129 | + // Handler for 'zoom' event. |
1130 | + zoomHandler: function(evt) { |
1131 | + var s = this.slider, |
1132 | + vis = this.get('component').vis; |
1133 | + |
1134 | + s.set('value', Math.floor(evt.scale * 100)); |
1135 | + this.rescale(vis, evt); |
1136 | + }, |
1137 | + |
1138 | + renderSlider: function() { |
1139 | + var self = this, |
1140 | + topo = this.get('component'), |
1141 | + contianer = topo.get('container'), |
1142 | + value = 100, |
1143 | + currentScale = topo.get('scale'); |
1144 | + |
1145 | + if (self.slider) { |
1146 | + return; |
1147 | + } |
1148 | + // Build a slider to control zoom level |
1149 | + if (currentScale) { |
1150 | + value = currentScale * 100; |
1151 | + } |
1152 | + var slider = new Y.Slider({ |
1153 | + min: 25, |
1154 | + max: 200, |
1155 | + value: value |
1156 | + }); |
1157 | + slider.render('#slider-parent'); |
1158 | + topo.recordSubscription(this, |
1159 | + slider.after('valueChange', function(evt) { |
1160 | + // Don't fire a zoom if there's a zoom event |
1161 | + // already in progress; that will run rescale |
1162 | + // for us. |
1163 | + if (d3.event && d3.event.scale && |
1164 | + d3.event.translate) { |
1165 | + return; |
1166 | + } |
1167 | + self._fire_zoom(( |
1168 | + evt.newVal - evt.prevVal) / 100); |
1169 | + })); |
1170 | + self.slider = slider; |
1171 | }, |
1172 | |
1173 | update: function() { |
1174 | PanZoomModule.superclass.update.apply(this, arguments); |
1175 | return this; |
1176 | + }, |
1177 | + |
1178 | + /* |
1179 | + * Zoom out event handler. |
1180 | + */ |
1181 | + zoom_out: function(data, context) { |
1182 | + var slider = context.slider, |
1183 | + val = slider.get('value'); |
1184 | + slider.set('value', val - 25); |
1185 | + }, |
1186 | + |
1187 | + /* |
1188 | + * Zoom in event handler. |
1189 | + */ |
1190 | + zoom_in: function(data, context) { |
1191 | + var slider = context.slider, |
1192 | + val = slider.get('value'); |
1193 | + slider.set('value', val + 25); |
1194 | + }, |
1195 | + |
1196 | + /* |
1197 | + * Wrapper around the actual rescale method for zoom buttons. |
1198 | + */ |
1199 | + _fire_zoom: function(delta) { |
1200 | + var topo = this.get('component'), |
1201 | + vis = topo.vis, |
1202 | + zoom = topo.zoom, |
1203 | + evt = {}; |
1204 | + |
1205 | + // Build a temporary event that rescale can use of a similar |
1206 | + // construction to d3.event. |
1207 | + evt.translate = zoom.translate(); |
1208 | + evt.scale = zoom.scale() + delta; |
1209 | + |
1210 | + // Update the scale in our zoom behavior manager to maintain state. |
1211 | + zoom.scale(evt.scale); |
1212 | + |
1213 | + // Update the translate so that we scale from the center |
1214 | + // instead of the origin. |
1215 | + var rect = vis.select('rect'); |
1216 | + evt.translate[0] -= parseInt(rect.attr('width'), 10) / 2 * delta; |
1217 | + evt.translate[1] -= parseInt(rect.attr('height'), 10) / 2 * delta; |
1218 | + zoom.translate(evt.translate); |
1219 | + |
1220 | + this.rescale(vis, evt); |
1221 | + }, |
1222 | + |
1223 | + /* |
1224 | + * Rescale the visualization on a zoom/pan event. |
1225 | + */ |
1226 | + rescale: function(vis, evt) { |
1227 | + // Make sure we don't scale outside of our bounds. |
1228 | + // This check is needed because we're messing with d3's zoom |
1229 | + // behavior outside of mouse events (e.g.: with the slider), |
1230 | + // and can't trust that zoomExtent will play well. |
1231 | + var new_scale = Math.floor(evt.scale * 100), |
1232 | + topo = this.get('component'); |
1233 | + |
1234 | + if (new_scale < 25 || new_scale > 200) { |
1235 | + evt.scale = topo.get('scale'); |
1236 | + } |
1237 | + // Store the current value of scale so that it can be restored later. |
1238 | + this._scale = evt.scale; |
1239 | + // Store the current value of translate as well, by copying the event |
1240 | + // array in order to avoid reference sharing. |
1241 | + this._translate = Y.mix(evt.translate); |
1242 | + vis.attr('transform', 'translate(' + evt.translate + ')' + |
1243 | + ' scale(' + evt.scale + ')'); |
1244 | + topo.fire('rescaled'); |
1245 | + }, |
1246 | + |
1247 | + renderedHandler: function(evt) { |
1248 | + // Preserve zoom when the scene is updated. |
1249 | + var topo = this.get('component'), |
1250 | + changed = false, |
1251 | + currentScale = this._scale, |
1252 | + currentTranslate = this._translate; |
1253 | + |
1254 | + this.renderSlider(); |
1255 | + if (currentTranslate && currentTranslate !== topo.get('translate')) { |
1256 | + topo.zoom.translate(currentTranslate); |
1257 | + changed = true; |
1258 | + } |
1259 | + if (currentScale && currentScale !== topo.zoom.scale()) { |
1260 | + topo.zoom.scale(currentScale); |
1261 | + changed = true; |
1262 | + } |
1263 | + if (changed) { |
1264 | + this._fire_zoom(0); |
1265 | + } |
1266 | } |
1267 | - |
1268 | }, { |
1269 | ATTRS: {} |
1270 | |
1271 | |
1272 | === modified file 'app/views/topology/topology.js' |
1273 | --- app/views/topology/topology.js 2012-12-05 05:23:37 +0000 |
1274 | +++ app/views/topology/topology.js 2012-12-20 16:25:28 +0000 |
1275 | @@ -14,6 +14,11 @@ |
1276 | * configuration belong here. If the only shared requirement on shared state |
1277 | * is watch/event like behavior fire an event and place the logic in a module. |
1278 | * |
1279 | + * Emmitted Events: |
1280 | + * |
1281 | + * zoom: When the zoom level of the canvas changes a 'zoom' |
1282 | + * event is fired. Analogous to d3's zoom event. |
1283 | + * |
1284 | * @class Topology |
1285 | * @namespace juju.views |
1286 | **/ |
1287 | @@ -23,6 +28,33 @@ |
1288 | this.options = Y.mix(options || {}); |
1289 | }, |
1290 | |
1291 | + /** |
1292 | + * Called by render, conditionally attach container to the DOM if |
1293 | + * it isn't already. The framework calls this before module |
1294 | + * rendering so that d3 Events will have attached DOM elements. If |
1295 | + * your application doesn't need this behavior feel free to override. |
1296 | + * |
1297 | + * In this case we currently rely on app.showView to do all the |
1298 | + * container management, this only works on a preserved view. |
1299 | + * |
1300 | + * @method attachContainer |
1301 | + * @chainable |
1302 | + **/ |
1303 | + attachContainer: function() { |
1304 | + return this; |
1305 | + }, |
1306 | + |
1307 | + /** |
1308 | + * Remove container from DOM returning container. This |
1309 | + * is explicitly not chainable. |
1310 | + * |
1311 | + * @method detachContainer |
1312 | + **/ |
1313 | + detachContainer: function() { |
1314 | + return; |
1315 | + }, |
1316 | + |
1317 | + |
1318 | renderOnce: function() { |
1319 | var self = this, |
1320 | vis, |
1321 | @@ -31,30 +63,72 @@ |
1322 | container = this.get('container'), |
1323 | templateName = this.options.template || 'overview'; |
1324 | |
1325 | - if (this.svg) { |
1326 | + if (this._templateRendered) { |
1327 | return; |
1328 | } |
1329 | - container.setHTML(views.Templates[templateName]()); |
1330 | + //container.setHTML(views.Templates[templateName]()); |
1331 | // Take the first element. |
1332 | - this.svg = container.one(':first-child'); |
1333 | + this._templateRendered = true; |
1334 | + |
1335 | + // Create a pan/zoom behavior manager. |
1336 | + this.xScale = d3.scale.linear() |
1337 | + .domain([-width / 2, width / 2]) |
1338 | + .range([0, width]); |
1339 | + this.yScale = d3.scale.linear() |
1340 | + .domain([-height / 2, height / 2]) |
1341 | + .range([height, 0]); |
1342 | + |
1343 | + // Include very basic behavior, fire |
1344 | + // yui event for anything more complex. |
1345 | + this.zoom = d3.behavior.zoom() |
1346 | + .x(this.xScale) |
1347 | + .y(this.yScale) |
1348 | + .scaleExtent([0.25, 2.0]) |
1349 | + .on('zoom', function(evt) { |
1350 | + // This will add the d3 properties to the |
1351 | + // eventFacade |
1352 | + self.fire('zoom', d3.event); |
1353 | + }); |
1354 | |
1355 | // Set up the visualization with a pack layout. |
1356 | vis = d3.select(container.getDOMNode()) |
1357 | - .selectAll('.topology-canvas') |
1358 | - .append('svg:svg') |
1359 | - .attr('pointer-events', 'all') |
1360 | - .attr('width', width) |
1361 | - .attr('height', height) |
1362 | - .append('svg:g') |
1363 | - .append('g'); |
1364 | + .selectAll('.topology-canvas') |
1365 | + .append('svg:svg') |
1366 | + .attr('pointer-events', 'all') |
1367 | + .attr('width', width) |
1368 | + .attr('height', height) |
1369 | + .append('svg:g') |
1370 | + .call(this.zoom) |
1371 | + .append('g'); |
1372 | |
1373 | vis.append('svg:rect') |
1374 | - .attr('class', 'graph') |
1375 | - .attr('fill', 'rgba(255,255,255,0)'); |
1376 | + .attr('class', 'graph') |
1377 | + .attr('fill', 'rgba(255,255,255,0)'); |
1378 | |
1379 | this.vis = vis; |
1380 | |
1381 | + // Build out scale and zoom. |
1382 | + // These are defaults, a (Viewport) Module |
1383 | + // can implement policy around them. |
1384 | + this.sizeChangeHandler(); |
1385 | + this.on('sizeChanged', this.sizeChangeHandler); |
1386 | + |
1387 | + Topology.superclass.renderOnce.apply(this, arguments); |
1388 | return this; |
1389 | + }, |
1390 | + |
1391 | + sizeChangeHandler: function() { |
1392 | + var self = this, |
1393 | + width = this.get('width'), |
1394 | + height = this.get('height'); |
1395 | + |
1396 | + // Update the pan/zoom behavior manager. |
1397 | + this.xScale.domain([-width / 2, width / 2]) |
1398 | + .range([0, width]); |
1399 | + this.yScale.domain([-height / 2, height / 2]) |
1400 | + .range([height, 0]); |
1401 | + this.zoom.x(this.xScale) |
1402 | + .y(this.yScale); |
1403 | } |
1404 | |
1405 | }, { |
1406 | @@ -82,9 +156,9 @@ |
1407 | /** |
1408 | * @property {Array} transform |
1409 | **/ |
1410 | - transform: { |
1411 | - getter: function() {return this.get('zoom').transform();}, |
1412 | - setter: function(v) {this.get('zoom').transform(v);} |
1413 | + translate: { |
1414 | + getter: function() {return this.zoom.translate();}, |
1415 | + setter: function(v) {this.zoom.translate(v);} |
1416 | }, |
1417 | |
1418 | width: { |
1419 | |
1420 | === modified file 'test/test_d3_components.js' |
1421 | --- test/test_d3_components.js 2012-12-14 15:25:55 +0000 |
1422 | +++ test/test_d3_components.js 2012-12-20 16:25:28 +0000 |
1423 | @@ -73,7 +73,7 @@ |
1424 | comp.addModule(TestModule); |
1425 | |
1426 | // Test that default bindings work by simulating |
1427 | - Y.fire('cancel'); |
1428 | + comp.fire('cancel'); |
1429 | state.cancelled.should.equal(true); |
1430 | |
1431 | // XXX: While on the plane I determined that things like |
1432 | @@ -84,12 +84,12 @@ |
1433 | state.cancelled = false; |
1434 | comp.removeModule('TestModule'); |
1435 | |
1436 | - Y.fire('cancel'); |
1437 | + comp.fire('cancel'); |
1438 | state.cancelled.should.equal(false); |
1439 | |
1440 | // Adding the module back again doesn't create any issues. |
1441 | comp.addModule(TestModule); |
1442 | - Y.fire('cancel'); |
1443 | + comp.fire('cancel'); |
1444 | state.cancelled.should.equal(true); |
1445 | |
1446 | // Simulated events on DOM handlers better work. |
1447 | @@ -137,7 +137,9 @@ |
1448 | modA.windowResizeHandler = function(evt) { |
1449 | resized = true; |
1450 | }; |
1451 | - modA.events.yui.windowresize = 'windowResizeHandler'; |
1452 | + modA.events.yui.windowresize = { |
1453 | + callback: 'windowResizeHandler', |
1454 | + context: 'window'}; |
1455 | comp.addModule(modA); |
1456 | var subscription = Y.after('windowresize', function(evt) { |
1457 | subscription.detach(); |
1458 | |
1459 | === modified file 'test/test_environment_view.js' |
1460 | --- test/test_environment_view.js 2012-12-14 15:25:55 +0000 |
1461 | +++ test/test_environment_view.js 2012-12-20 16:25:28 +0000 |
1462 | @@ -119,15 +119,18 @@ |
1463 | }); |
1464 | |
1465 | it('must handle the window resize event', function(done) { |
1466 | - var view = new views.environment({container: container, db: db}); |
1467 | + var view = new views.environment({container: container, db: db}), |
1468 | + topo, |
1469 | + beforeResizeEventFired = false; |
1470 | view.render(); |
1471 | - var beforeResizeEventFired = false; |
1472 | - Y.once('beforePageSizeRecalculation', function() { |
1473 | - // This event must be fired by views.MegaModule.setSizesFromViewport. |
1474 | + topo = view.topo; |
1475 | + |
1476 | + topo.once('beforePageSizeRecalculation', function() { |
1477 | + // This event must be fired. |
1478 | beforeResizeEventFired = true; |
1479 | }); |
1480 | - Y.once('afterPageSizeRecalculation', function() { |
1481 | - // This event must be fired by views.MegaModule.setSizesFromViewport. |
1482 | + topo.once('afterPageSizeRecalculation', function() { |
1483 | + // This event must be fired. |
1484 | assert.isTrue(beforeResizeEventFired); |
1485 | done(); |
1486 | }); |
1487 | @@ -257,7 +260,7 @@ |
1488 | view.postRender(); |
1489 | var zoom_in = container.one('#zoom-in-btn'), |
1490 | zoom_out = container.one('#zoom-out-btn'), |
1491 | - module = view.topo.modules.MegaModule, |
1492 | + module = view.topo.modules.PanZoomModule, |
1493 | slider = module.slider, |
1494 | svg = container.one('svg g g'); |
1495 | |
1496 | |
1497 | === modified file 'test/test_topology.js' |
1498 | --- test/test_topology.js 2012-12-11 15:35:25 +0000 |
1499 | +++ test/test_topology.js 2012-12-20 16:25:28 +0000 |
1500 | @@ -72,7 +72,7 @@ |
1501 | topo.render(); |
1502 | |
1503 | // Verify that we have built the default scene. |
1504 | - Y.Lang.isValue(topo.svg).should.equal(true); |
1505 | + Y.Lang.isValue(topo.vis).should.equal(true); |
1506 | }); |
1507 | |
1508 | function createStandardTopo() { |
1509 | @@ -80,6 +80,7 @@ |
1510 | topo = new views.Topology(); |
1511 | topo.setAttrs({container: container, db: db}); |
1512 | topo.addModule(views.MegaModule); |
1513 | + topo.addModule(views.PanZoomModule); |
1514 | return topo; |
1515 | } |
1516 | |
1517 | @@ -88,7 +89,7 @@ |
1518 | topo = createStandardTopo(); |
1519 | topo.render(); |
1520 | // Verify that we have built the default scene. |
1521 | - Y.Lang.isValue(topo.svg).should.equal(true); |
1522 | + Y.Lang.isValue(topo.vis).should.equal(true); |
1523 | }); |
1524 | |
1525 | }); |
1526 | |
1527 | === modified file 'undocumented' |
1528 | --- undocumented 2012-12-11 04:11:39 +0000 |
1529 | +++ undocumented 2012-12-20 16:25:28 +0000 |
1530 | @@ -1,5 +1,5 @@ |
1531 | app/app.js:95 "callback" |
1532 | -app/app.js:569 "callback" |
1533 | +app/app.js:552 "callback" |
1534 | app/store/env.js:64 "on_close" |
1535 | app/store/env.js:69 "on_message" |
1536 | app/store/env.js:181 "status" |
1537 | @@ -71,30 +71,30 @@ |
1538 | app/views/utils.js:647 "get" |
1539 | app/views/utils.js:276 "removeSVGClass" |
1540 | app/views/environment.js:24 "initializer" |
1541 | -app/views/environment.js:29 "render" |
1542 | -app/views/environment.js:61 "postRender" |
1543 | -app/views/charm-panel.js:948 "createInstance" |
1544 | +app/views/environment.js:63 "postRender" |
1545 | +app/views/environment.js:31 "render" |
1546 | +app/views/charm-panel.js:1168 "calculatePanelPosition" |
1547 | app/views/charm-panel.js:476 "initializer" |
1548 | app/views/charm-panel.js:250 "render" |
1549 | -app/views/charm-panel.js:1140 "calculatePanelPosition" |
1550 | -app/views/charm-panel.js:723 "showDescription" |
1551 | +app/views/charm-panel.js:901 "onCharmDeployClicked" |
1552 | +app/views/charm-panel.js:976 "createInstance" |
1553 | +app/views/charm-panel.js:764 "hideDescription" |
1554 | +app/views/charm-panel.js:1022 "setPanel" |
1555 | app/views/charm-panel.js:332 "showConfiguration" |
1556 | -app/views/charm-panel.js:936 "setupOverlay" |
1557 | +app/views/charm-panel.js:1151 "updatePanelPosition" |
1558 | +app/views/charm-panel.js:1213 "killInstance" |
1559 | app/views/charm-panel.js:655 "render" |
1560 | -app/views/charm-panel.js:873 "onCharmDeployClicked" |
1561 | -app/views/charm-panel.js:736 "hideDescription" |
1562 | +app/views/charm-panel.js:1198 "setDefaultSeries" |
1563 | app/views/charm-panel.js:195 "mouseleave" |
1564 | app/views/charm-panel.js:417 "_showErrors" |
1565 | -app/views/charm-panel.js:1179 "getInstance" |
1566 | +app/views/charm-panel.js:651 "initializer" |
1567 | app/views/charm-panel.js:480 "render" |
1568 | -app/views/charm-panel.js:994 "setPanel" |
1569 | -app/views/charm-panel.js:651 "initializer" |
1570 | +app/views/charm-panel.js:751 "showDescription" |
1571 | +app/views/charm-panel.js:725 "_moveTooltip" |
1572 | app/views/charm-panel.js:209 "initializer" |
1573 | -app/views/charm-panel.js:1123 "updatePanelPosition" |
1574 | -app/views/charm-panel.js:700 "_moveTooltip" |
1575 | +app/views/charm-panel.js:964 "setupOverlay" |
1576 | app/views/charm-panel.js:192 "mouseenter" |
1577 | -app/views/charm-panel.js:1170 "setDefaultSeries" |
1578 | -app/views/charm-panel.js:1185 "killInstance" |
1579 | +app/views/charm-panel.js:1207 "getInstance" |
1580 | app/views/charm.js:32 "render" |
1581 | app/views/charm.js:96 "_deployCallback" |
1582 | app/views/charm.js:61 "on_charm_data" |
1583 | @@ -154,60 +154,59 @@ |
1584 | app/views/service.js:23 "resetUnits" |
1585 | app/views/service.js:230 "unexposeService" |
1586 | app/views/service.js:237 "_unexposeServiceCallback" |
1587 | -app/views/topology/mega.js:1441 "subRelBlockMouseLeave" |
1588 | -app/views/topology/mega.js:892 "show" |
1589 | -app/views/topology/mega.js:509 "drawRelationGroup" |
1590 | -app/views/topology/mega.js:1337 "updateServiceMenuLocation" |
1591 | -app/views/topology/mega.js:1187 "zoom_in" |
1592 | -app/views/topology/mega.js:1196 "_fire_zoom" |
1593 | -app/views/topology/mega.js:189 "renderOnce" |
1594 | -app/views/topology/mega.js:574 "drawRelation" |
1595 | -app/views/topology/mega.js:1036 "removeRelation" |
1596 | -app/views/topology/mega.js:1222 "rescale" |
1597 | -app/views/topology/mega.js:265 "serviceDblClick" |
1598 | -app/views/topology/mega.js:1469 "toggleControlPanel" |
1599 | -app/views/topology/mega.js:1093 "cancelRelationBuild" |
1600 | -app/views/topology/mega.js:1519 "destroyService" |
1601 | -app/views/topology/mega.js:1073 "removeRelationConfirm" |
1602 | -app/views/topology/mega.js:904 "fade" |
1603 | -app/views/topology/mega.js:1254 "hideGraphListPicker" |
1604 | -app/views/topology/mega.js:248 "serviceClick" |
1605 | -app/views/topology/mega.js:590 "drawService" |
1606 | -app/views/topology/mega.js:271 "relationClick" |
1607 | -app/views/topology/mega.js:854 "renderSlider" |
1608 | -app/views/topology/mega.js:165 "initializer" |
1609 | -app/views/topology/mega.js:489 "updateLinks" |
1610 | -app/views/topology/mega.js:1048 "_removeRelationCallback" |
1611 | -app/views/topology/mega.js:1652 "addRelationEnd" |
1612 | -app/views/topology/mega.js:1432 "subRelBlockMouseEnter" |
1613 | -app/views/topology/mega.js:362 "updateCanvas" |
1614 | -app/views/topology/mega.js:1409 "serviceMouseLeave" |
1615 | -app/views/topology/mega.js:1244 "showGraphListPicker" |
1616 | -app/views/topology/mega.js:322 "updateData" |
1617 | -app/views/topology/mega.js:176 "render" |
1618 | -app/views/topology/mega.js:97 "callback" |
1619 | -app/views/topology/mega.js:976 "addRelationDragStart" |
1620 | -app/views/topology/mega.js:883 "serviceForBox" |
1621 | -app/views/topology/mega.js:1496 "destroyServiceConfirm" |
1622 | -app/views/topology/mega.js:1178 "zoom_out" |
1623 | -app/views/topology/mega.js:1691 "_addRelationCallback" |
1624 | -app/views/topology/mega.js:999 "addRelationDrag" |
1625 | -app/views/topology/mega.js:1558 "addRelationStart" |
1626 | -app/views/topology/mega.js:965 "addRelation" |
1627 | -app/views/topology/mega.js:823 "processRelations" |
1628 | -app/views/topology/mega.js:1291 "setSizesFromViewport" |
1629 | -app/views/topology/mega.js:1370 "serviceMouseEnter" |
1630 | -app/views/topology/mega.js:812 "processRelation" |
1631 | -app/views/topology/mega.js:1488 "show_service" |
1632 | -app/views/topology/mega.js:922 "postRender" |
1633 | -app/views/topology/mega.js:898 "hide" |
1634 | -app/views/topology/mega.js:1017 "addRelationDragEnd" |
1635 | -app/views/topology/mega.js:1569 "ambiguousAddRelationCheck" |
1636 | -app/views/topology/mega.js:1528 "_destroyCallback" |
1637 | -app/views/topology/mega.js:848 "subordinateRelationsForService" |
1638 | -app/views/topology/panzoom.js:14 "initializer" |
1639 | -app/views/topology/panzoom.js:18 "render" |
1640 | -app/views/topology/panzoom.js:23 "update" |
1641 | +app/views/topology/mega.js:810 "fade" |
1642 | +app/views/topology/mega.js:173 "serviceClick" |
1643 | +app/views/topology/mega.js:935 "_removeRelationCallback" |
1644 | +app/views/topology/mega.js:1067 "showGraphListPicker" |
1645 | +app/views/topology/mega.js:1523 "_addRelationCallback" |
1646 | +app/views/topology/mega.js:419 "updateLinks" |
1647 | +app/views/topology/mega.js:861 "addRelationDragStart" |
1648 | +app/views/topology/mega.js:850 "addRelation" |
1649 | +app/views/topology/mega.js:828 "renderedHandler" |
1650 | +app/views/topology/mega.js:1077 "hideGraphListPicker" |
1651 | +app/views/topology/mega.js:248 "updateData" |
1652 | +app/views/topology/mega.js:789 "serviceForBox" |
1653 | +app/views/topology/mega.js:1325 "destroyServiceConfirm" |
1654 | +app/views/topology/mega.js:1260 "subRelBlockMouseEnter" |
1655 | +app/views/topology/mega.js:505 "drawRelation" |
1656 | +app/views/topology/mega.js:1357 "_destroyCallback" |
1657 | +app/views/topology/mega.js:754 "processRelations" |
1658 | +app/views/topology/mega.js:1297 "toggleControlPanel" |
1659 | +app/views/topology/mega.js:1482 "addRelationEnd" |
1660 | +app/views/topology/mega.js:743 "processRelation" |
1661 | +app/views/topology/mega.js:1316 "show_service" |
1662 | +app/views/topology/mega.js:191 "serviceDblClick" |
1663 | +app/views/topology/mega.js:798 "show" |
1664 | +app/views/topology/mega.js:439 "drawRelationGroup" |
1665 | +app/views/topology/mega.js:904 "addRelationDragEnd" |
1666 | +app/views/topology/mega.js:886 "addRelationDrag" |
1667 | +app/views/topology/mega.js:289 "update" |
1668 | +app/views/topology/mega.js:1269 "subRelBlockMouseLeave" |
1669 | +app/views/topology/mega.js:1196 "serviceMouseEnter" |
1670 | +app/views/topology/mega.js:960 "removeRelationConfirm" |
1671 | +app/views/topology/mega.js:1387 "addRelationStart" |
1672 | +app/views/topology/mega.js:1398 "ambiguousAddRelationCheck" |
1673 | +app/views/topology/mega.js:923 "removeRelation" |
1674 | +app/views/topology/mega.js:980 "cancelRelationBuild" |
1675 | +app/views/topology/mega.js:92 "callback" |
1676 | +app/views/topology/mega.js:521 "drawService" |
1677 | +app/views/topology/mega.js:804 "hide" |
1678 | +app/views/topology/mega.js:163 "initializer" |
1679 | +app/views/topology/mega.js:197 "relationClick" |
1680 | +app/views/topology/mega.js:1348 "destroyService" |
1681 | +app/views/topology/mega.js:1161 "updateServiceMenuLocation" |
1682 | +app/views/topology/mega.js:1114 "setSizesFromViewport" |
1683 | +app/views/topology/mega.js:1236 "serviceMouseLeave" |
1684 | +app/views/topology/mega.js:779 "subordinateRelationsForService" |
1685 | +app/views/topology/panzoom.js:83 "update" |
1686 | +app/views/topology/panzoom.js:40 "zoomHandler" |
1687 | +app/views/topology/panzoom.js:100 "zoom_in" |
1688 | +app/views/topology/panzoom.js:136 "rescale" |
1689 | +app/views/topology/panzoom.js:109 "_fire_zoom" |
1690 | +app/views/topology/panzoom.js:33 "initializer" |
1691 | +app/views/topology/panzoom.js:157 "renderedHandler" |
1692 | +app/views/topology/panzoom.js:91 "zoom_out" |
1693 | +app/views/topology/panzoom.js:48 "renderSlider" |
1694 | app/views/topology/relation.js:18 "render" |
1695 | app/views/topology/relation.js:23 "update" |
1696 | app/views/topology/relation.js:14 "initializer" |
1697 | @@ -216,14 +215,15 @@ |
1698 | app/views/topology/service.js:26 "initializer" |
1699 | app/views/topology/service.js:50 "update" |
1700 | app/views/topology/service.js:39 "_scaleLayout" |
1701 | -app/views/topology/topology.js:26 "renderOnce" |
1702 | -app/views/topology/topology.js:87 "setter" |
1703 | -app/views/topology/topology.js:95 "getter" |
1704 | -app/views/topology/topology.js:91 "getter" |
1705 | -app/views/topology/topology.js:80 "setter" |
1706 | -app/views/topology/topology.js:86 "getter" |
1707 | -app/views/topology/topology.js:21 "initializer" |
1708 | -app/views/topology/topology.js:79 "getter" |
1709 | +app/views/topology/topology.js:161 "setter" |
1710 | +app/views/topology/topology.js:26 "initializer" |
1711 | +app/views/topology/topology.js:154 "setter" |
1712 | +app/views/topology/topology.js:165 "getter" |
1713 | +app/views/topology/topology.js:120 "sizeChangeHandler" |
1714 | +app/views/topology/topology.js:160 "getter" |
1715 | +app/views/topology/topology.js:153 "getter" |
1716 | +app/views/topology/topology.js:169 "getter" |
1717 | +app/views/topology/topology.js:58 "renderOnce" |
1718 | app/views/topology/viewport.js:33 "initializer" |
1719 | app/views/topology/viewport.js:66 "update" |
1720 | app/views/topology/viewport.js:37 "render" |
Reviewers: mp+140671_ code.launchpad. net,
Message:
Please take a look.
Description:
Panzoom Module
This branch breaks out the first module
from Mega into a more module unit. While this
module is small a number of changes occur to make
this happen. The framework underwent some improvemnts,
changes around interaction with App and view replacement
occured, event bindings had to be updated. A pattern for
cross module event firing was established.
In future modules, the topo/component fires the events and
modules are bubble targets.
https:/ /code.launchpad .net/~bcsaller/ juju-gui/ topology- panzoom/ +merge/ 140671
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/6971045/
Affected files: assets/ javascripts/ d3-components. js templates/ overview. handlebars views/environme nt.js views/topology/ mega.js views/topology/ panzoom. js views/topology/ topology. js test_d3_ components. js test_environmen t_view. js test_topology. js
[revision details]
app/app.js
app/
app/
app/
app/
app/
app/
test/
test/
test/
undocumented