Merge lp:~rharding/juju-gui/charm-details-1172050 into lp:juju-gui/experimental

Proposed by Richard Harding
Status: Merged
Merged at revision: 607
Proposed branch: lp:~rharding/juju-gui/charm-details-1172050
Merge into: lp:juju-gui/experimental
Diff against target: 369 lines (+182/-18)
6 files modified
app/subapps/browser/browser.js (+46/-9)
app/subapps/browser/views/editorial.js (+4/-7)
app/subapps/browser/views/search.js (+48/-0)
test/test_browser_app.js (+60/-0)
test/test_browser_editorial.js (+0/-2)
test/test_browser_search_view.js (+24/-0)
To merge this branch: bzr merge lp:~rharding/juju-gui/charm-details-1172050
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+160536@code.launchpad.net

Description of the change

Fixes #1172050 correct browser state tracking.

- The browser viewstate wasn't tracking the query string correctly.
- The searchresults view didn't track charm-token clicks and navigate so that
you always ended up to the link sidebar/charmid.
- Drive by fix the editorial as it shouldn't have had to specify the viewmode.
- Update the routing to handle search/charm/id urls better and not double
route through the state.

To post a comment you must log in.
605. By Richard Harding

Add failing test to start the day

606. By Richard Harding

Add test that query string is in the state and checked

607. By Richard Harding

Fix the tests for the cases around search dispatch

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/subapps/browser/browser.js'
2--- app/subapps/browser/browser.js 2013-04-23 16:20:42 +0000
3+++ app/subapps/browser/browser.js 2013-04-24 12:51:26 +0000
4@@ -92,9 +92,10 @@
5 */
6 _initState: function() {
7 this._oldState = {
8- viewmode: null,
9+ charmID: null,
10+ querystring: null,
11 search: null,
12- charmID: null
13+ viewmode: null
14 };
15 this._viewState = Y.merge(this._oldState, {});
16 },
17@@ -151,7 +152,8 @@
18 (
19 this._hasStateChanged('search') ||
20 this._hasStateChanged('viewmode') ||
21- this._hasStateChanged('querystring')
22+ this._hasStateChanged('querystring') ||
23+ (this._hasStateChanged('charmID') && !this._viewState.charmID)
24 )
25 ) {
26 return true;
27@@ -197,13 +199,15 @@
28 @param {Object} params the params from the request payload.
29
30 */
31- _updateState: function(path, params) {
32+ _updateState: function(path, params, query) {
33 // Update the viewmode. Every request has a viewmode.
34 this._viewState.viewmode = params.viewmode;
35
36 // Check for a charm id in the request.
37 if (params.id && params.id !== 'search') {
38- this._viewState.charmID = params.id;
39+ // Make sure we clear out any accidental matching of search/ in the
40+ // url.
41+ this._viewState.charmID = params.id.replace(/^search\//, '');
42 } else {
43 this._viewState.charmID = null;
44 }
45@@ -214,6 +218,14 @@
46 } else {
47 this._viewState.search = false;
48 }
49+
50+ // Check if there's a query string to set.
51+ if (query) {
52+ // Store it as a straight string.
53+ this._viewState.querystring = Y.QueryString.stringify(query);
54+ } else {
55+ this._viewState.querystring = null;
56+ }
57 },
58
59 /**
60@@ -278,7 +290,7 @@
61 *
62 */
63 renderCharmDetails: function(req, res, next) {
64- var charmID = req.params.id;
65+ var charmID = this._viewState.charmID;
66 var extraCfg = {
67 charmID: charmID,
68 container: Y.Node.create('<div class="charmview"/>'),
69@@ -402,6 +414,20 @@
70 this._fullscreen = this.showView('fullscreen', this._getViewCfg());
71 }
72
73+ // Regardless of the results below we need to clear out the old
74+ // subviews.
75+ if (this._editorial) {
76+ this._editorial.destroy();
77+ }
78+
79+ if (this._details) {
80+ this._details.destroy();
81+ }
82+
83+ if (this._search) {
84+ this._search.destroy();
85+ }
86+
87 // If we've changed the charmID or the viewmode has changed and we have
88 // a charmID, render charmDetails.
89 if (this._shouldShowCharm()) {
90@@ -411,7 +437,7 @@
91 // Render search results if search is in the url and the viewmode or
92 // the search has been changed in the state.
93 this.renderSearchResults(req, res, next);
94- } else if (!this._viewState.charmID) {
95+ } else if (!this._viewState.search && !this._viewState.charmID) {
96 // Render the editorial in fullscreen only if we don't have a charmid
97 this.renderEditorial(req, res, next);
98 }
99@@ -440,10 +466,20 @@
100 // Render search results if search is in the url and the viewmode or the
101 // search has been changed in the state.
102 if (this._shouldShowSearch()) {
103+ // Showing search implies that other sidebar content is destroyed.
104+ if (this._editorial) {
105+ this._editorial.destroy();
106+ }
107+
108 this.renderSearchResults(req, res, next);
109 }
110
111 if (this._shouldShowEditorial()) {
112+ // Showing editorial implies that other sidebar content is destroyed.
113+ if (this._search) {
114+ this._search.destroy();
115+ }
116+
117 this.renderEditorial(req, res, next);
118 }
119
120@@ -454,7 +490,7 @@
121 this.renderCharmDetails(req, res, next);
122 }
123
124- // If the sidebar is the final part of the route, then hide the div for
125+ // If no details in the route then hide the div for
126 // viewing the charm details.
127 if (!this._viewState.charmID) {
128 this._detailsVisible(false);
129@@ -484,7 +520,7 @@
130 */
131 routeView: function(req, res, next) {
132 // Update the state for the rest of things to figure out what to do.
133- this._updateState(req.path, req.params);
134+ this._updateState(req.path, req.params, req.query);
135 this[req.params.viewmode](req, res, next);
136 }
137
138@@ -574,6 +610,7 @@
139 'juju-charm-store',
140 'juju-models',
141 'querystring-parse',
142+ 'querystring-stringify',
143 'sub-app',
144 'subapp-browser-charmview',
145 'subapp-browser-editorial',
146
147=== modified file 'app/subapps/browser/views/editorial.js'
148--- app/subapps/browser/views/editorial.js 2013-04-23 18:52:28 +0000
149+++ app/subapps/browser/views/editorial.js 2013-04-24 12:51:26 +0000
150@@ -60,17 +60,14 @@
151 var charmID = charm.getData('charmid');
152
153 // Update the UI for the active one.
154- this._updateActive(ev.currentTarget);
155+ if (!this.get('isFullscreen')) {
156+ this._updateActive(ev.currentTarget);
157+ }
158+
159 var change = {
160 charmID: charmID
161 };
162
163- if (this.get('isFullscreen')) {
164- change.viewmode = 'fullscreen';
165- } else {
166- change.viewmode = 'sidebar';
167- }
168-
169 this.fire('viewNavigate', {change: change});
170 },
171
172
173=== modified file 'app/subapps/browser/views/search.js'
174--- app/subapps/browser/views/search.js 2013-04-23 13:31:29 +0000
175+++ app/subapps/browser/views/search.js 2013-04-24 12:51:26 +0000
176@@ -17,7 +17,52 @@
177 ns.BrowserSearchView = Y.Base.create('browser-view-searchview', Y.View, [
178 views.utils.apiFailingView
179 ], {
180+ events: {
181+ '.charm-token': {
182+ click: '_handleCharmSelection'
183+ }
184+ },
185+
186 template: views.Templates.search,
187+
188+ /**
189+ When selecting a charm from the list make sure we re-route the app to
190+ the details view with that charm selected.
191+
192+ @method _handleCharmSelection
193+ @param {Event} ev the click event handler for the charm selected.
194+
195+ */
196+ _handleCharmSelection: function(ev) {
197+ ev.halt();
198+ var charm = ev.currentTarget;
199+ var charmID = charm.getData('charmid');
200+
201+ // Update the UI for the active one.
202+ if (!this.get('isFullscreen')) {
203+ this._updateActive(ev.currentTarget);
204+ }
205+ var change = {
206+ charmID: charmID
207+ };
208+ this.fire('viewNavigate', {change: change});
209+ },
210+
211+ /**
212+ Update the node in the editorial list marked as 'active'.
213+
214+ @method _updateActive
215+ @param {Node} clickTarget the charm-token clicked on to activate.
216+
217+ */
218+ _updateActive: function(clickTarget) {
219+ // Remove the active class from any nodes that have it.
220+ Y.all('.yui3-charmtoken.active').removeClass('active');
221+
222+ // Add it to the current node.
223+ clickTarget.ancestor('.yui3-charmtoken').addClass('active');
224+ },
225+
226 /**
227 * Renders the search results from the the store query.
228 *
229@@ -30,6 +75,9 @@
230 tplNode = Y.Node.create(tpl),
231 container = tplNode.one('.search-results');
232
233+ // Set the container so that our events will delegate based off of it.
234+ this.set('container', container);
235+
236 results.map(function(charm) {
237 var ct = new widgets.browser.CharmToken(charm.getAttrs());
238 ct.render(container);
239
240=== modified file 'test/test_browser_app.js'
241--- test/test_browser_app.js 2013-04-23 19:36:04 +0000
242+++ test/test_browser_app.js 2013-04-24 12:51:26 +0000
243@@ -468,6 +468,66 @@
244 assert.deepEqual(hits, expected);
245 });
246
247+ it('changing the query string dispatches correctly', function() {
248+ var req = {
249+ path: '/bws/fullscreen/search/',
250+ params: {
251+ viewmode: 'fullscreen'
252+ },
253+ query: {
254+ text: 'test'
255+ }
256+ };
257+ browser.routeView(req, undefined, function() {});
258+
259+ // Reset the hits and we should not redraw anything to update the view.
260+ resetHits();
261+ req.query.text = 'test2';
262+
263+ var expected = Y.merge(hits, {
264+ renderSearchResults: true
265+ });
266+ browser.routeView(req, undefined, function() {});
267+ assert.deepEqual(hits, expected);
268+ });
269+
270+ it('no change to query string does not redraw', function() {
271+ var req = {
272+ path: '/bws/fullscreen/search/',
273+ params: {
274+ viewmode: 'fullscreen'
275+ },
276+ query: {
277+ text: 'test'
278+ }
279+ };
280+ browser.routeView(req, undefined, function() {});
281+
282+ // Reset the hits and we should not redraw anything to update the view.
283+ resetHits();
284+
285+ var expected = Y.merge(hits);
286+ browser.routeView(req, undefined, function() {});
287+ assert.deepEqual(hits, expected);
288+ });
289+
290+ it('no querystring still searched', function() {
291+ var req = {
292+ path: '/bws/fullscreen/search/',
293+ params: {
294+ viewmode: 'fullscreen'
295+ }
296+ };
297+
298+ var expected = Y.merge(hits, {
299+ fullscreen: true,
300+ renderSearchResults: true
301+ });
302+
303+ browser.routeView(req, undefined, function() {});
304+ assert.deepEqual(hits, expected);
305+ });
306+
307 });
308 })();
309
310
311=== modified file 'test/test_browser_editorial.js'
312--- test/test_browser_editorial.js 2013-04-23 19:36:04 +0000
313+++ test/test_browser_editorial.js 2013-04-24 12:51:26 +0000
314@@ -106,7 +106,6 @@
315
316 view.on('viewNavigate', function(ev) {
317 ev.halt();
318- assert(ev.change.viewmode === 'fullscreen');
319 assert(ev.change.charmID === 'precise/ceph-7');
320 done();
321 });
322@@ -134,7 +133,6 @@
323
324 view.on('viewNavigate', function(ev) {
325 ev.halt();
326- assert(ev.change.viewmode === 'sidebar');
327 assert(ev.change.charmID === 'precise/ceph-7');
328 done();
329 });
330
331=== modified file 'test/test_browser_search_view.js'
332--- test/test_browser_search_view.js 2013-04-22 21:04:27 +0000
333+++ test/test_browser_search_view.js 2013-04-24 12:51:26 +0000
334@@ -12,6 +12,7 @@
335 'json',
336 'juju-charm-store',
337 'node',
338+ 'node-event-simulate',
339 'subapp-browser-searchview',
340 function(Y) {
341 done();
342@@ -75,4 +76,27 @@
343 view.render();
344 assert.equal('charms?text=', apiURL);
345 });
346+
347+ it('clicking a charm navigates for fullscreen', function(done) {
348+ view.render();
349+ view.on('viewNavigate', function(ev) {
350+ ev.halt();
351+ assert(ev.change.charmID === 'foo/bar-2');
352+ done();
353+ });
354+
355+ container.one('.charm-token').simulate('click');
356+ });
357+
358+ it('clicking a charm navigates for sidebar', function(done) {
359+ view.render();
360+ view.on('viewNavigate', function(ev) {
361+ ev.halt();
362+ assert(ev.change.charmID === 'foo/bar-2');
363+ done();
364+ });
365+
366+ container.one('.charm-token').simulate('click');
367+ });
368+
369 });

Subscribers

People subscribed via source and target branches