Merge lp:~rharding/juju-gui/update-urls into lp:juju-gui/experimental

Proposed by Richard Harding
Status: Merged
Merged at revision: 556
Proposed branch: lp:~rharding/juju-gui/update-urls
Merge into: lp:juju-gui/experimental
Diff against target: 1368 lines (+529/-416)
13 files modified
app/modules-debug.js (+9/-0)
app/subapps/browser/browser.js (+184/-68)
app/subapps/browser/templates/browser_charm.handlebars (+3/-7)
app/subapps/browser/templates/editorial.handlebars (+9/-0)
app/subapps/browser/templates/sidebar.handlebars (+2/-4)
app/subapps/browser/views/charm.js (+90/-16)
app/subapps/browser/views/editorial.js (+166/-0)
app/subapps/browser/views/fullscreen.js (+7/-24)
app/subapps/browser/views/sidebar.js (+10/-154)
app/subapps/browser/views/view.js (+0/-59)
app/templates/charm-token.handlebars (+20/-18)
test/test_browser_app.js (+0/-47)
test/test_browser_charm_details.js (+29/-19)
To merge this branch: bzr merge lp:~rharding/juju-gui/update-urls
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+158713@code.launchpad.net

Description of the change

Rearchitect the browser to use routes more

- Use nested routes and route handling to build the browser UI
- Break up the Views in the browser subapp to be more singular per UX
component.
- Adjust tests to fit with this.

https://codereview.appspot.com/8679045/

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

Reviewers: mp+158713_code.launchpad.net,

Message:
Please take a look.

Description:

TBD

https://code.launchpad.net/~rharding/juju-gui/update-urls/+merge/158713

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/8679045/

Affected files:
   A [revision details]
   M app/modules-debug.js
   M app/subapps/browser/browser.js
   M app/subapps/browser/templates/browser_charm.handlebars
   A app/subapps/browser/templates/editorial.handlebars
   M app/subapps/browser/templates/sidebar.handlebars
   M app/subapps/browser/views/charm.js
   A app/subapps/browser/views/editorial.js
   M app/subapps/browser/views/fullscreen.js
   M app/subapps/browser/views/sidebar.js
   M app/subapps/browser/views/view.js
   M app/templates/charm-token.handlebars
   M test/test_browser_app.js
   M test/test_browser_charm_details.js

lp:~rharding/juju-gui/update-urls updated
555. By Richard Harding

Garden

556. By Richard Harding

Garden

Revision history for this message
Richard Harding (rharding) wrote :
Revision history for this message
Richard Harding (rharding) wrote :
Revision history for this message
Jeff Pihach (hatch) wrote :
Download full text (4.8 KiB)

Thanks for the refactor - I have a few comments below that I'd like some
discussion on.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (right):

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode110
app/subapps/browser/browser.js:110: var isFullscreen = true;
unused

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode137
app/subapps/browser/browser.js:137: var containerID = '#subapp-browser',
should this not be
var containerID = this.get('container') ?

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode146
app/subapps/browser/browser.js:146: if (this._getSubPath(req.path) !==
'fullscreen') {
see comment in routes.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode150
app/subapps/browser/browser.js:150: containerID += ' .bws-content';
maybe add a comment here as to what's happening here.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode178
app/subapps/browser/browser.js:178: if (!this._sidebar) {
maybe a comment here as to why this is necessary

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode206
app/subapps/browser/browser.js:206: if (req.path.indexOf('fullscreen')
!== -1) {
see comment in routes

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode245
app/subapps/browser/browser.js:245: valueFn: function() {
maybe some documentation as to what's going on here.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/browser.js#newcode267
app/subapps/browser/browser.js:267: { path: '/bws/fullscreen/*/',
callbacks: 'fullscreen' },
It looks like these routes are identical and then you are using a string
search to determine the view to render. I would probably investigate
using a route like:
/bws/:viewSize/

and then viewSize will be available as a param in your callbacks so you
won't have to stringsearch for it. IMHO it should also simplify your
routes and callbacks.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/templates/browser_charm.handlebars
File app/subapps/browser/templates/browser_charm.handlebars (right):

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/templates/browser_charm.handlebars#newcode4
app/subapps/browser/templates/browser_charm.handlebars:4: <a
href="/bws/sidebar/" class="back">{{#if
isFullscreen}}Back{{else}}Close{{/if}}</a>
this url will need to be generated and then passed in with the hbs
params to work properly with both namespaces.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/templates/browser_charm.handlebars#newcode52
app/subapps/browser/templates/browser_charm.handlebars:52: Change log
isn't changelog one word?

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

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/views/charm.js#newcode372
app/subapps/browser/views/charm.js:372: isFullscreen = false;
you accept ...

Read more...

Revision history for this message
Richard Harding (rharding) wrote :

We had a chat about most of it. I've added a bunch of comment blocks to
help clear up some of the bits.

Some of this is missing for the follow up such as url generation and app
state tracking.

Thanks for catching a few hard coded bits for in progress I missed
cleaning up!

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/templates/browser_charm.handlebars
File app/subapps/browser/templates/browser_charm.handlebars (right):

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/templates/browser_charm.handlebars#newcode4
app/subapps/browser/templates/browser_charm.handlebars:4: <a
href="/bws/sidebar/" class="back">{{#if
isFullscreen}}Back{{else}}Close{{/if}}</a>
On 2013/04/15 18:21:21, jeff.pihach wrote:
> this url will need to be generated and then passed in with the hbs
params to
> work properly with both namespaces.

Yes, url generation will come in a follow up branch.

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/templates/browser_charm.handlebars#newcode52
app/subapps/browser/templates/browser_charm.handlebars:52: Change log
On 2013/04/15 18:21:21, jeff.pihach wrote:
> isn't changelog one word?

I would say so, but we're going off the UX docs given to us which lists
it as two. We can bring up the point to UX.

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

https://codereview.appspot.com/8679045/diff/3001/app/subapps/browser/views/charm.js#newcode372
app/subapps/browser/views/charm.js:372: isFullscreen = false;
On 2013/04/15 18:21:21, jeff.pihach wrote:
> you accept isFullscreen as a param and then overwrite it regardless.

Yep, hard coded for early dev and didn't back out.

https://codereview.appspot.com/8679045/

lp:~rharding/juju-gui/update-urls updated
557. By Richard Harding

Update per review

Revision history for this message
Richard Harding (rharding) wrote :
Revision history for this message
j.c.sackett (jcsackett) wrote :

Thanks for this refactor; I have some comments and questions below.

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (left):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js#oldcode191
app/subapps/browser/browser.js:191: ]
Don't we need a route like "/bws/sidebar/id*/* to match things like the
readme and interface routes above?

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (right):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js#newcode139
app/subapps/browser/browser.js:139: // The fullscreen view requires that
there be no editorial content if
The sequence of if statements here seem odd; can't we just check the
subpath thing, and set our containerID &c based on that, as that will
also be false if fullscreen isn't even in the URL?

It's possible I'm just not really understanding some of this branch, as
I'm having trouble figuring out the situation in which we call
renderEditorial but don't want to renderEditorial.

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/templates/browser_charm.handlebars
File app/subapps/browser/templates/browser_charm.handlebars (left):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/templates/browser_charm.handlebars#oldcode54
app/subapps/browser/templates/browser_charm.handlebars:54: {{/if}}
Does this (and the events change in views/charm.js) have to do with the
routes &c change, or just a driveby?

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

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/views/charm.js#newcode366
app/subapps/browser/views/charm.js:366: * @param {Node} container the
node to insert our rendered content into.
These params don't match; _renderCharmView has "charm" and isFullScreen,
not container. It's getting container as an attr.

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/views/editorial.js
File app/subapps/browser/views/editorial.js (right):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/views/editorial.js#newcode126
app/subapps/browser/views/editorial.js:126: );
Given views/charm.js has this same failure as apiFailure, and we're
using it here as a callback, we should probably define it in one place
and have both of those places use it.

https://codereview.appspot.com/8679045/diff/10002/app/templates/charm-token.handlebars
File app/templates/charm-token.handlebars (right):

https://codereview.appspot.com/8679045/diff/10002/app/templates/charm-token.handlebars#newcode1
app/templates/charm-token.handlebars:1: <a href="/bws/sidebar/{{id}}">
Can you update the token's less file to make sure pointer type remains
normal now that the entire thing is a link?

https://codereview.appspot.com/8679045/

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

Comments below. Will have an updated branch in a bit.

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (left):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js#oldcode191
app/subapps/browser/browser.js:191: ]
On 2013/04/15 21:06:34, j.c.sackett wrote:
> Don't we need a route like "/bws/sidebar/id*/* to match things like
the readme
> and interface routes above?

Short answer: yes
Longer: this functionality never worked and needs to be tied into the
rendering state of the charm details so it'll be implemented later.

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js
File app/subapps/browser/browser.js (right):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/browser.js#newcode139
app/subapps/browser/browser.js:139: // The fullscreen view requires that
there be no editorial content if
On 2013/04/15 21:06:34, j.c.sackett wrote:
> The sequence of if statements here seem odd; can't we just check the
subpath
> thing, and set our containerID &c based on that, as that will also be
false if
> fullscreen isn't even in the URL?

> It's possible I'm just not really understanding some of this branch,
as I'm
> having trouble figuring out the situation in which we call
renderEditorial but
> don't want to renderEditorial.

Editorial is kind of a 'default' content. It's also going to be rendered
differently if it's for a fullscreen or sidebar view. Because of that we
track it's state here.

Render editorial is called manually from both sidebar() and
fullscreen(). It determines if it should go through with it based on the
current state.

/bws/sidebar/precise/apach2-2 will call renderEditorial as it's needed
to fill the sidebar while the charm details loads next to it.

/bws/fullscreen/precise/apache2-2 will also enter this path, but we
don't need to render the editorial data so we bypass it.

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/templates/browser_charm.handlebars
File app/subapps/browser/templates/browser_charm.handlebars (left):

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/templates/browser_charm.handlebars#oldcode54
app/subapps/browser/templates/browser_charm.handlebars:54: {{/if}}
On 2013/04/15 21:06:34, j.c.sackett wrote:
> Does this (and the events change in views/charm.js) have to do with
the routes
> &c change, or just a driveby?

sorry, this was part of some debugging work where I had issues with the
double render causing events to bind twice and thus clicking this would
open and close the comments at once.

In debugging I moved the event to the h3 as a whole vs just the <a> and
missed reverting it, but it works so it didn't come out.

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

https://codereview.appspot.com/8679045/diff/10002/app/subapps/browser/views/charm.js#newcode366
app/subapps/browser/views/charm.js:366: * @param {Node} container the
node to insert our rendered content into.
On 2013/04/15 21:06:34, j.c.sackett wrote:
> These params don't match; ...

Read more...

Revision history for this message
j.c.sackett (jcsackett) wrote :

On 2013/04/15 22:15:24, rharding wrote:
> Short answer: yes
> Longer: this functionality never worked and needs to be tied into the
rendering
> state of the charm details so it'll be implemented later.

Ok. Can you XXX or something to that effect?

> Editorial is kind of a 'default' content. It's also going to be
rendered
> differently if it's for a fullscreen or sidebar view. Because of that
we track
> it's state here.

> Render editorial is called manually from both sidebar() and
fullscreen(). It
> determines if it should go through with it based on the current state.

> /bws/sidebar/precise/apach2-2 will call renderEditorial as it's needed
to fill
> the sidebar while the charm details loads next to it.

> /bws/fullscreen/precise/apache2-2 will also enter this path, but we
don't need
> to render the editorial data so we bypass it.

Ok. This still feels weird to me, but I understand it. Thanks.

> sorry, this was part of some debugging work where I had issues with
the double
> render causing events to bind twice and thus clicking this would open
and close
> the comments at once.

> In debugging I moved the event to the h3 as a whole vs just the <a>
and missed
> reverting it, but it works so it didn't come out.

I think it's fine as is, I was just double checking my understanding.

> Sorry, you're correct. This was tweaked and I didn't update the
docstring.
> Thanks!

Thanks for fixing!

> Yes, my goal is to move this to a view/utils but was trying to keep
from doing
> too much in the branch. I'll update this.

You can just XXX it for now and get it later, if you like. I just want
to make sure it gets handled.

> This is very temp. Actually you have to carefully click in a blank
area to
> trigger the link right now. I want to get UX/Huw follow up to fix it
up.

Ok, thanks.

https://codereview.appspot.com/8679045/

Revision history for this message
j.c.sackett (jcsackett) wrote :

LGTM from discussion and with promised fixes. Thanks!

https://codereview.appspot.com/8679045/

Revision history for this message
Jeff Pihach (hatch) wrote :

LGTM Thanks for all the work!

https://codereview.appspot.com/8679045/

lp:~rharding/juju-gui/update-urls updated
558. By Richard Harding

Updates per review

559. By Richard Harding

Updates per review

560. By Richard Harding

lint

561. By Richard Harding

Resolve conflicts from trunk

Revision history for this message
Richard Harding (rharding) wrote :

*** Submitted:

Rearchitect the browser to use routes more

- Use nested routes and route handling to build the browser UI
- Break up the Views in the browser subapp to be more singular per UX
component.
- Adjust tests to fit with this.

R=jeff.pihach, j.c.sackett
CC=
https://codereview.appspot.com/8679045

https://codereview.appspot.com/8679045/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'app/modules-debug.js'
--- app/modules-debug.js 2013-04-09 18:10:36 +0000
+++ app/modules-debug.js 2013-04-16 00:07:30 +0000
@@ -290,6 +290,15 @@
290290
291 'subapp-browser-sidebar': {291 'subapp-browser-sidebar': {
292 fullpath: '/juju-ui/subapps/browser/views/sidebar.js',292 fullpath: '/juju-ui/subapps/browser/views/sidebar.js',
293 requires: [
294 'subapp-browser-editorial',
295 'subapp-browser-mainview',
296 'subapp-browser-charmview'
297 ]
298 },
299
300 'subapp-browser-editorial': {
301 fullpath: '/juju-ui/subapps/browser/views/editorial.js',
293 requires: ['subapp-browser-sidebar']302 requires: ['subapp-browser-sidebar']
294 },303 },
295304
296305
=== modified file 'app/subapps/browser/browser.js'
--- app/subapps/browser/browser.js 2013-04-04 19:16:03 +0000
+++ app/subapps/browser/browser.js 2013-04-16 00:07:30 +0000
@@ -9,7 +9,8 @@
9 *9 *
10 */10 */
11YUI.add('subapp-browser', function(Y) {11YUI.add('subapp-browser', function(Y) {
12 var ns = Y.namespace('juju.subapps');12 var ns = Y.namespace('juju.subapps'),
13 models = Y.namespace('juju.models');
1314
14 /**15 /**
15 * Browser Sub App for the Juju Gui.16 * Browser Sub App for the Juju Gui.
@@ -19,7 +20,6 @@
19 *20 *
20 */21 */
21 ns.Browser = Y.Base.create('subapp-browser', Y.juju.SubApp, [], {22 ns.Browser = Y.Base.create('subapp-browser', Y.juju.SubApp, [], {
22
23 /**23 /**
24 * Some routes might have sub parts that hint to where a user wants focus.24 * Some routes might have sub parts that hint to where a user wants focus.
25 * In particular we've got the tabs that might have focus. They are the25 * In particular we've got the tabs that might have focus. They are the
@@ -49,7 +49,8 @@
49 */49 */
50 _getViewCfg: function(cfg) {50 _getViewCfg: function(cfg) {
51 return Y.merge(cfg, {51 return Y.merge(cfg, {
52 db: this.get('db')52 db: this.get('db'),
53 store: this.get('store')
53 });54 });
54 },55 },
5556
@@ -59,32 +60,42 @@
59 *60 *
60 */61 */
61 views: {62 views: {
63 charmDetails: {
64 type: 'juju.browser.views.BrowserCharmView',
65 preserve: false
66 },
62 fullscreen: {67 fullscreen: {
63 type: 'juju.browser.views.FullScreen',68 type: 'juju.browser.views.FullScreen',
64 preserve: true
65 },
66 fullscreenCharm: {
67 type: 'juju.browser.views.FullScreen',
68 preserve: false69 preserve: false
69 },70 },
70 sidebar: {71 sidebar: {
71 type: 'juju.browser.views.Sidebar',72 type: 'juju.browser.views.Sidebar',
72 preserve: true
73 },
74 sidebarCharm: {
75 type: 'juju.browser.views.Sidebar',
76 preserve: false73 preserve: false
77 }74 }
78 },75 },
7976
80 /**77 /**
78 Cleanup after ourselves on destroy.
79
80 @method destructor
81
82 */
83 destructor: function() {
84 this._cacheCharms.destroy();
85 },
86
87 /**
81 * General app initializer88 * General app initializer
82 *89 *
83 * @method initializer90 * @method initializer
84 * @param {Object} cfg general init config object.91 * @param {Object} cfg general init config object.
85 *92 *
86 */93 */
87 initializer: function(cfg) {},94 initializer: function(cfg) {
95 // Hold onto charm data so we can pass model instances to other views when
96 // charms are selected.
97 this._cacheCharms = new models.BrowserCharmList();
98 },
8899
89 /**100 /**
90 * Render the fullscreen view to the client.101 * Render the fullscreen view to the client.
@@ -96,35 +107,63 @@
96 *107 *
97 */108 */
98 fullscreen: function(req, res, next) {109 fullscreen: function(req, res, next) {
99 this.showView('fullscreen', this._getViewCfg());110 if (!this._fullscreen) {
100 next();111 this._fullscreen = this.showView('fullscreen', this._getViewCfg(), {
101 },112 'callback': function(view) {
102113 // if the fullscreen isn't the last part of the path, then ignore
103 /**114 // the editorial content.
104 * Render the fullscreen view of a specific charm to the client.115 if (this._getSubPath(req.path) === 'fullscreen') {
105 *116 this.renderEditorial(req, res, next);
106 * @method fullscreenCharm117 }
107 * @param {Request} req current request object.118 next();
108 * @param {Response} res current response object.119 }
109 * @param {function} next callable for the next route in the chain.120 });
110 *121 } else {
111 */122 next();
112 fullscreenCharm: function(req, res, next) {123 }
113 var subpath = this._getSubPath(req.path);124 },
114 this.showView('fullscreenCharm', this._getViewCfg({125
115 charmID: req.params.id,126 /**
116 subpath: subpath127 Render editorial content into the parent view when required.
117 }));128
118 next();129 The parent view is either fullscreen/sidebar which determines how the
119 },130 editorial content is to be rendered.
120131
121 /**132 @method renderEditorial
122 * Destroy the subapp instance.133 @param {Request} req current request object.
123 *134 @param {Response} res current response object.
124 * @method destructor135 @param {function} next callable for the next route in the chain.
125 *136
126 */137 */
127 destructor: function() {},138 renderEditorial: function(req, res, next) {
139 var container = this.get('container'),
140 editorialContainer,
141 extraCfg = {};
142
143 if (req.path.indexOf('fullscreen') !== -1) {
144 // The fullscreen view requires that there be no editorial content if
145 // we're looking at a specific charm. The div we dump our content into
146 // is shared. So if the url is /fullscreen show editorial content, but
147 // if it's not, there's something else handling displaying the
148 // view-data.
149 editorialContainer = container.one(' .bws-view-data');
150 extraCfg.isFullscreen = true;
151 } else {
152 // If this is the sidebar view, then the editorial content goes into a
153 // different div since we can view both editorial content and
154 // view-data (such as a charm details) side by side.
155 editorialContainer = container.one('.bws-content');
156 }
157
158 if (!this._editorial) {
159 this._editorial = new Y.juju.browser.views.EditorialView(
160 this._getViewCfg(extraCfg));
161
162 this._editorial.render(editorialContainer);
163 // Add any sidebar charms to the running cache.
164 this._cacheCharms.add(this._editorial._cacheCharms);
165 }
166 },
128167
129 /**168 /**
130 * Handle the route for the sidebar view.169 * Handle the route for the sidebar view.
@@ -136,8 +175,26 @@
136 *175 *
137 */176 */
138 sidebar: function(req, res, next) {177 sidebar: function(req, res, next) {
139 this.showView('sidebar', this._getViewCfg());178 // Clean up any details we've got.
140 next();179 if (this._details) {
180 this._details.destroy({remove: true});
181 }
182
183 if (!this._sidebar) {
184 // Whenever the sidebar view is rendered it needs some editorial
185 // content to display to the user. We only need once instance though,
186 // so only render it on the first view. As users click on charm to
187 // charm and we generate urls /sidebar/precise/xxx we don't want to
188 // re-render the sidebar content.
189 this._sidebar = this.showView('sidebar', this._getViewCfg(), {
190 'callback': function(view) {
191 this.renderEditorial(req, res, next);
192 next();
193 }
194 });
195 } else {
196 next();
197 }
141 },198 },
142199
143 /**200 /**
@@ -149,53 +206,112 @@
149 * @param {function} next callable for the next route in the chain.206 * @param {function} next callable for the next route in the chain.
150 *207 *
151 */208 */
152 sidebarCharm: function(req, res, next) {209 charmDetails: function(req, res, next) {
153 var subpath = this._getSubPath(req.path);210 var charmID = req.params.id;
154 this.showView('sidebarCharm', this._getViewCfg({211 var extraCfg = {
155 charmID: req.params.id,212 charmID: charmID,
156 subpath: subpath213 container: Y.Node.create('<div class="charmview"/>')
157 }));214 };
215
216 // The details view needs to know if we're using a fullscreen template
217 // or the sidebar version.
218 if (req.path.indexOf('fullscreen') !== -1) {
219 extraCfg.isFullscreen = true;
220 }
221
222 // Gotten from the sidebar creating the cache.
223 var model = this._cacheCharms.getById(charmID);
224
225 if (model) {
226 extraCfg.charm = model;
227 }
228
229 this._details = new Y.juju.browser.views.BrowserCharmView(
230 this._getViewCfg(extraCfg));
231 this._details.render();
158 next();232 next();
159 }233 }
160234
161 }, {235 }, {
162 ATTRS: {236 ATTRS: {
237 /**
238 @attribute container
239 @default '#subapp-browser'
240 @type {String}
241
242 */
163 container: {243 container: {
164 value: '#subapp-browser'244 value: '#subapp-browser'
165 },245 },
166 urlNamespace: {246
167 value: 'charmstore'247 /**
248 @attribute store
249 @default Charmworld0
250 @type {Charmworld0}
251
252 */
253 store: {
254 /**
255 We keep one instance of the store and will work on caching results
256 at the app level so that routes can share api calls. However, in
257 tests there's no config for talking to the api so we have to watch
258 out in test runs and allow the store to be broken.
259
260 method store.valueFn
261
262 */
263 valueFn: function() {
264 var url = '';
265 if (!window.juju_config || ! window.juju_config.charmworldURL) {
266 console.error('No juju config to fetch charmworld store url');
267 } else {
268 url = window.juju_config.charmworldURL;
269 }
270 return new Y.juju.Charmworld0({
271 'apiHost': url
272 });
273 }
168 },274 },
275
276 /**
277 @attribute routes
278 @default Array of subapp routes.
279 @type {Array}
280
281 */
169 routes: {282 routes: {
170 value: [283 value: [
284 // Double routes are needed to catch /fullscreen and /fullscreen/
171 { path: '/bws/fullscreen/', callbacks: 'fullscreen' },285 { path: '/bws/fullscreen/', callbacks: 'fullscreen' },
172 { path: '/bws/fullscreen/*id/configuration/',286 { path: '/bws/fullscreen/*/', callbacks: 'fullscreen' },
173 callbacks: 'fullscreenCharm' },287 { path: '/bws/fullscreen/*id/', callbacks: 'charmDetails' },
174 { path: '/bws/fullscreen/*id/hooks/', callbacks: 'fullscreenCharm' },
175 { path: '/bws/fullscreen/*id/interfaces/',
176 callbacks: 'fullscreenCharm' },
177 { path: '/bws/fullscreen/*id/qa/', callbacks: 'fullscreenCharm' },
178 { path: '/bws/fullscreen/*id/readme/', callbacks: 'fullscreenCharm' },
179 { path: '/bws/fullscreen/*id/', callbacks: 'fullscreenCharm' },
180288
181 { path: '/bws/sidebar/', callbacks: 'sidebar' },289 { path: '/bws/sidebar/', callbacks: 'sidebar' },
182290 { path: '/bws/sidebar/*/', callbacks: 'sidebar' },
183 { path: '/bws/sidebar/*id/configuration/',291 { path: '/bws/sidebar/*id/', callbacks: 'charmDetails' }
184 callbacks: 'sidebarCharm' },
185 { path: '/bws/sidebar/*id/hooks/', callbacks: 'sidebarCharm' },
186 { path: '/bws/sidebar/*id/interfaces/',
187 callbacks: 'sidebarCharm' },
188 { path: '/bws/sidebar/*id/qa/', callbacks: 'sidebarCharm' },
189 { path: '/bws/sidebar/*id/readme/', callbacks: 'sidebarCharm' },
190 { path: '/bws/sidebar/*id/', callbacks: 'sidebarCharm' }
191 ]292 ]
293 },
294
295 /**
296 @attribute urlNamespace
297 @default 'charmstore'
298 @type {String}
299
300 */
301 urlNamespace: {
302 value: 'charmstore'
192 }303 }
304
193 }305 }
194 });306 });
195307
196}, '0.1.0', {308}, '0.1.0', {
197 requires: [309 requires: [
310 'juju-charm-store',
311 'juju-models',
198 'sub-app',312 'sub-app',
313 'subapp-browser-charmview',
314 'subapp-browser-editorial',
199 'subapp-browser-fullscreen',315 'subapp-browser-fullscreen',
200 'subapp-browser-sidebar'316 'subapp-browser-sidebar'
201 ]317 ]
202318
=== modified file 'app/subapps/browser/templates/browser_charm.handlebars'
--- app/subapps/browser/templates/browser_charm.handlebars 2013-04-15 14:16:16 +0000
+++ app/subapps/browser/templates/browser_charm.handlebars 2013-04-16 00:07:30 +0000
@@ -1,7 +1,7 @@
1<div class="charm yui3-g">1<div class="charm yui3-g">
2 <div class="content yui3-u-{{#if isFullscreen}}5-6{{else}}1{{/if}}">2 <div class="content yui3-u-{{#if isFullscreen}}5-6{{else}}1{{/if}}">
3 <div class="nav">3 <div class="nav">
4 <a href="" class="back">{{#if isFullscreen}}Back{{else}}Close{{/if}}</a>4 <a href="/bws/sidebar/" class="back">{{#if isFullscreen}}Back{{else}}Close{{/if}}</a>
5 <a href="" class="add">Add</a>5 <a href="" class="add">Add</a>
6 <a href="" class="share">6 <a href="" class="share">
7 <img src="/juju-ui/assets/images/share_icon.jpg" alt="Share" />7 <img src="/juju-ui/assets/images/share_icon.jpg" alt="Share" />
@@ -36,11 +36,8 @@
36 </div>36 </div>
3737
38 <div class="changelog">38 <div class="changelog">
39 <h3 class="section-title">39 <h3 class="section-title" data-state="closed">
40 {{#if recent_commits}}40 Change log
41 <a href="" class="expandToggle" data-state="closed">
42 {{/if}}
43 Change log
44 {{#if recent_commits}}41 {{#if recent_commits}}
45 <span class="expand">42 <span class="expand">
46 <span class="more">43 <span class="more">
@@ -52,7 +49,6 @@
52 alt="Contract" />49 alt="Contract" />
53 </span>50 </span>
54 </span>51 </span>
55 </a>
56 {{/if}}52 {{/if}}
57 </h3>53 </h3>
58 {{#if recent_commits}}54 {{#if recent_commits}}
5955
=== added file 'app/subapps/browser/templates/editorial.handlebars'
--- app/subapps/browser/templates/editorial.handlebars 1970-01-01 00:00:00 +0000
+++ app/subapps/browser/templates/editorial.handlebars 2013-04-16 00:07:30 +0000
@@ -0,0 +1,9 @@
1<div id="bws_editorial">
2 {{#if isFullscreen}}
3 <p>Some static content</p>
4 {{/if}}
5
6 <div class="featured"></div>
7 <div class="popular"></div>
8 <div class="new"></div>
9</div>
010
=== modified file 'app/subapps/browser/templates/sidebar.handlebars'
--- app/subapps/browser/templates/sidebar.handlebars 2013-04-15 07:26:46 +0000
+++ app/subapps/browser/templates/sidebar.handlebars 2013-04-16 00:07:30 +0000
@@ -2,12 +2,10 @@
2 <div id="bws-sidebar" class="yui3-u-1-4">2 <div id="bws-sidebar" class="yui3-u-1-4">
3 <div class="bws-header">3 <div class="bws-header">
4 </div>4 </div>
5 <div class="bws-left">5 <div class="bws-content">
6 <div class="featured"></div>
7 <div class="popular"></div>
8 <div class="new"></div>
9 </div>6 </div>
10 </div>7 </div>
11 <div class="bws-view-data yui3-u-3-4">8 <div class="bws-view-data yui3-u-3-4">
9 <div></div>
12 </div>10 </div>
13</div>11</div>
1412
=== modified file 'app/subapps/browser/views/charm.js'
--- app/subapps/browser/views/charm.js 2013-04-12 07:01:02 +0000
+++ app/subapps/browser/views/charm.js 2013-04-16 00:07:30 +0000
@@ -3,6 +3,7 @@
33
4YUI.add('subapp-browser-charmview', function(Y) {4YUI.add('subapp-browser-charmview', function(Y) {
5 var ns = Y.namespace('juju.browser.views'),5 var ns = Y.namespace('juju.browser.views'),
6 models = Y.namespace('juju.models'),
6 views = Y.namespace('juju.views'),7 views = Y.namespace('juju.views'),
7 widgets = Y.namespace('juju.widgets'),8 widgets = Y.namespace('juju.widgets'),
8 DATE_FORMAT = '%H:%M %d/%b/%y';9 DATE_FORMAT = '%H:%M %d/%b/%y';
@@ -29,7 +30,7 @@
29 *30 *
30 */31 */
31 events: {32 events: {
32 '.changelog .expandToggle': {33 '.changelog h3.section-title': {
33 click: '_toggleLog'34 click: '_toggleLog'
34 },35 },
35 '.charm .add': {36 '.charm .add': {
@@ -55,6 +56,31 @@
55 },56 },
5657
57 /**58 /**
59 * Shared method to generate a message to the user based on a bad api
60 * call.
61 *
62 * @method apiFailure
63 * @param {Object} data the json decoded response text.
64 * @param {Object} request the original io_request object for debugging.
65 *
66 */
67 apiFailure: function(data, request) {
68 var message;
69 if (data && data.type) {
70 message = 'Charm API error of type: ' + data.type;
71 } else {
72 message = 'Charm API server did not respond';
73 }
74 this.get('db').notifications.add(
75 new models.Notification({
76 title: 'Failed to load sidebar content.',
77 message: message,
78 level: 'error'
79 })
80 );
81 },
82
83 /**
58 * The API retuns the questions and the scores. Combine the data into a84 * The API retuns the questions and the scores. Combine the data into a
59 * single source to make looping in the handlebars templates nicer.85 * single source to make looping in the handlebars templates nicer.
60 *86 *
@@ -263,7 +289,6 @@
263289
264 }290 }
265 }, this);291 }, this);
266
267 },292 },
268293
269 /**294 /**
@@ -334,29 +359,30 @@
334 },359 },
335360
336 /**361 /**
337 * Render out the view to the DOM.362 * Render the view of a single charm details page.
338 *363 *
339 * @method render364 * @method _renderCharmView
340 * @param {Node} container optional specific container to render out to.365 * @param {BrowserCharm} charm the charm model instance to view.
366 * @param {Boolean} isFullscreen is this display for the fullscreen
367 * experiecne?
341 *368 *
342 */369 */
343 render: function(container, isFullscreen) {370 _renderCharmView: function(charm, isFullscreen) {
344 var charm = this.get('charm');371 this.set('charm', charm);
345 var tplData = charm.getAttrs();372
373 var tplData = charm.getAttrs(),
374 container = this.get('container');
375
346 tplData.isFullscreen = isFullscreen;376 tplData.isFullscreen = isFullscreen;
347 tplData.prettyCommits = this._formatCommitsForHtml(377 tplData.prettyCommits = this._formatCommitsForHtml(
348 tplData.recent_commits);378 tplData.recent_commits);
349379
350 var tpl = this.template(tplData);380 var tpl = this.template(tplData);
351 var tplNode = Y.Node.create(tpl);381 var tplNode = container.setHTML(tpl);
352382
353 container.setHTML(tplNode);383 // Set the content then update the container so that it reload
354
355 // Allow for specifying the container to use. This should reset the
356 // events.384 // events.
357 if (container) {385 Y.one('.bws-view-data').setHTML(tplNode);
358 this.set('container', container);
359 }
360386
361 this.tabview = new widgets.browser.TabView({387 this.tabview = new widgets.browser.TabView({
362 srcNode: tplNode.one('.tabs')388 srcNode: tplNode.one('.tabs')
@@ -374,10 +400,45 @@
374 } else {400 } else {
375 this._noReadme(tplNode.one('#bws-readme'));401 this._noReadme(tplNode.one('#bws-readme'));
376 }402 }
403 },
404
405 /**
406 Render out the view to the DOM.
407
408 The View might be given either a charmID, which means go fetch the
409 charm data, or a charm model instance, in which case the view has the
410 data it needs to render.
411
412 @method render
413
414 */
415 render: function() {
416 var isFullscreen = this.get('isFullscreen');
417
418 if (this.get('charm')) {
419 this._renderCharmView(this.get('charm'), isFullscreen);
420 } else {
421 this.get('store').charm(this.get('charmID'), {
422 'success': function(data) {
423 var charm = new models.BrowserCharm(data);
424 this.set('charm', charm);
425 this._renderCharmView(this.get('charm'), isFullscreen);
426 },
427 'failure': this.apiFailure
428 }, this);
429 }
377 }430 }
378 }, {431 }, {
379 ATTRS: {432 ATTRS: {
380 /**433 /**
434 @attribute charmID
435 @default undefined
436 @type {Int}
437
438 */
439 charmID: {},
440
441 /**
381 * The charm we're viewing the details of.442 * The charm we're viewing the details of.
382 *443 *
383 * @attribute charm444 * @attribute charm
@@ -388,6 +449,16 @@
388 charm: {},449 charm: {},
389450
390 /**451 /**
452 @attribute isFullscreen
453 @default false
454 @type {Boolean}
455
456 */
457 ifFullscreen: {
458 value: false
459 },
460
461 /**
391 * The store is the api endpoint for fetching data.462 * The store is the api endpoint for fetching data.
392 *463 *
393 * @attribute store464 * @attribute store
@@ -408,9 +479,12 @@
408 'datatype-date-format',479 'datatype-date-format',
409 'event-tracker',480 'event-tracker',
410 'gallery-markdown',481 'gallery-markdown',
482 'juju-charm-store',
483 'juju-models',
411 'juju-templates',484 'juju-templates',
412 'juju-views',485 'juju-views',
413 'juju-view-utils',486 'juju-view-utils',
487 'node',
414 'prettify',488 'prettify',
415 'view'489 'view'
416 ]490 ]
417491
=== added file 'app/subapps/browser/views/editorial.js'
--- app/subapps/browser/views/editorial.js 1970-01-01 00:00:00 +0000
+++ app/subapps/browser/views/editorial.js 2013-04-16 00:07:30 +0000
@@ -0,0 +1,166 @@
1'use strict';
2
3
4/**
5 * Browser Editorial View.
6 *
7 * @module juju.browser
8 * @submodule views
9 *
10 */
11YUI.add('subapp-browser-editorial', function(Y) {
12 var ns = Y.namespace('juju.browser.views'),
13 models = Y.namespace('juju.models'),
14 views = Y.namespace('juju.views'),
15 widgets = Y.namespace('juju.widgets');
16
17
18 /**
19 * Editorial view for landing pages.
20 *
21 * @class Editorial
22 * @extends {juju.browser.views.Editorial}
23 *
24 */
25 ns.EditorialView = Y.Base.create('browser-view-sidebar', Y.View, [], {
26 template: views.Templates.editorial,
27
28 /**
29 * General YUI initializer.
30 *
31 * @method initializer
32 * @param {Object} cfg configuration object.
33 *
34 */
35 initializer: function(cfg) {
36 // Hold onto charm data so we can pass model instances to other views when
37 // charms are selected.
38 this._cacheCharms = new models.BrowserCharmList();
39 },
40
41 /**
42 * Load the editorial content into the container specified.
43 *
44 * @method render
45 * @param {Node} container An optional node to override where it's going.
46 *
47 */
48 render: function(container) {
49 var tpl = this.template(this.getAttrs()),
50 tplNode = Y.Node.create(tpl),
51 store = this.get('store');
52
53 if (typeof container !== 'object') {
54 container = this.get('container');
55 } else {
56 this.set('container', container);
57 }
58
59 // By default we grab the editorial content from the api to use for
60 // display.
61 this.get('store').sidebarEditorial({
62 'success': function(data) {
63 // Add featured charms
64 var featuredCharms = this.get('store').resultsToCharmlist(
65 data.result.featured);
66 var featuredContainer = tplNode.one('.featured');
67 var featuredCharmContainer = new widgets.browser.CharmContainer({
68 name: 'Featured Charms',
69 cutoff: 1,
70 children: featuredCharms.map(function(charm) {
71 return charm.getAttrs(); })
72 });
73 featuredCharmContainer.render(featuredContainer);
74
75 // Add popular charms
76 var popularCharms = this.get('store').resultsToCharmlist(
77 data.result.popular);
78 var popularContainer = tplNode.one('.popular');
79 var popularCharmContainer = new widgets.browser.CharmContainer({
80 name: 'Popular Charms',
81 cutoff: 2,
82 children: popularCharms.map(function(charm) {
83 return charm.getAttrs(); })
84 });
85 popularCharmContainer.render(popularContainer);
86
87 // Add in the charm tokens for the new as well.
88 var newContainer = tplNode.one('.new');
89 var newCharms = this.get('store').resultsToCharmlist(
90 data.result['new']);
91 var newCharmContainer = new widgets.browser.CharmContainer({
92 name: 'New Charms',
93 cutoff: 2,
94 children: newCharms.map(function(charm) {
95 return charm.getAttrs(); })
96 });
97 newCharmContainer.render(newContainer);
98
99 container.setHTML(tplNode);
100
101 // Add the charms to the cache for use in other views.
102 // Start with a reset to empty any current cached models.
103 this._cacheCharms.reset(newCharms);
104 this._cacheCharms.add(popularCharms);
105 this._cacheCharms.add(featuredCharms);
106 this.charmContainers = [
107 featuredCharmContainer,
108 newCharmContainer,
109 popularCharmContainer
110 ];
111 },
112
113 'failure': function(data, request) {
114 var message;
115 if (data && data.type) {
116 message = 'Charm API error of type: ' + data.type;
117 } else {
118 message = 'Charm API server did not respond';
119 }
120 this.get('db').notifications.add(
121 new models.Notification({
122 title: 'Failed to load sidebar content.',
123 message: message,
124 level: 'error'
125 })
126 );
127 }
128 }, this);
129 },
130
131 /**
132 * Destroy this view and clear from the dom world.
133 *
134 * @method destructor
135 *
136 */
137 destructor: function() {
138 if (this.charmContainers) {
139 Y.Array.each(this.charmContainers, function(container) {
140 container.destroy();
141 });
142 }
143 this._cacheCharms.destroy();
144 }
145 }, {
146 ATTRS: {
147 isFullscreen: {
148 value: false
149 },
150 store: {
151
152 }
153 }
154 });
155
156}, '0.1.0', {
157 requires: [
158 'browser-charm-container',
159 'browser-charm-token',
160 'browser-search-widget',
161 'juju-charm-store',
162 'juju-models',
163 'juju-templates',
164 'view'
165 ]
166});
0167
=== modified file 'app/subapps/browser/views/fullscreen.js'
--- app/subapps/browser/views/fullscreen.js 2013-04-03 15:03:56 +0000
+++ app/subapps/browser/views/fullscreen.js 2013-04-16 00:07:30 +0000
@@ -16,44 +16,27 @@
16 *16 *
17 */17 */
18 ns.FullScreen = Y.Base.create('browser-view-fullscreen', ns.MainView, [], {18 ns.FullScreen = Y.Base.create('browser-view-fullscreen', ns.MainView, [], {
19 _fullscreenTarget: '/bws/sidebar',
20
21 template: views.Templates.fullscreen,19 template: views.Templates.fullscreen,
2220
23
24 /**21 /**
25 * The default view is the editorial rendering. Render this view out.22 * Render out the view to the DOM.
26 *23 *
27 * @method _renderEditorialView24 * @method render
28 * @param {Node} container node to render out to.25 * @param {Node} container optional specific container to render out to.
29 *26 *
30 */27 */
31 _renderEditorialView: function(container) {28 render: function(container) {
32 var tpl = this.template(),29 var tpl = this.template(),
33 tplNode = Y.Node.create(tpl);30 tplNode = Y.Node.create(tpl);
3431
35 this._renderSearchWidget(tplNode);32 this._renderSearchWidget(tplNode);
3633
37 if (!Y.Lang.isValue(container)) {34 if (typeof container !== 'object') {
38 container = this.get('container');35 container = this.get('container');
36 } else {
37 this.set('container', container);
39 }38 }
40 container.setHTML(tplNode);39 container.setHTML(tplNode);
41 },
42
43 /**
44 * Render out the view to the DOM.
45 *
46 * @method render
47 * @param {Node} container optional specific container to render out to.
48 *
49 */
50 render: function(container) {
51 if (this.get('charmID')) {
52 this._renderCharmView(container);
53 } else {
54 this._renderEditorialView(container);
55 }
56
57 // Bind our view to the events from the search widget used for controls.40 // Bind our view to the events from the search widget used for controls.
58 this._bindSearchWidgetEvents();41 this._bindSearchWidgetEvents();
59 }42 }
6043
=== modified file 'app/subapps/browser/views/sidebar.js'
--- app/subapps/browser/views/sidebar.js 2013-04-11 14:48:10 +0000
+++ app/subapps/browser/views/sidebar.js 2013-04-16 00:07:30 +0000
@@ -10,9 +10,7 @@
10 */10 */
11YUI.add('subapp-browser-sidebar', function(Y) {11YUI.add('subapp-browser-sidebar', function(Y) {
12 var ns = Y.namespace('juju.browser.views'),12 var ns = Y.namespace('juju.browser.views'),
13 models = Y.namespace('juju.models'),13 views = Y.namespace('juju.views');
14 views = Y.namespace('juju.views'),
15 widgets = Y.namespace('juju.widgets');
1614
1715
18 /**16 /**
@@ -23,56 +21,17 @@
23 *21 *
24 */22 */
25 ns.Sidebar = Y.Base.create('browser-view-sidebar', ns.MainView, [], {23 ns.Sidebar = Y.Base.create('browser-view-sidebar', ns.MainView, [], {
26 _fullscreenTarget: '/bws/fullscreen',
27
28 template: views.Templates.sidebar,24 template: views.Templates.sidebar,
29 visible: true,25
3026 /**
31 events: {27 * Render out the view to the DOM.
32 '.charm-token': {28 *
33 'click': '_handleTokenSelect'29 * @method render
34 }30 *
35 },31 */
3632 render: function(container) {
37 /**
38 * Event handler for selecting a charm from a list on the page. Forces a
39 * render of the charm details view for the user.
40 *
41 * @method _handleTokenSelect
42 * @param {Event} ev the click event from the charm token.
43 *
44 */
45 _handleTokenSelect: function(ev) {
46 var id = ev.currentTarget.getData('charmid');
47 var model = this._cacheCharms.getById(id);
48 var container = this.get('container');
49
50 // Deselect the currently selected charm and highlight the new one.
51 var selected_charm = container.one('.yui3-charmtoken.active');
52 if (selected_charm) {
53 selected_charm.removeClass('active');
54 }
55 ev.currentTarget.ancestor('.yui3-charmtoken').addClass('active');
56
57 // Show the details view for this model.
58 this._renderCharmDetails(
59 model,
60 container
61 );
62 },
63
64 /**
65 * Initially we load editorial content to populate the sidebar. Build this
66 * content.
67 *
68 * @method _renderEditorialView
69 * @param {Node} container A node to stick the rendered output into.
70 *
71 */
72 _renderEditorialView: function(container) {
73 var tpl = this.template(),33 var tpl = this.template(),
74 tplNode = Y.Node.create(tpl),34 tplNode = Y.Node.create(tpl);
75 store = this.get('store');
7635
77 this._renderSearchWidget(tplNode);36 this._renderSearchWidget(tplNode);
7837
@@ -83,104 +42,6 @@
83 }42 }
8443
85 container.setHTML(tplNode);44 container.setHTML(tplNode);
86
87 // By default we grab the editorial content from the api to use for
88 // display.
89 this.get('store').sidebarEditorial({
90 'success': function(data) {
91
92 // Add featured charms
93 var featuredCharms = this.get('store').resultsToCharmlist(
94 data.result.featured);
95 var featuredContainer = container.one('.bws-left .featured');
96 var featuredCharmContainer = new widgets.browser.CharmContainer({
97 name: 'Featured Charms',
98 cutoff: 1,
99 children: featuredCharms.map(function(charm) {
100 return charm.getAttrs(); })
101 });
102 featuredCharmContainer.render(featuredContainer);
103
104 // Add popular charms
105 var popularCharms = this.get('store').resultsToCharmlist(
106 data.result.popular);
107 var popularContainer = container.one('.bws-left .popular');
108 var popularCharmContainer = new widgets.browser.CharmContainer({
109 name: 'Popular Charms',
110 cutoff: 2,
111 children: popularCharms.map(function(charm) {
112 return charm.getAttrs(); })
113 });
114 popularCharmContainer.render(popularContainer);
115
116 // Add in the charm tokens for the new as well.
117 var newContainer = container.one('.bws-left .new');
118 var newCharms = this.get('store').resultsToCharmlist(
119 data.result['new']);
120 var newCharmContainer = new widgets.browser.CharmContainer({
121 name: 'New Charms',
122 cutoff: 2,
123 children: newCharms.map(function(charm) {
124 return charm.getAttrs(); })
125 });
126 newCharmContainer.render(newContainer);
127
128 // Add the charms to the cache for use in other views.
129 // Start with a reset to empty any current cached models.
130 this._cacheCharms.reset(newCharms);
131 this._cacheCharms.add(popularCharms);
132 this._cacheCharms.add(featuredCharms);
133 this.charmContainers = [
134 featuredCharmContainer,
135 newCharmContainer,
136 popularCharmContainer
137 ];
138 },
139
140 'failure': function(data, request) {
141 var message;
142 if (data && data.type) {
143 message = 'Charm API error of type: ' + data.type;
144 } else {
145 message = 'Charm API server did not respond';
146 }
147 this.get('db').notifications.add(
148 new models.Notification({
149 title: 'Failed to load sidebar content.',
150 message: message,
151 level: 'error'
152 })
153 );
154 }
155 }, this);
156 },
157
158 /**
159 * Destroy this view and clear from the dom world.
160 *
161 * @method destructor
162 *
163 */
164 destructor: function() {
165 if (this.charmContainers) {
166 Y.Array.each(this.charmContainers, function(container) {
167 container.destroy();
168 });
169 }
170 },
171
172 /**
173 * Render out the view to the DOM.
174 *
175 * @method render
176 *
177 */
178 render: function(container) {
179 if (this.get('charmID')) {
180 this._renderCharmView(container);
181 } else {
182 this._renderEditorialView(container);
183 }
184 // Bind our view to the events from the search widget used for controls.45 // Bind our view to the events from the search widget used for controls.
185 this._bindSearchWidgetEvents();46 this._bindSearchWidgetEvents();
186 }47 }
@@ -191,13 +52,8 @@
19152
192}, '0.1.0', {53}, '0.1.0', {
193 requires: [54 requires: [
194 'browser-charm-container',
195 'browser-charm-token',
196 'browser-search-widget',55 'browser-search-widget',
197 'juju-charm-store',
198 'juju-models',
199 'juju-templates',56 'juju-templates',
200 'subapp-browser-charmview',
201 'subapp-browser-mainview',57 'subapp-browser-mainview',
202 'view'58 'view'
203 ]59 ]
20460
=== modified file 'app/subapps/browser/views/view.js'
--- app/subapps/browser/views/view.js 2013-04-09 18:10:36 +0000
+++ app/subapps/browser/views/view.js 2013-04-16 00:07:30 +0000
@@ -78,63 +78,6 @@
78 );78 );
79 },79 },
8080
81 /**
82 * Helper to just render the charm details pane. This is shared in the
83 * sidebar/fullscreen views.
84 *
85 * @method _renderCharmDetails
86 * @param {BrowserCharm} charm model instance to render from.
87 * @param {Node} container node to look for a details div in to render to.
88 *
89 */
90 _renderCharmDetails: function(charm, container) {
91 var detailsNode = container.one('.bws-view-data');
92 // Destroy any current details.
93 if (this.details) {
94 this.details.destroy(true);
95 }
96 this.details = new ns.BrowserCharmView({
97 charm: charm,
98 store: this.get('store')
99 });
100 this.details.render(detailsNode);
101 },
102
103 /**
104 * Render the view of a single charm details page.
105 *
106 * @method _renderCharmView
107 * @param {Node} container the node to insert our rendered content into.
108 *
109 */
110 _renderCharmView: function(container) {
111 var tpl = this.template(),
112 tplNode = Y.Node.create(tpl);
113
114 // Create/bind the search before we wait for the charm data to load so
115 // that we're prepared for search events in case that request takes a
116 // while or even fails.
117 this._renderSearchWidget(tplNode);
118
119 // We need to have the template in the DOM for sub views to be able to
120 // expect proper structure.
121 if (!Y.Lang.isValue(container)) {
122 container = this.get('container');
123 }
124 container.setHTML(tplNode);
125
126 this.get('store').charm(this.get('charmID'), {
127 'success': function(data) {
128 var charmView = new ns.BrowserCharmView({
129 charm: new models.BrowserCharm(data),
130 store: this.get('store')
131 });
132 charmView.render(tplNode.one('.bws-view-data'), this.isFullscreen());
133 container.setHTML(tplNode);
134 },
135 'failure': this.apiFailure
136 }, this);
137 },
13881
139 /**82 /**
140 * Render out the main search widget and controls shared across various83 * Render out the main search widget and controls shared across various
@@ -271,7 +214,6 @@
271 */214 */
272 charmID: {},215 charmID: {},
273216
274
275 /**217 /**
276 * An instance of the Charmworld API object to hit for any data that218 * An instance of the Charmworld API object to hit for any data that
277 * needs fetching.219 * needs fetching.
@@ -295,7 +237,6 @@
295 *237 *
296 */238 */
297 subpath: {}239 subpath: {}
298
299 }240 }
300 });241 });
301242
302243
=== modified file 'app/templates/charm-token.handlebars'
--- app/templates/charm-token.handlebars 2013-04-15 07:26:46 +0000
+++ app/templates/charm-token.handlebars 2013-04-16 00:07:30 +0000
@@ -1,19 +1,21 @@
1<div class="charm-token yui3-g" data-charmid="{{id}}">1<a href="/bws/sidebar/{{id}}">
2 <div class="yui3-u-1-4">2 <div class="charm-token yui3-g" data-charmid="{{id}}">
3 <img url="{{ iconfile }}" alt="Icon" />3 <div class="yui3-u-1-4">
4 </div>4 <img url="{{ iconfile }}" alt="Icon" />
5 <div class="yui3-u-3-4">5 </div>
6 <a href="" class="add hidden">6 <div class="yui3-u-3-4">
7 <img src="/juju-ui/assets/images/sidebar_add_icon.jpg" alt="Add" />7 <a href="" class="add hidden">
8 </a>8 <img src="/juju-ui/assets/images/sidebar_add_icon.jpg" alt="Add" />
9 <h3 class="title">9 </a>
10 {{ name }}10 <h3 class="title">
11 </h3>11 {{ name }}
12 <div class="metadata">12 </h3>
13 {{ recent_commit_count }} {{pluralize 'commit' recent_commit_count}},13 <div class="metadata">
14 {{ recent_download_count }}14 {{ recent_commit_count }} {{pluralize 'commit' recent_commit_count}},
15 {{pluralize 'download' recent_download_count}}15 {{ recent_download_count }}
16 {{pluralize 'download' recent_download_count}}
17 </div>
18 <p class="description">{{truncate description 110 }}</p>
19 </div>
16 </div>20 </div>
17 <p class="description">{{truncate description 110 }}</p>21</a>
18 </div>
19</div>
2022
=== modified file 'test/test_browser_app.js'
--- test/test_browser_app.js 2013-04-11 14:48:10 +0000
+++ test/test_browser_app.js 2013-04-16 00:07:30 +0000
@@ -130,53 +130,6 @@
130 assert.isTrue(Y.Lang.isObject(container.one('input')));130 assert.isTrue(Y.Lang.isObject(container.one('input')));
131 });131 });
132132
133 it('caches models fetched from the api for later use', function() {
134 var container = Y.one('#subapp-browser');
135 view = new Sidebar();
136 view._cacheCharms.size().should.eql(0);
137
138 // mock out the request data for the editorial view. We want to make
139 // sure we're caching the results.
140 view.get('store').set(
141 'datasource',
142 new Y.DataSource.Local({source: sampleData}));
143 view.render(container);
144
145 view._cacheCharms.size().should.eql(5);
146 });
147
148 it('handles details event when clicking on a charm token', function(done) {
149 var container = Y.one('#subapp-browser');
150 view = new Sidebar();
151
152 // Test is successful when it completes by hitting this callback we've
153 // over written.
154 view._handleTokenSelect = function(ev) {
155 done();
156 };
157
158 view.get('store').set(
159 'datasource',
160 new Y.DataSource.Local({source: sampleData}));
161 view.render(container);
162 container.one('.charm-token').simulate('click');
163 });
164
165 it('renders details when clicking on a charm token', function() {
166 var container = Y.one('#subapp-browser');
167 view = new Sidebar();
168
169 view.get('store').set(
170 'datasource',
171 new Y.DataSource.Local({source: sampleData}));
172 view.render(container);
173 container.one('.charm-token').simulate('click');
174
175 var details_node = container.one('.bws-view-data');
176 details_node.one('h1').get('text').should.eql('byobu-classroom');
177 details_node.all('.tabs').size().should.eql(1);
178 });
179
180 });133 });
181})();134})();
182135
183136
=== modified file 'test/test_browser_charm_details.js'
--- test/test_browser_charm_details.js 2013-04-15 07:26:46 +0000
+++ test/test_browser_charm_details.js 2013-04-16 00:07:30 +0000
@@ -23,9 +23,13 @@
23 });23 });
2424
25 beforeEach(function() {25 beforeEach(function() {
26 var docBody = Y.one(document.body);26 var docBody = Y.one(document.body),
27 Y.Node.create('<div id="testcontent">' +27 testcontent = [
28 '</div>').appendTo(docBody);28 '<div id=testcontent><div class="bws-view-data">',
29 '</div></div>'
30 ].join();
31
32 Y.Node.create(testcontent).appendTo(docBody);
2933
30 // Mock out a dummy location for the Store used in view instances.34 // Mock out a dummy location for the Store used in view instances.
31 window.juju_config = {35 window.juju_config = {
@@ -87,10 +91,11 @@
87 ],91 ],
88 id: 'precise/ceph-9'92 id: 'precise/ceph-9'
89 }),93 }),
94 container: Y.Node.create('<div class="charmview"/>'),
90 store: fakeStore95 store: fakeStore
91 });96 });
9297
93 view.render(node);98 view.render();
94 Y.one('#bws-readme').get('text').should.eql('README content.');99 Y.one('#bws-readme').get('text').should.eql('README content.');
95 });100 });
96101
@@ -102,7 +107,8 @@
102 'hooks/install'107 'hooks/install'
103 ],108 ],
104 id: 'precise/ceph-9'109 id: 'precise/ceph-9'
105 })110 }),
111 container: Y.Node.create('<div class="charmview"/>')
106 });112 });
107113
108 // Hook up to the callback for the click event.114 // Hook up to the callback for the click event.
@@ -112,8 +118,7 @@
112 done();118 done();
113 };119 };
114120
115 view.render(node);121 view.render();
116 view.get('container').should.eql(node);
117 node.one('.charm .add').simulate('click');122 node.one('.charm .add').simulate('click');
118 });123 });
119124
@@ -140,10 +145,11 @@
140 ],145 ],
141 id: 'precise/ceph-9'146 id: 'precise/ceph-9'
142 }),147 }),
148 container: Y.Node.create('<div class="charmview"/>'),
143 store: fakeStore149 store: fakeStore
144 });150 });
145151
146 view.render(node);152 view.render();
147 Y.one('#bws-hooks').all('select option').size().should.equal(3);153 Y.one('#bws-hooks').all('select option').size().should.equal(3);
148154
149 // Select the hooks install and the content should update.155 // Select the hooks install and the content should update.
@@ -180,10 +186,11 @@
180 ],186 ],
181 id: 'precise/ceph-9'187 id: 'precise/ceph-9'
182 }),188 }),
189 container: Y.Node.create('<div class="charmview"/>'),
183 store: fakeStore190 store: fakeStore
184 });191 });
185192
186 view.render(node);193 view.render();
187 Y.one('#bws-readme').get('innerHTML').should.eql(194 Y.one('#bws-readme').get('innerHTML').should.eql(
188 '<h1>README Header</h1>');195 '<h1>README Header</h1>');
189 });196 });
@@ -200,9 +207,10 @@
200 'type': 'int'207 'type': 'int'
201 }208 }
202 }209 }
203 })210 }),
211 container: Y.Node.create('<div class="charmview"/>')
204 });212 });
205 view.render(node);213 view.render();
206214
207 Y.one('#bws-configuration dd div').get('text').should.eql(215 Y.one('#bws-configuration dd div').get('text').should.eql(
208 'Default: 9160');216 'Default: 9160');
@@ -235,9 +243,10 @@
235 charm: new models.BrowserCharm({243 charm: new models.BrowserCharm({
236 files: [],244 files: [],
237 id: 'precise/ceph-9'245 id: 'precise/ceph-9'
238 })246 }),
247 container: Y.Node.create('<div class="charmview"/>')
239 });248 });
240 view.render(node);249 view.render();
241250
242 view._loadQAContent = function() {251 view._loadQAContent = function() {
243 // This test is just verifying that we don't timeout. The event fired,252 // This test is just verifying that we don't timeout. The event fired,
@@ -274,7 +283,8 @@
274 // We don't want any files so we don't have to mock/load them.283 // We don't want any files so we don't have to mock/load them.
275 data.files = [];284 data.files = [];
276 var view = new CharmView({285 var view = new CharmView({
277 charm: new models.BrowserCharm(data)286 charm: new models.BrowserCharm(data),
287 container: Y.Node.create('<div class="charmview"/>')
278 });288 });
279289
280 // Hook up to the callback for the click event.290 // Hook up to the callback for the click event.
@@ -283,9 +293,8 @@
283 done();293 done();
284 };294 };
285295
286 view.render(node);296 view.render();
287 view.get('container').should.eql(node);297 node.one('.changelog .expand').simulate('click');
288 node.one('.changelog .expandToggle').simulate('click');
289 });298 });
290299
291 it('changelog is reformatted and displayed', function() {300 it('changelog is reformatted and displayed', function() {
@@ -295,10 +304,11 @@
295 // We don't want any files so we don't have to mock/load them.304 // We don't want any files so we don't have to mock/load them.
296 data.files = [];305 data.files = [];
297 view = new CharmView({306 view = new CharmView({
298 charm: new models.BrowserCharm(data)307 charm: new models.BrowserCharm(data),
308 container: Y.Node.create('<div class="charmview"/>')
299 });309 });
300310
301 view.render(node);311 view.render();
302 // Basics that we have the right number of nodes.312 // Basics that we have the right number of nodes.
303 node.all('.remaining li').size().should.eql(9);313 node.all('.remaining li').size().should.eql(9);
304 node.all('.first p').size().should.eql(1);314 node.all('.first p').size().should.eql(1);

Subscribers

People subscribed via source and target branches