Merge lp:~abentley/launchpad/display-cleanup into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Deryck Hodge
Approved revision: no longer in the source branch.
Merged at revision: 14216
Proposed branch: lp:~abentley/launchpad/display-cleanup
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/navigate-batches
Diff against target: 353 lines (+186/-26)
4 files modified
lib/lp/bugs/browser/bugtask.py (+1/-0)
lib/lp/bugs/browser/tests/test_bugtask.py (+1/-0)
lib/lp/bugs/javascript/buglisting.js (+55/-7)
lib/lp/bugs/javascript/tests/test_buglisting.js (+129/-19)
To merge this branch: bzr merge lp:~abentley/launchpad/display-cleanup
Reviewer Review Type Date Requested Status
Abel Deuring (community) Approve
Deryck Hodge Pending
Review via email: mp+80621@code.launchpad.net

Commit message

[r=adeuring][bug=882795] Polish ajax bug listing navigation.

Description of the change

= Summary =
Fix bug #882795: ajax bug listing navigation needs polish

== Proposed fix ==
Replace all navigation nodes with hyperlink nodes.
Mark first/previous disabled on first batch.
Mark last/next disabled on last batch.
Update batch info (1 -> 5 of 10) on navigation.

== Pre-implementation notes ==
None

== Implementation details ==
None

== Tests ==
bin/test -t test_buglisting.html

== Demo and Q/A ==
- Go to a listing page. first & previous should be disabled.
- Click last. next & last should be disabled, and first & previous should be green. The info about the batch should be updated.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/javascript/buglisting.js
  lib/lp/bugs/templates/bugs-listing-table.pt
  lib/lp/bugs/templates/buglisting-default.pt
  lib/lp/bugs/browser/tests/test_bugtask.py
  lib/lp/bugs/browser/bugtask.py
  lib/lp/bugs/templates/bugs-listing-table-without-navlinks.pt
  lib/lp/bugs/javascript/tests/test_buglisting.js

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

(16:40:10) adeuring: abentley: I think "this.current_batch.mustache_model.bugtasks.length + 1" in line 113 is wrong. Shouldn't that be "... -1"?
(16:40:35) adeuring: (line 113 of the diff)
(16:41:09) adeuring: abentley: no, that should be "+0"...
(16:41:13) abentley: adeuring: So, the +1 is trying to compensate for start being 0-indexed.
(16:41:47) adeuring: abentley: right. But for 5 results and start = 0, we want to show "results 1-> 5"
(16:41:56) adeuring: while we get "1 to 6"
(16:42:08) adeuring: abentley: just click on any of the links ;)
(16:42:15) abentley: Hmm, so why does it look right in use?
(16:42:36) adeuring: and compare the number in "n->m of k" with the real number of results ;)
(16:42:49) abentley: adeuring: that's an estimate, though.
(16:43:26) adeuring: abentley: well, k is an estimate (at least for StormRangeFactory), but we known the number of results in the batch precisely
(16:44:07) abentley: adeuring: You're right, I'll fix it.
(16:44:15) adeuring: abentley: cool, thanks
(16:46:07) adeuring: abentley: another quirk: For the first batch, the "first" and "previous" links are gray, but still active. "unlinking" them is something for antother branch, I assume?
(16:46:30) adeuring: (same for the last batch and the last/next links)
(16:47:39) abentley: adeuring: I would prefer if the CSS was fixed so that inactive links don't show as links.
(16:48:33) adeuring: abentley: right, makes sense. So, r=me, and file a bug about the links?
(16:49:02) abentley: adeuring: Thanks. I'll file a bug.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2011-10-28 15:09:16 +0000
+++ lib/lp/bugs/browser/bugtask.py 2011-10-28 15:09:17 +0000
@@ -2495,6 +2495,7 @@
2495 cache.objects['next'] = _getBatchInfo(next_batch)2495 cache.objects['next'] = _getBatchInfo(next_batch)
2496 prev_batch = batch_navigator.batch.prevBatch()2496 prev_batch = batch_navigator.batch.prevBatch()
2497 cache.objects['prev'] = _getBatchInfo(prev_batch)2497 cache.objects['prev'] = _getBatchInfo(prev_batch)
2498 cache.objects['total'] = batch_navigator.batch.total()
2498 cache.objects['order_by'] = ','.join(2499 cache.objects['order_by'] = ','.join(
2499 get_sortorder_from_request(self.request))2500 get_sortorder_from_request(self.request))
2500 cache.objects['forwards'] = batch_navigator.batch.range_forwards2501 cache.objects['forwards'] = batch_navigator.batch.range_forwards
25012502
=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
--- lib/lp/bugs/browser/tests/test_bugtask.py 2011-10-28 15:09:16 +0000
+++ lib/lp/bugs/browser/tests/test_bugtask.py 2011-10-28 15:09:17 +0000
@@ -1424,6 +1424,7 @@
1424 self.assertIs(None, cache.objects['memo'])1424 self.assertIs(None, cache.objects['memo'])
1425 self.assertEqual(0, cache.objects['start'])1425 self.assertEqual(0, cache.objects['start'])
1426 self.assertTrue(cache.objects['forwards'])1426 self.assertTrue(cache.objects['forwards'])
1427 self.assertEqual(1, cache.objects['total'])
14271428
1428 def test_cache_has_all_batch_vars_specified(self):1429 def test_cache_has_all_batch_vars_specified(self):
1429 """Cache has all the needed variables.1430 """Cache has all the needed variables.
14301431
=== modified file 'lib/lp/bugs/javascript/buglisting.js'
--- lib/lp/bugs/javascript/buglisting.js 2011-10-28 15:09:16 +0000
+++ lib/lp/bugs/javascript/buglisting.js 2011-10-28 15:09:17 +0000
@@ -18,11 +18,13 @@
18 * cache is the JSONRequestCache for the batch.18 * cache is the JSONRequestCache for the batch.
19 * template is the template to use for rendering batches.19 * template is the template to use for rendering batches.
20 * target is a YUI node to update when rendering batches.20 * target is a YUI node to update when rendering batches.
21 * navigation_indices is a YUI NodeList of nodes to update with the current
22 * batch info.
21 * io_provider is something providing the Y.io interface, typically used for23 * io_provider is something providing the Y.io interface, typically used for
22 * testing. Defaults to Y.io.24 * testing. Defaults to Y.io.
23 */25 */
24namespace.ListingNavigator = function(current_url, cache, template, target,26namespace.ListingNavigator = function(current_url, cache, template, target,
25 io_provider) {27 navigation_indices, io_provider) {
26 var lp_client = new Y.lp.client.Launchpad();28 var lp_client = new Y.lp.client.Launchpad();
27 this.search_params = namespace.get_query(current_url);29 this.search_params = namespace.get_query(current_url);
28 delete this.search_params.start;30 delete this.search_params.start;
@@ -34,6 +36,14 @@
34 this.template = template;36 this.template = template;
35 this.target = target;37 this.target = target;
36 this.batches = {};38 this.batches = {};
39 this.backwards_navigation = new Y.NodeList([]);
40 this.forwards_navigation = new Y.NodeList([]);
41 if (!Y.Lang.isValue(navigation_indices)){
42 navigation_indices = new Y.NodeList([]);
43 }
44 this.navigation_indices = navigation_indices;
45 this.batch_info_template = '<strong>{{start}}</strong> &rarr; ' +
46 '<strong>{{end}}</strong> of {{total}} results';
37};47};
3848
3949
@@ -53,18 +63,38 @@
53 nodes.addClass('js-action');63 nodes.addClass('js-action');
54};64};
5565
66/**
67 * Rewrite all nodes with navigation classes so that they are hyperlinks.
68 * Content is retained.
69 */
70namespace.linkify_navigation = function(){
71 Y.each(['previous', 'next', 'first', 'last'], function(class_name){
72 Y.all('.' + class_name).each(function(node){
73 new_node = Y.Node.create('<a href="#"></a>');
74 new_node.addClass(class_name);
75 new_node.setContent(node.getContent());
76 node.replace(new_node);
77 });
78 });
79};
5680
57/**81/**
58 * Factory to return a ListingNavigator for the given page.82 * Factory to return a ListingNavigator for the given page.
59 */83 */
60namespace.ListingNavigator.from_page = function(){84namespace.ListingNavigator.from_page = function(){
61 var target = Y.one('#client-listing');85 var target = Y.one('#client-listing');
86 var navigation_indices = Y.all('.batch-navigation-index');
62 var navigator = new namespace.ListingNavigator(87 var navigator = new namespace.ListingNavigator(
63 window.location, LP.cache, LP.mustache_listings, target);88 window.location, LP.cache, LP.mustache_listings, target,
89 navigation_indices);
90 namespace.linkify_navigation();
91 navigator.backwards_navigation = Y.all('.first,.previous');
92 navigator.forwards_navigation = Y.all('.last,.next');
64 navigator.clickAction('.first', navigator.first_batch);93 navigator.clickAction('.first', navigator.first_batch);
65 navigator.clickAction('.next', navigator.next_batch);94 navigator.clickAction('.next', navigator.next_batch);
66 navigator.clickAction('.previous', navigator.prev_batch);95 navigator.clickAction('.previous', navigator.prev_batch);
67 navigator.clickAction('.last', navigator.last_batch);96 navigator.clickAction('.last', navigator.last_batch);
97 navigator.render_navigation();
68 return navigator;98 return navigator;
69};99};
70100
@@ -77,13 +107,31 @@
77 *107 *
78 * The template is always LP.mustache_listings.108 * The template is always LP.mustache_listings.
79 */109 */
80namespace.ListingNavigator.prototype.rendertable = function(){110namespace.ListingNavigator.prototype.render = function(){
81 if (! Y.Lang.isValue(this.target)){111 if (! Y.Lang.isValue(this.target)){
82 return;112 return;
83 }113 }
84 var model = this.current_batch.mustache_model;114 var model = this.current_batch.mustache_model;
85 var txt = Mustache.to_html(this.template, model);115 var batch_info = Mustache.to_html(this.batch_info_template, {
86 this.target.set('innerHTML', txt);116 start: this.current_batch.start + 1,
117 end: this.current_batch.start +
118 this.current_batch.mustache_model.bugtasks.length,
119 total: this.current_batch.total
120 });
121 this.target.setContent(Mustache.to_html(this.template, model));
122 this.navigation_indices.setContent(batch_info);
123 this.render_navigation();
124};
125
126
127/**
128 * Enable/disable navigation links as appropriate.
129 */
130namespace.ListingNavigator.prototype.render_navigation = function(){
131 this.backwards_navigation.toggleClass(
132 'inactive', this.current_batch.prev === null);
133 this.forwards_navigation.toggleClass(
134 'inactive', this.current_batch.next === null);
87};135};
88136
89137
@@ -105,7 +153,7 @@
105 var key = namespace.ListingNavigator.get_batch_key(model);153 var key = namespace.ListingNavigator.get_batch_key(model);
106 this.batches[key] = model;154 this.batches[key] = model;
107 this.current_batch = model;155 this.current_batch = model;
108 this.rendertable();156 this.render();
109};157};
110158
111159
@@ -139,7 +187,7 @@
139 var cached_batch = this.batches[key];187 var cached_batch = this.batches[key];
140 if (Y.Lang.isValue(cached_batch)){188 if (Y.Lang.isValue(cached_batch)){
141 this.current_batch = cached_batch;189 this.current_batch = cached_batch;
142 this.rendertable();190 this.render();
143 }191 }
144 else {192 else {
145 this.load_model(config);193 this.load_model(config);
146194
=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.js'
--- lib/lp/bugs/javascript/tests/test_buglisting.js 2011-10-28 15:09:16 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting.js 2011-10-28 15:09:17 +0000
@@ -21,26 +21,132 @@
21}));21}));
2222
23suite.add(new Y.Test.Case({23suite.add(new Y.Test.Case({
24 name: 'rendertable',24 name: 'render',
2525
26 test_rendertable_no_client_listing: function() {26 tearDown: function(){
27 Y.one('#fixture').setContent('');
28 },
29
30 test_render_no_client_listing: function() {
27 // Rendering should not error with no #client-listing.31 // Rendering should not error with no #client-listing.
28 var navigator = new module.ListingNavigator();32 var navigator = new module.ListingNavigator();
29 navigator.rendertable();33 navigator.render();
30 },34 },
31 test_rendertable: function() {35 get_render_navigator: function() {
32 // Rendering should work with #client-listing supplied.
33 var target = Y.Node.create('<div id="client-listing"></div>');36 var target = Y.Node.create('<div id="client-listing"></div>');
34 var lp_cache = {37 var lp_cache = {
35 mustache_model: {38 mustache_model: {
36 foo: 'bar'39 foo: 'bar',
37 }40 bugtasks: ['a', 'b', 'c']
41 },
42 next: null,
43 prev: null,
44 start: 5,
45 total: 256
38 };46 };
39 var template = "{{foo}}";47 var template = "{{foo}}";
40 var navigator = new module.ListingNavigator(48 var navigator = new module.ListingNavigator(
41 null, lp_cache, template, target);49 null, lp_cache, template, target);
42 navigator.rendertable();50 var index = Y.Node.create(
43 Y.Assert.areEqual('bar', navigator.target.get('innerHTML'));51 '<div><strong>3</strong> &rarr; <strong>4</strong>' +
52 ' of 512 results</div>');
53 navigator.navigation_indices.push(index);
54 navigator.backwards_navigation.push(Y.Node.create('<div></div>'));
55 navigator.forwards_navigation.push(Y.Node.create('<div></div>'));
56 return navigator;
57 },
58 test_render: function() {
59 // Rendering should work with #client-listing supplied.
60 var navigator = this.get_render_navigator();
61 navigator.render();
62 Y.Assert.areEqual('bar', navigator.target.getContent());
63 },
64 /**
65 * render_navigation should disable "previous" and "first" if there is
66 * no previous batch (i.e. we're at the beginning.)
67 */
68 test_render_navigation_disables_backwards_navigation_if_no_prev:
69 function() {
70 var navigator = this.get_render_navigator();
71 var action = navigator.backwards_navigation.item(0);
72 navigator.render_navigation();
73 Y.Assert.isTrue(action.hasClass('inactive'));
74 },
75 /**
76 * render_navigation should enable "previous" and "first" if there is
77 * a previous batch (i.e. we're not at the beginning.)
78 */
79 test_render_navigation_enables_backwards_navigation_if_prev:
80 function() {
81 var navigator = this.get_render_navigator();
82 var action = navigator.backwards_navigation.item(0);
83 action.addClass('inactive');
84 navigator.current_batch.prev = {
85 start: 1, memo: 'pi'
86 };
87 navigator.render_navigation();
88 Y.Assert.isFalse(action.hasClass('inactive'));
89 },
90 /**
91 * render_navigation should disable "next" and "last" if there is
92 * no next batch (i.e. we're at the end.)
93 */
94 test_render_navigation_disables_forwards_navigation_if_no_next:
95 function() {
96 var navigator = this.get_render_navigator();
97 var action = navigator.forwards_navigation.item(0);
98 navigator.render_navigation();
99 Y.Assert.isTrue(action.hasClass('inactive'));
100 },
101 /**
102 * render_navigation should enable "next" and "last" if there is a next
103 * batch (i.e. we're not at the end.)
104 */
105 test_render_navigation_enables_forwards_navigation_if_next: function() {
106 var navigator = this.get_render_navigator();
107 var action = navigator.forwards_navigation.item(0);
108 action.addClass('inactive');
109 navigator.current_batch.next = {
110 start: 1, memo: 'pi'
111 };
112 navigator.render_navigation();
113 Y.Assert.isFalse(action.hasClass('inactive'));
114 },
115 /**
116 * linkify_navigation should convert previous, next, first last into
117 * hyperlinks, while retaining the original content.
118 */
119 test_linkify_navigation: function(){
120 Y.one('#fixture').setContent(
121 '<span class="previous">PreVious</span>' +
122 '<span class="next">NeXt</span>' +
123 '<span class="first">FiRST</span>' +
124 '<span class="last">lAst</span>');
125 module.linkify_navigation();
126 function checkNav(selector, content){
127 var node = Y.one(selector);
128 Y.Assert.areEqual('a', node.get('tagName').toLowerCase());
129 Y.Assert.areEqual(content, node.getContent());
130 Y.Assert.areEqual('#', node.get('href').substr(-1, 1));
131 }
132 checkNav('.previous', 'PreVious');
133 checkNav('.next', 'NeXt');
134 checkNav('.first', 'FiRST');
135 checkNav('.last', 'lAst');
136 },
137 /**
138 * Render should update the navigation_indices with the result info.
139 */
140 test_render_navigation_indices: function(){
141 var navigator = this.get_render_navigator();
142 index = navigator.navigation_indices.item(0);
143 Y.Assert.areEqual(
144 '<strong>3</strong> \u2192 <strong>4</strong> of 512 results',
145 index.getContent());
146 navigator.render();
147 Y.Assert.areEqual(
148 '<strong>6</strong> \u2192 <strong>8</strong> of 256 results',
149 index.getContent());
44 }150 }
45}));151}));
46152
@@ -58,26 +164,29 @@
58 web_link: 'http://foo/bar'164 web_link: 'http://foo/bar'
59 },165 },
60 mustache_model: {166 mustache_model: {
61 foo: 'bar'167 foo: 'bar',
168 bugtasks: []
62 }169 }
63 };170 };
64 var target = Y.Node.create('<div id="client-listing"></div>');171 var target = Y.Node.create('<div id="client-listing"></div>');
65 var navigator = new module.ListingNavigator(172 var navigator = new module.ListingNavigator(
66 "http://yahoo.com?start=5&memo=6&direction=backwards",173 "http://yahoo.com?start=5&memo=6&direction=backwards",
67 lp_cache, "<ol>" + "{{#item}}<li>{{name}}</li>{{/item}}</ol>",174 lp_cache, "<ol>" + "{{#item}}<li>{{name}}</li>{{/item}}</ol>",
68 target, mock_io);175 target, null, mock_io);
69 navigator.first_batch('intensity');176 navigator.first_batch('intensity');
70 Y.Assert.areEqual('', navigator.target.get('innerHTML'));177 Y.Assert.areEqual('', navigator.target.getContent());
71 mock_io.last_request.successJSON({178 mock_io.last_request.successJSON({
72 context: {179 context: {
73 resource_type_link: 'http://foo_type',180 resource_type_link: 'http://foo_type',
74 web_link: 'http://foo/bar'181 web_link: 'http://foo/bar'
75 },182 },
76 mustache_model:183 mustache_model:
77 {item: [184 {
185 item: [
78 {name: 'first'},186 {name: 'first'},
79 {name: 'second'}187 {name: 'second'}],
80 ]},188 bugtasks: []
189 },
81 order_by: 'intensity',190 order_by: 'intensity',
82 start: 0,191 start: 0,
83 forwards: true,192 forwards: true,
@@ -91,7 +200,7 @@
91 var navigator = this.get_intensity_listing();200 var navigator = this.get_intensity_listing();
92 var mock_io = navigator.io_provider;201 var mock_io = navigator.io_provider;
93 Y.Assert.areEqual('<ol><li>first</li><li>second</li></ol>',202 Y.Assert.areEqual('<ol><li>first</li><li>second</li></ol>',
94 navigator.target.get('innerHTML'));203 navigator.target.getContent());
95 Y.Assert.areEqual('/bar/+bugs/++model++?orderby=intensity&start=0',204 Y.Assert.areEqual('/bar/+bugs/++model++?orderby=intensity&start=0',
96 mock_io.last_request.url);205 mock_io.last_request.url);
97 },206 },
@@ -247,7 +356,8 @@
247 order_by: 'foo',356 order_by: 'foo',
248 last_start: 23357 last_start: 23
249 };358 };
250 return new module.ListingNavigator(url, lp_cache, null, null, mock_io);359 return new module.ListingNavigator(url, lp_cache, null, null, null,
360 mock_io);
251};361};
252362
253suite.add(new Y.Test.Case({363suite.add(new Y.Test.Case({