Merge lp:~jcsackett/juju-gui/search-routing-and-view into lp:juju-gui/experimental
- search-routing-and-view
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 598 |
Proposed branch: | lp:~jcsackett/juju-gui/search-routing-and-view |
Merge into: | lp:juju-gui/experimental |
Diff against target: |
803 lines (+365/-116) 14 files modified
app/modules-debug.js (+5/-2) app/subapps/browser/browser.js (+41/-11) app/subapps/browser/templates/search.handlebars (+4/-0) app/subapps/browser/views/charm.js (+3/-14) app/subapps/browser/views/editorial.js (+20/-24) app/subapps/browser/views/search.js (+110/-0) app/subapps/browser/views/view.js (+12/-26) app/templates/browser-search.handlebars (+1/-1) app/views/utils.js (+56/-9) app/widgets/charm-search.js (+21/-5) test/index.html (+10/-8) test/test_browser_charm_details.js (+0/-1) test/test_browser_search_view.js (+78/-0) test/test_browser_search_widget.js (+4/-15) |
To merge this branch: | bzr merge lp:~jcsackett/juju-gui/search-routing-and-view |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+160110@code.launchpad.net |
Commit message
Description of the change
Adds search functionality
This adds text search from the search widget
-Search widget propogates search text to browser subapp
-Subapp routes data to search view
-Search view does query from text
-Search view can render search results
As a driveby, this moves repeated apiFailure code out to an extension.
There is an issue outstanding:
-The querystring can be eaten by the routing code in some cases. A follow up
branch is in progress for this problem.
j.c.sackett (jcsackett) wrote : | # |
Richard Harding (rharding) wrote : | # |
LGTM With some comments
- I really think the apiFailure as extension makes a lot of sense and
will be cleaner.
- I think that renderSearchRes
confusion we had as search is two parts. The widget/input, and the
rendering view of results.
- We also need to make sure to note that we need a way to 'undo' search.
There needs to be a way to reset back to the default views and get
editorial content. I had thought an empty search input valueChange would
do it, but it's been brought up that the search term being empty doesn't
mean 'do no search'.
https:/
File app/modules-
https:/
app/modules-
'/juju-
Can we add this to the existing view utils? Maybe it's worthwhile to
sync this code up as long as it doesn't collide with anything.
https:/
File app/subapps/
https:/
app/subapps/
'search') {
I wonder if there's some way to make this work like a python web app and
hit only the first route. Then we'd not need to worry about the /search
and /*id collision since we've gone this route.
https:/
File app/subapps/
https:/
app/subapps/
results found</h3>
check the css used in the other headings and try to match up. Huw's got
a class='
step.
https:/
File app/subapps/
https:/
app/subapps/
Y.juju.
Maybe this is a diff bug or something, but this might be great as an
extension you can stick on the view so that the right scope is there and
you can tack it onto any view.
https:/
File app/subapps/
https:/
app/subapps/
typo?
https:/
File app/subapps/
https:/
app/subapps/
does it make sense to use a charm-container here with a larger default
count?
https:/
Jeff Pihach (hatch) wrote : | # |
LGTM - I'd like to see that apiFailure util moved into an extension as
it's not really a utility method because it requires the view instance
to be passed in
https:/
File app/subapps/
https:/
app/subapps/
'search') {
That could be why the *id is undocumented as it brings with it this
issue :)
https:/
app/subapps/
'fullscreen') {
because this is a route callback the req object will have the param for
:viewmode which you should probably use here.
https:/
File app/subapps/
https:/
app/subapps/
request, view) {
This should probably be an extension to the views because it isn't
really a utility method as it requires the view to function.
https:/
File app/subapps/
https:/
app/subapps/
Y.juju.
see comment about view extension in utils
https:/
File test/test_
https:/
test/test_
search string', function() {
should support clearning
small typo.
Curtis Hovey (sinzui) wrote : | # |
Hi Jon. Thank you for the branch. I have a question about a test.
https:/
File app/subapps/
https:/
app/subapps/
class="
the div is not subordinate to the heading, it does not need to be
indented.
https:/
File test/test_
https:/
test/test_
Do we need a query string test for the cause when there isn't one? I see
renderSearch in app/subapps/
with with {text: ''}.
- 575. By j.c.sackett
-
Merged trunk.
- 576. By j.c.sackett
-
Made the apiFailure part of an extension and put in main view utils.
- 577. By j.c.sackett
-
Fixed search template.
- 578. By j.c.sackett
-
Removed the apifailure stuff from mainview; it's easily shared via extension on views that need it.
- 579. By j.c.sackett
-
Fixed typos and formatting.
- 580. By j.c.sackett
-
Fixes from review.
- 581. By j.c.sackett
-
Fixed test string from review.
- 582. By j.c.sackett
-
Remaining changes from review.
j.c.sackett (jcsackett) wrote : | # |
Comments addressed; changes coming in submission.
https:/
File app/modules-
https:/
app/modules-
'/juju-
On 2013/04/22 14:08:15, rharding wrote:
> Can we add this to the existing view utils? Maybe it's worthwhile to
sync this
> code up as long as it doesn't collide with anything.
Done.
https:/
File app/subapps/
https:/
app/subapps/
'search') {
On 2013/04/22 14:20:55, jeff.pihach wrote:
> That could be why the *id is undocumented as it brings with it this
issue :)
I've got a follow up branch to deal with the two outstanding issues
referenced in this proposal; I'll investigate this in that branch.
https:/
app/subapps/
'fullscreen') {
On 2013/04/22 14:20:55, jeff.pihach wrote:
> because this is a route callback the req object will have the param
for
> :viewmode which you should probably use here.
Done, thanks.
https:/
File app/subapps/
https:/
app/subapps/
results found</h3>
On 2013/04/22 14:08:15, rharding wrote:
> check the css used in the other headings and try to match up. Huw's
got a
> class='
step.
Done, thanks.
https:/
app/subapps/
class="
On 2013/04/22 14:33:07, curtis wrote:
> the div is not subordinate to the heading, it does not need to be
indented.
Fixed, thanks
https:/
File app/subapps/
https:/
app/subapps/
Y.juju.
On 2013/04/22 14:08:15, rharding wrote:
> Maybe this is a diff bug or something, but this might be great as an
extension
> you can stick on the view so that the right scope is there and you can
tack it
> onto any view.
Done.
https:/
File app/subapps/
https:/
app/subapps/
On 2013/04/22 14:08:15, rharding wrote:
> typo?
Fixed.
- 583. By j.c.sackett
-
Testfix.
Curtis Hovey (sinzui) wrote : | # |
LGTM, thanks.
- 584. By j.c.sackett
-
Revert a typo, silencing lint.
- 585. By j.c.sackett
-
Merged trunk.
- 586. By j.c.sackett
-
Removed stray debugger statement.
- 587. By j.c.sackett
-
Comments.
j.c.sackett (jcsackett) wrote : | # |
*** Submitted:
Adds search functionality
This adds text search from the search widget
-Search widget propogates search text to browser subapp
-Subapp routes data to search view
-Search view does query from text
-Search view can render search results
As a driveby, this moves repeated apiFailure code out to an extension.
There is an issue outstanding:
-The querystring can be eaten by the routing code in some cases. A
follow up
branch is in progress for this problem.
R=rharding, jeff.pihach, curtis
CC=
https:/
Preview Diff
1 | === modified file 'app/modules-debug.js' |
2 | --- app/modules-debug.js 2013-04-13 00:18:21 +0000 |
3 | +++ app/modules-debug.js 2013-04-23 13:47:38 +0000 |
4 | @@ -288,6 +288,10 @@ |
5 | fullpath: '/juju-ui/subapps/browser/views/fullscreen.js' |
6 | }, |
7 | |
8 | + 'subapp-browser-searchview': { |
9 | + fullpath: '/juju-ui/subapps/browser/views/search.js' |
10 | + }, |
11 | + |
12 | 'subapp-browser-sidebar': { |
13 | fullpath: '/juju-ui/subapps/browser/views/sidebar.js', |
14 | requires: [ |
15 | @@ -298,8 +302,7 @@ |
16 | }, |
17 | |
18 | 'subapp-browser-editorial': { |
19 | - fullpath: '/juju-ui/subapps/browser/views/editorial.js', |
20 | - requires: ['subapp-browser-sidebar'] |
21 | + fullpath: '/juju-ui/subapps/browser/views/editorial.js' |
22 | }, |
23 | |
24 | //Browser Models |
25 | |
26 | === modified file 'app/subapps/browser/browser.js' |
27 | --- app/subapps/browser/browser.js 2013-04-22 17:11:25 +0000 |
28 | +++ app/subapps/browser/browser.js 2013-04-23 13:47:38 +0000 |
29 | @@ -59,9 +59,14 @@ |
30 | if (this._viewState.charmID) { |
31 | urlParts.push(this._viewState.charmID); |
32 | } |
33 | - |
34 | - // Always end on a / |
35 | - return urlParts.join('/'); |
36 | + var url = urlParts.join('/'); |
37 | + if (this._viewState.querystring) { |
38 | + url = Y.Lang.sub('{ url }?{ qs }', { |
39 | + url: url, |
40 | + qs: this._viewState.querystring |
41 | + }); |
42 | + } |
43 | + return url; |
44 | }, |
45 | |
46 | /** |
47 | @@ -197,7 +202,7 @@ |
48 | this._viewState.viewmode = params.viewmode; |
49 | |
50 | // Check for a charm id in the request. |
51 | - if (params.id) { |
52 | + if (params.id && params.id !== 'search') { |
53 | this._viewState.charmID = params.id; |
54 | } else { |
55 | this._viewState.charmID = null; |
56 | @@ -264,9 +269,9 @@ |
57 | }, |
58 | |
59 | /** |
60 | - * Render the sidebar view of a specific charm to the client. |
61 | + * Render the charm details view |
62 | * |
63 | - * @method sidebarCharm |
64 | + * @method renderCharmDetails |
65 | * @param {Request} req current request object. |
66 | * @param {Response} res current response object. |
67 | * @param {function} next callable for the next route in the chain. |
68 | @@ -314,7 +319,6 @@ |
69 | renderEditorial: function(req, res, next) { |
70 | // If loading the interesting content then it's not a search going on. |
71 | var container = this.get('container'), |
72 | - editorialContainer, |
73 | extraCfg = {}; |
74 | |
75 | if (this._viewState.viewmode === 'fullscreen') { |
76 | @@ -343,11 +347,35 @@ |
77 | }, |
78 | |
79 | /** |
80 | - Place holder for a method to render out search so we can test url parsing |
81 | - |
82 | - */ |
83 | + * Render search results |
84 | + * |
85 | + * @method renderSearchResults |
86 | + * @param {Request} req current request object. |
87 | + * @param {Response} res current response object. |
88 | + * @param {function} next callable for the next route in the chain. |
89 | + */ |
90 | renderSearchResults: function(req, res, next) { |
91 | - console.log('rendered search results.'); |
92 | + var container = this.get('container'), |
93 | + extraCfg = {}, |
94 | + query; |
95 | + if (this._viewState.querystring) { |
96 | + query = Y.QueryString.parse(this._viewState.querystring); |
97 | + } else { |
98 | + // If there's no querystring, we need a default "empty" search. |
99 | + query = {text: ''}; |
100 | + } |
101 | + |
102 | + if (req.params.viewmode === 'fullscreen') { |
103 | + extraCfg.renderTo = container.one('.bws-view-data'); |
104 | + extraCfg.isFullscreen = true; |
105 | + } else { |
106 | + extraCfg.renderTo = container.one('.bws-content'); |
107 | + } |
108 | + extraCfg.text = query.text; |
109 | + this._search = new Y.juju.browser.views.BrowserSearchView( |
110 | + this._getViewCfg(extraCfg)); |
111 | + this._search.render(); |
112 | + this._search.addTarget(this); |
113 | }, |
114 | |
115 | /** |
116 | @@ -539,10 +567,12 @@ |
117 | requires: [ |
118 | 'juju-charm-store', |
119 | 'juju-models', |
120 | + 'querystring-parse', |
121 | 'sub-app', |
122 | 'subapp-browser-charmview', |
123 | 'subapp-browser-editorial', |
124 | 'subapp-browser-fullscreen', |
125 | + 'subapp-browser-searchview', |
126 | 'subapp-browser-sidebar' |
127 | ] |
128 | }); |
129 | |
130 | === added file 'app/subapps/browser/templates/search.handlebars' |
131 | --- app/subapps/browser/templates/search.handlebars 1970-01-01 00:00:00 +0000 |
132 | +++ app/subapps/browser/templates/search.handlebars 2013-04-23 13:47:38 +0000 |
133 | @@ -0,0 +1,4 @@ |
134 | +<div id="bws-search"> |
135 | + <h3 class="section-title">{{ count }} results found</h3> |
136 | + <div class="search-results"></div> |
137 | +</div> |
138 | |
139 | === modified file 'app/subapps/browser/views/charm.js' |
140 | --- app/subapps/browser/views/charm.js 2013-04-19 21:01:13 +0000 |
141 | +++ app/subapps/browser/views/charm.js 2013-04-23 13:47:38 +0000 |
142 | @@ -18,7 +18,8 @@ |
143 | */ |
144 | ns.BrowserCharmView = Y.Base.create('browser-view-charmview', Y.View, [ |
145 | widgets.browser.IndicatorManager, |
146 | - Y.Event.EventTracker |
147 | + Y.Event.EventTracker, |
148 | + views.utils.apiFailingView |
149 | ], { |
150 | |
151 | template: views.Templates.browser_charm, |
152 | @@ -77,19 +78,7 @@ |
153 | * |
154 | */ |
155 | apiFailure: function(data, request) { |
156 | - var message; |
157 | - if (data && data.type) { |
158 | - message = 'Charm API error of type: ' + data.type; |
159 | - } else { |
160 | - message = 'Charm API server did not respond'; |
161 | - } |
162 | - this.get('db').notifications.add( |
163 | - new models.Notification({ |
164 | - title: 'Failed to load sidebar content.', |
165 | - message: message, |
166 | - level: 'error' |
167 | - }) |
168 | - ); |
169 | + this._apiFailure(data, request, 'Failed to load charm details.'); |
170 | }, |
171 | |
172 | /** |
173 | |
174 | === modified file 'app/subapps/browser/views/editorial.js' |
175 | --- app/subapps/browser/views/editorial.js 2013-04-19 17:09:07 +0000 |
176 | +++ app/subapps/browser/views/editorial.js 2013-04-23 13:47:38 +0000 |
177 | @@ -22,7 +22,9 @@ |
178 | * @extends {juju.browser.views.Editorial} |
179 | * |
180 | */ |
181 | - ns.EditorialView = Y.Base.create('browser-view-sidebar', Y.View, [], { |
182 | + ns.EditorialView = Y.Base.create('browser-view-sidebar', Y.View, [ |
183 | + views.utils.apiFailingView |
184 | + ], { |
185 | template: views.Templates.editorial, |
186 | |
187 | events: { |
188 | @@ -51,6 +53,18 @@ |
189 | }, |
190 | |
191 | /** |
192 | + * Generates a message to the user based on a bad api call. |
193 | + * |
194 | + * @method apiFailure |
195 | + * @param {Object} data the json decoded response text. |
196 | + * @param {Object} request the original io_request object for debugging. |
197 | + * |
198 | + */ |
199 | + apiFailure: function(data, request) { |
200 | + this._apiFailure(data, request, 'Failed to load sidebar content.'); |
201 | + }, |
202 | + |
203 | + /** |
204 | * General YUI initializer. |
205 | * |
206 | * @method initializer |
207 | @@ -131,21 +145,7 @@ |
208 | ]; |
209 | }, |
210 | |
211 | - 'failure': function(data, request) { |
212 | - var message; |
213 | - if (data && data.type) { |
214 | - message = 'Charm API error of type: ' + data.type; |
215 | - } else { |
216 | - message = 'Charm API server did not respond'; |
217 | - } |
218 | - this.get('db').notifications.add( |
219 | - new models.Notification({ |
220 | - title: 'Failed to load landing page content.', |
221 | - message: message, |
222 | - level: 'error' |
223 | - }) |
224 | - ); |
225 | - } |
226 | + 'failure': this.apiFailure |
227 | }, this); |
228 | }, |
229 | |
230 | @@ -172,33 +172,28 @@ |
231 | * @attribute isFullscreen |
232 | * @default false |
233 | * @type {Boolean} |
234 | - * |
235 | */ |
236 | isFullscreen: { |
237 | value: false |
238 | }, |
239 | + |
240 | /** |
241 | * What is the container node we should render our container into? |
242 | * |
243 | * @attribute renderTo |
244 | * @default undefined |
245 | * @type {Node} |
246 | - * |
247 | */ |
248 | - renderTo: { |
249 | + renderTo: {}, |
250 | |
251 | - }, |
252 | /** |
253 | * The Charmworld0 Api store instance for loading content. |
254 | * |
255 | * @attribute store |
256 | * @default undefined |
257 | * @type {Charmworld0} |
258 | - * |
259 | */ |
260 | - store: { |
261 | - |
262 | - } |
263 | + store: {} |
264 | } |
265 | }); |
266 | |
267 | @@ -210,6 +205,7 @@ |
268 | 'juju-charm-store', |
269 | 'juju-models', |
270 | 'juju-templates', |
271 | + 'juju-view-utils', |
272 | 'view' |
273 | ] |
274 | }); |
275 | |
276 | === added file 'app/subapps/browser/views/search.js' |
277 | --- app/subapps/browser/views/search.js 1970-01-01 00:00:00 +0000 |
278 | +++ app/subapps/browser/views/search.js 2013-04-23 13:47:38 +0000 |
279 | @@ -0,0 +1,110 @@ |
280 | +'use strict'; |
281 | + |
282 | + |
283 | +/** |
284 | + * Provides searching functionality for the charm browser. |
285 | + * |
286 | + * @namespace juju |
287 | + * @module browser |
288 | + * @submodule views |
289 | + */ |
290 | +YUI.add('subapp-browser-searchview', function(Y) { |
291 | + var ns = Y.namespace('juju.browser.views'), |
292 | + views = Y.namespace('juju.views'), |
293 | + widgets = Y.namespace('juju.widgets'), |
294 | + models = Y.namespace('juju.models'); |
295 | + |
296 | + ns.BrowserSearchView = Y.Base.create('browser-view-searchview', Y.View, [ |
297 | + views.utils.apiFailingView |
298 | + ], { |
299 | + template: views.Templates.search, |
300 | + /** |
301 | + * Renders the search results from the the store query. |
302 | + * |
303 | + * @method _renderSearchResults |
304 | + * @param {Y.Node} container Optional container to render results to. |
305 | + */ |
306 | + _renderSearchResults: function(results) { |
307 | + var target = this.get('renderTo'), |
308 | + tpl = this.template({count: results.size()}), |
309 | + tplNode = Y.Node.create(tpl), |
310 | + container = tplNode.one('.search-results'); |
311 | + |
312 | + results.map(function(charm) { |
313 | + var ct = new widgets.browser.CharmToken(charm.getAttrs()); |
314 | + ct.render(container); |
315 | + }); |
316 | + target.setHTML(tplNode); |
317 | + }, |
318 | + |
319 | + /** |
320 | + * Generates a message to the user based on a bad api call. |
321 | + * |
322 | + * @method apiFailure |
323 | + * @param {Object} data the json decoded response text. |
324 | + * @param {Object} request the original io_request object for debugging. |
325 | + * |
326 | + */ |
327 | + apiFailure: function(data, request) { |
328 | + this._apiFailure(data, request, 'Failed to load search results.'); |
329 | + }, |
330 | + |
331 | + /** |
332 | + * Renders the searchview, rendering search results for the view's search |
333 | + * text. |
334 | + * |
335 | + * @method render |
336 | + */ |
337 | + render: function() { |
338 | + var text = this.get('text'); |
339 | + this.get('store').search(text, { |
340 | + 'success': function(data) { |
341 | + var results = this.get('store').resultsToCharmlist(data.result); |
342 | + this._renderSearchResults(results); |
343 | + }, |
344 | + 'failure': this.apiFailure |
345 | + }, this); |
346 | + } |
347 | + }, { |
348 | + ATTRS: { |
349 | + /** |
350 | + * The container node the view is rendering to. |
351 | + * |
352 | + * @attribute renderTo |
353 | + * @default undefined |
354 | + * @type {Y.Node} |
355 | + */ |
356 | + renderTo: {}, |
357 | + |
358 | + /** |
359 | + * An instance of the Charmworld API object to hit for any data that |
360 | + * needs fetching. |
361 | + * |
362 | + * @attribute store |
363 | + * @default undefined |
364 | + * @type {Charmworld0} |
365 | + * |
366 | + */ |
367 | + store: {}, |
368 | + |
369 | + /** |
370 | + * The text being searched on |
371 | + * |
372 | + * @attribute text |
373 | + * @default '' |
374 | + * @type {String} |
375 | + */ |
376 | + text: {} |
377 | + } |
378 | + }); |
379 | + |
380 | +}, '0.1.0', { |
381 | + requires: [ |
382 | + 'base-build', |
383 | + 'browser-charm-token', |
384 | + 'browser-overlay-indicator', |
385 | + 'event-tracker', |
386 | + 'juju-view-utils', |
387 | + 'view' |
388 | + ] |
389 | +}); |
390 | |
391 | === modified file 'app/subapps/browser/views/view.js' |
392 | --- app/subapps/browser/views/view.js 2013-04-16 23:39:54 +0000 |
393 | +++ app/subapps/browser/views/view.js 2013-04-23 13:47:38 +0000 |
394 | @@ -107,7 +107,17 @@ |
395 | * |
396 | */ |
397 | _searchChanged: function(ev) { |
398 | - console.log('search changed.'); |
399 | + // NB: This is temporary; eventually filtering will include categories, |
400 | + // and the Filter object will handle qs generation. But it's an unwieldy |
401 | + // url to parse while we only support text search. |
402 | + var qs = Y.QueryString.stringify({text: ev.details[0]}); |
403 | + var change = { |
404 | + search: true, |
405 | + querystring: qs |
406 | + }; |
407 | + this.fire('viewNavigate', { |
408 | + change: change |
409 | + }); |
410 | }, |
411 | |
412 | /** |
413 | @@ -151,31 +161,6 @@ |
414 | }, |
415 | |
416 | /** |
417 | - * Shared method to generate a message to the user based on a bad api |
418 | - * call. |
419 | - * |
420 | - * @method apiFailure |
421 | - * @param {Object} data the json decoded response text. |
422 | - * @param {Object} request the original io_request object for debugging. |
423 | - * |
424 | - */ |
425 | - apiFailure: function(data, request) { |
426 | - var message; |
427 | - if (data && data.type) { |
428 | - message = 'Charm API error of type: ' + data.type; |
429 | - } else { |
430 | - message = 'Charm API server did not respond'; |
431 | - } |
432 | - this.get('db').notifications.add( |
433 | - new models.Notification({ |
434 | - title: 'Failed to load sidebar content.', |
435 | - message: message, |
436 | - level: 'error' |
437 | - }) |
438 | - ); |
439 | - }, |
440 | - |
441 | - /** |
442 | * Destroy this view and clear from the dom world. |
443 | * |
444 | * @method destructor |
445 | @@ -269,6 +254,7 @@ |
446 | 'event-tracker', |
447 | 'juju-charm-store', |
448 | 'juju-models', |
449 | + 'querystring-stringify', |
450 | 'view' |
451 | ] |
452 | }); |
453 | |
454 | === modified file 'app/templates/browser-search.handlebars' |
455 | --- app/templates/browser-search.handlebars 2013-04-16 21:58:19 +0000 |
456 | +++ app/templates/browser-search.handlebars 2013-04-23 13:47:38 +0000 |
457 | @@ -6,7 +6,7 @@ |
458 | <div class="bws-searchbox"> |
459 | <form> |
460 | <input type="search" name="bws-search" |
461 | - value="{{ term }}" |
462 | + value="{{ text }}" |
463 | placeholder="Search…"/> |
464 | </form> |
465 | </div> |
466 | |
467 | === modified file 'app/views/utils.js' |
468 | --- app/views/utils.js 2013-04-15 15:45:18 +0000 |
469 | +++ app/views/utils.js 2013-04-23 13:47:38 +0000 |
470 | @@ -1,15 +1,16 @@ |
471 | 'use strict'; |
472 | |
473 | + |
474 | /** |
475 | * The view utils. |
476 | * |
477 | * @module views |
478 | * @submodule views.utils |
479 | */ |
480 | - |
481 | YUI.add('juju-view-utils', function(Y) { |
482 | |
483 | var views = Y.namespace('juju.views'), |
484 | + models = Y.namespace('juju.models'), |
485 | utils = Y.namespace('juju.views.utils'); |
486 | |
487 | /*jshint bitwise: false*/ |
488 | @@ -1288,13 +1289,59 @@ |
489 | } |
490 | }); |
491 | |
492 | + /** |
493 | + * Extension for views to provide an apiFailure method. |
494 | + * |
495 | + * @class apiFailure |
496 | + */ |
497 | + utils.apiFailingView = function() { |
498 | + this._initAPIFailingView(); |
499 | + }; |
500 | + utils.apiFailingView.prototype = { |
501 | + /** |
502 | + * Constructor |
503 | + * |
504 | + * @method _initAPIFailingView |
505 | + */ |
506 | + _initAPIFailingView: function() {}, |
507 | + |
508 | + /** |
509 | + * Shared method to generate a message to the user based on a bad api |
510 | + * call from a view. |
511 | + * |
512 | + * @method apiFailure |
513 | + * @param {Object} data the json decoded response text. |
514 | + * @param {Object} request the original io_request object for debugging. |
515 | + * @param {String} title the title for the generated notification. |
516 | + */ |
517 | + _apiFailure: function(data, request, title) { |
518 | + var message; |
519 | + if (data && data.type) { |
520 | + message = 'Charm API error of type: ' + data.type; |
521 | + } else { |
522 | + message = 'Charm API server did not respond'; |
523 | + } |
524 | + if (!title) { |
525 | + title = 'Unidentified API failure'; |
526 | + } |
527 | + this.get('db').notifications.add( |
528 | + new models.Notification({ |
529 | + title: title, |
530 | + message: message, |
531 | + level: 'error' |
532 | + }) |
533 | + ); |
534 | + } |
535 | + }; |
536 | }, '0.1.0', { |
537 | - requires: ['base-build', |
538 | - 'handlebars', |
539 | - 'node', |
540 | - 'view', |
541 | - 'panel', |
542 | - 'json-stringify', |
543 | - 'gallery-markdown', |
544 | - 'datatype-date-format'] |
545 | + requires: [ |
546 | + 'base-build', |
547 | + 'handlebars', |
548 | + 'node', |
549 | + 'view', |
550 | + 'panel', |
551 | + 'json-stringify', |
552 | + 'gallery-markdown', |
553 | + 'datatype-date-format' |
554 | + ] |
555 | }); |
556 | |
557 | === modified file 'app/widgets/charm-search.js' |
558 | --- app/widgets/charm-search.js 2013-04-09 17:00:45 +0000 |
559 | +++ app/widgets/charm-search.js 2013-04-23 13:47:38 +0000 |
560 | @@ -34,6 +34,17 @@ |
561 | TEMPLATE: templates['browser-search'], |
562 | |
563 | /** |
564 | + * Halt page reload from form submit and let the app know we have a new |
565 | + * search. |
566 | + * |
567 | + * @method _handleSubmit |
568 | + * @param {Event} ev the submit event. |
569 | + */ |
570 | + _handleSubmit: function(ev) { |
571 | + ev.halt(); |
572 | + this.fire(this.EVT_UPDATE_SEARCH, this.get('text')); |
573 | + }, |
574 | + /** |
575 | * Expose to the outside world that we've got a request to go fullscreen. |
576 | * |
577 | * @method _toggleFullScreen |
578 | @@ -76,6 +87,10 @@ |
579 | container.one('.toggle-fullscreen').on( |
580 | 'click', this._toggleFullScreen, this) |
581 | ); |
582 | + this.addEvent( |
583 | + container.one('form').on( |
584 | + 'submit', this._handleSubmit, this) |
585 | + ); |
586 | |
587 | // Note that the search could be updated either from our internal input |
588 | // control, or it could come from someone outside of the widget asking |
589 | @@ -84,6 +99,7 @@ |
590 | var input = container.one('input'); |
591 | this.addEvent( |
592 | input.on('valueChange', function(ev) { |
593 | + this.set('text', ev.newVal); |
594 | this.fire(this.EVT_SEARCH_CHANGED); |
595 | }, this) |
596 | ); |
597 | @@ -155,13 +171,13 @@ |
598 | }, |
599 | |
600 | /** |
601 | - * @attribute term |
602 | - * @default undefined |
603 | + * The search text. |
604 | + * |
605 | + * @attribute text |
606 | + * @default '' |
607 | * @type {String} |
608 | - * |
609 | */ |
610 | - term: {} |
611 | - |
612 | + text: {} |
613 | } |
614 | }); |
615 | |
616 | |
617 | === modified file 'test/index.html' |
618 | --- test/index.html 2013-04-10 11:57:11 +0000 |
619 | +++ test/index.html 2013-04-23 13:47:38 +0000 |
620 | @@ -30,36 +30,38 @@ |
621 | |
622 | <!-- Tests (Alphabetical)--> |
623 | |
624 | + <script src="test_app_hotkeys.js"></script> |
625 | <script src="test_app.js"></script> |
626 | - <script src="test_app_hotkeys.js"></script> |
627 | <script src="test_application_notifications.js"></script> |
628 | <script src="test_browser_app.js"></script> |
629 | <script src="test_browser_charm_details.js"></script> |
630 | <script src="test_browser_models.js"></script> |
631 | + <script src="test_browser_search_view.js"></script> |
632 | + <script src="test_browser_search_widget.js"></script> |
633 | + <script src="test_browser_view_utils.js"></script> |
634 | <script src="test_charm_collection_view.js"></script> |
635 | <script src="test_charm_configuration.js"></script> |
636 | <script src="test_charm_container.js"></script> |
637 | <script src="test_charm_panel.js"></script> |
638 | + <script src="test_charm_store.js"></script> |
639 | <script src="test_charm_token.js"></script> |
640 | - <script src="test_browser_search_widget.js"></script> |
641 | - <script src="test_charm_store.js"></script> |
642 | <script src="test_charm_view.js"></script> |
643 | <script src="test_console.js"></script> |
644 | <script src="test_d3_components.js"></script> |
645 | <script src="test_endpoints.js"></script> |
646 | + <script src="test_env_go.js"></script> |
647 | + <script src="test_environment_view.js"></script> |
648 | <script src="test_env.js"></script> |
649 | + <script src="test_env_python.js"></script> |
650 | <script src="test_event_tracker.js"></script> |
651 | - <script src="test_env_go.js"></script> |
652 | - <script src="test_env_python.js"></script> |
653 | - <script src="test_environment_view.js"></script> |
654 | <script src="test_fakebackend.js"></script> |
655 | - <script src="test_overlay_indicator.js"></script> |
656 | <script src="test_landscape.js"></script> |
657 | <script src="test_login.js"></script> |
658 | + <script src="test_model_handlers.js"></script> |
659 | <script src="test_model.js"></script> |
660 | - <script src="test_model_handlers.js"></script> |
661 | <script src="test_notifications.js"></script> |
662 | <script src="test_notifier_widget.js"></script> |
663 | + <script src="test_overlay_indicator.js"></script> |
664 | <script src="test_panzoom.js"></script> |
665 | <script src="test_prettify.js"></script> |
666 | <script src="test_routing.js"></script> |
667 | |
668 | === modified file 'test/test_browser_charm_details.js' |
669 | --- test/test_browser_charm_details.js 2013-04-22 15:40:54 +0000 |
670 | +++ test/test_browser_charm_details.js 2013-04-23 13:47:38 +0000 |
671 | @@ -46,7 +46,6 @@ |
672 | delete window.juju_config; |
673 | }); |
674 | |
675 | - // Ensure the search results are rendered inside the container. |
676 | it('should be able to locate a readme file', function() { |
677 | view = new CharmView({ |
678 | charm: new models.BrowserCharm({ |
679 | |
680 | === added file 'test/test_browser_search_view.js' |
681 | --- test/test_browser_search_view.js 1970-01-01 00:00:00 +0000 |
682 | +++ test/test_browser_search_view.js 2013-04-23 13:47:38 +0000 |
683 | @@ -0,0 +1,78 @@ |
684 | +'use strict'; |
685 | + |
686 | + |
687 | +describe('search view', function() { |
688 | + var apiURL, |
689 | + container, |
690 | + view, |
691 | + Y; |
692 | + |
693 | + before(function(done) { |
694 | + Y = YUI(GlobalConfig).use( |
695 | + 'json', |
696 | + 'juju-charm-store', |
697 | + 'node', |
698 | + 'subapp-browser-searchview', |
699 | + function(Y) { |
700 | + done(); |
701 | + }); |
702 | + }); |
703 | + |
704 | + beforeEach(function() { |
705 | + // Mock out a dummy location for the Store used in view instances. |
706 | + window.juju_config = {charmworldURL: 'http://localhost'}; |
707 | + container = Y.Node.create('<div id="container"></div>'); |
708 | + Y.one('body').append(container); |
709 | + view = new Y.juju.browser.views.BrowserSearchView({text: 'foo'}); |
710 | + // |
711 | + // Create monkeypatched store to verify right method is called. |
712 | + apiURL = ''; |
713 | + var fakeStore = new Y.juju.Charmworld0({}); |
714 | + var sampleData = { |
715 | + result: [{ |
716 | + id: 'foo/bar-2', |
717 | + name: 'bar', |
718 | + description: 'some charm named bar' |
719 | + }] |
720 | + }; |
721 | + fakeStore.set('datasource', { |
722 | + sendRequest: function(params) { |
723 | + // Stubbing the server callback value |
724 | + apiURL = params.request; |
725 | + params.callback.success({ |
726 | + response: { |
727 | + results: [{ |
728 | + responseText: Y.JSON.stringify(sampleData) |
729 | + }] |
730 | + } |
731 | + }); |
732 | + } |
733 | + }); |
734 | + view.set('store', fakeStore); |
735 | + view.set('renderTo', container); |
736 | + }); |
737 | + |
738 | + afterEach(function() { |
739 | + delete window.juju_config; |
740 | + view.destroy(); |
741 | + container.remove(true); |
742 | + }); |
743 | + |
744 | + it('exists', function() { |
745 | + assert.isObject(view); |
746 | + }); |
747 | + |
748 | + it('renders correctly', function() { |
749 | + view.render(); |
750 | + assert.equal('charms?text=foo', apiURL); |
751 | + assert.equal(1, Y.all('.yui3-charmtoken').size()); |
752 | + var charmText = Y.one('.yui3-charmtoken').one('.title').get('text'); |
753 | + assert.equal(charmText.replace(/\s+/g, ''), 'bar'); |
754 | + }); |
755 | + |
756 | + it('handles empty text for search', function() { |
757 | + view.set('text', ''); |
758 | + view.render(); |
759 | + assert.equal('charms?text=', apiURL); |
760 | + }); |
761 | +}); |
762 | |
763 | === modified file 'test/test_browser_search_widget.js' |
764 | --- test/test_browser_search_widget.js 2013-03-25 19:12:45 +0000 |
765 | +++ test/test_browser_search_widget.js 2013-04-23 13:47:38 +0000 |
766 | @@ -44,6 +44,7 @@ |
767 | // now trigger the event and make sure that it fired to our custom |
768 | // watcher outside the widget. |
769 | triggered.should.eql(true); |
770 | + assert.equal('test', this.get('text')); |
771 | done(); |
772 | }); |
773 | |
774 | @@ -60,20 +61,9 @@ |
775 | container.one('input').get('value').should.eql('test'); |
776 | }); |
777 | |
778 | - it('should supports clearing search string', function() { |
779 | - var search = new Search({ |
780 | - term: 'test' |
781 | - }); |
782 | - search.render(container); |
783 | - container.one('input').get('value').should.eql('test'); |
784 | - |
785 | - search.clearSearch(); |
786 | - container.one('input').get('value').should.eql(''); |
787 | - }); |
788 | - |
789 | - it('should supports clearing search string', function() { |
790 | - var search = new Search({ |
791 | - term: 'test' |
792 | + it('should support clearing search string', function() { |
793 | + var search = new Search({ |
794 | + text: 'test' |
795 | }); |
796 | search.render(container); |
797 | container.one('input').get('value').should.eql('test'); |
798 | @@ -109,5 +99,4 @@ |
799 | toggle.simulate('click'); |
800 | triggered.should.eql(true); |
801 | }); |
802 | - |
803 | }); |
Reviewers: mp+160110_ code.launchpad. net,
Message:
Please take a look.
Description:
Adds search functionality
This adds text search from the search widget
-Search widget propogates search text to browser subapp
-Subapp routes data to search view
-Search view does query from text
-Search view can render search results
As a driveby, this moves repeated apiFailure code out to a utility browser. views.utils
function in
Y.juju.
There are two issues outstanding:
-Fullscreen should work, but doesn't because of rendering problems with
the
fullscreen view (being taken care of by Huw)
-The querystring can be eaten by the routing code in some cases. A
follow up
branch is in progress for this problem.
https:/ /code.launchpad .net/~jcsackett /juju-gui/ search- routing- and-view/ +merge/ 160110
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/8910043/
Affected files: debug.js browser/ browser. js browser/ templates/ search. handlebars browser/ views/charm. js browser/ views/editorial .js browser/ views/search. js browser/ views/utils. js browser/ views/view. js browser- search. handlebars charm-search. js browser_ app.js browser_ charm_details. js browser_ search_ view.js browser_ search_ widget. js browser_ view_utils. js
A [revision details]
M app/modules-
M app/subapps/
A app/subapps/
M app/subapps/
M app/subapps/
A app/subapps/
A app/subapps/
M app/subapps/
M app/templates/
M app/widgets/
M test/index.html
M test/test_
M test/test_
A test/test_
M test/test_
A test/test_