Merge lp:~jcsackett/juju-gui/search-routing-and-view into lp:juju-gui/experimental

Proposed by j.c.sackett
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
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+160110@code.launchpad.net

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.

https://codereview.appspot.com/8910043/

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

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
function in
Y.juju.browser.views.utils

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:
   A [revision details]
   M app/modules-debug.js
   M app/subapps/browser/browser.js
   A app/subapps/browser/templates/search.handlebars
   M app/subapps/browser/views/charm.js
   M app/subapps/browser/views/editorial.js
   A app/subapps/browser/views/search.js
   A app/subapps/browser/views/utils.js
   M app/subapps/browser/views/view.js
   M app/templates/browser-search.handlebars
   M app/widgets/charm-search.js
   M test/index.html
   M test/test_browser_app.js
   M test/test_browser_charm_details.js
   A test/test_browser_search_view.js
   M test/test_browser_search_widget.js
   A test/test_browser_view_utils.js

Revision history for this message
Richard Harding (rharding) wrote :
Download full text (3.9 KiB)

LGTM With some comments

- I really think the apiFailure as extension makes a lot of sense and
will be cleaner.

- I think that renderSearchResults, while longer clears up some
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://codereview.appspot.com/8910043/diff/1/app/modules-debug.js
File app/modules-debug.js (right):

https://codereview.appspot.com/8910043/diff/1/app/modules-debug.js#newcode310
app/modules-debug.js:310: fullpath:
'/juju-ui/subapps/browser/views/utils.js'
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js#newcode209
app/subapps/browser/browser.js:209: if (params.id && params.id !==
'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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars
File app/subapps/browser/templates/search.handlebars (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars#newcode2
app/subapps/browser/templates/search.handlebars:2: <h3>{{ count }}
results found</h3>
check the css used in the other headings and try to match up. Huw's got
a class='section-title' we should make sure to reuse and help him out a
step.

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/charm.js
File app/subapps/browser/views/charm.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/charm.js#newcode71
app/subapps/browser/views/charm.js:71:
Y.juju.browser.views.utils.apiFailure(data, request, this);
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/editorial.js
File app/subapps/browser/views/editorial.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/editorial.js#newcode149
app/subapps/browser/views/editorial.js:149: /*
typo?

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/search.js
File app/subapps/browser/views/search.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/search.js#newcode33
app/subapps/browser/views/search.js:33: results.map(function(charm) {
does it make sense to use a charm-container here with a larger default
count?

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/search.js#newcod...

Read more...

Revision history for this message
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js#newcode209
app/subapps/browser/browser.js:209: if (params.id && params.id !==
'search') {
That could be why the *id is undocumented as it brings with it this
issue :)

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js#newcode371
app/subapps/browser/browser.js:371: if (this._viewState.viewmode ===
'fullscreen') {
because this is a route callback the req object will have the param for
:viewmode which you should probably use here.

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/utils.js
File app/subapps/browser/views/utils.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/utils.js#newcode25
app/subapps/browser/views/utils.js:25: ns.apiFailure = function(data,
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/view.js
File app/subapps/browser/views/view.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/view.js#newcode173
app/subapps/browser/views/view.js:173:
Y.juju.browser.views.utils.apiFaiure(data, request, this);
see comment about view extension in utils

https://codereview.appspot.com/8910043/diff/1/test/test_browser_search_widget.js
File test/test_browser_search_widget.js (right):

https://codereview.appspot.com/8910043/diff/1/test/test_browser_search_widget.js#newcode64
test/test_browser_search_widget.js:64: it('should supports clearing
search string', function() {
should support clearning
small typo.

https://codereview.appspot.com/8910043/

Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Jon. Thank you for the branch. I have a question about a test.

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars
File app/subapps/browser/templates/search.handlebars (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars#newcode3
app/subapps/browser/templates/search.handlebars:3: <div
class="search-results"></div>
the div is not subordinate to the heading, it does not need to be
indented.

https://codereview.appspot.com/8910043/diff/1/test/test_browser_search_view.js
File test/test_browser_search_view.js (right):

https://codereview.appspot.com/8910043/diff/1/test/test_browser_search_view.js#newcode71
test/test_browser_search_view.js:71: });
Do we need a query string test for the cause when there isn't one? I see
renderSearch in app/subapps/browser/browser.js will perform the search
with with {text: ''}.

https://codereview.appspot.com/8910043/

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.

Revision history for this message
j.c.sackett (jcsackett) wrote :
Download full text (6.4 KiB)

Comments addressed; changes coming in submission.

https://codereview.appspot.com/8910043/diff/1/app/modules-debug.js
File app/modules-debug.js (right):

https://codereview.appspot.com/8910043/diff/1/app/modules-debug.js#newcode310
app/modules-debug.js:310: fullpath:
'/juju-ui/subapps/browser/views/utils.js'
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js#newcode209
app/subapps/browser/browser.js:209: if (params.id && params.id !==
'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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/browser.js#newcode371
app/subapps/browser/browser.js:371: if (this._viewState.viewmode ===
'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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars
File app/subapps/browser/templates/search.handlebars (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars#newcode2
app/subapps/browser/templates/search.handlebars:2: <h3>{{ count }}
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='section-title' we should make sure to reuse and help him out a
step.

Done, thanks.

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/templates/search.handlebars#newcode3
app/subapps/browser/templates/search.handlebars:3: <div
class="search-results"></div>
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/charm.js
File app/subapps/browser/views/charm.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/charm.js#newcode71
app/subapps/browser/views/charm.js:71:
Y.juju.browser.views.utils.apiFailure(data, request, this);
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://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/editorial.js
File app/subapps/browser/views/editorial.js (right):

https://codereview.appspot.com/8910043/diff/1/app/subapps/browser/views/editorial.js#newcode149
app/subapps/browser/views/editorial.js:149: /*
On 2013/04/22 14:08:15, rharding wrote:
> typo?
Fixed.

https://codereview.appspot.com/8910043/d...

Read more...

583. By j.c.sackett

Testfix.

Revision history for this message
Curtis Hovey (sinzui) wrote :
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.

Revision history for this message
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://codereview.appspot.com/8910043

https://codereview.appspot.com/8910043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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&hellip;"/>
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 });

Subscribers

People subscribed via source and target branches