launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05670
[Merge] lp:~abentley/launchpad/cheap-spinner into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/cheap-spinner into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #885272 in Launchpad itself: "Need a loading icon for bug listings ajax navigation and sorting"
https://bugs.launchpad.net/launchpad/+bug/885272
For more details, see:
https://code.launchpad.net/~abentley/launchpad/cheap-spinner/+merge/83344
= Summary =
Fix bug #885272: Need a loading icon for bug listings ajax navigation and sorting
== Proposed fix ==
Add a hidden spinner at the navigation links and show it when loading.
== Pre-implementation notes ==
Discussed briefly with deryck
== Implementation details ==
The spinners are hidden using the visibility property because they are taller than the navigation text, and if they were display: none, the page would jump when they appeared.
deryck asked me to either complete his branch lp:~deryck/launchpad/buglists-loading-885272 or implement a solution in the style of wallyworld's ajax batch navigator. I didn't understand how to complete deryck's branch, so I did the latter.
== Tests ==
bin/test -t buglisting.html
== Demo and Q/A ==
Go go a bug listing such as https://bugs.qastaging.launchpad.net/nova/+bugs
Click "Bug number". A spinner should appear while the new batch is loading.
Click "Next", then "First". No spinner should appear, because the listings don't need to be loaded.
= 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
--
https://code.launchpad.net/~abentley/launchpad/cheap-spinner/+merge/83344
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/cheap-spinner into lp:launchpad.
=== modified file 'lib/lp/bugs/javascript/buglisting.js'
--- lib/lp/bugs/javascript/buglisting.js 2011-11-18 20:11:11 +0000
+++ lib/lp/bugs/javascript/buglisting.js 2011-11-24 20:48:27 +0000
@@ -41,7 +41,8 @@
forwards_navigation: {valueFn: empty_nodelist},
io_provider: {value: null},
pre_fetch: {value: false},
- navigation_indices: {valueFn: empty_nodelist}
+ navigation_indices: {valueFn: empty_nodelist},
+ spinners: {valueFn: empty_nodelist}
};
@@ -57,11 +58,6 @@
delete this.get('search_params').direction;
delete this.get('search_params').orderby;
this.set('io_provider', config.io_provider);
- this.set('error_handler', new Y.lp.client.ErrorHandler());
- this.get('error_handler').showError = Y.bind(
- Y.lp.app.errors.display_error, window, null);
- this.set(
- 'failure_handler', this.get('error_handler').getFailureHandler());
this.set('field_visibility', cache.field_visibility);
batch_key = this.handle_new_batch(cache);
this.set('current_batch', cache);
@@ -72,6 +68,7 @@
template = template.replace(/\n/g, ' ');
}
this.set('template', template);
+ this.set_pending(false);
this.set('target', config.target);
if (Y.Lang.isValue(config.navigation_indices)) {
this.set('navigation_indices', config.navigation_indices);
@@ -84,6 +81,17 @@
this.get('history').on('change', this.history_changed, this);
},
+ get_failure_handler: function(fetch_only){
+ var error_handler = new Y.lp.client.ErrorHandler();
+ error_handler.showError = Y.bind(
+ Y.lp.app.errors.display_error, window, null);
+ if (!fetch_only){
+ error_handler.clearProgressUI = Y.bind(
+ this.set_pending, this, false);
+ }
+ return error_handler.getFailureHandler();
+ },
+
/**
* Event handler for history:change events.
*/
@@ -152,6 +160,15 @@
this.render_navigation();
},
+ set_pending: function(is_pending){
+ if (is_pending){
+ this.get('spinners').setStyle('visibility', 'visible');
+ }
+ else{
+ this.get('spinners').setStyle('visibility', 'hidden');
+ }
+ },
+
has_prev: function(){
return !Y.Lang.isNull(this.get('current_batch').prev);
},
@@ -175,6 +192,7 @@
if (fetch_only) {
return;
}
+ this.set_pending(false);
this.update_from_cache(query, batch_key);
},
@@ -356,13 +374,16 @@
on: {
success: Y.bind(
this.update_from_new_model, this, query, fetch_only),
- failure: this.get('failure_handler')
+ failure: this.get_failure_handler(fetch_only)
}
};
var context = this.get('current_batch').context;
if (Y.Lang.isValue(this.get('io_provider'))) {
load_model_config.io_provider = this.get('io_provider');
}
+ if (!fetch_only){
+ this.set_pending(true);
+ }
Y.lp.client.load_model(
context, '+bugs', load_model_config, query);
}
@@ -380,6 +401,12 @@
new_node.addClass(class_name);
new_node.setContent(node.getContent());
node.replace(new_node);
+ if (class_name === 'first'){
+ var spinner_node = Y.Node.create(
+ '<img class="spinner" src="/@@/spinner"'+
+ ' alt="Loading..." />');
+ new_node.insertBefore(spinner_node, new_node);
+ }
});
});
};
@@ -401,15 +428,16 @@
var target = Y.one('#client-listing');
var navigation_indices = Y.all('.batch-navigation-index');
var pre_fetch = get_feature_flag('bugs.dynamic_bug_listings.pre_fetch');
+ namespace.linkify_navigation();
var navigator = new namespace.ListingNavigator({
current_url: window.location,
cache: LP.cache,
template: LP.mustache_listings,
target: target,
navigation_indices: navigation_indices,
- pre_fetch: Boolean(pre_fetch)
+ pre_fetch: Boolean(pre_fetch),
+ spinners: Y.all('.spinner')
});
- namespace.linkify_navigation();
navigator.set('backwards_navigation', Y.all('.first,.previous'));
navigator.set('forwards_navigation', Y.all('.last,.next'));
navigator.clickAction('.first', navigator.first_batch);
=== modified file 'lib/lp/bugs/javascript/tests/test_buglisting.js'
--- lib/lp/bugs/javascript/tests/test_buglisting.js 2011-11-18 17:20:22 +0000
+++ lib/lp/bugs/javascript/tests/test_buglisting.js 2011-11-24 20:48:27 +0000
@@ -466,14 +466,18 @@
lp_cache.prev = null;
}
var target = Y.Node.create('<div id="client-listing"></div>');
- return new module.ListingNavigator({
+ var navigator_config = {
current_url: url,
cache: lp_cache,
io_provider: mock_io,
pre_fetch: config.pre_fetch,
target: target,
template: ''
- });
+ };
+ if (Y.Lang.isValue(config.spinners)){
+ navigator_config.spinners = config.spinners;
+ }
+ return new module.ListingNavigator(navigator_config);
};
suite.add(new Y.Test.Case({
@@ -744,6 +748,97 @@
}
}));
+suite.add(new Y.Test.Case({
+ name: "spinners",
+
+ get_navigator: function() {
+ var spinner = Y.Node.create('<img src="/spinner" />');
+ var navigator = get_navigator(
+ '', {spinners: new Y.NodeList([spinner])});
+ Y.Assert.areSame(navigator.get('spinners').item(0), spinner);
+ return navigator;
+ },
+
+ /**
+ * On init, spinners are hidden.
+ */
+ test_spinners_default_hidden: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ Y.Assert.areEqual('hidden', spinner.getStyle('visibility'));
+ },
+
+ /**
+ * load_model shows the spinners.
+ */
+ test_load_model_shows_spinner: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ navigator.load_model({});
+ Y.Assert.areEqual('visible', spinner.getStyle('visibility'));
+ },
+
+ /**
+ * load_model ignores the spinners if fetch_only is set.
+ */
+ test_load_model_fetch_only_ignores_spinner: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ navigator.load_model({}, true);
+ Y.Assert.areEqual('hidden', spinner.getStyle('visibility'));
+ },
+
+ /**
+ * After a successful load, the spinners are hidden.
+ */
+ test_success_clears_spinner: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ var mock_io = navigator.get('io_provider');
+ navigator.load_model({});
+ mock_io.last_request.successJSON({mustache_model: {bugtasks: []}});
+ Y.Assert.areEqual('hidden', spinner.getStyle('visibility'));
+ },
+
+ /**
+ * After a successful load with fetch_only, spinners are left alone.
+ */
+ test_fetch_only_success_ignores_spinner: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ var mock_io = navigator.get('io_provider');
+ navigator.set_pending(true);
+ navigator.load_model({}, true);
+ mock_io.last_request.successJSON({mustache_model: {bugtasks: []}});
+ Y.Assert.areEqual('visible', spinner.getStyle('visibility'));
+ },
+
+ /**
+ * When a load fails, the spinners are hidden
+ */
+ test_failure_clears_spinner: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ var mock_io = navigator.get('io_provider');
+ navigator.load_model({});
+ mock_io.failure();
+ Y.Assert.areEqual('hidden', spinner.getStyle('visibility'));
+ },
+
+ /**
+ * When a fetch-only load fails, the spinners are left alone.
+ */
+ test_fetch_only_failure_ignores_spinner: function(){
+ var navigator = this.get_navigator();
+ var spinner = navigator.get('spinners').item(0);
+ var mock_io = navigator.get('io_provider');
+ navigator.load_model({}, true);
+ navigator.set_pending(true);
+ mock_io.failure();
+ Y.Assert.areEqual('visible', spinner.getStyle('visibility'));
+ }
+}));
+
var handle_complete = function(data) {
window.status = '::::' + JSON.stringify(data);
};