mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 21:40:39 +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
|
// 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'],
|
sortProperties: ['status', 'published_at', 'updated_at'],
|
||||||
|
|
||||||
@ -66,64 +66,11 @@ var PostsController = Ember.ArrayController.extend({
|
|||||||
return statusResult;
|
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 () {
|
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'),
|
users: Ember.computed.alias('model'),
|
||||||
|
|
||||||
activeUsers: Ember.computed.filterBy('users', 'active', true).property('users'),
|
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 styleBody from 'ghost/mixins/style-body';
|
||||||
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
|
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
|
||||||
import loadingIndicator from 'ghost/mixins/loading-indicator';
|
import loadingIndicator from 'ghost/mixins/loading-indicator';
|
||||||
|
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
|
||||||
|
|
||||||
var paginationSettings = {
|
var paginationSettings = {
|
||||||
status: 'all',
|
status: 'all',
|
||||||
@ -9,7 +10,7 @@ var paginationSettings = {
|
|||||||
page: 1
|
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'],
|
classNames: ['manage'],
|
||||||
|
|
||||||
model: function () {
|
model: function () {
|
||||||
@ -22,7 +23,7 @@ var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, Sh
|
|||||||
|
|
||||||
setupController: function (controller, model) {
|
setupController: function (controller, model) {
|
||||||
this._super(controller, model);
|
this._super(controller, model);
|
||||||
controller.set('paginationSettings', paginationSettings);
|
this.setupPagination(paginationSettings);
|
||||||
},
|
},
|
||||||
|
|
||||||
shortcuts: {
|
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 () {
|
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>
|
</section>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="content settings-users">
|
{{#view "settings/users/users-list-view" tagName="section" class="content settings-users" }}
|
||||||
{{#if invitedUsers}}
|
{{#if invitedUsers}}
|
||||||
|
|
||||||
<section class="object-list invited-users">
|
<section class="object-list invited-users">
|
||||||
@ -62,4 +62,4 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
{{/view}}
|
||||||
|
@ -1,34 +1,17 @@
|
|||||||
import setScrollClassName from 'ghost/utils/set-scroll-classname';
|
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'],
|
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 () {
|
didInsertElement: function () {
|
||||||
|
this._super();
|
||||||
var el = this.$();
|
var el = this.$();
|
||||||
el.on('scroll', Ember.run.bind(this, this.checkScroll));
|
|
||||||
el.on('scroll', Ember.run.bind(el, setScrollClassName, {
|
el.on('scroll', Ember.run.bind(el, setScrollClassName, {
|
||||||
target: el.closest('.content-list'),
|
target: el.closest('.content-list'),
|
||||||
offset: 10
|
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