Merge lp:~abentley/launchpad/back-button-support into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 14284
Proposed branch: lp:~abentley/launchpad/back-button-support
Merge into: lp:launchpad
Diff against target: 260 lines (+102/-25)
2 files modified
lib/lp/bugs/javascript/buglisting.js (+41/-18)
lib/lp/bugs/javascript/tests/test_buglisting.js (+61/-7)
To merge this branch: bzr merge lp:~abentley/launchpad/back-button-support
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+81875@code.launchpad.net

Commit message

Dynamic bug listings support back button

Description of the change

= Summary =
Support back, next, reload in browser

== Proposed fix ==
Use YUI History module

== Pre-implementation notes ==
Discussed with deryck

== Implementation details ==
Rendering is now triggered by YUI's history:change event. The event contains the batch_key, which is used to retrieve the batch from the cache.

Various methods had their parameters changed, to reduce the number of sites where we generate a batch_key or batch query. update_from_model is split into update_from new_model and update_from_cache, the latter triggering the render using Y.history.

I also reduced the number of places where namespace.ListingNavigator is used, preferring this.constructor where applicable.

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

== Demo and Q/A ==
Go to a bug listing page. Click Next, Next. Press the browser back button. You should be on the second batch. Click the browser forward button. You should be on the third batch. Click reload. You should still be on the third batch.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/javascript/buglisting.js
  lib/lp/bugs/javascript/tests/test_buglisting.js

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

This looks fine to land.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/javascript/buglisting.js'
2--- lib/lp/bugs/javascript/buglisting.js 2011-11-07 18:48:20 +0000
3+++ lib/lp/bugs/javascript/buglisting.js 2011-11-10 16:57:26 +0000
4@@ -48,6 +48,7 @@
5 initializer: function(config) {
6 var lp_client = new Y.lp.client.Launchpad();
7 var cache = lp_client.wrap_resource(null, config.cache);
8+ var batch_key;
9 var template = config.template;
10 this.set('search_params', namespace.get_query(config.current_url));
11 delete this.get('search_params').start;
12@@ -56,7 +57,7 @@
13 delete this.get('search_params').orderby;
14 this.set('io_provider', config.io_provider);
15 this.set('field_visibility', cache.field_visibility);
16- this.handle_new_batch(cache);
17+ batch_key = this.handle_new_batch(cache);
18 this.set('current_batch', cache);
19 // Work around mustache.js bug 48 "Blank lines are not preserved."
20 // https://github.com/janl/mustache.js/issues/48
21@@ -68,6 +69,22 @@
22 if (Y.Lang.isValue(config.navigation_indices)) {
23 this.set('navigation_indices', config.navigation_indices);
24 }
25+ this.set('history', new Y.History({
26+ initialState: {
27+ batch_key: batch_key
28+ }
29+ }));
30+ this.get('history').on('change', this.history_changed, this);
31+ },
32+
33+ /**
34+ * Event handler for history:change events.
35+ */
36+ history_changed: function(e){
37+ var batch_key = e.newVal.batch_key;
38+ var batch = this.get('batches')[batch_key];
39+ this.set('current_batch', batch);
40+ this.render();
41 },
42
43 /**
44@@ -76,12 +93,11 @@
45 * The node is also marked up appropriately.
46 */
47 clickAction: function(selector, callback) {
48- that = this;
49 var nodes = Y.all(selector);
50 nodes.on('click', function(e) {
51 e.preventDefault();
52- callback.call(that);
53- });
54+ callback.call(this);
55+ }, this);
56 nodes.addClass('js-action');
57 },
58
59@@ -92,13 +108,14 @@
60 */
61 handle_new_batch: function(batch) {
62 var key, i;
63- var batch_key = namespace.ListingNavigator.get_batch_key(batch);
64+ var batch_key = this.constructor.get_batch_key(batch);
65 Y.each(this.get('field_visibility'), function(value, key) {
66 for (i = 0; i < batch.mustache_model.bugtasks.length; i++) {
67 delete batch.mustache_model.bugtasks[i][key];
68 }
69 });
70 this.get('batches')[batch_key] = batch;
71+ return batch_key;
72 },
73
74 /**
75@@ -135,16 +152,20 @@
76 'inactive', this.get('current_batch').next === null);
77 },
78
79+ update_from_new_model: function(query, model){
80+ this.update_from_cache(query, this.handle_new_batch(model));
81+ },
82+
83 /**
84 * A shim to use the data of an LP.cache to render the bug listings and
85 * cache their data.
86 *
87- * order_by is the ordering used by the model.
88+ * query is a mapping of query variables generated by get_batch_query.
89+ * batch_key is the key generated by get_batch_key for the model.
90 */
91- update_from_model: function(model) {
92- this.handle_new_batch(model);
93- this.set('current_batch', model);
94- this.render();
95+ update_from_cache: function(query, batch_key) {
96+ var url = '?' + Y.QueryString.stringify(query);
97+ this.get('history').addValue('batch_key', batch_key, {url: url});
98 },
99
100 /**
101@@ -173,14 +194,14 @@
102 * will be retrieved and cached upon retrieval.
103 */
104 update: function(config) {
105- var key = namespace.ListingNavigator.get_batch_key(config);
106+ var key = this.constructor.get_batch_key(config);
107 var cached_batch = this.get('batches')[key];
108+ var query = this.get_batch_query(config);
109 if (Y.Lang.isValue(cached_batch)) {
110- this.set('current_batch', cached_batch);
111- this.render();
112+ this.update_from_cache(query, key);
113 }
114 else {
115- this.load_model(config);
116+ this.load_model(query);
117 }
118 },
119
120@@ -248,12 +269,14 @@
121
122 /**
123 * Load the specified batch via ajax. Display & cache on load.
124+ *
125+ * query is the query string for the URL, as a mapping. (See
126+ * get_batch_query).
127 */
128- load_model: function(config) {
129- var query = this.get_batch_query(config);
130+ load_model: function(query) {
131 var load_model_config = {
132 on: {
133- success: Y.bind(this.update_from_model, this)
134+ success: Y.bind(this.update_from_new_model, this, query)
135 }
136 };
137 var context = this.get('current_batch').context;
138@@ -324,4 +347,4 @@
139 };
140
141
142-}, "0.1", {"requires": ["node", 'lp.client']});
143+}, "0.1", {"requires": ["history", "node", 'lp.client']});
144
145=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.js'
146--- lib/lp/bugs/javascript/tests/test_buglisting.js 2011-11-07 18:48:20 +0000
147+++ lib/lp/bugs/javascript/tests/test_buglisting.js 2011-11-10 16:57:26 +0000
148@@ -38,7 +38,8 @@
149 var target = Y.Node.create('<div id="client-listing"></div>');
150 var model = {
151 field_visibility: {show_item: true},
152- mustache_model: {bugtasks: []}
153+ mustache_model: {bugtasks: []},
154+ memo: 1
155 };
156 var navigator = new module.ListingNavigator({
157 current_url: '',
158@@ -46,8 +47,13 @@
159 template: '',
160 target: target
161 });
162- navigator.update_from_model(
163- {mustache_model: {bugtasks: [{show_item: true}]}});
164+ var batch = {
165+ mustache_model: {
166+ bugtasks: [{show_item: true}]},
167+ memo: 2
168+ };
169+ var query = navigator.get_batch_query(batch);
170+ navigator.update_from_new_model(query, batch);
171 bugtask = navigator.get('current_batch').mustache_model.bugtasks[0];
172 Y.Assert.isFalse(bugtask.hasOwnProperty('show_item'));
173 }
174@@ -265,8 +271,8 @@
175 suite.add(new Y.Test.Case({
176 name: 'Batch caching',
177
178- test_update_from_model_caches: function() {
179- /* update_from_model caches the settings in the module.batches. */
180+ test_update_from_new_model_caches: function() {
181+ /* update_from_new_model caches the settings in the module.batches. */
182 var lp_cache = {
183 context: {
184 resource_type_link: 'http://foo_type',
185@@ -303,7 +309,8 @@
186 ],
187 bugtasks: ['a', 'b', 'c']
188 }};
189- navigator.update_from_model(batch);
190+ var query = navigator.get_batch_query(batch);
191+ navigator.update_from_new_model(query, batch);
192 Y.lp.testing.assert.assert_equal_structure(
193 batch, navigator.get('batches')[key]);
194 },
195@@ -429,10 +436,13 @@
196 order_by: 'foo',
197 last_start: 23
198 };
199+ var target = Y.Node.create('<div id="client-listing"></div>');
200 return new module.ListingNavigator({
201 current_url: url,
202 cache: lp_cache,
203- io_provider: mock_io
204+ io_provider: mock_io,
205+ target: target,
206+ template: ''
207 });
208 };
209
210@@ -488,6 +498,50 @@
211 }));
212
213 suite.add(new Y.Test.Case({
214+ name: 'browser history',
215+
216+ /**
217+ * Update from cache generates a change event for the specified batch.
218+ */
219+ test_update_from_cache_generates_event: function(){
220+ var navigator = get_navigator('');
221+ var e = null;
222+ navigator.get('history').on('change', function(inner_e){
223+ e = inner_e;
224+ });
225+ navigator.get('batches')['some-batch-key'] = {
226+ mustache_model: {
227+ bugtasks: []
228+ }
229+ };
230+ navigator.update_from_cache({foo: 'bar'}, 'some-batch-key');
231+ Y.Assert.areEqual('some-batch-key', e.newVal.batch_key);
232+ Y.Assert.areEqual('?foo=bar', e._options.url);
233+ },
234+
235+ /**
236+ * When a change event is emitted, the relevant batch becomes the current
237+ * batch and is rendered.
238+ */
239+ test_change_event_renders_cache: function(){
240+ var navigator = get_navigator('');
241+ var batch = {
242+ mustache_model: {
243+ bugtasks: [],
244+ foo: 'bar'
245+ }
246+ };
247+ navigator.set('template', '{{foo}}');
248+ navigator.get('batches')['some-batch-key'] = batch;
249+ navigator.get('history').addValue('batch_key', 'some-batch-key');
250+ Y.Assert.areEqual(batch, navigator.get('current_batch'));
251+ Y.Assert.areEqual('bar', navigator.get('target').getContent());
252+ }
253+}));
254+
255+suite.add(new Y.Test.Case({
256+ name: 'from_page tests',
257+
258 setUp: function() {
259 window.LP = {
260 cache: {