Merge lp:~benji/juju-gui/charm-dnd-2 into lp:juju-gui/experimental
- charm-dnd-2
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 765 |
Proposed branch: | lp:~benji/juju-gui/charm-dnd-2 |
Merge into: | lp:juju-gui/experimental |
Diff against target: |
660 lines (+327/-40) (has conflicts) 11 files modified
app/app.js (+6/-0) app/store/env/fakebackend.js (+31/-24) app/subapps/browser/views/editorial.js (+2/-1) app/views/charm-panel.js (+1/-1) app/views/topology/importexport.js (+8/-1) app/views/topology/service.js (+21/-1) app/widgets/charm-token.js (+82/-3) test/index.html (+4/-0) test/test_charm_token_drag_and_drop.js (+147/-0) test/test_notifier_widget.js (+9/-9) test/test_service_module.js (+16/-0) Text conflict in test/index.html |
To merge this branch: | bzr merge lp:~benji/juju-gui/charm-dnd-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+171084@code.launchpad.net |
Commit message
Description of the change
Make charms able to be dragged to the environment.
- 745. By Benji York
-
remove unneeded dependencies
- 746. By Benji York
-
checkpoint
- 747. By Benji York
-
tests
- 748. By Benji York
-
pre-review fixes
- 749. By Benji York
-
more pre-review fixes
Jeff Pihach (hatch) wrote : | # |
Gary Poster (gary) wrote : | # |
On Jun 24, 2013, at 6:37 PM, Jeff Pihach <email address hidden> wrote:
> This is such a cool feature thanks for implementing it. I added a
> comment about deploying to the position that the user dropped as a
> follow-up branch as it's probably a bit too much work to tack onto this
> one :-)
Yes, that would be fine. If you do that, please create it as a high priority card in the inspector story.
Thank you,
Gary
>
> My comments below are mostly just cleanup and few small improvement
> requests. If you have any questions don't hesitate to ask!
>
>
> https:/
> File app/app.js (right):
>
> https:/
> app/app.js:582: Y.on('initiateD
> as per comment in topology/service.js this should be:
>
> this.views.
> ... });
>
> Environment is a bubble target for the topology so it might be
> preferable to do
>
> this.env.
> -or-
> this.env.
>
> I'd even entertain the idea of renaming the event to be
> 'deployFromCharm'
>
> https:/
> File app/views/
>
> https:/
> app/views/
> - possible followup branch
>
> This should take note of the drop position and place the service there
> instead of the default deploy location.
>
> https:/
> app/views/
> just to get to the event? And why
> Typically libraries will wrap native objects with their own convenience
> layer and then provide access to the original one on a private property.
> As for why it's not passed to the handler....not sure Makyo might know.
>
> https:/
> app/views/
> This should not be firing on Y, but firing from this class so that the
> app can listen on this class for the event.
>
> this.fire(
>
> https:/
> File app/widgets/
>
> https:/
> app/widgets/
> should no longer be necessary as per my comments in the test script.
>
> https:/
> app/widgets/
> container, charmData);
> this._makeDragg
> you to a visibile UI while dragging without attaching to every child
> element.
>
> https:/
> app/widgets/
Richard Harding (rharding) wrote : | # |
Comments below. Jeff has some great feedback and I agree that there's
some work around the container/
easier.
https:/
File app/widgets/
https:/
app/widgets/
please make a valueFn so that it's done once and stored. It won't be
changing.
https:/
File app/widgets/
https:/
app/widgets/
Should we be catching this event to clean up later? This class is using
the Y.Event.
this.addEvent(
Then it'll auto get removed and cleaned up on the widgets destroy.
https:/
app/widgets/
I'm not sure why we need the container here. If you pass the boundingBox
into the widget cfg on init you'll have the correct space for the
content box and such.
https:/
app/widgets/
This should only be run if the charm-token is instructed to be
draggable. This widget is shared among fullscreen, sidebar, and the
related charms side data uses. It doesn't make sense to have them
draggable in all cases. An ATTR for isDraggable should be added and then
set from the sidebar related views I'd think.
- 750. By Benji York
-
review changes
- 751. By Benji York
-
lint fixes
Richard Harding (rharding) wrote : | # |
LGTM with the discussion on using boundingBox via cfg vs the container
and the simpler remove below.
https:/
File test/test_
https:/
test/test_
outerContainer.
you can just remove(true) and get it destroyed fully.
Benjamin Saller (bcsaller) wrote : | # |
Thanks for this, going to be a really nice feature when it lands. I
think you need to merge trunk and maybe look at this again, the ghost
handling and the inspector stuff changes and possibly simplifies some of
this.
https:/
File app/app.js (right):
https:/
app/app.js:584: }, this.charmPanel);
We can't invoke the function above in cfg.deploy? we already switch the
impl based on the feature flag.
https:/
File app/store/
https:/
app/store/
This looks like a story for another day.. Not sure what you must have
hit here to need this. :)
https:/
File app/views/
https:/
app/views/
handler?
I blame YUI :)
https:/
app/views/
Y.JSON.
Why JSON parse at all if its a single string?
https:/
app/views/
If you merge trunk we also have db.services.
creates the ghost model from the charm. The rest of the system has been
changed to react to that, render the proper model, set up the inspector,
etc.
https:/
File app/widgets/
https:/
app/widgets/
evt.dataTransfe
cute with the image
https:/
app/widgets/
Y.JSON.
You only need the id here, no? If thats true then no need for JSON
https:/
File test/test_
https:/
test/test_
That must have been a hard one to track down.
- 752. By Benji York
-
review changes
- 753. By Benji York
-
fix lint
Preview Diff
1 | === modified file 'app/app.js' |
2 | --- app/app.js 2013-06-25 19:27:16 +0000 |
3 | +++ app/app.js 2013-06-26 13:48:25 +0000 |
4 | @@ -593,6 +593,12 @@ |
5 | this.on_database_changed, this); |
6 | } |
7 | this.addSubApplications(cfg); |
8 | + |
9 | + // When someone wants a charm to be deployed they fire initiateDeploy |
10 | + // and we show the charm panel to configure/deploy the service. |
11 | + Y.on('initiateDeploy', function(charm) { |
12 | + this.deploy(charm); |
13 | + }, this.charmPanel); |
14 | }, |
15 | |
16 | /** |
17 | |
18 | === modified file 'app/store/env/fakebackend.js' |
19 | --- app/store/env/fakebackend.js 2013-06-25 19:36:29 +0000 |
20 | +++ app/store/env/fakebackend.js 2013-06-26 13:48:25 +0000 |
21 | @@ -28,7 +28,8 @@ |
22 | YUI.add('juju-env-fakebackend', function(Y) { |
23 | |
24 | var models = Y.namespace('juju.models'); |
25 | - var UNAUTHENTICATEDERROR = {error: 'Please log in.'}; |
26 | + var UNAUTHENTICATED_ERROR = {error: 'Please log in.'}; |
27 | + var VALUE_ERROR = {error: 'Unparsable environment data.'}; |
28 | |
29 | /** |
30 | Loops through the charm endpoint data to determine whether we have a |
31 | @@ -163,7 +164,7 @@ |
32 | */ |
33 | nextChanges: function() { |
34 | if (!this.get('authenticated')) { |
35 | - return UNAUTHENTICATEDERROR; |
36 | + return UNAUTHENTICATED_ERROR; |
37 | } |
38 | var result; |
39 | if (Y.Object.isEmpty(this.changes.services) && |
40 | @@ -205,7 +206,7 @@ |
41 | */ |
42 | nextAnnotations: function() { |
43 | if (!this.get('authenticated')) { |
44 | - return UNAUTHENTICATEDERROR; |
45 | + return UNAUTHENTICATED_ERROR; |
46 | } |
47 | var result; |
48 | if (Y.Object.isEmpty(this.annotations.services) && |
49 | @@ -293,7 +294,7 @@ |
50 | */ |
51 | deploy: function(charmId, callback, options) { |
52 | if (!this.get('authenticated')) { |
53 | - return callback(UNAUTHENTICATEDERROR); |
54 | + return callback(UNAUTHENTICATED_ERROR); |
55 | } |
56 | if (!options) { |
57 | options = {}; |
58 | @@ -502,7 +503,7 @@ |
59 | */ |
60 | destroyService: function(serviceName) { |
61 | if (!this.get('authenticated')) { |
62 | - return UNAUTHENTICATEDERROR; |
63 | + return UNAUTHENTICATED_ERROR; |
64 | } |
65 | var service = this.db.services.getById(serviceName); |
66 | if (!service) { |
67 | @@ -539,7 +540,7 @@ |
68 | */ |
69 | getService: function(serviceName) { |
70 | if (!this.get('authenticated')) { |
71 | - return UNAUTHENTICATEDERROR; |
72 | + return UNAUTHENTICATED_ERROR; |
73 | } |
74 | var service = this.db.services.getById(serviceName); |
75 | if (!service) { |
76 | @@ -567,7 +568,7 @@ |
77 | */ |
78 | getCharm: function(charmName, callback) { |
79 | if (!this.get('authenticated')) { |
80 | - return callback(UNAUTHENTICATEDERROR); |
81 | + return callback(UNAUTHENTICATED_ERROR); |
82 | } |
83 | var formatCharm = function(charm) { |
84 | // Simulate a delay in the charm loading for testing. |
85 | @@ -597,7 +598,7 @@ |
86 | */ |
87 | addUnit: function(serviceName, numUnits) { |
88 | if (!this.get('authenticated')) { |
89 | - return UNAUTHENTICATEDERROR; |
90 | + return UNAUTHENTICATED_ERROR; |
91 | } |
92 | if (Y.Lang.isUndefined(numUnits)) { |
93 | numUnits = 1; |
94 | @@ -742,7 +743,7 @@ |
95 | warning, error; |
96 | |
97 | if (!this.get('authenticated')) { |
98 | - return UNAUTHENTICATEDERROR; |
99 | + return UNAUTHENTICATED_ERROR; |
100 | } |
101 | |
102 | if (service) { |
103 | @@ -775,7 +776,7 @@ |
104 | warning, error; |
105 | |
106 | if (!this.get('authenticated')) { |
107 | - return UNAUTHENTICATEDERROR; |
108 | + return UNAUTHENTICATED_ERROR; |
109 | } |
110 | if (service) { |
111 | if (service.get('exposed')) { |
112 | @@ -805,7 +806,7 @@ |
113 | */ |
114 | addRelation: function(endpointA, endpointB) { |
115 | if (!this.get('authenticated')) { |
116 | - return UNAUTHENTICATEDERROR; |
117 | + return UNAUTHENTICATED_ERROR; |
118 | } |
119 | if ((typeof endpointA !== 'string') || |
120 | (typeof endpointB !== 'string')) { |
121 | @@ -886,7 +887,7 @@ |
122 | */ |
123 | removeRelation: function(endpointA, endpointB) { |
124 | if (!this.get('authenticated')) { |
125 | - return UNAUTHENTICATEDERROR; |
126 | + return UNAUTHENTICATED_ERROR; |
127 | } |
128 | if ((typeof endpointA !== 'string') || |
129 | (typeof endpointB !== 'string')) { |
130 | @@ -960,7 +961,7 @@ |
131 | */ |
132 | updateAnnotations: function(entityName, annotations) { |
133 | if (!this.get('authenticated')) { |
134 | - return UNAUTHENTICATEDERROR; |
135 | + return UNAUTHENTICATED_ERROR; |
136 | } |
137 | var entity = this.db.resolveModelByName(entityName); |
138 | var existing; |
139 | @@ -993,7 +994,7 @@ |
140 | */ |
141 | getAnnotations: function(entityName) { |
142 | if (!this.get('authenticated')) { |
143 | - return UNAUTHENTICATEDERROR; |
144 | + return UNAUTHENTICATED_ERROR; |
145 | } |
146 | var entity = this.db.resolveModelByName(entityName); |
147 | |
148 | @@ -1015,7 +1016,7 @@ |
149 | */ |
150 | removeAnnotations: function(entityName, keys) { |
151 | if (!this.get('authenticated')) { |
152 | - return UNAUTHENTICATEDERROR; |
153 | + return UNAUTHENTICATED_ERROR; |
154 | } |
155 | var entity = this.db.resolveModelByName(entityName); |
156 | var annotations; |
157 | @@ -1058,7 +1059,7 @@ |
158 | */ |
159 | setConfig: function(serviceName, config) { |
160 | if (!this.get('authenticated')) { |
161 | - return UNAUTHENTICATEDERROR; |
162 | + return UNAUTHENTICATED_ERROR; |
163 | } |
164 | var service = this.db.services.getById(serviceName); |
165 | if (!service) { |
166 | @@ -1096,7 +1097,7 @@ |
167 | var constraints = {}; |
168 | |
169 | if (!this.get('authenticated')) { |
170 | - return UNAUTHENTICATEDERROR; |
171 | + return UNAUTHENTICATED_ERROR; |
172 | } |
173 | var service = this.db.services.getById(serviceName); |
174 | if (!service) { |
175 | @@ -1138,7 +1139,7 @@ |
176 | */ |
177 | resolved: function(unitName, relationName) { |
178 | if (!this.get('authenticated')) { |
179 | - return UNAUTHENTICATEDERROR; |
180 | + return UNAUTHENTICATED_ERROR; |
181 | } |
182 | var unit = this.db.units.getById(unitName); |
183 | if (!unit) { |
184 | @@ -1217,7 +1218,7 @@ |
185 | }; |
186 | |
187 | if (!this.get('authenticated')) { |
188 | - return UNAUTHENTICATEDERROR; |
189 | + return UNAUTHENTICATED_ERROR; |
190 | } |
191 | |
192 | serviceList.each(function(s) { |
193 | @@ -1260,11 +1261,17 @@ |
194 | */ |
195 | importEnvironment: function(jsonData, callback) { |
196 | if (!this.get('authenticated')) { |
197 | - return callback(UNAUTHENTICATEDERROR); |
198 | - } |
199 | - var data = JSON.parse(jsonData), |
200 | - version = 0, |
201 | - importImpl; |
202 | + return callback(UNAUTHENTICATED_ERROR); |
203 | + } |
204 | + var data; |
205 | + try { |
206 | + data = JSON.parse(jsonData); |
207 | + } catch (e) { |
208 | + console.log('error parsing environment data'); |
209 | + return callback(VALUE_ERROR); |
210 | + } |
211 | + var version = 0; |
212 | + var importImpl; |
213 | // Dispatch to the correct version after inspecting the JSON data. |
214 | if (data.meta && data.meta.exportFormat) { |
215 | version = data.meta.exportFormat; |
216 | |
217 | === modified file 'app/subapps/browser/views/editorial.js' |
218 | --- app/subapps/browser/views/editorial.js 2013-06-18 13:08:42 +0000 |
219 | +++ app/subapps/browser/views/editorial.js 2013-06-26 13:48:25 +0000 |
220 | @@ -115,7 +115,8 @@ |
221 | |
222 | var containerCfg = { |
223 | additionalChildConfig: { |
224 | - size: this.get('isFullscreen') ? 'large' : 'small' |
225 | + size: this.get('isFullscreen') ? 'large' : 'small', |
226 | + isDraggable: !this.get('isFullscreen') |
227 | } |
228 | }; |
229 | |
230 | |
231 | === modified file 'app/views/charm-panel.js' |
232 | --- app/views/charm-panel.js 2013-06-20 15:37:45 +0000 |
233 | +++ app/views/charm-panel.js 2013-06-26 13:48:25 +0000 |
234 | @@ -856,7 +856,7 @@ |
235 | charmId: charm.get('id') |
236 | }); |
237 | // Since we are showing the configure/deploy panel ex nihilo, we want the |
238 | - // panel to disappear when the deploy completes or is candled, but just |
239 | + // panel to disappear when the deploy completes or is canceled, but just |
240 | // this once (i.e., they should work normally if the user opens the panel |
241 | // via a charm search). |
242 | panels.configuration.once('panelRemoved', hide); |
243 | |
244 | === modified file 'app/views/topology/importexport.js' |
245 | --- app/views/topology/importexport.js 2013-06-25 20:49:59 +0000 |
246 | +++ app/views/topology/importexport.js 2013-06-26 13:48:25 +0000 |
247 | @@ -98,7 +98,14 @@ |
248 | reader.readAsText(file); |
249 | }); |
250 | } else { |
251 | - env.importEnvironment(evt._event.dataTransfer.getData('Text')); |
252 | + // If this is not a drop event generated by the application (all |
253 | + // of which have a dataType), then it was caused by dropping |
254 | + // something external to the app, in which case it might be an |
255 | + // environment configuration, so process it as such. |
256 | + var dataType = evt._event.dataTransfer.getData('dataType'); |
257 | + if (dataType === undefined) { |
258 | + env.importEnvironment(evt._event.dataTransfer.getData('Text')); |
259 | + } |
260 | } |
261 | evt.preventDefault(); |
262 | evt.stopPropagation(); |
263 | |
264 | === modified file 'app/views/topology/service.js' |
265 | --- app/views/topology/service.js 2013-06-23 22:54:49 +0000 |
266 | +++ app/views/topology/service.js 2013-06-26 13:48:25 +0000 |
267 | @@ -77,7 +77,8 @@ |
268 | mouseout: 'serviceStatusMouseOut' |
269 | }, |
270 | '.zoom-plane': { |
271 | - click: 'canvasClick' |
272 | + click: 'canvasClick', |
273 | + drop: 'canvasDropHandler' |
274 | }, |
275 | // Menu/Controls |
276 | '.view-service': { |
277 | @@ -378,6 +379,25 @@ |
278 | }, |
279 | |
280 | /** |
281 | + * Handle deploying a services by dropping a charm onto the canvas. |
282 | + * |
283 | + * @method canvasDropHandler |
284 | + * @static |
285 | + * @return {undefined} Nothing. |
286 | + */ |
287 | + canvasDropHandler: function() { |
288 | + var evt = d3.event._event; // So well hidden. |
289 | + var dataType = evt.dataTransfer.getData('dataType'); |
290 | + if (dataType === 'charm-token-drag-and-drop') { |
291 | + // The charm data was JSON encoded because the dataTransfer mechanism |
292 | + // only allows for string values. |
293 | + var charmData = Y.JSON.parse(evt.dataTransfer.getData('charmData')); |
294 | + var charm = new models.Charm(charmData); |
295 | + Y.fire('initiateDeploy', charm); |
296 | + } |
297 | + }, |
298 | + |
299 | + /** |
300 | * Clear any stateful actions (menus, etc.) when a clearState event is |
301 | * received. |
302 | * |
303 | |
304 | === modified file 'app/widgets/charm-token.js' |
305 | --- app/widgets/charm-token.js 2013-06-14 13:14:27 +0000 |
306 | +++ app/widgets/charm-token.js 2013-06-26 13:48:25 +0000 |
307 | @@ -38,6 +38,18 @@ |
308 | TEMPLATE: Y.namespace('juju.views').Templates['charm-token'], |
309 | |
310 | /** |
311 | + * Default general initializer method. |
312 | + * |
313 | + * @method initializer |
314 | + * @param {Object} cfg the config for the widget. |
315 | + * @return {undefined} Nothing. |
316 | + */ |
317 | + initializer: function(charmAttributes) { |
318 | + // This passed-in config is made up of charm attributes. |
319 | + this.charmAttributes = charmAttributes; |
320 | + }, |
321 | + |
322 | + /** |
323 | Setter for the boundingBox attribute |
324 | |
325 | **Override vs YUI to prevent node id setting based on BrowserCharm** |
326 | @@ -56,15 +68,71 @@ |
327 | }, |
328 | |
329 | /** |
330 | + * Generate a function that records drag and drop data when a drag starts. |
331 | + * |
332 | + * @method _makeDragStartHandler |
333 | + * @param {Node} dragImage The node to show during the drag. |
334 | + * @param {String} charmData The JSON encoded charm attributes. |
335 | + * @return {undefined} Nothing. |
336 | + */ |
337 | + _makeDragStartHandler: function(dragImage, charmData) { |
338 | + return function(evt) { |
339 | + evt = evt._event; // We want the real event. |
340 | + evt.dataTransfer.effectAllowed = 'copy'; |
341 | + evt.dataTransfer.setData('charmData', charmData); |
342 | + evt.dataTransfer.setData('dataType', 'charm-token-drag-and-drop'); |
343 | + evt.dataTransfer.setDragImage(dragImage._node, 0, 0); |
344 | + }; |
345 | + }, |
346 | + |
347 | + /** |
348 | + * Make an element draggable. |
349 | + * |
350 | + * @method _makeDraggable |
351 | + * @param {Node} element The node which is to be made draggable. |
352 | + * @param {Node} dragImage The node which will be displayed during |
353 | + * dragging. |
354 | + * @param {String} charmData The JSON encoded charm attributes. |
355 | + * @return {undefined} Nothing. |
356 | + */ |
357 | + _makeDraggable: function(element, dragImage, charmData) { |
358 | + element.setAttribute('draggable', 'true'); |
359 | + this.addEvent(element.on('dragstart', |
360 | + this._makeDragStartHandler(dragImage, charmData))); |
361 | + }, |
362 | + |
363 | + /** |
364 | + * Make the charm token draggable. |
365 | + * |
366 | + * @method _addDraggability |
367 | + * @param {Node} container he node which contains the charm list. |
368 | + * @return {undefined} Nothing; side-effects only. |
369 | + */ |
370 | + _addDraggability: function(container) { |
371 | + // Since the browser's dataTransfer mechanism only accepts string values |
372 | + // we have to JSON encode the charm data. |
373 | + var charmData = Y.JSON.stringify(this.charmAttributes); |
374 | + this._makeDraggable(container, container, charmData); |
375 | + this._makeDraggable(container.one('a'), container, charmData); |
376 | + }, |
377 | + |
378 | + /** |
379 | * Create the nodes required by this widget and attach them to the DOM. |
380 | * |
381 | + * @param {Node} container The contaner to render into. Mainly for |
382 | + * testing. |
383 | * @method renderUI |
384 | */ |
385 | - renderUI: function() { |
386 | + renderUI: function(container) { |
387 | var content = this.TEMPLATE(this.getAttrs()); |
388 | - var container = this.get('contentBox'); |
389 | + // This way we can pass in a container for easier testing. |
390 | + container = container || this.get('contentBox'); |
391 | container.setHTML(content); |
392 | - container.ancestor('.yui3-charmtoken').addClass('yui3-u'); |
393 | + var outerContainer = container.ancestor('.yui3-charmtoken') |
394 | + .addClass('yui3-u'); |
395 | + if (this.get('isDraggable')) { |
396 | + this._addDraggability(outerContainer); |
397 | + } |
398 | } |
399 | |
400 | }, { |
401 | @@ -147,6 +215,17 @@ |
402 | */ |
403 | size: { |
404 | value: 'small' |
405 | + }, |
406 | + /** |
407 | + * Should the charm token be draggable? |
408 | + * |
409 | + * @attribute isDraggable |
410 | + * @default true |
411 | + * @type {Boolean} |
412 | + * |
413 | + */ |
414 | + isDraggable: { |
415 | + value: true |
416 | } |
417 | } |
418 | }); |
419 | |
420 | === modified file 'test/index.html' |
421 | --- test/index.html 2013-06-20 22:15:49 +0000 |
422 | +++ test/index.html 2013-06-26 13:48:25 +0000 |
423 | @@ -60,8 +60,12 @@ |
424 | <script src="test_charm_panel.js"></script> |
425 | <script src="test_charm_store.js"></script> |
426 | <script src="test_charm_token.js"></script> |
427 | +<<<<<<< TREE |
428 | |
429 | <!-- FIXME: tests flicker, add container. --> |
430 | +======= |
431 | + <script src="test_charm_token_drag_and_drop.js"></script> |
432 | +>>>>>>> MERGE-SOURCE |
433 | <script src="test_charm_view.js"></script> |
434 | |
435 | <script src="test_console.js"></script> |
436 | |
437 | === added file 'test/test_charm_token_drag_and_drop.js' |
438 | --- test/test_charm_token_drag_and_drop.js 1970-01-01 00:00:00 +0000 |
439 | +++ test/test_charm_token_drag_and_drop.js 2013-06-26 13:48:25 +0000 |
440 | @@ -0,0 +1,147 @@ |
441 | +/* |
442 | +This file is part of the Juju GUI, which lets users view and manage Juju |
443 | +environments within a graphical interface (https://launchpad.net/juju-gui). |
444 | +Copyright (C) 2012-2013 Canonical Ltd. |
445 | + |
446 | +This program is free software: you can redistribute it and/or modify it under |
447 | +the terms of the GNU Affero General Public License version 3, as published by |
448 | +the Free Software Foundation. |
449 | + |
450 | +This program is distributed in the hope that it will be useful, but WITHOUT |
451 | +ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
452 | +SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero |
453 | +General Public License for more details. |
454 | + |
455 | +You should have received a copy of the GNU Affero General Public License along |
456 | +with this program. If not, see <http://www.gnu.org/licenses/>. |
457 | +*/ |
458 | + |
459 | +'use strict'; |
460 | + |
461 | +describe('charm token drag and drop', function() { |
462 | + var Y, container, outerContainer, CharmToken, token; |
463 | + |
464 | + before(function(done) { |
465 | + Y = YUI(GlobalConfig).use([ |
466 | + 'browser-charm-token', |
467 | + 'juju-tests-utils' |
468 | + ], |
469 | + function(Y) { |
470 | + CharmToken = Y.juju.widgets.browser.CharmToken; |
471 | + done(); |
472 | + }); |
473 | + |
474 | + }); |
475 | + |
476 | + beforeEach(function() { |
477 | + outerContainer = Y.namespace('juju-tests.utils').makeContainer() |
478 | + .addClass('yui3-charmtoken'); |
479 | + container = Y.namespace('juju-tests.utils').makeContainer(); |
480 | + outerContainer.append(container); |
481 | + }); |
482 | + |
483 | + afterEach(function() { |
484 | + outerContainer.remove().destroy(true); |
485 | + if (token) { |
486 | + token.destroy(); |
487 | + } |
488 | + }); |
489 | + |
490 | + it('makes each charm token draggable', function() { |
491 | + var cfg = { |
492 | + id: 'test', |
493 | + name: 'some-charm', |
494 | + description: 'some description', |
495 | + recent_commit_count: 1, |
496 | + recent_download_count: 3, |
497 | + tested_providers: ['ec2'] |
498 | + }; |
499 | + token = new CharmToken(cfg); |
500 | + var draggable = []; |
501 | + token._makeDraggable = function(element, dragImage, charmData) { |
502 | + draggable.push(Y.JSON.parse(charmData).id); |
503 | + }; |
504 | + token.renderUI(container); |
505 | + // Many elements are made draggable for a single token token. |
506 | + assert.isTrue(draggable.length > 1); |
507 | + // There is only a single token represented by all the draggable elements. |
508 | + draggable = Y.Array.dedupe(draggable); |
509 | + assert.equal(draggable.length, 1); |
510 | + // All of the charm tokens are made draggable. |
511 | + assert.deepEqual(draggable, ['test']); |
512 | + }); |
513 | + |
514 | + it('can make an element draggable', function() { |
515 | + token = new CharmToken(); |
516 | + var setAttributeCalled, onCalled; |
517 | + var element = { |
518 | + setAttribute: function(name, value) { |
519 | + assert.equal(name, 'draggable'); |
520 | + assert.equal(value, 'true'); |
521 | + setAttributeCalled = true; |
522 | + }, |
523 | + on: function(when, callable) { |
524 | + assert.equal(when, 'dragstart'); |
525 | + onCalled = true; |
526 | + return {detach: function() {}}; |
527 | + } |
528 | + }; |
529 | + var dragImage = {}; |
530 | + token._makeDragStartHandler = function(dragImage, charmData) { |
531 | + assert.equal(element, element); |
532 | + assert.equal(dragImage, dragImage); |
533 | + assert.equal(charmData, charmData); |
534 | + }; |
535 | + token._makeDraggable(element, dragImage, 'charm-id'); |
536 | + assert.isTrue(setAttributeCalled); |
537 | + assert.isTrue(onCalled); |
538 | + }); |
539 | + |
540 | + it('can set up drag and drop configuration', function() { |
541 | + token = new CharmToken(); |
542 | + var setDataCalled, setDragImageCalled; |
543 | + var dragImage = {_node: {id: 'the real drag image'}}; |
544 | + var charmData = 'data'; |
545 | + var handler = token._makeDragStartHandler(dragImage, charmData); |
546 | + var dragDataSet = []; |
547 | + var evt = { |
548 | + _event: { |
549 | + dataTransfer: { |
550 | + setData: function(name, value) { |
551 | + dragDataSet.push([name, value]); |
552 | + setDataCalled = true; |
553 | + }, |
554 | + setDragImage: function(provideDragImage, x, y) { |
555 | + assert.equal(provideDragImage, dragImage._node); |
556 | + assert.equal(x, 0); |
557 | + assert.equal(y, 0); |
558 | + setDragImageCalled = true; |
559 | + } |
560 | + } |
561 | + } |
562 | + }; |
563 | + handler(evt); |
564 | + assert.equal(evt._event.dataTransfer.effectAllowed, 'copy'); |
565 | + assert.equal(Y.JSON.stringify(dragDataSet), |
566 | + '[["charmData","data"],["dataType","charm-token-drag-and-drop"]]'); |
567 | + assert.isTrue(setDataCalled); |
568 | + assert.isTrue(setDragImageCalled); |
569 | + }); |
570 | + |
571 | + it('respects the isDraggable switch', function() { |
572 | + token = new CharmToken({contentBox: container}); |
573 | + token.set('isDraggable', false); |
574 | + var dragEnabled = false; |
575 | + token._addDraggability = function() { |
576 | + dragEnabled = true; |
577 | + }; |
578 | + token.renderUI(container); |
579 | + // Since we set isDraggable to false, _addDraggability was not called. |
580 | + assert.isFalse(dragEnabled); |
581 | + // If we set isDraggable to true, _addDraggability will be called. |
582 | + token.set('isDraggable', true); |
583 | + token.renderUI(container); |
584 | + assert.isTrue(dragEnabled); |
585 | + }); |
586 | + |
587 | +}); |
588 | |
589 | === modified file 'test/test_notifier_widget.js' |
590 | --- test/test_notifier_widget.js 2013-05-17 14:51:05 +0000 |
591 | +++ test/test_notifier_widget.js 2013-06-26 13:48:25 +0000 |
592 | @@ -89,34 +89,34 @@ |
593 | |
594 | it('should destroy notifications after N milliseconds', function(done) { |
595 | makeNotifier('mytitle', 'mymessage', 1); |
596 | - // A timeout of 250 milliseconds is used so that we ensure the destroying |
597 | - // animation can be completed. |
598 | + // A timeout is used so that we ensure the destroying animation can be |
599 | + // completed. |
600 | setTimeout(function() { |
601 | assertNumNotifiers(0); |
602 | done(); |
603 | - }, 250); |
604 | + }, 500); |
605 | }); |
606 | |
607 | it('should destroy notifications on click', function(done) { |
608 | makeNotifier(); |
609 | notifierBox.one('*').simulate('click'); |
610 | - // A timeout of 250 milliseconds is used so that we ensure the destroying |
611 | - // animation can be completed. |
612 | + // A timeout is used so that we ensure the destroying animation can be |
613 | + // completed. |
614 | setTimeout(function() { |
615 | assertNumNotifiers(0); |
616 | done(); |
617 | - }, 250); |
618 | + }, 500); |
619 | }); |
620 | |
621 | it('should prevent notification removal on mouse enter', function(done) { |
622 | makeNotifier('mytitle', 'mymessage', 1); |
623 | notifierBox.one('*').simulate('mouseover'); |
624 | - // A timeout of 250 milliseconds is used so that we ensure the node is not |
625 | - // preserved by the destroying animation. |
626 | + // A timeout is used so that we ensure the destroying animation can be |
627 | + // completed. |
628 | setTimeout(function() { |
629 | assertNumNotifiers(1); |
630 | done(); |
631 | - }, 250); |
632 | + }, 500); |
633 | }); |
634 | |
635 | }); |
636 | |
637 | === modified file 'test/test_service_module.js' |
638 | --- test/test_service_module.js 2013-06-19 15:44:25 +0000 |
639 | +++ test/test_service_module.js 2013-06-26 13:48:25 +0000 |
640 | @@ -369,4 +369,20 @@ |
641 | assert.deepPropertyVal(boxes, 'wordpress.model', wordpress); |
642 | }); |
643 | |
644 | + it('should deploy a service on charm token drop events', function(done) { |
645 | + d3.event._event = {dataTransfer: { |
646 | + getData: function(name) { |
647 | + if (name === 'dataType') { |
648 | + return 'charm-token-drag-and-drop'; |
649 | + } else if (name === 'charmData') { |
650 | + return '{"id": "cs:foo/bar-1"}'; |
651 | + } |
652 | + }}}; |
653 | + var eventHandle = Y.on('initiateDeploy', function(charm) { |
654 | + eventHandle.detach(); |
655 | + done(); |
656 | + }); |
657 | + serviceModule.canvasDropHandler(); |
658 | + }); |
659 | + |
660 | }); |
This is such a cool feature thanks for implementing it. I added a
comment about deploying to the position that the user dropped as a
follow-up branch as it's probably a bit too much work to tack onto this
one :-)
My comments below are mostly just cleanup and few small improvement
requests. If you have any questions don't hesitate to ask!
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/app. js
File app/app.js (right):
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/app. js#newcode582 eploy', function(charm) {
app/app.js:582: Y.on('initiateD
as per comment in topology/service.js this should be:
this.views. environment. instance. topo.on( 'initiateDeploy ', function() {
... });
Environment is a bubble target for the topology so it might be
preferable to do
this.env. on('initiateDep loy', ... on('Topology: initiateDeploy' , ...
-or-
this.env.
I'd even entertain the idea of renaming the event to be
'deployFromCharm'
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/views/ topology/ service. js topology/ service. js (right):
File app/views/
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/views/ topology/ service. js#newcode387 topology/ service. js:387: canvasDropHandler: function() {
app/views/
- possible followup branch
This should take note of the drop position and place the service there
instead of the default deploy location.
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/views/ topology/ service. js#newcode388 topology/ service. js:388: // XXX Why do we have to dig so deep
app/views/
just to get to the event? And why
Typically libraries will wrap native objects with their own convenience
layer and then provide access to the original one on a private property.
As for why it's not passed to the handler....not sure Makyo might know.
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/views/ topology/ service. js#newcode395 topology/ service. js:395: Y.fire( 'initiateDeploy ', charm);
app/views/
This should not be firing on Y, but firing from this class so that the
app can listen on this class for the event.
this.fire( 'initiateDeploy ', charm);
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/widgets/ charm-token. js charm-token. js (right):
File app/widgets/
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/widgets/ charm-token. js#newcode112 charm-token. js:112: if (!container) {
app/widgets/
should no longer be necessary as per my comments in the test script.
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/widgets/ charm-token. js#newcode117 charm-token. js:117: this._makeDragg able(container, able(container. one('a' ), container, charmData) will get
app/widgets/
container, charmData);
this._makeDragg
you to a visibile UI while dragging without attaching to every child
element.
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/widgets/ charm-token. js#newcode119 charm-token. js:119: container. all('*' ).each( function( child)
app/widgets/
{
This can be removed as per comment above
https:/ /codereview. appspot. com/10500044/ diff/5001/ app/widgets/ charm-token. js#newcode133 charm-token. js:133: var outerContainer = ancestor( '.yui3- charmtoken' ) 'boundingBox' ).addClass( 'yui3-u' );
app/widgets/
container.
this is the bounding box this.get(
Although I'...