Theme Installer: Caching and paginating of API requests.

props matveb.
see #27055.


git-svn-id: https://develop.svn.wordpress.org/trunk@27830 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Andrew Nacin 2014-03-28 21:48:52 +00:00
parent d658762063
commit 363c2b4810

View File

@ -213,6 +213,124 @@ themes.Collection = Backbone.Collection.extend({
collection = _( collection.first( 20 ) ); collection = _( collection.first( 20 ) );
return collection; return collection;
},
// Handles requests for more themes
// and caches results
//
// When we are missing a cache object we fire an apiCall()
// which triggers events of `query:success` or `query:fail`
query: function( request ) {
/**
* @static
* @type Array
*/
var queries = this.queries,
self = this,
query, isPaginated, count;
// Search the query cache for matches.
query = _.find( queries, function( query ) {
return _.isEqual( query.request, request );
});
// If the request matches the stored currentQuery.request
// it means we have a paginated request.
isPaginated = _.has( request, 'page' );
// Reset the internal api page counter for non paginated queries.
if ( ! isPaginated ) {
this.currentQuery.page = 1;
}
// Otherwise, send a new API call and add it to the cache.
if ( ! query ) {
query = this.apiCall( request ).done( function( data ) {
// Update the collection with the queried data.
self.reset( data.themes );
count = data.info.results;
// Trigger a collection refresh event
// and a `query:success` event with a `count` argument.
self.trigger( 'update' );
self.trigger( 'query:success', count );
// Store the results and the query request
queries.push( { themes: data.themes, request: request } );
}).fail( function() {
self.trigger( 'query:fail' );
});
} else {
// If it's a paginated request we need to fetch more themes...
if ( isPaginated ) {
return this.apiCall( request, isPaginated ).done( function( data ) {
// Add the new themes to the current collection
// @todo update counter
self.add( data.themes );
self.trigger( 'query:success' );
}).fail( function() {
self.trigger( 'query:fail' );
});
}
// Only trigger an update event since we already have the themes
// on our cached object
this.reset( query.themes );
this.trigger( 'update' );
}
},
// Local cache array for API queries
queries: [],
// Keep track of current query so we can handle pagination
currentQuery: {
page: 1,
request: {}
},
// Send Ajax POST request to api.wordpress.org/themes
apiCall: function( request, paginated ) {
// Store current query request args
// for later use with the event `theme:end`
this.currentQuery.request = request;
// Ajax request to .org API
return $.ajax({
url: 'https://api.wordpress.org/themes/info/1.1/?action=query_themes',
// We want JSON data
dataType: 'json',
type: 'POST',
crossDomain: true,
// Request data
data: {
action: 'query_themes',
request: _.extend({
per_page: 72,
fields: {
description: true,
tested: true,
requires: true,
rating: true,
downloaded: true,
downloadLink: true,
last_updated: true,
homepage: true,
num_ratings: true
}
}, request)
},
beforeSend: function() {
if ( ! paginated ) {
// Spin it
$( 'body' ).addClass( 'loading-themes' );
}
}
});
} }
}); });
@ -571,6 +689,15 @@ themes.view.Themes = wp.Backbone.View.extend({
self.render( this ); self.render( this );
}); });
// Update theme count to full result set when available.
this.listenTo( self.collection, 'query:success', function( count ) {
if ( _.isNumber( count ) ) {
self.count.text( count );
} else {
self.count.text( self.collection.length );
}
});
this.listenTo( this.parent, 'theme:scroll', function() { this.listenTo( this.parent, 'theme:scroll', function() {
self.renderThemes( self.parent.page ); self.renderThemes( self.parent.page );
}); });
@ -628,7 +755,10 @@ themes.view.Themes = wp.Backbone.View.extend({
// Generate the themes // Generate the themes
// Using page instance // Using page instance
// While checking the collection has items
if ( this.options.collection.size() > 0 ) {
this.renderThemes( this.parent.page ); this.renderThemes( this.parent.page );
}
// Display a live theme count for the collection // Display a live theme count for the collection
this.count.text( this.collection.length ); this.count.text( this.collection.length );
@ -642,7 +772,9 @@ themes.view.Themes = wp.Backbone.View.extend({
self.instance = self.collection.paginate( page ); self.instance = self.collection.paginate( page );
// If we have no more themes bail // If we have no more themes bail
if ( self.instance.length === 0 ) { if ( self.instance.size() === 0 ) {
// Fire a no-more-themes event.
this.parent.trigger( 'theme:end' );
return; return;
} }
@ -967,7 +1099,7 @@ themes.view.InstallerSearch = themes.view.Search.extend({
_.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value ); _.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value );
}, },
doSearch: function( value ) { doSearch: _.debounce( function( value ) {
var request = {}, var request = {},
self = this; self = this;
@ -991,23 +1123,10 @@ themes.view.InstallerSearch = themes.view.Search.extend({
request.tag = [ value.slice( 4 ) ]; request.tag = [ value.slice( 4 ) ];
} }
// Send Ajax POST request to api.wordpress.org/themes // Get the themes by sending Ajax POST request to api.wordpress.org/themes
themes.view.Installer.prototype.apiCall( request ).done( function( data ) { // or searching the local cache
// Update the collection with the queried data this.collection.query( request );
self.collection.reset( data.themes ); }, 300 ),
// Trigger a collection refresh event to render the views
self.collection.trigger( 'update' );
// Un-spin it
$( 'body' ).removeClass( 'loading-themes' );
$( '.theme-browser' ).find( 'div.error' ).remove();
}).fail( function() {
$( '.theme-browser' ).find( 'div.error' ).remove();
$( '.theme-browser' ).append( '<div class="error"><p>' + l10n.error + '</p></div>' );
});
return false;
}
}); });
themes.view.Installer = themes.view.Appearance.extend({ themes.view.Installer = themes.view.Appearance.extend({
@ -1022,62 +1141,33 @@ themes.view.Installer = themes.view.Appearance.extend({
'click [type="checkbox"]': 'addFilter' 'click [type="checkbox"]': 'addFilter'
}, },
// Send Ajax POST request to api.wordpress.org/themes
apiCall: function( request ) {
return $.ajax({
url: 'https://api.wordpress.org/themes/info/1.1/?action=query_themes',
// We want JSON data
dataType: 'json',
type: 'POST',
crossDomain: true,
// Request data
data: {
action: 'query_themes',
request: _.extend({
per_page: 36,
fields: {
description: true,
tested: true,
requires: true,
rating: true,
downloaded: true,
downloadLink: true,
last_updated: true,
homepage: true,
num_ratings: true
}
}, request)
},
beforeSend: function() {
// Spin it
$( 'body' ).addClass( 'loading-themes' );
}
});
},
// Handles all the rendering of the public theme directory // Handles all the rendering of the public theme directory
browse: function( section ) { browse: function( section ) {
var self = this; var self = this;
// @todo Cache the collection after fetching based on the section
this.collection = new themes.Collection(); this.collection = new themes.Collection();
// Create a new collection with the proper theme data // Bump `collection.currentQuery.page` and request more themes if we hit the end of the page.
// for each section this.listenTo( this, 'theme:end', function() {
this.apiCall({ browse: section }).done( function( data ) { self.collection.currentQuery.page++;
// Update the collection with the queried data _.extend( self.collection.currentQuery.request, { page: self.collection.currentQuery.page } );
self.collection.reset( data.themes ); self.collection.query( self.collection.currentQuery.request );
// Trigger a collection refresh event to render the views });
self.collection.trigger( 'update' );
// Un-spin it this.listenTo( this.collection, 'query:success', function() {
$( 'body' ).removeClass( 'loading-themes' ); $( 'body' ).removeClass( 'loading-themes' );
$( '.theme-browser' ).find( 'div.error' ).remove(); $( '.theme-browser' ).find( 'div.error' ).remove();
}); });
this.listenTo( this.collection, 'query:fail', function() {
$( '.theme-browser' ).find( 'div.error' ).remove();
$( '.theme-browser' ).append( '<div class="error"><p>' + l10n.error + '</p></div>' );
});
// Create a new collection with the proper theme data
// for each section
this.collection.query( { browse: section } );
if ( this.view ) { if ( this.view ) {
this.view.remove(); this.view.remove();
} }
@ -1153,27 +1243,12 @@ themes.view.Installer = themes.view.Appearance.extend({
// Construct the filter request // Construct the filter request
// using the default values // using the default values
// @todo Cache the collection after fetching based on the filter
filter = _.union( filter, this.filtersChecked() ); filter = _.union( filter, this.filtersChecked() );
request = { tag: [ filter ] }; request = { tag: [ filter ] };
// Send Ajax POST request to api.wordpress.org/themes // Get the themes by sending Ajax POST request to api.wordpress.org/themes
this.apiCall( request ).done( function( data ) { // or searching the local cache
// Update the collection with the queried data this.collection.query( request );
self.collection.reset( data.themes );
// Trigger a collection refresh event to render the views
self.collection.trigger( 'update' );
// Un-spin it
$( 'body' ).removeClass( 'loading-themes' );
$( '.theme-browser' ).find( 'div.error' ).remove();
}).fail( function() {
$( '.theme-browser' ).find( 'div.error' ).remove();
$( '.theme-browser' ).append( '<div class="error"><p>' + l10n.error + '</p></div>' );
});
return false;
}, },
// Clicking on a checkbox triggers a tag request // Clicking on a checkbox triggers a tag request
@ -1182,20 +1257,9 @@ themes.view.Installer = themes.view.Appearance.extend({
tags = this.filtersChecked(), tags = this.filtersChecked(),
request = { tag: tags }; request = { tag: tags };
// Send Ajax POST request to api.wordpress.org/themes // Get the themes by sending Ajax POST request to api.wordpress.org/themes
this.apiCall( request ).done( function( data ) { // or searching the local cache
// Update the collection with the queried data this.collection.query( request );
self.collection.reset( data.themes );
// Trigger a collection refresh event to render the views
self.collection.trigger( 'update' );
// Un-spin it
$( 'body' ).removeClass( 'loading-themes' );
$( '.theme-browser' ).find( 'div.error' ).remove();
}).fail( function() {
$( '.theme-browser' ).find( 'div.error' ).remove();
$( '.theme-browser' ).append( '<div class="error"><p>' + l10n.error + '</p></div>' );
});
}, },
// Get the checked filters and return an array // Get the checked filters and return an array