Skip to content

Commit

Permalink
added paging and connection fields and args extension example.
Browse files Browse the repository at this point in the history
  • Loading branch information
BerndWessels committed Dec 11, 2015
1 parent a05527e commit 6396aba
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 55 deletions.
73 changes: 55 additions & 18 deletions data/graphql/qlPerson.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
connectionArgs,
connectionDefinitions,
connectionFromArray,
connectionFromArraySlice,
cursorToOffset,
fromGlobalId,
globalIdField,
mutationWithClientMutationId,
Expand All @@ -49,7 +51,20 @@ import qlPost from './qlPost';
/**
* Create the associations.
*/
var {connectionType: postsConnection} = connectionDefinitions({name: 'post', nodeType: qlPost});
var {connectionType: postsConnection} = connectionDefinitions({
name: 'post',
nodeType: qlPost,
connectionFields: () => ({
totalCount: {
type: GraphQLInt,
resolve: (connection) => connection.totalCount,
description: `A count of the total number of objects in this connection, ignoring pagination.
This allows a client to fetch the first five objects by passing "5" as the
argument to "first", then fetch the total count so it could display "5 of 83",
for example.`
}
})
});

/**
* Create the GraphQL Type.
Expand All @@ -64,38 +79,60 @@ var qlPerson = new GraphQLObjectType({
fields: () => ({
id: globalIdField(typeName),
firstName: {
type: GraphQLString,
resolve(dbPerson) {
return dbPerson.firstName;
}
type: GraphQLString
/**
* No need to provide a resolve function for simple properties
* which map directly from the source object to the target.
*
* resolve(dbPerson) {
* return dbPerson.firstName;
* }
*/
},
lastName: {
type: GraphQLString,
resolve(dbPerson) {
return dbPerson.lastName;
}
type: GraphQLString
},
email: {
type: GraphQLString,
resolve(dbPerson) {
return dbPerson.email;
}
type: GraphQLString
},
posts: {
// This serves the person to post connection with support for paging.
type: postsConnection,
args: connectionArgs,
// We can extend the connection args with our own if we want to.
args: {...{}, ...connectionArgs},
// Resolve the requested page of posts.
resolve(dbPerson, args) {
return dbPerson.getPosts().then(function (data) {
return connectionFromArray(data, args);
});
// Calculate the database offset to the requested page.
var offset = args.after ? cursorToOffset(args.after) + 1 : args.before ? Math.max(cursorToOffset(args.before) - args.last, 0) : 0;
// Query the database.
return db.post.findAndCountAll({
where: {personId: dbPerson.id},
offset: offset,
limit: args.first ? args.first : args.last ? args.last : undefined
})
.then(function (result) {
// Combine the returned connection result with the extra totalCount property.
return {
...connectionFromArraySlice(result.rows, args, {
sliceStart: offset,
arrayLength: result.count
}),
totalCount: result.count
}
});
}
}
}),
interfaces: [nodeInterface]
});

// Type registration.
registerType({name: typeName, getByID: (id)=>db.person.findOne({where: {id: id}}), dbType: db.person.Instance, qlType: qlPerson});
registerType({
name: typeName,
getByID: (id)=>db.person.findOne({where: {id: id}}),
dbType: db.person.Instance,
qlType: qlPerson
});

// Type export.
export default qlPerson;
7 changes: 6 additions & 1 deletion data/graphql/qlPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ var qlPost = new GraphQLObjectType({
});

// Type registration.
registerType({name: typeName, getByID: (id)=>db.post.findOne({where: {id: id}}), dbType: db.post.Instance, qlType: qlPost});
registerType({
name: typeName,
getByID: (id)=>db.post.findOne({where: {id: id}}),
dbType: db.post.Instance,
qlType: qlPost
});

// Type export.
export default qlPost;
1 change: 1 addition & 0 deletions data/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Post implements Node {
type postConnection {
pageInfo: PageInfo!
edges: [postEdge]
totalCount: Int
}

type postEdge {
Expand Down
12 changes: 12 additions & 0 deletions data/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,18 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalCount",
"description": "A count of the total number of objects in this connection, ignoring pagination.\nThis allows a client to fetch the first five objects by passing \"5\" as the\nargument to \"first\", then fetch the total count so it could display \"5 of 83\",\nfor example.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-webpack",
"version": "0.1.1",
"version": "0.1.2",
"description": "React Relay SQL Webpack SPA Example.",
"main": "index.js",
"scripts": {
Expand Down
42 changes: 19 additions & 23 deletions scripts/databaseFake.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ function createPersonAndPosts() {
lastName: Faker.name.lastName(),
email: Faker.internet.email()
})
.then(person => {
return person.createPost({
title: `Sample 1 by ${person.firstName}`,
content: `Content 1 for ${person.lastName}`
});
});
.then(person => createPost(person, 0));
}

function createPost(person, index) {
return person.createPost({
title: `Sample ${index} by ${person.firstName}`,
content: `Content ${index} for ${person.lastName}`
});
}


export default task('create/override fake database', async () => {
var viewer;
await db.sequelize.sync({force: true})
Expand All @@ -50,26 +53,19 @@ export default task('create/override fake database', async () => {
.then(createPersonAndPosts)
.then(createPersonAndPosts)
.then(createPersonAndPosts)
.then(()=>{
.then(()=> {
return db.person.findOne({where: {id: 2}});
})
.then(person => {
viewer = person;
return viewer.createPost({
title: `Sample 2 by ${viewer.firstName}`,
content: `Content 2 for ${viewer.lastName}`
});
})
.then(_ => {
return viewer.createPost({
title: `Sample 3 by ${viewer.firstName}`,
content: `Content 3 for ${viewer.lastName}`
});
return createPost(viewer, 1)
})
.then(_ => {
return viewer.createPost({
title: `Sample 4 by ${viewer.firstName}`,
content: `Content 4 for ${viewer.lastName}`
});
});
.then(_ => createPost(viewer, 2))
.then(_ => createPost(viewer, 3))
.then(_ => createPost(viewer, 4))
.then(_ => createPost(viewer, 5))
.then(_ => createPost(viewer, 6))
.then(_ => createPost(viewer, 7))
.then(_ => createPost(viewer, 8))
.then(_ => createPost(viewer, 9))
});
70 changes: 58 additions & 12 deletions src/app/home/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,43 @@ class Home extends React.Component {

// Handle the button click event.
_handleUpdatePerson = () => {
Relay.Store.update(new UpdatePersonMutation({person: this.props.viewer, email: this.refs.email.value}));
Relay.Store.update(new UpdatePersonMutation({person: this.props.viewer, email: this.refs.email.getValue()}));
};
// Handle the button click event.
_handleMorePosts = () => {
_handleGetPrevPage = () => {
// update relay query parameters
this.props.relay.setVariables({
forward: false,
first: null,
after: null,
last: this.refs.pageSize.getValue(),
before: this.props.viewer.posts.pageInfo.startCursor
});
};
// Handle the button click event.
_handleGetNextPage = () => {
// read current params
var count = this.props.relay.variables.numPosts;
// var count = this.props.relay.variables.postsPerPage;
// update params
this.props.relay.setVariables({
numPosts: count + 1
forward: true,
first: this.refs.pageSize.getValue(),
after: this.props.viewer.posts.pageInfo.endCursor,
last: null,
before: null
});
};
// Render the component.
render() {
return (
<div>
<h1>Home {this.props.viewer.email}</h1>
<input ref="email" type="text" defaultValue={this.props.viewer.email}/>
<h1>Home {this.props.viewer.firstName} - {this.props.viewer.email}</h1>
<Input ref="email" type="text" label="Email" defaultValue={this.props.viewer.email}/>
<Button onClick={this._handleUpdatePerson}>Update the email!</Button>
<Button onClick={this._handleMorePosts}>count++</Button>
<Input ref="pageSize" type="text" label="Posts per page"
defaultValue={this.props.relay.variables.first}/>
<Button onClick={this._handleGetPrevPage}>Get Prev Page</Button>
<Button onClick={this._handleGetNextPage}>Get Next Page</Button>
<ul>
{this.props.viewer.posts.edges.map(edge =>
<li key={edge.node.id}>{edge.node.title} (ID: {edge.node.id})</li>
Expand All @@ -69,20 +87,48 @@ class Home extends React.Component {
*/
export default Relay.createContainer(Home, {
initialVariables: {
numPosts: 1
forward: true,
first: 2,
after: null,
last: null,
before: null
},
fragments: {
viewer: () => Relay.QL`
fragment on Person {
email,
posts(first: $numPosts) {
firstName
email
posts(first: $first, after: $after) @include(if: $forward) {
edges {
cursor
node {
id
title
}
}
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
},
posts(last: $last, before: $before) @skip(if: $forward) {
edges {
cursor,
cursor
node {
id,
id
title
}
}
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
},
${UpdatePersonMutation.getFragment('person')}
}
Expand Down

0 comments on commit 6396aba

Please sign in to comment.