Merge lp:~bcsaller/juju-gui/component-framework into lp:juju-gui/experimental
- component-framework
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 238 |
Proposed branch: | lp:~bcsaller/juju-gui/component-framework |
Merge into: | lp:juju-gui/experimental |
Diff against target: |
609 lines (+578/-0) 4 files modified
app/assets/javascripts/d3-components.js (+401/-0) app/modules.js (+3/-0) test/index.html (+1/-0) test/test_d3_components.js (+173/-0) |
To merge this branch: | bzr merge lp:~bcsaller/juju-gui/component-framework |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+133391@code.launchpad.net |
Commit message
Description of the change
Micro Framework for Environment View
A subclass of Component will container 1 or more modules
which implement their own areas of application concern.
The pattern is that we can declaratively define event listeners
and respond to those across modules. For example A canvas click
might fire an application event 'clickedCanvas' which each module
can have subscribers for that do things like close menus and remove
drag lines, etc.
Benjamin Saller (bcsaller) wrote : | # |
Thiago Veronezi (tveronezi) wrote : | # |
Hi Ben!
Thanks for this branch. It seems you had a lot of fun doing this. :O)
This is a review for your main file. I still need to look at the tests.
https:/
File app/assets/
https:/
app/assets/
Y.Base.
I like this code style. You could the same with the class above. I
mean...
var Module = ...
https:/
app/assets/
this.after(
You could remove the commented code.
https:/
app/assets/
I don't like setting a value to one function parameter.
https:/
app/assets/
module;
Funny indentation
https:/
app/assets/
module.events;
Usually we have only one var per closure. Maybe you can declare the
variables in the first line and then attribute values to it here.
https:/
app/assets/
It seems self is never used.
https:/
app/assets/
'object') {
Null is also an object...
https:/
Maybe you could use "Lang" for all the cases here.
https:/
app/assets/
If that is an error, you could throw the exception.
https:/
app/assets/
If that is an error, you could throw the exception.
https:/
app/assets/
modEvents.scene) {
YUI utility?
https:/
app/assets/
var declarations should be the first statement in the function body. I
dont know why lint didn't catch this.
http://
https:/
app/assets/
The blocks of scope in js are crazy. The filtered variable ...
Thiago Veronezi (tveronezi) wrote : | # |
https:/
File test/test_
https:/
test/test_
Nothing to assert?
Madison Scott-Clary (makyo) wrote : | # |
This all looks pretty good to me, with a few little
minors/
in, for sure.
https:/
File app/assets/
https:/
app/assets/
modules implementing various portions
Minor - should be 'collects', I think? Was just confusing.
https:/
app/assets/
around updating the interactive portions
Minor - 'updating'
https:/
app/assets/
Minor - Docs don't match method signature.
https:/
app/assets/
Curious about the necessity for not doing Y.clone(
Clarity?
https:/
app/assets/
phase is set and carefully handled, but not used. Is this a future
implementation thing?
https:/
app/assets/
includes phase (before, on, after)
From above, assuming this is the future use of phase?
Benjamin Saller (bcsaller) wrote : | # |
Thank you both for the review feedback. I've made the changes and will
propose again after getting these comments out.
The most significant change is the expanded handling around event
phases.
https:/
File app/assets/
https:/
app/assets/
Y.Base.
On 2012/11/08 13:33:35, thiago wrote:
> I like this code style. You could the same with the class above. I
mean...
> var Module = ...
The linter complained that I didn't combine the var decls into one set,
I prefer the other style as well. I'll see if I cna get lint to shut up.
https:/
app/assets/
this.after(
On 2012/11/08 13:33:35, thiago wrote:
> You could remove the commented code.
Done.
https:/
app/assets/
On 2012/11/08 18:49:46, matthew.scott wrote:
> Minor - Docs don't match method signature.
Done.
https:/
app/assets/
On 2012/11/08 13:33:35, thiago wrote:
> I don't like setting a value to one function parameter.
I agree its questionable, questionable but common. Still, I can alter
it.
https:/
app/assets/
module;
On 2012/11/08 13:33:35, thiago wrote:
> Funny indentation
fixjsstyle must have done this, fixed.
https:/
app/assets/
module.events;
On 2012/11/08 13:33:35, thiago wrote:
> Usually we have only one var per closure. Maybe you can declare the
variables in
> the first line and then attribute values to it here.
Done.
https:/
app/assets/
On 2012/11/08 18:49:46, matthew.scott wrote:
> Curious about the necessity for not doing Y.clone(
here.
> Clarity?
Its not needed now I think, it was because the Module was returning its
real dict and so there was a Module level instance with shared state
being used. This could leave subscriptions bound to all instances.
Module was changed to do the shallow copy of events in its init method
and this was removed.
https:/
app/assets/
On 2012/11/08 13:33:35, thiago wrote:
> It seems self is never used.
Done.
https:/
- 227. By Benjamin Saller
-
use yui methods for iteration more places, support phase event bindings for custom events
- 228. By Benjamin Saller
-
lint
Benjamin Saller (bcsaller) wrote : | # |
Please take a look.
- 229. By Benjamin Saller
-
basic assertion around addModule
Madison Scott-Clary (makyo) wrote : | # |
Thanks for the changes! Looks good to me.
Thiago Veronezi (tveronezi) wrote : | # |
On 2012/11/09 18:07:59, matthew.scott wrote:
> Thanks for the changes! Looks good to me.
Thanks, Ben. It looks good to me too.
Benjamin Saller (bcsaller) wrote : | # |
*** Submitted:
Micro Framework for Environment View
A subclass of Component will container 1 or more modules
which implement their own areas of application concern.
The pattern is that we can declaratively define event listeners
and respond to those across modules. For example A canvas click
might fire an application event 'clickedCanvas' which each module
can have subscribers for that do things like close menus and remove
drag lines, etc.
R=thiago, matthew.scott, benjamin.saller
CC=
https:/
Preview Diff
1 | === added file 'app/assets/javascripts/d3-components.js' |
2 | --- app/assets/javascripts/d3-components.js 1970-01-01 00:00:00 +0000 |
3 | +++ app/assets/javascripts/d3-components.js 2012-11-09 14:20:28 +0000 |
4 | @@ -0,0 +1,401 @@ |
5 | +'use strict'; |
6 | + |
7 | +/** |
8 | + * Provides a declarative structure around interactive D3 |
9 | + * applications. |
10 | + * |
11 | + * @module d3-components |
12 | + **/ |
13 | + |
14 | +YUI.add('d3-components', function(Y) { |
15 | + var ns = Y.namespace('d3'), |
16 | + L = Y.Lang; |
17 | + |
18 | + var Module = Y.Base.create('Module', Y.Base, [], { |
19 | + /** |
20 | + * @property events |
21 | + * @type {object} |
22 | + **/ |
23 | + events: { |
24 | + scene: {}, |
25 | + d3: {}, |
26 | + yui: {} |
27 | + }, |
28 | + |
29 | + initializer: function() { |
30 | + this.events = Y.merge(this.events); |
31 | + } |
32 | + }, { |
33 | + ATTRS: { |
34 | + component: {}, |
35 | + options: {}, |
36 | + container: {getter: function() { |
37 | + return this.get('component').get('container');}} |
38 | + } |
39 | + }); |
40 | + ns.Module = Module; |
41 | + |
42 | + |
43 | + var Component = Y.Base.create('Component', Y.Base, [], { |
44 | + /** |
45 | + * @class Component |
46 | + * |
47 | + * Component collects modules implementing various portions of an |
48 | + * applications functionality in a declarative way. It is designed to allow |
49 | + * both a cleaner separation of concerns and the ability to reuse the |
50 | + * component in different ways. |
51 | + * |
52 | + * Component accomplishes these goals by: |
53 | + * - Control how events are bound and unbound. |
54 | + * - Providing patterns for update data cleanly. |
55 | + * - Providing suggestions around updating the interactive portions |
56 | + * of the application. |
57 | + * |
58 | + * @constructor |
59 | + **/ |
60 | + initializer: function() { |
61 | + this.modules = {}; |
62 | + this.events = {}; |
63 | + }, |
64 | + |
65 | + /** |
66 | + * @method addModule |
67 | + * @chainable |
68 | + * @param {Module} ModClassOrInstance bound will be to this. |
69 | + * @param {Object} options dict of options set as options attribute on |
70 | + * module. |
71 | + * |
72 | + * Add a Module to this Component. This will bind its events and set up all |
73 | + * needed event subscriptions. Modules can return three sets of events |
74 | + * that will be bound in different ways |
75 | + * |
76 | + * - scene: {selector: event-type: handlerName} -> YUI styled event |
77 | + * delegation |
78 | + * - d3 {selector: event-type: handlerName} -> Bound using |
79 | + * specialized d3 event handling |
80 | + * - yui {event-type: handlerName} -> collection of global and custom |
81 | + * events the module reacts to. |
82 | + **/ |
83 | + |
84 | + addModule: function(ModClassOrInstance, options) { |
85 | + var config = options || {}, |
86 | + module = ModClassOrInstance, |
87 | + modEvents; |
88 | + |
89 | + if (!(ModClassOrInstance instanceof Module)) { |
90 | + module = new ModClassOrInstance(); |
91 | + } |
92 | + module.setAttrs({ |
93 | + component: this, |
94 | + options: config}); |
95 | + |
96 | + this.modules[module.name] = module; |
97 | + |
98 | + modEvents = module.events; |
99 | + this.events[module.name] = modEvents; |
100 | + this.bind(module.name); |
101 | + return this; |
102 | + }, |
103 | + |
104 | + /** |
105 | + * @method removeModule |
106 | + * @param {String} moduleName Module name to remove. |
107 | + * @chainable |
108 | + **/ |
109 | + removeModule: function(moduleName) { |
110 | + this.unbind(moduleName); |
111 | + delete this.events[moduleName]; |
112 | + delete this.modules[moduleName]; |
113 | + return this; |
114 | + }, |
115 | + |
116 | + /** |
117 | + * Internal implementation of |
118 | + * binding both |
119 | + * Module.events.scene and |
120 | + * Module.events.yui. |
121 | + **/ |
122 | + _bindEvents: function(modName) { |
123 | + var self = this, |
124 | + modEvents = this.events[modName], |
125 | + module = this.modules[modName], |
126 | + owns = Y.Object.owns, |
127 | + container = this.get('container'), |
128 | + subscriptions = [], |
129 | + handlers, |
130 | + handler; |
131 | + |
132 | + function _bindEvent(name, handler, container, selector, context) { |
133 | + // Adapt between d3 events and YUI delegates. |
134 | + var d3Adaptor = function(evt) { |
135 | + var selection = d3.select(evt.currentTarget.getDOMNode()), |
136 | + d = selection.data()[0]; |
137 | + // This is a minor violation (extension) |
138 | + // of the interface, but suits us well. |
139 | + d3.event = evt; |
140 | + return handler.call( |
141 | + evt.currentTarget.getDOMNode(), d, context); |
142 | + }; |
143 | + |
144 | + subscriptions.push( |
145 | + Y.delegate(name, d3Adaptor, container, selector, context)); |
146 | + } |
147 | + |
148 | + // Return a resolved handler object in the form |
149 | + // {phase: str, callback: function} |
150 | + function _normalizeHandler(handler, module, selector) { |
151 | + var result = {}; |
152 | + |
153 | + if (L.isString(handler)) { |
154 | + result.callback = module[handler]; |
155 | + result.phase = 'on'; |
156 | + } |
157 | + |
158 | + if (L.isObject(handler)) { |
159 | + result.phase = handler.phase || 'on'; |
160 | + result.callback = handler.callback; |
161 | + } |
162 | + |
163 | + if (L.isString(result.callback)) { |
164 | + result.callback = module[result.callback]; |
165 | + } |
166 | + |
167 | + if (!result.callback) { |
168 | + console.error('No Event handler for', selector, modName); |
169 | + return; |
170 | + } |
171 | + if (!L.isFunction(result.callback)) { |
172 | + console.error('Unable to resolve a proper callback for', |
173 | + selector, handler, modName, result); |
174 | + return; |
175 | + } |
176 | + return result; |
177 | + } |
178 | + |
179 | + this.unbind(modName); |
180 | + |
181 | + // Bind 'scene' events |
182 | + Y.each(modEvents.scene, function(handlers, selector, sceneEvents) { |
183 | + Y.each(handlers, function(handler, trigger) { |
184 | + handler = _normalizeHandler(handler, module, selector); |
185 | + if (L.isValue(handler)) { |
186 | + _bindEvent(trigger, handler.callback, container, selector, self); |
187 | + } |
188 | + }); |
189 | + }); |
190 | + |
191 | + // Bind 'yui' custom/global subscriptions |
192 | + // yui: {str: str_or_function} |
193 | + // TODO {str: str/func/obj} |
194 | + // where object includes phase (before, on, after) |
195 | + if (modEvents.yui) { |
196 | + // Resolve any 'string' handlers to methods on module. |
197 | + Y.each(['after', 'before', 'on'], function(eventPhase) { |
198 | + var resolvedHandler = {}; |
199 | + Y.each(modEvents.yui, function(handler, name) { |
200 | + handler = _normalizeHandler(handler, module); |
201 | + if (!handler || handler.phase !== eventPhase) { |
202 | + return; |
203 | + } |
204 | + resolvedHandler[name] = handler.callback; |
205 | + }, this); |
206 | + // Bind resolved event handlers as a group. |
207 | + if (Y.Object.keys(resolvedHandler).length) { |
208 | + subscriptions.push(Y[eventPhase](resolvedHandler)); |
209 | + } |
210 | + }); |
211 | + } |
212 | + return subscriptions; |
213 | + }, |
214 | + |
215 | + /** |
216 | + * @method bind |
217 | + * |
218 | + * Internal. Called automatically by addModule. |
219 | + **/ |
220 | + bind: function(moduleName) { |
221 | + var eventSet = this.events, |
222 | + filtered = {}; |
223 | + |
224 | + if (moduleName) { |
225 | + filtered[moduleName] = eventSet[moduleName]; |
226 | + eventSet = filtered; |
227 | + } |
228 | + |
229 | + Y.each(Y.Object.keys(eventSet), function(name) { |
230 | + this.events[name].subscriptions = this._bindEvents(name); |
231 | + }, this); |
232 | + return this; |
233 | + }, |
234 | + |
235 | + /** |
236 | + * Specialized handling of events only found in d3. |
237 | + * This is again an internal implementation detail. |
238 | + * |
239 | + * Its worth noting that d3 events don't use a delegate pattern |
240 | + * and thus must be bound to nodes present in a selection. |
241 | + * For this reason binding d3 events happens after render cycles. |
242 | + * |
243 | + * @method _bindD3Events |
244 | + * @param {String} modName Module name. |
245 | + **/ |
246 | + _bindD3Events: function(modName) { |
247 | + // Walk each selector for a given module 'name', doing a |
248 | + // d3 selection and an 'on' binding. |
249 | + var modEvents = this.events[modName], |
250 | + owns = Y.Object.owns, |
251 | + module; |
252 | + if (!modEvents || !modEvents.d3) { |
253 | + return; |
254 | + } |
255 | + modEvents = modEvents.d3; |
256 | + module = this.modules[modName]; |
257 | + |
258 | + function _normalizeHandler(handler, module) { |
259 | + if (handler && !L.isFunction(handler)) { |
260 | + handler = module[handler]; |
261 | + } |
262 | + return handler; |
263 | + } |
264 | + |
265 | + Y.each(modEvents, function(handlers, selector) { |
266 | + Y.each(handlers, function(handler, trigger) { |
267 | + handler = _normalizeHandler(handler, module); |
268 | + d3.selectAll(selector).on(trigger, handler); |
269 | + }); |
270 | + }); |
271 | + }, |
272 | + |
273 | + /** |
274 | + * @method _unbindD3Events |
275 | + * |
276 | + * Internal Detail. Called by unbind automatically. |
277 | + * D3 events follow a 'slot' like system. Setting the |
278 | + * event to null unbinds existing handlers. |
279 | + **/ |
280 | + _unbindD3Events: function(modName) { |
281 | + var modEvents = this.events[modName], |
282 | + owns = Y.Object.owns, |
283 | + module; |
284 | + |
285 | + if (!modEvents || !modEvents.d3) { |
286 | + return; |
287 | + } |
288 | + modEvents = modEvents.d3; |
289 | + module = this.modules[modName]; |
290 | + |
291 | + Y.each(modEvents, function(handlers, selector) { |
292 | + Y.each(handlers, function(handler, trigger) { |
293 | + d3.selectAll(selector).on(trigger, null); |
294 | + }); |
295 | + }); |
296 | + }, |
297 | + |
298 | + /** |
299 | + * @method unbind |
300 | + * Internal. Called automatically by removeModule. |
301 | + **/ |
302 | + unbind: function(moduleName) { |
303 | + var eventSet = this.events, |
304 | + filtered = {}; |
305 | + |
306 | + function _unbind(modEvents) { |
307 | + Y.each(modEvents.subscriptions, function(handler) { |
308 | + if (handler) { |
309 | + handler.detach(); |
310 | + } |
311 | + }); |
312 | + delete modEvents.subscriptions; |
313 | + } |
314 | + |
315 | + if (moduleName) { |
316 | + filtered[moduleName] = eventSet[moduleName]; |
317 | + eventSet = filtered; |
318 | + } |
319 | + Y.each(Y.Object.values(eventSet), _unbind, this); |
320 | + // Remove any d3 subscriptions as well. |
321 | + this._unbindD3Events(); |
322 | + |
323 | + return this; |
324 | + }, |
325 | + |
326 | + /** |
327 | + * @method render |
328 | + * @chainable |
329 | + * |
330 | + * Render each module bound to the canvas |
331 | + */ |
332 | + render: function() { |
333 | + var self = this; |
334 | + function renderAndBind(module, name) { |
335 | + if (module && module.render) { |
336 | + module.render(); |
337 | + } |
338 | + self._bindD3Events(name); |
339 | + } |
340 | + |
341 | + // If the container isn't bound to the DOM |
342 | + // do so now. |
343 | + this.attachContainer(); |
344 | + // Render modules. |
345 | + Y.each(this.modules, renderAndBind, this); |
346 | + return this; |
347 | + }, |
348 | + |
349 | + /** |
350 | + * @method attachContainer |
351 | + * @chainable |
352 | + * |
353 | + * Called by render, conditionally attach container to the DOM if |
354 | + * it isn't already. The framework calls this before module |
355 | + * rendering so that d3 Events will have attached DOM elements. If |
356 | + * your application doesn't need this behavior feel free to override. |
357 | + **/ |
358 | + attachContainer: function() { |
359 | + var container = this.get('container'); |
360 | + if (container && !container.inDoc()) { |
361 | + Y.one('body').append(container); |
362 | + } |
363 | + return this; |
364 | + }, |
365 | + |
366 | + /** |
367 | + * @method detachContainer |
368 | + * |
369 | + * Remove container from DOM returning container. This |
370 | + * is explicitly not chainable. |
371 | + **/ |
372 | + detachContainer: function() { |
373 | + var container = this.get('container'); |
374 | + if (container.inDoc()) { |
375 | + container.remove(); |
376 | + } |
377 | + return container; |
378 | + }, |
379 | + |
380 | + /** |
381 | + * |
382 | + * @method update |
383 | + * @chainable |
384 | + * |
385 | + * Update the data for each module |
386 | + * see also the dataBinding event hookup |
387 | + */ |
388 | + update: function() { |
389 | + Y.each(Y.Object.values(this.modules), function(mod) { |
390 | + mod.update(); |
391 | + }); |
392 | + return this; |
393 | + } |
394 | + }, { |
395 | + ATTRS: { |
396 | + container: {} |
397 | + } |
398 | + |
399 | + }); |
400 | + ns.Component = Component; |
401 | +}, '0.1', { |
402 | + 'requires': ['d3', |
403 | + 'base', |
404 | + 'array-extras', |
405 | + 'event']}); |
406 | |
407 | === modified file 'app/modules.js' |
408 | --- app/modules.js 2012-11-01 13:30:58 +0000 |
409 | +++ app/modules.js 2012-11-09 14:20:28 +0000 |
410 | @@ -12,6 +12,9 @@ |
411 | modules: { |
412 | 'd3': { |
413 | 'fullpath': '/juju-ui/assets/javascripts/d3.v2.min.js' |
414 | + }, |
415 | + 'd3-components': { |
416 | + fullpath: '/juju-ui/assets/javascripts/d3-components.js' |
417 | } |
418 | } |
419 | }, |
420 | |
421 | === modified file 'test/index.html' |
422 | --- test/index.html 2012-11-01 13:12:28 +0000 |
423 | +++ test/index.html 2012-11-09 14:20:28 +0000 |
424 | @@ -15,6 +15,7 @@ |
425 | mocha.setup({'ui': 'bdd', 'ignoreLeaks': false}) |
426 | </script> |
427 | |
428 | + <script src="test_d3_components.js"></script> |
429 | <script src="test_env.js"></script> |
430 | <script src="test_model.js"></script> |
431 | <script src="test_notifications.js"></script> |
432 | |
433 | === added file 'test/test_d3_components.js' |
434 | --- test/test_d3_components.js 1970-01-01 00:00:00 +0000 |
435 | +++ test/test_d3_components.js 2012-11-09 14:20:28 +0000 |
436 | @@ -0,0 +1,173 @@ |
437 | +'use strict'; |
438 | + |
439 | +describe('d3-components', function() { |
440 | + var Y, NS, TestModule, modA, state, |
441 | + container, comp; |
442 | + |
443 | + before(function(done) { |
444 | + Y = YUI(GlobalConfig).use(['d3-components', |
445 | + 'node', |
446 | + 'node-event-simulate'], |
447 | + function(Y) { |
448 | + NS = Y.namespace('d3'); |
449 | + |
450 | + TestModule = Y.Base.create('TestModule', NS.Module, [], { |
451 | + events: { |
452 | + scene: { '.thing': {click: 'decorateThing'}}, |
453 | + d3: {'.target': {click: 'targetTarget'}}, |
454 | + yui: { |
455 | + cancel: 'cancelHandler' |
456 | + } |
457 | + }, |
458 | + |
459 | + decorateThing: function(evt) { |
460 | + state.thing = 'decorated'; |
461 | + }, |
462 | + |
463 | + targetTarget: function(evt) { |
464 | + state.targeted = true; |
465 | + }, |
466 | + |
467 | + cancelHandler: function(evt) { |
468 | + state.cancelled = true; |
469 | + } |
470 | + }); |
471 | + |
472 | + done(); |
473 | + }); |
474 | + }); |
475 | + |
476 | + beforeEach(function() { |
477 | + container = Y.Node.create('<div id="test" style="visibility: hidden">' + |
478 | + '<button class="thing"></button>' + |
479 | + '<button class="target"></button>' + |
480 | + '</div>'); |
481 | + state = {}; |
482 | + }); |
483 | + |
484 | + afterEach(function() { |
485 | + container.remove(); |
486 | + container.destroy(); |
487 | + if (comp) { |
488 | + comp.unbind(); |
489 | + } |
490 | + }); |
491 | + |
492 | + |
493 | + it('should be able to create a component and add a module', function() { |
494 | + comp = new NS.Component(); |
495 | + Y.Lang.isValue(comp).should.equal(true); |
496 | + }); |
497 | + |
498 | + it('should be able to add and remove a module', function() { |
499 | + comp = new NS.Component(); |
500 | + comp.setAttrs({container: container}); |
501 | + comp.addModule(TestModule); |
502 | + Y.Lang.isValue(comp.events).should.equal(true); |
503 | + Y.Lang.isValue(comp.modules).should.equal(true); |
504 | + }); |
505 | + |
506 | + it('should be able to (un)bind module event subscriptions', function() { |
507 | + comp = new NS.Component(); |
508 | + comp.setAttrs({container: container}); |
509 | + comp.addModule(TestModule); |
510 | + |
511 | + // Test that default bindings work by simulating |
512 | + Y.fire('cancel'); |
513 | + state.cancelled.should.equal(true); |
514 | + |
515 | + // XXX: While on the plane I determined that things like |
516 | + // 'events' are sharing state with other runs/modules. |
517 | + // This must be fixed before this can work again. |
518 | + |
519 | + // Manually set state, remove the module and test again |
520 | + state.cancelled = false; |
521 | + comp.removeModule('TestModule'); |
522 | + |
523 | + Y.fire('cancel'); |
524 | + state.cancelled.should.equal(false); |
525 | + |
526 | + // Adding the module back again doesn't create any issues. |
527 | + comp.addModule(TestModule); |
528 | + Y.fire('cancel'); |
529 | + state.cancelled.should.equal(true); |
530 | + |
531 | + // Simulated events on DOM handlers better work. |
532 | + // These require a bound DOM element however |
533 | + comp.render(); |
534 | + Y.one('.thing').simulate('click'); |
535 | + state.thing.should.equal('decorated'); |
536 | + }); |
537 | + |
538 | + it('should allow event bindings through the use of a declartive object', |
539 | + function() { |
540 | + comp = new NS.Component(); |
541 | + comp.setAttrs({container: container}); |
542 | + |
543 | + // Change test module to use rich captures on some events. |
544 | + // This defines a phase for click (before, after, on (default)) |
545 | + // and also shows an inline callback (which is discouraged but allowed) |
546 | + modA = new TestModule(); |
547 | + modA.events.scene['.thing'] = { |
548 | + click: {phase: 'after', |
549 | + callback: 'afterThing'}, |
550 | + dblclick: {phase: 'on', |
551 | + callback: function(evt) { |
552 | + state.dbldbl = true; |
553 | + }}}; |
554 | + modA.afterThing = function(evt) { |
555 | + state.clicked = true; |
556 | + }; |
557 | + comp.addModule(modA); |
558 | + comp.render(); |
559 | + |
560 | + Y.one('.thing').simulate('click'); |
561 | + state.clicked.should.equal(true); |
562 | + |
563 | + Y.one('.thing').simulate('dblclick'); |
564 | + state.dbldbl.should.equal(true); |
565 | + |
566 | + }); |
567 | + |
568 | + it('should support basic rendering from all modules', |
569 | + function() { |
570 | + var modA = new TestModule(), |
571 | + modB = new TestModule(); |
572 | + |
573 | + comp = new NS.Component(); |
574 | + // Give each of these a render method that adds to container |
575 | + modA.name = 'moda'; |
576 | + modA.render = function() { |
577 | + this.get('container').append(Y.Node.create('<div id="fromA"></div>')); |
578 | + }; |
579 | + |
580 | + modB.name = 'modb'; |
581 | + modB.render = function() { |
582 | + this.get('container').append(Y.Node.create('<div id="fromB"></div>')); |
583 | + }; |
584 | + |
585 | + comp.setAttrs({container: container}); |
586 | + comp.addModule(modA) |
587 | + .addModule(modB); |
588 | + |
589 | + comp.render(); |
590 | + Y.Lang.isValue(Y.one('#fromA')).should.equal(true); |
591 | + Y.Lang.isValue(Y.one('#fromB')).should.equal(true); |
592 | + }); |
593 | + |
594 | + it('should support d3 event bindings post render', function() { |
595 | + comp = new NS.Component(); |
596 | + comp.setAttrs({container: container}); |
597 | + |
598 | + comp.addModule(TestModule); |
599 | + |
600 | + comp.render(); |
601 | + |
602 | + // This is a d3 bound handler that occurs only after render. |
603 | + container.one('.target').simulate('click'); |
604 | + state.targeted.should.equal(true); |
605 | + }); |
606 | + |
607 | +}); |
608 | + |
609 | + |
Reviewers: mp+133391_ code.launchpad. net,
Message:
Please take a look.
Description:
Micro Framework for Environment View
A subclass of Component will container 1 or more modules
which implement their own areas of application concern.
The pattern is that we can declaratively define event listeners
and respond to those across modules. For example A canvas click
might fire an application event 'clickedCanvas' which each module
can have subscribers for that do things like close menus and remove
drag lines, etc.
https:/ /code.launchpad .net/~bcsaller/ juju-gui/ component- framework/ +merge/ 133391
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/6828048/
Affected files: javascripts/ d3-components. js d3_components. js
A [revision details]
A app/assets/
M app/modules.js
M test/index.html
A test/test_