├── .travis.yml
├── README.md
├── package.json
├── test.js
├── tutorials.json
└── tutorials
├── admin-template-override.md
├── how-to-contribute.md
├── logging-system.md
├── microscope.md
├── security.md
└── upgrading-to-v1-0.md
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Orion Tutorials
2 | [Orion](https://github.com/orionjs/core) is a CMS built on Meteor. If you haven't already please check out the current [Documentation](http://orionjs.org/docs/introduction) as that will be updated as Orion evolves.
3 |
4 | You can see the tutorials at [orionjs.org/tutorials](http://orionjs.org/tutorials).
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "orion-tutorials",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "test": "mocha"
6 | },
7 | "devDependencies": {
8 | "mocha": "^1.17.1"
9 | }
10 | }
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | var file = fs.readFileSync('./tutorials.json', 'utf8');
4 |
5 | describe('tutorials.json', function(){
6 | it('should have valid json structure', function(){
7 | JSON.parse(file);
8 | })
9 | it('all files exists', function() {
10 | var json = JSON.parse(file);
11 |
12 | if (!Array.isArray(json)) {
13 | throw Error('tutorials.json is not an array');
14 | }
15 |
16 | for (var i = 0; i < json.length; i++) {
17 | var page = json[i];
18 | var fileContents = fs.readFileSync('./tutorials/' + page.file, 'utf8');
19 | if (!fileContents) {
20 | throw Error('file "' + page.file + '" is empty');
21 | }
22 | }
23 | })
24 | it('should not generate errors with highlightjs', function() {
25 | var json = JSON.parse(file);
26 |
27 | if (!Array.isArray(json)) {
28 | throw Error('tutorials.json is not an array');
29 | }
30 |
31 | for (var i = 0; i < json.length; i++) {
32 | var page = json[i];
33 | var fileContents = fs.readFileSync('./tutorials/' + page.file, 'utf8');
34 | if (!fileContents) {
35 | throw Error('file "' + page.file + '" is empty');
36 | }
37 |
38 | var re = /\n```.*```/g;
39 | if (re.test(fileContents)) {
40 | throw Error('code without language should not start in a new line');
41 | }
42 |
43 | var re = /```console\n/g;
44 | if (re.test(fileContents)) {
45 | throw Error('console language does not exist');
46 | }
47 | }
48 | });
49 | })
50 |
--------------------------------------------------------------------------------
/tutorials.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "slug": "microscope",
4 | "title": "OrionJS with Microscope Tutorial",
5 | "description": "A comprehensive tutorial that that goes through the end-to-end setup for Orion",
6 | "author": "fuzzybabybunny",
7 | "authorGithub": "fuzzybabybunny",
8 | "file": "microscope.md",
9 | "smallImage": "https://farm9.staticflickr.com/8447/7767672620_597a2a1a11_z.jpg",
10 | "bigImage": "https://farm9.staticflickr.com/8447/7767672620_b5e1cbd0f6_h.jpg"
11 | },
12 | {
13 | "slug": "upgrading-to-v1-0",
14 | "title": "Upgrade your Orion app to v1.0",
15 | "description": "A complete tutorial on how to upgrade your app to be compatible with Orion v1.0",
16 | "author": "Nicolás López",
17 | "authorGithub": "nicolaslopezj",
18 | "file": "upgrading-to-v1-0.md",
19 | "smallImage": "http://i.imgur.com/U7QGtra.jpg",
20 | "bigImage": "http://i.imgur.com/l43lRWL.jpg"
21 | },
22 | {
23 | "slug": "securing-orion",
24 | "title": "Security and Browser policies",
25 | "description": "For protecting your server against script injection and your clients against XSS attacks",
26 | "author": "Pierre-Eric Marchandet",
27 | "authorGithub": "PEM--",
28 | "file": "security.md",
29 | "smallImage": "http://i.imgur.com/ZKtmTLq.jpg",
30 | "bigImage": "http://i.imgur.com/bXKOYu0.jpg"
31 | },
32 | {
33 | "slug": "how-to-contribute",
34 | "title": "How to Contribute",
35 | "description": "Set up your project so you contribute code",
36 | "author": "Alen Balja",
37 | "authorGithub": "mraak",
38 | "file": "how-to-contribute.md",
39 | "smallImage": "http://currentlabel.co.uk/hammock-thumb.jpg",
40 | "bigImage": "http://currentlabel.co.uk/hammock.jpg"
41 | },
42 | {
43 | "slug": "admin-template-override",
44 | "title": "Admin template override",
45 | "description": "Add specific behavior to your admin interface",
46 | "author": "Pierre-Eric Marchandet",
47 | "authorGithub": "PEM--",
48 | "file": "admin-template-override.md",
49 | "smallImage": "http://apod.nasa.gov/apod/image/1308/arp271_gemini_2048.jpg",
50 | "bigImage": "http://apod.nasa.gov/apod/image/1308/arp271_gemini_2048.jpg"
51 | },
52 | {
53 | "slug": "logging-system",
54 | "title": "Logging system",
55 | "description": "Create your own logger or re-use Orion's one",
56 | "author": "Pierre-Eric Marchandet",
57 | "authorGithub": "PEM--",
58 | "file": "logging-system.md",
59 | "smallImage": "https://s-media-cache-ak0.pinimg.com/originals/c5/68/46/c56846d983440c26fe430727d25f76b8.jpg",
60 | "bigImage": "https://s-media-cache-ak0.pinimg.com/originals/c5/68/46/c56846d983440c26fe430727d25f76b8.jpg"
61 | }
62 | ]
63 |
--------------------------------------------------------------------------------
/tutorials/admin-template-override.md:
--------------------------------------------------------------------------------
1 | ## Admin template override
2 | ### Introduction
3 | Orion comes with default templates for all your collections. These templates are defined within the admin interface that you've chosen to import, namely **[orionjs:bootstrap](https://github.com/orionjs/orion/tree/master/packages/bootstrap)** or **[orionjs:materialize](https://github.com/orionjs/orion/tree/master/packages/materialize)** for the official ones.
4 |
5 | When you need to add some special behavior for a given collection, you can override the default template with your own one. This is made possible thanks to **[nicolaslopezj:reactive-templates](https://atmospherejs.com/nicolaslopezj/reactive-templates)**.
6 |
7 | ### Template inheritance
8 | In this tutorial, we are going to add a button for exporting a collection in a CSV file.
9 |
10 | > For conciseness, all code samples are provided either in CoffeeScript or in Jade.
11 |
12 | Our collection is named `subscribers` and we are going to extend its index.
13 | We start by creating a template file that displays the usual title, a tabular table
14 | and an additional button used for the CSV export.
15 |
16 | ```html
17 |
18 |
19 |
20 | {{#Layout template='orionCpTitle'}}
21 | {{ collection.title }}
22 | {{#if collection.canShowCreate}}
23 |
24 |
25 |
26 | {{/if}}
27 | {{/Layout}}
28 | {{#Layout template='orionCpContentContainer'}}
29 | {{#if collection.canIndex}}
30 | {{#if showTable}}
31 | {{> tabular table=collection.tabularTable class='table index-table'}}
32 | {{/if}}
33 | {{!-- This is our additional button --}}
34 |
35 | {{/if}}
36 | {{/Layout}}
37 |
38 | ```
39 |
40 | Now we set the logic in:
41 |
42 | ```js
43 | ReactiveTemplates.set('collections.subscribers.index', 'subscribersIndex');
44 |
45 | if (Meteor.isClient) {
46 | Template.subscribersIndex.onCreated(function() {
47 | appLog.info('subscribersIndex created');
48 | this.subscribersIndex_showTable = new ReactiveVar;
49 | return this.subscribersIndex_showTable.set(false);
50 | });
51 | Template.subscribersIndex.onRendered(function() {
52 | return this.autorun((function(_this) {
53 | return function() {
54 | Template.currentData();
55 | _this.subscribersIndex_showTable.set(false);
56 | return Meteor.defer(function() {
57 | return _this.subscribersIndex_showTable.set(true);
58 | });
59 | };
60 | })(this));
61 | });
62 | Template.subscribersIndex.helpers({
63 | showTable: function() {
64 | return Template.instance().subscribersIndex_showTable.get();
65 | }
66 | });
67 | Template.subscribersIndex.events({
68 | 'click tr': function(e, t) {
69 | var collection, dataTable, path, rowData;
70 | if (!$(event.target).is('td')) {
71 | return;
72 | }
73 | collection = Template.currentData().collection;
74 | dataTable = $(e.target).closest('table').DataTable();
75 | rowData = dataTable.row(e.currentTarget).data();
76 | if (rowData != null ? rowData.canShowUpdate() : void 0) {
77 | path = collection.updatePath(rowData);
78 | return RouterLayer.go(path);
79 | }
80 | },
81 | 'click button.import-csv': function(e, t) {
82 | var csvButton, subscription;
83 | csvButton = t.$('button.import-csv');
84 | csvButton.addClass('disabled');
85 | return subscription = Meteor.subscribe('allSubscribers', {
86 | onReady: function() {
87 | var csv, data, i, len, sub;
88 | data = Subscribers.find({}, {
89 | name: 1,
90 | forname: 1,
91 | _id: 0
92 | }).fetch();
93 | csv = (_.keys(data[0])).join(';');
94 | for (i = 0, len = data.length; i < len; i++) {
95 | sub = data[i];
96 | csv += '\n' + (_.values(sub)).join(';');
97 | }
98 | blobDownload(csv, 'subscribers.csv', 'text/csv');
99 | return csvButton.removeClass('disabled');
100 | },
101 | onError: function(err) {
102 | sAlert.warning('CSV subscription failed');
103 | appLog.warn('CSV subscription failed', err);
104 | return csvButton.removeClass('disabled');
105 | }
106 | });
107 | }
108 | });
109 | }
110 |
111 | if (Meteor.isServer) {
112 | Meteor.publish('allSubscribers', function() {
113 | return Subscribers.find();
114 | });
115 | }
116 | ```
117 |
118 | ### Tip
119 | When you're creating your overridden templates, you should start from copying
120 | the ones from the official provided templates in **[orionjs:bootstrap](https://github.com/orionjs/orion/tree/master/packages/bootstrap)** or **[orionjs:materialize](https://github.com/orionjs/orion/tree/master/packages/materialize)**. They provide a nice starting point for adding your extended behavior.
121 |
--------------------------------------------------------------------------------
/tutorials/how-to-contribute.md:
--------------------------------------------------------------------------------
1 | ## How to Contribute
2 |
3 | This is a simple 3 step guide how to contribute to core orion packages.
4 |
5 | ###1. Fork the orion repo
6 |
7 | Fork the repo https://github.com/orionjs/orion
8 |
9 | Create a folder _orionjs_ somewhere on your hard drive and cd into it, then clone your fork:
10 |
11 | `git clone https://github.com//orion`
12 |
13 | That's it, you have a fork that you can edit!
14 |
15 | Optionally, set _upstream_, so you can fetch from original repo into your fork whenever there are some changes there. Here is more info on how to do that:
16 | https://help.github.com/articles/syncing-a-fork/
17 |
18 |
19 | ###2. Set up a working project
20 |
21 | To see the changes you make in your fork, you need a working project based on the packages from your fork. I simply use the blog example from orion-examples.
22 |
23 | Inside your _orionjs_ folder clone the examples repo.
24 |
25 | `git clone https://github.com/orionjs/examples.git`
26 |
27 | Your folder structure should now look like this:
28 |
29 |
30 | -orionjs
31 | --orion
32 | --examples
33 | ---blog
34 |
35 |
36 | Let your blog project use your fork's packages and not the original orion packages and run the project.
37 |
38 | `cd examples/blog`
39 | `export PACKAGE_DIRS=//orionjs/orion/packages`
40 | `meteor`
41 |
42 | (You may also want to set the env var in your .bash_profile to have it always ready.)
43 |
44 | That's it! Make some changes somewhere in the packages (e.g. packages/bootstrap), and you will see them in your running blog project. Continue working, commit back to your fork, others can work on your fork too, and when you're ready to merge your work into the main project, go to step 3.
45 |
46 |
47 | ###3. Send a Pull Request when ready
48 |
49 | Here's a very good guide, no more words are necessary. Just make sure NOT to send PR into main branch, ask around what is the current working branch.
50 |
51 | https://help.github.com/articles/using-pull-requests/
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/tutorials/logging-system.md:
--------------------------------------------------------------------------------
1 | ## Logging system
2 | ### Introduction
3 | Orion provides its own logging system based on [Bunyan](https://github.com/trentm/node-bunyan).
4 | Isomorphic, it allows you to use the same API wether you want to log information
5 | on the client side or on the server side.
6 |
7 | ### Simple use
8 | Though we recommend creating your own logger to discriminate your logs from the
9 | one of Orion, you can easily use the default logger just like the [Console API](https://developer.chrome.com/devtools/docs/console-api):
10 |
11 | * **trace**: Use it for tracking intermediary state information.
12 | ```js
13 | orion.log.trace('Simple trace level message', {value: true}, false);
14 | ```
15 | * **debug**: Use it for debugging.
16 | ```js
17 | orion.log.debug('Simple debug level message', {value: true}, false);
18 | ```
19 | * **info**: Use it for tracking relevant state or status information.
20 | ```js
21 | orion.log.info('Simple info level message', {value: true}, false);
22 | ```
23 | * **warn**: Use it for warning when something goes wrong but is still properly managed by your application.
24 | ```js
25 | orion.log.warn('Simple warning level message', {value: true}, false);
26 | ```
27 | * **error**: Use it for errors when something goes wrong and you've no certainty that you can keep a proper application behavior.
28 | ```js
29 | orion.log.error('Simple error level message', {value: true}, false);
30 | ```
31 | * **fatal**: Use it for fatal errors when the situation cannot be recovered with a full reboot.
32 | ```js
33 | orion.log.fatal('Simple fatal level message', {value: true}, false);
34 | ```
35 |
36 | ### Setting the appropriate log level
37 | You can remove set the log level using [Bunyan](https://github.com/trentm/node-bunyan)'s API. Here are some useful commands:
38 | #### Remove all logs on Orion's default logger
39 | ```js
40 | orion.log.level('none');
41 | ```
42 | #### Set level for the Orion's default logger
43 | ```js
44 | // For debug level and above
45 | orion.log.level('debug');
46 | ...
47 | // For info level and above
48 | orion.log.level('info');
49 | ...
50 | // For fatal only level
51 | orion.log.level('fatal');
52 | ```
53 |
54 | ### Create your own logger
55 | We recommend that you create your own logging system to discriminate your logs
56 | from the ones of Orion so that it gets easier for you to check wether the issue
57 | is within your application or caused by a misuse or an issue of Orion.
58 |
59 | Within you application, just create a Javascript file that it sufficiently
60 | prioritized so that you logger is available as soon as possible (see [Meteor's File Load Order](http://docs.meteor.com/#/full/structuringyourapp)).
61 | A file named `lib/utils/priority/_logger.js` should be sufficiently prioritized.
62 |
63 | As well as a default logger, Orion provides its default log formatter `orion.logFormatter` that you can
64 | rely on for a simple logging strategy. Here's how to create your logger with the
65 | default formatter and a default log level set on **info**:
66 | ```js
67 | // Client and server side
68 | myAppLog = new bunyan.createLogger({
69 | name: 'myApp',
70 | stream: orion.logFormatter,
71 | level: 'info'
72 | });
73 | ```
74 |
75 | Now wether you are client side or server side, you can use your logger like so:
76 | ```js
77 | myAppLog.info('Simple info level message', myVar, 'any text', anyOtherObjectOrVar);
78 | myAppLog.warn('Simple warning level message', myVar, 'any text', anyOtherObjectOrVar);
79 | myAppLog.error('Simple error level message', myVar, 'any text', anyOtherObjectOrVar);
80 | ...
81 | ```
82 |
83 | ### Create you own formatter
84 | Log formatter are the real power of [Bunyan](https://github.com/trentm/node-bunyan).
85 | This is where you can implement all your logging strategies like:
86 |
87 | * Sending your server's logs to a [logstash](https://www.elastic.co/products/logstash) server.
88 | * Send an email when a specific log level has been executed.
89 | * Sending your client's logs and your server's logs to a SaaS
90 | (ex. [Loggly](https://www.loggly.com/), [LogEntries](https://logentries.com), ...).
91 | * Enabling logging strategy for a specific connected customer so that you can
92 | see what's this user has done within your application.
93 | * ...
94 |
95 | While being completely isomorphic in its API, the log formatter has to be written
96 | differently depending on its execution context, client or server side as we depict
97 | it afterwards. Both implementation must leverage the power of [Node's Stream](https://nodejs.org/api/stream.html).
98 |
99 | > Thanks to [Browserify](http://browserify.org/) and [Cosmo's meteor package for browserify](https://github.com/elidoran/cosmos-browserify),
100 | the [Node's Stream](https://nodejs.org/api/stream.html) is available on client side making it isomorphic as well.
101 |
102 | #### Client side log formatter
103 | For this part, we are simply using the basic [Console API](https://developer.chrome.com/devtools/docs/console-api)
104 | along with some colored styles thanks to [Log with style](https://www.npmjs.com/package/log-with-style)
105 | which is also exposed by Orion.
106 |
107 | On client side, Orion's log system exposes the following API:
108 | * `bunyan`: [Bunyan](https://github.com/trentm/node-bunyan).
109 | * `process`: [Browserified Node's process](https://www.npmjs.com/package/process).
110 | * `WritableStream`: [Browserified Node's WritableStream](https://nodejs.org/api/stream.html#stream_class_stream_writable).
111 | * `inherits`: [Browserified Node utils's inherits](https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor).
112 | * `logStyle`: [Log with style](https://www.npmjs.com/package/log-with-style) a simple set of colors and styles for [Console API](https://developer.chrome.com/devtools/docs/console-api).
113 |
114 | One of the major advantage of using [Bunyan](https://github.com/trentm/node-bunyan)
115 | is that all log informations are treated as streams of JSON objects. In this
116 | formatter example we are using this capabilities to pick the appropriate value
117 | that we want to display in your DevTools console.
118 |
119 | ```js
120 | if (Meteor.isClient) {
121 | inherits(BrowserStdout, WritableStream);
122 |
123 | function BrowserStdout() {
124 | if (!(this instanceof BrowserStdout)) {
125 | return new BrowserStdout();
126 | }
127 | WritableStream.call(this);
128 | }
129 |
130 | BrowserStdout.prototype._write = function(chunks, encoding, cb) {
131 | var output = JSON.parse(chunks.toString ? chunks.toString() : chunks);
132 | var color = '[c="color: green"]';
133 | var level = 'INFO';
134 | if (output.level > 40) {
135 | color = '[c="color: red"]';
136 | if (output.level === 60) {
137 | level = 'FATAL';
138 | } else {
139 | level = 'ERROR';
140 | }
141 | } else if (output.level === 40) {
142 | color = '[c="color: orange"]';
143 | level = 'WARNING';
144 | } else if (output.level === 20) {
145 | level = 'DEBUG';
146 | } else if (output.level === 10) {
147 | level = 'TRACE';
148 | }
149 | logStyle(color + level + '[c] ' + '[c="color: blue"]' + output.name + '[c] ' + output.msg);
150 | process.nextTick(cb);
151 | };
152 |
153 | myLogFormatter = BrowserStdout();
154 | }
155 | ```
156 | Now we have our custom log formatter client side named `myLogFormatter`.
157 |
158 | #### Server side log formatter
159 | For this part, we are using an already made formatter available in the NPM registry: [Bunyan Format](https://www.npmjs.com/package/bunyan-format).
160 |
161 | On server side, Orion's log system exposes the following API:
162 | * `bunyan`: [Bunyan](https://github.com/trentm/node-bunyan).
163 | * `bunyanFormat`: [Bunyan Format](https://www.npmjs.com/package/bunyan-format).
164 |
165 | ```js
166 | if (Meteor.isServer) {
167 | myLogFormatter = bunyanFormat({outputMode: 'short', color: true});
168 | }
169 | ```
170 |
171 | Super easy. Of course, you can go pretty far using [Node's Stream](https://nodejs.org/api/stream.html)
172 | and implement every use cases that would suit your logging strategies.
173 |
174 | #### Using our custom formatter
175 | Now that we have implemented our custom logger with the same name on the client
176 | side as well as the server side, we can go back to isomorphic Javascript and
177 | customize our logger with our shared `myLogFormatter`:
178 |
179 | ```js
180 | // Client and server side
181 | myAppLog = new bunyan.createLogger({
182 | name: 'myApp',
183 | stream: myLogFormatter
184 | });
185 | ```
186 |
187 | ### Links
188 | * Inspired from [Ongoworks's Bunyan](https://github.com/ongoworks/meteor-bunyan)
189 | * [Bunyan](https://github.com/trentm/node-bunyan)
190 | * [Bunyan Format](https://www.npmjs.com/package/bunyan-format)
191 | * [Comparison between Winston and Bunyan](https://strongloop.com/strongblog/compare-node-js-logging-winston-bunyan/)
192 | * [Log with style](https://www.npmjs.com/package/log-with-style)
193 | * [Node's Stream](https://nodejs.org/api/stream.html)
194 | * [Handbook for Node's Stream](https://github.com/substack/stream-handbook)
195 |
--------------------------------------------------------------------------------
/tutorials/microscope.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **Table of Contents**
4 |
5 | - [Meteor OrionJS with Microscope Tutorial](#meteor-orionjs-with-microscope-tutorial)
6 | - [Purpose](#purpose)
7 | - [Cloning Microscope](#cloning-microscope)
8 | - [Download OrionJS](#download-orionjs)
9 | - [Initial Impressions](#initial-impressions)
10 | - [Creating Users](#creating-users)
11 | - [Adding and Removing Roles from Users](#adding-and-removing-roles-from-users)
12 | - [Getting Roles](#getting-roles)
13 | - [Setting Roles](#setting-roles)
14 | - [Adding Collections to OrionJS](#adding-collections-to-orionjs)
15 | - [Updating Collection Documents](#updating-collection-documents)
16 | - [Schemas](#schemas)
17 | - [Adding Comments Collection](#adding-comments-collection)
18 | - [Custom Input Types (Widgets)](#custom-input-types-widgets)
19 | - [Adding Summernote](#adding-summernote)
20 | - [Orion Attributes](#orion-attributes)
21 | - [Adding Images to Amazon S3 (updated 07/28/2015)](#adding-images-to-amazon-s3-updated-07282015)
22 | - [Setting up S3](#setting-up-s3)
23 | - [Configuring OrionJS](#configuring-orionjs)
24 | - [Changing Tabular Templates (updated 7/29/2015)](#changing-tabular-templates-updated-7292015)
25 | - [orion.attributeColumn()](#orionattributecolumn)
26 | - [Custom Tabular Templates](#custom-tabular-templates)
27 | - [Template-Level Subscriptions](#template-level-subscriptions)
28 | - [Meteor Tabular Render](#meteor-tabular-render)
29 | - [Meteor Tabular with Actual Templates](#meteor-tabular-with-actual-templates)
30 | - [Dictionary (updated 7/28/2015)](#dictionary-updated-7282015)
31 | - [Relationships](#relationships)
32 | - [hasOne](#hasone)
33 | - [Chicken and the Egg](#chicken-and-the-egg)
34 | - [Correcting File Load Order](#correcting-file-load-order)
35 | - [hasMany](#hasmany)
36 | - [Multiple Relationships (updated 7/31/2015)](#multiple-relationships-updated-7312015)
37 | - [Limitations of Defining Relationships](#limitations-of-defining-relationships)
38 | - [Setting Roles and Permissions (updated 8/3/2015)](#setting-roles-and-permissions-updated-832015)
39 |
40 |
41 |
42 | # Meteor OrionJS with Microscope Tutorial #
43 |
44 | For questions, pull requests, or error submissions for this tutorial, go [here.](https://github.com/fuzzybabybunny/microscope-orionjs)
45 |
46 | ## Purpose ##
47 |
48 | I haven't been able to find a good tutorial that goes through the end-to-end setup for OrionJS, so I decided to create this tutorial both as a learning resource for others but also as a way for me to keep track of my own progress as I poke around OrionJS and figure out how to do stuff with it.
49 |
50 | There will be cursing in this tutorial.
51 |
52 | ## Cloning Microscope ##
53 |
54 | Most Meteor developers should be familiar with Microscope, the social news app which the user codes along with when following the [Discover Meteor](https://www.discovermeteor.com/) tutorial book. If you haven't read and coded along with this book, do it. Now.
55 |
56 | As of this writing, Microscope does not have a backend admin system in place to manage its data, so I thought that it would be the ideal candidate for creating a tutorial on how to get OrionJS working.
57 |
58 | First, go to https://github.com/DiscoverMeteor/Microscope and clone the repo.
59 |
60 | ```
61 | git clone git@github.com:DiscoverMeteor/Microscope.git
62 | cd Microscope
63 | meteor update
64 | ```
65 |
66 | ## Download OrionJS ##
67 |
68 | OrionJS is designed to work nicely with Bootstrap, which is perfect because Microscope does as well.
69 |
70 | ```
71 | meteor add orionjs:core fortawesome:fontawesome orionjs:bootstrap
72 | ```
73 |
74 | When you do `meteor list` you should see (versions may be different):
75 |
76 | ```
77 |
78 | accounts-password 1.1.1 Password support for accounts
79 | audit-argument-checks 1.0.3 Try to detect inadequate input sanitization
80 | fortawesome:fontawesome 4.3.0 Font Awesome (official): 500+ scalable vector icons, customizable via CSS, Retina friendly
81 | ian:accounts-ui-bootstrap-3 1.2.76 Bootstrap-styled accounts-ui with multi-language support.
82 | iron:router 1.0.9 Routing specifically designed for Meteor
83 | orionjs:bootstrap 1.2.1 A simple theme for orion
84 | orionjs:core 1.2.0 Orion
85 | sacha:spin 2.3.1 Simple spinner package for Meteor
86 | standard-app-packages 1.0.5 Moved to meteor-platform
87 | twbs:bootstrap 3.3.5 The most popular front-end framework for developing responsive, mobile first projects on the web.
88 | underscore 1.0.3 Collection of small helpers: _.map, _.each, ...
89 |
90 | ```
91 | ## Initial Impressions ##
92 |
93 | Great! Now that we've added all these packages, let's start up Microscope and see how screwed up we made everything.
94 | ```
95 | meteor
96 | ```
97 | Then point your browser to `http://localhost:3000/`
98 |
99 | Everything looks like it should:
100 |
101 | 
102 |
103 | Now let's go to `http://localhost:3000/admin`
104 |
105 | 
106 |
107 | Looks like crap.
108 |
109 | The reason this is here is because there's a hidden red alert box that is floating right. It's a style that was defined in Microscope so let's go remove it. Comment out that line of code.
110 |
111 | ```css
112 | /client/stylesheets/style.css
113 |
114 | .alert {
115 | animation: fadeOut 2700ms ease-in 0s 1 forwards;
116 | -webkit-animation: fadeOut 2700ms ease-in 0s 1 forwards;
117 | -moz-animation: fadeOut 2700ms ease-in 0s 1 forwards;
118 | width: 250px;
119 | /* float: right;*/
120 | clear: both;
121 | margin-bottom: 5px;
122 | pointer-events: auto;
123 | }
124 | ```
125 | This is going to make Microscope look crappier but I'm crap at CSS so screwit.
126 |
127 | 
128 |
129 | Better. The invisible alert box is still taking up space but I can't be screwed to do anything about it.
130 |
131 | ## Creating Users ##
132 |
133 | Looks like we need to create users and log in first before we can see the OrionJS backend.
134 |
135 | Go here and replace the original `// create two users` code with this:
136 |
137 | (Don't just copy this code and replace everything in `fixtures.js` with this since it's just partial code.)
138 |
139 | ```javascript
140 | /server/fixtures.js
141 |
142 | // Fixture data
143 | if (Posts.find().count() === 0) {
144 | var now = new Date().getTime();
145 |
146 | // create two users
147 |
148 | var sachaId = Accounts.createUser({
149 | profile: {
150 | name: 'Sacha Greif'
151 | },
152 | username: "sacha",
153 | email: "sacha@example.com",
154 | password: "123456",
155 | });
156 |
157 | var tomId = Accounts.createUser({
158 | profile: {
159 | name: 'Tom Coleman'
160 | },
161 | username: "tom",
162 | email: "tom@example.com",
163 | password: "123456",
164 | });
165 |
166 | var sacha = Meteor.users.findOne(sachaId);
167 | var tom = Meteor.users.findOne(tomId);
168 |
169 | var telescopeId = Posts.insert({
170 | title: 'Introducing Telescope',
171 | userId: sacha._id,
172 | author: sacha.profile.name,
173 | url: 'http://sachagreif.com/introducing-telescope/',
174 | submitted: new Date(now - 7 * 3600 * 1000),
175 | commentsCount: 2,
176 | upvoters: [], votes: 0
177 | });
178 |
179 | Comments.insert({
180 | postId: telescopeId,
181 | userId: tom._id,
182 | author: tom.profile.name,
183 | submitted: new Date(now - 5 * 3600 * 1000),
184 | body: 'Interesting project Sacha, can I get involved?'
185 | });
186 |
187 | ```
188 |
189 | Shut down Meteor with `Ctrl+C` and do a `meteor reset` and start it back up again with `meteor`.
190 |
191 | Now let's log in **as Sacha** and see what happens. Once logged in, click on the `Accounts` link on the left.
192 |
193 | 
194 |
195 | - Nice. Absolutely nothing shows up except the `Accounts` collection. Microscope has a `Posts` collection as well as a `Comments` collection.
196 |
197 | - ... and WTF. Tom is an `admin` but Sacha is not. We didn't even tell Meteor to make Tom an `admin` in my fixtures code, so what the hell happened?
198 |
199 | It turns out that by default, OrionJS will create a user `Role` called `admin` and if there is no `admin`, will assign the **first** user created with `Accounts.createUser` as an `admin`. Remember this so you're not creating accidental admin users in your fixtures code.
200 |
201 | I can't spell Sasha's name for crap so let's remove him as admin and have Tom as admin. Because ignoring your spelling weaknesses make them go away.
202 |
203 | ## Adding and Removing Roles from Users ##
204 |
205 | Unlike other user roles packages, the `role` of the user in `OrionJS` isn't stored on the `user` object itself. You won't find something like:
206 |
207 | ```javascript
208 | {
209 | username: 'cletus',
210 | email: 'ileik@mysister.com',
211 | roles: [
212 | 'admin',
213 | 'parent',
214 | 'varmit_hunter'
215 | ]
216 | }
217 | ```
218 | Instead, each separate `role` is stored in a `Roles` collection and the `userId` of the user is referenced along with an array containing their `roles`.
219 |
220 | ### Getting Roles###
221 | Let's screw around in the Chrome console before we do anything. While in the `Accounts` admin page, do:
222 |
223 | ```
224 | var id = Meteor.users.findOne({username: "sacha"})._id;
225 | Roles.userHasRole(id, "admin")
226 |
227 | true
228 | ```
229 | It should return `true`. We got Sesha's userId and then used that ID to check if he has the role of "admin."
230 |
231 | Likewise, each user has a `roles()` and `hasRole()` method on its object:
232 |
233 | ```
234 | // gets the currently logged in user, which should be Sassha
235 | var user = Meteor.user();
236 | user.roles();
237 |
238 | ["admin"]
239 |
240 | user.hasRole("admin")
241 |
242 | true
243 | ```
244 | So that's how you check if a user has a role you want.
245 |
246 | Currently there doesn't seem to be a method to check *which* users have a certain role.
247 |
248 | ###Setting Roles###
249 |
250 | We need to give Tom the role of `admin`.
251 |
252 | In Chrome console:
253 | ```
254 | var user = Meteor.users.findOne({username: "tom"});
255 | Roles.addUserToRoles( user._id , ["admin"] );
256 |
257 | VM11445:2 Uncaught TypeError: Roles.addUserToRoles is not a function(anonymous function)
258 | ```
259 | DUH. You can't define roles on the client. Because that's dumb.
260 |
261 | Let's make a new file called `/server/admin.js`
262 | ```javascript
263 | /server/admin.js
264 |
265 | var tom = Meteor.users.findOne({username: 'tom'});
266 | Roles.addUserToRoles( tom._id , ["admin"] );
267 | ```
268 |
269 | If we go back to the OrionJS admin console we should see that now Tom and Sache are both admins.
270 |
271 | Now we want to remove Sachet as an admin.
272 |
273 | ```javascript
274 | /server/admin.js
275 |
276 | var tom = Meteor.users.findOne({username: 'tom'});
277 | Roles.addUserToRoles( tom._id , ["admin"] );
278 |
279 | var nameIcantSpel = Meteor.users.findOne({username: 'sacha'});
280 | Roles.removeUserFromRoles( nameIcantSpel._id, ["admin"] );
281 | ```
282 | You'll notice that you can log into `OrionJS` as Sacsh, but you won't see `Accounts` on the sidebar since he's no longer an `admin`. So log in as Tom instead.
283 |
284 | And now Tom is the only admin!
285 |
286 | 
287 |
288 | Spelling weakness successfully ignored!
289 |
290 | ##Adding Collections to OrionJS##
291 |
292 | Here's where this tutorial gets less horrible.
293 |
294 | Microscope has a `Posts` and `Comments` collection, but both aren't visible yet in the admin thingy. That's because they're not `Orion` collections yet.
295 |
296 | Let's make them appear.
297 |
298 | ```javascript
299 | // This is what it used to be:
300 | // Posts = new Mongo.Collection('posts');
301 |
302 | // Instead let's do:
303 | Posts = new orion.collection('posts', {
304 | singularName: 'post', // The name of one of these items
305 | pluralName: 'posts', // The name of more than one of these items
306 | link: {
307 | // *
308 | // * The text that you want to show in the sidebar.
309 | // * The default value is the name of the collection, so
310 | // * in this case it is not necessary.
311 |
312 | title: 'Posts'
313 | },
314 | /**
315 | * Tabular settings for this collection
316 | */
317 | tabular: {
318 | // here we set which data columns we want to appear on the data table
319 | // in the CMS panel
320 | columns: [
321 | {
322 | data: "title",
323 | title: "Title"
324 | },{
325 | data: "author",
326 | title: "Author"
327 | },{
328 | data: "submitted",
329 | title: "Submitted"
330 | },
331 | ]
332 | }
333 | });
334 |
335 | Posts.allow({
336 | update: function(userId, post) { return ownsDocument(userId, post); },
337 | remove: function(userId, post) { return ownsDocument(userId, post); },
338 | });
339 |
340 | Posts.deny({
341 | update: function(userId, post, fieldNames) {
342 | // may only edit the following two fields:
343 | ```
344 | If you go back to the Microscope home page you'll see that everything appears to have remained normal. Orion collections are just extended Mongo collections.
345 |
346 | Now it looks like we got `Posts` appearing in `OrionJS`.
347 |
348 | 
349 |
350 | Reactive search works. Sorting columns is working. Pagination is working.
351 |
352 | But when we click on a table item we get:
353 |
354 | 
355 |
356 | Errors and crap.
357 |
358 | Luckily the error is pretty descriptive. This form needs either a schema or a collection.
359 |
360 | ##Updating Collection Documents##
361 |
362 | When we click on one of the table items we expect to go to an update form for that particular item. `OrionJS` uses the vastly powerful `aldeed:autoform` package to generate its forms. `aldeed:autoform` in turn uses the `aldeed:simple-schema` package to know *how* to generate its forms.
363 |
364 | ###Schemas###
365 |
366 | Schemas are these little (tee-hee) things that define how the data in your database should be. If you've got a `User` document with a `first_name` property, you'd expect the value to be a `type: String`. If having a first name is critical, you'd want it to be `optional: false`.
367 |
368 | We use schemas to keep our data consistent. MongoDB is inherently a schema-less database. It would happily allow you to screw yourself over by storing an array of booleans inside the `first_name` property of your `user` document, for instance. And then you go to access it and your wife leaves you (probably not your husband because he's clueless).
369 |
370 | So this is why people decided to make a schema package for Meteor. They love you and want happy families.
371 |
372 | Let's start by defining a schema for our `Posts` collection all the way at the very bottom of `/lib/collections/posts.js`. I'm too lazy to type so read the comments.
373 |
374 |
375 | ```javascript
376 | /lib/collections/posts.js
377 |
378 | // Rest of the code above
379 |
380 | $addToSet: {upvoters: this.userId},
381 | $inc: {votes: 1}
382 | });
383 |
384 | if (! affected)
385 | throw new Meteor.Error('invalid', "You weren't able to upvote that post");
386 | }
387 | });
388 |
389 | /**
390 | * Now we will define and attach the schema for this collection.
391 | * Orion will automatically create the corresponding form.
392 | */
393 | Posts.attachSchema(new SimpleSchema({
394 | // We use `label` to put a custom label for this form field
395 | // Otherwise it would default to `Title`
396 | // 'optional: false' means that this field is required
397 | // If it's blank, the form won't submit and you'll get a red error message
398 | // 'type' is where you can set the expected data type for the 'title' key's value
399 | title: {
400 | type: String,
401 | optional: false,
402 | label: 'Post Title'
403 | },
404 | // regEx will validate this form field according to a RegEx for a URL
405 | url: {
406 | type: String,
407 | optional: false,
408 | label: 'URL',
409 | regEx: SimpleSchema.RegEx.Url
410 | },
411 | // autoform determines other aspects for how the form is generated
412 | // In this case we're making this field hidden from view
413 | userId: {
414 | type: String,
415 | optional: false,
416 | autoform: {
417 | type: "hidden",
418 | label: false
419 | },
420 | },
421 | author: {
422 | type: String,
423 | optional: false,
424 | },
425 | // 'type: Date' means that this field is expecting a data as an entry
426 | submitted: {
427 | type: Date,
428 | optional: false,
429 | },
430 | commentsCount: {
431 | type: Number,
432 | optional: false
433 | },
434 | // 'type: [String]' means this key's value is an array of strings'
435 | upvoters: {
436 | type: [String],
437 | optional: true,
438 | autoform: {
439 | disabled: true,
440 | label: false
441 | },
442 | },
443 | votes: {
444 | type: Number,
445 | optional: true
446 | },
447 | }));
448 | ```
449 |
450 | I told you schemas are little, right?
451 |
452 | Save and try clicking on a post item again.
453 |
454 | 
455 |
456 | Ohhhhh... crap! It's almost like I... planned... things.
457 |
458 | Play around with this form and look at how the `schema` we defined directly correlates to how this form was generated.
459 |
460 | - make the `Post Title` blank and save the form
461 | - remove `http` in `URL` and save the form
462 | - click on the `Submitted` field to see how `type: Date` works
463 |
464 | ##Adding Comments Collection##
465 |
466 | How about we do the same thing to the `Comments` collection as we did to the `Posts` collection?
467 |
468 | I'll race you. Ready? Go.
469 | Done I WIN.
470 |
471 | ```javascript
472 | /lib/collections/comments.js
473 |
474 | Comments = new orion.collection('comments', {
475 | singularName: 'comment', // The name of one of these items
476 | pluralName: 'comments', // The name of more than one of these items
477 | link: {
478 | // *
479 | // * The text that you want to show in the sidebar.
480 | // * The default value is the name of the collection, so
481 | // * in this case it is not necessary.
482 |
483 | title: 'Comments'
484 | },
485 | /**
486 | * Tabular settings for this collection
487 | */
488 | tabular: {
489 | // here we set which data columns we want to appear on the data table
490 | // in the CMS panel
491 | columns: [
492 | {
493 | data: "author",
494 | title: "Author"
495 | },{
496 | data: "postId",
497 | title: "Post ID"
498 | },{
499 | data: "submitted",
500 | title: "Submitted"
501 | },
502 | ]
503 | }
504 | });
505 |
506 | Meteor.methods({
507 | commentInsert: function(commentAttributes) {
508 | check(this.userId, String);
509 | check(commentAttributes, {
510 | postId: String,
511 | body: String
512 | });
513 |
514 | var user = Meteor.user();
515 | var post = Posts.findOne(commentAttributes.postId);
516 |
517 | if (!post)
518 | throw new Meteor.Error('invalid-comment', 'You must comment on a post');
519 |
520 | comment = _.extend(commentAttributes, {
521 | userId: user._id,
522 | author: user.username,
523 | submitted: new Date()
524 | });
525 |
526 | // update the post with the number of comments
527 | Posts.update(comment.postId, {$inc: {commentsCount: 1}});
528 |
529 | // create the comment, save the id
530 | comment._id = Comments.insert(comment);
531 |
532 | // now create a notification, informing the user that there's been a comment
533 | createCommentNotification(comment);
534 |
535 | return comment._id;
536 | }
537 | });
538 |
539 | /**
540 | * Now we will define and attach the schema for that collection.
541 | * Orion will automatically create the corresponding form.
542 | */
543 | Comments.attachSchema(new SimpleSchema({
544 | postId: {
545 | type: String,
546 | optional: false,
547 | label: 'Post ID'
548 | },
549 | userId: {
550 | type: String,
551 | optional: false,
552 | label: 'User ID',
553 | },
554 | author: {
555 | type: String,
556 | optional: false,
557 | },
558 | submitted: {
559 | type: Date,
560 | optional: false,
561 | },
562 | body: {
563 | type: String,
564 | optional: false,
565 | }
566 | }));
567 | ```
568 |
569 | 
570 |
571 | 
572 |
573 | How about we do something even more CRAZY and add in a text editor for the `Body` field?
574 |
575 | ##Custom Input Types (Widgets)##
576 |
577 | First, some background.
578 |
579 | Forms have standard input types. Checkboxes. Radio buttons. Text. These are all supported out of the box by `aldeed:autoform`. But things like text editors (with buttons for selecting font type, size, color, etc) are custom, and `autoform` gives us a way to define our own widgets in the `schema` so that autoform can generate that form for us.
580 |
581 | ###Adding Summernote###
582 |
583 | First do `meteor add orionjs:summernote`
584 |
585 | Now do this to the `body` property:
586 |
587 | ```javascript
588 | /lib/collections/comments.js
589 |
590 | // now create a notification, informing the user that there's been a comment
591 | createCommentNotification(comment);
592 |
593 | return comment._id;
594 | }
595 | });
596 |
597 | /**
598 | * Now we will attach the schema for that collection.
599 | * Orion will automatically create the corresponding form.
600 | */
601 | Comments.attachSchema(new SimpleSchema({
602 | postId: {
603 | type: String,
604 | optional: false,
605 | label: 'Post ID'
606 | },
607 | userId: {
608 | type: String,
609 | optional: false,
610 | label: 'User ID',
611 | },
612 | author: {
613 | type: String,
614 | optional: false,
615 | },
616 | submitted: {
617 | type: Date,
618 | optional: false,
619 | },
620 | body: orion.attribute('summernote', {
621 | label: 'Body'
622 | }),
623 | }));
624 | ```
625 | Click on the comment by Sashi in the OrionJS admin panel to see what's up.
626 |
627 | 
628 |
629 | Niiiice.
630 |
631 | ###Orion Attributes###
632 |
633 | So what the hell is the `orion.attribute` we adding as the value for the `body` key in our schema? How about we just use our Chrome Console?
634 | ```
635 | orion.attribute('summernote', {
636 | label: 'Body'
637 | })
638 |
639 | Object {label: "Body", type: function, orionAttribute: "summernote", ...}
640 | autoform: Object
641 | label: "Body"
642 | orionAttribute: "summernote"
643 | type: function String() { [native code] }
644 | __proto__: Object
645 | ```
646 | Oh. looks like by adding the `orionjs:summernote` package we got access to this method that returns a pre-made object for us that we can conveniently use in our schema. Remember that `aldeed:autoform` uses the schema to figure out *how* to generate form items, so this attribute did all the defining-the-input-widget stuff for us.
647 |
648 | `orion.attribute('nameOfAnAttribute', optionalObjectToExtendThisAttributeWith)`
649 |
650 | I'm going to make this comment fabulous. ROYGBIV and Comic Sans the crap out of that comment, Sechie.
651 |
652 | 
653 |
654 | Save it.
655 |
656 | Remember that this comment is now in HTML, so we have to add triple spacebars `{{{ TRIPLEX #vindiesel }}}` in our `comment_item.html` to make sure Spacebars will render the HTML properly.
657 |
658 | ```html
659 | /client/templates/comments/comment_item.html
660 |
661 |
662 |
663 |
664 | {{author}}
665 | on {{submittedText}}
666 |
667 |
{{{ body }}}
668 |
669 |
670 | ```
671 | Let's survey the improvements by going to the main page and clicking on the comments for the `Introducing Telescope` post.
672 |
673 | 
674 |
675 | Beauty.
676 |
677 | ###Adding Images to Amazon S3 (updated 07/28/2015)###
678 |
679 | No comment will be complete without image spamming.
680 |
681 | Some notes:
682 |
683 | - currently, uploading images directly from Summernote doesn't work
684 | - images will be hosted through Amazon S3. I don't go over how to do it with `GridFS` or other filesystem packages.
685 |
686 | `meteor add orionjs:image-attribute orionjs:s3`
687 |
688 | ####Setting up S3####
689 |
690 | You're going to want to follow this tutorial FIRST to set up your Amazon S3:
691 |
692 | https://github.com/Lepozepo/S3/#amazon-s3-uploader
693 |
694 | Add the AWS credentials in `http://localhost:3000/admin/config` and you are ready.
695 |
696 | Now it's schema time again since we want to add a file upload section to our Comment Update form!
697 |
698 | ```javascript
699 | /lib/collections/comments.js
700 |
701 | author: {
702 | type: String,
703 | optional: false,
704 | },
705 | submitted: {
706 | type: Date,
707 | optional: false,
708 | },
709 | body: orion.attribute('summernote', {
710 | label: 'Body'
711 | }),
712 | image: orion.attribute('image', {
713 | optional: true,
714 | label: 'Comment Image'
715 | }),
716 | }));
717 | ```
718 |
719 | Go into a comment in the admin panel and upload something!
720 |
721 | 
722 |
723 | Make sure to click on the `Save` button after you're done. Also note that the moment you select an image the Amazon uploader will start. You'll see a progress bar.
724 |
725 | Finally, we should go see what it looks like on the main page, but remember to modify the `comment_item` template with the new `image` key that a `comment` document now has.
726 |
727 | ```
728 | /client/templates/comments/comment_item.html
729 |
730 |
731 |
732 |
733 | {{author}}
734 | on {{submittedText}}
735 |
736 |
{{{body}}}
737 | {{#if image }}
738 |
739 | {{/if}}
740 |
741 |
742 | ```
743 |
744 | Voila!
745 |
746 | 
747 |
748 | Sassha and Tom are going to KILL me.
749 |
750 | ##Changing Tabular Templates (updated 7/29/2015)##
751 |
752 | If we go back to our admin panel and look at comments, we see that the table is pretty dumb:
753 |
754 | `
755 |
756 | 1. The `Submitted` column contains WAY too much information. Something like Month-Day-Year-Time would look nicer. I'm going to completely ignore you people who do it the more logical way of Time-Day-Month-Year because, uh, freedom.
757 |
758 | 2. We also have the issue of the `Post ID` column being essentially stupid. I'd prefer if that column contained the title of the Post instead.
759 |
760 | 3. I also want a column that shows a short blurb of the comment's `body`, something like `Interesting project Sacha, can I...`
761 |
762 | Let's tackle #1 first. If you guessed that we need to go back into our `Schema` to change this, you just WON the JACKPOT of zero money.
763 |
764 | ###orion.attributeColumn()###
765 |
766 | We are interested in:
767 |
768 | orion.attributeColumn('createdAt', 'submitted', 'FREEDOM!!!'),
769 |
770 | ```javascript
771 | /lib/collections/comments.js
772 |
773 | Comments = new orion.collection('comments', {
774 | singularName: 'comment', // The name of one of these items
775 | pluralName: 'comments', // The name of more than one of these items
776 | link: {
777 | // *
778 | // * The text that you want to show in the sidebar.
779 | // * The default value is the name of the collection, so
780 | // * in this case it is not necessary.
781 |
782 | title: 'Comments'
783 | },
784 | /**
785 | * Tabular settings for this collection
786 | */
787 | tabular: {
788 | // here we set which data columns we want to appear on the data table
789 | // in the CMS panel
790 | columns: [
791 | {
792 | data: "author",
793 | title: "Author"
794 | },{
795 | data: "postId",
796 | title: "Post ID"
797 | },
798 | orion.attributeColumn('createdAt', 'submitted', 'FREEDOM!!!'),
799 | ]
800 | }
801 | });
802 | ```
803 |
804 | Let's check it out!
805 |
806 | 
807 |
808 | This handy-dandy method goes like this:
809 |
810 | `orion.attributeColumn('nameOfTemplate', 'keyNameOnYourObject', 'columnLabel')`
811 |
812 | Think about it. Meteor uses templates to display stuff. We had a crazy long date and we wanted to change the *look* of it, so using a template makes sense.
813 |
814 | Luckily, `OrionJS` comes with some pre-made templates. One of them happens to be called `createdAt`.
815 |
816 | `createdAt` wants a `Date` object, which happens to reside in the `submitted` key of each of your documents in the `Comments` collection. Lastly, we tell it what we want our column label to be.
817 |
818 | Now, some freedom-hating people probably want a custom template for Time-Day-Month-Year. Let's get to that next.
819 |
820 | ###Custom Tabular Templates###
821 |
822 | Now onto issue #2 - the `Post ID` column should be the title of the Post instead.
823 |
824 | Open up Chrome Console and type in `Comments.findOne()`. It found a comment, right?
825 |
826 | Now do `Posts.findOne()`. Hmmm... no post found. That's because this route isn't subscribed to anything in the `Posts` collection. If you've done Meteor before, chances are you've been using `Iron Router` to manage your data subscriptions for your routes. Unfortunately, since OrionJS is a package, it's difficult to tap into and modify the generated routes that OrionJS has already made for us. So what do we do?
827 |
828 | ####Template-Level Subscriptions####
829 |
830 | Meteor can subscribe to data in normal template callbacks (`onRendered, onCreated`). And as it turns out, OrionJS has a very standardized template naming scheme*.
831 |
832 | `collections.myCollection.index` - the main page that lists all the items in the collection
833 | `collections.myCollection.create` - the form for creating a new item in the collection
834 | `collections.myCollection.update` - the form for updating an existing item in the collection
835 | `collections.myCollection.delete` - the form/page for deleting an existing item in the collection
836 |
837 | These, by the way, are your standard pages for CRUD actions.
838 |
839 | *These aren't necessarily the actual names of the templates, but the `identifier` that OrionJS uses to find the actual template.
840 |
841 | BTW, if you want to see a list of all the route names that are registered with Iron Router, open up Chrome Console and do:
842 |
843 | ```javascript
844 | _.each(Router.routes, function(route){
845 | console.log(route.getName());
846 | });
847 | ```
848 |
849 | Here are some more identifiers: http://orionjs.org/docs/customization#overridetemplates
850 |
851 | So let's use this to subscribe to the `Posts` collection on the `comments.index` template.
852 |
853 | ```javascript
854 | /client/templates/orion/comments_index.js
855 |
856 | ReactiveTemplates.onCreated('collections.comments.index', function() {
857 |
858 | this.subscribe('posts', {sort: {submitted: -1, _id: -1}, limit: 0});
859 |
860 | });
861 | ```
862 |
863 | What this is saying is that when the template with an identifier of 'collections.comments.index' (the `Comments` index page) is created, subscribe the template to the data you specify.
864 |
865 | Now go back to the `Comments` index page and do `Posts.find().count()` to see that you've got `Posts` now!
866 |
867 | ####Meteor Tabular Render####
868 |
869 | So now that this page has the data we need, how do we actually change the contents of the cell itself?
870 |
871 | `OrionJS` uses `aldeed:meteor-tabular` to show its datatables, and it just so happens that this latter package provides a way to change the cell value: https://github.com/aldeed/meteor-tabular#example
872 |
873 | ```javascript
874 | /lib/collections/comments.js
875 |
876 | Comments = new orion.collection('comments', {
877 | singularName: 'comment', // The name of one of these items
878 | pluralName: 'comments', // The name of more than one of these items
879 | link: {
880 | title: 'Comments'
881 | },
882 | /**
883 | * Tabular settings for this collection
884 | */
885 | tabular: {
886 | // here we set which data columns we want to appear on the data table
887 | // in the CMS panel
888 | columns: [
889 | {
890 | data: "author",
891 | title: "Author"
892 | },{
893 | data: "postId",
894 | title: "Post Title",
895 | render: function (val, type, doc) {
896 | var postId = val;
897 | var postTitle = Posts.findOne(postId).title;
898 | return postTitle;
899 | }
900 | },
901 | orion.attributeColumn('createdAt', 'submitted', 'FREEDOM!!!'),
902 | ]
903 | },
904 | });
905 | ```
906 | Go play around inside this function. `console.log` `val`, `type`, and `doc` to see what they are:
907 |
908 | ```javascript
909 | {
910 | data: "postId",
911 | title: "Post Title",
912 | render: function (val, type, doc) {
913 | var postId = val;
914 | var postTitle = Posts.findOne(postId).title;
915 | return postTitle;
916 | }
917 | }
918 | ```
919 | `data: "postId"` is critical here because that's how `val` gets its value.
920 |
921 | After you're through go back and look at the `Comments` index page:
922 |
923 | 
924 |
925 | ####Meteor Tabular with Actual Templates####
926 |
927 | Finally, onto #3. We want a column that shows a short blurb of the comment `body`, something like `Interesting project Sacha, can I...`. This is called `truncating` a string.
928 |
929 | I want to create an actual template for this:
930 |
931 | ```html
932 | /client/templates/orion/comments_index_blurb_cell.html
933 |
934 |
935 | {{{ blurb }}}
936 |
937 | ```
938 |
939 | Hold on there Skippy! Truncating a straight string that's, say, 100 characters long into one that's 15 characters long with a `...` at the end is fairly straightforward. But remember that we added Summernote and we have a *fabulous* comment?
940 |
941 | 
942 |
943 | The HTML for this comment actually looks like:
944 |
945 | ```html
946 |
YousurecanTom!!!
947 | ```
948 |
949 | Sooo... we can't just do a simple truncate of this down to 15 characters. I mean, we can...
950 |
951 | ...IF WE'RE NUBZ!
952 |
953 | But `pathable` is not a nub:
954 |
955 | https://github.com/pathable/truncate
956 |
957 | Go to `/client/javascript` and literally just chuck this script's `jquery.truncate.js` file in there. Meteor will take care of minifying and loading this script automatically onto your page, as it does with *all* javascript that's not in the `/public` folder.
958 |
959 | And now we can go ahead and create a helper for our template:
960 |
961 | ```javascript
962 | /client/templates/orion/comments_index_blurb_cell.js
963 |
964 | Template.commentsIndexBlurbCell.helpers({
965 |
966 | blurb: function(){
967 | var blurb = jQuery.truncate(this.body, {
968 | length: 15
969 | });
970 | return blurb
971 | }
972 |
973 | });
974 |
975 | ```
976 |
977 | Finally, we go back to modify our `tabular` object:
978 |
979 | ```javascript
980 | /lib/comments.js
981 |
982 | Comments = new orion.collection('comments', {
983 | singularName: 'comment', // The name of one of these items
984 | pluralName: 'comments', // The name of more than one of these items
985 | link: {
986 | title: 'Comments'
987 | },
988 | /**
989 | * Tabular settings for this collection
990 | */
991 | tabular: {
992 | // here we set which data columns we want to appear on the data table
993 | // in the CMS panel
994 | columns: [
995 | {
996 | data: "author",
997 | title: "Author"
998 | },{
999 | data: "postId",
1000 | title: "Post Title",
1001 | render: function (val, type, doc) {
1002 | var postId = val;
1003 | var postTitle = Posts.findOne(postId).title;
1004 | return postTitle;
1005 | }
1006 | },{
1007 | data: "body",
1008 | title: "Comment",
1009 | tmpl: Meteor.isClient && Template.commentsIndexBlurbCell
1010 | },
1011 | orion.attributeColumn('createdAt', 'submitted', 'FREEDOM!!!'),
1012 | ]
1013 | },
1014 | });
1015 | ```
1016 | `data: "body"` subscribes us to the values for the `body` key so that it's available for our template.
1017 |
1018 | `tmpl: Meteor.isClient && Template.commentsIndexBlurbCell` looks kind of weird but remember that this code is in `/lib`, which runs on both the client and server, and `Template` isn't defined on the server. So that's why `aldeed:meteor-tabular` requires you to do this `Meteor.isClient` thing.
1019 |
1020 | WABAM!
1021 |
1022 | 
1023 |
1024 | And... done.
1025 |
1026 | ##Dictionary (updated 7/28/2015)##
1027 |
1028 | Let's go back to the Microscope main page. Say that you wanted to add a little description blurb after the word `Microscope` at the top left.
1029 |
1030 | - You not only want to add a description, but you want to be able to periodically change it as well AND you want text formatting on it AND you don't want to touch any code to change it - all you want is to change it from the OrionJS admin panel from inside of an update form.
1031 |
1032 | - And since I'm rolling right now, let's be able to change the `Microscope` word as well.
1033 |
1034 | - AND since people like to sue, let's add a terms and conditions blurb at the bottom of every `Post Submit` page that our lawyers can periodically update with worse and worse conditions for the consumer.
1035 |
1036 | Sounds like we need a collection, a schema, and a dictionary of some sort that keeps track of lolidontknowhowtoexplainjustkeepreading.
1037 |
1038 | Create a new file:
1039 | ```javascript
1040 | /lib/orion_dictionary.js
1041 |
1042 | orion.dictionary.addDefinition('title', 'mainPage', {
1043 | type: String,
1044 | label: 'Site Title',
1045 | optional: false,
1046 | min: 1,
1047 | max: 40
1048 | });
1049 |
1050 | orion.dictionary.addDefinition('description', 'mainPage',
1051 | orion.attribute('summernote', {
1052 | label: 'Site Description',
1053 | optional: true
1054 | })
1055 | );
1056 |
1057 | orion.dictionary.addDefinition('termsAndConditions', 'submitPostPage',
1058 | orion.attribute('summernote', {
1059 | label: 'Terms and Conditions',
1060 | optional: true
1061 | })
1062 | );
1063 | ```
1064 |
1065 | `orion.dictionary.addDefinition()` takes three arguments:
1066 | ```
1067 | orion.dictionary.addDefinition(
1068 | nameOfYourDictionaryItem,
1069 | categoryOfYourDictionaryItem,
1070 | schemaForYourDictionaryItem
1071 | );
1072 | ```
1073 | You'll see how this pans out in a little bit.
1074 | ```
1075 | /client/templates/includes/header.html
1076 |
1077 |
1078 |