mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
Pagination for Users Management screen
closes #3222 - implementing server-side pagination for /users API - passing /users?limit=none will return all users - passing /users?status=invited will filter base on user status - creating 3 mixins (route, controller and view) to keep pagination logic DRY - updating route, controller and view for Posts to use new mixing - implementing infinite scrolling for Users Management screen (using new mixins) - Users Management screen displays all invited users, but paginates active users
This commit is contained in:
parent
95d3925c3e
commit
fb170d1725
@ -1,6 +1,6 @@
|
||||
import { getRequestErrorMessage } from 'ghost/utils/ajax';
|
||||
import PaginationControllerMixin from 'ghost/mixins/pagination-controller';
|
||||
|
||||
var PostsController = Ember.ArrayController.extend({
|
||||
var PostsController = Ember.ArrayController.extend(PaginationControllerMixin, {
|
||||
// this will cause the list to re-sort when any of these properties change on any of the models
|
||||
sortProperties: ['status', 'published_at', 'updated_at'],
|
||||
|
||||
@ -66,64 +66,11 @@ var PostsController = Ember.ArrayController.extend({
|
||||
return statusResult;
|
||||
},
|
||||
|
||||
// set from PostsRoute
|
||||
paginationSettings: null,
|
||||
|
||||
// holds the next page to load during infinite scroll
|
||||
nextPage: null,
|
||||
|
||||
// indicates whether we're currently loading the next page
|
||||
isLoading: null,
|
||||
|
||||
init: function () {
|
||||
this._super();
|
||||
//let the PaginationControllerMixin know what type of model we will be paginating
|
||||
//this is necesariy because we do not have access to the model inside the Controller::init method
|
||||
this._super({'modelType': 'post'});
|
||||
|
||||
var metadata = this.store.metadataFor('post');
|
||||
this.set('nextPage', metadata.pagination.next);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an ajax response, concatenates any error messages, then generates an error notification.
|
||||
* @param {jqXHR} response The jQuery ajax reponse object.
|
||||
* @return
|
||||
*/
|
||||
reportLoadError: function (response) {
|
||||
var message = 'A problem was encountered while loading more posts';
|
||||
|
||||
if (response) {
|
||||
// Get message from response
|
||||
message += ': ' + getRequestErrorMessage(response, true);
|
||||
} else {
|
||||
message += '.';
|
||||
}
|
||||
|
||||
this.notifications.showError(message);
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Loads the next paginated page of posts into the ember-data store. Will cause the posts list UI to update.
|
||||
* @return
|
||||
*/
|
||||
loadNextPage: function () {
|
||||
var self = this,
|
||||
store = this.get('store'),
|
||||
nextPage = this.get('nextPage'),
|
||||
paginationSettings = this.get('paginationSettings');
|
||||
|
||||
if (nextPage) {
|
||||
this.set('isLoading', true);
|
||||
this.set('paginationSettings.page', nextPage);
|
||||
store.find('post', paginationSettings).then(function () {
|
||||
var metadata = store.metadataFor('post');
|
||||
|
||||
self.set('nextPage', metadata.pagination.next);
|
||||
self.set('isLoading', false);
|
||||
}, function (response) {
|
||||
self.reportLoadError(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,12 @@
|
||||
var UsersIndexController = Ember.ArrayController.extend({
|
||||
import PaginationControllerMixin from 'ghost/mixins/pagination-controller';
|
||||
|
||||
var UsersIndexController = Ember.ArrayController.extend(PaginationControllerMixin, {
|
||||
init: function () {
|
||||
//let the PaginationControllerMixin know what type of model we will be paginating
|
||||
//this is necesariy because we do not have access to the model inside the Controller::init method
|
||||
this._super({'modelType': 'user'});
|
||||
},
|
||||
|
||||
users: Ember.computed.alias('model'),
|
||||
|
||||
activeUsers: Ember.computed.filterBy('users', 'active', true).property('users'),
|
||||
|
76
ghost/admin/mixins/pagination-controller.js
Normal file
76
ghost/admin/mixins/pagination-controller.js
Normal file
@ -0,0 +1,76 @@
|
||||
import { getRequestErrorMessage } from 'ghost/utils/ajax';
|
||||
|
||||
var PaginationControllerMixin = Ember.Mixin.create({
|
||||
|
||||
// set from PaginationRouteMixin
|
||||
paginationSettings: null,
|
||||
|
||||
// holds the next page to load during infinite scroll
|
||||
nextPage: null,
|
||||
|
||||
// indicates whether we're currently loading the next page
|
||||
isLoading: null,
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options: {
|
||||
* modelType: <String> name of the model that will be paginated
|
||||
* }
|
||||
*/
|
||||
init: function (options) {
|
||||
this._super();
|
||||
|
||||
var metadata = this.store.metadataFor(options.modelType);
|
||||
this.set('nextPage', metadata.pagination.next);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Takes an ajax response, concatenates any error messages, then generates an error notification.
|
||||
* @param {jqXHR} response The jQuery ajax reponse object.
|
||||
* @return
|
||||
*/
|
||||
reportLoadError: function (response) {
|
||||
var message = 'A problem was encountered while loading more records';
|
||||
|
||||
if (response) {
|
||||
// Get message from response
|
||||
message += ': ' + getRequestErrorMessage(response, true);
|
||||
} else {
|
||||
message += '.';
|
||||
}
|
||||
|
||||
this.notifications.showError(message);
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Loads the next paginated page of posts into the ember-data store. Will cause the posts list UI to update.
|
||||
* @return
|
||||
*/
|
||||
loadNextPage: function () {
|
||||
|
||||
var self = this,
|
||||
store = this.get('store'),
|
||||
recordType = this.get('model').get('type'),
|
||||
nextPage = this.get('nextPage'),
|
||||
paginationSettings = this.get('paginationSettings');
|
||||
|
||||
if (nextPage) {
|
||||
this.set('isLoading', true);
|
||||
this.set('paginationSettings.page', nextPage);
|
||||
store.find(recordType, paginationSettings).then(function () {
|
||||
var metadata = store.metadataFor(recordType);
|
||||
|
||||
self.set('nextPage', metadata.pagination.next);
|
||||
self.set('isLoading', false);
|
||||
}, function (response) {
|
||||
self.reportLoadError(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default PaginationControllerMixin;
|
23
ghost/admin/mixins/pagination-route.js
Normal file
23
ghost/admin/mixins/pagination-route.js
Normal file
@ -0,0 +1,23 @@
|
||||
var defaultPaginationSettings = {
|
||||
page: 1,
|
||||
limit: 15
|
||||
};
|
||||
|
||||
var PaginationRoute = Ember.Mixin.create({
|
||||
|
||||
/**
|
||||
* Sets up pagination details
|
||||
* @param {settings}: object that specifies additional pagination details
|
||||
*/
|
||||
setupPagination: function (settings) {
|
||||
|
||||
settings = settings || {};
|
||||
settings = _.defaults(settings, defaultPaginationSettings);
|
||||
|
||||
this.set('paginationSettings', settings);
|
||||
this.controller.set('paginationSettings', settings);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default PaginationRoute;
|
39
ghost/admin/mixins/pagination-view-infinite-scroll.js
Normal file
39
ghost/admin/mixins/pagination-view-infinite-scroll.js
Normal file
@ -0,0 +1,39 @@
|
||||
var PaginationViewInfiniteScrollMixin = Ember.Mixin.create({
|
||||
|
||||
/**
|
||||
* Determines if we are past a scroll point where we need to fetch the next page
|
||||
* @param event The scroll event
|
||||
*/
|
||||
checkScroll: function (event) {
|
||||
var element = event.target,
|
||||
triggerPoint = 100,
|
||||
controller = this.get('controller'),
|
||||
isLoading = controller.get('isLoading');
|
||||
|
||||
// If we haven't passed our threshold or we are already fetching content, exit
|
||||
if (isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller.send('loadNextPage');
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind to the scroll event once the element is in the DOM
|
||||
*/
|
||||
didInsertElement: function () {
|
||||
var el = this.$();
|
||||
|
||||
el.on('scroll', Ember.run.bind(this, this.checkScroll));
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbind from the scroll event when the element is no longer in the DOM
|
||||
*/
|
||||
willDestroyElement: function () {
|
||||
var el = this.$();
|
||||
el.off('scroll');
|
||||
}
|
||||
});
|
||||
|
||||
export default PaginationViewInfiniteScrollMixin;
|
@ -1,6 +1,7 @@
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
|
||||
import loadingIndicator from 'ghost/mixins/loading-indicator';
|
||||
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
|
||||
|
||||
var paginationSettings = {
|
||||
status: 'all',
|
||||
@ -9,7 +10,7 @@ var paginationSettings = {
|
||||
page: 1
|
||||
};
|
||||
|
||||
var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, ShortcutsRoute, styleBody, loadingIndicator, {
|
||||
var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, ShortcutsRoute, styleBody, loadingIndicator, PaginationRouteMixin, {
|
||||
classNames: ['manage'],
|
||||
|
||||
model: function () {
|
||||
@ -22,7 +23,7 @@ var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, Sh
|
||||
|
||||
setupController: function (controller, model) {
|
||||
this._super(controller, model);
|
||||
controller.set('paginationSettings', paginationSettings);
|
||||
this.setupPagination(paginationSettings);
|
||||
},
|
||||
|
||||
shortcuts: {
|
||||
|
@ -1,7 +1,34 @@
|
||||
var UsersIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, {
|
||||
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
|
||||
|
||||
var activeUsersPaginationSettings = {
|
||||
include: 'roles',
|
||||
page: 1,
|
||||
limit: 20
|
||||
};
|
||||
|
||||
var invitedUsersPaginationSettings = {
|
||||
include: 'roles',
|
||||
limit: 'all',
|
||||
status: 'invited'
|
||||
};
|
||||
|
||||
var UsersIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, PaginationRouteMixin, {
|
||||
setupController: function (controller, model) {
|
||||
this._super(controller, model.active);
|
||||
this.setupPagination(activeUsersPaginationSettings);
|
||||
},
|
||||
|
||||
model: function () {
|
||||
return this.store.find('user');
|
||||
// using `.filter` allows the template to auto-update when new models are pulled in from the server.
|
||||
// we just need to 'return true' to allow all models by default.
|
||||
return Ember.RSVP.hash({
|
||||
inactive: this.store.filter('user', invitedUsersPaginationSettings, function () {
|
||||
return true;
|
||||
}),
|
||||
active: this.store.filter('user', activeUsersPaginationSettings, function () {
|
||||
return true;
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="content settings-users">
|
||||
{{#view "settings/users/users-list-view" tagName="section" class="content settings-users" }}
|
||||
{{#if invitedUsers}}
|
||||
|
||||
<section class="object-list invited-users">
|
||||
@ -62,4 +62,4 @@
|
||||
{{/each}}
|
||||
|
||||
</section>
|
||||
</section>
|
||||
{{/view}}
|
||||
|
@ -1,34 +1,17 @@
|
||||
import setScrollClassName from 'ghost/utils/set-scroll-classname';
|
||||
import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll';
|
||||
|
||||
var PostsListView = Ember.View.extend({
|
||||
|
||||
var PostsListView = Ember.View.extend(PaginationViewMixin, {
|
||||
classNames: ['content-list-content'],
|
||||
|
||||
checkScroll: function (event) {
|
||||
var element = event.target,
|
||||
triggerPoint = 100,
|
||||
controller = this.get('controller'),
|
||||
isLoading = controller.get('isLoading');
|
||||
|
||||
// If we haven't passed our threshold, exit
|
||||
if (isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller.send('loadNextPage');
|
||||
},
|
||||
|
||||
didInsertElement: function () {
|
||||
this._super();
|
||||
var el = this.$();
|
||||
el.on('scroll', Ember.run.bind(this, this.checkScroll));
|
||||
el.on('scroll', Ember.run.bind(el, setScrollClassName, {
|
||||
target: el.closest('.content-list'),
|
||||
offset: 10
|
||||
}));
|
||||
},
|
||||
|
||||
willDestroyElement: function () {
|
||||
var el = this.$();
|
||||
el.off('scroll');
|
||||
}
|
||||
});
|
||||
|
||||
|
8
ghost/admin/views/settings/users/users-list-view.js
Normal file
8
ghost/admin/views/settings/users/users-list-view.js
Normal file
@ -0,0 +1,8 @@
|
||||
//import setScrollClassName from 'ghost/utils/set-scroll-classname';
|
||||
import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll';
|
||||
|
||||
var UsersListView = Ember.View.extend(PaginationViewMixin, {
|
||||
classNames: ['settings-users']
|
||||
});
|
||||
|
||||
export default UsersListView;
|
Loading…
Reference in New Issue
Block a user