Skip to content

Commit

Permalink
working on docs
Browse files Browse the repository at this point in the history
  • Loading branch information
SachaG committed Dec 15, 2016
1 parent 4a12dac commit 3f7f173
Show file tree
Hide file tree
Showing 19 changed files with 1,238 additions and 388 deletions.
6 changes: 5 additions & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ versions:
sidebar_categories:
null:
- index
Overview:
- features
- architecture
API:
- theming
- custom-fields
- schema
- data-layer
- forms
- callbacks
- email
- theming
- groups-permissions
- routing
- internationalization
- settings
Tutorials:
- tutorial-framework
- tutorial-customizing
Expand Down
111 changes: 105 additions & 6 deletions source/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,113 @@
title: Architecture
---

Nova's application structure is a bit different than most other Meteor apps. Generally speaking, we can distinguish between three ways of organizing code in a Meteor app: default, module-based, and package-based (which is what Nova uses).
## Nova's Building Blocks

The **default** app structure is what legacy Meteor apps such as [Microscope](https://github.com/DiscoverMeteor/Microscope) use. Files are stored in `/client`, `/server`, `/lib`, etc. directories and imported automatically by Meteor. This approach requires the least work, but also gives you less control over load order.
Nova is built on top of three main open-source technologies: React, GraphQL, and Meteor.

Starting with Meteor 1.3, the **module-based** approach is the pattern officially recommended by the [Meteor Guide](http://guide.meteor.com/structure.html). In it, all your files are stored in an `/imports` directory, with two `/client/main.js` and `/server/main.js` entry points that then import all other files. The main difference with the previous pattern is that files in `/imports` no longer run automatically.
- [React](https://facebook.github.io/react/) is Nova's front-end library.
- [GraphQL](http://graphql.org) is Nova's API layer, used to load data from the server with [Apollo](http://apollostack.com).
- [Meteor](http://meteor.com) is Nova's back-end layer, used to handle the database as well as server and bundle the app.

Finally, with the **package-based** technique, all your code is stored in [Meteor packages](http://guide.meteor.com/using-packages.html). Packages can be loaded from Meteor's package server, or stored locally in your `/packages` directory. Note that it is recommended you use modules within your packages.
Each layer is independent from the other two: you can take your React components and use them with a completely different stack, and – although we're not quite there yet – ultimately we also hope to make it easy to migrate out of Meteor.

When customizing Nova, you can use any of these three approaches for your own custom code. But if you can, I would recommend sticking with Nova's **package-based** approach just to maintain consistency between Nova's codebase and yours.
The goal is to avoid getting stuck: if at any point you outgrow Nova, it should be possible to leave it with minimal refactoring.

Also, using packages for customization means you have an easy way to turn off any customization you've added if you need to track down the source of a problem.
## A Note About Apollo

[Apollo](http://apollostack.com) is the set of tools used to manage the GraphQL layer. It consists of two parts, Apollo server and Apollo client.

- **Apollo server** is used to create the GraphQL API endpoint on the server.
- **Apollo client** (together with the `react-apollo` package) is used to query that endpoint and load data on the client.

Apollo is still young, but it's well-documented, evolving quickly, and supported by a great team.

## Code Architecture

In keeping with this idea of flexibility and modularity, Nova's application structure is a bit different than most other Meteor apps.

In Nova, each feature has its own distinct **Meteor package** which can be freely added or removed. For example, if your current theme doesn't use comments, you can get rid of every comments-related back-end feature with:

```
meteor remove nova:comments
```

Generally speaking, Nova packages fall in one of five categories, and they're all listed in your `.meteor/packages` file.

### Core Packages

These are the core packages that make the [Nova framework](tutorial-framework.html):

- `nova:lib`
- `nova:core`
- `nova:forms`
- `nova:apollo`
- `nova:routing`
- `nova:email`
- `nova:users`

Every Nova app will usually include them, but note that strictly speaking only `nova:core` (which itself depends on `nova:lib`) cannot be removed from Nova apps.

### Feature Packages

These includes all the main Nova features:

- `nova:events`
- `nova:settings`
- `nova:posts`
- `nova:newsletter`
- `nova:search`
- `nova:notifications`
- `nova:getting-started`
- `nova:categories`
- `nova:voting`
- `nova:embedly`
- `nova:api`
- `nova:rss`
- `nova:subscribe`

These can all be added and removed, but note that they might have dependencies on each other (for example `nova:comments` depends on `nova:posts`).

Any extra plug-in you install would also fall in this category.

### Theme Packages

These packages are the one that control what your app actually looks and feels like, and they're the ones you'll usually want to replace (or more likely, extend).

- `nova:base-components`
- `nova:base-styles`
- `nova:email-templates`
- `nova:i18n-en-us`

Theme packages can include React components, CSS styles, email templates, or just language strings.

### Meteor Packages

Sometimes you'll also want to include either official or third-party Meteor packages to add extra features to your app. While this is usually done from within a package's `package.js` file, you can also add packages directly in your `packages` file in some instances, such as for controlling user accounts:

- `accounts-password`
- `accounts-twitter`

### Custom Packages

Finally, any new package you create to extend Nova will fall in the “custom package” category.

## Extend, Don't Edit

Another key point of Nova's philosophy is to never edit the core codebase (i.e. what you get from the Nova repo) directly if you can avoid it.

Editing Nova's code makes it harder to keep it up to date when things change in the main Nova repo, as your modifications might conflict with a new updated version.

Instead, Nova includes many hooks and patterns that enable you to tweak and extend core features from your *own* packages without having to actually modify their code.

## The Nova Core Package

Unless mentioned otherwise, all Nova utilities function are imported from the `nova:core` Meteor package:

```js
import { createCollection } from 'meteor/nova:core';

const Posts = createCollection({...});
```

Note that a lot of these utilities actually live in the `nova:lib` package, but they're re-exported from `nova:core` for convenience's sake.
8 changes: 4 additions & 4 deletions source/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ function setEditedAt (post, user) {
post.editedAt = new Date();
return post;
}
Telescope.callbacks.add("posts.edit.sync", setEditedAt);
Callbacks.add("posts.edit.sync", setEditedAt);
```

If the callback function is named (i.e. declared using the `function foo () {}` syntax), you can also remove it from the callback using:

```js
Telescope.callbacks.remove("posts.edit.sync", "setEditedAt");
Callbacks.remove("posts.edit.sync", "setEditedAt");
```

Methods support three distinct types of callbacks, each with their own hook:
Expand All @@ -30,10 +30,10 @@ Methods support three distinct types of callbacks, each with their own hook:

<h2 id="running-callbacks">Running Callbacks</h2>

Callbacks are run using the `Telescope.callbacks.runSync` and `Telescope.callbacks.runAsync` functions:
Callbacks are run using the `Callbacks.runSync` and `Callbacks.runAsync` functions:

```js
modifier = Telescope.callbacks.run(`movies.edit.sync`, modifier, document, currentUser)
modifier = Callbacks.run(`movies.edit.sync`, modifier, document, currentUser)
```

In each case, the **first** argument is the name of the callback hook, the **second** argument is the one iterated on by each callback function on the hook, while any remaining arguments are just passed along from one iteration to the next.
Expand Down
29 changes: 29 additions & 0 deletions source/custom-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Custom Fields
---

Out of the box, Nova has three main collections: `Posts`, `Users`, and `Comments`. Each of them has a pre-set schema, but that schema can also be extended with custom fields.

For example, this is how the `nova:newsletter` package extends the `Posts` schema with a `scheduledAt` property that keeps track of when a post was sent out as part of an email newsletter:

```js
Posts.addField({
fieldName: 'scheduledAt',
fieldSchema: {
type: Date,
optional: true
}
});
```

The `collection.addField()` function takes either a field object, or an array of fields. Each field has a `fieldName` property, and a `fieldSchema` property.

Each field schema supports all of the [SimpleSchema properties](https://github.com/aldeed/meteor-simple-schema#schema-rules), such as `type`, `optional`, etc.

A few special properties (`insertableIf`, `editableIf`, `control`, and `order`) are also supported by the [Forms](forms.html) package.

You can also remove a field by calling `collection.removeField(fieldName)`. For example:

```js
Posts.removeField('scheduledAt');
```
29 changes: 12 additions & 17 deletions source/data-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ The server then looks for a **mutation resolver** and executes it.

## Helper Functions

Nova features a number of helpers to make settings up that data layer fast, most of which are initialized through the `Telescope.createCollection` function:
Nova features a number of helpers to make settings up that data layer fast, most of which are initialized through the `createCollection` function:

```js
const Movies = Telescope.createCollection({
const Movies = createCollection({

collectionName: 'movies',

Expand All @@ -50,13 +50,13 @@ The function takes the following arguments:

The first piece of any GraphQL API is the **schema**, which defines what data is made available to the client.

In Nova, this GraphQL schema can be generated automatically from your collection's JSON schema, so you don't have to type things twice. Just pass a [SimpleSchema](https://github.com/aldeed/meteor-simple-schema)-compatible JSON schema as `Telescope.createCollection`'s `schema` property.
In Nova, this GraphQL schema can be generated automatically from your collection's JSON schema, so you don't have to type things twice. Just pass a [SimpleSchema](https://github.com/aldeed/meteor-simple-schema)-compatible JSON schema as `createCollection`'s `schema` property.

You can learn more on defining your schema in the [Schema](schema.html) section.

### Custom Schemas

If you need to manually add a schema, you can also do so using the `Telescope.graphQL.addSchema` function:
If you need to manually add a schema, you can also do so using the `GraphQLSchema.addSchema` function:

```js
const customSchema = `
Expand All @@ -66,7 +66,7 @@ const customSchema = `
title: String
}
`;
Telescope.graphQL.addSchema(customSchema);
GraphQLSchema.addSchema(customSchema);
```

<h2 id="resolvers">Resolvers</h2>
Expand Down Expand Up @@ -98,7 +98,7 @@ The `total` resolver takes a `terms` argument and should return the total count

### Custom Resolvers

Just like for the schema, you can also define resolvers manually using `Telescope.graphQL.addResolvers`:
Just like for the schema, you can also define resolvers manually using `GraphQLSchema.addResolvers`:

```js
const movieResolver = {
Expand All @@ -108,7 +108,7 @@ const movieResolver = {
},
},
};
Telescope.graphQL.addResolvers(movieResolver);
GraphQLSchema.addResolvers(movieResolver);
```

Resolvers can be defined on any new or existing type (e.g. `Movie`).
Expand Down Expand Up @@ -139,11 +139,11 @@ Takes a single `documentId` argument.

### Custom Mutations

You can also add your own mutations using `Telescope.graphQL.addMutation` and `Telescope.graphQL.addResolvers`:
You can also add your own mutations using `GraphQLSchema.addMutation` and `GraphQLSchema.addResolvers`:

```js

Telescope.graphQL.addMutation('postsVote(documentId: String, voteType: String) : Post');
GraphQLSchema.addMutation('postsVote(documentId: String, voteType: String) : Post');

const voteResolver = {
Mutation: {
Expand All @@ -153,7 +153,7 @@ const voteResolver = {
},
};

Telescope.graphQL.addResolvers(voteResolver);
GraphQLSchema.addResolvers(voteResolver);
```

### Boilerplate Operations
Expand Down Expand Up @@ -205,15 +205,13 @@ The `withList` HoC is used to display lists of documents. It takes the following
- `queryName`: an arbitrary name for the query.
- `collection`: the collection on which to look for the `list` resolver.
- `fragment`: the fragment to use (see below).
- `fragmentName`: the name of the fragment.

For example:

```js
const listOptions = {
collection: Movies,
queryName: 'moviesListQuery',
fragmentName: MoviesItem.fragmentName,
fragment: MoviesItem.fragment,
};

Expand All @@ -235,9 +233,9 @@ The HoC then passes on the following props:
- `refetch`: a function that can be called to trigger a query refetch.
- `loadMore`: a function that can be called to load more data.

### withSingle
### withDocument

The `withSingle` HoC displays a single document. It takes the same options as `withList`, but takes a `documentId` **prop**.
The `withDocument` HoC displays a single document. It takes the same options as `withList`, but takes a `documentId` **prop**.

Like `terms` for `withList`, `documentId` needs to be passed not as an option, but as a prop from the *parent* component.

Expand All @@ -253,7 +251,6 @@ This HoC takes the following four options:
- `collection`: the collection to operate on.
- `queryName`: the name of the query to update on the client.
- `fragment`: specifies the data to ask for as a return value.
- `fragmentName`: the name of the fragment.

And passes on a `newMutation` function to the wrapped component, which takes a single `document` argument.

Expand All @@ -280,7 +277,6 @@ class MoviesItem extends Component {
//...
};

MoviesItem.fragmentName = 'moviesItemFragment';
MoviesItem.fragment = gql`
fragment moviesItemFragment on Movie {
_id
Expand All @@ -301,7 +297,6 @@ And reuse it in another:
const listOptions = {
collection: Movies,
queryName: 'moviesListQuery',
fragmentName: MoviesItem.fragmentName,
fragment: MoviesItem.fragment,
};

Expand Down
5 changes: 1 addition & 4 deletions source/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ This prevents you from having to specify your schema twice in two different form

Nova will also use your schema to generate client-side forms and handle their submission via the appropriate Apollo mutation.

![http://g.recordit.co/FcZuDIo8Rw.gif](http://g.recordit.co/FcZuDIo8Rw.gif)

For example, here's how you would display a form to edit a single movie:

```js
Expand All @@ -33,15 +31,14 @@ Note that NovaForm will also take care of loading the document to edit, if it's

<h2 id="data-loading">Easy Data Loading</h2>

Nova features a set of data loading helpers to make loading Apollo data easier, `withList` (to load a list of documents) and `withSingle` (to load a single document).
Nova features a set of data loading helpers to make loading Apollo data easier, `withList` (to load a list of documents) and `withDocument` (to load a single document).

For example, here's how you would use `withList` to pass a `results` prop containing all movies to your `MoviesList` component:

```js
const listOptions = {
collection: Movies,
queryName: 'moviesListQuery',
fragmentName: 'myFragment',
fragment: fragment,
};

Expand Down
Loading

0 comments on commit 3f7f173

Please sign in to comment.