Skip to content

Commit

Permalink
v0.3 added react-intl, more flexible mutation, windowed pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
BerndWessels committed Jan 15, 2016
1 parent 2804590 commit 41862e7
Show file tree
Hide file tree
Showing 24 changed files with 768 additions and 165 deletions.
7 changes: 7 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
}
]
}
],
[
"react-intl",
{
"messagesDir": "./build/i18n/",
"enforceDescriptions": true
}
]
]
},
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Current Version 0.2.6
# Current Version 0.3.0

# Overview

This is a template you can fork and clone to develop data driven React/Relay SPA Websites using a single GraphQL endpoint.

It also shows how to integrate access to databases like mysql or postgres.

And we will keep on adding other important SPA related components and processes for internationalization, whitelabeling and everything else you need to kick-start your unicorn.

# Babel 6

Starting with version 0.2.0 we moved on to Babel 6.
Expand All @@ -25,6 +27,8 @@ Remember that `babel-relay-plugin` will most likely be renamed soon to `babel-pl
* MySQL
* Webpack
* React Bootstrap
* React-Intl V2 Internationalization
* Windowed Pagination Connection
* Heavily commented webpack configuration with reasonable defaults.
* Latest JSX, ES6, and ES7 support with babel 6.
* Source maps included in all builds.
Expand Down Expand Up @@ -84,6 +88,17 @@ You can copy the `dist` content now to your production environment.
If you want to run the production version locally to make sure it works, you can run `npm run build:serve`.
This will serve the `dist` folder on port `8080`. Just make sure you serve the schema as well.

# i18n

We are using [react-intl v2](https://github.com/yahoo/react-intl/issues/162) to provide translations and internationalization.

For each language you want to provide you have to add a `[id].json` in the `public/assets/translations` folder.

To Extract the default messages from your project and merge them into all your language files run `npm run translations:extract`.
Already existing translations within your language files will not be touched. Only new default messages will be added.

You can also modify and extend the `translationsExtract` script with your own tools and workflows. This gives you plenty of ways to deal with your translator.

## Scripts

* `npm start` - start development server, try it by opening `http://localhost:8080/` or with browser sync `http://localhost:3000/`
Expand All @@ -94,6 +109,7 @@ This will serve the `dist` folder on port `8080`. Just make sure you serve the s
* `npm run database:fake` - create/override the database with fake data
* `npm run schema:update` - update the Relay GraphQL schema
* `npm run schema:serve` - serve the Relay GraphQL data endpoint
* `npm run translations:extract` - extracts all default messages and merges them into the translations files.

See what each script does by looking at the `scripts` section in [package.json](./package.json).

Expand Down
4 changes: 4 additions & 0 deletions data/database/dbPerson.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export default function (sequelize, DataTypes) {
validate: {
isEmail: true
}
},
language: {
type: DataTypes.STRING,
allowNull: false
}
},
// Declare the associations.
Expand Down
21 changes: 11 additions & 10 deletions data/graphql/mtUpdatePerson.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ import {
nodeDefinitions,
} from 'graphql-relay';

/**
* Import Database Entities.
*/
import db from '../database/db';

/**
* Import GraphQL Types.
*/
import qlPerson from '../graphql/qlPerson';
import qlPost from '../graphql/qlPost';

/**
* Import Database Entities.
*/
import db from '../database/db';

/**
* Create the GraphQL Mutation.
*/
Expand All @@ -55,28 +55,29 @@ export default mutationWithClientMutationId({
// Fields supplied by the client.
inputFields: {
id: {type: new GraphQLNonNull(GraphQLID)},
email: {type: new GraphQLNonNull(GraphQLString)}
email: {type: GraphQLString},
language: {type: GraphQLString}
},
// Mutated fields returned from the server.
outputFields: {
person: {
type: qlPerson,
// Parameters are payload from mutateAndGetPayload followed by outputFields.
resolve: (dbPerson, id, email) => {
resolve: (dbPerson, id, email, language) => {
return dbPerson;
}
}
},
// Take the input fields, process the mutation and return the output fields.
mutateAndGetPayload: ({id, email}, {rootValue}) => {
mutateAndGetPayload: (inputFields, {rootValue}) => {
// TODO: Process Authentication {"session":{"userId":1}}
console.log(JSON.stringify(rootValue));
// Convert the client id back to a database id.
var localPersonId = fromGlobalId(id).id;
var localPersonId = fromGlobalId(inputFields.id).id;
// Find the person with the given id in the database.
return db.person.findOne({where: {id: localPersonId}}).then((dbPerson)=> {
// Mutate the person.
dbPerson.email = email;
Object.assign(dbPerson, inputFields, {id: localPersonId});
// Save it back to the database.
return dbPerson.save().then(()=> {
// Return the mutated person as an output field.
Expand Down
171 changes: 163 additions & 8 deletions data/graphql/ql.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@
* LICENSE.txt file in the root directory of this source tree.
*/

/**
* Import Babel helpers.
*/
import _extends from 'babel-runtime/helpers/extends';

/**
* Import GraphQL types.
*/
import {
GraphQLBoolean,
GraphQLFloat,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';

/**
* Import Relay helpers.
*/
Expand All @@ -15,11 +35,6 @@ import {
nodeDefinitions,
} from 'graphql-relay';

/**
* Every type should register itself.
*/
var registeredTypes = [];

/**
* We get the node interface and field from the Relay library.
*/
Expand All @@ -39,11 +54,151 @@ var defs = nodeDefinitions(
}
);

export const nodeInterface = defs.nodeInterface;
export const nodeField = defs.nodeField;

/**
* Exports.
* Every type should register itself.
*/
var registeredTypes = [];

export function registerType(type) {
registeredTypes.push(type);
}
export const nodeInterface = defs.nodeInterface;
export const nodeField = defs.nodeField;

/**
* The common pagination info type used by all connections.
*/
export const paginationInfoType = new GraphQLObjectType({
name: 'paginationInfo',
description: 'Information about pagination in a connection.',
fields: function fields() {
return {
hasNextPage: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'When paginating forwards, are there more items?'
},
hasPreviousPage: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'When paginating backwards, are there more items?'
},
startCursor: {
type: GraphQLInt,
description: 'When paginating backwards, the cursor to continue.'
},
endCursor: {
type: GraphQLInt,
description: 'When paginating forwards, the cursor to continue.'
}
};
}
});

/**
* Returns a GraphQLObjectType for a windowed pagination with the given name,
* and whose nodes are of the specified type.
*/

export const paginationDefinitions = function (config) {
var nodeType = config.nodeType;

var name = config.name != null ? config.name : nodeType.name;
var edgeFields = config.edgeFields || {};
var connectionFields = config.connectionFields || {};
var resolveNode = config.resolveNode;
var resolveCursor = config.resolveCursor;
var edgeType = new GraphQLObjectType({
name: name + 'Edge',
description: 'An edge in a connection.',
fields: function fields() {
return _extends({
node: {
type: nodeType,
resolve: resolveNode,
description: 'The item at the end of the edge'
},
cursor: {
type: new GraphQLNonNull(GraphQLInt),
resolve: resolveCursor,
description: 'A cursor for use in pagination'
}
}, resolveMaybeThunk(edgeFields));
}
});

var connectionType = new GraphQLObjectType({
name: name + 'Connection',
description: 'A connection to a list of items.',
fields: function fields() {
return _extends({
pageInfo: {
type: new GraphQLNonNull(paginationInfoType),
description: 'Information to aid in pagination.'
},
edges: {
type: new GraphQLList(edgeType),
description: 'Information to aid in pagination.'
}
}, resolveMaybeThunk(connectionFields));
}
});

return { edgeType: edgeType, connectionType: connectionType };
};

/**
* Pagination connection input arguments.
*/
export const paginationArgs = {
offset: {
type: GraphQLInt
},
limit: {
type: GraphQLInt
}
};

/**
* Given a slice (subset) of an array, returns a connection object for use in
* GraphQL.
*
* This function is similar to `connectionFromArray`, but is intended for use
* cases where you know the cardinality of the connection, consider it too large
* to materialize the entire array, and instead wish pass in a slice of the
* total result large enough to cover the range specified in `args`.
*/
export const paginationFromArraySlice = function (arraySlice, offset, limit, sliceStart, arrayLength) {
// TODO: offset < sliceStart will crash!

var sliceEnd = sliceStart + arraySlice.length;

// If supplied slice is too large, trim it down before mapping over it.
var slice = arraySlice.slice(Math.max(offset - sliceStart, 0), Math.min(limit, sliceEnd - offset));

var edges = slice.map(function (value, index) {
return {
cursor: offset + index,
node: value
};
});

var firstEdge = edges[0];
var lastEdge = edges[edges.length - 1];

return {
edges: edges,
pageInfo: {
startCursor: firstEdge ? firstEdge.cursor : null,
endCursor: lastEdge ? lastEdge.cursor : null,
hasPreviousPage: offset > limit,
hasNextPage: offset < arrayLength - limit
}
};
};

/**
* Helper functions.
*/
function resolveMaybeThunk(thingOrThunk) {
return typeof thingOrThunk === 'function' ? thingOrThunk() : thingOrThunk;
}
14 changes: 11 additions & 3 deletions data/graphql/qlComment.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,24 @@ import {
} from 'graphql-relay';

/**
* Import Database Entities.
* Import GraphQL helpers.
*/
import db from '../database/db';
import {
nodeInterface,
nodeField,
registerType
} from './ql';

/**
* Import GraphQL Types.
*/
import {nodeInterface, nodeField, registerType} from './ql';
import qlPost from './qlPost';

/**
* Import Database Entities.
*/
import db from '../database/db';

/**
* Create the GraphQL Type.
*/
Expand Down
Loading

0 comments on commit 41862e7

Please sign in to comment.